Skip to content

assetFromV2 case-sensitive asset match — "sBTC" rejected, only "SBTC" accepted #13

@decentraliser

Description

@decentraliser

Summary

assetFromV2() in dist/utils.js (v2.0.3, also 2.0.1) does case-sensitive string comparison on the asset symbol. Server-side x402 facilitators that send the canonical mixed-case "sBTC" get silently fall-through to STX transfer.

Repro

const { assetFromV2 } = require('x402-stacks');

assetFromV2("STX")   // → { tokenType: "STX" }       ✅
assetFromV2("SBTC")  // → { tokenType: "sBTC" }      ✅
assetFromV2("sBTC")  // → {}  ← undefined tokenType  ❌
assetFromV2("USDCx") // → {}  ← undefined tokenType  ❌

Impact

When a resource server's 402 response carries accepted.asset = "sBTC" (mixed case, which aibtcdev's own agent payment docs list as canonical), signPaymentV2() falls through to the STX-transfer branch. The signed transaction is a native STX transfer for 2 microSTX instead of an sBTC SIP-010 transfer.

x402-relay.aibtc.com/verify then correctly rejects: {"isValid":false,"invalidReason":"unsupported_scheme"} (decoded internally as "Token type mismatch").

Internal inconsistency

assetFromV2 produces tokenType: "sBTC" (mixed case) as the canonical INTERNAL representation, but only accepts "SBTC" (uppercase) as input. So the library's own output cannot feed back into its own input. The relay docs document "sBTC" as the canonical asset string for the wire format. Both can't be right.

Suggested fix

Make matching case-insensitive at the input boundary:

function assetFromV2(asset) {
    const upper = asset?.toUpperCase();
    if (upper === 'STX')   return { tokenType: 'STX' };
    if (upper === 'SBTC')  return { tokenType: 'sBTC' };
    if (upper === 'USDCX') return { tokenType: 'USDCx' };
    // ... rest unchanged
}

Same fix applies in any other asset === '...' checks the lib does.

Workaround in production today

Resource servers that interop with this lib must normalize the asset string to uppercase before sending the 402 response. We did this in EmblemAI's x402 admin config (changed asset: "sBTC"asset: "SBTC") and the payment flow immediately worked end-to-end. But every other resource server using this SDK will hit the same wall.

Versions tested

  • x402-stacks@2.0.3 (current): bug present
  • x402-stacks@2.0.1: bug present
  • Server: EmblemAI hustle-v2 prod (@x402/core@2.3.1 + ExactStacksScheme adapter)
  • Facilitator: x402-relay.aibtc.com v1.32.1

Evidence

Decoded payment-signature header from x402-stacks signing on asset:"sBTC" (pre-fix):

  • Signed tx hex: 00000000010400... deserializes to a TokenTransfer payload (native STX), not a ContractCall to sbtc-token

After patching the lib locally for case-insensitive match, same flow against same server produces a ContractCall to SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token for 2 sats sBTC → relay verifies, settles, tool returns 200.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions