Pay with the SDK
Use this path when your agent holds its own keypair and the owner has registered that public address as a delegated signer on a spend account.
Package: @cmdoss/suipay-buyer. Constructor: createPayer.
1. Install
pnpm add @cmdoss/suipay-buyernpm install @cmdoss/suipay-buyer2. Get credentials from the console
The owner must already have:
- Funded a spend account for the asset.
- Registered this key's public Sui address and signed
create_grant.
Generate the keypair in the agent runtime. Share only the public address. Load the secret from your environment or secret store — never from this page.
// Step 1 of the SDK path: the delegate key. Included by guides/pay-with-sdk.
import { } from '@mysten/sui/keypairs/ed25519'
// Persist this once. Sui Agent Payments cannot recover it.
const = .(new (32))
export { }3. Configure the payer
import { } from '@cmdoss/suipay-buyer'
const = await ({
: 'https://gateway.example',
, // Ed25519Keypair; never leaves this process
// select: { coinType: '0x...::usdc::USDC' } // when the key holds several grants
})
if (!.) throw new (..)
export { }createPayer reads GET /v1/delegate/deployment, signs
GET /v1/delegate/grants with the key, and then reads the grant object with an
unsigned getObject against a public fullnode — refusing the session unless
the chain agrees the grant names this delegate and draws on the named spend
account.
The gateway's HMAC secret is not an agent input. It authenticates the gateway to itself.
4. First paid call
const = await ..(
'https://gateway.example/v1/image/generate',
{
: 'POST',
: .({ : 'A blue ceramic robot' }),
: 'application/json',
: { : '0x...::usdc::USDC', : '50000' },
},
)
export { }intent is required and is what bounds the payment: asset, an inclusive
maxAmount in atomic units, and optionally the exact recipient. An offer
outside it is refused before anything is reserved or signed.
The payer then builds spend_account::settle_policy_payment with the delegate
as sender, executes, and replays with the MPP proof.
5. Handle the result
// The shape a paid call returns, bound to the real type rather than described
// in prose. Included by guides/pay-with-sdk. If PayResult loses or renames a
// field, this stops compiling and the page documenting it fails the build.
import type { } from '@cmdoss/suipay-buyer'
declare const :
// `status` is how resolved the outcome is:
// 'ok' settled AND delivered
// 'ambiguous' something after the signature is unresolved: reconcile, do not retry
// 'failed' nothing was signed, or the chain rejected the settlement
const : 'ok' | 'ambiguous' | 'failed' = .
// `paid` answers "did money leave the grant" and has THREE states, not two.
// It is not a delivery flag: a settled payment whose delivery then failed is
// paid: true with status: 'ambiguous' and a settlement block.
const : boolean | 'unknown' = .
// 'unknown' means a signature left this process and finality did not resolve.
// The money may have moved. Treating it as false and retrying pays twice.
// Note `if (!result.paid)` is FALSE for 'unknown', so the falsy check alone
// does not catch it — reconcile on paymentId instead.
if (. === 'unknown') {
const = . // === offer.challengeId
void
}
// Present on every paid result, including a settled pay that failed delivery.
const = .
// May be the broadcast-unknown sentinel on digestless ambiguity.
const = .
export { , , , }On ambiguous or timeout after possible broadcast: reconcile the same
paymentId / digest. Do not open a second payment for the same work unit.
What the payer enforces (order is load-bearing)
- Bound the offer first — the agent's own
intent. Nothing else runs before it. - Request-binding and offer-expiry checks.
- Atomic reserve on the payment-attempt ledger (
payment_id= challenge id). Never sign without a durable claim. - Execution capability (submitter present).
- Derive
payment_id_hash = SHA-256(challengeId)andterms_hash = SHA-256(offer.digest). - Build a complete executable
paytransaction (real shared-object versions). - Validate-before-sign (Move call matches intended payment; gas may differ).
- Sign as the delegated key, execute, complete the ledger, present MPP proof, settle.
On-chain single-use is the account-scoped payment_id_hash replay marker
inside pay.
Gas options
| Mode | Configuration |
|---|---|
| Agent gas | The delegate owns SUI coin objects the payer can attach |
| Sponsored | The payer signs a personal message with the delegated key (sponsored gas) |
Default: agent gas when the delegate has payment objects, else sponsored when the gateway will sponsor.
Pay x402 from a spend account
MPP above is the default. Pass dialect: 'x402' to pay() to settle the same
spend account over the x402 dialect instead: the delegate key signs a
transaction rather than presenting an MPP proof.
Who this is for: agents paying a resource whose catalog entry advertises
x402 on the spend-account rail, on a deployment where the operator has turned
on SUIPAY_SPEND_ACCOUNT_X402 (alias SUIPAY_SHARED_POOL_X402). The flag
defaults off and is not enabled on any current deployment — enabling it is an
operator decision.
Prerequisites:
- The gateway has the flag on and is wired for it: a local sponsor key, a gRPC network, and a funded gas-lease pool.
- The resource's registration advertises
x402— otherwise the gateway answersPROOF_REQUIRED, the same as with the flag off. - Your grant is scoped to a recipient. Open-recipient grants are refused over x402.
- Your delegate key is ed25519. zkLogin and multisig delegates are not supported for x402 settlement.
What happens: the payer reads the x402 offer from the 402, builds the
same settle_policy_payment transaction as the MPP path above but with gas
pinned by the gateway's offer — the sponsor pays gas, not you — signs it with
the delegate key, and sends it as PAYMENT-SIGNATURE instead of executing
it. The gateway co-signs and submits. There is no client-side execute or
finality poll: presenting the signed credential is the settle.
Refused before anything is signed:
- No x402 offer on the 402, or a missing sponsor-gas pin (the gateway
couldn't lease a sponsor coin) →
X402_UNAVAILABLE. - An open-recipient grant →
UNSUPPORTED_SCOPE.
Ambiguous outcomes reconcile the same way as MPP. A 503 is re-presented
a bounded number of times; if it still doesn't resolve, the call ends
ambiguous with paid: 'unknown' and the settlement digest, so you reconcile
on paymentId / digest exactly as in "Handle the result" above — never open a
second payment for the same work unit.
Every 402 minted for an x402-eligible resource leases a sponsor gas coin, even if you end up paying over MPP — bounded by the aggregate gas-lease cap.
What not to use
createPaidFetch/paidFetch: V1 allowance rail, retired for new agent work.- x402 transaction-submit on the spend-account rail when the deployment
has not enabled
SUIPAY_SPEND_ACCOUNT_X402, or the resource does not advertise x402: refused (PROOF_REQUIRED). The delegated payer falls back to MPP proof. See "Pay x402 from a spend account" above for when it is available.
Next
- Pay with REST for the same flow without the helper
- Sponsored gas
- Receipts