@mysten-incubation/sui-agent-pass
v0.1.27
Published
AI builds transactions. A session signer signs. Your pass enforces the rules.
Readme
Sui Agent Pass
Your money. Your rules. AI at work.
Sui Agent Pass lets an AI agent run your onchain finance under a policy you write once. The agent pays, trades, lends and stakes; Sui checks every transaction against the policy at settlement. A policy is more than a spending limit: it names who the agent can pay, which markets it can trade on and at what price, how much lending risk it can take, which validators it can stake with, and how long its access lasts. Each new protocol plugs in as a new policy module.
An owner authorizes a new agent key with Slush. The agent builds its own programmable transactions, a local MCP signer holds the agent key on the owner's machine and signs complete transaction bytes, and the chain decides what executes. No hosted service ever holds a key.
One pass per agent, and the agent never owns anything:
- Your coins stay in your own address. Setup issues two allowances (
sui::allowance) bound to the pass, one per coin. Every agent action draws from your address balance through an allowance, is checked by the pass's policies, and returns its proceeds to your address. The pass never holds a coin. - The pass holds the rules and the objects the agent operates. A NAVI lending position is an object that can only be used under its owner's signature, so it sits in the pass where the agent can run it. Only you can take it out, and taking it out hands you the position directly.
- A receipt in your wallet points at each pass, so from any device you can find your passes, read their positions, and reclaim them. Authority stays with your address, never the receipt.
The pass is an account, not a typed container: each action names its coin. The owner installs
one policy per kind of action and coin. Transfers go to recipients the owner named; the agent
never supplies an address. Swaps run on the DeepBook and Cetus pools the owner listed, within
per-trade and daily caps and above a price floor. Lending supplies collateral to NAVI and, if
enabled, borrows against it while keeping the health factor above the owner's floor. Staking
puts SUI with the validators the owner listed (or any active one), within per-stake and daily
caps; the stake object stays in the pass and unstaking returns the SUI to the owner. An action
with no policy for its coin is refused. Revocation ends the agent's term and deletes the
allowances. The package references sui::allowance, so it needs protocol 137 or later, which
testnet, devnet and mainnet all run. The demo stays on localnet, testnet and devnet; mainnet
serves compatibility tests with small sums until launch.
The agent pays its own gas from the session address. Setup seeds an initial gas stipend and issues
a separate small SUI allowance for gas; when the session's balance runs low the agent tops it up
from that allowance, within its own lifetime cap and a limit per 24 hours from its first top-up,
so gas spend is bounded apart from the asset rules, whose daily limits reset at 00:00 UTC
(docs/ARCHITECTURE.md §7). Owners cannot bill an agent's transaction gas to the wallet
directly yet, because gas is authorized by a signature and sui::allowance cannot authorize it.
Transfers and staking are the real thing on every network. DeepBook, Cetus and NAVI are called
through their mainnet interfaces; off mainnet the demo publishes local stand-ins with the same
signatures over small mock markets, and the Move manifest lists the real packages as mainnet
replacements. See move/protocols/README.md.
This repository is fully self-contained. There is no broker, authorization database, intent adapter, or transaction builder inside MCP.
Install
The package is on npm as @mysten-incubation/sui-agent-pass (the incubation scope, for the
dogfood; the launch release will follow). A user needs no clone, no build, and no toolchain. The
client ships the per-network deployment IDs (deployments/<network>.json):
npm install -g @mysten-incubation/sui-agent-pass
agent-pass connect # connect all detected agents, or choose specific onesThat is the only command. For each agent you pick it registers the MCP server and links the
agent-pass skill, the playbook built into the package, into the agent's skills folder (Claude
Code, Codex, Cursor, Hermes and OpenClaw), so the agent has the tools and the guidance for them. A link
follows upgrades; rerun connect if you installed through npx. Then open the agent and tell
it what to do with your money. It
proposes a pass (propose_authorization) and opens the review page with the proposal in its URL.
Connect a wallet, check the rules the agent quoted to you, and sign once; the session key stays
local, and the agent sees your signature land on chain and acts within the rules. Claude Code
starts the signer when it needs it. In production the page is our site at a fixed origin, so the
agent cannot change what you see or sign; in development the same build is served from the local
port (see "The site" in docs/ARCHITECTURE.md). There is no setup wizard: the agent composes
the rules from your words and the facts the server gives it (coins, pools, balances, quotes), and
the review page is where you check them.
On a machine that reaches the internet only through a proxy (a hosted agent's VM such as Meta
Muse), the signer and the SDK send every request through HTTPS_PROXY/HTTP_PROXY. A client that
starts agent-pass mcp itself must pass those variables on: the MCP SDK's stdio transport hands
the server only a small default environment.
For a private publish:
agent-pass connect # asks which agents; or --client <name>Publishing the Move package and installing the Sui toolchain are maintainer steps, never on a user's machine. See the release checklist below.
Use it with your agent
The signer speaks MCP, so any agent that can load an MCP server can use it. The protocol is
the same for every client; where each one keeps its server list is not, so connect --client
writes or prints the entry in the right place:
| --client | What it does |
| ---------------- | ----------------------------------------------------------- |
| claude-code | claude mcp add in user scope |
| claude-desktop | Writes into claude_desktop_config.json |
| cursor | Writes into ~/.cursor/mcp.json |
| hermes | Uses hermes mcp add; prints configuration for --session |
| codex | Uses codex mcp add; prints configuration for --session |
| openclaw | Uses openclaw mcp set in the active OpenClaw profile |
| json | Prints a generic mcpServers entry |
The entry is agent-pass mcp, a stdio server the client starts when it needs it and stops with
it. It covers every pass on this machine, exposes session_info and sign_transaction, and
adds the owner tools (list_passes, propose_authorization, revoke_pass), so the agent can
also set a pass up. While it runs it also serves each pass's HTTP signer, which the SDK uses.
connect --session <directory> instead points a client at one session's foreground signer
(agent-pass serve), for agents that cannot start a process themselves.
OpenClaw needs a release with native openclaw mcp set support. Stop its gateway before
connecting (openclaw gateway stop when running as a service), then run
agent-pass connect --client openclaw. Restart OpenClaw and start a new chat session afterward.
Registration uses OpenClaw's own CLI and its reported managed skills directory, so custom
state directories and profiles stay aligned. For a named profile, use
OPENCLAW_PROFILE=<name> agent-pass connect --client openclaw; OPENCLAW_HOME and
OPENCLAW_STATE_DIR are passed through to OpenClaw. OpenClaw must run on the machine where
Agent Pass is installed and have access to a local Node runtime for SDK scripts; remote
gateways need their own installation.
Acting is a Node script against the SDK. session_info.sdk.install is the install command
(npm install <this checkout> after npm run build while the package is unpublished), then:
import { Agent, pickValidator, send, stake, swap } from '@mysten-incubation/sui-agent-pass/agent';
import { SUI, units } from '@mysten-incubation/sui-agent-pass/domain';
const agent = await Agent.open(directory); // from session_info
const usdc = agent.identity.deployment.coins.find((c) => c.symbol === 'USDC')!;
const out = await agent.submit(
swap(agent.identity, { coinIn: SUI, coinOut: usdc.type }, units('0.01', 9), units('0.019', 6)),
);
// out.success, out.reason (the owner's rule on a refusal), out.balanceChangessubmit signs through the session signer, submits to Sui and returns the persisted outcome; it
throws when nothing executed, so a dry-run refusal is never mistaken for enforcement.
docs/AGENT.md documents the SDK and the contract for hand-built PTBs. Agents
that do not speak MCP get the same two operations as plain HTTP with the bearer from
mcp.token: GET /v1/session and POST /v1/sign with { "transactionBytes": "<base64>" }.
Run the rehearsal
Clone the repository with your authorized GitHub account:
gh repo clone MystenLabs/sui-agent-pass ~/github/sui-agent-passRequires Node 22+, npm, and Sui 1.81.1 (mainnet-v1.81.1, the release mainnet runs; npm run
toolchain installs it with mvr). The project checks SUI_BIN, then
~/.cache/sui-agent-pass/toolchain/sui, then sui on PATH. Older installations are not used if
the project toolchain is present. The single Move package references sui::allowance, so it
builds and publishes only with a chain that has it enabled: localnet from this toolchain,
testnet, devnet, or mainnet (protocol 138).
cd ~/github/sui-agent-vault
npm ci
npm run toolchain
npm run build
npm run demoThe command starts its own fresh local Sui network, publishes immutable demo contracts,
creates separate owner/publisher/session identities, authorizes the session, starts MCP,
and prints an owner dashboard URL and Claude connection command. It keeps the chain and
signer alive until quit or Ctrl-C. Keys stay in ignored private directories.
Commands in the rehearsal: status, send (5 USDC to the recipient named owner), swap
(0.01 SUI on DeepBook), cetus (0.01 SUI on Cetus), supply (0.2 SUI to NAVI), borrow
(0.15 USDC from NAVI), stake (1 SUI with the localnet validator), unstake (the held
stake, back to the owner), over (11 USDC against a 10 per-transfer limit), revoke, quit.
npm run demo -- --smoke runs the sequence automatically and exits. After revocation the
allowance objects no longer exist, so a transaction that names one is refused before execution
rather than recorded as a chain failure.
The default rehearsal uses a generated test owner, not Slush. It proves chain execution and MCP signing. For the wallet connection/signature flow use the flow below.
Slush + Claude Code
Use a deployed configuration, or npm run devnet -- --owner <address>, which publishes to
devnet with a faucet-funded publisher, funds your wallet address from the faucet and writes the
deployment to .local/devnet/deployment.json. Tell the agent to pass that file's absolute path
as deployment when proposing a pass. Slush offers
mainnet, testnet and devnet only. npm run demo -- --wallet keeps a localnet alive and prints a
funded throwaway owner key for a wallet that accepts a custom RPC URL; not verified with any.
# Prepare valueless testnet contracts using a separate deployment identity (testnet is the
# default network):
npm run deploy -- create --directory .local/testnet
# If the faucet cannot provide enough funds, the command prints the deployment address.
# Fund it with at least 11 test SUI, then:
npm run deploy -- resume --directory .local/testnet
# Select a private deployment explicitly for each new pass:
node dist/cli.js connect # then ask the agent; it passes deployment: .local/testnet/deployment.json
# devnet works the same way:
npm run deploy -- create --network devnet --directory .local/devnetThe agent asks what the pass is for and proposes the rules: recipients with per-transfer and daily caps for each coin; for each trade direction the pools, per-trade and daily caps and a price floor; for each lent coin per-action and daily caps, whether borrowing is allowed and a health-factor floor; the validators it may stake with and per-stake and daily caps (a stake is at least 1 SUI, the system's minimum); a lifetime cap per coin and the gas terms. It quotes the exact rules text the page will show, generates a key and opens the review page. Connect Slush; allowances draw only from your address balance, never from coin objects, so the page reads what you hold on both sides and shows how much of your coins the signature converts into that balance to cover the caps, with a faucet link if your wallet is short. Gas for your own signature comes from either side. Sign. One PTB creates the pass and hands you its receipt, converts the coins, issues the allowances over your own balance, installs the policies, opens the pass's NAVI account when lending is allowed, and sends SUI to the session's address balance for gas. The service re-reads everything that landed on chain and compares it with what you reviewed before the signer serves the key.
Register the agents that may use your passes once with connect. It shows the detected agents
and offers to connect them all, or choose specific ones. Other / manual setup offers
undetected clients, Cursor and a generic MCP configuration; Cursor is excluded from detection
and bulk setup. You can also select a client directly with --client <name>. After connecting,
restart each connected agent and start a new chat session. The terminal shows a compact status
for each agent: green for connected, yellow for manual or incomplete setup, and red for failure.
Colors are disabled for redirected output and NO_COLOR. Use agent-pass connect --verbose
to see native setup output and configuration/skill paths. Claude Desktop receives the playbook
through MCP; optionally add the packaged skill in its settings (the path is shown with --verbose).
Then start Claude anywhere:
claudeThe registration is agent-pass mcp in Claude Code's user scope, a stdio server Claude starts
when it needs it. It contains no key and no token. If that MCP name already exists, remove the
old entry with claude mcp remove agent-pass before reconnecting.
node dist/cli.js status --session /absolute/session/directory
node dist/cli.js serve --session /absolute/session/directory # advanced: a foreground HTTP signerClosing Claude does not revoke the chain session;
the owner page's Stop the agent now button does, and Close the pass returns everything.
Your coins never left your address. If the
agent opened a NAVI position, Reclaim NAVI position on the owner page takes the account cap
into your wallet: the position is then yours to manage on NAVI directly and the agent has
nothing to lend on. Each stake the agent opened has a Reclaim button of its own, which moves
that StakedSui into your wallet. The pass also provides owner-only policy edits, documented in
the agent API guide.
Claude Code plugin
The plugin runs the whole owner flow from inside Claude Code. You describe what the agent may
do; Claude proposes rules; the plugin renders them, opens the review page, and you sign once in
Slush. After that Claude acts through the same session, with no connect step and no second
terminal.
cd ~/github/sui-agent-vault
npm ci && npm run toolchain && npm run build
# Publish with a faucet-funded key, fund your Slush address from the faucet, and write
# .local/devnet/deployment.json (the command prints its absolute path):
npm run devnet -- --owner <your Slush address>
cd ~/anywhere && claude --plugin-dir ~/github/sui-agent-vault/pluginThen tell Claude Code to use the printed deployment path for this rehearsal, and ask:
Manage 1 SUI and 100 USDC for me. Sell at most 0.1 SUI a day on DeepBook, never below 1.8 USDC. You may pay alice (0x…) up to 10 USDC a time and 20 a day. One week.
Claude calls propose_authorization, prints the rules text the tool returned, and the review
page opens. Connect Slush, check that the page shows the same rules, sign. Claude waits on
session_info until the pass is confirmed and its policies match, then works within the rules
with scripts against the SDK (@mysten-incubation/sui-agent-pass/agent), or with its own PTBs and
sign_transaction. /agent-pass:pass-status shows balances and today's
usage; /agent-pass:pass-revoke reopens the owner page for the revocation signature. The
guidance Claude follows is docs/PLAYBOOK.md, which the MCP serves to every
client as the agent-pass://playbook resource; the plugin skills only point at it.
The MCP is node dist/cli.js mcp. It keeps sessions under ~/.sui-agent-pass/sessions/ and
uses the deployments bundled with npm. Custom deployments require an explicit deployment
path on propose_authorization, or agent-pass mcp --deployment /absolute/path/deployment.json.
For development, use --root /absolute/path/development-sessions to keep sessions separate.
If a network and custom file are both selected, their networks must match.
An old ~/.sui-agent-pass/deployment.json is no longer loaded automatically. If one remains,
list_passes reports a migration message, and proposals without an explicit network or custom
deployment stop rather than silently choosing another network. Archive the old file if it is
obsolete, or select it explicitly if it is still needed. Existing sessions keep their own
deployment; upgrades can refresh addresses only within the same network and original package.
The plugin and Claude Code run as the same OS user: the session key is not hidden from Claude by the operating system, and the on-chain rules are the boundary.
The site
npm run build produces web-dist/, one static site that reads the chain directly and needs
no signer or session. #/authorize/<proposal> is the review page setup and the plugin open:
the rules come from the URL, the page builds the authorization against the package IDs it was
built with, your wallet signs, and the local signer finds the result on chain. Paste a pass id
or an owner address, or connect a wallet, and see the pass's rules, the owner's balances and
allowances, what the pass holds, and every transaction the agent sent with the chain's verdict
and the rule behind each refusal. Owner actions (revoke, reclaim a position or a stake, close
the pass) are wallet signatures from the same page. Closing deletes the pass, its policies, its
receipt and any remaining allowances in one transaction and hands back anything it still holds;
the storage deposits of everything deleted return with that transaction's gas. #/booth is the event view: the latest passes on the
package and a live feed of every call into it, refreshing every four seconds.
The pages use the Sui brand fonts, which are licensed and not in git: put the brand kit under
.local/brand/ and run npm run brand before building, or the pages fall back to system fonts.
The build bundles deployments/<network>.json; ?network=testnet picks one, otherwise the
first of mainnet, testnet, devnet, localnet that exists. #/ is the home page with the setup
steps, #/guide the longer guide, #/explorer the search and the wallet's own passes. To host it, serve web-dist/ as static
files at a fixed origin and set siteUrl in the deployment file. To run it locally against any
deployment:
npm run explorer -- --deployment .local/devnet/deployment.jsonBasecamp film and slides
Every word of the film (voiceover and on-screen text) and of the two slides lives in
presentation/copy.md. Edit it, then run npm run slides (seconds) or
npm run demo:film (about six minutes, since the voice paces the recording).
npm run demo:film records the three-minute cut on a fresh localnet and writes
scripts/film/out/basecamp-silent.mp4 (picture only, for the presenter's voiceover),
basecamp-scratch-vo.mp4 (the same cut read by a scratch voice, for pacing) and
basecamp-script.md (the voiceover with a timecode per line). The voice paces the recording, and quiet waits on the agent are cut. On a
published network: npm run film:record -- --deployment <deployment.json> --funder <key file>,
then npm run film:edit. There the owner signs in the real Slush extension, copied from the
local Chrome profile (or SLUSH_EXTENSION) into a throwaway browser profile with a fresh key;
the stage shows Slush's own screens as a docked panel, and what the key has left goes back to
the funder afterwards. Localnet keeps the page's dev wallet, since Slush has no localnet.
node --import tsx scripts/slides/render.ts renders the two concept
slides to .local/basecamp/. Both need the brand kit (npm run brand) and Codex on the PATH.
Drafts use the free edge-tts voice. npm run film:final (with ELEVENLABS_API_KEY set) reads the
lines in the account's ElevenLabs voice (ELEVENLABS_VOICE, default Tech); clips are cached
under .local/tts-cache/, so an unchanged line costs no credits on a re-recording.
Changing the voice or the words
The voice paces the recording, so a new voice or a new line means a new recording. To re-record the testnet cut as it was made for Basecamp, with the owner signing in Slush:
Have Codex (
codex) on the PATH and Slush installed in Chrome, thennpm install,npm run brandandnpx playwright install chromium.Put a funded testnet key (the
suiprivkey…string, a few SUI; each run uses about 2) in a file, for example.local/testnet/funder.key. Get testnet SUI at https://faucet.sui.io.Change the words in
presentation/copy.mdif you want to.[tone]tags and...shape the ElevenLabs reading; the free draft voice ignores them.Record with your own ElevenLabs voice:
export ELEVENLABS_API_KEY=… ELEVENLABS_VOICE="Your voice name" npm run film:record -- --final --deployment deployments/testnet.json --funder .local/testnet/funder.key npm run film:editLeave out
--finalfor a free draft first. The cut lands inscripts/film/out/:basecamp-scratch-vo.mp4is the voiced film andbasecamp-silent.mp4the same picture with no sound, for recording a human voiceover over it withbasecamp-script.mdas the timecoded script.
Show it to someone
- English demo script: pitch, exact commands, Claude prompts, expected results.
- Chinese presenter instructions: operator notes with English prompts and narration.
- Agent transaction guide: complete interface and agent-side examples.
- Architecture and boundaries.
- Status: implemented and todo.
- Verification record: dated runs with their digests. The test scripts
write their reports and screenshots to
.local/evidence/, which stays out of git.
Checks
npm run check
npm test
npm run test:move
npm run build
AGENT_PASS_BROWSER_TEST=1 npm run test:integration
# Optional live model check, using fresh localnet funds and your Claude login:
npm run test:claudeBrowser verification uses installed Google Chrome and a wallet-standard test wallet. It runs with a Chinese browser locale to verify the demo still displays English dates and copy. It is not evidence of a live Slush extension signature. Integration tests submit signed invalid transactions and inspect persisted failures; ordinary SDK simulations are not counted as chain refusals. Each test owns and stops its localnet process.
Release checklist
Publishing is a maintainer step, done once per network and once per npm release. None of it runs on a user's machine.
Publish the contracts to a network:
npm run toolchainandnpm run build.npm run deploy -- create --network testnet --directory .local/testnet. Testnet is the default. Mainnet needs a manually funded publisher (no faucet):create --network mainnetprints the address,resumepublishes once it holds 2 SUI.- Commit the resulting
deployment.jsonhere asdeployments/<network>.json.
Publish the npm client:
- Confirm
npm run check,npm test,npm run test:move,npm run build, andAGENT_PASS_BROWSER_TEST=1 npm run test:integrationpass. npm pack --dry-runto confirm the tarball holdsdist/,web-dist/anddeployments/.- Finalize the package name and version, remove
"private": true, thennpm publish;prepublishOnlyrebuilds first.
Scope
Demo only, localnet/testnet/devnet, SUI and a test USDC that the demo faucet mints (not
Circle's USDC). Off mainnet DeepBook, Cetus and NAVI are exercised through local stand-ins that
keep the mainnet function signatures over constant-product pools and a principal-only lending
market. The package also builds against the real protocols (sui move build -e mainnet) and
npm run deploy -- create --network mainnet publishes it there with Circle's USDC and the real
pools, for compatibility tests; no mainnet publication is claimed yet. The demo packages are
made immutable when published; the mainnet package keeps its upgrade cap.
Policies. Each rule set is one on-chain policy object for one coin type, installed by the
owner and metered per UTC calendar day. Transfers reach only named recipients. Swaps run only
on listed pools, within input caps, above a static price floor and the agent's own min_out;
proceeds return to the owner's address. Lending pins the NAVI pool and asset index, caps each
action, and refuses a withdraw or borrow that would leave the position below the owner's
health-factor floor; the floor sits on the collateral policy too, so a withdraw cannot breach
it. Positions belong to the pass's own NAVI account cap, never to the agent. Owners edit
policies by removing and reinstalling them, which keeps the meters.
Allowances. sui::allowance draws from address balances only, so setup moves the chosen
coins from the owner's coin objects into the owner's own address balance. Anything the owner
spends from that balance later simply reduces what the agent can withdraw; a spend above the
live balance is refused at signing. A session's allowances, the two asset allowances and the
separate SUI gas allowance, share one expiry and are deleted together on revocation. The pass
accepts only allowances funded by its owner for its agent, and only it can spend them. A
repayment to NAVI is charged to the allowance in full before NAVI runs, so an over-payment
returns to the owner but still counts against the allowance; the agent should size repayments
from the live debt.
Staking. Real on every network. The policy lists validators (empty means any active one)
and caps each stake and the day. The StakedSui the agent opens is held by the pass; the
agent may unstake it, which sends principal and rewards to the owner's address, and the owner
may reclaim it as an object. The system's own 1 SUI minimum per stake applies.
Objects. The pass holds objects under typed keys: a protocol account under its adapter's
key, a stake under its ID, anything else under its own ID. Only the owner deposits or withdraws
them. Withdrawing the NAVI account cap moves the position with it, into the owner's wallet,
where NAVI's own UI shows it; withdrawing a stake makes it an ordinary StakedSui there. An
emptied pass (no policies, no objects) can be destroyed by the owner.
The gas stipend belongs to the session address and is freely spendable. A local service and Claude running under the same OS user are not a private-key isolation boundary: the chain rules survive key theft, while key confidentiality needs a separate identity or container. The private owner/publisher fixture keys never enter the signing service.
