npm.io
1.2.0 • Published 5h ago

@abstraxn/agent-kit

Licence
MIT
Version
1.2.0
Deps
1
Size
86 kB
Vulns
0
Weekly
105

@abstraxn/agent-kit

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

Installation

npm install @abstraxn/agent-kit

Prerequisites


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.

License

MIT

Keywords