Provider economics
What you lock up, what you earn, and what you lose when you go offline. All figures are the deployed defaults; stake, windows, and bond are owner-tunable on-chain.
Stake
Registering locks 1,000 KEP (minStake, owner-adjustable) in the ProviderRegistry. You stay listable only while registered, not deactivated, and stake ≥ minStake — top up any time with topUpStake(amount). Exiting is deliberate: deactivate, settle every active deal, then wait the 7-day UNSTAKE_DELAY before withdrawStake() returns the full amount.
Collateral
Accepting a deal locks collateral equal to 50% of the deal's total price (collateralBps = 5000) from your wallet, on top of stake. It is returned in full at settlement — or bled away by faults. Keep enough unlocked KEP for incoming deals or the accept transaction reverts.
Payment vesting
The client escrows the full deal price at creation. From activation, it vests to you linearly per second over the deal duration. withdrawEarnings pays out whatever has vested since the last claim; settleDeal (callable by anyone after endTime, provided no challenge is open) pays the remainder and releases collateral.
vested(t) = totalPrice * (t - startTime) / duration
claimable = vested(now) - paidOut
withdrawEarnings(dealId) # provider, any time while Active
settleDeal(dealId) # anyone, after endTime:
# pays remainder + returns collateralThe challenge game
Any KEP holder except the provider can challenge(dealId) an active deal, posting a 10 KEP bond (challengeBond), at most once per 12 h per deal (challengeCooldown). The challenged leaf is not chosen by the challenger: it is pinned from chain entropy — the blockhash of the challenge's commit block, hashed with the deal id and fault count, selects a pseudo-random chunk index only after the challenge is issued. If the blockhash lookback lapses before the index is pinned, rebaseChallenge restarts the window instead of slashing — a provider is never punished for a leaf it could not have learned.
You then have 8 h (proofWindow) to submitProof with the chunk bytes and a Merkle path. Success pays the bond to you. Failure lets anyone call reportFault:
collateral = totalPrice * 50% # locked at accept
slashPerFault = collateral / 3 # fixed at accept time
per fault:
challenger ← slashPerFault / 2 + 10 KEP bond
client ← slashPerFault / 2
3rd fault (termination):
client ← unvested escrow + all remaining collateral
provider ← nothing furtherEach fault slashes a third of the original collateral, split half to the challenger and half to the client. The third fault terminates the deal: the client is refunded all unvested escrow plus every remaining wei of collateral, and your unvested earnings are gone.
Gas-aware withdrawals
The daemon checks vested earnings on every maintenance pass, but claims only when it is economically sensible. A claim goes out when both hold:
claimable ≥ MIN_WITHDRAW_KEP # worth checking at all
claimable > 2 × estimated tx cost # worth spending gas on
tx cost = L2 gas × gas price
+ L1 data fee # NodeInterface.gasEstimateL1Component
# on Arbitrum Nitro chainsThe cost estimate covers L2 execution and, on Arbitrum Nitro chains, the L1 data-fee component queried from the NodeInterface precompile — so small drips are left to accumulate instead of being burned as gas. Earnings that are never individually worth claiming are still paid in full at settleDeal. Tune the floor with MIN_WITHDRAW_KEP.
Config knobs
All daemon behavior is set with environment variables (packages/provider-node/src/config.ts):
| Variable | Default | Meaning |
|---|---|---|
| PROVIDER_PRIVATE_KEY | — (required) | Provider EOA key; signs register/accept/activate/proof/withdraw transactions. |
| KEPTO_CHAIN | local | Chain key to connect to (e.g. robinhoodTestnet). |
| RPC_URL | chain default | JSON-RPC endpoint override. |
| TOKEN_ADDRESS / KEPTO_TOKEN | from deployment | KeptoToken address when no generated deployment exists for the chain. |
| REGISTRY_ADDRESS / KEPTO_REGISTRY | from deployment | ProviderRegistry address override. |
| MARKET_ADDRESS / KEPTO_MARKET | from deployment | StorageMarket address override. |
| PORT | 8747 | HTTP listen port for the /v1/* daemon API. |
| PUBLIC_URL | http://127.0.0.1:<port> | Base URL registered on-chain; must be the public HTTPS endpoint clients can reach. |
| DATA_DIR | ./provider-data | Storage root: chunks under <dataDir>/content/<merkleRoot>, manifest at <dataDir>/deals.json. |
| AUTO_REGISTER | true | Approve the stake and register in the ProviderRegistry on boot if not yet registered. |
| AUTO_ACCEPT | true | Accept incoming deals automatically when they pass policy. |
| MIN_PRICE_PER_GIB_EPOCH | 10 KEP | Auto-accept policy: minimum deal price per GiB per 30-day epoch (decimal KEP). |
| MAX_DEAL_SIZE_BYTES | 1 GiB | Auto-accept policy: largest single deal accepted. |
| CAPACITY_BYTES | 10 GiB | Advertised capacity; deals are refused when used + incoming would exceed it. |
| POLL_INTERVAL_MS | 3000 | Chain polling cadence for events and maintenance (challenges, vesting, settlement). |
| LOG_CHUNK_BLOCKS | 10000 | Maximum block range per getLogs call when catching up on events. |
| MIN_WITHDRAW_KEP | 0.5 | Minimum claimable KEP before a withdrawEarnings transaction is even considered. |
For the deploy that sets these for you, go back to the provider guide.