open, an add_collateral and a request_close carry no quote, so a person can approve each one
in a wallet. A session key removes the prompt on every trade: the trader authorises a
short-lived, scoped, revocable key once, and that key then signs locally. This page says who can
sign a trade, and documents the session key the program provides.
The instructions that carry a quote are set_take_profit, and any settle, stamp_book or
prove_price a client sends itself. A quote ages out in slots, so a person reading a wallet dialog
cannot land one of those: sign it locally. Read quotes for the age rule.
Who can sign a trade
Who signs is an integration decision, and there is more than one reasonable answer. Weigh them for your own product.Your service signs
Your own backend holds, or is delegated, the authority to sign and signs each trade
programmatically with no per-trade prompt. You choose the custody model and where the key lives.
Whatever signs holds that authority for as long as it holds the key.
A scoped delegate
The program provides a session-key delegate: the trader authorises a short-lived, scoped,
revocable key once, and that key then signs locally with no dialog. The rest of this page is its
reference.
The trader signs each
The trader approves every open, add and close request in their wallet. None of them carries a
quote, so each is a single attempt and no clock runs against the prompt. The cost is a prompt on
every trade.
Whichever you choose, the program checks the signer. The pages on opening,
orders and closes and reading gates do not assume a particular signing
model. Gas is a separate decision: see who pays the fee.
Who pays the fee
A Solana transaction has a fee payer, and the fee payer signs. That gives you a second axis, independent of who signs the trade:- The user pays their own fee. Simplest, but the user needs lamports on the key that pays. In the delegate model below that is the delegate key, not the wallet.
- You sponsor the fee. A fee payer you control is added to the transaction so the user needs no SOL. How you fund it is your choice.
The program’s option: a session-key delegate
The program provides aTradeDelegate: a short-lived, scoped, rate-limited key that the trader
authorises once and that then signs locally with no dialog. If you take this route, the human is out
of the trading path.
It never holds funds
The money stays in the trader’s own token account. The session spends against an SPL allowance
the trader grants. That allowance is the ceiling on what a leaked key can take. It is not the
ceiling on what a leak can cost: see
what a leak costs.
Its trading has a hard deadline
An absolute expiry on the
TradeDelegate, 24 hours by default, after which the key can neither
open nor close here. The SPL allowance has no clock and does not expire with it. Revoke; do
not wait.Its trading has a spend rate
A decaying budget per window on what the key may OPEN here, metered on the open’s
budget: the
fee-inclusive total, the money actually at risk. It does not meter the SPL allowance, and by
default it equals the allowance, so it is not what bounds a leak.It is revocable, with one exception
One instruction, effective in the same slot, and it returns the account’s rent.The exception: if the USDC account the allowance was granted over is frozen, closed, or has
been handed to somebody else, the program cannot clear the approval. It ends the session, and the
allowance survives and comes back the moment the account works again. Clearing it then takes its
own signature against that account.The token account is a required account on the revoke: pass
TradeDelegate.tokenAccount,
read off the chain.Using the delegate, step by step
It is five moves. The funding step is the one most often skipped: a session that authorises correctly and then cannot send anything.1
Generate a session key in the client
An ordinary keypair, created where the trades will be signed. It is not the trader’s wallet and
must never be derived from it.
session.ts
2
Authorise it — one transaction, one wallet prompt, ever
authorizeSession builds the authorisation. One instruction sets all three limits: the
expiry, the spend window, and the SPL token allowance. The program performs the SPL approve
itself, so the half the token program enforces cannot go missing.authorise.ts
Show the trader what they are signing: the expiry, the per-window ceiling and the allowance, in
dollars. This transaction is the whole of the authority they are handing over, and it is the
only moment they get to look at it.
3
Fund it — the session pays its own fee
If the owner pays the fee, the owner signs, and the wallet prompt is back on every trade. So on
this route the fee is paid by a key that is not the human: the session key itself (shown here),
or a separate sponsoring fee payer you add to the transaction (see
who pays the fee). Either way it needs lamports. The simplest shape funds the
session key in the same moment you authorise it.
fund.ts
4
Sign every trade with the session key
The session signs and pays. The book, the token account and the money all stay the owner’s:
authority is whoever signs, and delegate names the session’s account so the program can
check it.trade-with-session.ts
5
Revoke on logout
endSession closes the delegate account and drops the SPL allowance, in one instruction
signed by the trader. It takes effect in the same slot and returns the account’s rent, so
revoking costs less than nothing.revoke.ts
Revoke on logout, on wallet disconnect, and on tab close if you can reach it. Also revoke
when the trader asks: put the control somewhere they can find it, showing the expiry and the
remaining allowance.
Gate before you send
Three separate clocks can stop a session, and only two of them belong to this program. Check all three before you build a transaction. A refusal predicted costs nothing; a refusal discovered costs a fee and a confusing failure.session-gates.ts
The three clocks
renew.ts
The byte wall
A session open costs about 128 bytes more than an owner-signed one: theTradeDelegate account
(32 B), the session’s own signature (64 B) and its pubkey in the account list (32 B). An order carries
no quote, so it still fits Solana’s 1,232-byte packet with room to spare. A wrapped open names
Minute’s accounts as well as the venue’s, and an instruction that carries a quote (a keeper’s settle)
carries the quote too: those compose a v0 message with the market’s address lookup table. Measure your
real byte count with a signed transaction before you send it.
Choose the lookup table with chooseMarketTable. It
tries the market’s own table when the SDK names one, then the shared table SHARED_KIND2_TABLE, and
returns the first one that passes the SDK’s check. It never skips a market for want of a table.
Every table is frozen: nobody can change what it resolves. A new table address reaches you in an SDK
release, and chooseMarketTable picks it up. Read a table’s contents once and cache them by address
for the life of your process.
Pass as required the protocol accounts your transaction names: config and vault (from the SDK’s
configPda() and vaultPda()) and, if you read it, vault_token. Pass protocol_token
(config.treasury) as rotatable, never in required: the treasury can change after a table is
frozen, and a table that lacks it costs 31 bytes and a warning, never the table. Never pass a trader’s
or a position’s account.
lookup-table.ts
A market’s own table must contain that market and every
required account. The shared table does
not contain the markets it serves: a required account it lacks composes as a static key (+31 B).
If you check a table yourself instead of calling chooseMarketTable, call
marketTableCheckSized({ market, address, table, required, shared }) on each of
marketTableCandidates(market), and hold each
table to its own size. One minimum for every table refuses a good table.submitBenchmarkQuoted, the sender for instructions that carry a quote, refuses a lookup table on a
legacy message rather than dropping it silently. Measure your real byte count with dryRun: true:
it builds and signs exactly what would be sent, and sends nothing.Closing: a session key can do it
What a session key costs you if it leaks
The arrangement is deliberately weaker than signing each trade with the wallet. State this to yourself plainly before you ship:-
The exposure is the SPL allowance, and the allowance is spendable to any destination. A leaked
key can open positions until the allowance, the window budget or the expiry stops it. It can also
move up to the remaining allowance wherever the holder likes with one SPL
Transfer, without touching this program, so the window and the expiry do not bind that. It cannot exceed the allowance. Revoking an approval is a separate act from letting a session expire. -
It CAN close a position and CAN move a take-profit target, unless you withhold that. A leaked
key can close a trader out at a moment the attacker chooses, or move a position’s target. Size the
session for it.
Pass
restrictions: DELEGATE_NO_CLOSEtoauthorizeSessionand the session can neither close nor retarget (DelegateMayNotClose, 6156). Passing0grants close and retarget.restrictionsis required onauthorizeSessionand onrenewSession; omitting it is a build error, and with the types erased it throws. A renewal re-states the byte rather than keeping it: renew a session with0and you have granted it a Close button. Read the current value offTradeDelegate.restrictions(sessionStandingreturns it asrestrictions) and pass it in. The raw instructionsauthorize_delegateandrenew_delegatetake(duration_s, window_seconds, window_limit, allowance, restrictions). Bit 1 ofrestrictionsis accepted and does nothing. Any other bit is refused (DelegateRestrictionUnknown, 6157), so checkDELEGATE_RESTRICTIONS_KNOWNbefore you send. The owner is never restricted by this: a trader signing with their own wallet can close, unless a guardian stop is set (config.settlesDisabled, orconfig.oracleFrozen, which leaves a priced close no price). -
Revoking clears both halves.
endSessionends the session and withdraws the SPL approval in the same instruction, so revoke rather than waiting for expiry. Expiry ends the trading rights and does not withdraw the approval. If the trader’s token account was frozen or closed when you revoked, send a bare SPLrevokeagainst it once it is usable again. - Settlement proceeds cannot be redirected. A settle pays the destination recorded on the book, and no session key can change that record.
- A session key cannot authorise another delegate. That still needs the owner.
-
Size the allowance for the session, not for the year. The SDK’s default is
DEFAULT_DELEGATE_WINDOW_LIMIT, $1,000 (allowance ?? terms.windowLimit). A large allowance to save a renewal prompt trades the trader’s money for your convenience. A small one has a cost too: a trader who exhausts it meets a wallet prompt at exactly the moment the session existed to remove one. Passallowanceexplicitly. The window limit is a per-window rate ceiling and the allowance is a lifetime total: they are two different numbers. - Revoke on logout. It is one instruction, it is instant, and it refunds rent.
Tell what state a session is in, from the chain
Two accounts, and one call.1
Read the delegate PDA
delegatePda(owner) — seeds [b"delegate", owner], one per trader. decodeTradeDelegate
gives you expiresAt, delegate, windowLimit, windowSpent, windowSince, restrictions
and tokenAccount.2
Read TradeDelegate.tokenAccount, and never a guess
revokeDelegate pins its trader_token to that exact address and refuses any other with
WrongDelegateTokenAccount (6122). decodeTokenDelegate reads the SPL side of it: the
account’s state, its delegate slot, and the allowance still standing.3
Ask for the standing
sessionStanding names the combination, because the combination is what decides what to do.sessions-state.ts
A token account holds one delegate slot. SPL Token’s
approve writes it unconditionally, so
any other program the trader approves takes it from this session, with nothing emitted. The
session keeps passing every gate this program can check and fails inside the token program on the
next open. sessionStanding reports it as displaced; a renewal takes the slot back.The four pathways
- A renewal resets the spend meter and re-states
restrictions. Read the current byte offTradeDelegate.restrictionsand pass it back;0is not unchanged, it is this session may close positions. - A renewal re-approves the SPL allowance in the same instruction, so it is the fix for an exhausted or displaced approval as well as for an expiry.
- Rotating a leaked key is a renewal, not a reopen.
renewSessiontakes a differentdelegateKey, and the approval moves to it in the same instruction. endSessionis safe on a session that already expired, and safe to send twice.
The refusals a session meets
None of these appear inopenGates: the program checks them very early, before the other gates in
that report. delegateGates predicts the first four; read it.
And one that is not this program’s at all:
InsufficientFunds from the SPL Token program, when the
allowance is exhausted. sessionAllowanceGates is the only thing that predicts it.
Where to go next
Quotes and freshness
What a quote is, why a settle can be refused with
6007, and what to do about it.Opening a position
The transaction shape, the two hard walls, and building the instruction.
Reading gates
Every protocol refusal, before you send, with its measured value.
Error handling
The full catalogue, and whether retrying can ever help.
