fix(public-api): reject sell assets we cannot build a transaction for - #12586
Conversation
extractTransactionData only serializes eip155, bip122, cosmos and solana tx data, so Tron, Sui, TON, NEAR and Starknet quotes came back with no transactionData at all - advertised as supported, impossible to execute. Gate rates and quotes on the sell chain being one we can serialize. Those chains stay valid as a buy asset, where only a receive address is needed, and /chains now carries isSellSupported so clients can build a correct sell picker. Also drop CoW Swap from the enabled swappers: it signs an off-chain EIP-712 order rather than a transaction, which the wire has no shape for yet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rates are informational, so a client that fulfils the swap elsewhere - the widget hands these off to app.shapeshift.com - can ask for them anyway via allowNonExecutableSellChain. Defaults off, so a consumer with no fallback path still gets nothing it cannot execute. /v1/swap/quote stays hard gated with no escape hatch, since a quote implies something to sign. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reverts e8ce4d4. Its only intended consumer was the widget's pre-redirect rate estimate, which is being dropped: the api enables a thinner swapper set than the app for the chains in question, so any rate served under the flag would understate what the user receives. Nothing else asks for rates it cannot execute, so the escape hatch has no caller. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nothing imports it - the set and the predicate are what callers use. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…step Review follow-ups. The rejection carried no code, so clients had to regex the message to tell it from a validation 400 - it now sends UNSUPPORTED_SELL_CHAIN, distinct from the existing UNSUPPORTED_CHAIN raised for evm chains with no viem client. The namespace gate only proves a chain is serializable, not that this swapper produced anything; assert transactionData on the step so an enabled swapper that returns nothing to sign fails loudly instead of 200ing an unusable quote. Document destination-only chains in the partner guide, which described neither isSellSupported nor the new 400. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 44 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
e911bc6 to
a29acb2
Compare
Caught by the root type-check, which runs over packages/**/* with options the standalone public-api tsconfig does not share. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Description
extractTransactionDataonly serializes four chain namespaces —eip155,bip122,cosmos,solana. Anything else falls off the end and returnsundefined, andtransformQuoteStepassigns it without complaint. So Tron, Sui, TON, NEAR and Starknet were advertised on/v1/chainsand/v1/assets, served rates and quotes, and handed back a quote with nothing to sign.This stops the API advertising what it can't fulfil:
EXECUTABLE_SELL_CHAIN_IDSis derived fromSUPPORTED_CHAIN_IDSby namespace, so it can't drift from what the extractor actually handles. Comments on both sides point at each other./v1/swap/ratesand/v1/swap/quotereject those chains as a sell asset with400/code: UNSUPPORTED_SELL_CHAIN(distinct from the existingUNSUPPORTED_CHAINraised for EVM chains with no viem client).ETH→TRXand friends are unaffected./v1/chainsgainsisSellSupported: booleanso clients can build a correct sell picker. The partner guide documents it, including how to filter assets by joining onchainId(the constraint is chain-level, so there's no per-asset flag).ENABLED_SWAPPER_NAMES— it signs an off-chain EIP-712 order rather than a transaction, so every CoW quote carried notransactionDataat all. Same defect, at the swapper level. Tracked in SS-5734.getQuotenow asserts the step actually hastransactionDataand returns502if not. The namespace gate only proves a chain is serializable; this proves the swapper produced something. Nothing currently reaches it — it's regression prevention.Two of the six commits are a feature and its revert (
allowNonExecutableSellChain, an opt-in to serve rates anyway). It was built for the swap widget's redirect fallback and then backed out: for these five chains the API enables a strictly thinner swapper set than the app does — STON.fi, AVNU, Sun.io and Cetus are the chain-native DEXes and none are API-enabled — so any rate served under the flag would systematically understate what the user receives and argue against clicking through. The revert commit records that reasoning.Issue (if applicable)
closes #
Risk
Medium, and worth splitting in two because the halves are not equivalent:
/v1/swap/quote— fixing a defect, not breaking a contract. These chains returned a 200 whosetransactionDatawasundefined. No consumer could ever have completed a swap that way, so no working integration breaks; the 400 replaces a response that was already a dead end./v1/swap/rates— a genuine, narrow removal. A rate is useful withouttransactionDatafor price display or comparison. Anyone using the API purely to show Tron/NEAR prices loses that.Separately, dropping CoW Swap removes real rate coverage on Ethereum, Gnosis and Base until SS-5734 lands. That's the change most likely to prompt a "why did rates get worse" question.
No on-chain transaction construction is altered. Executable paths are untouched — the same tx data goes out for every chain that could already be executed.
CoW Swap becomes unavailable via the API. Tron, Sui, TON, NEAR and Starknet become unavailable as sell assets (they were never executable). Everything else is unchanged.
Testing
Engineering
Verified end-to-end against a locally built server hitting real mainnet unchained/node/swapper endpoints, at branch tip
9110e0dbf1:400 UNSUPPORTED_SELL_CHAIN— all five400200, 5 valid rates (NEAR Intents, THORChain, Relay, Chainflip, ButterSwap)200, 9 swappers, CoW absent400 Swapper not available: CoW Swap200,transactionData.type: evm200,transactionData.type: cosmossdk_msg_deposit/v1/chainsisSellSupported: falseIntegration tests were restructured: a
NON_SELLABLEtable asserts rejection on both endpoints, that buy-side rates still work for the same chains, and that/v1/chainsflags them correctly. A new case asserts every quote in the suite carriestransactionData.Note the full monorepo
pnpm type-checkcould not be run in this worktree (no localnode_modules;tsc --buildfails inunchained-clientcodegen andhdwallet-coreethers types before reaching this code — both environmental and unrelated). The standalonepublic-apicheck above is clean.Operations
Not user-facing in the web app — the app doesn't consume the public API. For API partners: selecting Tron/Sui/TON/NEAR/Starknet as a sell asset now returns a clear 400 instead of an unusable quote; the same chains as a buy asset are unchanged and should be regression-checked.
Screenshots (if applicable)
N/A
🤖 Generated with Claude Code