Integration

Integrate in 5 lines.

@onleash/sdkis a TypeScript SDK that wraps the Anchor program. Works standalone or as a solana-agent-kit plugin with Zod-schema'd AI tool actions.

Install

pnpm add github:amalia020/onleash @solana/web3.js @solana/spl-token

✓ Orca Whirlpools compatible

Onleash-protected tokens work in Orca pools via TokenBadge. Policy enforcement doesn't break DeFi composability — your agent can still swap through the deepest Solana liquidity.

View compatibility notes →

Quickstart

Deploy a protected mint in 3 steps.

1Connect the client
import { Connection, Keypair } from "@solana/web3.js";
import { Wallet } from "@coral-xyz/anchor";
import { OnleashClient } from "@onleash/sdk";

const conn   = new Connection("https://api.devnet.solana.com", "confirmed");
const wallet = new Wallet(payer); // your Keypair
const client = new OnleashClient(conn, wallet);
2Deploy a policy-protected mint
const { mint, policy, signatures } = await client.deployProtectedMint({
  decimals:  6,
  perTxMax:  10n * 1_000_000n,   // 10 tokens per transfer
  dailyCap:  50n * 1_000_000n,   // 50 tokens per 24h rolling window
  allowlist: [approvedPoolATA],  // up to 8 approved destinations
});
// Every transfer of this mint now runs the on-chain hook.
3Send a transfer — hook validates automatically
// This throws if the policy is violated. The chain reverts atomically.
await client.transfer({
  mint,
  source:      sourceATA,
  destination: approvedPoolATA,
  owner:       payer,
  amount:      5n * 1_000_000n,
  decimals:    6,
});

Error handling

Every policy violation throws a typed error.

The chain reverts atomically — funds never move. Parse the error code from the transaction logs to give your users a clear message.

import { OnleashClient, OnleashError } from "@onleash/sdk";

try {
  await client.transfer({
    mint,
    source:      sourceATA,
    destination: someATA,
    owner:       payer,
    amount:      5n * 1_000_000n,
    decimals:    6,
  });
} catch (err: any) {
  const msg = err?.message ?? String(err);

  if (/6001|DestinationNotAllowed/.test(msg)) {
    // destination not in allowlist — check your allowlist or add the ATA
    console.error("Blocked: destination not approved");
  } else if (/6002|ExceedsPerTxMax/.test(msg)) {
    // single transfer too large — split into smaller amounts
    console.error("Blocked: amount exceeds per-tx cap");
  } else if (/6003|ExceedsDailyCap/.test(msg)) {
    // daily budget exhausted — wait for the 24h window to roll over
    console.error("Blocked: daily cap reached");
  } else if (/6007|PolicyPaused/.test(msg)) {
    // authority paused the policy — contact the mint authority
    console.error("Blocked: policy is paused");
  } else if (/6008|CooldownActive/.test(msg)) {
    // too soon after last transfer — respect the cooldown_secs interval
    console.error("Blocked: cooldown active, retry later");
  } else if (/6009|ExceedsTransferCount/.test(msg)) {
    // daily transfer count limit hit — wait for window to reset
    console.error("Blocked: daily transfer count exceeded");
  } else {
    throw err; // unexpected — rethrow
  }
}

tip · pre-flight check

Call client.fetchPolicy(mint) before sending to read current caps, allowlist, and spentToday. The chain enforces anyway — this just gives you a clearer error message before the transaction lands.

AI agent actions

Drop into solana-agent-kit, Vercel AI, or LangChain.

createOnleashActions(client)returns four Zod-schema'd tool actions ready for any LLM framework.

import { createOnleashActions } from "@onleash/sdk";

// solana-agent-kit
const tools = createVercelAITools(agent, [...createOnleashActions(client)]);

// LangChain
const tools = createLangchainTools(agent, [...createOnleashActions(client)]);
onleash_deploy_protected_mint

Create a new Token-2022 mint with the Onleash hook and initialize its on-chain Policy.

onleash_update_policy

Update caps or allowlist on an existing Policy. Only the authority can call this.

onleash_get_policy

Fetch the on-chain Policy for a mint. Returns caps, allowlist, and today's spend.

onleash_protected_transfer

Send a hooked transfer. Chain validates allowlist + caps atomically before settling.

API reference · OnleashClient

deployProtectedMint(params)

Create a Token-2022 mint + ExtraAccountMetaList + Policy in three transactions. Returns mint address, policy PDA, and signatures.

mintTo({ mint, destinationOwner, amount })

Mint tokens to an ATA, creating the account if missing. Caller must be mint authority.

transfer({ mint, source, destination, owner, amount, decimals })

Send a hooked transfer via createTransferCheckedWithTransferHookInstruction. Throws on policy violation.

fetchPolicy(mint)

Read the on-chain Policy PDA. Returns authority, caps, allowlist, and spentToday.

ixInitPolicy(params)

Build the initPolicy instruction without sending. Useful for composing with other instructions.

ixUpdatePolicy(params)

Build the updatePolicy instruction. Pass null fields to leave them unchanged.

policyPda(mint)

Derive the Policy PDA address for a given mint (seeds: ["policy", mint]).

Policy schema

PDA seeds: [b"policy", mint.key().as_ref()]

FieldTypeNotes
authorityPubkeyOnly this key can call update_policy
mintPubkeyBound at init — policy PDA is per-mint
per_tx_maxu64Per-single-transfer cap, raw units
daily_capu64Per 24h rolling window cap, raw units
day_start_unixi64Window anchor — auto-rolls when ≥86400s elapsed
spent_todayu64Mutated atomically inside the hook
transfers_todayu32Count of transfers in current 24h window
destination_allowlistVec<Pubkey>Up to 8 approved destination token accounts
pausedbooltrue = all transfers halted; set via update_policy
cooldown_secsi64Min seconds between transfers; 0 = disabled
last_transfer_unixi64Timestamp of last successful transfer
max_transfers_per_dayu32Max transfer count per 24h window; 0 = unlimited

Error codes

CodeNameMeaning
6000NotTransferringHook called outside a transfer context
6001DestinationNotAllowedDestination ATA not in destination_allowlist
6002ExceedsPerTxMaxamount > per_tx_max
6003ExceedsDailyCapspent_today + amount > daily_cap
6004AllowlistTooLongTried to set more than 8 allowlist entries
6005UnauthorizedNon-authority called update_policy
6006Overflowu64 overflow in spent_today math
6007PolicyPausedpaused=true — authority has halted all transfers
6008CooldownActivenow < last_transfer_unix + cooldown_secs
6009ExceedsTransferCounttransfers_today >= max_transfers_per_day

Spend-log webhook

Get notified on every policy violation.

client.watchViolations() subscribes to on-chain logs and POSTs a structured payload to any URL you configure — your Slack bot, PagerDuty, custom dashboard, or serverless function. No polling. Fires within one slot of the rejected transaction.

import { OnleashClient } from "@onleash/sdk";

const client = new OnleashClient(connection, wallet);

// Watch a specific mint — fires on every policy violation for that mint
const unsub = client.watchViolations({
  mint:       myMint,
  webhookUrl: "https://your-server.com/onleash-webhook",
  onViolation: (v) => console.log("blocked:", v.errorName, v.signature),
});

// Watch ALL onleash mints (omit mint param)
const unsubAll = client.watchViolations({
  webhookUrl: "https://your-server.com/onleash-webhook",
});

// Stop listening
unsub();

Webhook payload · ViolationEvent

signaturestringTransaction signature of the reverted transfer — link to Solana Explorer
errorCodenumberNumeric Anchor error code (6001–6009)
errorNamestringHuman-readable name e.g. DestinationNotAllowed
errorMessagestringFull error message from the program
slotnumberSolana slot the violation was observed in
observedAtstringISO 8601 timestamp of off-chain observation

Example payload

{
  "signature":    "5KtP...q8Wz",
  "errorCode":    6001,
  "errorName":    "DestinationNotAllowed",
  "errorMessage": "Destination not in allowlist",
  "slot":         312847291,
  "observedAt":   "2026-05-06T14:22:01.483Z"
}

Slack alert example

// In your webhook receiver
app.post("/onleash-webhook", async (req, res) => {
  const v = req.body;
  await slack.chat.postMessage({
    channel: "#agent-alerts",
    text: `Onleash blocked ${v.errorName} (${v.errorCode})
` +
          `Tx: ${v.signature}`,
  });
  res.sendStatus(200);
});

Vercel serverless example

// app/api/onleash-webhook/route.ts
export async function POST(req: Request) {
  const v = await req.json();
  // log to your DB, alert system, etc.
  console.log(`[${v.errorName}] ${v.signature}`);
  await db.violations.insert(v);
  return Response.json({ ok: true });
}

Build from source

git clone https://github.com/amalia020/onleash.git
cd onleash && pnpm install

# Anchor program (Rust)
cd program && anchor build
anchor test --provider.cluster devnet --skip-local-validator

# SDK (TypeScript)
cd ../sdk && pnpm build
SOLANA_KEYPAIR=~/.config/solana/id.json pnpm smoke

Requirements: Rust 1.79+, Solana CLI 3.1+, Anchor 1.0.2, Node 22+, pnpm 9+