Knox is a Bun-based CLI payment client for paid HTTP APIs that support 402 Payment Required with:
- x402
- MPP
It provides a curl-like request command, a local account store, and a plugin system with hooks throughout the request lifecycle.
npm install -g knox-wallet
knox --help# account management
knox account create --force
knox account import --private-key <hex> --force
knox account status
# request execution
knox request https://httpbin.org/get
knox request --protocol x402 "https://lorem.steer.fun/generate?count=2&units=paragraphs&format=plain"
knox request --protocol mpp "https://lorem.steer.fun/generate?count=2&units=paragraphs&format=plain"
# dry run (no signature, no payment)
knox --dry-run request --protocol x402 "https://lorem.steer.fun/generate?count=2&units=paragraphs&format=plain"
# transactions and plugins
knox tx list
knox tx show <id>
knox plugins list
knox plugins setup <plugin-name>--protocol auto|x402|mpp--dry-run--no-plugins
- Knox supports one local account for now.
- Running
account createoraccount importreplaces the existing account. - If an account already exists, use
--forcewithaccount createandaccount importto confirm replacement.
~/.knox/plugins/*.{ts,js,mjs,cjs}.knox/plugins/*.{ts,js,mjs,cjs}
Plugin module shape:
type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
type PluginKvStore = {
get: (params: { key: string }) => Promise<JsonValue | undefined>;
set: (params: { key: string; value: JsonValue }) => Promise<void>;
};
export type AccountPlugin = {
name: string;
setup?: (event: PluginSetupEvent) => Promise<PluginSetupResult | undefined>;
beforeTransaction?: (event: BeforeTransactionEvent) => Promise<BeforeTransactionResult | undefined>;
beforeSign?: (event: BeforeSignEvent) => Promise<BeforeSignResult | undefined>;
afterTransaction?: (event: AfterTransactionEvent) => Promise<void>;
accountStatus?: (event: AccountStatusEvent) => Promise<AccountStatusResult | undefined>;
};Event contracts:
type BeforeTransactionResult =
| { action: "continue" }
| { action: "abort"; reason: string };
type BeforeSignResult =
| { action: "continue"; intentOverride?: Partial<PaymentIntent> }
| { action: "abort"; reason: string };
type AccountStatusResult = { output: string };
type PluginSetupResult = { output?: string };
type PluginSetupEvent = {
userAddress: `0x${string}` | null;
kv: PluginKvStore;
};
type BeforeTransactionEvent = {
userAddress: `0x${string}`;
intent: PaymentIntent;
attempt: number;
kv: PluginKvStore;
};
type BeforeSignEvent = {
userAddress: `0x${string}`;
intent: PaymentIntent;
challengeRaw: unknown;
attempt: number;
kv: PluginKvStore;
};
type AfterTransactionEvent = {
userAddress: `0x${string}`;
intent: PaymentIntent;
success: boolean;
responseStatus?: number;
error?: string;
kv: PluginKvStore;
};
type AccountStatusEvent = {
userAddress: `0x${string}`;
accountSource: string;
kv: PluginKvStore;
};Behavior:
beforeTransaction: fail-closed, can block payment.beforeSign: fail-closed, can block payment and optionally mutatePaymentIntentviaintentOverride.afterTransaction: fail-open, errors are logged.accountStatus: runs duringknox account status; output is rendered as multiline text under plugin name.setup: runs when invokingknox plugins setup <plugin-name>and receivesuserAddress(ornull).kv: persistent, plugin-scoped JSON key-value store available in all hook events.
Minimal plugin example:
export default {
name: "status-note",
async setup({ kv }) {
await kv.set({ key: "message", value: "All systems nominal" });
},
async accountStatus({ kv }) {
const message = await kv.get({ key: "message" });
return {
output: `${message ?? "Ready"}\nReady to pay`,
};
},
};Example plugins:
examples/plugins/confirm-before-sign.ts: interactive blocking confirmation before signing.examples/plugins/account-status-balances.ts: reports Base USDC and Tempo token balances inknox account status.
To activate an example plugin, copy it to one of the plugin directories:
mkdir -p .knox/plugins
cp examples/plugins/confirm-before-sign.ts .knox/plugins/
cp examples/plugins/account-status-balances.ts .knox/plugins/bun install
bun run typecheckKnox uses Changesets with a manually triggered GitHub Actions workflow.
- Add a changeset in your PR:
bun run changeset- Merge the PR into
main. - In GitHub, run the
Releaseworkflow manually from Actions. - If there are unpublished changesets, the workflow creates or updates a version PR (
chore: version packages). - Merge the version PR, then run the
Releaseworkflow again to publish.
Trusted publishing is configured for npm via GitHub OIDC, so no long-lived NPM_TOKEN is required for normal releases.