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:
- Agent token mint address — the token mint created when the agent coin was launched on pump.fun.
- Payment currency — USDC or SOL (determines the
currencyMint). - Price / amount — how much to charge per request, in the currency's smallest unit.
- Service to deliver — what the agent should do after payment is confirmed (generate content, access an API, etc.).
- 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 > 0before creating an invoice. - Always ensure
endTime > startTimeand 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
validateInvoicePaymentbefore 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:
# 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=<your-agent-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
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:
- Before installing
@solana/web3.js,@solana/spl-token, or any@solana/wallet-adapter-*package, first check what versions@pump-fun/agent-payments-sdkdeclares in its ownpackage.json(inspect it vianpm 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. - Never blindly install "latest" for these shared packages. Always prefer the version that is most compatible with the latest
@pump-fun/agent-payments-sdk. - 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.
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
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:
const agent = new PumpAgent(agentMint);
With connection — also enables RPC-based verification fallback and balance queries:
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 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
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:
SetComputeUnitLimitis always prepended (default92_849CU). Override viacomputeUnitLimitif your transactions fail with "compute exceeded".SetComputeUnitPriceis prepended only whencomputeUnitPriceis 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:
- Create the user's wrapped SOL token account (idempotent)
- Transfer SOL lamports into that token account
- Sync the native balance
- The accept-payment instruction
- 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, andendTimemust 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
memofor 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)
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.
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):
import { Connection, Transaction } from "@solana/web3.js";
async function signAndSendPayment(
txBase64: string,
signTransaction: (tx: Transaction) => Promise<Transaction>,
connection: Connection,
): Promise<string> {
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):
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 <button onClick={handlePay}>Pay</button>;
}
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
- Derives the Invoice ID PDA from
(mint, currencyMint, amount, memo, startTime, endTime). - Queries the Pump HTTP API to check if the payment event exists.
- If the HTTP API is unavailable and a
Connectionwas provided, falls back to scanning on-chain transaction logs via RPC. - Returns
trueif a matching payment event is found,falseotherwise.
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
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:
async function verifyPayment(params: {
user: string;
currencyMint: string;
amount: number;
memo: number;
startTime: number;
endTime: number;
}): Promise<boolean> {
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 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.