Skip to main content

Concept

An L402 token (macaroon:preimage) is a portable string — once paid, it can be passed to any sub-agent that needs access to the same resource. The orchestrator holds the wallet; sub-agents only receive a token.
Orchestrator (has wallet)

   ├── pays invoice → receives token (macaroon:preimage)

   ├── passes token string to SubAgent A
   └── passes token string to SubAgent B

SubAgent A — calls /api/data with token  ✓  (no wallet needed)
SubAgent B — calls /api/data with token  ✓  (no wallet needed)
Tokens expire after 1 hour. Each preimage can only be used once per server (replay protection). For parallel sub-agents hitting the same endpoint, each needs its own payment.

When to share a token vs. pay independently

There are two delegation patterns, each with different trade-offs: Token sharing works when sub-agents access the same endpoint sequentially. The orchestrator pays once, extracts the Authorization header value, and passes it as a string. Sub-agents call the endpoint with that header directly — no wallet required. This minimizes cost (one payment) but is not safe for concurrent access — only one agent can hold a given preimage, and servers reject replays. Independent payment (each sub-agent has its own L402Client sharing the orchestrator’s wallet) works for parallel or concurrent access. Each agent pays its own invoice and caches its own token. This costs more sats but is simpler to reason about and safe at any concurrency level. Rule of thumb: If your sub-agents fan out in parallel, give each its own L402Client. If they run sequentially and hit the same endpoint, share the token string.

Security considerations

  • Never pass wallet credentials to sub-agents. Pass only the token string (L402 <macaroon>:<preimage>). A token can call one endpoint for up to one hour — a private key can drain a wallet.
  • Tokens are single-use per server. After the first accepted request, the server records the preimage hash. A second request with the same preimage returns 402. This prevents replay attacks but means you cannot share one token across two concurrent sub-agents hitting the same server.
  • Budget control is at the wallet level. Set budgetSats on the orchestrator’s L402Client to cap total spend across all sub-agents sharing that wallet. See Budget Control.

TypeScript — orchestrator pays, sub-agent uses token

import { L402Client, BlinkWallet } from "l402-kit";

// Orchestrator: has wallet, pays invoice
const wallet = new BlinkWallet(process.env.BLINK_API_KEY!, process.env.BLINK_WALLET_ID!);
const orchestrator = new L402Client({ wallet, budgetSats: 5000 });

// Fetch triggers payment; token is cached inside orchestrator
const res = await orchestrator.fetch("https://api.example.com/data");
const data = await res.json();

// Build the delegation header from the macaroon + preimage the Lightning Network returned.
// In practice, retrieve these from the payment result or the client's token cache.
// Format is always: "L402 <base64-macaroon>:<hex-preimage>"
const tokenHeader = `L402 ${macaroon}:${preimage}`; // substitute values from payment result

// Sub-agent: no wallet, passes the header directly
const subRes = await fetch("https://api.example.com/data", {
  headers: { Authorization: tokenHeader },
});

Python — orchestrator pays, sub-agent uses token

from l402kit import L402Client
from l402kit.wallets.blink import BlinkWallet

# Orchestrator pays
wallet = BlinkWallet(api_key=os.environ["BLINK_API_KEY"])
orchestrator = L402Client(wallet=wallet, budget_sats=5000)

async with orchestrator:
    response = await orchestrator.fetch("https://api.example.com/data")

# Sub-agents: pass the Authorization header directly
# Authorization: L402 <macaroon>:<preimage>

Multi-agent pattern: one payment, parallel reads

If multiple sub-agents need the same resource concurrently, the cleanest pattern is for each sub-agent to pay independently using its own L402Client with a shared wallet:
import { L402Client, BlinkWallet } from "l402-kit";

const wallet = new BlinkWallet(process.env.BLINK_API_KEY!, process.env.BLINK_WALLET_ID!);

// Each sub-agent gets its own L402Client — independent payment + token cache
const agents = Array.from({ length: 3 }, () => new L402Client({ wallet }));

const results = await Promise.all(
  agents.map((agent) => agent.fetch("https://api.example.com/data"))
);

Security properties

PropertyHow it works today
Wallet isolationSub-agents receive a token string, never private keys
Time-boundMacaroons expire after 1 hour
Replay-safeEach preimage can only be accepted once by the server
Budget controlSet budgetSats on the orchestrator L402Client

Coming in a future version

Full macaroon caveat delegation (endpoint scoping, single-use tokens, max-use limits) is planned. Track progress on GitHub. See Budget Control for session-level spending limits.