VELLARSDK
GitHubOpen Vellar Wallet

Reference

x402 Agentic Payments

Pay x402 (HTTP-402) resources from a Vellar smart account — the "give your agent a budget, not your keys" flow. An autonomous agent holds a scoped session key that pays for x402-protected APIs on its own, but only within a budget enforced on-chain by a spending-limit policy. The agent never holds the account's admin passkey, and it cannot spend beyond its cap.

The SDK exposes this on the wallet handle as wallet.x402.

Requires vellar-sdk ≥ 0.4.0.

How it works

x402 is an open protocol built on HTTP 402 Payment Required. A resource server answers an unpaid request with 402 plus payment requirements (amount, asset, recipient, network); the client builds a signed payment and retries; a hosted facilitator verifies and settles it on-chain. On Stellar the payment is a SEP-41 token transfer authorized by a Soroban authorization entry.

Vellar pays as a smart account (a C-address). wallet.x402.fetch() runs the whole exchange:

  1. Request the resource → get 402 + requirements.
  2. Build the SEP-41 transfer(from = smart account, to = payTo, amount).
  3. Sign the wallet's auth entry with your session key (V1 credentials).
  4. Retry with the PAYMENT-SIGNATURE header.
  5. The facilitator verifies by re-simulation — which runs the account's __check_auth, and therefore the budget policy — then settles on-chain.

Because verification re-simulates, an over-budget or wrong-token payment is rejected before it settles.

Enabling x402

Pass x402 config to createVellarWallet with a signer (who pays) and a simulationSourceAccount (any funded classic account, used only to simulate — the facilitator rebuilds the transaction and pays the network fee):

import { createVellarWallet, createSessionKeySigner } from "vellar-sdk";

const vellar = createVellarWallet({
  network: "testnet",
  appName: "My Agent",
  kit,
  sac,
  backend,
  isValidAddress,
  x402: {
    // Agent flow: a scoped ed25519 session key signs headlessly (no passkey).
    signer: createSessionKeySigner({
      address: walletCAddress, // the smart account that pays
      secretKey: sessionKeySecret, // the session key attached to it
    }),
    simulationSourceAccount: aFundedGAccount,
  },
});

Without x402 config, wallet.x402 throws X402NotConfiguredError.

Paying a resource

const { response, paid, settlement } = await vellar.x402.fetch(
  "https://api.example.com/paid",
  {
    maxAmount: 1_000_000n, // hard per-request ceiling, in the asset's base units
    // allowedAssets: [usdcSac], // optional — restrict which asset(s) you'll pay in
  },
);

if (paid) {
  console.log("settled on-chain:", settlement.transaction);
}
const data = await response.json(); // the unlocked resource

fetch returns { response, paid, settlement }: the unlocked Response, whether a payment was made, and (when paid) the on-chain settlement { transaction, payer, asset, amount, network }. If the resource needs no payment, it passes through untouched with paid: false.

For callers managing their own transport, wallet.x402.createPayment(requirements, opts) signs a payment and returns the PAYMENT-SIGNATURE header value without sending it.

The two signers

Both satisfy the same SmartAccountX402Signer interface, so the client is signer-agnostic:

SignerFlowPrompt
createSessionKeySigner({ address, secretKey })Agent — a scoped ed25519 session key signs headlessly. Bounded on-chain by the spending-limit policy attached to it.None — an agent runs unattended.
createPasskeyX402Signer({ address, webAuthn })Human — a person pays from the web app. webAuthn is a seam you wire to your passkey ceremony.One passkey prompt per payment.

Both produce V1 (sorobanCredentialsAddress) credentials in the smart-wallet signature format the account's __check_auth expects. (The SDK builds this itself rather than delegating to passkey-kit, which upgrades to V2 credentials that hosted facilitators reject.)

maxAmount is a guard, not the budget

There are two independent protections, and it matters that they are different:

  • maxAmount (client-side) — the SDK refuses to sign a payment whose required amount exceeds this ceiling, guarding against an over-charging or misconfigured server. It is only as trustworthy as the client process. It is not the budget.
  • The spending-limit policy (on-chain) — the real, enforced budget. The agent session key is attached with SignerLimits that require a spending-limit policy to co-sign any transfer; the policy caps cumulative spend over a rolling window inside __check_auth. Even if client code is bypassed, the chain refuses to move funds beyond the cap.

Use the token-scoped spending-limit policy for an agent budget so only one specific token's transfers count against it — then "give your agent $10/day of USDC" means exactly that: USDC only, capped, on-chain.

Facilitators and the fee ceiling

The exact scheme requires sponsored fees — the facilitator rebuilds the transaction with its own source and pays the Stellar fee, so the smart account needs no XLM. The SDK rejects an option that does not advertise sponsored fees.

A policy-governed payment runs the policy inside __check_auth, costing more resource fee than a plain transfer. Hosted facilitators cap the fee they sponsor (the reference x402.org facilitator defaults to 50,000 stroops), which a policy-governed payment can exceed — such a payment needs a facilitator with a higher ceiling. A plain transfer stays well under the default.

Vellar runs one. The x402 Facilitator (https://vellar-facilitator.onrender.com, testnet) ships with a raised ceiling specifically so policy-governed agent payments settle instead of being rejected — plus Bazaar discovery so agents can find payable resources.

Errors

The client throws typed errors:

ErrorWhen
MaxAmountExceededErrorThe server asked for more than your maxAmount. Nothing signed.
DisallowedAssetErrorNo offered asset was in allowedAssets. Nothing signed.
NoUsablePaymentOptionErrorNo option matched the scheme/network, or none sponsored fees.
InvalidRequirementsErrorA requirement was malformed (e.g. a non-integer amount).
PaymentRejectedErrorThe facilitator rejected the payment at verify — e.g. an over-budget payment blocked by the on-chain policy. Carries the reason.
X402NotConfiguredErrorwallet.x402 used without x402 config.

Lower-level

For a client without a wallet handle, createX402Client({ signer, rpcUrl, network, simulationSourceAccount }) returns the same fetch / createPayment API. See Advanced for the exported building blocks.