Documentation

XyncPay Developer Docs

Integrate non-custodial protocol translation for AI agent payments. Translate between x402, MPP, and AP2 with a single API.

Getting Started

Quickstart

Set up XyncPay and process your first cross-protocol payment in minutes.

1. Install the SDK

npm install @xyncpay/sdk ethers

2. Configure Environment

Set your API endpoint and wallet address. XyncPay never holds your private key. All signing happens client-side.

# .env.local
XYNCPAY_API_URL=https://api.xyncpay.com
WALLET_ADDRESS=0xYourWalletAddress

3. Translate a Payment

Send a translation request to convert between protocols. The API returns an unsigned transaction. Your agent signs it locally before broadcasting.

const response = await fetch(`${process.env.XYNCPAY_API_URL}/v1/payments/translate`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Wallet-Address": process.env.WALLET_ADDRESS,
  },
  body: JSON.stringify({
    source_protocol: "x402",
    target_protocol: "mpp",
    amount: "10.00",
    currency: "USDC",
    recipient: "0xRecipientAddress",
    chain_id: 8453,
    metadata: {
      agent_id: "agent-001",
      purpose: "api-access",
    },
  }),
});

const { unsigned_tx, fee, session_id } = await response.json();
console.log("Fee:", fee.amount, fee.currency);
console.log("Transaction ready for signing");

4. Sign & Submit

Sign the returned transaction with your wallet and submit it to the chain. XyncPay never touches your private key.

import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("https://mainnet.base.org");
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

const signedTx = await wallet.signTransaction(unsigned_tx);
const txResponse = await provider.broadcastTransaction(signedTx);
const receipt = await txResponse.wait();

console.log("Settled:", receipt.hash);

Security

Authentication

XyncPay uses wallet-based authentication. Every request must include your wallet address and a signature over the request body. No API keys, no OAuth. Your wallet is your identity.

Required Headers

HeaderDescription
X-Wallet-AddressYour Ethereum wallet address (checksummed). Used to identify the agent and look up session state.
X-Wallet-SignatureEIP-191 signature of the JSON-serialized request body. The server recovers the signer address and verifies it matches X-Wallet-Address.

Signing a Request

Sign the exact JSON string you send as the request body. The server will deserialize and re-serialize with deterministic key ordering, so use sorted keys when signing.

import { ethers } from "ethers";

const wallet = new ethers.Wallet(process.env.PRIVATE_KEY);

const body = JSON.stringify({
  amount: "10.00",
  currency: "USDC",
  recipient: "0xRecipientAddress",
  source_protocol: "x402",
  target_protocol: "mpp",
});

const signature = await wallet.signMessage(body);

const response = await fetch(`${process.env.XYNCPAY_API_URL}/v1/payments/translate`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Wallet-Address": wallet.address,
    "X-Wallet-Signature": signature,
  },
  body,
});

Core Concepts

Translation Flow

Every payment follows a six-step lifecycle. Steps 2 and 6 are optional depending on the target protocol.

1

Register Agent

Register your agent's wallet address. This creates an on-chain identity that the protocol uses to track sessions and enforce spending limits.

const res = await fetch(`${API}/v1/agents/register`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Wallet-Address": wallet.address,
    "X-Wallet-Signature": await wallet.signMessage(body),
  },
  body: JSON.stringify({
    wallet_address: wallet.address,
    label: "trading-agent-alpha",
    allowed_protocols: ["x402", "mpp"],
    spending_limit: "1000.00",
    spending_limit_currency: "USDC",
  }),
});

const { agent_id, registered_at } = await res.json();
2

Create Session (MPP only)

MPP requires a session context to track spending caps and multi-party state. Sessions are optional for x402 point-to-point translations.

const sessionBody = JSON.stringify({
  agent_id,
  protocol: "mpp",
  spending_cap: "500.00",
  currency: "USDC",
  ttl_seconds: 3600,
});

const sessionRes = await fetch(`${API}/v1/sessions/create`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Wallet-Address": wallet.address,
    "X-Wallet-Signature": await wallet.signMessage(sessionBody),
  },
  body: sessionBody,
});

const { session_id, expires_at } = await sessionRes.json();
3

Translate Payment

Submit the payment details along with source and target protocols. The API constructs the appropriate on-chain call and returns an unsigned transaction.

const translateBody = JSON.stringify({
  source_protocol: "x402",
  target_protocol: "mpp",
  session_id,
  amount: "25.00",
  currency: "USDC",
  recipient: "0xMerchantAddress",
  chain_id: 8453,
});

const translateRes = await fetch(`${API}/v1/payments/translate`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Wallet-Address": wallet.address,
    "X-Wallet-Signature": await wallet.signMessage(translateBody),
  },
  body: translateBody,
});

const { unsigned_tx, fee, payment_id } = await translateRes.json();
4

Sign the Unsigned Transaction

The unsigned transaction is a standard Ethereum transaction object. Sign it with your wallet. XyncPay never has access to your private key.

const signedTx = await wallet.signTransaction(unsigned_tx);
5

Submit to Chain

Broadcast the signed transaction directly to the Base network. You can use any RPC provider; the transaction is self-contained.

const provider = new ethers.JsonRpcProvider("https://mainnet.base.org");
const txResponse = await provider.broadcastTransaction(signedTx);
const receipt = await txResponse.wait();

console.log("Tx hash:", receipt.hash);
console.log("Block:", receipt.blockNumber);
6

Confirm Settlement

Optionally confirm settlement with the XyncPay API. This updates the session spending totals and triggers any webhook notifications.

const confirmBody = JSON.stringify({
  payment_id,
  tx_hash: receipt.hash,
  block_number: receipt.blockNumber,
});

await fetch(`${API}/v1/payments/confirm`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Wallet-Address": wallet.address,
    "X-Wallet-Signature": await wallet.signMessage(confirmBody),
  },
  body: confirmBody,
});

Protocols

Protocol Support

XyncPay translates between three payment protocols. Each protocol has different capabilities, settlement mechanisms, and chain requirements.

x402

Coinbase

HTTP-native micropayment protocol using EIP-3009 transferWithAuthorization. Designed for pay-per-request API access on Base.

  • Full inbound support
  • Full outbound support
  • Base chain (8453)
  • EIP-3009 authorization

MPP

Stripe / Tempo

Multi-party payment protocol with session-based spending caps. Supports complex payment flows with multiple participants and approval chains.

  • Full inbound support
  • Full outbound support
  • Session-based context
  • Spending caps & limits

AP2

Google

Agent Payment Protocol from Google. Inbound translations are routed through x402 or MPP as the settlement layer. Outbound support is on the roadmap.

  • Inbound support
  • Outbound (planned)
  • Routes via x402 / MPP
  • Google agent identity

Translation Matrix

From → Tox402MPPAP2
x402--
MPP--
AP2-

Pricing

Fee Calculation

XyncPay charges a flat 1% translation fee with a $0.001 floor and a $1.00 cap. Fees are settled atomically on-chain via the FeeSplit contract. The recipient and XyncPay are paid in the same transaction.

Formula

fee = max(amount * 0.01, 0.001)
fee = min(fee, 1.00)

// Equivalent:
// fee = clamp(amount * 1% / 100 scaled, $0.001, $1.00)

Examples

Payment AmountCalculated FeeApplied FeeRule
$0.10$0.001$0.001Floor applied
$1.00$0.01$0.01Standard 1%
$10.00$0.10$0.10Standard 1%
$50.00$0.50$0.50Standard 1%
$200.00$2.00$1.00Cap applied

On-Chain Fee Split

The FeeSplit contract handles atomic distribution in a single transaction. The translated payment and the protocol fee are settled together. If either transfer fails, the entire transaction reverts. This eliminates the risk of partial settlement.

// FeeSplit contract (simplified)
// Both transfers execute atomically in the translated transaction

struct Split {
  address recipient;     // merchant / service provider
  uint256 amount;        // payment amount minus fee
  address feeCollector;  // XyncPay fee address
  uint256 feeAmount;     // calculated fee
}

// The unsigned_tx returned by /v1/payments/translate
// already encodes the FeeSplit call. No extra work needed.

Reference

Error Handling

All error responses follow a consistent structure. The top-level error object contains a machine-readable code, a human-readable message, and optional details for validation errors.

Error Response Format

{
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "Wallet 0xAbc...def has 4.20 USDC but 25.00 USDC is required.",
    "details": {
      "wallet": "0xAbcdef1234567890abcdef1234567890abcdef12",
      "available": "4.20",
      "required": "25.00",
      "currency": "USDC"
    }
  }
}

Error Codes

CodeHTTPDescription
INVALID_SIGNATURE401The X-Wallet-Signature header is missing or does not match the request body and wallet address.
AGENT_NOT_FOUND404No agent is registered for the provided wallet address. Call /v1/agents/register first.
SESSION_EXPIRED410The MPP session has exceeded its TTL. Create a new session to continue.
SPENDING_CAP_EXCEEDED403The requested amount would exceed the session or agent spending cap.
INSUFFICIENT_BALANCE402The wallet does not hold enough USDC to cover the payment amount plus fees.
UNSUPPORTED_TRANSLATION400The requested source → target protocol combination is not supported.
CHAIN_NOT_SUPPORTED400The provided chain_id is not supported. XyncPay currently operates on Base (chain_id: 8453). Arc support is coming soon.
RATE_LIMITED429Too many requests. Back off and retry with exponential delay. The Retry-After header indicates wait time in seconds.

Handling Errors in Code

const response = await fetch(`${API}/v1/payments/translate`, {
  method: "POST",
  headers: { /* ... */ },
  body: translateBody,
});

if (!response.ok) {
  const { error } = await response.json();

  switch (error.code) {
    case "INSUFFICIENT_BALANCE":
      console.error(`Need ${error.details.required} ${error.details.currency}, have ${error.details.available}`);
      break;
    case "SESSION_EXPIRED":
      // create a new session and retry
      break;
    case "RATE_LIMITED":
      const retryAfter = response.headers.get("Retry-After");
      await new Promise((r) => setTimeout(r, Number(retryAfter) * 1000));
      // retry the request
      break;
    default:
      throw new Error(`XyncPay error [${error.code}]: ${error.message}`);
  }
}