mcpField
N
nonce-manager

nonce-manager

github.com/aibtcdevDevelopment1193 installsUpdated 1d ago
Repository

About nonce-manager

A command-line nonce tracker for coordinating Stacks transactions across skills and processes. It uses file-locked state, resynchronizes stale values from Hiro, and distinguishes rejected transactions from those already broadcast.

Key features

  1. 01Atomic nonce acquisition
  2. 02Confirmation and failure release
  3. 03Stale-state resynchronization with Hiro
  4. 04File locking for concurrent access
  5. 05Broadcast-versus-rejection handling
  6. 06CLI and TypeScript library access

Use cases

  1. 01Preventing nonce collisions in concurrent transactions
  2. 02Recovering after stale-nonce errors
  3. 03Tracking transaction outcomes before reuse
  4. 04Sharing nonce coordination across skills

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/aibtcdev/skills --skill nonce-manager

After installing, type/nonce-managerin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

AGENT.md
nonce-manager.ts
nonce-store.ts

Details

From the project README

Nonce Manager

Backup sender nonce tracker for Stacks transactions. Use canonical payment-status polling by paymentId as the primary x402 state machine; use this tool for local nonce coordination and recovery when a fresh sender nonce is actually needed.

Problem

Each skill independently fetches nonce from Hiro API. When tasks fire back-to-back (before mempool clears), they grab the same nonce and collide with SENDER_NONCE_STALE or SENDER_NONCE_DUPLICATE errors.

Solution

Single file-locked nonce state at ~/.aibtc/nonce-state.json. Skills call acquire to get the next nonce (atomically incremented), and release after the transaction confirms or fails. If state is stale (>5 min), auto-resyncs from Hiro.

Subcommands

acquire

Get the next nonce for a Stacks address. Atomically increments the stored value. Auto-syncs from Hiro if state is missing or stale (>5 min).

bun run nonce-manager/nonce-manager.ts acquire --address SP...

Output:

{ "nonce": 42, "address": "SP...", "source": "local" }
release

Mark a nonce as confirmed or failed after transaction outcome is known.

bun run nonce-manager/nonce-manager.ts release --address SP... --nonce 42
bun run nonce-manager/nonce-manager.ts release --address SP... --nonce 42 --failed
bun run nonce-manager/nonce-manager.ts release --address SP... --nonce 42 --failed --rejected

Failure kinds (critical distinction):

  • --rejected — tx never reached mempool (signing error, relay 409 nonce rejection). Nonce NOT consumed, safe to roll back and reuse.
  • --broadcast (default when --failed) — tx reached mempool. Nonce IS consumed even if the tx fails on-chain. Do NOT roll back.

Only --failed --rejected triggers a rollback. Default --failed assumes broadcast (safer).

Output:

{ "address": "SP...", "nonce": 42, "action": "confirmed" }
sync

Force re-sync nonce state from Hiro API. Use after manual intervention or mempool clearance.

bun run nonce-manager/nonce-manager.ts sync --address SP...

Output:

{ "nonce": 42, "address": "SP...", "mempoolPending": 3, "lastExecuted": 41, "detectedMissing": [] }
status

Show current nonce state for one or all tracked addresses.

bun run nonce-manager/nonce-manager.ts status
bun run nonce-manager/nonce-manager.ts status --address SP...

Library Import

Skills running in the same process can import directly:

import { acquireNonce, releaseNonce, syncNonce } from "../nonce-manager/nonce-store.js";

const { nonce } = await acquireNonce("SP...");
// ... send transaction ...
await releaseNonce("SP...", nonce, true); // true = success

Nonce Strategy

  1. Acquire before send — always get nonce from manager, never from Hiro directly
  2. Release after confirm/fail — keeps state accurate for next caller
  3. Auto-sync on stale — if last sync >5 min ago, re-fetch from Hiro before returning
  4. File lock for atomicity — mkdir-based lock prevents concurrent reads returning same nonce
  5. Distinguish broadcast vs rejected — only rejected nonces can be rolled back

Integration with x402 Error Codes

Use canonical payment status plus terminalReason first. When local nonce bookkeeping is still needed, map outcomes to release actions like this:

Relay ResponseRelease Action
confirmedrelease --address ... --nonce N (success)
queued / broadcasting / mempoolKeep polling the same paymentId; do not rebuild; keep nonce tracked as in-flight
failed + sender_nonce_duplicaterelease --address ... --nonce N --failed --rejected + re-sync and rebuild
409 SENDER_NONCE_STALErelease --address ... --nonce N --failed --rejected + re-sync
409 SENDER_NONCE_GAPrelease --address ... --nonce N --failed --rejected + re-sync
failed + relay/sponsor/internal reasonDo not treat as sender rebuild guidance; bounded retry or stop by tool policy
replaced / not_foundStop polling the old paymentId; start a new payment flow only if the higher-level action still needs to pay

Comments

Markdown supported