Agon keeps user funds in two places:
- The protocol vault, which holds real SPL tokens.
- Protocol state, which tracks who is entitled to what inside that vault.
Balance accounting sits at two layers:
- Participant balance — stored in
ParticipantAccount
- Channel lock — stored in
ChannelState.locked_balance
Participant balance states
For each allowlisted token, a participant can hold:
| State | Meaning |
|---|
available_balance | Spendable inside the protocol right now |
withdrawing_balance | Amount reserved for a pending withdrawal |
withdrawal_destination + withdrawal_unlock_at | Where a pending withdrawal will go and when it becomes executable |
The first deposit for a token appears entirely in available_balance.
Channel locks are separate
Channel locks live in ChannelState.locked_balance, not in ParticipantAccount. A participant can therefore hold:
- shared available balance that may flow across several relationships, plus
- one or more channel-specific locked amounts that are ring-fenced for specific payees.
During settlement, the protocol spends channel-specific locked balance first and shared participant balance second.
Deposit flow
Depositing is a normal SPL token transfer from the user’s token account into the protocol vault, followed by a credit to protocol balance:
await program.methods
.deposit(tokenId, new anchor.BN(amount))
.accounts({
owner: owner.publicKey,
participantAccount,
ownerTokenAccount,
vaultTokenAccount,
} as any)
.signers([owner])
.rpc();
deposit_for is the batch version of the same idea. It lets one funder credit up to 16 participants in one transaction.
Withdrawal flow
Withdrawals are intentionally two-step. This protects the payee from a payer draining shared balance immediately after signing a commitment.
Step 1 — request
request_withdrawal moves funds from available_balance to withdrawing_balance, records the destination token account, and starts the timelock.
Step 2 — execute
Once the timelock has expired, execute_withdrawal_timelocked transfers the net amount to the destination token account and the withdrawal fee to the protocol fee recipient.
The withdrawal fee is the larger of:
- the configured basis-point fee, or
- a minimum fee floor scaled to the token’s decimals.
On mainnet the timelock is seven days. On devnet, testnet, and localnet the timelock is short so the flow is practical for testing. See Deployment for exact values.
Pending withdrawal is not final
One V4 behavior surprises people at first:
A pending withdrawal can still be consumed by later settlement before it is executed.
A withdrawal request is not final settlement. Until the execute step succeeds, the funds are still part of the participant’s total protocol balance. If later settlement consumes that balance:
- the withdrawal may execute with only the residual amount, or
- it may execute with zero and simply clear the pending state.
This is deliberate. “Requested for withdrawal” is not stronger than “already withdrawn.”
Cancelling a withdrawal
If the participant changes their mind before execution, cancel_withdrawal moves withdrawing_balance back to available_balance and clears the stored destination and unlock time.
Channel unlock flow
Channel locks have their own two-step flow:
request_unlock_channel_funds
execute_unlock_channel_funds
This is separate from participant withdrawals because channel locks are not the same thing as participant balances.
When a channel unlock executes, the protocol returns min(pending_unlock_amount, locked_balance) to the payer’s available_balance. If later settlement has already used part or all of the locked balance, the unlock releases only what remains.
Practical rules
If you are building a provider, client, or gateway, these are the balance rules that matter most:
- Deposits increase
available_balance.
- Channel locks reduce shared flexibility but strengthen one specific relationship.
- Pending withdrawals are not final until executed.
- Settlement can consume both shared balance and channel-specific locks — locked balance first.
- Unlock requests and signer rotations are also timelocked.
If you are considering a yield-bearing settlement asset, prefer a fixed-balance share token over a rebasing one. Agon’s internal accounting is aligned with tokens whose balances stay constant while redemption value rises over time — see Yield-bearing assets.
See also