Pay with REST (raw 402 flow)
This is the same economic path as the SDK payer, written as HTTP and Sui steps you can implement in any language. Prefer Pay with the SDK unless you are building a custom client.
Assumptions:
- Owner registered your public address as a BYO delegated signer.
- You hold the private key for that address and a connection manifest (package, pool, grant, policies, network, coin type).
- Resource is a spend-account rail (
delegateon the wire) with MPP.
1. Call the paid resource unpaid
POST /v1/your-resource
Content-Type: application/json
{"...": "..."}Expect 402 Payment Required with:
WWW-Authenticate: Payment ...(MPP challenge), and possibly- x402 offers (may be present even if you will not use x402). The gateway
sends them in the
PAYMENT-REQUIREDheader (base64 JSON) and repeats the same object as the JSON body. A seller built on@cmdoss/suipay-sdk/sellerreturns them in the 402 JSON body only.
Decode the MPP challenge / offer. Validate at least:
- offer digest / HMAC (with the deployment's verification material)
- amount (atomic integer string), asset, decimals
payTo, network (sui:testnetetc.), package idsettlementRail === "delegate"(spend-account rail tag)- method, path, and body binding (
bodyHash/ content type) for body-bearing requests expiresAtnot passed- service id / revision match what you intended
Do not proceed if validation fails.
2. Derive binding hashes
payment_id_hash = SHA-256(challengeId)
terms_hash = SHA-256(offer.digest)These must match the values you put on chain and the ones the facilitator re-derives from the finalized event.
3. Build spend_account::settle_policy
Transaction sender: your delegated address.
Move call (argument order is package-defined; follow the published package / SDK builder, not this sketch alone):
package::spend_account::settle_policy
exclusion directory (shared),
recipient registry (shared),
pool (shared),
grant (shared grant object),
policy_id,
target_hash,
amount,
payment_id_hash,
terms_hash,
expires_at_ms,
clock,
...Resolve real initial shared versions for every shared object (directory, registry, pool, grant) from chain. Using placeholder versions will fail shared-object consensus.
Recipient is not a free argument at pay time: the contract resolves it from the policy target for the target hash.
4. Gas
- You have SUI: attach gas coin objects owned by the delegate and set budget/price.
- You do not: use sponsored gas. The agent signs
a personal message to prove it holds the key, then gas is sponsored:
POST /api/sponsor/personal-message/challengewithdelegateAddress,poolId,grantId,network, andpackageId(all required;network/packageIdmust match the deployment). This returns a single-use nonce.- Sign the domain-separated payload (
suipay:sponsor-personal-message:v1) over the pre-sponsor kind hash with the delegated key (signPersonalMessage). The payload binds the nonce,requestId, expiry, delegate, pool, grant, network, and package. POST /api/sponsorwith the kind bytes plus the personal-message fields (nonce, signature,requestId, expiry, and the pool/grant/network/package bindings). The response carries the sponsored transactionbytes, itsdigest, and a single-useexecuteToken.- Sign the sponsored
byteswith the delegated key, thenPOST /api/sponsor/executewithdigest,signature, andexecuteToken(plussponsorBackendTokenwhen step 3 returned one). The gateway executes the transaction and answers with itsdigest. Without the token it answers403.
The personal-message challenge authorizes gas only.
5. Sign and execute
With your own gas, sign the full transaction as the delegate and execute it via
Sui fullnode / gRPC. With sponsored gas, POST /api/sponsor/execute in step 4
is the execute. Either way, wait until the transaction is finalized
success. Keep the digest.
If execute times out after a possible broadcast, or /api/sponsor/execute
fails, treat the payment as ambiguous. Do not build a second payment for
the same challenge without reconciling chain state.
6. Replay with MPP proof
Retry the same URL, method, headers, and body with:
Authorization: Payment /* MPP PaymentProof including digest, payer, challenge, offer */Never send MPP and x402 credentials together. Never forward payment credentials across an origin-changing redirect.
7. Confirm delivery and receipt
Success responses include settlement evidence:
| Dialect | Receipt header |
|---|---|
| MPP | Payment-Receipt |
| x402 | PAYMENT-RESPONSE |
Require challenge ID, network, amount, asset, payer, and non-empty
txDigest. Cross-check with GET /v1/settlements?challengeId=... when
authenticated, and with Suiscan.
Errors and idempotency
| Situation | Action |
|---|---|
| 402 validation fails | Do not sign |
| Grant / policy rejects on chain | Fix authority; do not retry blindly with a larger amount |
| Ambiguous after submit | Reconcile digest + challenge; one payment id, one settlement |
| Gateway refuses proof | Digest may still be final; reconcile before a new challenge |
Exactly-once on chain is the pool-scoped replay marker. Your client should still keep a durable "attempt in flight / done" ledger so concurrent retries do not double-sign.