Skip to content
 
 

Repository files navigation

@abstraxn/agent-kit

npm version npm downloads CI License: MIT

Backend SDK for creating and managing Web3 AI agents with auto-provisioned Abstraxn server wallets.

Report Bug · Request Feature

Installation

npm install @abstraxn/agent-kit

Prerequisites

Table of Contents

Basic Integration

1. Initialize the SDK

import { AgentKitClient } from '@abstraxn/agent-kit';

const agentKit = new AgentKitClient({
  apiKey: process.env.ABSTRAXN_API_KEY!,
  wallet: 'server', // default — auto-provision Abstraxn server wallet
});

2. Create an Agent (with auto-provisioned wallet)

This is the primary flow. The SDK will:

  • Generate a P-256 access key pair
  • Create a server wallet via Abstraxn
  • Retrieve the wallet's EVM address
  • Register the agent in the backend
const { agent, wallet } = await agentKit.createAgent({
  name: 'Trading Assistant',
  description: 'AI agent that executes trades within spend policy',
  userIdentity: 'user-123@example.com',
  userName: 'John Doe',         // optional
  userEmail: 'john@example.com', // optional
  metadata: { role: 'trader' },  // optional
});

// ─── Agent Details ───
console.log('Agent ID:', agent.id);
console.log('Agent API Key:', agent.apiKey);
console.log('Is Active:', agent.isActive);

// ─── Wallet Details ───
console.log('EVM Address:', wallet.evmAddress);
console.log('Organization ID:', wallet.organizationId);

// ⚠️ IMPORTANT: Store these securely in your backend database.
// The accessKey is the ONLY way to sign transactions for this wallet.
// It CANNOT be retrieved again.
console.log('Access Key:', wallet.accessKey);

await saveToDatabase({
  agentId: agent.id,
  accessKey: wallet.accessKey!,        // secret — encrypt at rest (server wallet only)
  evmAddress: wallet.evmAddress,
  organizationId: wallet.organizationId,
});

2b. Create an Agent (external / bring-your-own wallet)

Use your own EVM or Solana address — no server wallet or accessKey is created:

const agentKit = new AgentKitClient({
  apiKey: process.env.ABSTRAXN_API_KEY!,
  wallet: 'external',
});

const { agent, wallet } = await agentKit.createAgent({
  name: 'Trading Assistant',
  description: 'Uses the user MetaMask wallet',
  userIdentity: 'user-123@example.com',
  evmAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', // required (or solanaAddress)
});

console.log(wallet.type);       // 'external'
console.log(wallet.evmAddress); // saved on the agent record
// wallet.accessKey is undefined — sign txs with your own wallet stack

You can also override per call without changing client config:

const { agent, wallet } = await agentKit.createAgent({
  wallet: 'external',
  evmAddress: '0x...',
  name: '...',
  description: '...',
  userIdentity: '...',
});

createAgentDirect() is equivalent to external mode (POST /agents with addresses only).

3. List Agents

const { items, total } = await agentKit.listAgents({
  offset: 0,
  limit: 20,
});

console.log(`Total agents: ${total}`);
items.forEach((agent) => {
  console.log(`${agent.name} — ${agent.evmAddress} — active: ${agent.isActive}`);
});

4. Get Agent by ID

const agent = await agentKit.getAgent('agent-uuid-here');
console.log(agent.name, agent.evmAddress);

5. Update Agent

const updated = await agentKit.updateAgent('agent-uuid-here', {
  name: 'Updated Agent Name',
  description: 'Updated description',
  isActive: false,
});

6. Set Spend Policy

// Enable a daily budget of $5
const agent = await agentKit.updateSpendPolicy('agent-uuid-here', {
  enabled: true,
  budgetUsd: '5.00',
  period: 'daily',
  hardBlock: true,  // false = advisory only
});

// Disable spend policy
await agentKit.updateSpendPolicy('agent-uuid-here', {
  enabled: false,
});

Spend policy controls paid MCP tool budgets (x402). For on-chain interaction guardrails (contracts, recipients, amounts), use interaction policies below.

7. Interaction Policies (off-chain guardrails)

Configure per-agent rules enforced before every MCP tool call. All enabled policies must pass (AND). Enforcement is server-side — agents cannot bypass it.

Rule types (per-chain):

Rule Purpose
contractWhitelist Allow only listed contract/mint addresses
methodWhitelist Allow only matching calldata patterns (* = hex wildcard)
recipientBlacklist Block transfers to listed addresses
nativeAmountLimits Min/max for native token amounts
tokenAmountLimits Min/max per token symbol or contract
// Create a policy
const policy = await agentKit.createInteractionPolicy('agent-uuid-here', {
  name: 'transfer-guardrails',
  enabled: true,
  hardBlock: true,
  rules: {
    recipientBlacklist: [{
      chain: 'ethereum',
      addresses: ['0xBadAddress000000000000000000000000000001'],
    }],
    nativeAmountLimits: [{
      chain: 'ethereum',
      max: '0.5',
    }],
    contractWhitelist: [{
      chain: 'ethereum',
      addresses: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'], // USDC
    }],
    methodWhitelist: [{
      chain: 'ethereum',
      contract: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
      // Full calldata pattern — length must match; * = any hex digit
      calldataPatterns: ['0xa9059cbb' + '*'.repeat(128)],
    }],
    tokenAmountLimits: [{
      chain: 'ethereum',
      token: 'USDC',
      max: '1000',
    }],
  },
});

// List all policies for an agent
const { items, total } = await agentKit.listInteractionPolicies('agent-uuid-here');

// Get, update, or delete
const one = await agentKit.getInteractionPolicy('agent-uuid-here', policy.id);
await agentKit.updateInteractionPolicy('agent-uuid-here', policy.id, {
  enabled: false,
});
await agentKit.deleteInteractionPolicy('agent-uuid-here', policy.id);

Agent app integration: expose policy CRUD from your backend (not the browser) using your dashboard API key. Map your user → agent.id, then call the SDK methods above. See your agent app's policy UI → backend → SDK flow.

Auth: policies can be managed with your dashboard API key (all agents under your app) or the agent's own Kong API key (agent.apiKey from createAgent) for that agent only.

MCP violation: when a tool call breaks a policy, MCP returns JSON-RPC -32404 with data.interactionPolicy.violations[].

8. Autonomous Transactions (delegated MCP execution)

When autonomous transactions are enabled for an agent, external MCP clients (Cursor, Claude Desktop, CLI) can queue on-chain intents instead of receiving only unsigned payloads. Your integrator backend (which holds the agent accessKey) claims, signs, broadcasts, and reports status back to Agent Kit.

Two execution paths:

Context MCP header MCP response (autonomous ON) Who signs
In-app chat managed unsigned_transaction_ready Your app backend (immediate)
External MCP delegated (default) transaction_pending Your integrator backend

When autonomous is OFF and the caller is external, MCP returns unsigned_transaction_ready only (no queue).

Enable per agent

const config = await agentKit.getAutonomousTx('agent-uuid-here');
console.log(config.enabled); // false by default

await agentKit.updateAutonomousTx('agent-uuid-here', {
  enabled: true,
  maxPendingAgeSeconds: 300,   // optional — pending tx expiry
  maxConcurrentPending: 10,     // optional — queue cap per agent
});

Combine with strict interaction policies (section 7). Enabling autonomous increases risk if policies are loose.

MCP execution context

Use createMcpClient() with the agent's Kong API key (agent.apiKey). In-app chat should send managed; external tools default to delegated.

const mcp = agentKit.createMcpClient(agent.apiKey);

// External client (Cursor / CLI) — may queue when autonomous ON
const result = await mcp.callTool('transfer', {
  to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
  amount: '1',
  token: 'POL',
  chain: 'polygon',
}, {
  executionContext: 'delegated', // default
  idempotencyKey: 'cursor-transfer-001', // optional — dedupe retries
});

// In-app chat — sign immediately on your backend
await mcp.callTool('transfer', args, {
  executionContext: 'managed',
});

When autonomous is enabled and context is delegated, on-chain tools may return transaction_pending with pendingTransactionId instead of unsigned_transaction_ready only.

Integrator execution loop

  1. Subscribe to webhooks (per dashboard API key):
await agentKit.createWebhookSubscription({
  url: 'https://api.yourapp.com/kit-webhooks/transactions',
  secret: process.env.KIT_WEBHOOK_SECRET!,
  events: ['transaction.created', 'transaction.updated', 'transaction.expired'],
});

// List or remove subscriptions
const subs = await agentKit.listWebhookSubscriptions();
await agentKit.deleteWebhookSubscription(subs[0].id);

Webhooks are HMAC-signed (X-Agent-Kit-Signature, X-Agent-Kit-Timestamp). Verify the signature before processing.

  1. On transaction.created — claim, sign with stored accessKey, broadcast, update status:
const pending = await agentKit.claimPendingTransaction(agentId, pendingId);

// Sign pending.mcpResult with your wallet stack (ServerSigner, viem, etc.)
const txHash = '0x…';

await agentKit.updatePendingTransaction(agentId, pendingId, {
  status: 'submitted',
  txHash,
});
await agentKit.updatePendingTransaction(agentId, pendingId, {
  status: 'confirmed',
  txHash,
});
  1. Poll as fallback if webhooks are unavailable:
const { items } = await agentKit.listPendingTransactions(agentId, {
  status: 'pending',
  limit: 20,
});

const one = await agentKit.getPendingTransaction(agentId, pendingId);
await agentKit.cancelPendingTransaction(agentId, pendingId); // optional

Auth: autonomous config, pending txs, and webhooks use your dashboard API key (integrator). MCP tool calls use the agent's Kong API key (agent.apiKey).

See also: Autonomous transactions guide (Abstraxn docs).

9. Delete Agent

await agentKit.deleteAgent('agent-uuid-here');

10. Register ERC-8004 On-Chain Identity

Server wallet

After creating an agent and funding its server wallet with native gas on the target chain:

const identity = await agentKit.registerAgentIdentity({
  agentId: agent.id,
  userIdentity: 'user-123@example.com',
  accessKey: wallet.accessKey,
  organizationId: wallet.organizationId,
  evmAddress: wallet.evmAddress,
  chainId: 11155111, // Sepolia — backend must have IdentityRegistry configured for this chain
});

console.log(identity.registration.agentIdentity);
// eip155:11155111:0x8004A818BFB912233c491871b3d84c89A494BD9e:42

const stored = await agentKit.getAgentIdentity(agent.id, 11155111);

External wallet (bring-your-own signer)

Registry is a 3-step flow: prepare (backend) → sign (your wallet) → confirm (backend).

import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { sepolia } from 'viem/chains';

// 1. Create agent with your address (no server wallet)
const { agent } = await agentKit.createAgent({
  wallet: 'external',
  evmAddress: process.env.DEV_EVM_ADDRESS!,
  name: 'My Agent',
  description: '...',
  userIdentity: 'user@example.com',
});

// 2. Register — you provide the signer
const identity = await agentKit.registerAgentIdentityExternal({
  agentId: agent.id,
  evmAddress: process.env.DEV_EVM_ADDRESS!,
  chainId: 11155111,
  signTransaction: async ({ prepare, fromAddress }) => {
    const account = privateKeyToAccount(process.env.DEV_PRIVATE_KEY as `0x${string}`);
    const walletClient = createWalletClient({
      account,
      chain: sepolia,
      transport: http(prepare.rpcUrl),
    });

    const hash = await walletClient.sendTransaction({
      account: fromAddress,
      chain: sepolia,
      to: prepare.registerCall.to as `0x${string}`,
      data: prepare.registerCall.data,
      value: BigInt(prepare.registerCall.value || '0'),
    });
    return hash;
  },
});

Manual steps (same APIs, full control):

const prepare = await agentKit.prepareAgentIdentity(agent.id, { chainId: 11155111 });
// sign prepare.registerCall with MetaMask / viem / ethers…
const confirmed = await agentKit.confirmAgentIdentity(agent.id, {
  chainId: 11155111,
  txHash: '0x…',
});

Requirements for external registry:

  • Agent evmAddress must match the account that signs (and holds gas on that chainId).
  • Backend must have IdentityRegistry configured for the chainId.
  • registerAgentIdentity() (server) needs accessKey + organizationId; external flow does not.

Registration file (public): GET {AGENT_KIT_PUBLIC_URL}/agents/{agentId}/registration


Signing Transactions with the Wallet

After creating an agent, use the stored accessKey to sign transactions via the ServerSignerClient:

const signer = agentKit.getServerSigner();

// Authenticate using the stored access key
const session = await signer.authenticate({
  userIdentity: 'user-123@example.com',
  accessKey: storedAccessKey, // retrieved from your database
});

// Create a viem public client for EVM operations
const publicClient = signer.createPublicClient({
  rpcUrl: 'https://rpc-amoy.polygon.technology',
  chainId: 80002,
  organizationId: storedOrganizationId,
  fromAddress: storedEvmAddress as `0x${string}`,
});

// Send a transaction
const prepared = await publicClient.prepareTransaction({
  to: '0x1111111111111111111111111111111111111111' as `0x${string}`,
  value: 1_000_000_000_000_000n, // 0.001 ETH
});

const txHash = await publicClient.signAndSendPreparedTransaction(
  prepared.unsignedTransaction,
);
const receipt = await publicClient.waitForTransactionReceipt(txHash);
console.log('Transaction confirmed:', receipt.transactionHash);

// Sign a message
const signature = await publicClient.signMessage({
  message: 'Hello from my agent',
});

Full Integration Example

A complete backend service that creates an agent and later uses it to send a transaction:

import { AgentKitClient } from '@abstraxn/agent-kit';

const agentKit = new AgentKitClient({
  apiKey: process.env.ABSTRAXN_API_KEY!,
});

// ── Step 1: Create agent + wallet (run once, store results) ──

async function onboardAgent(userEmail: string) {
  const { agent, wallet } = await agentKit.createAgent({
    name: `Agent for ${userEmail}`,
    description: 'Autonomous trading agent',
    userIdentity: userEmail,
    userEmail,
  });

  // Store in your database
  return {
    agentId: agent.id,
    apiKey: agent.apiKey,
    accessKey: wallet.accessKey,           // ⚠️ encrypt at rest
    evmAddress: wallet.evmAddress,
    organizationId: wallet.organizationId,
  };
}

// ── Step 2: Use the agent to sign & send transactions ──

async function executeAgentTrade(agentRecord: {
  accessKey: string;
  evmAddress: string;
  organizationId: string;
  userIdentity: string;
}) {
  const signer = agentKit.getServerSigner();

  await signer.authenticate({
    userIdentity: agentRecord.userIdentity,
    accessKey: agentRecord.accessKey,
  });

  const client = signer.createPublicClient({
    rpcUrl: process.env.RPC_URL!,
    chainId: Number(process.env.CHAIN_ID!),
    organizationId: agentRecord.organizationId,
    fromAddress: agentRecord.evmAddress as `0x${string}`,
  });

  const prepared = await client.prepareTransaction({
    to: '0xRecipientAddress...' as `0x${string}`,
    value: 500_000_000_000_000n,
  });

  const txHash = await client.signAndSendPreparedTransaction(
    prepared.unsignedTransaction,
  );
  const receipt = await client.waitForTransactionReceipt(txHash);
  return receipt;
}

// ── Step 3: Manage agents ──

async function manageAgents() {
  // List all agents
  const { items } = await agentKit.listAgents();

  // Set spend limits
  for (const agent of items) {
    await agentKit.updateSpendPolicy(agent.id, {
      enabled: true,
      budgetUsd: '10.00',
      period: 'daily',
      hardBlock: true,
    });

    // Set interaction guardrails (optional)
    await agentKit.createInteractionPolicy(agent.id, {
      name: 'default-guardrails',
      rules: {
        nativeAmountLimits: [{ chain: 'ethereum', max: '1.0' }],
      },
    });
  }

  // Deactivate an agent
  await agentKit.updateAgent(items[0].id, { isActive: false });
}

Configuration

Option Required Default Description
apiKey Yes — API key from the Abstraxn dashboard
baseUrl No https://agent-kit.abstraxn.com Agent Kit service base URL
retryCount No 1 Number of retries for transient failures
fetch No Global fetch Custom fetch implementation

Error Handling

All errors extend AgentKitError with code and statusCode properties:

import {
  AgentKitError,
  ValidationError,     // 400 — invalid input
  BadRequestError,     // 400 — server rejected request
  UnauthorizedError,   // 401 — invalid or missing API key
  ForbiddenError,      // 403 — insufficient permissions
  NotFoundError,       // 404 — agent not found
  ConflictError,       // 409 — duplicate resource
  NetworkError,        // network connectivity issue
} from '@abstraxn/agent-kit';

try {
  await agentKit.createAgent({ ... });
} catch (error) {
  if (error instanceof UnauthorizedError) {
    console.error('Invalid API key — check your dashboard');
  } else if (error instanceof NotFoundError) {
    console.error('Agent not found');
  } else if (error instanceof AgentKitError) {
    console.error(`[${error.code}] ${error.message}`);
  }
}

Security Notes

  • Store accessKey in a secure secrets manager or encrypted database column.
  • Never expose accessKey in frontend code or logs.
  • The mcpToken (from bindAgent) is a one-time value — store it securely on first receipt.
  • Rotate API keys via the Abstraxn dashboard if compromised.
  • Found a vulnerability? See SECURITY.md for how to report it privately.

Examples

Runnable examples live in examples/:

ABSTRAXN_API_KEY=... npm run example:register-identity

Contributing

Contributions are welcome — see CONTRIBUTING.md for dev setup, commit conventions, and the release process. See CHANGELOG.md for release history.

Support

License

MIT

About

Backend SDK for creating and managing Web3 AI agents with auto-provisioned Abstraxn server wallets.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages