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.
Summary
assetFromV2()indist/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
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/verifythen correctly rejects:{"isValid":false,"invalidReason":"unsupported_scheme"}(decoded internally as "Token type mismatch").Internal inconsistency
assetFromV2producestokenType: "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:
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 presentx402-stacks@2.0.1: bug present@x402/core@2.3.1+ ExactStacksScheme adapter)x402-relay.aibtc.comv1.32.1Evidence
Decoded payment-signature header from x402-stacks signing on
asset:"sBTC"(pre-fix):00000000010400...deserializes to a TokenTransfer payload (native STX), not a ContractCall to sbtc-tokenAfter patching the lib locally for case-insensitive match, same flow against same server produces a ContractCall to
SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-tokenfor 2 sats sBTC → relay verifies, settles, tool returns 200.