fix: fix Relay non-EVM transaction notification and Tron calldata handling#12015
fix: fix Relay non-EVM transaction notification and Tron calldata handling#120150xApotheosis wants to merge 1 commit intodevelopfrom
Conversation
…dling
Two bugs caused Relay Tron deposits to be untracked:
1. The Relay indexer notification in checkTradeStatus was gated behind
isEvmChainId(), preventing non-EVM chains (Tron, Solana, Bitcoin)
from notifying Relay about completed deposits. Changed the gate to
check chainIdToRelayChainId membership instead, which correctly
covers all Relay-supported chains.
2. Tron Relay transactions were built as simple TRC20 transfers instead
of using the Relay quote calldata to call depositErc20() on the vault
contract. The quote's parameter.data field (containing the vault
calldata) was not being stored in relayTransactionMetadata, and the
transaction builder always used buildSendApiTransaction. Now:
- getTrade.ts stores the quote's calldata in metadata.data
- getTrade.ts sets allowanceContract for proper token approval
- getUnsignedTronTransaction.ts uses buildCustomApiTx with the
Relay calldata when available, falling back to buildSendApiTransaction
for NearIntents and other simple-transfer swappers
Tron remains disabled in chainIdToRelayChainId until this fix is
verified end-to-end with the Relay API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR disables Tron support in the RelaySwapper by removing the tronChainId mapping entry, replacing the EVM chain filter with chainIdToRelayChainId mapping checks, and updating Tron transaction handling to leverage relay metadata with custom API transactions. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/swapper/src/swappers/RelaySwapper/constant.ts`:
- Around line 89-93: Remove the inline “DISABLED” explanatory comment block in
constant.ts that precedes the commented-out [tronChainId]: 728126428 entry; the
file should not contain rationale comments—only keep the actual constants (or
the commented-out key if you must leave it disabled) and move the explanatory
text to the PR/ticket description or changelog. Locate the block around the
tronChainId symbol in RelaySwapper/constant.ts and delete the multi-line
rationale, ensuring the file remains comment-free per repo guidelines.
In `@packages/swapper/src/swappers/RelaySwapper/utils/getTrade.ts`:
- Around line 654-663: The code currently defaults missing Relay Tron calldata
or vault address to empty strings which can lead getUnsignedTronTransaction to
mis-handle the quote; update the isRelayQuoteTronItemData branch to validate
that selectedItem.data.parameter.contract_address and
selectedItem.data.parameter.data are present and non-empty, and if either is
missing return/throw a clear validation error (do not fallback to ''), then
populate relayTransactionMetadata and allowanceContract using the validated
values; reference isRelayQuoteTronItemData,
selectedItem.data.parameter.contract_address, selectedItem.data.parameter.data,
and getUnsignedTronTransaction when making the change.
In `@packages/swapper/src/tron-utils/getUnsignedTronTransaction.ts`:
- Around line 49-63: The guard in getUnsignedTronTransaction currently throws a
generic Error when relayTransactionMetadata.to is missing; replace that with a
custom error from `@shapeshiftoss/errors` (import the appropriate error
class/function) and throw a namespaced/i18n-ready error (e.g., code
"RELAY_MISSING_TO") instead of new Error('Missing Relay transaction destination
address'), including relevant context (relayTransactionMetadata, sellAsset, step
or from) in the error details to aid debugging while preserving the function
return flow.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
packages/swapper/src/swappers/RelaySwapper/constant.tspackages/swapper/src/swappers/RelaySwapper/endpoints.tspackages/swapper/src/swappers/RelaySwapper/utils/getTrade.tspackages/swapper/src/tron-utils/getUnsignedTronTransaction.ts
| // DISABLED: Tron deposits are built as simple TRC20 transfers instead of depositErc20() vault calls, | ||
| // and the Relay indexer notification is never sent for non-EVM chains. This causes deposits to be | ||
| // untracked by Relay, resulting in stuck/lost funds. Re-enable once the Tron transaction building | ||
| // is verified to use the Relay quote calldata (depositErc20) correctly. | ||
| // [tronChainId]: 728126428, |
There was a problem hiding this comment.
Remove the inline “DISABLED” comment block.
Repo guidelines disallow adding code comments unless explicitly requested. Please move this rationale to a ticket/PR description and keep the constants file comment-free.
🧹 Proposed cleanup
- // DISABLED: Tron deposits are built as simple TRC20 transfers instead of depositErc20() vault calls,
- // and the Relay indexer notification is never sent for non-EVM chains. This causes deposits to be
- // untracked by Relay, resulting in stuck/lost funds. Re-enable once the Tron transaction building
- // is verified to use the Relay quote calldata (depositErc20) correctly.
- // [tronChainId]: 728126428,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // DISABLED: Tron deposits are built as simple TRC20 transfers instead of depositErc20() vault calls, | |
| // and the Relay indexer notification is never sent for non-EVM chains. This causes deposits to be | |
| // untracked by Relay, resulting in stuck/lost funds. Re-enable once the Tron transaction building | |
| // is verified to use the Relay quote calldata (depositErc20) correctly. | |
| // [tronChainId]: 728126428, |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/swapper/src/swappers/RelaySwapper/constant.ts` around lines 89 - 93,
Remove the inline “DISABLED” explanatory comment block in constant.ts that
precedes the commented-out [tronChainId]: 728126428 entry; the file should not
contain rationale comments—only keep the actual constants (or the commented-out
key if you must leave it disabled) and move the explanatory text to the
PR/ticket description or changelog. Locate the block around the tronChainId
symbol in RelaySwapper/constant.ts and delete the multi-line rationale, ensuring
the file remains comment-free per repo guidelines.
| if (isRelayQuoteTronItemData(selectedItem.data)) { | ||
| return { | ||
| allowanceContract: '', | ||
| allowanceContract: selectedItem.data?.parameter?.contract_address ?? '', | ||
| solanaTransactionMetadata: undefined, | ||
| relayTransactionMetadata: { | ||
| relayId: quote.steps[0].requestId, | ||
| orderId, | ||
| to: selectedItem.data?.parameter?.contract_address, | ||
| data: selectedItem.data?.parameter?.data, | ||
| }, |
There was a problem hiding this comment.
Fail fast if Relay Tron calldata or vault address is missing.
parameter.contract_address/parameter.data are optional-chained and defaulted to ''. If the Relay response is malformed, this produces incomplete metadata and getUnsignedTronTransaction can fall back to a simple TRC20 transfer—the stuck-funds path this PR is fixing. Treat missing fields as a hard error and remove the empty-string fallback.
✅ Suggested validation
if (isRelayQuoteTronItemData(selectedItem.data)) {
+ const contractAddress = selectedItem.data?.parameter?.contract_address
+ const calldata = selectedItem.data?.parameter?.data
+ if (!contractAddress) throw new Error('Relay Tron quote missing contract_address')
+ if (!calldata) throw new Error('Relay Tron quote missing calldata')
return {
- allowanceContract: selectedItem.data?.parameter?.contract_address ?? '',
+ allowanceContract: contractAddress,
solanaTransactionMetadata: undefined,
relayTransactionMetadata: {
relayId: quote.steps[0].requestId,
orderId,
- to: selectedItem.data?.parameter?.contract_address,
- data: selectedItem.data?.parameter?.data,
+ to: contractAddress,
+ data: calldata,
},
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/swapper/src/swappers/RelaySwapper/utils/getTrade.ts` around lines
654 - 663, The code currently defaults missing Relay Tron calldata or vault
address to empty strings which can lead getUnsignedTronTransaction to mis-handle
the quote; update the isRelayQuoteTronItemData branch to validate that
selectedItem.data.parameter.contract_address and
selectedItem.data.parameter.data are present and non-empty, and if either is
missing return/throw a clear validation error (do not fallback to ''), then
populate relayTransactionMetadata and allowanceContract using the validated
values; reference isRelayQuoteTronItemData,
selectedItem.data.parameter.contract_address, selectedItem.data.parameter.data,
and getUnsignedTronTransaction when making the change.
| if (relayTransactionMetadata?.data) { | ||
| const to = relayTransactionMetadata.to | ||
| if (!to) throw new Error('Missing Relay transaction destination address') | ||
|
|
||
| const isNativeTron = sellAsset.assetId === tronAssetId | ||
| const value = isNativeTron ? step.sellAmountIncludingProtocolFeesCryptoBaseUnit : '0' | ||
|
|
||
| return adapter.buildCustomApiTx({ | ||
| from, | ||
| to, | ||
| accountNumber, | ||
| data: relayTransactionMetadata.data, | ||
| value, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Use a custom error class for the new Relay guard.
The new guard throws a generic Error. Please switch to a custom error class from @shapeshiftoss/errors (or propagate via the existing Result flow) so error codes remain consistent and i18n-ready.
As per coding guidelines: ALWAYS use custom error classes from @shapeshiftoss/errors with meaningful error codes for internationalization and relevant details in error objects.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/swapper/src/tron-utils/getUnsignedTronTransaction.ts` around lines
49 - 63, The guard in getUnsignedTronTransaction currently throws a generic
Error when relayTransactionMetadata.to is missing; replace that with a custom
error from `@shapeshiftoss/errors` (import the appropriate error class/function)
and throw a namespaced/i18n-ready error (e.g., code "RELAY_MISSING_TO") instead
of new Error('Missing Relay transaction destination address'), including
relevant context (relayTransactionMetadata, sellAsset, step or from) in the
error details to aid debugging while preserving the function return flow.
Description
Fixes two bugs that caused Relay Tron deposits to be completely untracked, resulting in stuck user funds.
Depends on / supersedes: #12014 (production mitigation — Tron remains disabled until this fix is verified)
Bug 1: Indexer notification gated to EVM-only (
endpoints.ts)The
notifyTransactionIndexingcall incheckTradeStatuswas gated behindisEvmChainId(chainId), which excluded all non-EVM chains (Tron, Solana, Bitcoin) from notifying Relay's indexer about completed deposits. This notification is how Relay correlates an on-chain transaction hash with a swap intent.Fix: Changed the gate from
isEvmChainId(chainId)tochainIdToRelayChainId[chainId] !== undefined, which correctly covers all Relay-supported chains.Bug 2: Tron transactions built as simple transfers (
getTrade.ts+getUnsignedTronTransaction.ts)The Relay quote for Tron returns calldata for
depositErc20(depositor, token, amount, id)— a vault contract method that embeds the relay request ID on-chain. However:getTrade.tswas not storing the quote'sparameter.datafield inrelayTransactionMetadatagetTrade.tswas settingallowanceContractto''instead of the vault contract addressgetUnsignedTronTransaction.tsalways usedbuildSendApiTransaction(simple TRC20 transfer) instead ofbuildCustomApiTxwith the vault calldataFix:
relayTransactionMetadata.dataallowanceContractto the vault contract address for proper token approvalrelayTransactionMetadata.dataexists, usebuildCustomApiTxto call the vault'sdepositErc20method directlyBackground
Discovered via investigation of transaction
4a76a14976d4ed350d846a33fcd5f8fdf8f0d4053c522c59941c2b7e52f3d6a4— 500 USDT sent to Relay's Tron vault as a simple TRC20 transfer that Relay has zero record of. The Relay/requests/v2API confirms no matching request exists for the user or tx hash.Risk
Medium risk — Tron is currently disabled on Relay (via #12014) so these code changes cannot be exercised until Tron is re-enabled. The notification gate change (
isEvmChainId→chainIdToRelayChainIdcheck) affects the status polling path for all chains but is functionally equivalent for EVM chains and strictly additive for non-EVM chains.Testing
Engineering
checkTradeStatuscallsnotifyTransactionIndexingfor Solana and Bitcoin Relay swaps (previously skipped)chainIdToRelayChainId, get a Relay quote for a Tron sell, and verify:relayTransactionMetadata.datais populated with vault calldataallowanceContractis set to the vault addressdepositErc20()on the vault (not a simple TRC20 transfer)Operations
Summary by CodeRabbit
Bug Fixes
Refactor