← Home

SolKnife for agents

SolKnife is agent-native by construction. Every action a user can take through the UI is reachable as a JSON HTTP call. The server builds unsigned transactions; the caller (a user wallet, or an agent with key custody) signs; the server self-submits. The machine-readable manifest at /api/manifest is the source of truth.

The four kinds of tools

Each entry in the manifest carries one of four tags:

  • read: pure GET, parameters in the query string, no signing. Used for the rug checker, portfolio, pool data, LP positions, current state of any account.
  • build: POST with structured JSON in and out. Returns an unsigned base64 transaction the caller can verify and sign.
  • submit: POST that takes a signed transaction (or array of signed transactions, for saga flows) and self-submits. Per-tool naming varies — most tools use /execute, confidential transfer uses a single /submit with an op discriminator.
  • worker-bound: the only proof step that is not exposed as plain HTTP or MCP. Confidential-transfer proof generation runs in a browser Web Worker because the ElGamal and AES keys are derived from a wallet signature and never leave the process that derived them. An agent can still read CT state over HTTP, and can build or submit CT transactions after it supplies the required proofs.

What an agent can do today

Without any extra infrastructure, an HTTP-only agent can already:

  • Rug-check any Solana token and inspect mint authorities
  • Read any wallet's priced holdings and DLMM positions
  • Compare DLMM pools for a token side by side
  • Quote and execute non-custodial swaps via Jupiter (with its own keypair or a user-authorized wallet)
  • Create SPL or Token-2022 mints (including with the confidential-transfer extension), mint additional supply, revoke authorities, set Metaplex metadata
  • Create token accounts, reclaim rent from empty ones, burn NFTs
  • Read confidential-transfer state for any owner-and-mint pair, list any wallet's orphan ZK context-state accounts, and reclaim their rent

The only missing plain-HTTP piece is ZK proof generation for confidential-transfer deposit/apply/empty/withdraw/transfer. Driving those without a browser is possible (the WASM zk-sdk runs in Node), but it extends the trust boundary because the agent's runtime takes custody of the user's derived ElGamal and AES keys.

MCP server (reads + builds + submits)

Streamable-HTTP MCP endpoint at /api/mcp. Stateless transport, one request per response. Phases 1+2+3 are live: 16 read tools, 22 build tools, 17 submit/execute tools (55 total) — every action the agent-parity audit identified is callable. The confidential-transfer worker stays off MCP for the non-custodial reasons explained below.

Point any MCP client at:

{
  "mcpServers": {
    "solknife": {
      "transport": "streamable-http",
      "url": "https://solknife.xyz/api/mcp"
    }
  }
}

Quickstart (raw HTTP)

Read the manifest:

curl -s https://solknife.xyz/api/manifest | jq .

Rug-check a token:

curl -s "https://solknife.xyz/api/check?mint=<MINT_ADDRESS>" | jq .

Build an unsigned create-mint tx (then sign + execute):

curl -s -X POST https://solknife.xyz/api/create-mint/build \
  -H 'content-type: application/json' \
  -d '{ "owner": "<PUBKEY>", "decimals": 6, "extensions": {} }' | jq .

Every response uses the ApiResponse envelope: { ok: true, data } or { ok: false, error: { code, message } }. No bare 500s with HTML bodies.

Node reference CLI

A local clone includes a dependency-free Node reference for the read-only HTTP surface. It reads the manifest, counts tools by kind, and runs a token safety scan. It does not need a wallet.

node scripts/agent-read-demo.mjs --manifest-only --json
node scripts/agent-read-demo.mjs --mint So11111111111111111111111111111111111111112
BASE=http://localhost:3000 node scripts/agent-read-demo.mjs

The same request pattern applies to build/submit tools: call a build endpoint, verify the unsigned transaction in your runtime, sign with the authorized key-holder, then post the signed bytes to the matching execute endpoint.

Non-custodial guarantees

The server never holds a key with authority over your funds. Every build endpoint returns bytes; every submit endpoint takes signed bytes and relays them. The client's verifier (and the matching server-side re-verifier) walks the assembled transaction against a strict program allowlist before the wallet ever sees it.

One exception worth stating plainly, because it is a real key: when the x402 gate is enabled, the server holds a fee-payer keypair. The x402 v2 spec requires the resource server to co-sign payment transactions as fee payer, so this is not a choice we made. It sponsors network fees on payments you authored and signed. It is not an authority on any account of yours, it can move nothing you own, and it touches no flow other than x402 settlement.

For confidential transfer, the ElGamal and AES keys derive from a wallet signature in the browser's Web Worker and are not posted to the server. An agent that drives the CT flows from outside a browser is choosing to take custody of those derived keys — that is an explicit trust extension, not something the architecture obscures.

Confidential-transfer boundary

CT read endpoints, orphan discovery, and orphan reclaim builds are ordinary HTTP. Active CT operations that change encrypted balances need proof bytes generated from wallet-derived ElGamal and AES keys. SolKnife intentionally does not expose an HTTP endpoint that accepts those keys.

For a browser user, the Web Worker owns that proof pipeline. For a headless agent, the equivalent is embedding the worker logic or the zk-sdk in the agent runtime. That agent then becomes the key-holder for those derived secrets. This is the one place where agent automation changes the trust boundary.

Fees

The server is ungated by default. Each transaction-producing tool has an optional flat lamport fee, configured by the operator via environment variable. When set, the fee shows up as a single SystemProgram.transferinside the build output, pinned by the verifier — the fee can't be stripped or redirected without the verifier rejecting the transaction. The swap fee accrues to a wSOL token account; every other fee goes to SOLKNIFE_FEE_WALLET.

x402 (v2, off by default)

The HTTP-native x402 Payment Required gate is shipped and speaks the real v2 protocol, so any standard x402 client can pay it. It is OFF by default; operators flip SOLKNIFE_X402_ENABLED=true to enable per-call payment on the agent-callable surface.

When enabled, a gated request returns the challenge in the body and base64 in the PAYMENT-REQUIRED header:

HTTP/1.1 402 Payment Required
Content-Type: application/json
PAYMENT-REQUIRED: <base64 of the body below>

{
  "x402Version": 2,
  "resource": { "url": "https://solknife.xyz/api/check?mint=...", "mimeType": "application/json" },
  "accepts": [
    {
      "scheme": "exact",
      "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
      "amount": "10000",
      "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "payTo": "<SOLKNIFE_FEE_WALLET>",
      "maxTimeoutSeconds": 60,
      "extra": { "feePayer": "<ours>", "memo": "<nonce>" }
    }
  ]
}

Note the shape of the flow: you sign the payment but do not submit it. We co-sign as fee payer and submit. Agent flow:

  1. Receive the 402 challenge and pick the `exact` entry.
  2. Build a Solana tx whose fee payer is extra.feePayer, with exactly: SetComputeUnitLimit, SetComputeUnitPrice (≤ 5 lamports/CU), then a TransferChecked of amount USDC — sent to the derived associated token account of (payTo, asset), never the bare wallet address.
  3. Append an SPL Memo ix carrying extra.memo verbatim when x402.nonceRequired is set in the manifest. It expires after x402.nonceTtlSeconds.
  4. Sign it with your key only, leaving the fee-payer signature slot empty, and send it base64-JSON in PAYMENT-SIGNATURE.

We re-check every instruction, simulate, add the fee-payer signature, submit, and return the receipt in PAYMENT-RESPONSE. A payment that fails any check is never signed, so nothing lands and nothing is charged.

The one key we hold. x402 v2 on Solana requires the resource server to co-sign as fee payer — the protocol has no variant where you submit and we merely observe. That key sponsors network fees and has no authority over your funds: it cannot move anything of yours, and a full compromise of it costs us its SOL balance and nothing else. Everything else on SolKnife stays exactly as non-custodial as before.

Deprecated: the solknife-sol-landed entry in accepts[] is our old pay-then-prove path — send lamports yourself, then retry with X-Payment: <base58 signature>. It still works while existing callers migrate, but no standard x402 client can pay it. Prefer exact.

Machine-readable everything: /openapi.json describes each endpoint and its input schema, and /api/manifest carries the live gate state.

Sign-in bypass (SIWS session)

Pay-per-call is the universal path, but agents that make many calls in a row can sign in once and skip the per-call fee for an hour. The bypass is a cookie issued only after a real Sign-In With Solana (SIWS) signature — there's no shared origin allow-list, no spoofable header.

Three-step handshake:

# 1. Ask for a challenge bound to your wallet
curl -s -c cookies.txt -X POST https://solknife.xyz/api/auth/challenge \
  -H 'content-type: application/json' \
  -d '{ "wallet": "<YOUR_PUBKEY>" }' | jq .

# Response:
# { "ok": true, "data": { "message": "...", "nonce": "...", "issuedAt": "..." } }

# 2. Sign the EXACT message bytes with your wallet's Ed25519 key
#    (Node example using @noble/curves):
#      const sig = ed25519.sign(new TextEncoder().encode(message), privKey);
#      const signatureBase64 = Buffer.from(sig).toString("base64");

# 3. POST the signed message back to mint a session cookie
curl -s -b cookies.txt -c cookies.txt -X POST https://solknife.xyz/api/auth/verify \
  -H 'content-type: application/json' \
  -d '{ "wallet": "<YOUR_PUBKEY>", "message": "<MSG>", "nonce": "<NONCE>", "signatureBase64": "<SIG_B64>" }'

# Subsequent gated calls just need the cookie:
curl -s -b cookies.txt "https://solknife.xyz/api/check?mint=<MINT>"

# Sign out (clears the cookie):
curl -s -b cookies.txt -X POST https://solknife.xyz/api/auth/logout

The cookie is HttpOnly; Secure; SameSite=Lax and HMAC-signed with a server-side secret over (wallet, expiresAt). It can't be forged client-side and survives only as long as its embedded expiry. Lifetime: 1 hour. Browser users get this automatically; the wallet adapter calls /api/auth/challenge the moment they connect.

Discover the endpoints + cookie name from the x402.sessionAuth block of /api/manifest — that's the contract, not this paragraph.

Reference