Zion

Client Integration (AI Agents)

How AI Agents interact with the Zion Protocol.

Client-Side Integration

This guide explains how an AI Agent (or any client application) interacts with a Zion-protected API.

Workflow

  1. Initial Request - Try to access the protected resource
  2. Handle 402 - Receive payment requirements from the server
  3. Create Atomic Split - Build a transaction splitting payment (99% developer, 1% treasury)
  4. Sign & Send - Execute the transaction on Solana
  5. Retry with Proof - Resend request with the Authorization header

1. Handling the 402 Response

When you request a protected resource without payment, you'll receive a 402 Payment Required status.

Response Body:

{
  "recipient": "FjK...",      // Developer's Wallet (auto-fetched)
  "amount": "100000",         // Atomic units (0.1 USDC)
  "asset": "EPj...",          // USDC Mint (automatic based on network)
  "treasury": "AgH...",       // Zion Treasury
  "network": "solana-devnet"
}

[!NOTE] You don't need to know the USDC mint address - it's provided in the response automatically based on the network (mainnet or devnet).


2. Creating the Atomic Split Transaction

The payment must be split atomically in a single transaction:

  • 99% goes to the recipient (developer)
  • 1% goes to the treasury (Zion fee)

Use PaymentBuilder.createAtomicSplitInstructions() to generate the transfer instructions:

import { PaymentBuilder } from "@ziongateway/sdk";
import { Connection, Transaction, PublicKey } from "@solana/web3.js";
import { 
    getAssociatedTokenAddress, 
    createAssociatedTokenAccountInstruction 
} from "@solana/spl-token";

// 1. Receive 402 response with payment requirements
const res = await fetch("/api/protected");
if (res.status !== 402) return;
const requirements = await res.json();

// 2. Create atomic split transfer instructions
const connection = new Connection("https://api.devnet.solana.com");
const instructions = await PaymentBuilder.createAtomicSplitInstructions({
    sender: wallet.publicKey.toBase58(),
    recipient: requirements.recipient,    // Developer wallet
    treasury: requirements.treasury,      // Zion treasury
    amount: requirements.amount,          // Total amount (fee split handled automatically)
    asset: requirements.asset,            // USDC mint
    connection
});

// 3. Build transaction (create ATAs if needed)
const tx = new Transaction();
const assetMint = new PublicKey(requirements.asset);

// Check if recipient/treasury ATAs exist, create if not
const recipientAta = await getAssociatedTokenAddress(
    assetMint, 
    new PublicKey(requirements.recipient)
);
const treasuryAta = await getAssociatedTokenAddress(
    assetMint, 
    new PublicKey(requirements.treasury)
);

const recipientInfo = await connection.getAccountInfo(recipientAta);
if (!recipientInfo) {
    tx.add(createAssociatedTokenAccountInstruction(
        wallet.publicKey, 
        recipientAta, 
        new PublicKey(requirements.recipient), 
        assetMint
    ));
}

const treasuryInfo = await connection.getAccountInfo(treasuryAta);
if (!treasuryInfo) {
    tx.add(createAssociatedTokenAccountInstruction(
        wallet.publicKey, 
        treasuryAta, 
        new PublicKey(requirements.treasury), 
        assetMint
    ));
}

// Add the split transfer instructions
tx.add(...instructions);

// 4. Sign and send
const { blockhash } = await connection.getLatestBlockhash();
tx.recentBlockhash = blockhash;
tx.feePayer = wallet.publicKey;

const signature = await wallet.sendTransaction(tx, connection);
await connection.confirmTransaction(signature, "confirmed");

[!IMPORTANT] The transaction must be atomic - both transfers happen in the same transaction. This ensures the payment is valid and cannot be partially completed.


3. Creating the Payment Header

After the transaction is confirmed, create the payment header:

const paymentHeader = PaymentBuilder.createHeader({
    signature,                              // Transaction signature
    sender: wallet.publicKey.toBase58(),
    recipient: requirements.recipient,
    amount: requirements.amount,
    asset: requirements.asset,
    timestamp: Math.floor(Date.now() / 1000),
    nonce: crypto.randomUUID(),             // Unique per request
    network: "solana-devnet"
});

4. Retry with Authorization Header

const response = await fetch("/api/protected", {
    headers: {
        "Authorization": `Bearer ${paymentHeader}`
    }
});

if (response.ok) {
    const data = await response.json();
    console.log("Success!", data);
}

Complete Example

Here's a full example combining all the steps:

import { PaymentBuilder } from "@ziongateway/sdk";
import { Connection, Transaction, PublicKey } from "@solana/web3.js";
import { getAssociatedTokenAddress, createAssociatedTokenAccountInstruction } from "@solana/spl-token";

async function accessProtectedAPI(wallet: WalletAdapter) {
    const API_URL = "https://api.example.com/ai-endpoint";
    const connection = new Connection("https://api.devnet.solana.com");

    // Step 1: Try to access the resource
    let response = await fetch(API_URL);
    
    if (response.status === 402) {
        const requirements = await response.json();
        
        // Step 2: Create atomic split transaction
        const instructions = await PaymentBuilder.createAtomicSplitInstructions({
            sender: wallet.publicKey.toBase58(),
            recipient: requirements.recipient,
            treasury: requirements.treasury,
            amount: requirements.amount,
            asset: requirements.asset,
            connection
        });

        const tx = new Transaction();
        const assetMint = new PublicKey(requirements.asset);

        // Add ATA creation if needed (omitted for brevity)
        tx.add(...instructions);

        const { blockhash } = await connection.getLatestBlockhash();
        tx.recentBlockhash = blockhash;
        tx.feePayer = wallet.publicKey;

        const signature = await wallet.sendTransaction(tx, connection);
        await connection.confirmTransaction(signature, "confirmed");

        // Step 3: Create payment header
        const paymentHeader = PaymentBuilder.createHeader({
            signature,
            sender: wallet.publicKey.toBase58(),
            recipient: requirements.recipient,
            amount: requirements.amount,
            asset: requirements.asset,
            timestamp: Math.floor(Date.now() / 1000),
            nonce: crypto.randomUUID(),
            network: requirements.network
        });

        // Step 4: Retry with payment proof
        response = await fetch(API_URL, {
            headers: { "Authorization": `Bearer ${paymentHeader}` }
        });
    }

    return response.json();
}

On this page