Sovrnsovrn
Docs · v0.1

Six lines, no backend.

Everything below is the actual SDK shipping in@sovrn/wallet— copy any snippet and it runs.

Install#

Node ≥ 18. Works in Node, Bun, Deno, edge runtimes, and the browser.

npm install @sovrn/wallet
# or
pnpm add @sovrn/wallet
# or
bun add @sovrn/wallet

Quickstart#

Generate a wallet, hit an x402 endpoint. The wallet's keys are created and signed entirely in your process — Sovrn has no backend in this path.

import { Wallet } from "@sovrn/wallet";

const agent = Wallet.create({
  name: "research-agent.04",
  chains: ["solana", "base"],
  policy: { dailyCapMicros: 500_000_000 }, // $500 / 24h
});

const result = await agent.fetch(
  "https://sovrn.fun/api/x402/demo?resource=arxiv",
);

if (result.response.ok) {
  console.log(await result.response.json());
}

Multi-chain#

One wallet handles both Solana (ed25519) and EVM (secp256k1) accounts. All EVM chains share a single 0x address.

const agent = Wallet.create({
  chains: ["solana", "base", "ethereum", "arbitrum", "polygon"],
});

agent.address              // Solana base58 address
agent.evmAddress           // 0x... checksummed
agent.addressFor("base")   // 0x...
agent.signEvmMessage("hi") // EIP-191 personal_sign

x402 auto-pay#

agent.fetch() is a drop-in replacement for window.fetch. When the server responds with HTTP 402, the SDK parses the challenge, applies the wallet policy, signs an authorization, and retries with x-payment-authorization.

const { response, authorization, event } = await agent.fetch(url);

console.log(authorization.signature); // base58 ed25519 sig
console.log(event.kind);               // "x402.paid"
console.log(await response.json());    // the actual data

If policy rejects the payment, you get back { decision, event } instead, with event.kind === "x402.denied" and a reason like daily_cap_exceeded.

Policies#

Every payment runs through evaluate(). You can mutate the policy at runtime; the ledger keeps a signed history of every change.

agent.updatePolicy({
  dailyCapMicros: 250_000_000,        // $250 / 24h
  perCallCapMicros: 5_000_000,        // $5 max per call
  allowlist: ["compute.fly.io"],
  denylist: ["sketchy.api"],
});

agent.pause();   // immediately blocks all auto-pays
agent.resume();  // back on

Signed ledger#

Every wallet event (creation, policy change, attempt, paid, denied) is appended to an in-process log, each entry signed with the wallet's ed25519 key and chained to the previous event's signature.

for (const e of agent.events()) {
  console.log(e.seq, e.kind, e.signature);
}

agent.spent24hMicros(); // sum of x402.paid events in the last 24h

Verifying signatures#

Anyone with the payer's public key can independently verify an authorization. No Sovrn server in the loop.

import { verify, decodeBase58 } from "@sovrn/wallet";

const ok = verify(
  decodeBase58(payerAddress),
  new TextEncoder().encode(canonicalEnvelope),
  decodeBase58(signatureBase58),
);

For EVM, verifyEvmMessage(address, message, signatureHex) recovers the signer from an EIP-191 personal_sign signature.

Wire format#

The x402 challenge and authorization headers are semicolon-delimited key/value pairs. Field order doesn't matter; the canonical envelope used for signing is alphabetically sorted by key, newline-joined.

# Server → client (HTTP 402)
x-payment-required: price=0.014; recipient=sovrn_demo_treasury_5GqL; chain=solana; nonce=ab12cd34; resource=arxiv; expires=1733080000000

# Client → server (retry)
x-payment-authorization: payer=<base58 pubkey>; recipient=...; chain=solana;
  nonce=ab12cd34; price=0.014000; resource=arxiv;
  signed_at=1733079940123; signature=<base58 sig>

The canonical message that gets signed is:

chain=solana
nonce=ab12cd34
payer=<base58 pubkey>
priceMicros=14000
recipient=sovrn_demo_treasury_5GqL
resource=arxiv
signedAt=1733079940123