Sui Charge Method

v1.0

The sui charge method enables on-chain USDC micropayments on the Sui network for the Machine Payments Protocol (MPP).

Overview

MPP uses HTTP 402 Payment Required to negotiate payments between clients and servers. The sui charge method implements this negotiation using USDC transfers on the Sui blockchain, providing sub-second finality and trustless verification.

Network
Sui (mainnet)
Currency
USDC
Finality
~400ms
Verification
Peer-to-peer

Protocol Flow

Every MPP payment follows a four-step request-challenge-pay-verify cycle:

  1. 1
    Request
    Client sends a standard HTTP request to the server endpoint.
  2. 2
    Challenge (402)
    Server responds with 402 Payment Required and a WWW-Authenticate header containing the charge method, amount, currency, and recipient address.
  3. 3
    Pay
    Client parses the challenge, builds a Sui transaction transferring the requested USDC amount to the recipient, executes it on-chain, and retries the original request with the transaction digest as a credential.
  4. 4
    Verify & Deliver
    Server queries the Sui RPC, confirms the transaction succeeded, verifies the payment amount and recipient, then returns the API response.

Challenge Format

When a server requires payment, it responds with 402 and a challenge encoded in the WWW-Authenticate header:

Response Header
HTTP/1.1 402 Payment Required
WWW-Authenticate: MPP method="sui",
  amount="0.01",
  currency="0xdba346...::usdc::USDC",
  recipient="0xYOUR_SUI_ADDRESS"
ParameterTypeDescription
methodstring"sui" — identifies this charge method
amountstringHuman-readable amount (e.g. "0.01" = 1 cent USDC)
currencystringSui coin type — fully qualified Move type
recipientstringSui address to receive payment (0x-prefixed, 64 hex chars)

Credential Format

After executing the on-chain payment, the client retries the request with the credential in the Authorization header:

Retry Request Header
Authorization: MPP method="sui", digest="<TX_DIGEST>"
ParameterTypeDescription
methodstring"sui" — matches the challenge method
digeststringSui transaction digest (Base58-encoded, 44 chars)

Method Schema

The sui charge method is defined using the mppx Method schema:

method.ts
import { Method, z } from 'mppx';

export const suiCharge = Method.from({
  intent: 'charge',
  name: 'sui',
  schema: {
    credential: {
      payload: z.object({
        digest: z.string(),
      }),
    },
    request: z.object({
      amount: z.string(),
      currency: z.string(),
      recipient: z.string(),
    }),
  },
});
TypeScript

Server Verification

When the server receives a credential, it performs four verification steps:

  1. 1
    Fetch transaction
    Query the Sui RPC with getTransaction using the provided digest. Include balanceChanges in the response.
  2. 2
    Check success
    Verify status.success === true. Reject failed or pending transactions.
  3. 3
    Find payment
    Scan balanceChanges for an entry where:
    • coinType matches the requested currency
    • address matches the recipient (normalized)
    • amount is positive (incoming transfer)
  4. 4
    Check amount
    Convert the challenge amount to raw units using the currency's decimals (USDC = 6). Verify the transferred amount >= requested amount.
Verification logic (simplified)
const tx = await client.getTransaction({ digest, include: { balanceChanges: true } });

if (!tx.status.success) throw new Error('Transaction failed');

const payment = tx.balanceChanges.find(
  (bc) =>
    bc.coinType === currency &&
    normalize(bc.address) === normalize(recipient) &&
    BigInt(bc.amount) > 0n,
);

if (!payment) throw new Error('Payment not found');

const transferredRaw = BigInt(payment.amount);
const requestedRaw = parseAmountToRaw(amount, 6); // USDC = 6 decimals
if (transferredRaw < requestedRaw) throw new Error('Underpaid');
TypeScript

Client Payment

When a client receives a 402 challenge, it builds and executes a Sui transaction:

Payment construction
import { Transaction, coinWithBalance } from '@mysten/sui/transactions';

const tx = new Transaction();
tx.setSender(walletAddress);

const amountRaw = parseAmountToRaw(challenge.amount, 6);
const payment = coinWithBalance({ balance: amountRaw, type: challenge.currency });
tx.transferObjects([payment], challenge.recipient);

const result = await client.signAndExecuteTransaction({ transaction: tx });
// Credential: { digest: result.digest }
TypeScript

The coinWithBalance helper automatically splits coins from the sender's balance. The transaction digest becomes the credential's proof of payment.

USDC on Sui

The canonical USDC coin type on Sui mainnet:

0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC
PropertyValue
Decimals6
1 USDC1,000,000 raw units
$0.0110,000 raw units
Min practical$0.000001 (1 raw unit)
IssuerCircle (native issuance on Sui)

Amount Parsing

Amounts in challenges and credentials are human-readable strings (e.g. "0.01"). Both client and server must convert to raw units for on-chain operations:

parseAmountToRaw
function parseAmountToRaw(amount: string, decimals: number): bigint {
  const [whole = '0', frac = ''] = amount.split('.');
  const paddedFrac = frac.padEnd(decimals, '0').slice(0, decimals);
  return BigInt(whole + paddedFrac);
}

// Examples:
// parseAmountToRaw("0.01", 6)  → 10000n
// parseAmountToRaw("1",    6)  → 1000000n
// parseAmountToRaw("0.5",  6)  → 500000n
TypeScript

Security Considerations

Replay protection
Each transaction digest is unique. Servers should track used digests to prevent replay. Alternatively, the on-chain finality and balance-change check makes double-spending impossible.
Amount precision
Always use BigInt for amount comparisons. Floating-point arithmetic can produce rounding errors that allow underpayment.
Address normalization
Always normalize Sui addresses (lowercase, 0x prefix, zero-padded to 64 hex chars) before comparison. Use normalizeSuiAddress() from @mysten/sui/utils.
Transaction finality
Sui provides immediate finality. No confirmation wait is needed — if the RPC returns the transaction with success: true, the payment is irreversible.
RPC trust
Verification depends on a trusted Sui RPC endpoint. Use official fullnodes or run your own for production deployments.

Reference Implementation

Discovery spec — OpenAPI & validationUse APIs with MPPRegister your serverDeveloper guideBrowse serversMPP ProtocolGitHub