SDK

@arsene/core

The TypeScript SDK wraps the three Anchor programs behind one ergonomic class. It ships with two runtimes: a mock mode (in-memory, deterministic, no chain) for tests and local dev, and a chain mode (Anchor + wallet adapter) for devnet and mainnet.

Note
The mock and chain modes expose the identical interface. You write your agent once; swap the import when you're ready to leave devnet.

ArseneAgent

The primary entry point. Construct one per agent you control.

@arsene/core
export class ArseneAgent {
  readonly pubkey: string;
  readonly name: string;
  readonly principal: string;

  constructor(config: AgentConfig);

  pay(to: string, amountLamports: bigint, note?: string): Promise<PaymentResult>;

  compromise(): void;      // demo/testing helper
  isCompromised(): boolean;

  revoke(reason?: number): void;
  pause(): void;

  get masque(): MasqueCredential | null;
  get policy(): SerrurePolicy | null;
}

AgentConfig

ts
interface AgentConfig {
  principal: string;              // base58 pubkey of the human/DAO
  name?: string;
  scopeHash?: `0x${string}`;      // default: random
  lifespanSeconds?: number;       // default: 30 days
  policy: PolicyParams;           // required — see Serrure docs
}

PaymentResult

Every pay() returns a discriminated union. Handle both branches explicitly.

ts
type PaymentResult =
  | {
      status: 'settled';
      txId: string;
      publicCommitment: `0x${string}`;
      timestamp: number;
    }
  | {
      status: 'rejected';
      reason: RejectionReason;
      message: string;
      timestamp: number;
    };

type RejectionReason =
  | 'revoked' | 'expired' | 'paused'
  | 'exceeds_per_tx' | 'rate_limited'
  | 'daily_cap' | 'total_cap'
  | 'merchant_not_allowed';

Protocol events

The protocol store exposes an event bus. Subscribe to watch credential lifecycle, policy checks, and settlements. Useful for dashboards, alerting, and anomaly detection.

ts
import { protocolStore } from '@arsene/core';

const unsub = protocolStore.subscribe((evt) => {
  switch (evt.type) {
    case 'payment:settled':   /* … */ break;
    case 'payment:rejected':  /* … */ break;
    case 'masque:revoked':    /* page the on-call */ break;
    case 'policy:paused':     /* … */ break;
    case 'policy:check':      /* evt.passed, evt.reason? */ break;
    case 'masque:attested':   /* evt.reputation */ break;
    case 'masque:issued':     /* … */ break;
    case 'policy:created':    /* … */ break;
  }
});

// later: unsub();

Framework adapters

Thin wrappers that hand an ArseneAgent instance to the framework's payment hook.

ElizaOS

ts
import { arsenePlugin } from '@arsene/eliza';

const runtime = new AgentRuntime({
  plugins: [arsenePlugin({ principal, policy: standardPolicy })],
  // ...
});

LangGraph

ts
import { withArsene } from '@arsene/langgraph';

const graph = withArsene(baseGraph, {
  principal,
  policy: enterprisePolicy,
  onRejection: async (reason) => {
    await notifyPrincipal(reason);
  },
});

Vercel AI SDK

ts
import { arseneTool } from '@arsene/ai-sdk';

const result = await generateText({
  model: openai('gpt-4o'),
  tools: {
    pay: arseneTool({ agent }),    // agent.pay behind a tool-use interface
  },
});

Mock vs. chain

In mock mode, all state lives in a singleton Map. Tests get deterministic ordering. Demos run in any browser. In chain mode, reads go through Helius-indexed accounts, writes go through the wallet adapter's signTransaction. The .pay() signature is identical; you swap from '@arsene/core/mock' for from '@arsene/core/solana'.

Tip
Every integration test in the reference implementation runs against the mock. CI is fast, parallel, and doesn't need a validator.