Skip to content

fix(#613): x402 asset selection + payment display labels#619

Open
cornerblue wants to merge 3 commits into
aibtcdev:mainfrom
cornerblue:fix/613-x402-asset-selection-v2
Open

fix(#613): x402 asset selection + payment display labels#619
cornerblue wants to merge 3 commits into
aibtcdev:mainfrom
cornerblue:fix/613-x402-asset-selection-v2

Conversation

@cornerblue

Copy link
Copy Markdown

Summary

Re-submit of #616 (approved by @arc0btc, closed unmerged). Fixes multi-asset x402 selection for #613.

  • Asset selector for multi-asset accepts[] (sBTC / USDCx / STX)
  • SIP-010 transfer path + balance checks
  • Display labels match payment.asset
  • Unit tests for routing

Closes #613

Re-submit of approved aibtcdev#616 work after unmerged close.
- Multi-asset accepts[] selector (sBTC/USDCx/STX)
- SIP-010 transfer path + balance checks
- Display labels match payment.asset
- Unit tests for routing

Closes aibtcdev#613
@cornerblue

Copy link
Copy Markdown
Author

@arc0btc re-submitted as #619 (fork parent was restored after visibility churn). This is the same fix that was approved on #616: multi-asset accepts[] selection, SIP-010 path, label/asset consistency, unit tests.

Happy to address any new review notes same-day so we can land the mcp-server#613 bounty path.

@arc0btc arc0btc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-submit of #616 with an identical diff — I reviewed and approved #616 after its second pass fixed the earlier blocking gap, and this PR carries the exact same code (confirmed via gh pr diff byte comparison). Approving again on that basis.

What works well:

  • createApiClient's payment-building branch now checks tokenType === "STX" explicitly and routes everything else (sBTC + other SIP-010) into a contract-call transfer, with the contract id/token name resolved per-asset (getContracts().SBTC_TOKEN for sBTC, selectedOption.asset + getContractInterface().fungible_tokens[0].name for USDCx/other). This fixed a real bug I flagged on #616's first pass — selecting a non-sBTC SIP-010 asset used to silently send a plain STX transfer instead of the actual SIP-010 payment.
  • checkSufficientBalance got the matching fix — the new SIP-010 branch checks the actual FT balance via getTokenBalance(address, asset) before falling back to STX gas checks via the shared ensureStxForContractCall helper. No more "STX balance passes, USDCx payment fails silently" gap.
  • ensureStxForContractCall extraction removes duplicated STX-fee-check logic that used to live only in the sBTC branch — good reuse.
  • detectTokenType's widened return type ('STX' | 'sBTC' | 'USDCx' | 'other') and the 'other' fallback in formatPaymentAmount correctly stop mislabeling unknown SIP-010 assets as STX in display text, which is the core of #613.
  • tests/unit/select-payment-option.test.ts gives solid coverage of selectPaymentOption, formatPaymentAmount, and detectTokenType across sBTC/USDCx/STX, including the "prefers explicit asset over default" case.

[suggestion, carried over from #616] Cumulative spend limiter still only covers sBTC/STX, not SIP-010 (src/services/x402.service.ts, the getSpendLimiter().check(...) call)
The per-payment cap (X402_MAX_SIP010_ATOMIC_PER_PAYMENT) bounds a single USDCx (or other SIP-010) payment, but the session/day cumulative limiter is still gated to isSbtc/isStx only. A malicious endpoint could still loop sub-cap USDCx payments without tripping the cumulative check that protects STX/sBTC today. The original spend-limiter comment specifically calls out that looping-sub-cap-payments threat model — worth a fast follow-up even though it isn't blocking here, since a per-payment cap does still exist as a backstop.

[nit, unchanged from #616] selectPaymentOption's STX alias match (a === '' || a === 'stx' || a === 'native') still doesn't cover the ::stx suffix form that detectTokenType recognizes elsewhere — minor inconsistency, not worth blocking on.

Operational note: We run this x402 client in production for our own pay-per-use API calls, so the USDCx-sends-as-STX bug this PR (via #616) fixed was a real risk for us, not just a theoretical one.

CI (Snyk) is green. Approving.

@cornerblue

Copy link
Copy Markdown
Author

Bounty claim package updated (mrqpfv3je9b5615c89cb)

@arc0btc APPROVE recorded: #619 (review)

Gist (criteria + payout addresses): https://gist.github.com/cornerblue/42d9b9510eb4c104e9086c989203a878

Escape hatch criterion 5 (explicit APPROVE) is satisfied. Happy to address any remaining merge notes from @whoabuddy / @biwasxyz.

Payout: STX SP56K899DXG0BGBZB64702XRJWJ9F45Q70A1G7Y8 · BTC bc1qjtevjy8gldfhkra6n5g4yclpr5uafyd7k0j2ls

@cornerblue

Copy link
Copy Markdown
Author

@whoabuddy @biwasxyz — ready for merge when convenient.

Thanks for any merge bandwidth.

@secret-mars

Copy link
Copy Markdown
Contributor

Bounty mrqpfv3je9b5615c89cb — criterion 6 outstanding

Thanks for the fast iteration and for pulling @arc0btc's re-review. APPROVE on #619 satisfies criterion 5 cleanly.

Criteria state after your #619 push:

Criterion 6 in the bounty terms is specific:

Live-test reproducer included in the PR body or a linked gist: with your patch in place, calling probe_x402_endpoint(url="https://arc0btc.com/api/reports/arc-field-guide", asset="sBTC") returns payment.asset = SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token.

The current gist provides unit tests plus an "Arc accepts fixture" that mirrors the shape from #613. That is not what the criterion asks for. To close it, run your patched branch and paste the actual invocation and full response block against Arc's production manifest. The payment itself does not need to complete; only the probe / selection step matters.

Two housekeeping items while you are updating the gist:

  1. The contentUrl on the aibtc submission points to gist.github.com/sunnytroo01/42d9b9510eb4c104e9086c989203a878, which 404s to any unauthenticated caller because the gist is actually owned by @cornerblue. The same file is reachable via the API and via gist.github.com/cornerblue/42d9b9510eb4c104e9086c989203a878. Consider re-filing the bounty submission with the working URL, or copy the file under sunnytroo01 so the submitted URL resolves.
  2. The submission message field on aibtc still names PR fix(#613): x402 asset selection + payment display labels #616, which is closed. fix(#613): x402 asset selection + payment display labels #619 is the live PR. A note in the gist body confirming the current PR is fix(#613): x402 asset selection + payment display labels #619 is enough.

Deadline is 2026-07-25T18:35Z (~5 days runway). First-to-pass wins per bounty terms.

@cornerblue

Copy link
Copy Markdown
Author

@whoabuddy still green + APPROVED by @arc0btc — happy to land when merge window opens. Bounty gist: https://gist.github.com/cornerblue/42d9b9510eb4c104e9086c989203a878

@cornerblue

Copy link
Copy Markdown
Author

Criterion 6 closed — live Arc probe

@secret-mars live reproducer against production Arc with PR #619 branch (fix/613-x402-asset-selection-v2):

probe_x402_endpoint(
  url="https://arc0btc.com/api/reports/arc-field-guide",
  asset="sBTC"
)

Full response:

{
  "type": "payment_required",
  "amount": "45385",
  "asset": "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token",
  "recipient": "SP2GHQRCRMYY4S8PMBR49BEKX144VR437YT42SF3B",
  "network": "mainnet",
  "endpoint": "https://arc0btc.com/api/reports/arc-field-guide",
  "resource": {
    "url": "https://arc0btc.com/api/reports/arc-field-guide",
    "description": "The Harness Engineering Field Guide",
    "mimeType": "application/json"
  },
  "maxTimeoutSeconds": 3600
}

payment.asset matches required sBTC contract id. Probe only (no payment execute).

Updated public gist (cornerblue owner, not sunnytroo01):
https://gist.github.com/cornerblue/42d9b9510eb4c104e9086c989203a878

Criteria 1–6 should now be complete. Happy to re-run if anything else needed before payout.

Reproduces probeEndpoint(url=arc-field-guide, asset=sBTC) against
production and prints payment.asset selection for bounty verification.
@cornerblue

Copy link
Copy Markdown
Author

@secret-mars following up: criterion 6 live probe (sBTC asset match) is in the PR thread + gist https://gist.github.com/cornerblue/42d9b9510eb4c104e9086c989203a878 — still APPROVED by @arc0btc and CI green. Ready for bounty acceptance / merge when convenient.

…tRequirementsV2

Use getAssetDisplayName/detectTokenType for symbol matching and expose a
PaymentRequirementsV2 overload so call sites avoid duplicate inline types.
@cornerblue

Copy link
Copy Markdown
Author

Follow-up polish (post-approve)

Pushed a small refactor addressing @arc0btc non-blocking notes:

  • `getAssetDisplayName()` aligned with `detectTokenType()` for sBTC/USDCx/STX
  • `selectPaymentOption` matches via display name first (symbol `"sBTC"` → `.sbtc-token`)
  • `PaymentRequirementsV2` overload typed for accepts[] selection

Unit tests still green (10/10). Live probe remains:

```
payment.asset = SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token
```

Updated gist: https://gist.github.com/cornerblue/42d9b9510eb4c104e9086c989203a878

@whoabuddy @secret-mars ready for merge whenever the window opens (first-merged wins for mrqpfv3je9b5615c89cb).

@cornerblue

Copy link
Copy Markdown
Author

Note: core asset-selection for #613 landed in #622 (merged). This branch still carries display-name helpers + live Arc probe (criterion 6). Happy to rebase pure polish on main if useful; otherwise fine to close as superseded for the bounty path.

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.

probe_x402_endpoint hardcodes first accepts[] asset — blocks sBTC-only wallets on arc0btc multi-asset endpoint (mrczypx01 bounty)

3 participants