From 9f428543fda5d73b36763733840e5a90aa28b504 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Mar 2026 13:51:17 +0000 Subject: [PATCH 1/3] Fix PIN validation and cache security in ManagePinForm - Fix isSimpleSequence to also reject descending sequences (e.g. 4321) - Set gcTime: 0 and staleTime: 0 on PIN query to prevent decrypted PIN from persisting in React Query cache after component unmount https://claude.ai/code/session_01W66wRpe8pxFbdrgLi7PVFi --- components/Card/ManagePinForm.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/Card/ManagePinForm.tsx b/components/Card/ManagePinForm.tsx index 0e0d782d7..89f6d3e9a 100644 --- a/components/Card/ManagePinForm.tsx +++ b/components/Card/ManagePinForm.tsx @@ -19,10 +19,13 @@ import { import { withRefreshToken } from '@/lib/utils/utils'; function isSimpleSequence(pin: string): boolean { + let ascending = true; + let descending = true; for (let i = 1; i < pin.length; i++) { - if (parseInt(pin[i]) !== parseInt(pin[i - 1]) + 1) return false; + if (parseInt(pin[i]) !== parseInt(pin[i - 1]) + 1) ascending = false; + if (parseInt(pin[i]) !== parseInt(pin[i - 1]) - 1) descending = false; } - return true; + return ascending || descending; } function isRepeatedDigits(pin: string): boolean { @@ -75,6 +78,8 @@ export default function ManagePinForm() { ); }, retry: false, + gcTime: 0, + staleTime: 0, }); const hasExistingPin = !!existingPin; From e24c256e45edab24eb83bc8c9160c56e63974774 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Mar 2026 14:49:52 +0000 Subject: [PATCH 2/3] fix: standardize card amount handling to string type and use parseUnits - Change store amount types from number to string to avoid float precision loss - Replace Math.round(parseFloat * 10^6) with viem parseUnits for collateral withdrawal - Use formatUnits consistently for balance conversions instead of manual division - Add MAX_DECIMAL_PLACES_REGEX validation (max 6 decimals) to all deposit/withdraw forms - Update TransactionStatus component to accept string | number for amounts - Remove all Number(data.amount) conversions when setting transaction state https://claude.ai/code/session_01W66wRpe8pxFbdrgLi7PVFi --- components/Card/CardDepositExternalForm.tsx | 7 ++++++- components/Card/CardDepositInternalForm.tsx | 17 ++++++++++++----- components/Card/CardDepositModalProvider.tsx | 2 +- components/Card/CardRepayForm.tsx | 2 +- components/Card/CardRepayModalProvider.tsx | 2 +- components/Card/CardWithdrawForm.tsx | 19 ++++++++++++++----- components/Card/CardWithdrawModalProvider.tsx | 2 +- components/TransactionStatus/index.tsx | 2 +- lib/utils/utils.ts | 3 +++ store/useCardDepositStore.ts | 2 +- store/useCardRepayStore.ts | 2 +- store/useCardWithdrawStore.ts | 2 +- 12 files changed, 43 insertions(+), 19 deletions(-) diff --git a/components/Card/CardDepositExternalForm.tsx b/components/Card/CardDepositExternalForm.tsx index 939420302..1fe23ebbc 100644 --- a/components/Card/CardDepositExternalForm.tsx +++ b/components/Card/CardDepositExternalForm.tsx @@ -29,11 +29,13 @@ import { EXPO_PUBLIC_CARD_FUNDING_CHAIN_ID } from '@/lib/config'; import { getChain } from '@/lib/thirdweb'; import { CardProvider, Status, TransactionStatus, TransactionType } from '@/lib/types'; import { + CARD_DEPOSIT_TOKEN_DECIMALS, cn, formatNumber, getCardDepositTokenAddress, getCardDepositTokenSymbol, getCardFundingAddress, + MAX_DECIMAL_PLACES_REGEX, } from '@/lib/utils'; import { getChain as getChainWagmi } from '@/lib/wagmi'; import { useCardDepositStore } from '@/store/useCardDepositStore'; @@ -113,6 +115,9 @@ export default function CardDepositExternalForm() { amount: z .string() .refine(val => val !== '' && !isNaN(Number(val)), { error: 'Enter a valid amount' }) + .refine(val => MAX_DECIMAL_PLACES_REGEX.test(val), { + error: `Maximum ${CARD_DEPOSIT_TOKEN_DECIMALS} decimal places`, + }) .refine(val => Number(val) > 0, { error: 'Amount must be greater than 0' }) .refine(val => Number(val) <= balanceAmount, { error: `Available balance is ${formatNumber(balanceAmount)} ${depositTokenSymbol}`, @@ -255,7 +260,7 @@ export default function CardDepositExternalForm() { }); setSendStatus(Status.SUCCESS); - setTransaction({ amount: Number(data.amount) }); + setTransaction({ amount: data.amount }); setModal(CARD_DEPOSIT_MODAL.OPEN_TRANSACTION_STATUS); reset(); diff --git a/components/Card/CardDepositInternalForm.tsx b/components/Card/CardDepositInternalForm.tsx index d8bf36555..79f41a0d5 100644 --- a/components/Card/CardDepositInternalForm.tsx +++ b/components/Card/CardDepositInternalForm.tsx @@ -48,11 +48,13 @@ import { getAsset } from '@/lib/assets'; import { ADDRESSES, EXPO_PUBLIC_CARD_FUNDING_CHAIN_ID, isProduction } from '@/lib/config'; import { CardProvider, Status, TransactionStatus, TransactionType } from '@/lib/types'; import { + CARD_DEPOSIT_TOKEN_DECIMALS, cn, formatNumber, getCardDepositTokenAddress, getCardDepositTokenSymbol, getCardFundingAddress, + MAX_DECIMAL_PLACES_REGEX, } from '@/lib/utils'; import { getChain } from '@/lib/wagmi'; import { CardDepositSource, useCardDepositStore } from '@/store/useCardDepositStore'; @@ -599,12 +601,14 @@ export default function CardDepositInternalForm() { // Get borrow APY from Aave const { borrowAPY, isLoading: isBorrowAPYLoading } = useAaveBorrowPosition(); - const usdcBalanceAmount = fuseUsdcBalance ? Number(fuseUsdcBalance) / 1e6 : 0; + const usdcBalanceAmount = fuseUsdcBalance + ? Number(formatUnits(fuseUsdcBalance, 6)) + : 0; const soUsdBalanceAmount = soUsdToken - ? Number(soUsdToken.balance) / Math.pow(10, soUsdToken.contractDecimals) + ? Number(formatUnits(BigInt(soUsdToken.balance), soUsdToken.contractDecimals)) : 0; const testnetWalletBalanceAmount = - testnetDepositBalance != null ? Number(testnetDepositBalance) / 1e6 : 0; + testnetDepositBalance != null ? Number(formatUnits(testnetDepositBalance, 6)) : 0; const balanceAmount = watchedFrom === CardDepositSource.WALLET @@ -668,6 +672,9 @@ export default function CardDepositInternalForm() { amount: z .string() .refine(val => val !== '' && !isNaN(Number(val)), { error: 'Enter a valid amount' }) + .refine(val => MAX_DECIMAL_PLACES_REGEX.test(val), { + error: `Maximum ${CARD_DEPOSIT_TOKEN_DECIMALS} decimal places`, + }) .refine(val => Number(val) > 0, { error: 'Amount must be greater than 0' }) .refine( val => { @@ -848,7 +855,7 @@ export default function CardDepositInternalForm() { }, }); - setTransaction({ amount: Number(data.amount) }); + setTransaction({ amount: data.amount }); setModal(CARD_DEPOSIT_MODAL.OPEN_TRANSACTION_STATUS); reset(); } catch (error) { @@ -970,7 +977,7 @@ export default function CardDepositInternalForm() { }, }); - setTransaction({ amount: Number(data.amount) }); + setTransaction({ amount: data.amount }); setModal(CARD_DEPOSIT_MODAL.OPEN_TRANSACTION_STATUS); reset(); } catch (error) { diff --git a/components/Card/CardDepositModalProvider.tsx b/components/Card/CardDepositModalProvider.tsx index 4407e3ae0..1a1a4196c 100644 --- a/components/Card/CardDepositModalProvider.tsx +++ b/components/Card/CardDepositModalProvider.tsx @@ -105,7 +105,7 @@ const CardDepositModalProvider = () => { if (isTransactionStatus) { return ( { if (isTransactionStatus) { return ( val !== '' && !isNaN(Number(val)), { message: 'Enter a valid amount' }) + .refine(val => MAX_DECIMAL_PLACES_REGEX.test(val), { + message: `Maximum ${CARD_DEPOSIT_TOKEN_DECIMALS} decimal places`, + }) .refine(val => Number(val) >= 1, { message: 'Minimum withdrawal is $1' }) .refine(val => Number(val) <= spendableAmount, { message: `Amount exceeds spendable balance (${formattedBalance} available)`, @@ -91,6 +96,9 @@ export default function CardWithdrawForm() { amount: z .string() .refine(val => val !== '' && !isNaN(Number(val)), { message: 'Enter a valid amount' }) + .refine(val => MAX_DECIMAL_PLACES_REGEX.test(val), { + message: `Maximum ${CARD_DEPOSIT_TOKEN_DECIMALS} decimal places`, + }) .refine(val => Number(val) >= 1, { message: 'Minimum withdrawal is $1' }) .refine(val => Number(val) <= collateralAvailable, { message: `Amount exceeds available (${collateralFormatted} available)`, @@ -151,7 +159,7 @@ export default function CardWithdrawForm() { if (toSavings) { const res = await withdrawFromCardToSavings({ amount: data.amount }); setTransaction({ - amount: Number(data.amount), + amount: data.amount, clientTxId: `card-${res.withdrawalId}`, to: data.to, }); @@ -163,8 +171,9 @@ export default function CardWithdrawForm() { text2: `$${data.amount} is being sent to your Savings.`, }); } else if (toCollateral) { - const amountInSmallestUnits = Math.round( - parseFloat(data.amount) * 10 ** CARD_DEPOSIT_TOKEN_DECIMALS, + const amountInSmallestUnits = parseUnits( + data.amount, + CARD_DEPOSIT_TOKEN_DECIMALS, ).toString(); const res = await withdrawCardCollateral({ amount: amountInSmallestUnits, @@ -175,7 +184,7 @@ export default function CardWithdrawForm() { }), }); setTransaction({ - amount: Number(data.amount), + amount: data.amount, clientTxId: res.transactionHash, to: data.to, transactionHash: res.transactionHash, @@ -200,7 +209,7 @@ export default function CardWithdrawForm() { }); setTransaction({ - amount: Number(data.amount), + amount: data.amount, clientTxId: `card-${response.id}`, to: data.to, }); diff --git a/components/Card/CardWithdrawModalProvider.tsx b/components/Card/CardWithdrawModalProvider.tsx index cf1454300..60ae3cd14 100644 --- a/components/Card/CardWithdrawModalProvider.tsx +++ b/components/Card/CardWithdrawModalProvider.tsx @@ -65,7 +65,7 @@ const CardWithdrawModalProvider = () => { : 'USDC'; return ( void; title?: string; diff --git a/lib/utils/utils.ts b/lib/utils/utils.ts index b7602c3cf..2cfd5169f 100644 --- a/lib/utils/utils.ts +++ b/lib/utils/utils.ts @@ -358,6 +358,9 @@ export function getCardDepositTokenSymbol( /** Decimals for card deposit/withdraw token (e.g. rUSD, USDC). */ export const CARD_DEPOSIT_TOKEN_DECIMALS = 6; +/** Regex that matches a numeric string with at most CARD_DEPOSIT_TOKEN_DECIMALS decimal places. */ +export const MAX_DECIMAL_PLACES_REGEX = /^\d+(\.\d{1,6})?$/; + /** Resolve card funding address: Rain from contracts API (EXPO_PUBLIC_CARD_FUNDING_CHAIN_ID), Bridge from card details. */ export function getCardFundingAddress( cardDetails: CardResponse | null | undefined, diff --git a/store/useCardDepositStore.ts b/store/useCardDepositStore.ts index 9f43c452e..701934276 100644 --- a/store/useCardDepositStore.ts +++ b/store/useCardDepositStore.ts @@ -14,7 +14,7 @@ export enum CardDepositSource { } export interface CardDepositTransactionState { - amount?: number; + amount?: string; } interface CardDepositState { diff --git a/store/useCardRepayStore.ts b/store/useCardRepayStore.ts index 4509184b1..3d9ebb70a 100644 --- a/store/useCardRepayStore.ts +++ b/store/useCardRepayStore.ts @@ -7,7 +7,7 @@ import mmkvStorage from '@/lib/mmvkStorage'; import { TokenBalance } from '@/lib/types'; export interface CardRepayTransactionState { - amount?: number; + amount?: string; } interface CardRepayState { diff --git a/store/useCardWithdrawStore.ts b/store/useCardWithdrawStore.ts index 5be1dac8c..8ac4a9f79 100644 --- a/store/useCardWithdrawStore.ts +++ b/store/useCardWithdrawStore.ts @@ -4,7 +4,7 @@ import { CARD_WITHDRAW_MODAL } from '@/constants/modals'; import { CardDepositSource } from '@/store/useCardDepositStore'; export interface CardWithdrawTransactionState { - amount?: number; + amount?: string; clientTxId?: string; to?: CardDepositSource; /** Rain collateral withdrawal: tx hash and chain for explorer link */ From c3b59adcce5566a3e0d3095cf19019423f3432c5 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Mar 2026 15:12:42 +0000 Subject: [PATCH 3/3] Revert "fix: standardize card amount handling to string type and use parseUnits" This reverts commit e24c256e45edab24eb83bc8c9160c56e63974774. --- components/Card/CardDepositExternalForm.tsx | 7 +------ components/Card/CardDepositInternalForm.tsx | 17 +++++------------ components/Card/CardDepositModalProvider.tsx | 2 +- components/Card/CardRepayForm.tsx | 2 +- components/Card/CardRepayModalProvider.tsx | 2 +- components/Card/CardWithdrawForm.tsx | 19 +++++-------------- components/Card/CardWithdrawModalProvider.tsx | 2 +- components/TransactionStatus/index.tsx | 2 +- lib/utils/utils.ts | 3 --- store/useCardDepositStore.ts | 2 +- store/useCardRepayStore.ts | 2 +- store/useCardWithdrawStore.ts | 2 +- 12 files changed, 19 insertions(+), 43 deletions(-) diff --git a/components/Card/CardDepositExternalForm.tsx b/components/Card/CardDepositExternalForm.tsx index 1fe23ebbc..939420302 100644 --- a/components/Card/CardDepositExternalForm.tsx +++ b/components/Card/CardDepositExternalForm.tsx @@ -29,13 +29,11 @@ import { EXPO_PUBLIC_CARD_FUNDING_CHAIN_ID } from '@/lib/config'; import { getChain } from '@/lib/thirdweb'; import { CardProvider, Status, TransactionStatus, TransactionType } from '@/lib/types'; import { - CARD_DEPOSIT_TOKEN_DECIMALS, cn, formatNumber, getCardDepositTokenAddress, getCardDepositTokenSymbol, getCardFundingAddress, - MAX_DECIMAL_PLACES_REGEX, } from '@/lib/utils'; import { getChain as getChainWagmi } from '@/lib/wagmi'; import { useCardDepositStore } from '@/store/useCardDepositStore'; @@ -115,9 +113,6 @@ export default function CardDepositExternalForm() { amount: z .string() .refine(val => val !== '' && !isNaN(Number(val)), { error: 'Enter a valid amount' }) - .refine(val => MAX_DECIMAL_PLACES_REGEX.test(val), { - error: `Maximum ${CARD_DEPOSIT_TOKEN_DECIMALS} decimal places`, - }) .refine(val => Number(val) > 0, { error: 'Amount must be greater than 0' }) .refine(val => Number(val) <= balanceAmount, { error: `Available balance is ${formatNumber(balanceAmount)} ${depositTokenSymbol}`, @@ -260,7 +255,7 @@ export default function CardDepositExternalForm() { }); setSendStatus(Status.SUCCESS); - setTransaction({ amount: data.amount }); + setTransaction({ amount: Number(data.amount) }); setModal(CARD_DEPOSIT_MODAL.OPEN_TRANSACTION_STATUS); reset(); diff --git a/components/Card/CardDepositInternalForm.tsx b/components/Card/CardDepositInternalForm.tsx index 79f41a0d5..d8bf36555 100644 --- a/components/Card/CardDepositInternalForm.tsx +++ b/components/Card/CardDepositInternalForm.tsx @@ -48,13 +48,11 @@ import { getAsset } from '@/lib/assets'; import { ADDRESSES, EXPO_PUBLIC_CARD_FUNDING_CHAIN_ID, isProduction } from '@/lib/config'; import { CardProvider, Status, TransactionStatus, TransactionType } from '@/lib/types'; import { - CARD_DEPOSIT_TOKEN_DECIMALS, cn, formatNumber, getCardDepositTokenAddress, getCardDepositTokenSymbol, getCardFundingAddress, - MAX_DECIMAL_PLACES_REGEX, } from '@/lib/utils'; import { getChain } from '@/lib/wagmi'; import { CardDepositSource, useCardDepositStore } from '@/store/useCardDepositStore'; @@ -601,14 +599,12 @@ export default function CardDepositInternalForm() { // Get borrow APY from Aave const { borrowAPY, isLoading: isBorrowAPYLoading } = useAaveBorrowPosition(); - const usdcBalanceAmount = fuseUsdcBalance - ? Number(formatUnits(fuseUsdcBalance, 6)) - : 0; + const usdcBalanceAmount = fuseUsdcBalance ? Number(fuseUsdcBalance) / 1e6 : 0; const soUsdBalanceAmount = soUsdToken - ? Number(formatUnits(BigInt(soUsdToken.balance), soUsdToken.contractDecimals)) + ? Number(soUsdToken.balance) / Math.pow(10, soUsdToken.contractDecimals) : 0; const testnetWalletBalanceAmount = - testnetDepositBalance != null ? Number(formatUnits(testnetDepositBalance, 6)) : 0; + testnetDepositBalance != null ? Number(testnetDepositBalance) / 1e6 : 0; const balanceAmount = watchedFrom === CardDepositSource.WALLET @@ -672,9 +668,6 @@ export default function CardDepositInternalForm() { amount: z .string() .refine(val => val !== '' && !isNaN(Number(val)), { error: 'Enter a valid amount' }) - .refine(val => MAX_DECIMAL_PLACES_REGEX.test(val), { - error: `Maximum ${CARD_DEPOSIT_TOKEN_DECIMALS} decimal places`, - }) .refine(val => Number(val) > 0, { error: 'Amount must be greater than 0' }) .refine( val => { @@ -855,7 +848,7 @@ export default function CardDepositInternalForm() { }, }); - setTransaction({ amount: data.amount }); + setTransaction({ amount: Number(data.amount) }); setModal(CARD_DEPOSIT_MODAL.OPEN_TRANSACTION_STATUS); reset(); } catch (error) { @@ -977,7 +970,7 @@ export default function CardDepositInternalForm() { }, }); - setTransaction({ amount: data.amount }); + setTransaction({ amount: Number(data.amount) }); setModal(CARD_DEPOSIT_MODAL.OPEN_TRANSACTION_STATUS); reset(); } catch (error) { diff --git a/components/Card/CardDepositModalProvider.tsx b/components/Card/CardDepositModalProvider.tsx index 1a1a4196c..4407e3ae0 100644 --- a/components/Card/CardDepositModalProvider.tsx +++ b/components/Card/CardDepositModalProvider.tsx @@ -105,7 +105,7 @@ const CardDepositModalProvider = () => { if (isTransactionStatus) { return ( { if (isTransactionStatus) { return ( val !== '' && !isNaN(Number(val)), { message: 'Enter a valid amount' }) - .refine(val => MAX_DECIMAL_PLACES_REGEX.test(val), { - message: `Maximum ${CARD_DEPOSIT_TOKEN_DECIMALS} decimal places`, - }) .refine(val => Number(val) >= 1, { message: 'Minimum withdrawal is $1' }) .refine(val => Number(val) <= spendableAmount, { message: `Amount exceeds spendable balance (${formattedBalance} available)`, @@ -96,9 +91,6 @@ export default function CardWithdrawForm() { amount: z .string() .refine(val => val !== '' && !isNaN(Number(val)), { message: 'Enter a valid amount' }) - .refine(val => MAX_DECIMAL_PLACES_REGEX.test(val), { - message: `Maximum ${CARD_DEPOSIT_TOKEN_DECIMALS} decimal places`, - }) .refine(val => Number(val) >= 1, { message: 'Minimum withdrawal is $1' }) .refine(val => Number(val) <= collateralAvailable, { message: `Amount exceeds available (${collateralFormatted} available)`, @@ -159,7 +151,7 @@ export default function CardWithdrawForm() { if (toSavings) { const res = await withdrawFromCardToSavings({ amount: data.amount }); setTransaction({ - amount: data.amount, + amount: Number(data.amount), clientTxId: `card-${res.withdrawalId}`, to: data.to, }); @@ -171,9 +163,8 @@ export default function CardWithdrawForm() { text2: `$${data.amount} is being sent to your Savings.`, }); } else if (toCollateral) { - const amountInSmallestUnits = parseUnits( - data.amount, - CARD_DEPOSIT_TOKEN_DECIMALS, + const amountInSmallestUnits = Math.round( + parseFloat(data.amount) * 10 ** CARD_DEPOSIT_TOKEN_DECIMALS, ).toString(); const res = await withdrawCardCollateral({ amount: amountInSmallestUnits, @@ -184,7 +175,7 @@ export default function CardWithdrawForm() { }), }); setTransaction({ - amount: data.amount, + amount: Number(data.amount), clientTxId: res.transactionHash, to: data.to, transactionHash: res.transactionHash, @@ -209,7 +200,7 @@ export default function CardWithdrawForm() { }); setTransaction({ - amount: data.amount, + amount: Number(data.amount), clientTxId: `card-${response.id}`, to: data.to, }); diff --git a/components/Card/CardWithdrawModalProvider.tsx b/components/Card/CardWithdrawModalProvider.tsx index 60ae3cd14..cf1454300 100644 --- a/components/Card/CardWithdrawModalProvider.tsx +++ b/components/Card/CardWithdrawModalProvider.tsx @@ -65,7 +65,7 @@ const CardWithdrawModalProvider = () => { : 'USDC'; return ( void; title?: string; diff --git a/lib/utils/utils.ts b/lib/utils/utils.ts index 2cfd5169f..b7602c3cf 100644 --- a/lib/utils/utils.ts +++ b/lib/utils/utils.ts @@ -358,9 +358,6 @@ export function getCardDepositTokenSymbol( /** Decimals for card deposit/withdraw token (e.g. rUSD, USDC). */ export const CARD_DEPOSIT_TOKEN_DECIMALS = 6; -/** Regex that matches a numeric string with at most CARD_DEPOSIT_TOKEN_DECIMALS decimal places. */ -export const MAX_DECIMAL_PLACES_REGEX = /^\d+(\.\d{1,6})?$/; - /** Resolve card funding address: Rain from contracts API (EXPO_PUBLIC_CARD_FUNDING_CHAIN_ID), Bridge from card details. */ export function getCardFundingAddress( cardDetails: CardResponse | null | undefined, diff --git a/store/useCardDepositStore.ts b/store/useCardDepositStore.ts index 701934276..9f43c452e 100644 --- a/store/useCardDepositStore.ts +++ b/store/useCardDepositStore.ts @@ -14,7 +14,7 @@ export enum CardDepositSource { } export interface CardDepositTransactionState { - amount?: string; + amount?: number; } interface CardDepositState { diff --git a/store/useCardRepayStore.ts b/store/useCardRepayStore.ts index 3d9ebb70a..4509184b1 100644 --- a/store/useCardRepayStore.ts +++ b/store/useCardRepayStore.ts @@ -7,7 +7,7 @@ import mmkvStorage from '@/lib/mmvkStorage'; import { TokenBalance } from '@/lib/types'; export interface CardRepayTransactionState { - amount?: string; + amount?: number; } interface CardRepayState { diff --git a/store/useCardWithdrawStore.ts b/store/useCardWithdrawStore.ts index 8ac4a9f79..5be1dac8c 100644 --- a/store/useCardWithdrawStore.ts +++ b/store/useCardWithdrawStore.ts @@ -4,7 +4,7 @@ import { CARD_WITHDRAW_MODAL } from '@/constants/modals'; import { CardDepositSource } from '@/store/useCardDepositStore'; export interface CardWithdrawTransactionState { - amount?: string; + amount?: number; clientTxId?: string; to?: CardDepositSource; /** Rain collateral withdrawal: tx hash and chain for explorer link */