--- name: tokenized-agents description: Build payment flows for Pump Tokenized Agents using @pump-fun/agent-payments-sdk. Use when accepting payments, building accept-payment transactions, integrating Solana wallets, or verifying that a user has paid an invoice on-chain. metadata: author: pump-fun version: "1.0" --- # Pump Tokenized Agent Payments Pump Tokenized Agents are AI agents whose revenue is linked to a token on pump.fun. The `@pump-fun/agent-payments-sdk` lets you build payment transactions and verify invoices on Solana. ## Before You Start — Gather Required Information Before writing any code, ask the user for the following if not already provided: 1. **Agent token mint address** — the token mint created when the agent coin was launched on pump.fun. 2. **Payment currency** — USDC or SOL (determines the `currencyMint`). 3. **Price / amount** — how much to charge per request, in the currency's smallest unit. 4. **Service to deliver** — what the agent should do after payment is confirmed (generate content, access an API, etc.). 5. **Solana RPC** — which solana rpc to use, we have some defaults if they don't provide. Do not assume these values. If any are missing, ask the user before proceeding. ## Safety Rules - **NEVER** log, print, or return private keys or secret key material. - **NEVER** sign transactions on behalf of a user — you build the instruction, the user signs. - Always validate that `amount > 0` before creating an invoice. - Always ensure `endTime > startTime` and both are valid Unix timestamps. - Use the correct decimal precision for the currency (6 decimals for USDC, 9 for SOL). - **Always verify payments on the server** using `validateInvoicePayment` before delivering any service. Never trust the client alone — clients can be spoofed. ## Supported Currencies | Currency | Decimals | Smallest unit example | | ----------- | -------- | --------------------- | | USDC | 6 | `1000000` = 1 USDC | | Wrapped SOL | 9 | `1000000000` = 1 SOL | ## Environment Variables Create a `.env` (or `.env.local` for Next.js) file with the following: ```env # Solana RPC — server-side (used to build transactions and verify payments) SOLANA_RPC_URL=https://rpc.solanatracker.io/public # Solana RPC — client-side (used by wallet adapter in the browser) NEXT_PUBLIC_SOLANA_RPC_URL=https://rpc.solanatracker.io/public # The token mint address of your tokenized agent on pump.fun AGENT_TOKEN_MINT_ADDRESS= # Payment currency mint # USDC: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v # SOL (wrapped): So11111111111111111111111111111111111111112 CURRENCY_MINT=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v ``` **RPC for mainnet-beta:** The default Solana public RPC (`https://api.mainnet-beta.solana.com`) does **not** support sending transactions. Ask the user for an RPC provider, If the user has not provided their own RPC URL, use one of these free mainnet-beta endpoints that support `sendTransaction`: - **Solana Tracker** — `https://rpc.solanatracker.io/public` - **Ankr** — `https://rpc.ankr.com/solana` Read these values from `process.env` at runtime. Never hard-code mint addresses or RPC URLs. ## Install ```bash npm install @pump-fun/agent-payments-sdk@3.0.0 @solana/web3.js@^1.98.0 ``` ### Dependency Compatibility — IMPORTANT `@pump-fun/agent-payments-sdk` depends on `@solana/web3.js` and `@solana/spl-token`. When the app also installs these packages directly, mismatched versions can cause runtime errors. **Rules:** 1. Before installing `@solana/web3.js`, `@solana/spl-token`, or any `@solana/wallet-adapter-*` package, first check what versions `@pump-fun/agent-payments-sdk` declares in its own `package.json` (inspect it via `npm info @pump-fun/agent-payments-sdk dependencies`). Install the same ranges — or ranges that resolve to the same major.minor — so npm/pnpm hoists a single copy instead of two. 2. Never blindly install "latest" for these shared packages. Always prefer the version that is most compatible with the latest `@pump-fun/agent-payments-sdk`. 3. If the project already has these packages at different versions, align them to match the SDK and re-install. ## SDK Setup `PumpAgent` is the main class. It can build payment instructions and verify invoices. ```typescript import { PumpAgent } from "@pump-fun/agent-payments-sdk"; import { PublicKey } from "@solana/web3.js"; const agentMint = new PublicKey(process.env.AGENT_TOKEN_MINT_ADDRESS!); ``` ### Constructor ```typescript new PumpAgent(mint: PublicKey, environment?: "mainnet" | "devnet", connection?: Connection) ``` | Parameter | Type | Default | Description | | ------------- | ------------------------- | ----------- | ------------------------------------------------------------- | | `mint` | `PublicKey` | — | The tokenized agent's token mint address | | `environment` | `"mainnet"` \| `"devnet"` | `"mainnet"` | Network environment | | `connection` | `Connection` (optional) | `undefined` | Solana RPC connection (enables RPC fallback for verification) | **Without connection** — enough for building instructions and HTTP-based payment verification: ```typescript const agent = new PumpAgent(agentMint); ``` **With connection** — also enables RPC-based verification fallback and balance queries: ```typescript import { Connection } from "@solana/web3.js"; const connection = new Connection(process.env.SOLANA_RPC_URL!); const agent = new PumpAgent(agentMint, "mainnet", connection); ``` ## Wallet Integration (Frontend) Install `@solana/wallet-adapter-react`, `@solana/wallet-adapter-react-ui`, and `@solana/wallet-adapter-wallets`. Use `useWallet()` for `publicKey` and `signTransaction`, and `useConnection()` for the active RPC connection. MUST See [https://raw.githubusercontent.com/pump-fun/pump-fun-skills/refs/heads/main/tokenized-agents/references/WALLET_INTEGRATION.md](https://raw.githubusercontent.com/pump-fun/pump-fun-skills/refs/heads/main/tokenized-agents/references/WALLET_INTEGRATION.md) for the full WalletProvider setup, layout wrapping, and hook usage. ## Building Payment Instructions Use `buildAcceptPaymentInstructions` to get all the instructions needed for a payment. This is the recommended method — it automatically derives the user's token account and handles native SOL wrapping/unwrapping. ### Parameters (`BuildAcceptPaymentParams`) | Parameter | Type | Description | | ------------------ | ---------------------------- | ---------------------------------------------------------------------------------------------------- | | `user` | `PublicKey` | The payer's wallet address | | `currencyMint` | `PublicKey` | Mint address of the payment currency (USDC, wSOL) | | `amount` | `bigint \| number \| string` | Price in the currency's smallest unit | | `memo` | `bigint \| number \| string` | Unique invoice identifier (random number) | | `startTime` | `bigint \| number \| string` | Unix timestamp — when the invoice becomes valid | | `endTime` | `bigint \| number \| string` | Unix timestamp — when the invoice expires | | `tokenProgram` | `PublicKey` (optional) | Token program for the currency (defaults to SPL Token) | | `computeUnitLimit` | `number` (optional) | Compute unit budget (default `100_000`). Increase if transactions fail with compute exceeded. | | `computeUnitPrice` | `number` (optional) | Priority fee in microlamports per CU. If provided, a `SetComputeUnitPrice` instruction is prepended. | ### Example ```typescript const ixs = await agent.buildAcceptPaymentInstructions({ user: userPublicKey, currencyMint, amount: "1000000", // 1 USDC memo: "123456789", // unique invoice identifier startTime: "1700000000", // valid from endTime: "1700086400", // expires at }); ``` ### What It Returns The returned `TransactionInstruction[]` always starts with compute budget instructions, followed by the payment instructions: - **`SetComputeUnitLimit`** is always prepended (default `92_849` CU). Override via `computeUnitLimit` if your transactions fail with "compute exceeded". - **`SetComputeUnitPrice`** is prepended only when `computeUnitPrice` is provided. Use this to set a priority fee for faster landing during congestion. After the compute budget prefix: - **For SPL tokens (USDC):** The accept-payment instruction. - **For native SOL:** Instructions that handle wrapping/unwrapping automatically: 1. Create the user's wrapped SOL token account (idempotent) 2. Transfer SOL lamports into that token account 3. Sync the native balance 4. The accept-payment instruction 5. Close the wrapped SOL account (returns rent back to user) You do not need to handle SOL wrapping or compute budget yourself — `buildAcceptPaymentInstructions` does it for you. ### Important - The `amount`, `memo`, `startTime`, and `endTime` must exactly match when verifying later. - Each unique combination of `(mint, currencyMint, amount, memo, startTime, endTime)` can only be paid once — the on-chain Invoice ID PDA prevents duplicate payments. - Generate a unique `memo` for each invoice (e.g. `Math.floor(Math.random() * 900000000000) + 100000`). ## Full Transaction Flow — Server to Client This is the complete flow for building a transaction on the server, signing it on the client, and sending it on-chain. ### Step 1: Generate Invoice Parameters (Server) ```typescript function generateInvoiceParams() { const memo = String(Math.floor(Math.random() * 900000000000) + 100000); const now = Math.floor(Date.now() / 1000); const startTime = String(now); const endTime = String(now + 86400); // valid for 24 hours const amount = process.env.PRICE_AMOUNT || "1000000"; // e.g. 1 USDC return { amount, memo, startTime, endTime }; } ``` ### Step 2: Build Transaction and Serialize as Base64 (Server) Build the payment instructions, assemble them into a full `Transaction` with a recent blockhash and fee payer, then serialize the unsigned transaction as a base64 string for the client. ```typescript import { Connection, PublicKey, Transaction, ComputeBudgetProgram, } from "@solana/web3.js"; import { PumpAgent } from "@pump-fun/agent-payments-sdk"; async function buildPaymentTransaction(params: { userWallet: string; amount: string; memo: string; startTime: string; endTime: string; }) { const connection = new Connection(process.env.SOLANA_RPC_URL!); const agentMint = new PublicKey(process.env.AGENT_TOKEN_MINT_ADDRESS!); const currencyMint = new PublicKey(process.env.CURRENCY_MINT!); const agent = new PumpAgent(agentMint, "mainnet", connection); const userPublicKey = new PublicKey(params.userWallet); const instructions = await agent.buildAcceptPaymentInstructions({ user: userPublicKey, currencyMint, amount: params.amount, memo: params.memo, startTime: params.startTime, endTime: params.endTime, }); const { blockhash } = await connection.getLatestBlockhash("confirmed"); const tx = new Transaction(); tx.recentBlockhash = blockhash; tx.feePayer = userPublicKey; tx.add( ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 100_000 }), ...instructions, ); const serializedTx = tx .serialize({ requireAllSignatures: false }) .toString("base64"); return { transaction: serializedTx }; } ``` Return the base64 transaction string (and the invoice params like `memo`, `startTime`, `endTime`) to the client as JSON. ### Step 3: Deserialize, Sign, and Send the Transaction (Client) Deserialize the base64 transaction from the server, sign it with `signTransaction` from the wallet adapter, then send and confirm it. Call `useWallet()` and `useConnection()` only at the top level of your component; pass `signTransaction` and `connection` into the async helper so the async logic does not call hooks. **Async helper** (e.g. in a utils file or alongside your component): ```typescript import { Connection, Transaction } from "@solana/web3.js"; async function signAndSendPayment( txBase64: string, signTransaction: (tx: Transaction) => Promise, connection: Connection, ): Promise { if (!signTransaction) { throw new Error("Wallet does not support signing"); } const tx = Transaction.from(Buffer.from(txBase64, "base64")); const signedTx = await signTransaction(tx); const signature = await connection.sendRawTransaction(signedTx.serialize(), { skipPreflight: false, preflightCommitment: "confirmed", }); const latestBlockhash = await connection.getLatestBlockhash("confirmed"); await connection.confirmTransaction( { signature, ...latestBlockhash }, "confirmed", ); return signature; } ``` **Component usage** — call the hooks at the top level, then pass them into the helper (e.g. from a payment button handler): ```typescript import { useWallet, useConnection } from "@solana/wallet-adapter-react"; function PaymentButton({ txBase64 }: { txBase64: string }) { const { signTransaction } = useWallet(); const { connection } = useConnection(); const handlePay = async () => { if (!signTransaction) return; await signAndSendPayment(txBase64, signTransaction, connection); }; return ; } ``` The wallet prompts the user to approve. After signing, the serialized transaction is submitted via `sendRawTransaction` and you wait for on-chain confirmation. ## Verify Payment Use `validateInvoicePayment` to confirm that a specific invoice was paid on-chain. ### How It Works 1. Derives the Invoice ID PDA from `(mint, currencyMint, amount, memo, startTime, endTime)`. 2. Queries the Pump HTTP API to check if the payment event exists. 3. If the HTTP API is unavailable and a `Connection` was provided, falls back to scanning on-chain transaction logs via RPC. 4. Returns `true` if a matching payment event is found, `false` otherwise. ### Parameters | Parameter | Type | Description | | -------------- | ----------- | ----------------------------------- | | `user` | `PublicKey` | The wallet that paid | | `currencyMint` | `PublicKey` | Currency used for payment | | `amount` | `number` | Amount paid (smallest unit) | | `memo` | `number` | The invoice memo | | `startTime` | `number` | Invoice start time (Unix timestamp) | | `endTime` | `number` | Invoice end time (Unix timestamp) | ### Simple Backend Verification ```typescript import { PumpAgent } from "@pump-fun/agent-payments-sdk"; import { PublicKey } from "@solana/web3.js"; const agentMint = new PublicKey(process.env.AGENT_TOKEN_MINT_ADDRESS!); const agent = new PumpAgent(agentMint); const paid = await agent.validateInvoicePayment({ user: new PublicKey(userWalletAddress), currencyMint: new PublicKey(process.env.CURRENCY_MINT!), amount: 1000000, memo: 123456789, startTime: 1700000000, endTime: 1700086400, }); if (paid) { // Payment confirmed — deliver the service } else { // Payment not found } ``` No `Connection` is needed for basic verification — it uses the HTTP API by default. ### Verification with Retries Transactions may take a few seconds to confirm. Use a retry loop for reliability: ```typescript async function verifyPayment(params: { user: string; currencyMint: string; amount: number; memo: number; startTime: number; endTime: number; }): Promise { const agentMint = new PublicKey(process.env.AGENT_TOKEN_MINT_ADDRESS!); const agent = new PumpAgent(agentMint); const invoiceParams = { user: new PublicKey(params.user), currencyMint: new PublicKey(params.currencyMint), amount: params.amount, memo: params.memo, startTime: params.startTime, endTime: params.endTime, }; for (let attempt = 0; attempt < 10; attempt++) { const verified = await agent.validateInvoicePayment(invoiceParams); if (verified) return true; await new Promise((r) => setTimeout(r, 2000)); } return false; } ``` ## End-to-End Flow ``` 1. Agent decides on price → generates unique memo → sets time window ↓ 2. Server: buildAcceptPaymentInstructions(...) → returns TransactionInstruction[] ↓ 3. Server: builds full Transaction (blockhash + feePayer + instructions) → serializes as base64 ↓ 4. Client: deserializes base64 → Transaction.from(Buffer.from(txBase64, "base64")) ↓ 5. Client: signTransaction(tx) — wallet prompts user to approve ↓ 6. Client: connection.sendRawTransaction(signedTx.serialize()) → connection.confirmTransaction(signature) ↓ 7. Server: validateInvoicePayment(...) → returns true/false (ALWAYS verify server-side) ↓ 8. Agent delivers the service (or asks user to retry) ``` --- ## Scenario Tests & Troubleshooting See [https://raw.githubusercontent.com/pump-fun/pump-fun-skills/refs/heads/main/tokenized-agents/references/SCENARIOS.md](https://raw.githubusercontent.com/pump-fun/pump-fun-skills/refs/heads/main/tokenized-agents/references/SCENARIOS.md) for detailed test scenarios (happy path, duplicate rejection, expired invoices, etc.) and a troubleshooting table for common errors and for wallet Integration must follow [https://raw.githubusercontent.com/pump-fun/pump-fun-skills/refs/heads/main/tokenized-agents/references/WALLET_INTEGRATION.md](https://raw.githubusercontent.com/pump-fun/pump-fun-skills/refs/heads/main/tokenized-agents/references/WALLET_INTEGRATION.md).