For AI agents and bots

Give your agent a wallet, not a blank cheque.

Each swap is checked against your limits, on your own RPC, before the agent signs.

  • Signs only what it asked forAn extra instruction, another token or a worse price, and the agent does not sign.
  • Your limitsPer swap, per day and which tokens it may buy, set by the owner.
  • Safe after a crashEach order is recorded and settled after a restart.
  • No sign-upThe wallet signs a message and gets a key.

Overview

Your agent decides what to trade. Orientim builds the swap, your wallet signs it, and if less than your minimum would arrive, the transaction reverts. Pick a way in:

UseFor
Solana Agent KitAgents built on Solana Agent Kit.
Agent skillCoding agents such as Claude Code.
Command lineBots in Python, Rust, Go or any language.
MCP serverClaude Desktop, Cursor and other assistants.
APIYour own client. It must run the check before signing and keep its own records.

All but the API check, record and recover each swap for you. You need:

  • An API key for the wallet (the MCP server gets its own).
  • Your own RPC, and your own Jupiter API key (free).
  • A wallet that signs and hands the transaction back: a key file or a signing service.
  • Node 20.18 or later.

Quickstart

On Claude Desktop, open orientim.mcpb and answer its questions (MCP server). Otherwise:

  1. Get an API key for the wallet: API keys.
  2. Install @orientimhq/swap v2.1.0 (source, Apache-2.0).
    npm install --save-exact @orientimhq/swap@2.1.0
  3. Check it was built from the GitHub source.
    npm audit signatures
  4. Configure (all settings: guide).
    export ORIENTIM_API_URL=https://orientim.com
    export ORIENTIM_API_KEY=ori_...
    export SOLANA_RPC_URL=<your own RPC>
    export JUPITER_API_KEY=<your Jupiter key>
    export ORIENTIM_POLICY=/home/agent/orientim-policy.json
  5. Set the owner's limits in a file the agent cannot edit: 50 USDC a swap, 200 a day, only into SOL. A daily limit needs a stateDir.
    {
      "maxAmountIn": { "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": "50000000" },
      "maxAmountInPerDay": { "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": "200000000" },
      "allowedOutputMints": ["So11111111111111111111111111111111111111112"],
      "stateDir": "/home/agent/.orientim-state"
    }
  6. Try it without signing.
    npx orientim swap --in EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v \
      --out So11111111111111111111111111111111111111112 --amount 5000000 \
      --owner <wallet> --dry-run
  7. Swap, with the same order id on every retry.
    export ORIENTIM_WALLET_KEYPAIR=/path/to/wallet.json
    npx orientim swap --in EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v \
      --out So11111111111111111111111111111111111111112 --amount 5000000 --id order-1
  8. Make restarts safe: keep the state directory on a lasting disk, run recover on every start, and count only confirmed as done (Results and recovery).

API keys

The wallet signs a message and gets a key for itself. Signing moves nothing. The wallet needs 0.01 SOL; a key lasts 90 days.

A key file or a signing service (Turnkey, Privy). The key never leaves your machine.

export ORIENTIM_API_URL=https://orientim.com
echo '{"wallet": "<address>"}' | npx orientim key-challenge
# sign the bytes of "message" with the wallet, then send "message" unchanged:
echo '{"message": "...", "challenge": "...", "signature": "<base58>"}' | npx orientim key

In code: requestApiKey, which signs only Orientim's key message. Over HTTP: Keys.

Solana Agent Kit

Adds one action, ORIENTIM_PROTECTED_SWAP, to the kit. It uses the kit's wallet and RPC.

npm install --save-exact @orientimhq/solana-agent-kit @orientimhq/swap@2.1.0

Not on npm yet: build it from its source.

  • amount is in whole tokens, as text ("0.5").
  • A policy is required. The same orderId never swaps twice.
  • Slippage is capped at 3% unless the policy raises it. signOnly kits are not supported.

AI agents: the skill

For coding agents such as Claude Code. @orientimhq/swap holds the skill, the orientim command and the library.

npm install --save-exact @orientimhq/swap@2.1.0
cp -r node_modules/@orientimhq/swap/skills/orientim-protected-swap .claude/skills/

Other agents: point them to SKILL.md (GitHub). Settings: Quickstart.

  • Use the same order id on every retry. An order never swaps twice.
  • In your own code, pass policy and spends, or the owner's limits do not apply.
  • Signing service: pass signerFromSignTransaction (Turnkey, Privy) or signerFromSignBytes (KMS, HSM) as wallet.

Production setup

  • Keep the key out of the agent's reach: a signing service, a KMS or an HSM.
  • Put the policy at /etc/orientim/policy.json (C:\ProgramData\Orientim\policy.json on Windows), read-only to the agent.
  • Fund the wallet with only what the agent may swap.

Bots: the command line

For bots in any language: JSON on stdin, JSON on stdout, and an exit code. Your bot signs one message.

npm install --save-exact @orientimhq/swap@2.1.0
  • Branch on error.code. untrustedServerMessage is for logs only.
  • Rust: keypair.sign_message(&message).to_string().
  • resolve settles a swap by hand; key-challenge and key get an API key.

Assistants: the MCP server

For Claude Desktop, Claude Code, Cursor and any client that runs a local MCP server. It gets its own API key.

gh attestation verify orientim.mcpb -R orientimhq/orientim-protected-swap

Download orientim.mcpb (v2.1.0), verify it and open it. It asks for your RPC and keypair file.

ToolWhat it does
orientim_walletAddress and balances.
orientim_quotePrices and verifies a swap. Nothing is signed.
orientim_swapSwaps the quote you approved, within 10 minutes.
orientim_recoverSettles an interrupted swap.
  • Your client asks you before each swap, when it supports it.
  • Caps: 3% slippage and 5% price impact. Raise them with ORIENTIM_MAX_SLIPPAGE_BPS and ORIENTIM_MAX_PRICE_IMPACT_BPS.
  • It runs locally, next to the key. Remote-only assistants such as ChatGPT cannot use it.

How a protected swap works

  1. Prepare: Orientim builds the transaction and returns it unsigned, with a ticket.
  2. Verify: your agent checks the exact bytes on its own RPC.
  3. Sign: your wallet signs. Orientim never holds your key.
  4. Finalize: Orientim adds its one-time signature and sends it.

A transaction lives about 40 seconds. With fewer than 30 blocks left, prepare again.

Verify before you sign

With the check, even a compromised server cannot make your wallet sign more than you approved, or below your own floor. Without it, you trust Orientim's server with your wallet.

checkPrepared(prepared, intent, yourRpc) returns the problems it finds. Sign only when there are none. The skill and the command line run it for you. It checks:

  • The fee, the network fee and your minimum.
  • The exact bytes, simulated on your own RPC.
  • Rent kept by the route: at most 0.001 SOL (maxRouteCostLamports).

Your minimum must come from your own price, never Orientim's; ownMinimum gets one from Jupiter.

Any language: pipe {"prepared": ..., "intent": {...}} into npx orientim check. Exit 0 means safe to sign.

Your own Jupiter key

With JUPITER_API_KEY set, the skill fetches routes with your key, which never leaves your process. ORIENTIM_OWN_ROUTES=0 (ownRoutes: false in code) uses Orientim's key instead.

Each round counts as one prepare toward the rate limit. Direct API clients: the rounds are in reference/AGENT-API.md.

Results and recovery

The skill and the command line read the result on chain and return an outcome: confirmed, failed, expired, rejected, or unknown (check again before anything new).

For a direct API client

Your wallet's signature is the transaction id. Store it with the ticket before you call finalize; it is how you find out what happened.

statusMeaning
sentAccepted. Confirm on chain; re-send signedTransaction until it confirms or expires. It lands only once.
unknownThe answer was lost. Check the signature first.
signedWith "send": false: signed, not sent. Send it yourself.
rejectedNot sent, usually because the price moved.

Only a swap confirmed on chain is done. sent and unknown mean it may still land.

Orientim keeps no order database. Your store and the chain are the record. The skill keeps that store for you.

  • Finalizing the same ticket again is safe: the transaction lands only once.
  • Two prepares are two transactions. Use one order id per decision and one swap per wallet at a time.
  • Prepare the same swap again only once the signed one can no longer land.

On every start, for a direct API client

  1. Load every signed swap not yet settled.
  2. Look up each signature on your RPC and record confirmed or failed.
  3. Not found: re-send the same bytes, and mark it expired only once the network is past its last valid block.
  4. Only then prepare anything new for that wallet.

Authentication

Base URL https://orientim.com. Every request carries an API key:

Authorization: Bearer ori_...

A key prepares swaps for its own wallet only.

Prepare

POST /api/v1/prepare
Authorization: Bearer ori_...
Content-Type: application/json

{
  "owner": "<your wallet address>",
  "inputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  "outputMint": "So11111111111111111111111111111111111111112",
  "amountIn": "5000000",
  "minOut": "42400000",
  "slippageBps": 100
}
FieldMeaning
ownerrequiredThe wallet that pays, receives and signs first.
inputMint, outputMintrequiredMint addresses. SOL is So11111111111111111111111111111111111111112.
amountInrequiredBase units, as a string ("5000000" is 5 USDC).
minOutrequiredThe least your wallet must receive, in base units, from a price you got yourself.
slippageBpsoptional10 to 1500. Default 50, or 300 on a Pump.fun curve.
acceptCostBpsoptionalAccept a protected route this many bps below the market (costs-more). minOut still holds.
ownRoutes, session, routesoptionalRoutes fetched with your own Jupiter key. See Your own Jupiter key.

Answer

All amounts are strings in base units.

FieldMeaning
ticketPass it to finalize.
transactionThe unsigned transaction, base64. Verify it, then sign.
messageSha256The hash of the transaction message.
temporaryAuthorityThe swap’s one-time key.
lastValidBlockHeight, blocksLeftThe lifetime: 150 blocks, about 40 seconds.
amountsamountIn, fee, feeMint, feeBps, quotedOut, minOut, priceImpactPct (null means unknown).
costsNetwork fee, rent, Orientim’s fee, and keptSolLamports: all the SOL the swap does not return.
notices, tokensA busy network, and whether a token’s issuer can freeze, mint or move balances.
certificate, policyWhat the transaction does, and the rules it was checked against.

A prepare answers within 45 seconds, or 503 unavailable. Bodies may be up to 1 MiB.

Finalize

POST /api/v1/finalize
Authorization: Bearer ori_...
Content-Type: application/json

{ "ticket": "eyJ2Ijox....", "signedTransaction": "<base64, signed by your wallet>" }

Orientim signs and sends. The same ticket again returns the same transaction.

{
  "signature": "5h...",
  "status": "sent",
  "signedTransaction": "<base64, fully signed>",
  "lastValidBlockHeight": "312345678"
}

status: see Results and recovery. To send it yourself (staked RPC, bundle), add "send": false and re-send until it confirms or expires.

Keys

GET /api/v1/keys/challenge?wallet=<address>
→ { "message": "...", "challenge": "...", "expiresAt": "..." }

POST /api/v1/keys
{ "message": "<the message, unchanged>", "challenge": "...", "signature": "<base58 or base64>" }
→ { "key": "ori_...", "wallet": "<address>", "expiresAt": "..." }
  • Sign the challenge within 10 minutes.
  • Sign only Orientim's key message. Other bytes could be a transaction.
  • Keep your key; do not request a new one on every start.

Errors

Every error is { "error": { "code": "...", "message": "..." } }, and nothing was signed or sent. requiresApproval: true means ask the user, do not retry.

HTTPcodeWhat to do
400bad-requestFix the field named in message.
400invalid-ticketThe ticket is not this key’s, or was altered.
400transaction-changedSign the transaction exactly as returned.
400bad-signatureKeys: the signature or challenge is not valid. Ask for a new challenge.
400wallet-changed-transactionYour wallet’s signature is missing or wrong.
400bad-sessionownRoutes: the session expired or is another swap’s. Prepare again without it.
401unauthorizedThe API key is missing, expired or revoked. Get a new one.
403wrong-walletA key prepares swaps for its own wallet only.
403wallet-emptyKeys: the wallet holds less than 0.01 SOL.
404not-enabledThe agent API is not available.
409price-movedThe market cannot meet minOut. Retry with newMinOut only if the user approves.
409costs-moreThe protected route is gapBps below the market. Retry with acceptCostBps only if the user approves.
409routes-neededownRoutes: fetch the routes with your Jupiter key and prepare again. The skill does this.
409session-usedownRoutes: this session already signed a transaction. Check its signature first.
409output-balance-changedYour output balance moved since prepare. Check the signature, then prepare again.
409output-in-flightAnother swap into the same account has not landed yet. Finalize again after Retry-After.
410expiredThe transaction expired. Check the signature, then prepare again.
422amount-too-smallBelow about 0.004 SOL or $1. Selling a token’s whole balance is allowed at any size.
422unsupported-token, no-route, insufficient-sol, insufficient-balance, simulation-failed, …This swap cannot be built safely now; message says why.
426skill-outdatedUpdate the skill. A swap already signed still finalizes.
429rate-limitedToo many requests. Wait Retry-After seconds.
500internalNothing was signed or sent. Retry later.
503busy, unavailableAt capacity, or a provider is slow. Retry after Retry-After.
503fee-unavailableNothing was built. Retry later.
503pausedSwaps and new keys are paused. Your funds are not affected.
503route-formatSwaps are briefly unavailable. Wait at least Retry-After.

Rate limits

  • Per wallet: 60 prepare and 30 finalize a minute.
  • Per IP address: 120 requests a minute.
  • Keys: 30 challenges and 10 keys an hour per IP, 5 keys a day per wallet.
  • A 429 carries Retry-After.
  • GET /api/health needs no key: 200 when swaps can go through, else 503.

Fees and limits

  • 0.25%, inside the transaction, in SOL, USDC or USDT when the swap has one, else the input token (amounts.feeMint).
    • On the input: that share of amountIn.
    • On the output: that share of the enforced minimum. With your own routes, it must be within 1% of Orientim's price.
    • Otherwise: that share of the swap's value, in SOL from the wallet.
  • The skill refuses an Orientim fee above 0.25% (maxFeeBps, up to 0.3%) and a network fee above 0.001 SOL.
  • Slippage: 0.5% by default, 3% on a Pump.fun curve (slippageBps, 0.1% to 15%).
  • Price impact: at most 5% (maxPriceImpactBps, up to 20%).
  • The minimum is never more than 20% below Jupiter's price.
  • The owner's limits per swap, per day and per token (ORIENTIM_POLICY).
  • Smallest swap: about 0.004 SOL or $1, except selling a whole balance.

Tokens and wallets

See supported tokens. Key files and signing services work; sign-and-send-only wallets and multisig vaults do not.

Downloads

  • @orientimhq/swap v2.1.0 on npm: library, CLI, MCP server and skill.
  • orientim.mcpb: the MCP server for Claude Desktop.
  • Verify: gh attestation verify <file> -R orientimhq/orientim-protected-swap, or the sha256 in RELEASE-SHA256SUMS on the release.
  • Source on GitHub, Apache-2.0, with a reproducible build.
  • Full API reference: reference/AGENT-API.md in the skill folder.