0xtrails 0.12.0 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics.d.ts +65 -50
- package/dist/analytics.d.ts.map +1 -1
- package/dist/{ccip-DtfgR432.js → ccip-62W6LwH2.js} +28 -28
- package/dist/chains.d.ts.map +1 -1
- package/dist/error.d.ts +2 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/estimate.d.ts.map +1 -1
- package/dist/fees.d.ts.map +1 -1
- package/dist/{index-CHiCSmCD.js → index-C0QTNYIA.js} +43750 -41806
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +199 -171
- package/dist/localeUtils.d.ts.map +1 -1
- package/dist/meld/components/MeldCountriesList.d.ts +0 -2
- package/dist/meld/components/MeldCountriesList.d.ts.map +1 -1
- package/dist/meld/components/MeldFundMethods.d.ts.map +1 -1
- package/dist/meld/components/MeldTokensList.d.ts.map +1 -1
- package/dist/meld/utils/meld.d.ts +2 -52
- package/dist/meld/utils/meld.d.ts.map +1 -1
- package/dist/poolUtils.d.ts.map +1 -1
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/prices.d.ts +1 -2
- package/dist/prices.d.ts.map +1 -1
- package/dist/query/balance.fetchers.d.ts +2 -2
- package/dist/query/balance.fetchers.d.ts.map +1 -1
- package/dist/query/fiat.fetchers.d.ts +11 -0
- package/dist/query/fiat.fetchers.d.ts.map +1 -0
- package/dist/query/fiat.hooks.d.ts +18 -0
- package/dist/query/fiat.hooks.d.ts.map +1 -0
- package/dist/query/fiat.queries.d.ts +24 -0
- package/dist/query/fiat.queries.d.ts.map +1 -0
- package/dist/query/meld.fetchers.d.ts +19 -0
- package/dist/query/meld.fetchers.d.ts.map +1 -0
- package/dist/query/meld.hooks.d.ts +4 -0
- package/dist/query/meld.hooks.d.ts.map +1 -0
- package/dist/query/meld.queries.d.ts +61 -0
- package/dist/query/meld.queries.d.ts.map +1 -0
- package/dist/recover.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/gaslessDeposit.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/standardDeposit.d.ts +7 -1
- package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/intentHandler.d.ts +2 -0
- package/dist/transactionIntent/handlers/intentHandler.d.ts.map +1 -1
- package/dist/transactionIntent/quote/normalizeQuote.d.ts +2 -2
- package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
- package/dist/transactionIntent/quote/quoteHelpers.d.ts +1 -1
- package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
- package/dist/transactionIntent/types.d.ts +2 -0
- package/dist/transactionIntent/types.d.ts.map +1 -1
- package/dist/transactionIntent/utils/balanceChecker.d.ts +3 -1
- package/dist/transactionIntent/utils/balanceChecker.d.ts.map +1 -1
- package/dist/transactions.d.ts +2 -9
- package/dist/transactions.d.ts.map +1 -1
- package/dist/umd/trails.min.js +206 -152
- package/dist/utils/fiat.d.ts +8 -0
- package/dist/utils/fiat.d.ts.map +1 -0
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/passthrough.d.ts +5 -2
- package/dist/utils/passthrough.d.ts.map +1 -1
- package/dist/utils/validation.d.ts +33 -0
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/walletUtils.d.ts +1 -1
- package/dist/walletUtils.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/DepositTracker.d.ts.map +1 -1
- package/dist/widget/components/Earn.d.ts +2 -0
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/FeeOption.d.ts.map +1 -1
- package/dist/widget/components/Fund.d.ts.map +1 -1
- package/dist/widget/components/FundMethods.d.ts.map +1 -1
- package/dist/widget/components/HookModalContent.d.ts.map +1 -1
- package/dist/widget/components/MeldForm.d.ts.map +1 -1
- package/dist/widget/components/MeldHistory.d.ts.map +1 -1
- package/dist/widget/components/MeldStepsFlow.d.ts.map +1 -1
- package/dist/widget/components/OFTProgressBar.d.ts +2 -0
- package/dist/widget/components/OFTProgressBar.d.ts.map +1 -1
- package/dist/widget/components/OnRampProviderSelector.d.ts.map +1 -1
- package/dist/widget/components/OnrampHistoryRow.d.ts.map +1 -1
- package/dist/widget/components/Pay.d.ts.map +1 -1
- package/dist/widget/components/PercentageMaxButtons.d.ts.map +1 -1
- package/dist/widget/components/PoolDeposit.d.ts +2 -0
- package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receipt.d.ts.map +1 -1
- package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
- package/dist/widget/components/Swap.d.ts +2 -0
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -1
- package/dist/widget/components/Withdraw.d.ts.map +1 -1
- package/dist/widget/hooks/useAmountUsd.d.ts.map +1 -1
- package/dist/widget/hooks/useCustomTokenSearch.d.ts.map +1 -1
- package/dist/widget/hooks/useDisplayCurrencyPreference.d.ts.map +1 -1
- package/dist/widget/hooks/useFiatOnRampCurrencies.d.ts +3 -21
- package/dist/widget/hooks/useFiatOnRampCurrencies.d.ts.map +1 -1
- package/dist/widget/hooks/useMeldTransactionHistory.d.ts.map +1 -1
- package/dist/widget/hooks/useOnRampCountryDefaults.d.ts +0 -18
- package/dist/widget/hooks/useOnRampCountryDefaults.d.ts.map +1 -1
- package/dist/widget/hooks/useOnRampPaymentMethods.d.ts +2 -18
- package/dist/widget/hooks/useOnRampPaymentMethods.d.ts.map +1 -1
- package/dist/widget/hooks/useOnRampQuote.d.ts.map +1 -1
- package/dist/widget/hooks/useQuote.d.ts +5 -1
- package/dist/widget/hooks/useQuote.d.ts.map +1 -1
- package/dist/widget/hooks/useSendForm.d.ts +3 -1
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
- package/dist/widget/hooks/useTokenWithFreshBalance.d.ts +3 -2
- package/dist/widget/hooks/useTokenWithFreshBalance.d.ts.map +1 -1
- package/dist/widget/index.js +1 -1
- package/dist/widget/types/commonProps.d.ts +2 -0
- package/dist/widget/types/commonProps.d.ts.map +1 -1
- package/dist/widget/utils/transactionFailure.d.ts +20 -0
- package/dist/widget/utils/transactionFailure.d.ts.map +1 -0
- package/dist/widget/widget.d.ts +44 -3
- package/dist/widget/widget.d.ts.map +1 -1
- package/dist/widget/workers/intentExecutionWorker.d.ts.map +1 -1
- package/package.json +22 -22
- package/src/analytics.ts +115 -79
- package/src/chains.ts +0 -1
- package/src/error.ts +11 -0
- package/src/estimate.ts +12 -7
- package/src/fees.ts +0 -1
- package/src/index.ts +11 -0
- package/src/localeUtils.ts +3 -1
- package/src/meld/components/MeldCountriesList.tsx +30 -15
- package/src/meld/components/MeldFundMethods.tsx +8 -4
- package/src/meld/components/MeldTokensList.tsx +90 -2
- package/src/meld/utils/meld.ts +3 -400
- package/src/poolUtils.ts +5 -19
- package/src/prepareSend.ts +32 -5
- package/src/prices.ts +7 -33
- package/src/query/balance.fetchers.ts +128 -168
- package/src/query/fiat.fetchers.ts +33 -0
- package/src/query/fiat.hooks.ts +71 -0
- package/src/query/fiat.queries.ts +67 -0
- package/src/query/meld.fetchers.ts +97 -0
- package/src/query/meld.hooks.ts +18 -0
- package/src/query/meld.queries.ts +184 -0
- package/src/recover.ts +6 -1
- package/src/tokens.ts +31 -6
- package/src/transactionIntent/deposits/depositOrchestrator.ts +2 -0
- package/src/transactionIntent/deposits/gaslessDeposit.ts +9 -2
- package/src/transactionIntent/deposits/standardDeposit.ts +35 -14
- package/src/transactionIntent/handlers/intentHandler.ts +134 -138
- package/src/transactionIntent/quote/normalizeQuote.ts +31 -22
- package/src/transactionIntent/quote/quoteHelpers.ts +24 -7
- package/src/transactionIntent/types.ts +2 -0
- package/src/transactionIntent/utils/balanceChecker.ts +10 -4
- package/src/transactions.ts +22 -13
- package/src/umd.tsx +1 -1
- package/src/utils/fiat.ts +32 -0
- package/src/utils/format.ts +1 -3
- package/src/utils/passthrough.ts +19 -3
- package/src/utils/validation.ts +88 -0
- package/src/utils.ts +2 -1
- package/src/walletUtils.ts +2 -2
- package/src/widget/components/AccountIntentTransactionHistory.tsx +2 -2
- package/src/widget/components/ClassicSwap.tsx +10 -4
- package/src/widget/components/DepositTracker.tsx +2 -5
- package/src/widget/components/Earn.tsx +6 -0
- package/src/widget/components/FeeOption.tsx +15 -8
- package/src/widget/components/Fund.tsx +16 -11
- package/src/widget/components/FundMethods.tsx +255 -192
- package/src/widget/components/HookModalContent.tsx +4 -0
- package/src/widget/components/MeldForm.tsx +44 -42
- package/src/widget/components/MeldHistory.tsx +4 -3
- package/src/widget/components/MeldStepsFlow.tsx +33 -71
- package/src/widget/components/OFTProgressBar.tsx +32 -12
- package/src/widget/components/OnRampProviderSelector.tsx +2 -1
- package/src/widget/components/OnrampHistoryRow.tsx +2 -1
- package/src/widget/components/Pay.tsx +8 -2
- package/src/widget/components/PercentageMaxButtons.tsx +5 -3
- package/src/widget/components/PoolDeposit.tsx +6 -0
- package/src/widget/components/PoolWithdraw.tsx +1 -1
- package/src/widget/components/QuoteDetails.tsx +5 -4
- package/src/widget/components/Receipt.tsx +4 -3
- package/src/widget/components/SlippageToleranceSettings.tsx +3 -2
- package/src/widget/components/Swap.tsx +2 -0
- package/src/widget/components/TransferPendingVertical.tsx +21 -28
- package/src/widget/components/UserPreferences.tsx +1 -1
- package/src/widget/components/Withdraw.tsx +20 -14
- package/src/widget/hooks/useAmountUsd.ts +3 -15
- package/src/widget/hooks/useCustomTokenSearch.tsx +2 -6
- package/src/widget/hooks/useDisplayCurrencyPreference.tsx +1 -2
- package/src/widget/hooks/useFiatOnRampCurrencies.ts +11 -76
- package/src/widget/hooks/useMeldTransactionHistory.ts +24 -89
- package/src/widget/hooks/useOnRampCountryDefaults.ts +3 -49
- package/src/widget/hooks/useOnRampPaymentMethods.ts +21 -100
- package/src/widget/hooks/useOnRampQuote.ts +2 -5
- package/src/widget/hooks/useQuote.ts +10 -12
- package/src/widget/hooks/useSendForm.ts +6 -0
- package/src/widget/hooks/useTokenList.ts +3 -6
- package/src/widget/hooks/useTokenWithFreshBalance.ts +141 -11
- package/src/widget/types/commonProps.ts +2 -0
- package/src/widget/utils/transactionFailure.ts +52 -0
- package/src/widget/widget.tsx +137 -59
- package/src/widget/workers/intentExecutionWorker.ts +3 -1
- package/dist/widget/hooks/useExchangeRate.d.ts +0 -31
- package/dist/widget/hooks/useExchangeRate.d.ts.map +0 -1
- package/dist/widget/hooks/useFiatCurrencyList.d.ts +0 -3
- package/dist/widget/hooks/useFiatCurrencyList.d.ts.map +0 -1
- package/src/widget/hooks/useExchangeRate.ts +0 -257
- package/src/widget/hooks/useFiatCurrencyList.ts +0 -66
package/src/poolUtils.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { encodeFunctionData, type WalletClient } from "viem"
|
|
2
2
|
import { logger } from "./logger.js"
|
|
3
3
|
import type { Pool } from "./pools.js"
|
|
4
|
+
import { isValidStringAmount, isPositiveBigInt } from "./utils/validation.js"
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Generate Aave deposit calldata
|
|
@@ -47,25 +48,15 @@ export function generateAaveDepositCalldata(
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
// Validate amount
|
|
50
|
-
if (
|
|
51
|
-
!amount ||
|
|
52
|
-
amount === "" ||
|
|
53
|
-
Number.isNaN(Number(amount)) ||
|
|
54
|
-
Number(amount) <= 0
|
|
55
|
-
) {
|
|
51
|
+
if (!isValidStringAmount(amount)) {
|
|
56
52
|
throw new Error(`Invalid amount: ${amount}`)
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
// Validate amount can be converted to BigInt
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
amountBigInt = BigInt(amount)
|
|
63
|
-
if (amountBigInt <= 0n) {
|
|
64
|
-
throw new Error("Amount must be greater than 0")
|
|
65
|
-
}
|
|
66
|
-
} catch {
|
|
56
|
+
if (!isPositiveBigInt(amount)) {
|
|
67
57
|
throw new Error(`Invalid amount format: ${amount}`)
|
|
68
58
|
}
|
|
59
|
+
const amountBigInt = BigInt(amount)
|
|
69
60
|
|
|
70
61
|
// Aave V3 Pool contract deposit function
|
|
71
62
|
// function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)
|
|
@@ -115,12 +106,7 @@ export function generateMorphoDepositCalldata(
|
|
|
115
106
|
}
|
|
116
107
|
|
|
117
108
|
// Validate amount
|
|
118
|
-
if (
|
|
119
|
-
!amount ||
|
|
120
|
-
amount === "" ||
|
|
121
|
-
Number.isNaN(Number(amount)) ||
|
|
122
|
-
Number(amount) <= 0
|
|
123
|
-
) {
|
|
109
|
+
if (!isValidStringAmount(amount)) {
|
|
124
110
|
throw new Error("Invalid amount")
|
|
125
111
|
}
|
|
126
112
|
|
package/src/prepareSend.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { abortControllerRegistry } from "./abortController.js"
|
|
2
2
|
import { trackPaymentError, trackPaymentStarted } from "./analytics.js"
|
|
3
3
|
import { getSlippageToleranceValue } from "./widget/components/SlippageToleranceSettings.js"
|
|
4
|
-
import { isNativeToken, isZeroAccount } from "./utils/address.js"
|
|
4
|
+
import { addressEqual, isNativeToken, isZeroAccount } from "./utils/address.js"
|
|
5
5
|
import { getERC20TransferData } from "./utils.js"
|
|
6
6
|
import { getTokenPrice } from "./prices.js"
|
|
7
7
|
import {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
replacePlaceholderInCalldata,
|
|
10
10
|
TRAILS_ROUTER_PLACEHOLDER_AMOUNT,
|
|
11
11
|
} from "./placeholder.js"
|
|
12
|
-
import { IntentProtocolVersion } from "@0xtrails/api"
|
|
12
|
+
import { IntentProtocolVersion, RouteProvider } from "@0xtrails/api"
|
|
13
13
|
import { wrapCalldataWithTrailsRouterIfNeeded } from "./abis/trailsRouter.js"
|
|
14
14
|
import { logger } from "./logger.js"
|
|
15
15
|
import { getIsCustomCalldata } from "./contractUtils.js"
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
isSameToken,
|
|
25
25
|
} from "./transactionIntent/index.js"
|
|
26
26
|
import { Address, Bytes, Hex } from "ox"
|
|
27
|
+
import { getWethAddress } from "./tokens.js"
|
|
27
28
|
import {
|
|
28
29
|
createHydratePayload,
|
|
29
30
|
encodeHydrateExecuteCalldata,
|
|
@@ -76,6 +77,8 @@ export async function prepareSend(
|
|
|
76
77
|
originNativeTokenPriceUsd,
|
|
77
78
|
swapProvider,
|
|
78
79
|
bridgeProvider,
|
|
80
|
+
swapProviderFallback,
|
|
81
|
+
bridgeProviderFallback,
|
|
79
82
|
fundMethod: fundMethodFromOptions,
|
|
80
83
|
mode,
|
|
81
84
|
checkoutOnHandlers,
|
|
@@ -151,11 +154,32 @@ export async function prepareSend(
|
|
|
151
154
|
let effectiveDestinationAddress = recipient
|
|
152
155
|
let effectiveDestinationApprovalAddress: Address.Address | undefined
|
|
153
156
|
let effectiveDestinationCalldata = destinationCalldata
|
|
157
|
+
let effectiveSwapProvider = swapProvider
|
|
154
158
|
|
|
155
159
|
// Check if this is a same-chain same-token transfer (before modifying effectiveDestinationAddress)
|
|
156
160
|
const isToSameChain = isSameChain(originChainId, destinationChainId)
|
|
157
161
|
const isToSameToken = isSameToken(originTokenAddress, destinationTokenAddress)
|
|
158
162
|
const isSameChainSameToken = isToSameChain && isToSameToken
|
|
163
|
+
const chainWethAddress = getWethAddress(originChainId)
|
|
164
|
+
const isSameChainNativeWrappedPair =
|
|
165
|
+
isToSameChain &&
|
|
166
|
+
!!chainWethAddress &&
|
|
167
|
+
((isNativeToken(originTokenAddress) &&
|
|
168
|
+
addressEqual(destinationTokenAddress, chainWethAddress)) ||
|
|
169
|
+
(addressEqual(originTokenAddress, chainWethAddress) &&
|
|
170
|
+
isNativeToken(destinationTokenAddress)))
|
|
171
|
+
const isSameChainNativeWrappedToSelf =
|
|
172
|
+
isSameChainNativeWrappedPair && addressEqual(recipient, account.address)
|
|
173
|
+
|
|
174
|
+
if (isSameChainNativeWrappedToSelf) {
|
|
175
|
+
effectiveDestinationAddress = account.address
|
|
176
|
+
effectiveSwapProvider = RouteProvider.WETH
|
|
177
|
+
// Only force empty calldata when caller did not provide custom calldata.
|
|
178
|
+
if (!hasCustomCalldata) {
|
|
179
|
+
effectiveDestinationCalldata = undefined
|
|
180
|
+
hasCustomCalldata = false
|
|
181
|
+
}
|
|
182
|
+
}
|
|
159
183
|
|
|
160
184
|
// For same-chain same-token with custom calldata:
|
|
161
185
|
// Replace any placeholder in the calldata with the actual swapAmount
|
|
@@ -179,7 +203,8 @@ export async function prepareSend(
|
|
|
179
203
|
!hasCustomCalldata &&
|
|
180
204
|
tradeType === TradeType.EXACT_INPUT &&
|
|
181
205
|
!isNativeToken(destinationTokenAddress) &&
|
|
182
|
-
!isSameChainSameToken
|
|
206
|
+
!isSameChainSameToken &&
|
|
207
|
+
!isSameChainNativeWrappedToSelf // Keep backend-owned passthrough handling for native<->WETH to-self
|
|
183
208
|
) {
|
|
184
209
|
effectiveDestinationCalldata = getERC20TransferData(
|
|
185
210
|
recipient,
|
|
@@ -401,7 +426,7 @@ export async function prepareSend(
|
|
|
401
426
|
sourceTokenPriceUsd: sourceTokenPriceUsd?.toString(),
|
|
402
427
|
destinationTokenPriceUsd: destinationTokenPriceUsd?.toString(),
|
|
403
428
|
tradeType: tradeType.toString(),
|
|
404
|
-
swapProvider:
|
|
429
|
+
swapProvider: effectiveSwapProvider || undefined,
|
|
405
430
|
bridgeProvider: bridgeProvider || undefined,
|
|
406
431
|
onramp,
|
|
407
432
|
walletId,
|
|
@@ -476,8 +501,10 @@ export async function prepareSend(
|
|
|
476
501
|
originNativeTokenPriceUsd,
|
|
477
502
|
slippageTolerance,
|
|
478
503
|
tradeType,
|
|
479
|
-
swapProvider,
|
|
504
|
+
swapProvider: effectiveSwapProvider,
|
|
480
505
|
bridgeProvider,
|
|
506
|
+
swapProviderFallback,
|
|
507
|
+
bridgeProviderFallback,
|
|
481
508
|
fundMethod,
|
|
482
509
|
mode,
|
|
483
510
|
checkoutOnHandlers,
|
package/src/prices.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { queryClient as defaultQueryClient } from "./query/client.js"
|
|
|
7
7
|
import { useTrailsClient, type TrailsClient } from "./trailsClient.js"
|
|
8
8
|
import { MINUTE_MS, SECOND_MS, retryDelay } from "./utils/time.js"
|
|
9
9
|
import { useUserActivityContext } from "./widget/providers/UserActivityProvider.js"
|
|
10
|
+
import { isValidNumber, isNonNegativeNumber } from "./utils/validation.js"
|
|
10
11
|
|
|
11
12
|
import { normalizeAddress } from "./utils/address.js"
|
|
12
13
|
|
|
@@ -459,25 +460,17 @@ export function calcAmountUsdPrice({
|
|
|
459
460
|
const sanitizedPrice = normalizeNumber(usdPrice)
|
|
460
461
|
|
|
461
462
|
// Validate inputs
|
|
462
|
-
if (
|
|
463
|
-
!Number.isFinite(sanitizedAmount) ||
|
|
464
|
-
Number.isNaN(sanitizedAmount) ||
|
|
465
|
-
sanitizedAmount < 0
|
|
466
|
-
) {
|
|
463
|
+
if (!isNonNegativeNumber(sanitizedAmount)) {
|
|
467
464
|
return 0
|
|
468
465
|
}
|
|
469
|
-
if (
|
|
470
|
-
!Number.isFinite(sanitizedPrice) ||
|
|
471
|
-
Number.isNaN(sanitizedPrice) ||
|
|
472
|
-
sanitizedPrice < 0
|
|
473
|
-
) {
|
|
466
|
+
if (!isNonNegativeNumber(sanitizedPrice)) {
|
|
474
467
|
return 0
|
|
475
468
|
}
|
|
476
469
|
|
|
477
470
|
const result = sanitizedAmount * sanitizedPrice
|
|
478
471
|
|
|
479
472
|
// Validate result
|
|
480
|
-
if (!
|
|
473
|
+
if (!isValidNumber(result)) {
|
|
481
474
|
logger.console.error("[trails-sdk] Error calculating amount USD:", {
|
|
482
475
|
sanitizedAmount,
|
|
483
476
|
sanitizedPrice,
|
|
@@ -501,7 +494,7 @@ export function normalizeNumber(
|
|
|
501
494
|
// If already a number, return it directly
|
|
502
495
|
if (typeof number === "number") {
|
|
503
496
|
// Validate the number
|
|
504
|
-
if (!
|
|
497
|
+
if (!isValidNumber(number)) {
|
|
505
498
|
logger.console.error("[trails-sdk] Invalid number value:", number)
|
|
506
499
|
return 0
|
|
507
500
|
}
|
|
@@ -513,7 +506,7 @@ export function normalizeNumber(
|
|
|
513
506
|
const normalized = parseLocaleNumber(number)
|
|
514
507
|
|
|
515
508
|
// Return 0 for invalid numbers
|
|
516
|
-
if (!
|
|
509
|
+
if (!isValidNumber(normalized)) {
|
|
517
510
|
logger.console.error("[trails-sdk] Error normalizing number string:", {
|
|
518
511
|
number,
|
|
519
512
|
normalized,
|
|
@@ -532,23 +525,4 @@ export function formatTvl(tvl: number): string {
|
|
|
532
525
|
return `$${tvl.toFixed(0)}`
|
|
533
526
|
}
|
|
534
527
|
|
|
535
|
-
|
|
536
|
-
export const isValidNumeric = (
|
|
537
|
-
value: number | string | null | undefined,
|
|
538
|
-
): boolean => {
|
|
539
|
-
if (value === null || value === undefined || value === "") {
|
|
540
|
-
return true // Empty values are considered valid
|
|
541
|
-
}
|
|
542
|
-
const strValue = String(value).trim()
|
|
543
|
-
return /^\d+(\.\d+)?$/.test(strValue)
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
export const isValidInteger = (
|
|
547
|
-
value: number | string | null | undefined,
|
|
548
|
-
): boolean => {
|
|
549
|
-
if (value === null || value === undefined || value === "") {
|
|
550
|
-
return true // Empty values are considered valid
|
|
551
|
-
}
|
|
552
|
-
const strValue = String(value).trim()
|
|
553
|
-
return /^\d+$/.test(strValue)
|
|
554
|
-
}
|
|
528
|
+
export { isValidNumeric, isValidInteger } from "./utils/validation.js"
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
* This is a leaf module — NO React imports, NO queryClient, NO balanceKeys.
|
|
4
4
|
*/
|
|
5
5
|
import type {
|
|
6
|
-
|
|
7
|
-
GatewayTokenBalance,
|
|
8
|
-
GetTokenBalancesSummaryReturn,
|
|
6
|
+
IndexerGateway as IndexerGatewayTypes,
|
|
9
7
|
NativeTokenBalance,
|
|
10
8
|
Page,
|
|
11
9
|
SequenceIndexerGateway,
|
|
@@ -90,8 +88,14 @@ function addPriceAndUsdFields(
|
|
|
90
88
|
priceData?: TokenPrice,
|
|
91
89
|
): void {
|
|
92
90
|
// Get prices from both sources
|
|
93
|
-
const apiPriceUSD =
|
|
94
|
-
|
|
91
|
+
const apiPriceUSD =
|
|
92
|
+
"priceUSD" in token && typeof token.priceUSD === "string"
|
|
93
|
+
? token.priceUSD
|
|
94
|
+
: undefined
|
|
95
|
+
const apiBalanceUSD =
|
|
96
|
+
"balanceUSD" in token && typeof token.balanceUSD === "string"
|
|
97
|
+
? token.balanceUSD
|
|
98
|
+
: undefined
|
|
95
99
|
const coinApiPrice = priceData?.priceUsd
|
|
96
100
|
const parsedIndexerPrice = apiPriceUSD ? parseFloat(apiPriceUSD) : Number.NaN
|
|
97
101
|
const indexerPrice = Number.isNaN(parsedIndexerPrice)
|
|
@@ -233,6 +237,87 @@ export function convertNativeTokenBalanceToToken(
|
|
|
233
237
|
// Default empty page info for query fallback
|
|
234
238
|
export const defaultPage = { page: 1, pageSize: 10, more: false }
|
|
235
239
|
|
|
240
|
+
function getPageRequest(page?: number, pageSize?: number): Page | undefined {
|
|
241
|
+
if (page === undefined && pageSize === undefined) return undefined
|
|
242
|
+
return {
|
|
243
|
+
page,
|
|
244
|
+
pageSize,
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function createEmptyMultiAccountResult(
|
|
249
|
+
accounts: string[],
|
|
250
|
+
): GetTokenBalancesForMultipleAccountsReturn {
|
|
251
|
+
const result: GetTokenBalancesForMultipleAccountsReturn = {}
|
|
252
|
+
for (const account of accounts) {
|
|
253
|
+
result[account.toLowerCase()] = {
|
|
254
|
+
tokens: [],
|
|
255
|
+
nativeTokens: [],
|
|
256
|
+
erc20Tokens: [],
|
|
257
|
+
page: defaultPage,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return result
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function mapChainEntryResults<T extends { accountAddress?: string }>(
|
|
264
|
+
entries: Array<{ chainId: number; results?: T[] }>,
|
|
265
|
+
result: GetTokenBalancesForMultipleAccountsReturn,
|
|
266
|
+
toToken: (entry: T) => Token,
|
|
267
|
+
bucket: "erc20Tokens" | "nativeTokens",
|
|
268
|
+
): void {
|
|
269
|
+
for (const chainEntry of entries) {
|
|
270
|
+
if (!getChainInfo(chainEntry.chainId)) {
|
|
271
|
+
logger.console.warn(
|
|
272
|
+
`[tokenBalances] Skipping unsupported chain ID: ${chainEntry.chainId}`,
|
|
273
|
+
)
|
|
274
|
+
continue
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
for (const entry of chainEntry.results || []) {
|
|
278
|
+
const accountAddr = entry.accountAddress?.toLowerCase()
|
|
279
|
+
const accountEntry = accountAddr ? result[accountAddr] : undefined
|
|
280
|
+
if (!accountEntry) continue
|
|
281
|
+
|
|
282
|
+
const token = toToken(entry)
|
|
283
|
+
accountEntry[bucket].push(token)
|
|
284
|
+
accountEntry.tokens.push(token)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function toTokenPriceKey(chainId: number, tokenAddress?: string): string {
|
|
290
|
+
return `${chainId}:${tokenAddress || zeroAddress}`
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function createTokenMapFromGatewaySummary(
|
|
294
|
+
summary: IndexerGatewayTypes.GetTokenBalancesSummaryResponse,
|
|
295
|
+
): Map<string, TokenBalance | NativeTokenBalance> {
|
|
296
|
+
const tokenMap = new Map<string, TokenBalance | NativeTokenBalance>()
|
|
297
|
+
|
|
298
|
+
for (const chainEntry of summary.balances) {
|
|
299
|
+
for (const tokenBalance of chainEntry.results) {
|
|
300
|
+
if (!("contractAddress" in tokenBalance)) continue
|
|
301
|
+
if (!("contractInfo" in tokenBalance)) continue
|
|
302
|
+
tokenMap.set(
|
|
303
|
+
`${tokenBalance.contractAddress}-${tokenBalance.chainId}-${tokenBalance.contractInfo?.symbol}`,
|
|
304
|
+
tokenBalance,
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
for (const chainEntry of summary.nativeBalances) {
|
|
310
|
+
for (const nativeBalance of chainEntry.results) {
|
|
311
|
+
tokenMap.set(
|
|
312
|
+
`${zeroAddress}-${nativeBalance.chainId}-native`,
|
|
313
|
+
nativeBalance,
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return tokenMap
|
|
319
|
+
}
|
|
320
|
+
|
|
236
321
|
// Type guard for native token balance
|
|
237
322
|
export function isNativeToken(
|
|
238
323
|
token: TokenBalance | NativeTokenBalance,
|
|
@@ -304,7 +389,7 @@ export async function fetchTokenBalancesSummary({
|
|
|
304
389
|
indexerGatewayClient,
|
|
305
390
|
page,
|
|
306
391
|
pageSize,
|
|
307
|
-
}: GetTokenBalancesParams): Promise<
|
|
392
|
+
}: GetTokenBalancesParams): Promise<IndexerGatewayTypes.GetTokenBalancesSummaryResponse> {
|
|
308
393
|
if (!account || !indexerGatewayClient) {
|
|
309
394
|
throw new Error("Account address and indexer client are required")
|
|
310
395
|
}
|
|
@@ -319,16 +404,10 @@ export async function fetchTokenBalancesSummary({
|
|
|
319
404
|
contractWhitelist: tokenAddress ? [tokenAddress] : undefined,
|
|
320
405
|
omitNativeBalances: false,
|
|
321
406
|
},
|
|
322
|
-
page:
|
|
323
|
-
page !== undefined || pageSize !== undefined
|
|
324
|
-
? {
|
|
325
|
-
page: page,
|
|
326
|
-
pageSize: pageSize,
|
|
327
|
-
}
|
|
328
|
-
: undefined,
|
|
407
|
+
page: getPageRequest(page, pageSize),
|
|
329
408
|
})
|
|
330
409
|
|
|
331
|
-
return summaryFromGateway
|
|
410
|
+
return summaryFromGateway
|
|
332
411
|
} catch (error) {
|
|
333
412
|
if (isNetworkError(error)) {
|
|
334
413
|
logger.console.warn(
|
|
@@ -380,84 +459,24 @@ export async function fetchMultiAccountBalances({
|
|
|
380
459
|
contractTypes: ["ERC20"],
|
|
381
460
|
omitNativeBalances: false,
|
|
382
461
|
},
|
|
383
|
-
page:
|
|
384
|
-
page !== undefined || pageSize !== undefined
|
|
385
|
-
? {
|
|
386
|
-
page: page,
|
|
387
|
-
pageSize: pageSize,
|
|
388
|
-
}
|
|
389
|
-
: undefined,
|
|
462
|
+
page: getPageRequest(page, pageSize),
|
|
390
463
|
})
|
|
391
464
|
|
|
392
|
-
|
|
393
|
-
// Each entry in balances/nativeBalances arrays corresponds to an account
|
|
394
|
-
const result: GetTokenBalancesForMultipleAccountsReturn = {}
|
|
395
|
-
|
|
396
|
-
// Initialize result with empty arrays for each account
|
|
397
|
-
for (const account of validAccounts) {
|
|
398
|
-
result[account.toLowerCase()] = {
|
|
399
|
-
tokens: [],
|
|
400
|
-
nativeTokens: [],
|
|
401
|
-
erc20Tokens: [],
|
|
402
|
-
page: defaultPage,
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Map balances to their respective accounts and convert to Token type
|
|
407
|
-
// Gateway returns data grouped by chainId: { chainId, results: [...] }
|
|
408
|
-
// Each result has accountAddress, so we need to iterate through results
|
|
409
|
-
const gatewayBalances =
|
|
410
|
-
summaryFromGateway.balances as unknown as GatewayTokenBalance[]
|
|
411
|
-
const gatewayNativeBalances =
|
|
412
|
-
summaryFromGateway.nativeBalances as unknown as GatewayNativeTokenBalances[]
|
|
413
|
-
|
|
414
|
-
// For ERC20 balances: iterate through chain entries, then through results
|
|
415
|
-
for (const chainEntry of gatewayBalances) {
|
|
416
|
-
const chainId = (chainEntry as any).chainId
|
|
417
|
-
// Skip unsupported chain IDs
|
|
418
|
-
if (!getChainInfo(chainId)) {
|
|
419
|
-
logger.console.warn(
|
|
420
|
-
`[tokenBalances] Skipping unsupported chain ID: ${chainId}`,
|
|
421
|
-
)
|
|
422
|
-
continue
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const results = (chainEntry as any).results || []
|
|
426
|
-
for (const tokenResult of results) {
|
|
427
|
-
const accountAddr = tokenResult.accountAddress?.toLowerCase()
|
|
428
|
-
if (accountAddr && result[accountAddr]) {
|
|
429
|
-
// Convert to Token type and add to arrays
|
|
430
|
-
const token = convertTokenBalanceToToken(tokenResult as TokenBalance)
|
|
431
|
-
result[accountAddr].erc20Tokens.push(token)
|
|
432
|
-
result[accountAddr].tokens.push(token)
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
465
|
+
const result = createEmptyMultiAccountResult(validAccounts)
|
|
436
466
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
`[tokenBalances] Skipping unsupported chain ID: ${chainId}`,
|
|
444
|
-
)
|
|
445
|
-
continue
|
|
446
|
-
}
|
|
467
|
+
mapChainEntryResults(
|
|
468
|
+
summaryFromGateway.balances,
|
|
469
|
+
result,
|
|
470
|
+
convertTokenBalanceToToken,
|
|
471
|
+
"erc20Tokens",
|
|
472
|
+
)
|
|
447
473
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
nativeResult as NativeTokenBalance,
|
|
455
|
-
)
|
|
456
|
-
result[accountAddr].nativeTokens.push(token)
|
|
457
|
-
result[accountAddr].tokens.push(token)
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
474
|
+
mapChainEntryResults(
|
|
475
|
+
summaryFromGateway.nativeBalances,
|
|
476
|
+
result,
|
|
477
|
+
convertNativeTokenBalanceToToken,
|
|
478
|
+
"nativeTokens",
|
|
479
|
+
)
|
|
461
480
|
|
|
462
481
|
// Set page info from gateway response
|
|
463
482
|
for (const account of validAccounts) {
|
|
@@ -474,17 +493,7 @@ export async function fetchMultiAccountBalances({
|
|
|
474
493
|
"[trails-sdk] Network error fetching token balances, returning empty result:",
|
|
475
494
|
{ error, accounts },
|
|
476
495
|
)
|
|
477
|
-
|
|
478
|
-
const result: GetTokenBalancesForMultipleAccountsReturn = {}
|
|
479
|
-
for (const account of validAccounts) {
|
|
480
|
-
result[account.toLowerCase()] = {
|
|
481
|
-
tokens: [],
|
|
482
|
-
nativeTokens: [],
|
|
483
|
-
erc20Tokens: [],
|
|
484
|
-
page: defaultPage,
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
return result
|
|
496
|
+
return createEmptyMultiAccountResult(validAccounts)
|
|
488
497
|
}
|
|
489
498
|
|
|
490
499
|
logger.console.error(
|
|
@@ -510,68 +519,21 @@ export async function fetchBalancesWithPrices({
|
|
|
510
519
|
indexerGatewayClient,
|
|
511
520
|
trailsClient,
|
|
512
521
|
}: GetTokenBalancesWithPricesParams): Promise<GetTokenBalancesWithPriceReturn> {
|
|
513
|
-
// Step 1: Fetch raw balance summary
|
|
514
522
|
const summaryFromGateway = await fetchTokenBalancesSummary({
|
|
515
523
|
account,
|
|
516
524
|
indexerGatewayClient,
|
|
517
525
|
})
|
|
518
|
-
|
|
519
|
-
// Step 2: Flatten into a TokenBalance array (combining ERC20 + native)
|
|
520
|
-
const tokenMap = new Map<string, TokenBalance>()
|
|
521
|
-
|
|
522
|
-
for (const balance of summaryFromGateway.balances) {
|
|
523
|
-
;(balance as any).results.forEach((b: any) => {
|
|
524
|
-
tokenMap.set(
|
|
525
|
-
`${b.contractAddress}-${b.contractInfo?.chainId}-${b.contractInfo?.symbol}`,
|
|
526
|
-
{
|
|
527
|
-
...b,
|
|
528
|
-
contractAddress: b.contractAddress ?? zeroAddress,
|
|
529
|
-
tokenId: b.contractInfo?.symbol,
|
|
530
|
-
symbol: b.contractInfo?.symbol,
|
|
531
|
-
name: b.contractInfo?.name,
|
|
532
|
-
decimals: b.contractInfo?.decimals,
|
|
533
|
-
imageUrl: getTokenImageUrl({
|
|
534
|
-
chainId: b.contractInfo?.chainId || 0,
|
|
535
|
-
contractAddress: b.contractAddress ?? zeroAddress,
|
|
536
|
-
symbol: b.contractInfo?.symbol,
|
|
537
|
-
fallbackImageUrl: b.contractInfo?.logoURI,
|
|
538
|
-
}),
|
|
539
|
-
},
|
|
540
|
-
)
|
|
541
|
-
})
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
for (const balance of summaryFromGateway.nativeBalances) {
|
|
545
|
-
;(balance as any).results.forEach((b: any) => {
|
|
546
|
-
const contractAddr = b.contractAddress ?? zeroAddress
|
|
547
|
-
tokenMap.set(`${contractAddr}-${b.chainId}-${b.symbol}`, {
|
|
548
|
-
...b,
|
|
549
|
-
contractAddress: contractAddr,
|
|
550
|
-
tokenId: b.symbol,
|
|
551
|
-
symbol: b.symbol,
|
|
552
|
-
name: b.name,
|
|
553
|
-
decimals: b.decimals ?? b.contractInfo?.decimals ?? 18,
|
|
554
|
-
imageUrl: getTokenImageUrl({
|
|
555
|
-
chainId: b.chainId,
|
|
556
|
-
contractAddress: zeroAddress,
|
|
557
|
-
symbol: b.symbol,
|
|
558
|
-
fallbackImageUrl: b.contractInfo?.logoURI,
|
|
559
|
-
}),
|
|
560
|
-
})
|
|
561
|
-
})
|
|
562
|
-
}
|
|
563
|
-
|
|
526
|
+
const tokenMap = createTokenMapFromGatewaySummary(summaryFromGateway)
|
|
564
527
|
const tokens = Array.from(tokenMap.values())
|
|
565
528
|
|
|
566
|
-
// Step 3: Fetch prices for all tokens (graceful — balances without prices beats no data)
|
|
567
529
|
let tokenPrices: Awaited<ReturnType<typeof getTokenPrices>> = []
|
|
568
530
|
try {
|
|
569
531
|
tokenPrices = await getTokenPrices(
|
|
570
532
|
trailsClient,
|
|
571
533
|
tokens.map((t) => ({
|
|
572
534
|
chainId: t.chainId,
|
|
573
|
-
tokenAddress: t
|
|
574
|
-
tokenSymbol: t.contractInfo?.symbol || "",
|
|
535
|
+
tokenAddress: isNativeToken(t) ? zeroAddress : t.contractAddress,
|
|
536
|
+
tokenSymbol: isNativeToken(t) ? "" : t.contractInfo?.symbol || "",
|
|
575
537
|
})),
|
|
576
538
|
)
|
|
577
539
|
} catch (err) {
|
|
@@ -581,27 +543,25 @@ export async function fetchBalancesWithPrices({
|
|
|
581
543
|
)
|
|
582
544
|
}
|
|
583
545
|
|
|
584
|
-
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
isSameToken =
|
|
592
|
-
p.token.tokenAddress === zeroAddress || !p.token.tokenAddress
|
|
593
|
-
}
|
|
594
|
-
return isSameChain && isSameToken
|
|
595
|
-
})
|
|
546
|
+
const tokenPriceMap = new Map<string, TokenPrice>()
|
|
547
|
+
for (const tokenPrice of tokenPrices) {
|
|
548
|
+
tokenPriceMap.set(
|
|
549
|
+
toTokenPriceKey(tokenPrice.token.chainId, tokenPrice.token.tokenAddress),
|
|
550
|
+
tokenPrice,
|
|
551
|
+
)
|
|
552
|
+
}
|
|
596
553
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
b
|
|
601
|
-
|
|
602
|
-
)
|
|
554
|
+
const balancesWithPrices: Token[] = tokens.map((b) => {
|
|
555
|
+
const priceData = tokenPriceMap.get(
|
|
556
|
+
toTokenPriceKey(
|
|
557
|
+
b.chainId,
|
|
558
|
+
isNativeToken(b) ? zeroAddress : b.contractAddress,
|
|
559
|
+
),
|
|
560
|
+
)
|
|
561
|
+
if (isNativeToken(b)) {
|
|
562
|
+
return convertNativeTokenBalanceToToken(b, priceData)
|
|
603
563
|
}
|
|
604
|
-
return convertTokenBalanceToToken(b
|
|
564
|
+
return convertTokenBalanceToToken(b, priceData)
|
|
605
565
|
})
|
|
606
566
|
|
|
607
567
|
return {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure async fetcher functions and helpers for fiat/currency queries.
|
|
3
|
+
* This is a leaf module — NO React imports, NO queryClient.
|
|
4
|
+
*/
|
|
5
|
+
import type { ExchangeRate, FiatCurrency } from "@0xtrails/api"
|
|
6
|
+
import type { TrailsClient } from "../trailsClient.js"
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Fetchers
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Fetch the list of supported fiat currencies from the API.
|
|
14
|
+
*/
|
|
15
|
+
export async function getFiatCurrencyList(
|
|
16
|
+
trailsClient: TrailsClient,
|
|
17
|
+
): Promise<FiatCurrency[]> {
|
|
18
|
+
const response = await trailsClient.getFiatCurrencyList()
|
|
19
|
+
return response.currencies || []
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fetch the exchange rate from USD to a target currency.
|
|
24
|
+
*/
|
|
25
|
+
export async function getExchangeRate(
|
|
26
|
+
trailsClient: TrailsClient,
|
|
27
|
+
targetCurrency: string,
|
|
28
|
+
): Promise<ExchangeRate> {
|
|
29
|
+
const response = await trailsClient.getExchangeRate({
|
|
30
|
+
toCurrency: targetCurrency,
|
|
31
|
+
})
|
|
32
|
+
return response.exchangeRate
|
|
33
|
+
}
|