Skip to content

fix: fix Relay non-EVM transaction notification and Tron calldata handling#12015

Draft
0xApotheosis wants to merge 1 commit intodevelopfrom
fix/relay-tron-notification-and-calldata
Draft

fix: fix Relay non-EVM transaction notification and Tron calldata handling#12015
0xApotheosis wants to merge 1 commit intodevelopfrom
fix/relay-tron-notification-and-calldata

Conversation

@0xApotheosis
Copy link
Member

@0xApotheosis 0xApotheosis commented Feb 23, 2026

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 notifyTransactionIndexing call in checkTradeStatus was gated behind isEvmChainId(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) to chainIdToRelayChainId[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.ts was not storing the quote's parameter.data field in relayTransactionMetadata
  • getTrade.ts was setting allowanceContract to '' instead of the vault contract address
  • getUnsignedTronTransaction.ts always used buildSendApiTransaction (simple TRC20 transfer) instead of buildCustomApiTx with the vault calldata

Fix:

  • Store the quote calldata in relayTransactionMetadata.data
  • Set allowanceContract to the vault contract address for proper token approval
  • When relayTransactionMetadata.data exists, use buildCustomApiTx to call the vault's depositErc20 method directly

Background

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/v2 API 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 (isEvmChainIdchainIdToRelayChainId check) affects the status polling path for all chains but is functionally equivalent for EVM chains and strictly additive for non-EVM chains.

Relay cross-chain swaps on all supported chains are potentially affected by the notification change. Tron transaction building is affected but currently disabled.

Testing

Engineering

  1. Notification fix: Verify that checkTradeStatus calls notifyTransactionIndexing for Solana and Bitcoin Relay swaps (previously skipped)
  2. Tron calldata fix: Re-enable Tron in chainIdToRelayChainId, get a Relay quote for a Tron sell, and verify:
    • relayTransactionMetadata.data is populated with vault calldata
    • allowanceContract is set to the vault address
    • The built transaction calls depositErc20() on the vault (not a simple TRC20 transfer)
  3. Verify existing EVM Relay swaps still work (notification path is functionally unchanged)

Operations

  • 🏁 Tron is disabled behind the chain mapping removal and doesn't require operations testing yet
  • Verify EVM-to-EVM Relay swaps still complete successfully (regression check on notification change)

Summary by CodeRabbit

  • Bug Fixes

    • Updated relay swapper logic to better handle transaction metadata and chain processing.
    • Improved transaction parameter handling for relay-based swaps.
  • Refactor

    • Refined chain eligibility check to use relay chain mapping instead of explicit chain type filtering.
    • Removed Tron deposit tracking from relay configuration.

…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>
@0xApotheosis 0xApotheosis requested a review from a team as a code owner February 23, 2026 23:00
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
RelaySwapper Configuration
packages/swapper/src/swappers/RelaySwapper/constant.ts, packages/swapper/src/swappers/RelaySwapper/endpoints.ts
Removed tronChainId import and its 728126428 mapping entry from chainIdToRelayChainId. Updated relay filtering logic from isEvmChainId check to chainIdToRelayChainId mapping existence, decoupling relay support from EVM chain assumption.
RelaySwapper Trade Logic
packages/swapper/src/swappers/RelaySwapper/utils/getTrade.ts
Modified Tron relay metadata handling to include data field from selectedItem.data.parameter.data and resolve allowanceContract from contract_address with fallback instead of hardcoding empty string.
Tron Transaction Building
packages/swapper/src/tron-utils/getUnsignedTronTransaction.ts
Added early-return branch for relayTransactionMetadata containing data field; builds custom API transaction using relay metadata (to, data, value) via adapter.buildCustomApiTx, bypassing default deposit path when relay data is present.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • NeOMakinG
  • gomesalexandre

Poem

🐰 A Tron once bridged through Relay's care,
But now we've cleared that market square,
Custom transactions take the lead,
With mapped chains that match our need! 🔄

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: fixing Relay non-EVM transaction notification and Tron calldata handling, which aligns with the PR objectives addressing two critical bugs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/relay-tron-notification-and-calldata

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 3cf88a5 and 7b44b2d.

📒 Files selected for processing (4)
  • packages/swapper/src/swappers/RelaySwapper/constant.ts
  • packages/swapper/src/swappers/RelaySwapper/endpoints.ts
  • packages/swapper/src/swappers/RelaySwapper/utils/getTrade.ts
  • packages/swapper/src/tron-utils/getUnsignedTronTransaction.ts

Comment on lines +89 to +93
// 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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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,
As per coding guidelines: Never add code comments unless explicitly requested.
📝 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.

Suggested change
// 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.

Comment on lines 654 to 663
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,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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,
           },
         }
       }
As per coding guidelines: ALWAYS validate inputs before processing with clear validation error messages and use early returns for validation failures.
🤖 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.

Comment on lines +49 to +63
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,
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

@0xApotheosis 0xApotheosis marked this pull request as draft February 24, 2026 00:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant