AI agents
coming soon. you will be able to let your AI trade. it never touches your money.
the on chain part is already in the program. the MCP server is a devnet preview. the app does not offer AI connect yet.
the agent key
a wallet can hand a key to an AI agent without handing over the account. the key is separate from the browser session key and carries hard caps.
register_agent_key is owner only:
| argument | rule |
|---|---|
key | a public key. it must differ from the owner and from the session key |
expiry | in the future and at most 30 days away |
max_margin | the most margin one trade may lock. above 0 |
max_leverage | 1 to 10,000 |
budget | the most margin the agent may lock in total. above 0 |
the zero key revokes the agent and zeroes the caps. every call, register or revoke, adds 1 to agent_gen and emits AgentKeyChanged.
what an agent can do
| action | limit |
|---|---|
request_open, request_dust_round, place_limit | margin ≤ max_margin, leverage ≤ max_leverage, margin ≤ the budget left. the budget goes down by the margin |
request_close | only positions the agent opened. a close uses no budget |
set_trigger, cancel_order | only on positions and orders the agent opened |
an expired key fails with BadAgent. the caps fail with AgentMarginCap, AgentLeverageCap and AgentBudget. closing something the agent did not open fails with AgentScope.
what an agent can never do
withdraw, deposit, stake, unstake, claim, claim referral USDC, name a referrer, register or change keys, and cancel a request. those instructions take the owner as the signer, and the agent key is accepted only by the request and order instructions above.
the budget
- it is cumulative margin. each open, round or limit order spends its margin from it
- it is not refilled when a trade closes. only the owner refills it, by registering again
- it is credited back when a request ends without a trade: a void, a cancel, a retire before the fill, an expired order. and only for the registration that spent it, so a revoke and a new registration never inflates the new budget
so under one registration the agent can lock budget of margin in total. a trade loses at most its margin. that bounds the loss.
things to know
- the per wallet limits still apply to an agent: live DUST MODE margin, DUST MODE rate, concurrent Core notional.
- revoking stops new requests at once. it does not close open positions.
- the scope check is
by_agent, not the key. a later agent key can close or change the triggers of the positions an earlier agent opened. revoke the key to stop an agent. - the only way an agent key can move value to someone else is an execution bounty to a cooperating executor, at most 0.1% of margin, and the money stays inside the program.
the AI tag
every position and request opened by an agent key stores by_agent = true. these events carry the flag: OpenRequested, Closed, Dusted, DustRequested, DustSettled, TriggerSet, TriggerExecuted, LimitPlaced, LimitCancelRequested, LimitExpired and ReferralEarned.
the app shows the AI tag on every agent trade. an agent trade is otherwise treated exactly like any other.
no speed edge
an agent gets the same price lock as you: the tick about two seconds after the request. there is no faster lane. see pricing oracle.
the MCP server (devnet preview)
dust-mcp is a Model Context Protocol server. an AI agent trades through it with a scoped agent key. every cap is enforced on chain, not by the server.
any MCP client ==stdio==> dust-mcp ==(agent key, Pyth Pro key, RPC)==> Solanait holds only the agent keypair. it never sees the owner's key.
the 11 tools
| tool | does | writes |
|---|---|---|
get_markets | assets, max leverage, open interest against caps, the exchange rules, the per wallet limits, the DUST MODE cells that are on | no |
get_price(asset) | the latest signed price with confidence and publisher count. indicative only: trades fill about 2 s later | no |
get_account | free balance, budget left, caps, key expiry, fee payer SOL, wallet limit usage, and a list of blockers | no |
get_my_positions | open and pending positions with entry, bust price, live PnL, triggers, resting orders and pending rounds | no |
get_recent_trades(limit?, asset?) | history from chain events, with by_agent | no |
open(asset, side, margin, leverage) | checks the caps client side, sends one bundled transaction, waits for the fill | yes |
close(position_id) | the same for a position this agent opened | yes |
place_limit(...) | rests a limit order. answers resting at once | yes |
set_trigger(position_id, kind, price_usd) | sets, changes or removes a take profit or stop loss on something this agent created | yes |
cancel_order(position_id) | cancels a resting order this agent placed and waits for the refund | yes |
dust_round(asset, side, margin, leverage, horizon_s) | plays one DUST MODE round if the cell is on | yes |
side is MOON for long and DUST for short. margin is in USDC with at most 6 decimals.
not in the tool list, by construction: withdraw, deposit, stake, unstake, claim, referral, cancel of a request, any key registration or revocation, any admin act, any transfer, and fill_order, execute_trigger and liquidate. no tool takes an address, a destination or a key. a test checks the exact tool list over the real protocol and checks every instruction the server ever sends.
set it up
- make the agent key on the machine that runs the server, with file mode 600. fund its address with about 0.05 SOL on devnet. it pays fees and rent.
- the owner registers the public key from their own wallet. the server cannot do this. build the instruction with the SDK's
registerAgentKeyIxand have the wallet sign it. - build and run. Node 22 or newer:
cd mcp && npm install && npm run build. - add it to your MCP client with one entry:
json
"dust": {
"command": "node",
"args": ["/abs/path/mcp/dist/index.js"],
"env": {
"DUST_AGENT_KEYPAIR": "/path/to/agent.json",
"DUST_OWNER": "<OWNER_PUBKEY>",
"PYTH_PRO_API_KEY": "<KEY>"
}
}the Pyth Pro key stays on this machine. it is never sent to the model or to any frontend. start with DUST_DRY_RUN=1 to see what would be sent.
settings
these are environment variables:
| variable | default | meaning |
|---|---|---|
DUST_AGENT_KEYPAIR | required | path to the agent keypair |
DUST_OWNER | required | the public key of the account owner |
DUST_RPC_URL | public devnet | the RPC. treat it as a secret |
PYTH_PRO_API_KEY | none | without it, price tools answer PYTH_KEY_MISSING |
DUST_RELAYER_KEYPAIR | the agent key | an optional separate fee and rent payer |
DUST_DRY_RUN | 0 | 1 simulates every write |
DUST_SETTLE_MODE | auto | auto waits for keepers, then settles its own request. wait never settles. self settles at once |
DUST_SETTLE_TIMEOUT_S | 40 | how long a write tool waits |
DUST_MAX_MARGIN_USDC, DUST_MAX_LEVERAGE | none | extra local caps, never above the on chain ones |
DUST_ALLOW_MAINNET | 0 | writes are refused on a mainnet RPC |
how a write works
- the server reads the exchange, the instruments, the vault and the clock, then checks the request against the same rules the program applies. a refusal sends nothing and costs nothing, and the message names the cap and the numbers.
- the SDK builds
[ed25519, compute budget, touch_oracle(latest tick), request_*]. the agent key signs as authority. - the server waits for the request account to disappear. in
automode it settles its own request after the keeper grace if nobody else has. settling is permissionless. - the result is read back from chain state and events, not from what the tool intended.
the security model
- the caps are on chain. a compromised host or a prompt injected model is bounded by
budget,max_margin,max_leverageandexpiry. it cannot withdraw, stake, claim, cancel a request or change keys. - tool outputs are numbers and fixed strings. the only chain text is the instrument symbol, reduced to
AtoZand0to9. tool results are never instructions. - secrets. config comes from the environment only. every error and log line passes a redactor that strips the configured secrets, any URL and key shaped strings.
- mainnet guard. writes are refused when the RPC is mainnet.
- no repeat by accident. if your client times out, do not repeat the call blindly.
get_my_positionsshows whether the request landed. the budget bounds the damage of a duplicate.
hosted mode (devnet beta)
one process can serve many players' AI connections over Streamable HTTP. a token in the URL maps to exactly one agent key and one owner.
- the agent keypair is generated and kept on the server. the player's owner wallet registers its public key on chain, then confirms. the server activates a connection only after it reads that registration from the chain.
- a request reaches exactly one agent. no request field names an owner, an account or a key.
- the token is 32 random bytes, shown once and stored as a hash.
- tool calls, requests and new connections are rate limited, per token, per owner and per client address.
- devnet only. the process will not start on a mainnet RPC.
hosted mode is a preview. it is not switched on in the app.
a note on orders
the MCP never presents a fill or a trigger crossing tick. an order rests and a trigger waits until a keeper presents the first crossing tick. an agent never presents one with hindsight.
