npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@usesigil/kit

v0.19.0

Published

Kit-native TypeScript SDK for Sigil — zero web3.js dependency, ESM-only

Downloads

570

Readme

@usesigil/kit

Kit-native TypeScript SDK for Sigil — on-chain spending limits, permission policies, and audit trails for AI-agent wallets on Solana.

Sigil is a security wrapper, not a DeFi SDK. Your agents keep using Jupiter, Flash Trade, Drift, or any Solana protocol. Sigil wraps the instructions they produce with a validate-and-authorize gate, enforces the policies the vault owner configured, and records the outcome — all without touching the underlying DeFi logic.


Install

npm install @usesigil/kit @solana/kit

@solana/kit ^6.2.0 is a peer dependency. Node >= 18.


Mental model

Sigil separates authority (owner) from execution (agents), enforced at the Solana transaction boundary rather than only in application code.

┌──────────────────────────────────────────────────────────────────┐
│                       OWNER (human or DAO)                       │
│   - Creates vault, sets policy (caps / timelock / allowlist)     │
│   - Registers agent signing keys                                 │
│   - Withdraws funds, freezes vault, revokes agents               │
│   - Cannot be impersonated by any agent                          │
└──────────────────────────┬───────────────────────────────────────┘
                           │ owner-signed transactions
                           ▼
┌──────────────────────────────────────────────────────────────────┐
│                    SIGIL ON-CHAIN PROGRAM                        │
│   ┌──────────────┐  ┌──────────────┐  ┌─────────────────────┐    │
│   │ AgentVault   │  │ PolicyConfig │  │ SpendTracker        │    │
│   │  (funds)     │  │  (limits)    │  │  (rolling 24 h)     │    │
│   └──────────────┘  └──────────────┘  └─────────────────────┘    │
│                                                                  │
│   Every spending tx is `[validate, <DeFi ix>, finalize]`.        │
│   The sandwich cannot be decomposed — all succeed or all revert. │
└──────────────────────────┬───────────────────────────────────────┘
                           │ validated tx
                           ▼
┌──────────────────────────────────────────────────────────────────┐
│        DeFi protocols (Jupiter, Flash Trade, Drift, …)           │
│        Sigil does not touch this layer — no protocol SDK needed. │
└──────────────────────────────────────────────────────────────────┘

Nothing an agent does can bypass the on-chain gate. If the validate instruction rejects — cap exceeded, protocol not allowed, agent paused — the entire transaction reverts before any funds move.


Quickstart

Provision a vault and execute your first agent-authorized swap:

import {
  createAndSendVault,
  SigilClient,
  SAFETY_PRESETS,
  parseUsd,
  createConsoleLogger,
} from "@usesigil/kit";

// 1. Owner provisions the vault on devnet.
const { vaultAddress } = await createAndSendVault({
  rpc, // Rpc<SolanaRpcApi> from @solana/kit
  network: "devnet",
  owner: ownerSigner, // TransactionSigner
  agent: agentSigner, // TransactionSigner — separate key from owner
  // Required safety posture (v0.9.0 — no silent defaults):
  ...SAFETY_PRESETS.development,
});

// 2. Agent opens an async client with genesis-hash verification.
const client = await SigilClient.create({
  rpc,
  vault: vaultAddress,
  agent: agentSigner,
  network: "devnet",
  logger: createConsoleLogger(), // opt in to structured warnings
});

// 3. Wrap arbitrary DeFi instructions from Jupiter, SAK, MCP, etc.
const jupiterInstructions = await buildJupiterSwap(/* your call */);
const { signature } = await client.executeAndConfirm(jupiterInstructions, {
  tokenMint: USDC_MINT_DEVNET,
  amount: parseUsd("$10"), // strict parser → 10_000_000n base units
});

The agent's signing key is never enough on its own — Sigil's validate instruction in the same transaction has to authorize the spend, and the finalize instruction has to record it. A leaked agent key cannot exceed the owner-configured daily cap or transfer to a non-allowed destination.


Sigil Facade (v0.11.0)

The six-step quickstart above is fine, but v0.11.0 ships a single-call facade that wraps it. Sigil.quickstart() provisions the vault + returns a handle; Sigil.fromVault() binds a handle to an existing vault:

import {
  Sigil,
  SAFETY_PRESETS,
  parseUsd,
  USDC_MINT_DEVNET,
} from "@usesigil/kit";

// Provision + get a handle in one call.
const { vault, funded, signatures } = await Sigil.quickstart({
  rpc,
  network: "devnet",
  owner: ownerSigner,
  agent: agentSigner,
  ...SAFETY_PRESETS.development,
  initialFundingUsd: parseUsd("$100"), // optional — zero or omit to skip
  fundingMint: USDC_MINT_DEVNET, // defaults to USDC on target network
});

if (!funded.funded) {
  console.warn("Vault live but not funded:", funded.reason, funded.error);
}

// Use the handle directly — no separate SigilClient / OwnerClient to wire.
const result = await vault.execute(jupiterInstructions, {
  tokenMint: USDC_MINT_DEVNET,
  amount: parseUsd("$10"),
});

const overview = await vault.overview(); // owner-only — throws OWNER_REQUIRED without owner
const budget = await vault.budget(); // cheapest read — agent-only works

Bind a handle to an existing vault:

const vault = await Sigil.fromVault({
  rpc,
  network: "devnet",
  address: existingVaultAddress,
  agent: agentSigner,
  owner: ownerSigner, // optional — required by vault.freeze() / vault.fund() / vault.overview()
});

Enumerate an owner's vaults:

const vaults = await Sigil.discoverVaults(rpc, ownerAddress, "devnet");

Presets are reachable through the same namespace:

Sigil.presets.safety.development; // { timelockDuration: 1800, ... }
Sigil.presets.safety.production; // null caps — caller supplies
Sigil.presets.vault["perps-trader"]; // VAULT_PRESETS entry
Sigil.presets.applySafetyPreset("production", {
  spendingLimitUsd,
  dailySpendingCapUsd,
});

Sigil is a frozen namespace object — no instance state, no new Sigil(), tree-shakeable.


Lifecycle hooks (v0.11.0)

SealHooks observe the transaction lifecycle. Pass hooks at client-config level (fire on every call) or per-call (compose on top of client-level hooks):

import type { SealHooks } from "@usesigil/kit";

const hooks: SealHooks = {
  onBeforeBuild(ctx, params) {
    // Runs before any RPC. Return { skipSeal: true, reason } to abort cleanly.
    if (isDryRunMode()) return { skipSeal: true, reason: "dry-run" };
  },
  onBeforeSign(ctx, tx) {
    // Observational — fires after build + size check, before signing.
  },
  onAfterSend(ctx, signature) {
    // Fires as soon as the signature is obtained — good for starting traces.
    startTrace(ctx.correlationId, signature);
  },
  onFinalize(ctx, result) {
    // Fires on the success path after confirmation.
    closeTrace(ctx.correlationId, result.signature);
  },
  onError(ctx, err) {
    // Fires in every failure path. Error is always rethrown after the hook.
  },
};

// Register at client level — fire on every vault.execute()
const vault = await Sigil.fromVault({ ..., hooks });

// Or per-call — composes with client-level hooks (client runs first, then per-call)
await vault.execute(instructions, { ..., hooks: perCallHooks });

Semantics:

  • Observe-only by default. A hook that throws is caught, logged via the injected logger, and swallowed. Hook exceptions never corrupt seal()'s atomic-transaction guarantee.
  • onBeforeBuild is the only hook that may abort. Returning { skipSeal: true, reason } throws SigilSdkDomainError(SIGIL_ERROR__SDK__HOOK_ABORTED) before any RPC round-trip. Use for consent flows, feature flags, or dry-run mode.
  • ctx.correlationId is stable across every hook for a single seal invocation — use it to correlate onBeforeBuildonAfterSendonFinalize in distributed traces.

Policy plugins (v0.11.0)

SigilPolicyPlugin is the rejection surface — distinct from SealHooks which observe. Plugins run after state resolution; the first rejection short-circuits seal() with SigilSdkDomainError(SIGIL_ERROR__SDK__PLUGIN_REJECTED):

import type { SigilPolicyPlugin } from "@usesigil/kit";

const maxAmountPlugin: SigilPolicyPlugin = {
  name: "max-amount-plugin",
  check(ctx) {
    if (ctx.amount > 1_000_000_000n) {
      return {
        allow: false,
        reason: "Amount exceeds $1,000 safety threshold",
      };
    }
    return { allow: true };
  },
};

const vault = await Sigil.fromVault({ ..., plugins: [maxAmountPlugin] });
// Calls to vault.execute() with amount > $1,000 throw PLUGIN_REJECTED.

Plugin semantics:

  • Enforce, not observe. First { allow: false } short-circuits the chain — downstream plugins don't run.
  • Async check() allowed — plugins can call feature-flag servers or compliance APIs. A plugin that takes >1 second logs a warning (target is sub-second).
  • Plugin throws become hard rejects. The runner catches them and treats the message as the rejection reason.
  • Plugin names must be unique per client. Config validation at handle construction rejects duplicates.

React hooks (v0.11.0, optional subpath)

Install React + TanStack Query as optional peer dependencies, then import from @usesigil/kit/react:

npm install react @tanstack/react-query
import { useVaultBudget, useOverview, useExecute } from "@usesigil/kit/react";

function VaultDashboard({ vault }: { vault: SigilVault }) {
  const budget = useVaultBudget(vault); // { data, isLoading, error }
  const overview = useOverview(vault); // owner-only
  const execute = useExecute(vault); // { mutate, mutateAsync, isPending }

  if (budget.isLoading) return <div>Loading...</div>;
  return (
    <button
      onClick={() =>
        execute.mutate({
          instructions: myJupiterInstructions,
          opts: { tokenMint: USDC_MINT_DEVNET, amount: parseUsd("$10") },
        })
      }
      disabled={execute.isPending}
    >
      Swap $10
    </button>
  );
}

Query keys are namespaced under "sigil" so they never collide with the consumer app's TanStack cache. Cache invalidation is the consumer's responsibility — wrap useExecute with a custom onSuccess that invalidates the specific vault keys you want refetched.

Consumers who don't use React never install the peer deps and never see a warning.


Security boundary

What the on-chain program enforces:

  • Daily and per-transaction spending caps (dailySpendingCapUsd, maxTransactionSizeUsd)
  • Per-agent spending limits (spendingLimitUsd)
  • Protocol allowlist / denylist (protocols, protocolMode)
  • Slippage tolerance on Jupiter swaps (maxSlippageBps)
  • Session expiry, position counters, leverage limits
  • Owner timelock on policy changes (timelockDuration)

What the SDK enforces pre-submission:

  • Agent capability check (2-bit enum: Disabled / Observer / Operator)
  • Genesis-hash assertion on SigilClient.create() — prevents cluster mismatch
  • Strict USD parsing (parseUsd) — no parseFloat rounding
  • Aggregate cap guard — sum of per-agent caps ≤ vault cap
  • SPL token-operation detection — blocks non-whitelisted transfer patterns

What the SDK does NOT attempt:

  • Key custody — bring your own TransactionSigner (Turnkey, Crossmint, Privy, or a local keypair)
  • Transaction simulation outcome trust — simulation is a hint, not a guarantee; on-chain enforcement is the source of truth
  • Replay prevention outside a session — agents must start a new session per transaction

If a piece of SDK logic is wrong, the worst a consumer loses is some UX clarity — the on-chain program still rejects the bad transaction. The boundary is deliberate.


Configuration presets

Use-case presets (VAULT_PRESETS)

Starting templates for specific agent roles. Each sets capability, allowlist, slippage, and caps appropriate for the use case:

  • jupiter-swap-bot — Jupiter only, conservative caps ($500/day, $100/tx)
  • perps-trader — Jupiter + Flash Trade, 10× leverage, $5,000/day
  • lending-optimizer — Jupiter Lend + Kamino, 1% slippage, $2,000/day
  • full-access — every protocol allowed, $10,000/day, 20× leverage

Safety presets (SAFETY_PRESETS) — v0.9.0

Orthogonal to the use-case presets. Picks safe defaults for timelock and caps without prescribing a use case:

  • development — 30-min timelock, $100/agent, $500/day. Safe for devnet / CI.
  • production — 24-hour timelock, caps explicitly null. You must supply real values via applySafetyPreset("production", { ... }) — the SDK will throw if you try to use an unfilled production preset with createVault.

Compose them:

import { createVault, VAULT_PRESETS, applySafetyPreset } from "@usesigil/kit";

const presetFields = VAULT_PRESETS["jupiter-swap-bot"];
const safety = applySafetyPreset("production", {
  spendingLimitUsd: 1_000_000_000n, // $1,000 per agent
  dailySpendingCapUsd: 10_000_000_000n, // $10,000 vault-wide
});
await createVault({
  rpc,
  network: "mainnet",
  owner,
  agent,
  ...presetFields,
  ...safety,
});

Jupiter integration

@usesigil/kit does not wrap Jupiter. Use the official @jup-ag/api client to build swap instructions, then pipe the Instruction[] through seal():

import { createJupiterApiClient } from "@jup-ag/api";
import { seal } from "@usesigil/kit";

const jupiter = createJupiterApiClient();
const { swapTransaction, addressLookupTableAddresses } = await jupiter.swapPost(
  { quoteResponse, userPublicKey: vault },
);

// Extract instructions from the serialized swapTransaction (helper omitted
// for brevity — decode with @solana/kit's `getCompiledTransactionMessageDecoder`
// then convert each CompiledInstruction to the Kit Instruction shape).
const jupiterIxs: Instruction[] = decodeSwapInstructions(swapTransaction);

const sealed = await seal({
  rpc,
  network: "devnet",
  vault,
  agent: agentSigner,
  instructions: jupiterIxs,
  tokenMint: USDC_MINT_DEVNET,
  amount: 10_000_000n, // $10 in base units
  protocolAltAddresses: addressLookupTableAddresses, // rotate per-route
});

Any Jupiter-supported protocol flows through the same path; Sigil treats the instructions opaquely and enforces policy on the account touches Jupiter actually makes.


Subpath imports

| Import | Use for | | ------------------------------ | -------------------------------------------------------------------- | | @usesigil/kit | Main API: seal, SigilClient, createVault, analytics, presets | | @usesigil/kit/errors | The 52 SIGIL_ERROR__* code constants for catch-block narrowing | | @usesigil/kit/dashboard | OwnerClient for vault management (reads + owner mutations) | | @usesigil/kit/x402 | HTTP 402 Payment Required helpers (shieldedFetch, payment parsing) | | @usesigil/kit/react | TanStack Query hooks (v0.11.0) — optional React peer deps | | @usesigil/kit/testing | Mock RPCs and fixtures for unit tests | | @usesigil/kit/testing/devnet | Devnet test harness (browser-incompatible — Node only) |


v0.10 → v0.11 migration

v0.11.0 is additive. Existing v0.10.0 consumers do not need to change code to upgrade — Sigil facade, SigilVault, hooks, plugins, and the /react subpath all sit on top of the existing createSigilClient / createOwnerClient / seal() primitives.

Recommended migration path:

  1. Replace bespoke createAndSendVault + SigilClient.create + createOwnerClient plumbing with Sigil.quickstart() / Sigil.fromVault() for new call sites.
  2. Add lifecycle hooks to your existing SigilClientConfig or SigilVault.execute() options for telemetry.
  3. Move React consumers off ad-hoc useEffect loops onto @usesigil/kit/react hooks.

v0.11.0 also added three new error codes to /errors (for a total of 52):

  • SIGIL_ERROR__SDK__HOOK_ABORTED — onBeforeBuild returned { skipSeal: true }
  • SIGIL_ERROR__SDK__PLUGIN_REJECTED — a plugin returned { allow: false }
  • SIGIL_ERROR__SDK__OWNER_REQUIRED — an owner-only SigilVault method was called on an agent-only handle

v0.8 → v0.9 migration

v0.9.0 is a breaking release. The headline changes:

  1. createVault now requires three fields that previously had silent defaults: spendingLimitUsd, dailySpendingCapUsd, timelockDuration. Set them explicitly or spread SAFETY_PRESETS.development / applySafetyPreset("production", {...}).
  2. SigilClient.create(config) is the new preferred entry point — it asserts the RPC's genesis hash matches the configured network. new SigilClient(config) is deprecated (removal in Sprint 2) and logs a warning.
  3. 49 SIGIL_ERROR__* constants moved from the root barrel to the ./errors subpath. Update imports:
    - import { SIGIL_ERROR__SDK__CAP_EXCEEDED } from "@usesigil/kit";
    + import { SIGIL_ERROR__SDK__CAP_EXCEEDED } from "@usesigil/kit/errors";
  4. Root barrel lost ~325 exports (Codama instruction builders, event/struct types, hex error constants). Consumers who imported generated internals should migrate to seal() / createVault() / OwnerClient. Account decoders stay at root.
  5. Structured logger replaces console.warn inside the SDK. Pass logger: createConsoleLogger() to SigilClient.create() (or your preferred logger matching the SigilLogger interface) to receive diagnostic output.

See CHANGELOG.md and the upgrade checklist for every grep you need to run.


Two digests, two scopes — AL3 vs TA-19

Sigil V2 binds owner intent through two complementary digests, each covering a different scope. They share the same canonical encoder (src/canonical-encode.ts) so the two cannot drift, but they are computed over disjoint inputs and protect against different attack classes.

TA-19 — policy_preview_digest (policy state)

SHA-256 over the canonical Borsh encoding of the 21-field PolicyConfig preview. Computed by the SDK in computePolicyPreviewDigest() and recomputed on-chain by apply_pending_policy against the actual PolicyConfig state at apply time. Mismatch ⇒ PolicyPreviewMismatch (6080).

Scope: the owner-approved POLICY STATE — caps, allowlists, modes, operating hours, agent set hash, cosign requirement, etc.

Defends against: silent policy mutation between queue and apply (e.g. a rogue program tampers with the PendingPolicyUpdate PDA, or a phished-owner register_agent silently changes the live agent set).

AL3 — computeSealInputDigest (per-call intent)

SHA-256 over the canonical Borsh encoding of a SealInput envelope: the specific (vault, agent, mint, amount, target_protocol, network, instructions[]) bound for a SINGLE seal() call. Reserved intent_version: u8 = 1 at canonical position 1 for future expansion.

Today, AL3 is a client-integrity digest — it surfaces on every SealResult.intentDigest so preview UIs and audit logs can bind to the exact bytes the SDK sealed. An on-chain verifier is planned for a later release; until it lands, AL3 does not by itself prevent a forked SDK from submitting different bytes than it hashed. Pair AL3 with TA-19 + the policy allowlists for on-chain enforcement.

Scope: the per-call EXECUTION INTENT — exactly what is being authorised for this one transaction.

Defends against: prompt-injection recipient-swap and ix-meta reorder attacks where a compromised agent submits a different (but still policy-allowed) action than what the user saw in the preview.

Why both

TA-19 binds the policy state the owner approved over weeks. AL3 binds the individual intent the owner approved for a single call. An attack that gets past TA-19 (e.g. the policy says "any allowlisted recipient") can still be caught by AL3 (e.g. "owner approved recipient A; agent submitted recipient B; digest mismatch ⇒ reject"). They are complementary, never duplicative.


License

Apache-2.0. Copyright 2024-2026 Kaleb Rupe.