Skip to content

feat(transactions): nonce-tracker integration and retry logic for sponsor-builder#312

Open
arc0btc wants to merge 2 commits intomainfrom
feat/nonce-tracker-sponsor-builder
Open

feat(transactions): nonce-tracker integration and retry logic for sponsor-builder#312
arc0btc wants to merge 2 commits intomainfrom
feat/nonce-tracker-sponsor-builder

Conversation

@arc0btc
Copy link
Copy Markdown
Contributor

@arc0btc arc0btc commented Apr 8, 2026

Summary

  • New retry-strategy.ts (Phase 1a): shared error classification engine for all transaction paths. Classifies relay /sponsor codes (NONCE_CONFLICT, BROADCAST_FAILED, NONCE_DO_UNAVAILABLE, SPONSOR_FAILED, RATE_LIMITED, SENDER_NONCE_CONFLICT) and direct Hiro broadcast errors (ConflictingNonceInMempool, BadNonce). Includes sleep() utility.
  • Modified sponsor-builder.ts (Phase 1b): adds sponsoredContractCallWithRetry() and transferStxSponsoredWithRetry() that acquire sender nonces from nonce-tracker before building transactions. Retries relay-side conflicts with same serialized tx hex; re-acquires nonce and rebuilds on sender-side conflicts. submitToSponsorRelay() is now exported.
  • Updated transactions/index.ts: exports retry-strategy.js.

Existing sponsoredContractCall and transferStxSponsored are unchanged — backward compatible.

Motivation

Sequential Zest supply ops in back-to-back dispatch cycles were generating ConflictingNonceInMempool errors because makeContractCall auto-fetches sender nonce from Hiro on each call, but Hiro's mempool view lags behind transactions we've already broadcast. The nonce-tracker serialises nonce assignment across concurrent processes using a file lock, preventing collisions.

Test plan

  • bun run typecheck passes (verified locally — clean)
  • sponsoredContractCallWithRetry handles relay NONCE_CONFLICT (resubmits same hex)
  • sponsoredContractCallWithRetry handles ConflictingNonceInMempool (re-acquires nonce, rebuilds tx)
  • sponsoredContractCallWithRetry succeeds on first attempt with correct nonce
  • Existing sponsoredContractCall callers unaffected

🤖 Generated with Claude Code

arc0btc and others added 2 commits March 27, 2026 20:41
…-enable-collateral subcommand

Syncs with aibtc-mcp-server v1.46.0 which adds zest_enable_collateral tool
(PR #423, closes #422). Documents the zest-enable-collateral CLI subcommand
and adds it to the mcp-tools frontmatter for discoverability.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sponsor-builder

Phase 1a + 1b from docs/nonce-strategy-alignment-plan.md:

1a. New retry-strategy.ts with classifyRelayError / classifyBroadcastError helpers
    - Classifies relay /sponsor error codes: NONCE_CONFLICT, BROADCAST_FAILED,
      NONCE_DO_UNAVAILABLE, SPONSOR_FAILED, RATE_LIMITED, SENDER_NONCE_CONFLICT
    - Classifies direct Hiro broadcast errors: ConflictingNonceInMempool, BadNonce
    - RetryInfo: { retryable, delayMs, relaySideConflict, senderSideConflict, category }
    - sleep() utility co-located with classification logic

1b. Modified sponsor-builder.ts with nonce-tracker integration
    - Import acquireNonce/releaseNonce from nonce-tracker
    - sponsoredContractCallWithRetry(): acquires sender nonce, builds tx with
      explicit nonce, retries on relay-side conflicts (same tx hex) and
      sender-side conflicts (re-acquire nonce, rebuild tx)
    - transferStxSponsoredWithRetry(): same pattern for STX transfers
    - submitToSponsorRelay() is now exported for external retry resubmission
    - Existing sponsoredContractCall/transferStxSponsored unchanged (backward compat)

Also exports retry-strategy from transactions/index.ts.

Resolves ConflictingNonceInMempool errors in sequential Zest supply ops
when multiple dispatch cycles broadcast sponsored txs concurrently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@tfireubs-ui tfireubs-ui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean implementation. The retry-strategy error classification covers all known relay failure modes (NONCE_CONFLICT, BROADCAST_FAILED, SENDER_NONCE_CONFLICT, RATE_LIMITED, transient). Nonce lifecycle is correct: acquire → build → submit → release(success/rejected/broadcast). The sender-side conflict path correctly releases as 'rejected' and re-acquires before rebuild, preventing nonce leaks.

Two minor observations (not blocking):

  1. The zest-enable-collateral addition to defi/SKILL.md looks like it belongs in a separate PR (unrelated to nonce/retry)
  2. Both sponsoredContractCallWithRetry and transferStxSponsoredWithRetry share identical retry loop logic — could be extracted to a shared helper, but the duplication is minimal and keeping them separate is fine for readability.

LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants