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
- Initial Request - Try to access the protected resource
- Handle 402 - Receive payment requirements from the server
- Create Atomic Split - Build a transaction splitting payment (99% developer, 1% treasury)
- Sign & Send - Execute the transaction on Solana
- 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();
}