Skip to main content
This section is for advanced users. You do NOT need to read this to use Action Codes. The SDK handles all protocol details automatically.

When to read this

  • You’re building a custom relayer
  • You’re implementing a new chain adapter
  • You’re auditing the protocol security
  • You want to understand how codes are derived
If you’re just integrating Action Codes into your app, see the Quick Start instead.

Performance

The protocol is designed for speed:
OperationTime
Code generation~1ms
Code validation~3ms
Memory footprintNegligible
Network dependencyNone for validation

Two Strategies

Action Codes supports two code generation strategies:

Wallet Strategy (Default)

Direct code generation from a user’s wallet. This is what most apps use. How it works:
  • User’s wallet signs a canonical message
  • Code is derived using HMAC-SHA256 with the signature as entropy
  • Codes are cryptographically bound to the wallet’s public key
  • Validation is immediate — no external dependencies
Use cases: Direct authentication, transaction signing, user interactions

Delegation Strategy (Advanced)

Pre-authorize a delegated keypair to generate codes on behalf of a wallet. Enables relayer services and automated workflows. How it works:
  1. User signs a delegation proof specifying: delegated keypair, chain, expiration
  2. Delegated keypair generates codes bound to the proof
  3. Relayers validate both the delegation proof AND the code signature
Security guarantees:
  • Stolen proofs cannot generate codes (require delegated private key)
  • Relayers cannot generate codes (only validate)
  • Cross-proof attacks are prevented through cryptographic binding
Use cases: Relayer services, automated trading bots, complex workflows

Core Concepts

Code derivation

Action Codes are deterministically derived:
code = HMAC-SHA256(signature, pubkey + timestamp)[0:8]
  • signature — Wallet signature over canonical message (secret entropy)
  • pubkey — User’s wallet public key
  • timestamp — Current time, rounded to 2-minute windows
This makes codes:
  • Unpredictable — Cannot be guessed without the signature
  • Verifiable — Can be validated with the signature
  • Time-bound — Expire after ~2 minutes

Canonical messages

Every code generation involves signing a deterministic JSON message:
{
  "pubkey": "7gNqUuY5...",
  "code": "48291037",
  "timestamp": 1704067200
}
Deterministic serialization (sorted keys, no whitespace) prevents ambiguity.

Protocol Meta

Metadata attached to transactions:
FieldDescription
verProtocol version
idCode hash identifier
intIntent owner (wallet public key)
issIssuer (optional, for delegation)
pParameters (optional)
Maximum size: 512 bytes. When iss is present, both issuer and intent owner must sign.

Architecture

Components

ComponentRole
WalletGenerates codes, signs transactions
RelayerValidates codes, stores encrypted state, coordinates flow
AppAttaches actions, observes status
Chain AdapterChain-specific transaction handling

The Relayer

The relayer is a trusted intermediary that:
  1. Validates codes — Verifies signature, timestamp, format
  2. Stores state — Encrypted transaction/message payloads
  3. Coordinates flow — Connects apps and wallets
  4. Enforces expiry — Rejects expired codes
The official relayer is free to use and maintained by Action Codes.

Security Model

PropertyHow it’s achieved
Codes are unpredictableDerived from wallet signature (secret entropy)
Codes are verifiableSignature can be verified against pubkey
Codes are time-bound2-minute windows, enforced by relayer
Codes are one-timeRelayer tracks usage
Payloads are encryptedCode itself is the decryption key
No on-chain stateEverything is off-chain until finalization

Threat mitigations

ThreatMitigation
Code guessing8 digits + signature binding = infeasible
Replay attacksTime windows + one-time use
Payload tamperingEncrypted with code-derived key
Relayer compromiseRelayer never has raw private keys
Delegation abuseTime-limited proofs + dual signatures
For full security details, see Security & Determinism.

Using the Protocol Package

For low-level protocol access:
npm install @actioncodes/protocol
import { ActionCodesProtocol, SolanaAdapter } from '@actioncodes/protocol'

// Initialize with configuration
const protocol = new ActionCodesProtocol({
  codeLength: 8,        // 6-24 digits
  ttlMs: 120000,        // 2 minutes
  clockSkewMs: 5000     // Clock tolerance
})

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

// Generate a code (wallet strategy)
const actionCode = await protocol.generate(
  'wallet',
  userPublicKey,
  'solana',
  signFn
)

// Validate a code
protocol.validate('wallet', actionCode)
Most applications should use @actioncodes/sdk instead, which wraps the protocol with a simpler API and handles relayer communication.

Chain Adapters

Adapters provide chain-specific functionality:
interface ChainAdapter {
  // Protocol meta
  createProtocolMetaIx(): TransactionInstruction
  parseMeta(tx: Transaction): ProtocolMeta

  // Verification
  verifyTransactionMatchesCode(tx, code): boolean
  verifyTransactionSignedByIntentOwner(tx, pubkey): boolean

  // Attachment
  attachProtocolMeta(tx, meta): Transaction
}
Currently supported: Solana See Adapter Reference for implementation details.

Best Practices

  1. Set appropriate TTL — Balance security vs. user experience
  2. Validate server-side — Never trust client-only validation
  3. Use delegation carefully — Set short expiration windows
  4. Monitor relayer activity — Watch for unusual patterns
  5. Handle expiry gracefully — Prompt users to regenerate codes