0xtrails 0.1.13 → 0.2.1
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/aave.d.ts.map +1 -1
- package/dist/analytics.d.ts +12 -2
- package/dist/analytics.d.ts.map +1 -1
- package/dist/apiClient.d.ts +1 -1
- package/dist/apiClient.d.ts.map +1 -1
- package/dist/{ccip-D3gTQONK.js → ccip-BbfANth7.js} +12 -12
- package/dist/cctp.d.ts.map +1 -1
- package/dist/cctpqueue.d.ts +3 -3
- package/dist/cctpqueue.d.ts.map +1 -1
- package/dist/chains.d.ts.map +1 -1
- package/dist/config.d.ts +18 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/constants.d.ts +6 -5
- package/dist/constants.d.ts.map +1 -1
- package/dist/contractUtils.d.ts +2 -0
- package/dist/contractUtils.d.ts.map +1 -1
- package/dist/customChains.d.ts +24 -0
- package/dist/customChains.d.ts.map +1 -0
- package/dist/gasless.d.ts +19 -7
- package/dist/gasless.d.ts.map +1 -1
- package/dist/{index-CnUM7lKf.js → index-WpIVoh3X.js} +36741 -31761
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +405 -394
- package/dist/indexerClient.d.ts +10 -0
- package/dist/indexerClient.d.ts.map +1 -1
- package/dist/intentEntrypoint.d.ts +122 -0
- package/dist/intentEntrypoint.d.ts.map +1 -0
- package/dist/intents.d.ts +5 -3
- package/dist/intents.d.ts.map +1 -1
- package/dist/metaTxnMonitor.d.ts.map +1 -1
- package/dist/morpho.d.ts.map +1 -1
- package/dist/pools.d.ts +3 -1
- package/dist/pools.d.ts.map +1 -1
- package/dist/prepareSend.d.ts +18 -9
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/prices.d.ts +1 -1
- package/dist/prices.d.ts.map +1 -1
- package/dist/relaySdk.d.ts.map +1 -1
- package/dist/relayer.d.ts.map +1 -1
- package/dist/toast.d.ts +9 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/tokenBalances.d.ts +6 -2
- package/dist/tokenBalances.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/trails.d.ts +6 -5
- package/dist/trails.d.ts.map +1 -1
- package/dist/trailsClient.d.ts +12 -0
- package/dist/trailsClient.d.ts.map +1 -0
- package/dist/trailsRouter.d.ts +22 -0
- package/dist/trailsRouter.d.ts.map +1 -0
- package/dist/transactions.d.ts +8 -1
- package/dist/transactions.d.ts.map +1 -1
- package/dist/wallets.d.ts.map +1 -1
- package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
- package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/components/AccountSettings.d.ts +7 -0
- package/dist/widget/components/AccountSettings.d.ts.map +1 -0
- package/dist/widget/components/ChainList.d.ts +0 -1
- package/dist/widget/components/ChainList.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts +46 -0
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -0
- package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
- package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
- package/dist/widget/components/ConnectedWallets.d.ts +9 -0
- package/dist/widget/components/ConnectedWallets.d.ts.map +1 -0
- package/dist/widget/components/DebugMenu.d.ts.map +1 -1
- package/dist/widget/components/DebugScreensList.d.ts.map +1 -1
- package/dist/widget/components/DebugToast.d.ts +3 -0
- package/dist/widget/components/DebugToast.d.ts.map +1 -0
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/EarnPools.d.ts.map +1 -1
- package/dist/widget/components/FeeOption.d.ts +22 -0
- package/dist/widget/components/FeeOption.d.ts.map +1 -0
- package/dist/widget/components/FeeOptions.d.ts +13 -17
- package/dist/widget/components/FeeOptions.d.ts.map +1 -1
- package/dist/widget/components/Fund.d.ts +44 -0
- package/dist/widget/components/Fund.d.ts.map +1 -0
- package/dist/widget/components/FundMethods.d.ts +1 -1
- package/dist/widget/components/FundMethods.d.ts.map +1 -1
- package/dist/widget/components/FundSendForm.d.ts.map +1 -1
- package/dist/widget/components/Identicon.d.ts +9 -0
- package/dist/widget/components/Identicon.d.ts.map +1 -0
- package/dist/widget/components/MeshConnectExchanges.d.ts +5 -2
- package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
- package/dist/widget/components/MeshConnectFlow.d.ts +2 -0
- package/dist/widget/components/MeshConnectFlow.d.ts.map +1 -1
- package/dist/widget/components/NativeGasOption.d.ts +12 -0
- package/dist/widget/components/NativeGasOption.d.ts.map +1 -0
- package/dist/widget/components/Pay.d.ts +46 -0
- package/dist/widget/components/Pay.d.ts.map +1 -0
- package/dist/widget/components/PaySendForm.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receive.d.ts.map +1 -1
- package/dist/widget/components/RecentTokens.d.ts.map +1 -1
- package/dist/widget/components/Recipients.d.ts +9 -0
- package/dist/widget/components/Recipients.d.ts.map +1 -0
- package/dist/widget/components/RefundWarning.d.ts +9 -0
- package/dist/widget/components/RefundWarning.d.ts.map +1 -0
- package/dist/widget/components/SimpleSwap.d.ts.map +1 -1
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/SwapSettings.d.ts +1 -5
- package/dist/widget/components/SwapSettings.d.ts.map +1 -1
- package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
- package/dist/widget/components/ThemeSyncer.d.ts +6 -0
- package/dist/widget/components/ThemeSyncer.d.ts.map +1 -0
- package/dist/widget/components/Toast.d.ts +24 -0
- package/dist/widget/components/Toast.d.ts.map +1 -0
- package/dist/widget/components/TokenList.d.ts.map +1 -1
- package/dist/widget/components/TokenSelector.d.ts.map +1 -1
- package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
- package/dist/widget/components/TruncatedAddress.d.ts +2 -0
- package/dist/widget/components/TruncatedAddress.d.ts.map +1 -1
- package/dist/widget/components/UserPreferences.d.ts +7 -0
- package/dist/widget/components/UserPreferences.d.ts.map +1 -0
- package/dist/widget/hooks/useBack.d.ts +2 -0
- package/dist/widget/hooks/useBack.d.ts.map +1 -1
- package/dist/widget/hooks/useBalanceVisible.d.ts +1 -0
- package/dist/widget/hooks/useBalanceVisible.d.ts.map +1 -1
- package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
- package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
- package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
- package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
- package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts +54 -0
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -0
- package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/hooks/usePayMessage.d.ts +34 -0
- package/dist/widget/hooks/usePayMessage.d.ts.map +1 -0
- package/dist/widget/hooks/useRecipients.d.ts +17 -0
- package/dist/widget/hooks/useRecipients.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedFeeToken.d.ts +32 -0
- package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedMeshExchange.d.ts +14 -0
- package/dist/widget/hooks/useSelectedMeshExchange.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedRecipient.d.ts +12 -0
- package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -0
- package/dist/widget/hooks/useSendForm.d.ts +10 -13
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/hooks/useSwapAmount.d.ts +13 -0
- package/dist/widget/hooks/useSwapAmount.d.ts.map +1 -0
- package/dist/widget/hooks/useSwapSettings.d.ts +16 -0
- package/dist/widget/hooks/useSwapSettings.d.ts.map +1 -0
- package/dist/widget/hooks/useTargetAmount.d.ts +5 -0
- package/dist/widget/hooks/useTargetAmount.d.ts.map +1 -0
- package/dist/widget/hooks/useTheme.d.ts +14 -0
- package/dist/widget/hooks/useTheme.d.ts.map +1 -0
- package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
- package/dist/widget/index.js +2 -2
- package/dist/widget/widget.d.ts +9 -0
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +38 -36
- package/src/aave.ts +6 -1
- package/src/analytics.ts +109 -53
- package/src/apiClient.ts +1 -1
- package/src/cctp.ts +6 -2
- package/src/cctpqueue.ts +7 -7
- package/src/chains.ts +18 -0
- package/src/config.ts +63 -17
- package/src/constants.ts +20 -16
- package/src/contractUtils.ts +33 -2
- package/src/customChains.ts +24 -0
- package/src/gasless.ts +162 -109
- package/src/index.ts +11 -1
- package/src/indexerClient.ts +73 -1
- package/src/intentEntrypoint.ts +218 -0
- package/src/intents.ts +85 -54
- package/src/metaTxnMonitor.ts +1 -0
- package/src/morpho.ts +13 -2
- package/src/pools.ts +68 -86
- package/src/prepareSend.ts +1719 -967
- package/src/prices.ts +51 -7
- package/src/relaySdk.ts +6 -4
- package/src/relayer.ts +6 -3
- package/src/toast.ts +110 -0
- package/src/tokenBalances.ts +112 -20
- package/src/tokens.ts +70 -7
- package/src/trails.ts +81 -80
- package/src/trailsClient.ts +48 -0
- package/src/{proxyCaller.ts → trailsRouter.ts} +25 -20
- package/src/transactions.ts +30 -88
- package/src/umd.tsx +1 -1
- package/src/wallets.ts +2 -1
- package/src/widget/assets/sequence-logo.svg +15 -0
- package/src/widget/compiled.css +2 -2
- package/src/widget/components/AccountActionsDropdown.tsx +18 -159
- package/src/widget/components/AccountIntentTransactionHistory.tsx +346 -63
- package/src/widget/components/AccountSettings.tsx +102 -0
- package/src/widget/components/ChainFilterDropdown.tsx +1 -1
- package/src/widget/components/ChainList.tsx +10 -20
- package/src/widget/components/ClassicSwap.tsx +921 -0
- package/src/widget/components/ConfigDisplay.tsx +41 -5
- package/src/widget/components/ConnectWallet.tsx +168 -11
- package/src/widget/components/ConnectedWallets.tsx +342 -0
- package/src/widget/components/DebugMenu.tsx +2 -0
- package/src/widget/components/DebugScreensList.tsx +3 -0
- package/src/widget/components/DebugToast.tsx +63 -0
- package/src/widget/components/Earn.tsx +112 -143
- package/src/widget/components/EarnPools.tsx +2 -4
- package/src/widget/components/EarnPoolsFilters.tsx +6 -6
- package/src/widget/components/FeeOption.tsx +78 -0
- package/src/widget/components/FeeOptions.tsx +192 -127
- package/src/widget/components/Fund.tsx +1236 -0
- package/src/widget/components/FundMethods.tsx +4 -4
- package/src/widget/components/FundSendForm.tsx +1 -34
- package/src/widget/components/Identicon.tsx +158 -0
- package/src/widget/components/MeshConnectExchanges.tsx +32 -3
- package/src/widget/components/MeshConnectFlow.tsx +23 -4
- package/src/widget/components/NativeGasOption.tsx +99 -0
- package/src/widget/components/Pay.tsx +1092 -0
- package/src/widget/components/PaySendForm.tsx +1 -38
- package/src/widget/components/QuoteDetails.tsx +1 -30
- package/src/widget/components/Receipt.tsx +1 -1
- package/src/widget/components/Receive.tsx +4 -2
- package/src/widget/components/RecentTokens.tsx +2 -1
- package/src/widget/components/Recipients.tsx +448 -0
- package/src/widget/components/RefundWarning.tsx +61 -0
- package/src/widget/components/ScreenHeader.tsx +1 -1
- package/src/widget/components/SimpleSwap.tsx +74 -58
- package/src/widget/components/Swap.tsx +35 -853
- package/src/widget/components/SwapSettings.tsx +5 -11
- package/src/widget/components/ThemeProvider.tsx +32 -0
- package/src/widget/components/ThemeSyncer.tsx +47 -0
- package/src/widget/components/Toast.tsx +315 -0
- package/src/widget/components/TokenList.tsx +2 -34
- package/src/widget/components/TokenSelector.tsx +14 -3
- package/src/widget/components/TransactionDetails.tsx +153 -13
- package/src/widget/components/TransferPendingVertical.tsx +1 -1
- package/src/widget/components/TruncatedAddress.tsx +5 -1
- package/src/widget/components/UserPreferences.tsx +155 -0
- package/src/widget/components/WalletList.tsx +1 -1
- package/src/widget/hooks/useBack.tsx +4 -0
- package/src/widget/hooks/useBalanceVisible.tsx +40 -2
- package/src/widget/hooks/useCheckout.ts +13 -0
- package/src/widget/hooks/useCurrentScreen.tsx +4 -0
- package/src/widget/hooks/useDebugScreens.ts +12 -2
- package/src/widget/hooks/useDefaultTokenSelection.tsx +471 -0
- package/src/widget/hooks/useIntentTransactionHistory.ts +212 -0
- package/src/widget/hooks/usePayMessage.tsx +370 -0
- package/src/widget/hooks/useRecipients.ts +168 -0
- package/src/widget/hooks/useSelectedFeeToken.tsx +299 -0
- package/src/widget/hooks/useSelectedMeshExchange.tsx +46 -0
- package/src/widget/hooks/useSelectedRecipient.tsx +48 -0
- package/src/widget/hooks/useSendForm.ts +257 -49
- package/src/widget/hooks/useSwapAmount.tsx +50 -0
- package/src/widget/hooks/useSwapSettings.tsx +100 -0
- package/src/widget/hooks/useTargetAmount.ts +23 -0
- package/src/widget/hooks/useTheme.tsx +80 -0
- package/src/widget/hooks/useTokenList.ts +20 -11
- package/src/widget/index.css +45 -21
- package/src/widget/widget.tsx +294 -136
- package/dist/address.d.ts +0 -2
- package/dist/address.d.ts.map +0 -1
- package/dist/proxyCaller.d.ts +0 -21
- package/dist/proxyCaller.d.ts.map +0 -1
- package/src/address.ts +0 -6
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { useMemo, createElement } from "react"
|
|
2
|
+
import type { ReactNode } from "react"
|
|
3
|
+
import { useWidgetProps } from "./useWidgetProps.js"
|
|
4
|
+
import { useTargetAmount } from "./useTargetAmount.js"
|
|
5
|
+
import { useTokenInfo } from "../../tokens.js"
|
|
6
|
+
import { getChainInfo } from "../../chains.js"
|
|
7
|
+
import { truncateAddress } from "../../utils.js"
|
|
8
|
+
import { getExplorerUrlForAddress } from "../../explorer.js"
|
|
9
|
+
import ChainImage from "../components/ChainImage.js"
|
|
10
|
+
import TokenImage from "../components/TokenImage.js"
|
|
11
|
+
import { logger } from "../../logger.js"
|
|
12
|
+
|
|
13
|
+
export interface PayMessagePart {
|
|
14
|
+
type:
|
|
15
|
+
| "text"
|
|
16
|
+
| "appImage"
|
|
17
|
+
| "appName"
|
|
18
|
+
| "appUrl"
|
|
19
|
+
| "amount"
|
|
20
|
+
| "toAddress"
|
|
21
|
+
| "toAddressTruncated"
|
|
22
|
+
| "toChainId"
|
|
23
|
+
| "toAmount"
|
|
24
|
+
| "toTokenSymbol"
|
|
25
|
+
| "toTokenName"
|
|
26
|
+
| "toChainName"
|
|
27
|
+
| "toTokenImage"
|
|
28
|
+
| "toChainImage"
|
|
29
|
+
value: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UsePayMessageReturn {
|
|
33
|
+
message: ReactNode
|
|
34
|
+
parts: PayMessagePart[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Hook for parsing and rendering payment messages with handlebar-style interpolation.
|
|
39
|
+
*
|
|
40
|
+
* Supported placeholders:
|
|
41
|
+
* - {APP_NAME} - App name from widget props
|
|
42
|
+
* - {APP_URL} - App URL from widget props
|
|
43
|
+
* - {APP_IMAGE} - Rendered app image component
|
|
44
|
+
* - {APP_DESCRIPTION} - App description from widget props
|
|
45
|
+
* - {TO_AMOUNT_USD} - Target amount in USD
|
|
46
|
+
* - {TO_ADDRESS} - Full destination address (linked to explorer)
|
|
47
|
+
* - {TO_ADDRESS_TRUNCATED} - Truncated destination address (linked to explorer)
|
|
48
|
+
* - {TO_CHAIN_ID} - Destination chain ID
|
|
49
|
+
* - {TO_AMOUNT} - Destination amount
|
|
50
|
+
* - {TO_TOKEN_SYMBOL} - Destination token symbol
|
|
51
|
+
* - {TO_TOKEN_NAME} - Destination token name
|
|
52
|
+
* - {TO_CHAIN_NAME} - Destination chain name
|
|
53
|
+
* - {TO_TOKEN_IMAGE} - Rendered destination token image
|
|
54
|
+
* - {TO_CHAIN_IMAGE} - Rendered destination chain image
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* payMessage: "Pay {APP_IMAGE} {APP_NAME} {TO_AMOUNT_USD}"
|
|
58
|
+
* // Renders: Pay [icon] MyApp $5.00
|
|
59
|
+
*/
|
|
60
|
+
export function usePayMessage(): UsePayMessageReturn {
|
|
61
|
+
const {
|
|
62
|
+
payMessage,
|
|
63
|
+
appName,
|
|
64
|
+
appUrl,
|
|
65
|
+
appImageUrl,
|
|
66
|
+
appDescription,
|
|
67
|
+
toAddress,
|
|
68
|
+
toChainId,
|
|
69
|
+
toAmount,
|
|
70
|
+
toToken,
|
|
71
|
+
} = useWidgetProps()
|
|
72
|
+
|
|
73
|
+
const { targetAmountUsdFormatted } = useTargetAmount()
|
|
74
|
+
|
|
75
|
+
// Get chain info
|
|
76
|
+
const chainInfo = useMemo(() => {
|
|
77
|
+
if (!toChainId) return null
|
|
78
|
+
return getChainInfo(Number(toChainId))
|
|
79
|
+
}, [toChainId])
|
|
80
|
+
|
|
81
|
+
// Get token info
|
|
82
|
+
const { tokenInfo } = useTokenInfo({
|
|
83
|
+
address: toToken || "",
|
|
84
|
+
chainId: toChainId ? Number(toChainId) : undefined,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const parsed = useMemo(() => {
|
|
88
|
+
// Default message if no payMessage provided
|
|
89
|
+
const defaultMessage = appName
|
|
90
|
+
? `Pay {APP_IMAGE} {APP_NAME} {TO_AMOUNT_USD}`
|
|
91
|
+
: `Pay {TO_AMOUNT_USD}`
|
|
92
|
+
|
|
93
|
+
const template = payMessage || defaultMessage
|
|
94
|
+
|
|
95
|
+
// Parse the template into parts
|
|
96
|
+
const parts: PayMessagePart[] = []
|
|
97
|
+
const regex = /\{([^}]+)\}/g
|
|
98
|
+
let lastIndex = 0
|
|
99
|
+
let match: RegExpExecArray | null = regex.exec(template)
|
|
100
|
+
|
|
101
|
+
while (match !== null) {
|
|
102
|
+
// Add text before the placeholder
|
|
103
|
+
if (match.index > lastIndex) {
|
|
104
|
+
parts.push({
|
|
105
|
+
type: "text",
|
|
106
|
+
value: template.substring(lastIndex, match.index),
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Add the placeholder
|
|
111
|
+
const placeholder = match[1]
|
|
112
|
+
switch (placeholder) {
|
|
113
|
+
case "APP_NAME":
|
|
114
|
+
parts.push({ type: "appName", value: appName || "" })
|
|
115
|
+
break
|
|
116
|
+
case "APP_URL":
|
|
117
|
+
parts.push({ type: "appUrl", value: appUrl || "" })
|
|
118
|
+
break
|
|
119
|
+
case "APP_IMAGE":
|
|
120
|
+
parts.push({ type: "appImage", value: appImageUrl || "" })
|
|
121
|
+
break
|
|
122
|
+
case "APP_DESCRIPTION":
|
|
123
|
+
parts.push({ type: "text", value: appDescription || "" })
|
|
124
|
+
break
|
|
125
|
+
case "TO_AMOUNT_USD":
|
|
126
|
+
parts.push({
|
|
127
|
+
type: "amount",
|
|
128
|
+
value: targetAmountUsdFormatted || "$0.00",
|
|
129
|
+
})
|
|
130
|
+
break
|
|
131
|
+
case "TO_ADDRESS":
|
|
132
|
+
parts.push({ type: "toAddress", value: toAddress || "" })
|
|
133
|
+
break
|
|
134
|
+
case "TO_ADDRESS_TRUNCATED":
|
|
135
|
+
parts.push({
|
|
136
|
+
type: "toAddressTruncated",
|
|
137
|
+
value: toAddress || "",
|
|
138
|
+
})
|
|
139
|
+
break
|
|
140
|
+
case "TO_CHAIN_ID":
|
|
141
|
+
parts.push({
|
|
142
|
+
type: "toChainId",
|
|
143
|
+
value: toChainId ? String(toChainId) : "",
|
|
144
|
+
})
|
|
145
|
+
break
|
|
146
|
+
case "TO_AMOUNT":
|
|
147
|
+
parts.push({ type: "toAmount", value: toAmount || "" })
|
|
148
|
+
break
|
|
149
|
+
case "TO_TOKEN_SYMBOL":
|
|
150
|
+
parts.push({
|
|
151
|
+
type: "toTokenSymbol",
|
|
152
|
+
value: tokenInfo?.symbol || "",
|
|
153
|
+
})
|
|
154
|
+
break
|
|
155
|
+
case "TO_TOKEN_NAME":
|
|
156
|
+
parts.push({ type: "toTokenName", value: tokenInfo?.name || "" })
|
|
157
|
+
break
|
|
158
|
+
case "TO_CHAIN_NAME":
|
|
159
|
+
parts.push({ type: "toChainName", value: chainInfo?.name || "" })
|
|
160
|
+
break
|
|
161
|
+
case "TO_TOKEN_IMAGE":
|
|
162
|
+
parts.push({
|
|
163
|
+
type: "toTokenImage",
|
|
164
|
+
value: tokenInfo?.imageUrl || "",
|
|
165
|
+
})
|
|
166
|
+
break
|
|
167
|
+
case "TO_CHAIN_IMAGE":
|
|
168
|
+
parts.push({
|
|
169
|
+
type: "toChainImage",
|
|
170
|
+
value: toChainId ? String(toChainId) : "",
|
|
171
|
+
})
|
|
172
|
+
break
|
|
173
|
+
default:
|
|
174
|
+
// Unknown placeholder, keep as text
|
|
175
|
+
parts.push({ type: "text", value: match[0] })
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
lastIndex = regex.lastIndex
|
|
179
|
+
match = regex.exec(template)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Add remaining text
|
|
183
|
+
if (lastIndex < template.length) {
|
|
184
|
+
parts.push({
|
|
185
|
+
type: "text",
|
|
186
|
+
value: template.substring(lastIndex),
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return parts
|
|
191
|
+
}, [
|
|
192
|
+
payMessage,
|
|
193
|
+
appName,
|
|
194
|
+
appUrl,
|
|
195
|
+
appImageUrl,
|
|
196
|
+
appDescription,
|
|
197
|
+
targetAmountUsdFormatted,
|
|
198
|
+
toAddress,
|
|
199
|
+
toAmount,
|
|
200
|
+
tokenInfo?.symbol,
|
|
201
|
+
tokenInfo?.name,
|
|
202
|
+
tokenInfo?.imageUrl,
|
|
203
|
+
chainInfo?.name,
|
|
204
|
+
toChainId,
|
|
205
|
+
])
|
|
206
|
+
|
|
207
|
+
// Render the message
|
|
208
|
+
const message = useMemo(() => {
|
|
209
|
+
return (
|
|
210
|
+
<>
|
|
211
|
+
{parsed.map((part) => {
|
|
212
|
+
switch (part.type) {
|
|
213
|
+
case "appImage":
|
|
214
|
+
return part.value ? (
|
|
215
|
+
<img
|
|
216
|
+
key={`${part.type}`}
|
|
217
|
+
src={part.value}
|
|
218
|
+
alt={appName || "App"}
|
|
219
|
+
className="inline-block w-4 h-4 mx-1 rounded"
|
|
220
|
+
onError={(e) => {
|
|
221
|
+
logger.console.error(
|
|
222
|
+
`Error loading app image: ${e}, appImageUrl: ${appImageUrl}`,
|
|
223
|
+
)
|
|
224
|
+
// Hide image on error
|
|
225
|
+
e.currentTarget.style.display = "none"
|
|
226
|
+
}}
|
|
227
|
+
/>
|
|
228
|
+
) : null
|
|
229
|
+
case "appName":
|
|
230
|
+
return appUrl ? (
|
|
231
|
+
<a
|
|
232
|
+
key={`${part.type}`}
|
|
233
|
+
href={appUrl}
|
|
234
|
+
target="_blank"
|
|
235
|
+
rel="noopener noreferrer"
|
|
236
|
+
className="font-bold hover:underline cursor-pointer mx-1"
|
|
237
|
+
title={appDescription || appName || ""}
|
|
238
|
+
>
|
|
239
|
+
{part.value}
|
|
240
|
+
</a>
|
|
241
|
+
) : (
|
|
242
|
+
<span
|
|
243
|
+
key={`${part.type}`}
|
|
244
|
+
className="font-bold mx-1"
|
|
245
|
+
title={appDescription || ""}
|
|
246
|
+
>
|
|
247
|
+
{part.value}
|
|
248
|
+
</span>
|
|
249
|
+
)
|
|
250
|
+
case "amount":
|
|
251
|
+
return (
|
|
252
|
+
<span key={`${part.type}`} className="font-bold mx-1">
|
|
253
|
+
{part.value}
|
|
254
|
+
</span>
|
|
255
|
+
)
|
|
256
|
+
case "appUrl":
|
|
257
|
+
return part.value ? (
|
|
258
|
+
<a
|
|
259
|
+
key={`${part.type}`}
|
|
260
|
+
href={part.value}
|
|
261
|
+
target="_blank"
|
|
262
|
+
rel="noopener noreferrer"
|
|
263
|
+
className="hover:underline cursor-pointer mx-1"
|
|
264
|
+
>
|
|
265
|
+
{part.value}
|
|
266
|
+
</a>
|
|
267
|
+
) : null
|
|
268
|
+
case "toAddress":
|
|
269
|
+
return part.value ? (
|
|
270
|
+
<a
|
|
271
|
+
key={`${part.type}`}
|
|
272
|
+
href={getExplorerUrlForAddress({
|
|
273
|
+
address: part.value,
|
|
274
|
+
chainId: toChainId ? Number(toChainId) : 1,
|
|
275
|
+
})}
|
|
276
|
+
target="_blank"
|
|
277
|
+
rel="noopener noreferrer"
|
|
278
|
+
className="font-mono text-sm hover:underline cursor-pointer mx-1"
|
|
279
|
+
>
|
|
280
|
+
{part.value}
|
|
281
|
+
</a>
|
|
282
|
+
) : null
|
|
283
|
+
case "toAddressTruncated":
|
|
284
|
+
return part.value ? (
|
|
285
|
+
<a
|
|
286
|
+
key={`${part.type}`}
|
|
287
|
+
href={getExplorerUrlForAddress({
|
|
288
|
+
address: part.value,
|
|
289
|
+
chainId: toChainId ? Number(toChainId) : 1,
|
|
290
|
+
})}
|
|
291
|
+
target="_blank"
|
|
292
|
+
rel="noopener noreferrer"
|
|
293
|
+
className="font-mono text-sm hover:underline cursor-pointer mx-1"
|
|
294
|
+
>
|
|
295
|
+
{truncateAddress(part.value)}
|
|
296
|
+
</a>
|
|
297
|
+
) : null
|
|
298
|
+
case "toChainId":
|
|
299
|
+
return (
|
|
300
|
+
<span key={`${part.type}`} className="mx-1">
|
|
301
|
+
{part.value}
|
|
302
|
+
</span>
|
|
303
|
+
)
|
|
304
|
+
case "toAmount":
|
|
305
|
+
return (
|
|
306
|
+
<span key={`${part.type}`} className="font-bold mx-1">
|
|
307
|
+
{part.value}
|
|
308
|
+
</span>
|
|
309
|
+
)
|
|
310
|
+
case "toTokenSymbol":
|
|
311
|
+
return (
|
|
312
|
+
<span key={`${part.type}`} className="font-bold mx-1">
|
|
313
|
+
{part.value}
|
|
314
|
+
</span>
|
|
315
|
+
)
|
|
316
|
+
case "toTokenName":
|
|
317
|
+
return (
|
|
318
|
+
<span key={`${part.type}`} className="mx-1">
|
|
319
|
+
{part.value}
|
|
320
|
+
</span>
|
|
321
|
+
)
|
|
322
|
+
case "toChainName":
|
|
323
|
+
return (
|
|
324
|
+
<span key={`${part.type}`} className="font-semibold mx-1">
|
|
325
|
+
{part.value}
|
|
326
|
+
</span>
|
|
327
|
+
)
|
|
328
|
+
case "toTokenImage":
|
|
329
|
+
return part.value && tokenInfo?.symbol ? (
|
|
330
|
+
<span key={`${part.type}`} className="inline-block mx-1">
|
|
331
|
+
{createElement(TokenImage, {
|
|
332
|
+
imageUrl: part.value,
|
|
333
|
+
symbol: tokenInfo.symbol,
|
|
334
|
+
chainId: toChainId ? Number(toChainId) : undefined,
|
|
335
|
+
size: 16,
|
|
336
|
+
})}
|
|
337
|
+
</span>
|
|
338
|
+
) : null
|
|
339
|
+
case "toChainImage":
|
|
340
|
+
return toChainId ? (
|
|
341
|
+
<span key={`${part.type}`} className="inline-block mx-1">
|
|
342
|
+
{createElement(ChainImage, {
|
|
343
|
+
chainId: Number(toChainId),
|
|
344
|
+
size: 16,
|
|
345
|
+
})}
|
|
346
|
+
</span>
|
|
347
|
+
) : null
|
|
348
|
+
case "text":
|
|
349
|
+
return <span key={`${part.type}`}>{part.value}</span>
|
|
350
|
+
default:
|
|
351
|
+
return <span key={`${part.type}`}>{part.value}</span>
|
|
352
|
+
}
|
|
353
|
+
})}
|
|
354
|
+
</>
|
|
355
|
+
)
|
|
356
|
+
}, [
|
|
357
|
+
parsed,
|
|
358
|
+
appName,
|
|
359
|
+
appUrl,
|
|
360
|
+
appImageUrl,
|
|
361
|
+
appDescription,
|
|
362
|
+
tokenInfo,
|
|
363
|
+
toChainId,
|
|
364
|
+
])
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
message,
|
|
368
|
+
parts: parsed,
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from "react"
|
|
2
|
+
import { isAddress } from "viem"
|
|
3
|
+
import { logger } from "../../logger.js"
|
|
4
|
+
|
|
5
|
+
export interface RecentRecipient {
|
|
6
|
+
address: string
|
|
7
|
+
ensName?: string
|
|
8
|
+
resolvedAddress?: string // For when ENS name resolves to address
|
|
9
|
+
timestamp: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const STORAGE_KEY = "trails-recent-recipients"
|
|
13
|
+
const MAX_RECENT_RECIPIENTS = 10
|
|
14
|
+
|
|
15
|
+
export const useRecipients = () => {
|
|
16
|
+
const [recentRecipients, setRecentRecipients] = useState<RecentRecipient[]>(
|
|
17
|
+
[],
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
// Load recent recipients from localStorage on mount
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
try {
|
|
23
|
+
const stored = localStorage.getItem(STORAGE_KEY)
|
|
24
|
+
if (stored) {
|
|
25
|
+
const parsed = JSON.parse(stored) as RecentRecipient[]
|
|
26
|
+
// Sort by timestamp (most recent first)
|
|
27
|
+
const sorted = parsed.sort((a, b) => b.timestamp - a.timestamp)
|
|
28
|
+
setRecentRecipients(sorted)
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
logger.console.error(
|
|
32
|
+
"[trails-sdk] Failed to load recent recipients:",
|
|
33
|
+
error,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}, [])
|
|
37
|
+
|
|
38
|
+
// Save recipients to localStorage
|
|
39
|
+
const saveToStorage = useCallback((recipients: RecentRecipient[]) => {
|
|
40
|
+
try {
|
|
41
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(recipients))
|
|
42
|
+
} catch (error) {
|
|
43
|
+
logger.console.error(
|
|
44
|
+
"[trails-sdk] Failed to save recent recipients:",
|
|
45
|
+
error,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
}, [])
|
|
49
|
+
|
|
50
|
+
// ENS resolution placeholder - in real implementation, use proper ENS resolver
|
|
51
|
+
const resolveENS = useCallback(
|
|
52
|
+
async (input: string): Promise<{ address?: string; ensName?: string }> => {
|
|
53
|
+
try {
|
|
54
|
+
if (input.endsWith(".eth")) {
|
|
55
|
+
// ENS name to address resolution - return empty for now
|
|
56
|
+
logger.console.log(
|
|
57
|
+
`[trails-sdk] ENS resolution not implemented: ${input}`,
|
|
58
|
+
)
|
|
59
|
+
return { ensName: input }
|
|
60
|
+
} else if (isAddress(input)) {
|
|
61
|
+
// Address to ENS name resolution (reverse lookup) - return empty for now
|
|
62
|
+
logger.console.log(
|
|
63
|
+
`[trails-sdk] Reverse ENS resolution not implemented: ${input}`,
|
|
64
|
+
)
|
|
65
|
+
return { address: input, ensName: "" }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.console.error("[trails-sdk] ENS resolution failed:", error)
|
|
71
|
+
return {}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
[],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
// Add a new recipient to the recent list
|
|
78
|
+
const addRecentRecipient = useCallback(
|
|
79
|
+
async (input: string, resolvedAddress?: string, ensName?: string) => {
|
|
80
|
+
const trimmedInput = input.trim()
|
|
81
|
+
if (!trimmedInput) return
|
|
82
|
+
|
|
83
|
+
let recipientData: RecentRecipient
|
|
84
|
+
|
|
85
|
+
if (isAddress(trimmedInput)) {
|
|
86
|
+
// It's a valid address
|
|
87
|
+
recipientData = {
|
|
88
|
+
address: trimmedInput,
|
|
89
|
+
ensName: ensName, // Use provided ENS name from reverse resolution
|
|
90
|
+
timestamp: Date.now(),
|
|
91
|
+
}
|
|
92
|
+
} else if (trimmedInput.endsWith(".eth")) {
|
|
93
|
+
// It's an ENS name
|
|
94
|
+
if (resolvedAddress && isAddress(resolvedAddress)) {
|
|
95
|
+
// We have a resolved address for this ENS name
|
|
96
|
+
recipientData = {
|
|
97
|
+
address: resolvedAddress,
|
|
98
|
+
ensName: ensName || trimmedInput, // Use provided ENS name or fallback to input
|
|
99
|
+
resolvedAddress: resolvedAddress,
|
|
100
|
+
timestamp: Date.now(),
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
return // Invalid ENS name - no resolved address
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
return // Invalid input
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
setRecentRecipients((prev) => {
|
|
110
|
+
// Remove any existing entry with the same address or ENS name
|
|
111
|
+
const filtered = prev.filter(
|
|
112
|
+
(r) =>
|
|
113
|
+
r.address.toLowerCase() !== recipientData.address.toLowerCase() &&
|
|
114
|
+
(!recipientData.ensName || r.ensName !== recipientData.ensName),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// Add new entry at the beginning
|
|
118
|
+
const updated = [recipientData, ...filtered].slice(
|
|
119
|
+
0,
|
|
120
|
+
MAX_RECENT_RECIPIENTS,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
// Save to localStorage
|
|
124
|
+
saveToStorage(updated)
|
|
125
|
+
|
|
126
|
+
return updated
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
logger.console.log("[trails-sdk] Added recent recipient:", recipientData)
|
|
130
|
+
},
|
|
131
|
+
[saveToStorage],
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
// Remove a recipient from the recent list
|
|
135
|
+
const removeRecentRecipient = useCallback(
|
|
136
|
+
(address: string) => {
|
|
137
|
+
setRecentRecipients((prev) => {
|
|
138
|
+
const updated = prev.filter(
|
|
139
|
+
(r) => r.address.toLowerCase() !== address.toLowerCase(),
|
|
140
|
+
)
|
|
141
|
+
saveToStorage(updated)
|
|
142
|
+
return updated
|
|
143
|
+
})
|
|
144
|
+
},
|
|
145
|
+
[saveToStorage],
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
// Clear all recent recipients
|
|
149
|
+
const clearRecentRecipients = useCallback(() => {
|
|
150
|
+
setRecentRecipients([])
|
|
151
|
+
try {
|
|
152
|
+
localStorage.removeItem(STORAGE_KEY)
|
|
153
|
+
} catch (error) {
|
|
154
|
+
logger.console.error(
|
|
155
|
+
"[trails-sdk] Failed to clear recent recipients:",
|
|
156
|
+
error,
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
}, [])
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
recentRecipients,
|
|
163
|
+
addRecentRecipient,
|
|
164
|
+
removeRecentRecipient,
|
|
165
|
+
clearRecentRecipients,
|
|
166
|
+
resolveENS,
|
|
167
|
+
}
|
|
168
|
+
}
|