fix(rpc): look up dst decimals on destination chain by destination address - #1520
Draft
ultrasecreth wants to merge 1 commit into
Draft
fix(rpc): look up dst decimals on destination chain by destination address#1520ultrasecreth wants to merge 1 commit into
ultrasecreth wants to merge 1 commit into
Conversation
…dress
In `Relay::source_funds`, when iterating funding sources, the destination
decimals were being looked up via:
chains.asset(destination_chain_id, mapped.address)
`mapped` is the source-chain descriptor (returned by
`map_interop_asset(dst, src, asset)`), so `mapped.address` is the
asset's address on the *source* chain. Querying that address against the
*destination* chain returns `None` whenever the asset has a different
address on the two chains (which is the common case for canonical
ERC-20s such as USDC, USDT, etc.).
When the lookup fails, `retain` returns `true` and the asset stays in
`remaining`; no funds are taken; `source_funds` returns `None`; and
`build_quotes` silently falls back to a single-chain quote with a
deficit. The user ends up with a quote they cannot submit
("quote has asset deficits and is expected to fail" on send).
Use the destination address (the original `asset` key being retained)
when looking up the destination decimals.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Relay::source_fundssilently returnsNonewhenever arequiredFunds-driven multichain bundle would need to source an ERC-20 from a chain where that asset has a different address than on the destination chain (i.e. most canonical ERC-20s — USDC, USDT, …). The user gets a single-chain quote with a deficit instead of the expected multichain bundle, andwallet_sendPreparedCallsthen fails withquote has asset deficits and is expected to fail.The cause is a wrong address in one lookup; one-line fix.
The bug
In
src/rpc/relay.rs, insidesource_funds's funding-chain loop:mappedis the source-chain descriptor (it came frommap_interop_asset(destination_chain_id, chain, asset.address()), whose final step looks the asset up on the sourcechain). Somapped.addressis the source-chain address. Querying that address againstdestination_chain_idreturnsNonewhenever the two chains use different addresses for the same asset.When the lookup fails,
retainreturnstrue, the asset stays inremaining,taken_assetsis empty, noFundSourceis pushed, andsource_fundsreturnsNone.build_quotesthen takes the silent fallback at src/rpc/relay.rs#L2058-L2062 and produces a single-chain quote with a deficit.Reproduction against
rpc.ithaca.xyzaccountImplementationv0.5.10 on Base.wallet_prepareCallson chain0xa(OP) with a 0.1 USDC transfer call andcapabilities.requiredFunds: [{ address: <USDC_OP>, value: 1000000 }].Expected:
multiChainRootset,quotes.length === 2(Base source leg + OP destination leg), no deficits.Observed (before fix):
multiChainRoot: null,quotes.length === 1,quotes[0].assetDeficits = [{ address: <USDC_OP>, deficit: 100001, required: 100001 }].With diagnostic logging inserted into
source_funds, the trace was unambiguous:After the fix, the same call returns a two-leg multichain quote (Base source + OP destination), non-null
multiChainRoot, no deficits.The fix
Use the destination address (the original
assetkey being retained on) when looking up the destination decimals:assetis the key fromremaining, which originates inrequested_assets— i.e. the asset address on the destination chain. That's the address whose decimals we want.Why existing tests don't catch this
The cross-chain e2e tests in
tests/e2e/cases/multichain_usdt_transfer.rsdeploy the test ERC-20 separately on each anvil chain via the same deployer nonce, so the contract lands at the same address on every chain. In that casemapped.address == asset.address()and the wrong lookup happens to resolve. The bug only manifests when source and destination addresses differ — which is the production reality for canonical USDC/USDT/etc.A targeted regression test would require extending the test harness to deploy ERC-20s at distinct per-chain addresses while registering them under a shared
AssetUid. Happy to add that here, or in a follow-up, depending on what the maintainers prefer — flagging now so I don't sit on the fix.Test plan
cargo build --bin relay(clean)multiChainRoot.🤖 Generated with Claude Code