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.
Quickstart
Deploy a protected mint in 3 steps.
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);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.// 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_mintCreate a new Token-2022 mint with the Onleash hook and initialize its on-chain Policy.
onleash_update_policyUpdate caps or allowlist on an existing Policy. Only the authority can call this.
onleash_get_policyFetch the on-chain Policy for a mint. Returns caps, allowlist, and today's spend.
onleash_protected_transferSend 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()]
| Field | Type | Notes |
|---|---|---|
| authority | Pubkey | Only this key can call update_policy |
| mint | Pubkey | Bound at init — policy PDA is per-mint |
| per_tx_max | u64 | Per-single-transfer cap, raw units |
| daily_cap | u64 | Per 24h rolling window cap, raw units |
| day_start_unix | i64 | Window anchor — auto-rolls when ≥86400s elapsed |
| spent_today | u64 | Mutated atomically inside the hook |
| transfers_today | u32 | Count of transfers in current 24h window |
| destination_allowlist | Vec<Pubkey> | Up to 8 approved destination token accounts |
| paused | bool | true = all transfers halted; set via update_policy |
| cooldown_secs | i64 | Min seconds between transfers; 0 = disabled |
| last_transfer_unix | i64 | Timestamp of last successful transfer |
| max_transfers_per_day | u32 | Max transfer count per 24h window; 0 = unlimited |
Error codes
| Code | Name | Meaning |
|---|---|---|
| 6000 | NotTransferring | Hook called outside a transfer context |
| 6001 | DestinationNotAllowed | Destination ATA not in destination_allowlist |
| 6002 | ExceedsPerTxMax | amount > per_tx_max |
| 6003 | ExceedsDailyCap | spent_today + amount > daily_cap |
| 6004 | AllowlistTooLong | Tried to set more than 8 allowlist entries |
| 6005 | Unauthorized | Non-authority called update_policy |
| 6006 | Overflow | u64 overflow in spent_today math |
| 6007 | PolicyPaused | paused=true — authority has halted all transfers |
| 6008 | CooldownActive | now < last_transfer_unix + cooldown_secs |
| 6009 | ExceedsTransferCount | transfers_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
| signature | string | Transaction signature of the reverted transfer — link to Solana Explorer |
| errorCode | number | Numeric Anchor error code (6001–6009) |
| errorName | string | Human-readable name e.g. DestinationNotAllowed |
| errorMessage | string | Full error message from the program |
| slot | number | Solana slot the violation was observed in |
| observedAt | string | ISO 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 smokeRequirements: Rust 1.79+, Solana CLI 3.1+, Anchor 1.0.2, Node 22+, pnpm 9+