✨ app: add support for multi-chain asset recovery#970
✨ app: add support for multi-chain asset recovery#970dieguezguille wants to merge 17 commits intotransportfrom
Conversation
🦋 Changeset detectedLatest commit: 2db4dd6 The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds cross-chain asset discovery and execution, portfolio restructuring, new UI sheets for external/collateral/unsupported networks, cross-chain account client routing, multiple query helpers for deployed/wallet balances, i18n updates, and assorted UI/layout tweaks. Includes many small changesets for patch releases. Changes
Sequence DiagramsequenceDiagram
participant User
participant Bridge as Bridge Component
participant Portfolio as Portfolio Screen
participant QueryClient as Query Client
participant LiFi as LiFi API
participant Deployed as Deployed Check
User->>Portfolio: Open portfolio
Portfolio->>QueryClient: fetch markets, same-chain & wallet balances
QueryClient->>LiFi: request tokenBalances & walletBalances
LiFi-->>QueryClient: return balances per chain
QueryClient->>Deployed: check contract bytecode per chain
Deployed-->>Portfolio: deployed status
Portfolio->>User: render collateral + external assets grouped by chain
User->>Bridge: select external asset -> navigate with sender, sourceChain, sourceToken
Bridge->>Bridge: classify mode (isTransfer/isSwap/isRecovery)
alt isTransfer (same-chain)
Bridge->>Wallet: send native transaction
Wallet->>Network: broadcast tx
else cross-chain / recovery
Bridge->>AccountClient: request cross-chain client for targetChain
AccountClient->>CrossChainClient: build/sign UserOperation
CrossChainClient->>Network: submit UserOperation
Network-->>CrossChainClient: execution/result
CrossChainClient-->>Bridge: return composite id/result
end
Bridge->>User: show success/failure
sequenceDiagram
participant User
participant ExternalAssets as ExternalAssets Component
participant QueryClient as Query Client
participant Deployed as Deployed Check
participant Unsupported as UnsupportedNetworksSheet
User->>ExternalAssets: Tap non-collateral asset
ExternalAssets->>QueryClient: ensure deployedOptions(account, chainId) queries
QueryClient->>Deployed: return deployed/not-deployed
alt not supported or not deployed
ExternalAssets->>Unsupported: open UnsupportedNetworksSheet with formatted recovery message
Unsupported->>User: show contact support flow (Intercom)
else supported & deployed
ExternalAssets->>Bridge: navigate to Bridge with asset params
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 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 |
|
✅ All tests passed. |
4868ef9 to
eb6d3a6
Compare
4bcfa86 to
c19bd5d
Compare
c19bd5d to
e79dd5b
Compare
e79dd5b to
aae3144
Compare
aae3144 to
5c6c745
Compare
5c6c745 to
b50b099
Compare
| defaultValues: { receiver: typeof receiver === "string" ? receiver : "" }, | ||
| onSubmit: ({ value }) => { | ||
| router.push({ pathname: "/send-funds/asset", params: { receiver: String(value.receiver) } }); | ||
| router.push({ pathname: "/send-funds/asset", params: { receiver: value.receiver } }); | ||
| }, |
There was a problem hiding this comment.
🚩 Receiver form defaultValues change affects pre-filled address UX
In src/components/send-funds/Receiver.tsx:41-44, the old code used safeParse + form.setFieldValue + form.validateAllFields in the render body to pre-fill and validate addresses from URL params (e.g., QR scan). The new code uses defaultValues: { receiver: typeof receiver === "string" ? receiver : "" }. This fixes the anti-pattern of setting state during render, but the submit button checks !isValid || !isTouched (line 353), so a pre-filled address from URL params won't enable the button until the user interacts with the input. This is a deliberate stabilization per the changeset, though it may require a user tap on the input before proceeding.
Was this helpful? React with 👍 or 👎 to provide feedback.
b50b099 to
de51847
Compare
de51847 to
fd9ee46
Compare
| async function getWalletBalances(account: Address) { | ||
| const balances: Record<number, TokenAmount[]> = {}; | ||
| const lifiConfig = config.get(); | ||
| let offset: string | undefined; | ||
| do { | ||
| const url = new URL(`${lifiConfig.apiUrl}/wallets/${account}/balances`); | ||
| url.searchParams.set("extended", "true"); | ||
| url.searchParams.set("limit", "1000"); | ||
| if (offset) url.searchParams.set("offset", offset); | ||
| const response = await fetch(url, { | ||
| headers: { | ||
| ...(lifiConfig.apiKey && { "x-lifi-api-key": lifiConfig.apiKey }), | ||
| ...(lifiConfig.integrator && { "x-lifi-integrator": lifiConfig.integrator }), | ||
| }, | ||
| }); | ||
| if (!response.ok) throw new Error("wallet balances request failed"); | ||
| const json = parse( | ||
| object({ | ||
| balances: optional( | ||
| record( | ||
| string(), | ||
| array( | ||
| looseObject({ | ||
| chainId: number(), | ||
| address: string(), | ||
| symbol: string(), | ||
| decimals: number(), | ||
| name: string(), | ||
| priceUSD: optional(string(), "0"), | ||
| logoURI: optional(string()), | ||
| amount: pipe(string(), regex(/^\d+$/)), | ||
| }), | ||
| ), | ||
| ), | ||
| ), | ||
| offset: optional(string()), | ||
| }), | ||
| await response.json(), | ||
| ); | ||
| for (const [chainId, tokens] of Object.entries(json.balances ?? {})) { | ||
| const id = Number(chainId); | ||
| if (!Number.isInteger(id)) continue; | ||
| balances[id] = [ | ||
| ...(balances[id] ?? []), | ||
| ...tokens.map(({ amount, ...token }) => ({ ...token, amount: BigInt(amount) })), | ||
| ]; | ||
| } | ||
| offset = json.offset; | ||
| } while (offset); | ||
| return balances; |
There was a problem hiding this comment.
🚩 Wallet balances API may return tokens LI.FI cannot route
The new getWalletBalances function (src/utils/lifi.ts:454-503) fetches ALL token balances for a wallet via GET /wallets/{address}/balances, replacing the old approach of passing a curated token list to getTokenBalancesByChain. This means the bridge asset selector in Bridge.tsx may now show tokens that LI.FI has no swap/bridge route for. A user selecting such a token would get a 'unable to fetch bridge quote' error after entering an amount. The old code only showed tokens LI.FI explicitly supported. This behavioral change is likely intentional (broader asset visibility for recovery flows) but worth confirming.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2db4dd6a65
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| error: deploymentError, | ||
| isLoading: isCheckingDeployment, | ||
| } = useQuery({ ...deployedOptions(senderAddress, source?.chain), enabled: isRecovery }); | ||
| const notDeployed = isRecovery && deployed === false; |
There was a problem hiding this comment.
Remove undeployed-account block from recovery submit path
This marks recovery as unavailable whenever deployed === false, which blocks users whose exa account has balances on a supported chain but has never been deployed there yet. In this same commit, createAccountClient builds cross-chain accounts with getAccountInitCode and sends user operations on target chains, so first-use deployment is expected to be possible; treating notDeployed as a hard blocker prevents that first recovery transaction from ever being submitted.
Useful? React with 👍 / 👎.
closes #854 , closes #649
Summary by CodeRabbit
New Features
Bug Fixes
Refactors
Localization