SDK DOCUMENTATION
// REFERENCE

PayKit SDK

PayKit is an open-source SDK for registering autonomous AI agents on Solana, enforcing spend limits, and recording payment history immutably onchain. It is the accountability infrastructure layer for the autonomous AI economy.

PayKit is not a payment processor. It is an accountability layer. The SDK wraps the PayKit Anchor program deployed on Solana Devnet and exposes a simple JavaScript/TypeScript API that any developer can integrate in minutes.

Current version (Camino A): Agents are tied to their owner's wallet — the owner signs transactions, and the agent's PDA stores the accountability data. Camino B (agents with their own keypairs and token accounts) is on the roadmap.
PROGRAM ID
F27DrerUQGnk...
NETWORK
Solana Devnet
FRAMEWORK
Anchor 0.31.1

INSTALLATION

Install the PayKit SDK via npm or yarn:

npm install @paykit/sdk

Or with yarn:

yarn add @paykit/sdk

The SDK requires Node.js 18+ and a Solana keypair file to sign transactions.

QUICKSTART

Get up and running in under 5 minutes:

const { createClient } = require("@paykit/sdk");

// 1. Create a client with your keypair
const client = createClient("/path/to/keypair.json", "devnet");

// 2. Register an AI agent with a 1 SOL spend limit
const { agentPDA, tx } = await client.registerAgent(
  "my-agent",
  1_000_000_000 // 1 SOL in lamports
);
console.log("Agent registered:", agentPDA.toBase58());

// 3. Record a payment made by the agent
await client.recordPayment(
  "my-agent",
  1_000_000, // 0.001 SOL
  recipientPublicKey,
  "API call payment"
);

// 4. Agent pays another agent autonomously
await client.agentToAgentPayment(
  "my-agent",
  "other-agent",
  250_000,
  "Data analysis service"
);

// 5. Fetch agent state
const agent = await client.fetchAgent("my-agent");
console.log("Total spent:", agent.totalSpent.toString());
console.log("Days remaining:", (await client.checkAgentExpiry("my-agent")).daysRemaining);

registerAgent

asynconchain

Registers a new AI agent onchain. Creates a PDA (Program Derived Address) account that stores the agent's identity, spend limit, payment history, and expiration date. Each agent is uniquely identified by the combination of its owner wallet and name. Agents expire after 365 days by default and can be renewed with renewAgent.

const { tx, agentPDA } = await client.registerAgent(name, spendLimitLamports);

// PARAMETERS

namestringUnique identifier for the agent. Maximum 32 characters. Used to derive the agent's PDA address. required
spendLimitLamportsnumberMaximum total spend allowed for this agent, in lamports. 1 SOL = 1,000,000,000 lamports. Cannot be exceeded by the contract. required

// RETURNS

{
  tx: string,          // Transaction signature
  agentPDA: PublicKey  // Onchain address of the agent account
}

// EXAMPLE

const { tx, agentPDA } = await client.registerAgent(
  "trading-agent-01",
  5_000_000_000  // 5 SOL
);
console.log("Agent PDA:", agentPDA.toBase58());

recordPayment

asynconchainenforces spend limit

Records a payment made by an agent onchain. Increments the agent's total_spent counter and payment_count. The contract enforces that total_spent never exceeds spend_limit, and that the agent does not exceed 10% of its total budget in any 24-hour period.

const { tx } = await client.recordPayment(agentName, amountLamports, recipient, memo);

// PARAMETERS

agentNamestringName of the agent making the payment. required
amountLamportsnumberPayment amount in lamports. required
recipientPublicKeyPublic key of the payment recipient. required
memostringPayment description. Maximum 64 characters. required

// RETURNS

{ tx: string }

// EXAMPLE

await client.recordPayment(
  "trading-agent-01",
  500_000,
  new PublicKey("recipient..."),
  "OpenAI API call - GPT-4"
);

agentToAgentPayment

asynconchainenforces spend limit

Records a direct payment from one registered agent to another. Both agents must be registered, active, and not expired. The sender's spend limit and daily limit are enforced by the contract. The receiver's payment_count is incremented atomically.

const { tx } = await client.agentToAgentPayment(senderName, receiverName, amountLamports, service);

// PARAMETERS

senderNamestringName of the agent sending the payment. required
receiverNamestringName of the agent receiving the payment. required
amountLamportsnumberPayment amount in lamports. required
servicestringDescription of the service being paid for. Maximum 64 characters. required

// EXAMPLE

await client.agentToAgentPayment(
  "agent-alpha",
  "agent-beta",
  250_000,
  "Market data analysis - Q4 2026"
);

batchPayment

asynconchainatomicmax 5 payments

Sends payments from one agent to multiple agents in a single Solana transaction. All payments succeed or all fail atomically — there is no partial execution. This is the key primitive for orchestrator patterns where one agent delegates work and pays multiple specialized agents.

Use case: An orchestrator agent receives a complex task, breaks it into subtasks, assigns them to specialized agents, and pays all of them in a single atomic transaction once the work is complete.
const { tx, count } = await client.batchPayment(senderName, payments);

// PARAMETERS

senderNamestringName of the agent sending all payments. required
paymentsarrayArray of payment objects. Maximum 5 items. required

// PAYMENT OBJECT

receiverNamestringName of the receiving agent. required
amountLamportsnumberPayment amount in lamports. required
servicestringService description. Maximum 64 characters. required

// RETURNS

{
  tx: string,   // Transaction signature
  count: number // Number of payments executed
}

// EXAMPLE

// Orchestrator pays 3 specialized agents in 1 transaction
const { tx, count } = await client.batchPayment("orchestrator", [
  { receiverName: "agent-researcher", amountLamports: 100_000, service: "Web research" },
  { receiverName: "agent-writer",     amountLamports: 150_000, service: "Content generation" },
  { receiverName: "agent-reviewer",   amountLamports: 50_000,  service: "Quality review" },
]);
console.log(`${count} payments confirmed in TX: ${tx}`);

updateSpendLimit

asynconchainowner only

Updates the spend limit of an existing agent. Only the owner wallet can call this. Does not reset total_spent or daily_spent.

await client.updateSpendLimit(agentName, newLimitLamports);

// PARAMETERS

agentNamestringName of the agent to update. required
newLimitLamportsnumberNew spend limit in lamports. Must be greater than zero. required

// EXAMPLE

await client.updateSpendLimit("trading-agent-01", 10_000_000_000); // 10 SOL

deactivateAgent

asynconchainirreversibleowner only

Permanently deactivates an agent. Once deactivated, the agent cannot make or receive payments. This action is irreversible. The agent account remains onchain as an immutable audit record.

await client.deactivateAgent(agentName);

// EXAMPLE

await client.deactivateAgent("trading-agent-01");

renewAgent

asynconchainowner only

Extends an agent's expiration date. If the agent is already expired, the extension starts from the current time. If the agent is still active, the extension is added to the current expiration date. Only agents created with the current contract version support renewal — legacy agents (created before the expires_at field was added) cannot be renewed.

const { tx } = await client.renewAgent(agentName, extensionSeconds);

// PARAMETERS

agentNamestringName of the agent to renew. required
extensionSecondsnumberNumber of seconds to extend. 31_536_000 = 1 year. required

// EXAMPLE

// Renew for 1 year
await client.renewAgent("my-agent", 31_536_000);

// Renew for 6 months
await client.renewAgent("my-agent", 15_768_000);

checkAgentExpiry

asyncread only

Returns the expiration status of an agent. Useful for checking before executing a payment, or for building renewal UIs.

const expiry = await client.checkAgentExpiry(agentName, ownerPubkey?);

// RETURNS

{
  expired: boolean,      // true if agent has expired
  expiresAt: Date,       // JavaScript Date object of expiration
  daysRemaining: number  // 0 if expired, otherwise days until expiration
}

// EXAMPLE

const expiry = await client.checkAgentExpiry("my-agent");

if (expiry.expired) {
  console.log("Agent has expired — renewing...");
  await client.renewAgent("my-agent", 31_536_000);
} else {
  console.log(`Agent expires in ${expiry.daysRemaining} days`);
}

estimateFee

asyncread only

Estimates the Solana network fee for a payment transaction before executing it. Useful for agents that need to account for gas costs in their budget calculations.

const { fee, feeSOL } = await client.estimateFee(agentName, amountLamports, type);

// PARAMETERS

agentNamestringName of the agent that would execute the payment. required
amountLamportsnumberPayment amount in lamports. required
typestring"record" or "agent_to_agent". Defaults to "record".

// RETURNS

{
  fee: number,    // Fee in lamports
  feeSOL: string  // Fee in SOL (formatted string)
}

// EXAMPLE

const { fee, feeSOL } = await client.estimateFee("my-agent", 1_000_000, "record");
console.log(`Estimated fee: ${feeSOL} SOL`); // ~0.000005000 SOL

fetchAgent

asyncread only

Fetches the current state of a single agent account from Solana. Any public key can be inspected — this call does not require the caller to be the agent's owner.

const agent = await client.fetchAgent(agentName, ownerPubkey?);

// PARAMETERS

agentNamestringName of the agent to fetch. required
ownerPubkeyPublicKeyOwner of the agent. Defaults to the client's wallet if not provided.

// RETURNS

{
  owner: PublicKey,
  name: string,
  spendLimit: BN,
  totalSpent: BN,
  paymentCount: BN,
  isActive: boolean,
  lastPaymentAt: BN,
  dailySpent: BN,
  dailyResetAt: BN,
  expiresAt: BN
}

// EXAMPLE

const agent = await client.fetchAgent("my-agent");

const remaining = agent.spendLimit.toNumber() - agent.totalSpent.toNumber();
console.log("Remaining budget:", remaining / 1e9, "SOL");
console.log("Active:", agent.isActive);

fetchAllAgents

asyncread only

Fetches all agent accounts owned by the current wallet using a memcmp filter on the owner field.

const agents = await client.fetchAllAgents();

// EXAMPLE

const agents = await client.fetchAllAgents();
for (const agent of agents) {
  console.log(`${agent.name}: ${agent.totalSpent.toString()} lamports spent`);
}

getPaymentHistory

asyncread onlyindexes onchain events

Fetches recent transaction history from the PayKit program by reading onchain transaction logs. Categorizes each transaction by instruction type.

const history = await client.getPaymentHistory(limit?);

// PARAMETERS

limitnumberNumber of recent transactions to fetch. Defaults to 50.

// RETURNS

Array<{
  type: "agent_to_agent" | "record_payment" | "register_agent",
  time: string,  // ISO 8601 timestamp
  tx: string     // Transaction signature
}>

// EXAMPLE

const history = await client.getPaymentHistory(10);
for (const entry of history) {
  console.log(`[${entry.type}] ${entry.time} — ${entry.tx}`);
}

ERROR CODES

The PayKit smart contract returns structured error codes when a transaction fails. All errors are thrown as AnchorError.

NameTooLong6000Agent name exceeds 32 characters.
InvalidSpendLimit6001Spend limit must be greater than zero.
InvalidAmount6002Payment amount must be greater than zero.
SpendLimitExceeded6003The payment would cause the agent to exceed its total spend limit.
AgentInactive6004The agent has been deactivated and cannot make or receive payments.
MemoTooLong6005Memo or service description exceeds 64 characters.
DailyLimitExceeded6006The payment would exceed the agent's daily spend limit (10% of total per 24 hours).
AgentExpired6007The agent has expired. Use renewAgent to extend its expiration.

// HANDLING ERRORS

try {
  await client.recordPayment("my-agent", 1_000_000, recipient, "payment");
} catch (e) {
  if (e.message.includes("SpendLimitExceeded")) {
    console.log("Agent has reached its spend limit");
  } else if (e.message.includes("DailyLimitExceeded")) {
    console.log("Agent has reached its daily limit — resets in 24h");
  } else if (e.message.includes("AgentInactive")) {
    console.log("Agent is deactivated");
  } else if (e.message.includes("AgentExpired")) {
    console.log("Agent has expired — renew with renewAgent()");
  }
}

LEGACY AGENT MIGRATION

Important: Agents created before the contract upgrade that added the expires_at field cannot be deserialized against the new struct. These agents appear as LEGACY in the dashboard and cannot be renewed or used with the current contract version.

This happens because Solana account data is fixed at creation time. When the contract struct gains a new field, existing accounts on-chain don't have that field — so the new deserializer fails to read them.

// HOW TO IDENTIFY LEGACY AGENTS

Legacy agents show EXPIRES: LEGACY in the dashboard. In the SDK, their expiresAt field returns a value of 0 or throws a deserialization error.

// MIGRATION STEPS

// Step 1 — Note the legacy agent's name and spend limit
const legacy = await client.fetchAllAgents();
const legacyAgent = legacy.find(a => a.name === "my-old-agent");
const originalLimit = legacyAgent.spendLimit.toNumber();

// Step 2 — Register a new agent with the same name
// (The old PDA still exists on-chain but is effectively replaced)
const { agentPDA } = await client.registerAgent(
  "my-old-agent",  // same name
  originalLimit    // same spend limit
);

// Step 3 — Verify the new agent is active and has an expiration
const expiry = await client.checkAgentExpiry("my-old-agent");
console.log("New agent expires in:", expiry.daysRemaining, "days");
// Output: New agent expires in: 365 days
The old legacy PDA remains on-chain permanently as an immutable record. It does not consume additional rent or resources — it simply cannot be interacted with using the current contract version.

LANGCHAIN INTEGRATION

PayKit integrates naturally with LangChain agents as a custom tool. Register PayKit methods as tools that your LangChain agent can call autonomously to record payments and manage budgets.

// SETUP

npm install langchain @langchain/openai @paykit/sdk

// CUSTOM TOOL DEFINITION

const { createClient } = require("@paykit/sdk");
const { DynamicStructuredTool } = require("langchain/tools");
const { z } = require("zod");

const paykit = createClient("/path/to/keypair.json", "devnet");

// Tool: record a payment onchain
const recordPaymentTool = new DynamicStructuredTool({
  name: "record_payment",
  description: "Record a payment made by the AI agent onchain with an immutable audit trail.",
  schema: z.object({
    agentName: z.string().describe("Name of the registered PayKit agent"),
    amountSOL: z.number().describe("Payment amount in SOL"),
    recipientAddress: z.string().describe("Recipient wallet address"),
    memo: z.string().describe("Description of what the payment is for"),
  }),
  func: async ({ agentName, amountSOL, recipientAddress, memo }) => {
    const { PublicKey } = require("@solana/web3.js");
    const { tx } = await paykit.recordPayment(
      agentName,
      Math.floor(amountSOL * 1_000_000_000),
      new PublicKey(recipientAddress),
      memo
    );
    return `Payment recorded onchain. TX: ${tx}`;
  },
});

// Tool: check agent budget
const checkBudgetTool = new DynamicStructuredTool({
  name: "check_agent_budget",
  description: "Check how much budget the agent has remaining before taking action.",
  schema: z.object({
    agentName: z.string().describe("Name of the registered PayKit agent"),
  }),
  func: async ({ agentName }) => {
    const agent = await paykit.fetchAgent(agentName);
    const remaining = agent.spendLimit.toNumber() - agent.totalSpent.toNumber();
    const expiry = await paykit.checkAgentExpiry(agentName);
    return JSON.stringify({
      remainingSOL: (remaining / 1e9).toFixed(4),
      paymentCount: agent.paymentCount.toString(),
      daysUntilExpiry: expiry.daysRemaining,
    });
  },
});

// Tool: pay another agent
const payAgentTool = new DynamicStructuredTool({
  name: "pay_agent",
  description: "Pay another registered PayKit agent for a completed service.",
  schema: z.object({
    senderAgent: z.string().describe("Name of the paying agent"),
    receiverAgent: z.string().describe("Name of the receiving agent"),
    amountSOL: z.number().describe("Payment amount in SOL"),
    service: z.string().describe("Description of the service rendered"),
  }),
  func: async ({ senderAgent, receiverAgent, amountSOL, service }) => {
    const { tx } = await paykit.agentToAgentPayment(
      senderAgent,
      receiverAgent,
      Math.floor(amountSOL * 1_000_000_000),
      service
    );
    return `Agent payment confirmed. TX: ${tx}`;
  },
});

// AGENT SETUP

const { ChatOpenAI } = require("@langchain/openai");
const { AgentExecutor, createOpenAIFunctionsAgent } = require("langchain/agents");
const { ChatPromptTemplate } = require("@langchain/core/prompts");

const llm = new ChatOpenAI({ model: "gpt-4o", temperature: 0 });
const tools = [recordPaymentTool, checkBudgetTool, payAgentTool];

const prompt = ChatPromptTemplate.fromMessages([
  ["system", `You are an autonomous AI agent with a PayKit wallet on Solana.
Your agent name is "langchain-agent-01".
Always check your budget before making payments.
Record all significant actions onchain for accountability.`],
  ["human", "{input}"],
  ["placeholder", "{agent_scratchpad}"],
]);

const agent = await createOpenAIFunctionsAgent({ llm, tools, prompt });
const executor = new AgentExecutor({ agent, tools, verbose: true });

// The agent now autonomously manages payments
const result = await executor.invoke({
  input: "Check my remaining budget, then pay agent-researcher 0.001 SOL for completing the market analysis task."
});
console.log(result.output);

CREWAI INTEGRATION

PayKit works with CrewAI to give your crew members autonomous payment capabilities. Each crew member can be assigned a PayKit agent identity and pay other crew members for completed tasks.

// SETUP

pip install crewai
npm install @paykit/sdk  # PayKit runs as a sidecar Node.js service
Architecture note: Since PayKit SDK is Node.js, the recommended pattern for CrewAI (Python) is to run PayKit as a lightweight HTTP sidecar service that CrewAI tools call via requests. Alternatively, use the subprocess pattern shown below.

// PAYKIT SIDECAR SERVICE (Node.js)

// paykit-service.js — Run this alongside your CrewAI crew
const express = require("express");
const { createClient } = require("@paykit/sdk");

const app = express();
app.use(express.json());

const paykit = createClient("/path/to/keypair.json", "devnet");

app.post("/register", async (req, res) => {
  const { name, spendLimitSOL } = req.body;
  const result = await paykit.registerAgent(name, spendLimitSOL * 1e9);
  res.json({ pda: result.agentPDA.toBase58(), tx: result.tx });
});

app.post("/pay", async (req, res) => {
  const { sender, receiver, amountSOL, service } = req.body;
  const result = await paykit.agentToAgentPayment(
    sender, receiver, Math.floor(amountSOL * 1e9), service
  );
  res.json({ tx: result.tx });
});

app.post("/batch", async (req, res) => {
  const { sender, payments } = req.body;
  const result = await paykit.batchPayment(sender, payments.map(p => ({
    ...p, amountLamports: Math.floor(p.amountSOL * 1e9)
  })));
  res.json({ tx: result.tx, count: result.count });
});

app.get("/agent/:name", async (req, res) => {
  const agent = await paykit.fetchAgent(req.params.name);
  const expiry = await paykit.checkAgentExpiry(req.params.name);
  res.json({
    spendLimit: agent.spendLimit.toString(),
    totalSpent: agent.totalSpent.toString(),
    paymentCount: agent.paymentCount.toString(),
    isActive: agent.isActive,
    daysRemaining: expiry.daysRemaining,
  });
});

app.listen(3333, () => console.log("PayKit sidecar running on port 3333"));

// CREWAI TOOLS (Python)

# paykit_tools.py
import requests
from crewai.tools import BaseTool

PAYKIT_URL = "http://localhost:3333"

class RecordPaymentTool(BaseTool):
    name: str = "record_payment"
    description: str = (
        "Record a payment onchain via PayKit for accountability. "
        "Use this after completing any paid task."
    )

    def _run(self, agent_name: str, amount_sol: float,
             recipient: str, memo: str) -> str:
        res = requests.post(f"{PAYKIT_URL}/pay", json={
            "sender": agent_name,
            "receiver": recipient,
            "amountSOL": amount_sol,
            "service": memo,
        })
        data = res.json()
        return f"Payment recorded onchain. TX: {data['tx']}"

class CheckBudgetTool(BaseTool):
    name: str = "check_budget"
    description: str = (
        "Check the remaining budget and status of a PayKit agent "
        "before executing any payment."
    )

    def _run(self, agent_name: str) -> str:
        res = requests.get(f"{PAYKIT_URL}/agent/{agent_name}")
        data = res.json()
        spent = int(data["totalSpent"]) / 1e9
        limit = int(data["spendLimit"]) / 1e9
        remaining = limit - spent
        return (
            f"Agent: {agent_name} | "
            f"Spent: {spent:.4f} SOL | "
            f"Remaining: {remaining:.4f} SOL | "
            f"Payments: {data['paymentCount']} | "
            f"Days until expiry: {data['daysRemaining']}"
        )

// CREW DEFINITION

# crew.py
from crewai import Agent, Task, Crew
from paykit_tools import RecordPaymentTool, CheckBudgetTool

record_payment = RecordPaymentTool()
check_budget = CheckBudgetTool()

researcher = Agent(
    role="Research Specialist",
    goal="Gather and analyze market data",
    backstory="Expert in data research with a PayKit agent identity on Solana.",
    tools=[check_budget, record_payment],
    verbose=True,
)

writer = Agent(
    role="Content Writer",
    goal="Produce high-quality written content",
    backstory="Professional writer compensated via PayKit for each deliverable.",
    tools=[check_budget, record_payment],
    verbose=True,
)

research_task = Task(
    description="Research the current state of AI agent payment protocols.",
    agent=researcher,
    expected_output="A concise market analysis with key findings.",
)

writing_task = Task(
    description="Write a blog post based on the research findings.",
    agent=writer,
    expected_output="A 500-word blog post ready for publication.",
)

crew = Crew(
    agents=[researcher, writer],
    tasks=[research_task, writing_task],
    verbose=True,
)

result = crew.kickoff()
print(result)

SMART CONTRACT

The PayKit program is written in Rust using the Anchor framework and deployed on Solana Devnet.

PROGRAM ID
F27DrerUQGnkmVhqkEy9m46zDkni2m37Df4ogxkoDhUF
NETWORK
Solana Devnet
FRAMEWORK
Anchor 0.31.1
LANGUAGE
Rust 1.94.1

// INSTRUCTIONS

register_agentCreates a new agent PDA with name, spend limit, and 365-day expiration.
record_paymentLogs a payment. Enforces total spend limit and daily rate limit.
agent_to_agent_paymentRecords a direct payment between two registered agents.
update_spend_limitUpdates the spend limit. Owner only.
deactivate_agentPermanently disables an agent. Irreversible.
renew_agentExtends an agent's expiration by a specified number of seconds.

// AGENT ACCOUNT SCHEMA

pub struct AgentAccount {
  pub owner: Pubkey,          // Wallet that controls the agent
  pub name: String,           // Unique identifier (max 32 chars)
  pub spend_limit: u64,       // Maximum spend in lamports
  pub total_spent: u64,       // Cumulative spend in lamports
  pub payment_count: u64,     // Number of payments recorded
  pub is_active: bool,        // Active/inactive status
  pub bump: u8,               // PDA bump seed
  pub last_payment_at: i64,   // Unix timestamp of last payment
  pub daily_spent: u64,       // Amount spent today in lamports
  pub daily_reset_at: i64,    // Unix timestamp of last daily reset
  pub expires_at: i64,        // Unix timestamp of expiration (0 = legacy)
}

ARCHITECTURE

PayKit is structured as three independent layers that can be used together or separately:

┌─────────────────────────────────────────────────────┐
│         AI Agent / LangChain / CrewAI / Custom       │
├─────────────────────────────────────────────────────┤
│               PayKit SDK (Node.js)                   │
│  registerAgent · recordPayment · batchPayment        │
│  agentToAgent · renewAgent · estimateFee · expiry    │
├─────────────────────────────────────────────────────┤
│          PayKit Program (Solana / Anchor)            │
│   PDAs · Spend Limits · Rate Limiting · Expiration   │
├─────────────────────────────────────────────────────┤
│                 Solana Blockchain                    │
│          ~400ms finality · ~$0.00025/tx              │
└─────────────────────────────────────────────────────┘

// PDA DERIVATION

Each agent account is a Program Derived Address derived from the program ID, the string "agent", the owner's public key, and the agent name:

const [agentPDA] = PublicKey.findProgramAddressSync(
  [
    Buffer.from("agent"),
    ownerPublicKey.toBuffer(),
    Buffer.from(agentName),
  ],
  PROGRAM_ID
);

// RATE LIMITING

Total spend limitThe agent can never spend more than spend_limit in total across its entire lifetime. Enforced at the protocol level — cannot be bypassed.
Daily spend limitThe agent can spend at most 10% of its total spend limit per 24-hour period. Resets automatically every 24 hours.
ExpirationAgents expire after 365 days by default. Expired agents cannot make or receive payments. Renewable via renewAgent.
PAYKIT · ZERO TWO LABS · 2026
GITHUB