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

@actioncodes/protocol

v2.0.28

Published

The Action Codes Protocol is a lightweight way to prove intent and authorize actions across blockchains, apps, and wallets. Instead of heavy signature popups or complex flows, it uses short-lived one-time codes derived from canonical cryptographic message

Downloads

258

Readme

Action Codes Protocol

The Action Codes Protocol is a lightweight way to prove intent and authorize actions across blockchains, apps, and wallets. Instead of heavy signature popups or complex flows, it uses short-lived one-time codes derived from canonical cryptographic messages.

This enables:

  • Secure intent binding – the code is cryptographically tied to a wallet/public key.
  • Fast verification – relayers/servers can validate in microseconds (~3ms per verification).
  • Cross-chain support – adapters handle chain-specific quirks (currently supporting Solana)
  • Simple dev UX – just generate → sign → verify.
  • Issuer support – allows separation between code issuer and intent owner for advanced use cases.

What's new in v2 (compared to v1)

  • Now we use Bun as core library. We are down to ~3ms per code signature verification on commodity hardware.
  • Canonical Messages only → No ambiguity. Codes are always derived from a canonical serialization (serializeCanonical).
  • No AIPs (Action Codes Improvement Proposals) → Overkill for lightweight protocol.
  • Chain Adapters simplified → They don't enforce business rules; they just provide utilities:
    • createProtocolMetaIx (for attaching metadata)
    • parseMeta / verifyTransactionMatchesCode (for checking integrity)
    • verifyTransactionSignedByIntentOwner (checks both int and iss signatures when present).
  • Errors are typed → Clear ProtocolError.* categories instead of generic fails.

Core Concepts

  1. Action Codes

    • A short-lived, one-time code bound to a wallet/public key.
    • Generated using HMAC/HKDF and canonical serialization.
  2. Protocol Meta

    • The "payload" carried with transactions to tie them to action codes.
    • Versioned, deterministic, size-limited (max 512 bytes).
    • Fields: ver (version), id (code hash), int (intent owner), iss (issuer, optional), p (params, optional).
    • When iss field is present, both issuer and intent owner must sign the transaction.
    • Perfect for attaching to transactions or off-chain messages for tracing.
  3. Canonical Message

    • A deterministic JSON serialization of (pubkey, code, timestamp, optional secret)
    • Always signed by the user's wallet.
    • Prevents replay / tampering.

Strategy Architecture

The Action Codes Protocol supports two main strategies for generating and validating codes:

1. Wallet Strategy (Direct)

The Wallet Strategy is the simplest approach where codes are generated directly from a user's wallet.

How it works:

import { ActionCodesProtocol } from '@actioncodes/protocol';
import { SolanaAdapter } from '@actioncodes/protocol';

// Initialize protocol
const protocol = new ActionCodesProtocol({
  codeLength: 8,      // Code length (6-24 digits)
  ttlMs: 120000,      // Time to live (2 minutes)
  clockSkewMs: 5000   // Clock skew tolerance (5 seconds)
});

// Register Solana adapter
protocol.registerAdapter('solana', new SolanaAdapter());

// 1. Generate code with wallet strategy
const signFn = async (message: Uint8Array, chain: string) => {
  // Sign the canonical message with user's wallet
  const signature = await userWallet.signMessage(message);
  return signature; // Returns base58-encoded signature
};

const actionCode = await protocol.generate(
  "wallet",
  userWallet.publicKey.toString(),
  "solana",
  signFn
);

// 2. Validate code
protocol.validate("wallet", actionCode);
// Throws ProtocolError if validation fails

Key Features:

  • Signature-based security - Codes require a valid signature over the canonical message (prevents public key + timestamp attacks)
  • Direct wallet binding - Codes are cryptographically tied to the user's public key
  • HMAC-based generation - Uses signature as entropy source for secure code generation
  • Immediate validation - No delegation certificates needed
  • Perfect for - Direct user interactions, simple authentication flows, transaction authorization

Security Model:

  • Signature verification - All codes require a valid signature over the canonical message
  • Public key + timestamp attack prevention - Signatures prevent attackers from generating codes with just public key + timestamp
  • HMAC-based entropy - Codes use HMAC-SHA256 with signature as key, ensuring cryptographic security
  • Codes are bound to the specific public key and timestamp
  • Time-based expiration prevents replay attacks
  • Canonical message signing ensures integrity

2. Delegation Strategy (Advanced)

The Delegation Strategy allows users to pre-authorize actions through delegation proofs, enabling more complex workflows like relayer services.

How it works:

Step 1: Create Delegation Proof
import { serializeDelegationProof } from '@actioncodes/protocol';

// User creates a delegation proof
const delegationProof = {
  walletPubkey: userWallet.publicKey.toString(),
  delegatedPubkey: delegatedKeypair.publicKey.toString(),
  chain: "solana",
  expiresAt: Date.now() + 3600000, // 1 hour expiration
  signature: "" // Will be set after signing
};

// User signs the delegation proof
const delegationMessage = serializeDelegationProof(delegationProof);
const delegationSignature = await userWallet.signMessage(delegationMessage);
delegationProof.signature = delegationSignature;
Step 2: Generate Delegated Codes
// Sign function for delegated keypair
const delegatedSignFn = async (message: Uint8Array, chain: string) => {
  const signature = await delegatedKeypair.signMessage(message);
  return signature;
};

// Generate code using delegation strategy
const delegatedActionCode = await protocol.generate(
  "delegation",
  delegationProof,
  "solana",
  delegatedSignFn
);
Step 3: Validate Delegated Codes
// Validate the delegated code
protocol.validate("delegation", delegatedActionCode);
// Throws ProtocolError if validation fails

Key Features:

  • Pre-authorization - Users can authorize actions for a limited time
  • Relayer support - Third parties can validate codes without generating them
  • Proof-based - Codes are bound to specific delegation proofs
  • Time-limited - Delegation proofs have expiration times
  • Perfect for - Relayer services, automated systems, complex workflows

Security Model:

  • Code-Proof Binding - Codes are cryptographically bound to their specific delegation proof
  • Signature Verification - Delegation proof signatures are verified using chain adapters
  • Dual Signature Requirement - Both wallet signature (on proof) and delegated signature (on code) are required
  • Cross-Proof Protection - Codes from one delegation proof cannot be used with another
  • Relayer Security - Relayers can validate codes but cannot generate them without the delegated keypair's signature

Important Security Guarantees:

  1. Stolen Delegation Proofs are Limited

    • Delegation proofs contain public data (pubkeys, expiration, chain)
    • They cannot be used to generate codes without the delegated keypair's private key
    • The proof signature prevents tampering but doesn't enable code generation
  2. Stolen Signatures Cannot Create Valid Codes

    • Even if an attacker steals a signature, they cannot create valid codes
    • Codes are bound to the ENTIRE delegation proof (not just the signature)
    • Different proof data = different code = validation failure
  3. Relayer Code Generation Prevention

    • Relayers cannot generate codes even with public delegation proof data
    • Codes require signatures from the delegated keypair (private asset)
    • Only holders of the delegated keypair can generate valid codes
  4. Code-Proof Binding

    • Codes are cryptographically linked to their specific delegation proof
    • Cross-proof attacks are impossible
    • Each delegation proof produces unique codes

Delegation Proof Structure:

interface DelegationProof {
  walletPubkey: string;      // Original wallet's public key
  delegatedPubkey: string;    // Delegated keypair's public key
  chain: string;             // Target blockchain
  expiresAt: number;         // Expiration timestamp
  signature: string;         // Wallet's signature of the delegation proof
}

Delegated Action Code Structure:

interface DelegatedActionCode {
  code: string;              // The actual action code
  pubkey: string;            // Delegated pubkey (who signs the code)
  timestamp: number;         // Generation timestamp
  expiresAt: number;         // Code expiration
  signature: string;         // Signature from delegated keypair
  chain: string;            // Target blockchain
  delegationProof: DelegationProof; // The delegation proof
}

Use Cases & Examples

Wallet Strategy Use Cases

1. Simple Authentication

// User logs into a dApp
const signFn = async (message: Uint8Array, chain: string) => {
  return await userWallet.signMessage(message);
};

const actionCode = await protocol.generate(
  "wallet",
  userWallet.publicKey.toString(),
  "solana",
  signFn
);

// dApp validates the code
protocol.validate("wallet", actionCode);

2. Transaction Authorization

import { buildProtocolMeta, codeHash } from '@actioncodes/protocol';

// User authorizes a specific transaction
const signFn = async (message: Uint8Array, chain: string) => {
  return await userWallet.signMessage(message);
};

const actionCode = await protocol.generate(
  "wallet",
  userWallet.publicKey.toString(),
  "solana",
  signFn
);

// Relayer validates before executing
protocol.validate("wallet", actionCode);

// Attach protocol meta to transaction
const adapter = protocol.getAdapter("solana") as SolanaAdapter;
const meta = buildProtocolMeta({
  ver: 2,
  id: codeHash(actionCode.code),
  int: actionCode.pubkey,
});
const txWithMeta = SolanaAdapter.attachProtocolMeta(transactionBase64, meta);

// Verify transaction is signed by the intended owner
adapter.verifyTransactionSignedByIntentOwner(txWithMeta);

Delegation Strategy Use Cases

1. Relayer Services

// User creates delegation proof for relayer
const delegationProof = {
  walletPubkey: userWallet.publicKey.toString(),
  delegatedPubkey: relayerKeypair.publicKey.toString(),
  chain: "solana",
  expiresAt: Date.now() + 3600000, // 1 hour
  signature: await signDelegationProof(delegationProof)
};

// Relayer can validate codes but not generate them
// User generates codes that relayer can validate
const delegatedSignFn = async (message: Uint8Array, chain: string) => {
  return await relayerKeypair.signMessage(message);
};

const actionCode = await protocol.generate(
  "delegation",
  delegationProof,
  "solana",
  delegatedSignFn
);

// Relayer validates the code
protocol.validate("delegation", actionCode);

2. Automated Trading Bots

// User authorizes trading bot for 24 hours
const delegationProof = {
  walletPubkey: userWallet.publicKey.toString(),
  delegatedPubkey: botKeypair.publicKey.toString(),
  chain: "solana",
  expiresAt: Date.now() + 86400000, // 24 hours
  signature: await signDelegationProof(delegationProof)
};

// Bot generates codes using delegated keypair
const botSignFn = async (message: Uint8Array, chain: string) => {
  return await botKeypair.signMessage(message);
};

const tradeCode = await protocol.generate(
  "delegation",
  delegationProof,
  "solana",
  botSignFn
);
// Bot executes trade with this code

Security Considerations

What Makes Action Codes Secure?

  1. Cryptographic Binding

    • Codes are mathematically tied to specific public keys
    • Impossible to forge without the private key
  2. Time-Limited Validity

    • Codes expire automatically
    • Prevents replay attacks
  3. One-Time Use

    • Each code is unique and time-bound
    • Cannot be reused
  4. Delegation Security

    • Delegation proofs are cryptographically signed
    • Codes are bound to specific delegation proofs
    • Cross-proof attacks are impossible

Best Practices

  1. Delegation Proof Expiration

    • Set appropriate expiration times for delegation proofs
    • Shorter expiration = higher security
    • Longer expiration = better UX
  2. Relayer Security

    • Only trust relayers with valid delegation proofs
    • Never share private keys with relayers
    • Monitor relayer behavior
  3. Issuer Field Usage

    • Use iss field when issuer and intent owner are different entities
    • Ensure both signatures are present when iss is specified
    • Omit iss when it's the same as int to save space
  4. Code Validation

    • Always validate codes server-side
    • Check expiration times
    • Verify the binding to the correct public key
  5. Protocol Meta with Issuer

    • Use iss field when issuer differs from intent owner
    • When iss is present, ensure transaction is signed by both
    • If iss equals int, it's automatically omitted (optimization)

Performance

  • Code Generation: ~1ms per code
  • Code Validation: ~3ms per validation
  • Memory Usage: Minimal (no state storage required)
  • Network: No network calls required for validation

Getting Started

# Install
npm install @actioncodes/protocol

# Basic usage
import { ActionCodesProtocol } from '@actioncodes/protocol';
import { SolanaAdapter } from '@actioncodes/protocol';

// Initialize protocol
const protocol = new ActionCodesProtocol({
  codeLength: 8,      // Code length (6-24 digits)
  ttlMs: 120000,      // Time to live (2 minutes)
  clockSkewMs: 5000   // Clock skew tolerance (5 seconds)
});

// Register Solana adapter
protocol.registerAdapter('solana', new SolanaAdapter());

// 1. Sign function for wallet
const signFn = async (message: Uint8Array, chain: string) => {
  // Sign the canonical message with your wallet
  const signature = await yourWallet.signMessage(message);
  return signature; // Returns base58-encoded signature
};

// 2. Generate a code
const actionCode = await protocol.generate(
  "wallet",
  yourWallet.publicKey.toString(),
  "solana",
  signFn
);

// 3. Validate a code
protocol.validate("wallet", actionCode);
// Throws ProtocolError if validation fails

Protocol Meta with Issuer Field

When you need to separate the issuer (who created the code) from the intent owner (who will use it):

import { buildProtocolMeta } from '@actioncodes/protocol';

// Create protocol meta with issuer
const meta = buildProtocolMeta({
  ver: 2,
  id: codeHash(actionCode.code),
  int: intentOwnerPubkey,    // Who will use the code
  iss: issuerPubkey,          // Who issued the code (optional)
});

// If iss is same as int, it's automatically omitted to save space
// If iss is present, transaction must be signed by BOTH int and iss

Vision

Action Codes Protocol aim to be the OTP protocol for blockchains but allowing more than authentication: a simple, universal interaction layer usable across apps, chains, and eventually banks/CBDCs interacting with blockchains.