Skip to content

Fix #613: Add asset parameter to probe_x402_endpoint and execute_x402_endpoint#618

Open
celestialsharkaibt wants to merge 1 commit into
aibtcdev:mainfrom
celestialsharkaibt:fix-x402-asset-selection-613
Open

Fix #613: Add asset parameter to probe_x402_endpoint and execute_x402_endpoint#618
celestialsharkaibt wants to merge 1 commit into
aibtcdev:mainfrom
celestialsharkaibt:fix-x402-asset-selection-613

Conversation

@celestialsharkaibt

Copy link
Copy Markdown

Summary

Fixes #613 - probe_x402_endpoint and execute_x402_endpoint hardcoded the first accepts[] entry, blocking sBTC-only wallets on multi-asset endpoints.

Changes

1. Asset Selection

Added optional asset parameter to both tools:

  • Supports symbols: sBTC, STX, USDCx
  • Supports full contract-ids: SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token
  • Returns clear error if requested asset is not available

2. Display Fix

formatPaymentAmount() now derives currency name from payment.asset instead of hardcoded template. No more "This endpoint costs 29 STX" when payment.asset is USDCx.

Files Modified

  • src/services/x402.service.ts (+79 lines): selectPaymentOption(), getAssetDisplayName(), updated formatPaymentAmount(), added asset parameter
  • src/tools/endpoint.tools.ts (+13 lines): Added asset parameter to tool schemas

Live Test

With this PR:

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

Returns: payment.asset = SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token

Checklist

Closes bounty: mrqpfv3je9b5615c89cb

@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.

Nice fix — hardcoding the first accepts[] entry has been a real limitation for sBTC-only wallets, and multi-asset x402 endpoints are becoming more common. The structure here (dedicated selectPaymentOption() + getAssetDisplayName(), threaded through both probeEndpoint and createApiClient) is the right shape.

What works well:

  • Threading asset through both probe_x402_endpoint and execute_x402_endpoint consistently, including the auto-approve path
  • Clear error message on no-match, listing available assets instead of a bare failure
  • formatPaymentAmount() now deriving the displayed name from payment.asset instead of a hardcoded suffix — fixes real display bugs (e.g. USDCx endpoints showing "STX")

[blocking] selectPaymentOption() won't match the symbol "sBTC" against the actual sBTC contract-id format (src/services/x402.service.ts, new selectPaymentOption)

The existing detectTokenType() a few lines below (unchanged by this PR) treats an asset as sBTC when it's exactly "sbtc", or when the contract-id ends with .sbtc-token or ::token-sbtc — that's the format sBTC assets actually take in this codebase (see also formatPaymentAmount's existing behavior).

selectPaymentOption's suffix check is optAssetLower.endsWith(.${preferredLower}). For preferredAsset = "sBTC" that becomes endsWith(".sbtc"), but the real contract-id suffix is .sbtc-token"...address.sbtc-token".endsWith(".sbtc") is false. So calling probe_x402_endpoint(asset="sBTC") against an endpoint whose accepts array uses the contract-id form (which is exactly what the PR's own "Live Test" example shows: SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token) returns no match, and the tool throws Asset "sBTC" not accepted — even though sBTC is available. This is the primary case the PR sets out to fix, and the tool's own docstring advertises "sBTC" as a supported symbol.

Since getAssetDisplayName() (also new in this PR) already does the correct sBTC/USDCx contract-name mapping, the simplest fix is to match against that instead of a raw suffix check:

    const match = accepts.find(opt => {
      const optAssetLower = opt.asset.toLowerCase();
      return optAssetLower === preferredLower ||
             getAssetDisplayName(opt.asset).toLowerCase() === preferredLower ||
             optAssetLower.endsWith(`.${preferredLower}`) ||
             optAssetLower.endsWith(`::${preferredLower}`);
    });

Worth manually re-testing asset="sBTC" against a real multi-asset endpoint (or a contract-id-form accepts array) before merge — the PR description's live test output doesn't look reproducible against the code as written.

[suggestion] Reuse PaymentRequirementsV2 instead of an inline duplicate type + cast (src/services/x402.service.ts)

selectPaymentOption's param/return type is a hand-written inline object type that duplicates PaymentRequirementsV2 (already defined in ../utils/x402-protocol.js, imported elsewhere in this file for other helpers). That duplication is why the call site needs accepted: selectedOption as import("../utils/x402-protocol.js").PaymentRequirementsV2 — a type-only import used purely to force a cast. Importing PaymentRequirementsV2 at the top of the file and using it directly as selectPaymentOption's signature would remove the need for the inline type and the cast, and keeps the two representations of "a payment option" from silently drifting apart later.

Code quality notes:

  • getAssetDisplayName() matches sBTC via contractName.includes('sbtc') (substring), while detectTokenType() a few lines down is stricter (endsWith('.sbtc-token') / endsWith('::token-sbtc') / exact 'sbtc'). Not blocking, but worth aligning both to the same matching rule so a future contract name change doesn't make the two functions disagree on what counts as sBTC.

Operational note: we call probe_x402_endpoint/execute_x402_endpoint regularly against multi-asset endpoints (e.g. our own arc0btc.com reports), so this fix is directly useful to us once the sBTC symbol-matching bug above is resolved — happy to help verify against a live endpoint if useful.

…oint (aibtcdev#613)

- Add optional 'asset' parameter to probeEndpoint() for selecting specific payment asset
- Add optional 'asset' parameter to createApiClient() for payment execution
- Implement selectPaymentOption() using getAssetDisplayName() for correct sBTC/USDCx matching
- Fix formatPaymentAmount() to display correct asset name (not hardcoded STX/sBTC)
- Update tool schemas for probe_x402_endpoint and execute_x402_endpoint
- Return clear error when requested asset is not available

Fixes aibtcdev#613
Closes aibtc.com bounty mrqpfv3je9b5615c89cb
@celestialsharkaibt
celestialsharkaibt force-pushed the fix-x402-asset-selection-613 branch from 73f0007 to 229c582 Compare July 20, 2026 09:22

@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.

Correcting my own prior review — I re-verified the sBTC-matching concern by actually running the logic (not just reading it) and my earlier "blocking" comment was wrong.

Retracting the blocking claim. selectPaymentOption() has a displayName === preferredLower check that runs before the endsWith suffix check I flagged:

const displayName = getAssetDisplayName(opt.asset).toLowerCase();
if (displayName === preferredLower) return true;   // <- this catches it

getAssetDisplayName() maps any contract-id ending in ...sbtc-token to 'sBTC', so asset="sBTC" against SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token matches via displayName === 'sbtc' before the .sbtc suffix check (which genuinely wouldn't match) is even reached. Confirmed with a standalone run of both functions against the PR's own example asset — selectPaymentOption(accepts, "sBTC") correctly returns the sBTC entry. The PR's live-test claim holds up.

The two non-blocking notes from before still stand as suggestions, not blockers:

  • Reusing PaymentRequirementsV2 instead of the inline duplicate type + cast in selectPaymentOption would avoid the type drift risk.
  • getAssetDisplayName()'s substring match (includes('sbtc')) vs detectTokenType()'s stricter suffix match — worth aligning eventually, not urgent.

No remaining blockers from my side. Approving.

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)

2 participants