0xtrails 0.7.0 → 0.8.0
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/{ccip-fConRNoG.js → ccip-uMWNlvmJ.js} +34 -34
- package/dist/fees.d.ts.map +1 -1
- package/dist/{index-BbajxCG_.js → index-BiPwqVkZ.js} +31527 -28874
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +478 -456
- package/dist/intents.d.ts +10 -4
- package/dist/intents.d.ts.map +1 -1
- package/dist/prepareSend.d.ts +1 -1
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/prices.d.ts +2 -2
- package/dist/prices.d.ts.map +1 -1
- package/dist/refund.d.ts +116 -0
- package/dist/refund.d.ts.map +1 -0
- package/dist/tokenBalances.d.ts +1 -1
- package/dist/tokenBalances.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/crossChain.d.ts +4 -3
- package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +3 -3
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
- package/dist/transactionIntent/quote/normalizeQuote.d.ts +1 -2
- package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
- package/dist/transactionIntent/quote/quoteHelpers.d.ts +3 -3
- package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
- package/dist/transactionIntent/types.d.ts +5 -4
- package/dist/transactionIntent/types.d.ts.map +1 -1
- package/dist/transactions.d.ts +4 -0
- package/dist/transactions.d.ts.map +1 -1
- package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts +2 -1
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/Earn.d.ts +2 -1
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/ErrorDisplay.d.ts.map +1 -1
- package/dist/widget/components/Fund.d.ts +2 -1
- package/dist/widget/components/Fund.d.ts.map +1 -1
- package/dist/widget/components/FundSwap.d.ts +2 -1
- package/dist/widget/components/FundSwap.d.ts.map +1 -1
- package/dist/widget/components/Pay.d.ts +2 -1
- package/dist/widget/components/Pay.d.ts.map +1 -1
- package/dist/widget/components/PoolDeposit.d.ts +2 -1
- package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts +1 -0
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Swap.d.ts +2 -1
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/TokenImage.d.ts.map +1 -1
- package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
- package/dist/widget/css/compiled.css +1 -1
- package/dist/widget/hooks/useAmountUsd.d.ts.map +1 -1
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
- package/dist/widget/hooks/useGetIntent.d.ts +18 -0
- package/dist/widget/hooks/useGetIntent.d.ts.map +1 -0
- package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/hooks/useQuote.d.ts +10 -7
- package/dist/widget/hooks/useQuote.d.ts.map +1 -1
- package/dist/widget/hooks/useSendForm.d.ts +3 -2
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
- package/dist/widget/hooks/useTrailsSendTransaction.d.ts.map +1 -1
- package/dist/widget/index.js +3 -3
- package/dist/widget/widget.d.ts +2 -1
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +5 -12
- package/src/fees.ts +8 -2
- package/src/index.ts +33 -1
- package/src/intents.ts +34 -7
- package/src/prepareSend.ts +6 -4
- package/src/prices.ts +6 -6
- package/src/refund.ts +914 -0
- package/src/tokenBalances.ts +4 -14
- package/src/transactionIntent/handlers/crossChain.ts +21 -10
- package/src/transactionIntent/handlers/sameChainSameToken.ts +12 -8
- package/src/transactionIntent/quote/normalizeQuote.ts +29 -27
- package/src/transactionIntent/quote/quoteHelpers.ts +5 -9
- package/src/transactionIntent/types.ts +5 -3
- package/src/transactions.ts +5 -0
- package/src/widget/compiled.css +1 -1
- package/src/widget/components/AccountIntentTransactionHistory.tsx +197 -5
- package/src/widget/components/ClassicSwap.tsx +6 -3
- package/src/widget/components/Earn.tsx +6 -3
- package/src/widget/components/ErrorDisplay.tsx +6 -4
- package/src/widget/components/Fund.tsx +6 -3
- package/src/widget/components/FundSwap.tsx +2 -1
- package/src/widget/components/Pay.tsx +15 -7
- package/src/widget/components/PoolDeposit.tsx +6 -3
- package/src/widget/components/QuoteDetails.tsx +34 -38
- package/src/widget/components/Swap.tsx +2 -1
- package/src/widget/components/TokenImage.tsx +3 -1
- package/src/widget/components/TransactionDetails.tsx +108 -0
- package/src/widget/hooks/useAmountUsd.ts +0 -3
- package/src/widget/hooks/useDefaultTokenSelection.tsx +0 -3
- package/src/widget/hooks/useGetIntent.ts +53 -0
- package/src/widget/hooks/useIntentTransactionHistory.ts +85 -3
- package/src/widget/hooks/useQuote.ts +16 -10
- package/src/widget/hooks/useSendForm.ts +30 -15
- package/src/widget/hooks/useTokenList.ts +2 -4
- package/src/widget/hooks/useTrailsSendTransaction.ts +2 -1
- package/src/widget/widget.tsx +12 -6
- package/dist/sequenceWallet.d.ts +0 -67
- package/dist/sequenceWallet.d.ts.map +0 -1
- package/src/sequenceWallet.ts +0 -532
package/src/refund.ts
ADDED
|
@@ -0,0 +1,914 @@
|
|
|
1
|
+
import type { IntentCalls } from "@0xtrails/api"
|
|
2
|
+
import {
|
|
3
|
+
Config,
|
|
4
|
+
Constants,
|
|
5
|
+
Erc6492,
|
|
6
|
+
Payload,
|
|
7
|
+
Signature,
|
|
8
|
+
} from "@0xsequence/wallet-primitives"
|
|
9
|
+
import { Address, Bytes, Hex } from "ox"
|
|
10
|
+
import { AbiFunction } from "ox"
|
|
11
|
+
import { SEQUENCE_V3_CONTRACT_ADDRESSES } from "./constants.js"
|
|
12
|
+
import { calculateIntentAddress, createIntentConfiguration } from "./intents.js"
|
|
13
|
+
import { getChainRpcClient, getChainInfo } from "./chains.js"
|
|
14
|
+
import { logger } from "./logger.js"
|
|
15
|
+
import type { WalletClient, Chain } from "viem"
|
|
16
|
+
import { splitSignature } from "./gasless.js"
|
|
17
|
+
import { useMemo, useCallback } from "react"
|
|
18
|
+
import { useGetIntent } from "./widget/hooks/useGetIntent.js"
|
|
19
|
+
import { useTokenBalances } from "./tokenBalances.js"
|
|
20
|
+
import { getERC20TransferData } from "./encoders.js"
|
|
21
|
+
import { zeroAddress } from "viem"
|
|
22
|
+
import type { Payload as WalletPayload } from "@0xsequence/wallet-primitives"
|
|
23
|
+
import { attemptSwitchChain } from "./chainSwitch.js"
|
|
24
|
+
|
|
25
|
+
// Constants for execute function
|
|
26
|
+
type ChainId = number
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Checks if an address is deployed by checking for bytecode
|
|
30
|
+
* @param address - The address to check
|
|
31
|
+
* @param chainId - The chain ID where the address exists
|
|
32
|
+
* @returns Promise resolving to true if the address has bytecode, false otherwise
|
|
33
|
+
*/
|
|
34
|
+
export async function isAddressDeployed(
|
|
35
|
+
address: Address.Address,
|
|
36
|
+
chainId: ChainId,
|
|
37
|
+
): Promise<boolean> {
|
|
38
|
+
const publicClient = getChainRpcClient(chainId)
|
|
39
|
+
const code = await publicClient.getCode({ address })
|
|
40
|
+
const isDeployed = code !== undefined && code !== "0x"
|
|
41
|
+
logger.console.log("[refund] Address deployment check:", {
|
|
42
|
+
address,
|
|
43
|
+
chainId,
|
|
44
|
+
codeLength: code?.length ?? 0,
|
|
45
|
+
isDeployed,
|
|
46
|
+
})
|
|
47
|
+
return isDeployed
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generates a random space value for Sequence wallet payloads.
|
|
52
|
+
* Space must be in the range [0, 2^160) (20 bytes).
|
|
53
|
+
*/
|
|
54
|
+
function generateRandomSpace(): bigint {
|
|
55
|
+
const bytes = new Uint8Array(20) // 20 bytes = 160 bits
|
|
56
|
+
crypto.getRandomValues(bytes)
|
|
57
|
+
// Convert bytes to BigInt, ensuring it's within [0, 2^160)
|
|
58
|
+
return BigInt(
|
|
59
|
+
`0x${Array.from(bytes)
|
|
60
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
61
|
+
.join("")}`,
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Creates a refund payload with random space
|
|
67
|
+
* @param refundCall - The refund call to include in the payload
|
|
68
|
+
* @param nonce - The nonce value to use
|
|
69
|
+
* @returns The created payload
|
|
70
|
+
*/
|
|
71
|
+
function createRefundPayload(
|
|
72
|
+
refundCall: Payload.Call,
|
|
73
|
+
nonce: bigint,
|
|
74
|
+
): Payload.Calls {
|
|
75
|
+
const refundSpace = generateRandomSpace()
|
|
76
|
+
return {
|
|
77
|
+
type: "call",
|
|
78
|
+
calls: [refundCall],
|
|
79
|
+
space: refundSpace,
|
|
80
|
+
nonce,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface BuildRefundTransactionParams {
|
|
85
|
+
mainSigner: Address.Address
|
|
86
|
+
calls: Array<IntentCalls>
|
|
87
|
+
refundCall: Payload.Call
|
|
88
|
+
chainId: ChainId
|
|
89
|
+
intentAddress: Address.Address
|
|
90
|
+
walletClient: WalletClient
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface BuildRefundTransactionWithSignatureParams {
|
|
94
|
+
mainSigner: Address.Address
|
|
95
|
+
calls: Array<IntentCalls>
|
|
96
|
+
payload: Payload.Calls // The payload that was signed
|
|
97
|
+
chainId: ChainId
|
|
98
|
+
intentAddress: Address.Address
|
|
99
|
+
signature: string // Hex string signature
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Signs the payload hash for a refund transaction
|
|
104
|
+
*/
|
|
105
|
+
export async function signPayload(
|
|
106
|
+
walletClient: WalletClient,
|
|
107
|
+
intentAddress: Address.Address,
|
|
108
|
+
chainId: ChainId,
|
|
109
|
+
payload: Payload.Calls,
|
|
110
|
+
): Promise<string> {
|
|
111
|
+
if (!walletClient.account) {
|
|
112
|
+
throw new Error("Wallet client account is required for signing")
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const typedData = Payload.toTyped(intentAddress, chainId, payload)
|
|
116
|
+
|
|
117
|
+
const signature = await walletClient.signTypedData({
|
|
118
|
+
account: walletClient.account,
|
|
119
|
+
...typedData,
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
logger.console.log("[refund] Signature received:", signature)
|
|
123
|
+
return signature
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Builds a refund transaction using a pre-signed hash
|
|
128
|
+
*/
|
|
129
|
+
export async function buildRefundTransactionWithSignature(
|
|
130
|
+
params: BuildRefundTransactionWithSignatureParams,
|
|
131
|
+
): Promise<{ to: `0x${string}`; data: Hex.Hex }> {
|
|
132
|
+
const { mainSigner, calls, payload, chainId, intentAddress, signature } =
|
|
133
|
+
params
|
|
134
|
+
|
|
135
|
+
// Validate refund call "to" address is not zeroAddress
|
|
136
|
+
const refundCall = payload.calls[0]
|
|
137
|
+
if (!refundCall) {
|
|
138
|
+
throw new Error("Refund call not found in payload")
|
|
139
|
+
}
|
|
140
|
+
if (!refundCall.to || Address.isEqual(refundCall.to, zeroAddress)) {
|
|
141
|
+
throw new Error("Refund call 'to' address cannot be zero address")
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
logger.console.log(
|
|
145
|
+
"[refund] buildRefundTransactionWithSignature called with params:",
|
|
146
|
+
{
|
|
147
|
+
mainSigner,
|
|
148
|
+
callsCount: calls.length,
|
|
149
|
+
calls: calls.map((c) => ({
|
|
150
|
+
chainId: c.chainId,
|
|
151
|
+
space: c.space?.toString(),
|
|
152
|
+
nonce: c.nonce?.toString(),
|
|
153
|
+
calls: c.calls.map((c) => ({
|
|
154
|
+
to: c.to,
|
|
155
|
+
value: c.value?.toString(),
|
|
156
|
+
data: c.data,
|
|
157
|
+
gasLimit: c.gasLimit?.toString(),
|
|
158
|
+
delegateCall: c.delegateCall,
|
|
159
|
+
onlyFallback: c.onlyFallback,
|
|
160
|
+
behaviorOnError: c.behaviorOnError,
|
|
161
|
+
})),
|
|
162
|
+
})),
|
|
163
|
+
refundCall: {
|
|
164
|
+
to: refundCall.to,
|
|
165
|
+
value: refundCall.value.toString(),
|
|
166
|
+
data: refundCall.data,
|
|
167
|
+
gasLimit: refundCall.gasLimit.toString(),
|
|
168
|
+
},
|
|
169
|
+
chainId,
|
|
170
|
+
intentAddress,
|
|
171
|
+
signatureLength: signature.length,
|
|
172
|
+
space: payload.space.toString(),
|
|
173
|
+
nonce: payload.nonce.toString(),
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
// Filter calls to only include origin calls (matching chainId)
|
|
178
|
+
// The intentAddress is the originIntentAddress, so we should only use origin calls
|
|
179
|
+
// when calculating the intent configuration and address
|
|
180
|
+
const originCalls = calls.filter((c) => {
|
|
181
|
+
const callChainId =
|
|
182
|
+
typeof c.chainId === "bigint" ? c.chainId : BigInt(String(c.chainId))
|
|
183
|
+
return callChainId === BigInt(chainId)
|
|
184
|
+
})
|
|
185
|
+
logger.console.log("[refund] Filtered origin calls:", {
|
|
186
|
+
originalCallsCount: calls.length,
|
|
187
|
+
originCallsCount: originCalls.length,
|
|
188
|
+
chainId,
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const intentConfig = createIntentConfiguration(mainSigner, originCalls)
|
|
192
|
+
logger.console.log("[refund] Intent configuration created:", {
|
|
193
|
+
threshold: intentConfig.threshold.toString(),
|
|
194
|
+
checkpoint: intentConfig.checkpoint.toString(),
|
|
195
|
+
topology: Array.isArray(intentConfig.topology)
|
|
196
|
+
? `Node with ${intentConfig.topology.length} items`
|
|
197
|
+
: "SignerLeaf",
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const context = SEQUENCE_V3_CONTRACT_ADDRESSES
|
|
201
|
+
const imageHash = Config.hashConfiguration(intentConfig)
|
|
202
|
+
logger.console.log("[refund] Image hash:", Hex.fromBytes(imageHash))
|
|
203
|
+
// See if the derived intent address is correct
|
|
204
|
+
const derivedConfigurationAddress = calculateIntentAddress(
|
|
205
|
+
mainSigner,
|
|
206
|
+
originCalls,
|
|
207
|
+
)
|
|
208
|
+
logger.console.log(
|
|
209
|
+
"[refund] Derived configuration address:",
|
|
210
|
+
derivedConfigurationAddress,
|
|
211
|
+
)
|
|
212
|
+
if (!Address.isEqual(derivedConfigurationAddress, intentAddress)) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
"Derived configuration address does not match intent address. Probably config is wrong",
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Use the payload that was passed in (already contains space, nonce, and refundCall)
|
|
219
|
+
logger.console.log("[refund] Payload created:", {
|
|
220
|
+
type: payload.type,
|
|
221
|
+
callsCount: payload.calls.length,
|
|
222
|
+
space: payload.space.toString(),
|
|
223
|
+
nonce: payload.nonce.toString(),
|
|
224
|
+
calls: payload.calls.map((c) => ({
|
|
225
|
+
to: c.to,
|
|
226
|
+
value: c.value.toString(),
|
|
227
|
+
data: c.data,
|
|
228
|
+
gasLimit: c.gasLimit.toString(),
|
|
229
|
+
})),
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
const encodedPayload = Bytes.toHex(Payload.encode(payload))
|
|
233
|
+
logger.console.log("[refund] Encoded payload:", encodedPayload)
|
|
234
|
+
|
|
235
|
+
const toSignatureLeaf = async (
|
|
236
|
+
signer: Address.Address,
|
|
237
|
+
signature: `0x${string}`,
|
|
238
|
+
): Promise<
|
|
239
|
+
Signature.SignatureOfSignerLeaf | Signature.SignatureOfSapientSignerLeaf
|
|
240
|
+
> => {
|
|
241
|
+
const signerIsContract = await isAddressDeployed(signer, chainId)
|
|
242
|
+
if (signerIsContract) {
|
|
243
|
+
return {
|
|
244
|
+
address: signer,
|
|
245
|
+
type: "erc1271",
|
|
246
|
+
data: signature as `0x${string}`,
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
const { r, s, v } = splitSignature(signature)
|
|
250
|
+
const yParity = v - 27 // Convert v to yParity (0 or 1)
|
|
251
|
+
logger.console.log("[refund] Signature parsed:", { r, s, v, yParity })
|
|
252
|
+
const rBigInt = BigInt(r)
|
|
253
|
+
const sBigInt = BigInt(s)
|
|
254
|
+
return {
|
|
255
|
+
type: "hash",
|
|
256
|
+
r: rBigInt,
|
|
257
|
+
s: sBigInt,
|
|
258
|
+
yParity,
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const signerSignatureLeaf = await toSignatureLeaf(
|
|
263
|
+
mainSigner,
|
|
264
|
+
signature as `0x${string}`,
|
|
265
|
+
)
|
|
266
|
+
const topology = Signature.fillLeaves(intentConfig.topology, (leaf) =>
|
|
267
|
+
Address.isEqual(leaf.address, mainSigner) ? signerSignatureLeaf : undefined,
|
|
268
|
+
)
|
|
269
|
+
logger.console.log("[refund] Topology filled with signature leaves")
|
|
270
|
+
|
|
271
|
+
const filledConfig = {
|
|
272
|
+
noChainId: false,
|
|
273
|
+
configuration: { ...intentConfig, topology },
|
|
274
|
+
}
|
|
275
|
+
const encodedSignature = Signature.encodeSignature(filledConfig, true, true)
|
|
276
|
+
logger.console.log(
|
|
277
|
+
"[refund] Signature encoded:",
|
|
278
|
+
Bytes.toHex(encodedSignature),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
// Check if intent address has bytecode deployed
|
|
282
|
+
const intentAddressDeployed = await isAddressDeployed(intentAddress, chainId)
|
|
283
|
+
|
|
284
|
+
const executeData = AbiFunction.encodeData(Constants.EXECUTE, [
|
|
285
|
+
encodedPayload,
|
|
286
|
+
Bytes.toHex(encodedSignature),
|
|
287
|
+
])
|
|
288
|
+
logger.console.log("[refund] Execute data encoded:", executeData)
|
|
289
|
+
|
|
290
|
+
// Validate intent address is not zeroAddress
|
|
291
|
+
if (!intentAddress || Address.isEqual(intentAddress, zeroAddress)) {
|
|
292
|
+
throw new Error("Intent address cannot be zero address")
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (intentAddressDeployed) {
|
|
296
|
+
logger.console.log(
|
|
297
|
+
"[refund] Intent address is deployed, returning direct transaction:",
|
|
298
|
+
{
|
|
299
|
+
to: intentAddress,
|
|
300
|
+
data: executeData,
|
|
301
|
+
},
|
|
302
|
+
)
|
|
303
|
+
return { to: intentAddress, data: executeData }
|
|
304
|
+
} else {
|
|
305
|
+
logger.console.log(
|
|
306
|
+
"[refund] Intent address not deployed, using ERC-6492 deploy",
|
|
307
|
+
)
|
|
308
|
+
const deploy = Erc6492.deploy(imageHash, context)
|
|
309
|
+
logger.console.log("[refund] ERC-6492 deploy created:", {
|
|
310
|
+
to: deploy.to,
|
|
311
|
+
dataLength: deploy.data.length,
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
// Validate deploy.to and intentAddress are not zeroAddress
|
|
315
|
+
if (!deploy.to || Address.isEqual(deploy.to, zeroAddress)) {
|
|
316
|
+
throw new Error("Deploy 'to' address cannot be zero address")
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const deployPayload = {
|
|
320
|
+
type: "call" as const,
|
|
321
|
+
space: 0n,
|
|
322
|
+
nonce: 0n,
|
|
323
|
+
calls: [
|
|
324
|
+
{
|
|
325
|
+
to: deploy.to,
|
|
326
|
+
value: 0n,
|
|
327
|
+
data: Hex.fromBytes(deploy.data),
|
|
328
|
+
gasLimit: 0n,
|
|
329
|
+
delegateCall: false,
|
|
330
|
+
onlyFallback: false,
|
|
331
|
+
behaviorOnError: "revert" as const,
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
to: intentAddress,
|
|
335
|
+
value: 0n,
|
|
336
|
+
data: executeData,
|
|
337
|
+
gasLimit: 0n,
|
|
338
|
+
delegateCall: false,
|
|
339
|
+
onlyFallback: false,
|
|
340
|
+
behaviorOnError: "revert" as const,
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
}
|
|
344
|
+
logger.console.log("[refund] Deploy payload created:", {
|
|
345
|
+
callsCount: deployPayload.calls.length,
|
|
346
|
+
calls: deployPayload.calls.map((c) => ({
|
|
347
|
+
to: c.to,
|
|
348
|
+
value: c.value.toString(),
|
|
349
|
+
dataLength: c.data.length,
|
|
350
|
+
gasLimit: c.gasLimit.toString(),
|
|
351
|
+
})),
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
const encodedDeployPayload = Bytes.toHex(Payload.encode(deployPayload))
|
|
355
|
+
logger.console.log("[refund] Encoded deploy payload:", encodedDeployPayload)
|
|
356
|
+
|
|
357
|
+
const result = {
|
|
358
|
+
to: context.guestModule as `0x${string}`,
|
|
359
|
+
data: encodedDeployPayload,
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Validate final transaction "to" address is not zeroAddress
|
|
363
|
+
if (!result.to || Address.isEqual(result.to, zeroAddress)) {
|
|
364
|
+
throw new Error("Transaction 'to' address cannot be zero address")
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
logger.console.log("[refund] Returning ERC-6492 transaction:", {
|
|
368
|
+
to: result.to,
|
|
369
|
+
dataLength: result.data.length,
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
return result
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Builds a refund transaction for a given intent configuration
|
|
378
|
+
* @param params - Object containing:
|
|
379
|
+
* - mainSigner: The main signer address
|
|
380
|
+
* - calls: Array of intent calls used to create the original intent configuration
|
|
381
|
+
* - refundCall: The refund call to execute
|
|
382
|
+
* - chainId: The chain ID where the refund transaction will be executed
|
|
383
|
+
* - intentAddress: The intent address from the API response
|
|
384
|
+
* - walletClient: The wallet client to sign the payload hash
|
|
385
|
+
* @returns Promise resolving to transaction object with `to` address and `data` hex string
|
|
386
|
+
*/
|
|
387
|
+
export async function buildRefundTransaction(
|
|
388
|
+
params: BuildRefundTransactionParams,
|
|
389
|
+
): Promise<{ to: `0x${string}`; data: Hex.Hex }> {
|
|
390
|
+
const {
|
|
391
|
+
mainSigner,
|
|
392
|
+
calls,
|
|
393
|
+
refundCall,
|
|
394
|
+
chainId,
|
|
395
|
+
intentAddress,
|
|
396
|
+
walletClient,
|
|
397
|
+
} = params
|
|
398
|
+
|
|
399
|
+
logger.console.log("[refund] buildRefundTransaction called with params:", {
|
|
400
|
+
mainSigner,
|
|
401
|
+
callsCount: calls.length,
|
|
402
|
+
refundCall: {
|
|
403
|
+
to: refundCall.to,
|
|
404
|
+
value: refundCall.value.toString(),
|
|
405
|
+
data: refundCall.data,
|
|
406
|
+
gasLimit: refundCall.gasLimit.toString(),
|
|
407
|
+
},
|
|
408
|
+
chainId,
|
|
409
|
+
intentAddress,
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
// Create payload with random space and calculate hash for signing
|
|
413
|
+
const payload = createRefundPayload(refundCall, BigInt(calls[0]?.nonce ?? 0))
|
|
414
|
+
|
|
415
|
+
// Sign the payload hash
|
|
416
|
+
const signature = await signPayload(
|
|
417
|
+
walletClient,
|
|
418
|
+
intentAddress,
|
|
419
|
+
chainId,
|
|
420
|
+
payload,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
// Build transaction with signature using the payload that was signed
|
|
424
|
+
return buildRefundTransactionWithSignature({
|
|
425
|
+
mainSigner,
|
|
426
|
+
calls,
|
|
427
|
+
payload,
|
|
428
|
+
chainId,
|
|
429
|
+
intentAddress,
|
|
430
|
+
signature,
|
|
431
|
+
})
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Determines the refund call based on token balances at the intent address
|
|
436
|
+
*/
|
|
437
|
+
export function determineRefundCall(
|
|
438
|
+
tokenBalancesData:
|
|
439
|
+
| {
|
|
440
|
+
nativeBalances?: Array<{ balance?: string; chainId?: number }>
|
|
441
|
+
balances?: Array<{
|
|
442
|
+
balance?: string
|
|
443
|
+
contractAddress?: string
|
|
444
|
+
contractInfo?: { decimals?: number; chainId?: number }
|
|
445
|
+
}>
|
|
446
|
+
}
|
|
447
|
+
| undefined,
|
|
448
|
+
refundToAddress: `0x${string}`,
|
|
449
|
+
): WalletPayload.Call {
|
|
450
|
+
// Validate refund address is not zeroAddress
|
|
451
|
+
if (!refundToAddress || Address.isEqual(refundToAddress, zeroAddress)) {
|
|
452
|
+
throw new Error("Refund address cannot be zero address")
|
|
453
|
+
}
|
|
454
|
+
// Find the token with the highest balance
|
|
455
|
+
let highestBalanceToken: {
|
|
456
|
+
type: "native" | "erc20"
|
|
457
|
+
balance: bigint
|
|
458
|
+
tokenAddress?: string
|
|
459
|
+
decimals?: number
|
|
460
|
+
chainId?: number
|
|
461
|
+
} | null = null
|
|
462
|
+
|
|
463
|
+
// Check native token balances
|
|
464
|
+
if (tokenBalancesData?.nativeBalances) {
|
|
465
|
+
for (const nativeBalance of tokenBalancesData.nativeBalances) {
|
|
466
|
+
const balance = BigInt(nativeBalance.balance || "0")
|
|
467
|
+
if (balance > 0n) {
|
|
468
|
+
if (!highestBalanceToken || balance > highestBalanceToken.balance) {
|
|
469
|
+
highestBalanceToken = {
|
|
470
|
+
type: "native",
|
|
471
|
+
balance,
|
|
472
|
+
chainId: nativeBalance.chainId,
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Check ERC20 token balances
|
|
480
|
+
if (tokenBalancesData?.balances) {
|
|
481
|
+
for (const tokenBalance of tokenBalancesData.balances) {
|
|
482
|
+
const balance = BigInt(tokenBalance.balance || "0")
|
|
483
|
+
if (balance > 0n) {
|
|
484
|
+
if (!highestBalanceToken || balance > highestBalanceToken.balance) {
|
|
485
|
+
highestBalanceToken = {
|
|
486
|
+
type: "erc20",
|
|
487
|
+
balance,
|
|
488
|
+
tokenAddress: tokenBalance.contractAddress,
|
|
489
|
+
decimals: tokenBalance.contractInfo?.decimals,
|
|
490
|
+
chainId: tokenBalance.contractInfo?.chainId,
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (!highestBalanceToken) {
|
|
498
|
+
logger.console.log(
|
|
499
|
+
"[refund] No tokens found at intent address, creating empty refund call",
|
|
500
|
+
)
|
|
501
|
+
return {
|
|
502
|
+
to: refundToAddress,
|
|
503
|
+
value: 0n,
|
|
504
|
+
data: "0x" as `0x${string}`,
|
|
505
|
+
gasLimit: 0n,
|
|
506
|
+
delegateCall: false,
|
|
507
|
+
onlyFallback: false,
|
|
508
|
+
behaviorOnError: "revert",
|
|
509
|
+
}
|
|
510
|
+
} else if (highestBalanceToken.type === "native") {
|
|
511
|
+
logger.console.log("[refund] Creating native token refund call:", {
|
|
512
|
+
to: refundToAddress,
|
|
513
|
+
value: highestBalanceToken.balance.toString(),
|
|
514
|
+
balance: highestBalanceToken.balance.toString(),
|
|
515
|
+
chainId: highestBalanceToken.chainId,
|
|
516
|
+
})
|
|
517
|
+
return {
|
|
518
|
+
to: refundToAddress,
|
|
519
|
+
value: highestBalanceToken.balance,
|
|
520
|
+
data: "0x" as `0x${string}`,
|
|
521
|
+
gasLimit: 0n,
|
|
522
|
+
delegateCall: false,
|
|
523
|
+
onlyFallback: false,
|
|
524
|
+
behaviorOnError: "revert",
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
// ERC20 token refund
|
|
528
|
+
const transferData = getERC20TransferData({
|
|
529
|
+
recipient: refundToAddress,
|
|
530
|
+
amount: highestBalanceToken.balance,
|
|
531
|
+
})
|
|
532
|
+
logger.console.log("[refund] Creating ERC20 token refund call:", {
|
|
533
|
+
to: highestBalanceToken.tokenAddress,
|
|
534
|
+
value: "0",
|
|
535
|
+
data: transferData,
|
|
536
|
+
balance: highestBalanceToken.balance.toString(),
|
|
537
|
+
decimals: highestBalanceToken.decimals,
|
|
538
|
+
chainId: highestBalanceToken.chainId,
|
|
539
|
+
recipient: refundToAddress,
|
|
540
|
+
})
|
|
541
|
+
// Validate token address is not zeroAddress
|
|
542
|
+
if (
|
|
543
|
+
!highestBalanceToken.tokenAddress ||
|
|
544
|
+
Address.isEqual(
|
|
545
|
+
Address.from(highestBalanceToken.tokenAddress),
|
|
546
|
+
zeroAddress,
|
|
547
|
+
)
|
|
548
|
+
) {
|
|
549
|
+
throw new Error("ERC20 token address cannot be zero address")
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return {
|
|
553
|
+
to: Address.from(highestBalanceToken.tokenAddress),
|
|
554
|
+
value: 0n,
|
|
555
|
+
data: transferData as `0x${string}`,
|
|
556
|
+
gasLimit: 0n,
|
|
557
|
+
delegateCall: false,
|
|
558
|
+
onlyFallback: false,
|
|
559
|
+
behaviorOnError: "revert",
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export interface UseTrailsRefundParams {
|
|
565
|
+
intentId: string | undefined
|
|
566
|
+
walletClient: WalletClient | undefined
|
|
567
|
+
refundToAddress?: `0x${string}`
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export interface UseTrailsRefundReturn {
|
|
571
|
+
intent: any | null
|
|
572
|
+
isLoadingIntent: boolean
|
|
573
|
+
isLoadingBalances: boolean
|
|
574
|
+
intentError: Error | null
|
|
575
|
+
balancesError: Error | null
|
|
576
|
+
hasIntentBalance: boolean
|
|
577
|
+
refetchIntent: () => void
|
|
578
|
+
signPayload: () => Promise<{
|
|
579
|
+
signature: string
|
|
580
|
+
payload: Payload.Calls
|
|
581
|
+
refundCall: Payload.Call
|
|
582
|
+
}>
|
|
583
|
+
getRefundTx: (params: {
|
|
584
|
+
signedHash: string
|
|
585
|
+
payload: Payload.Calls
|
|
586
|
+
}) => Promise<{
|
|
587
|
+
to: `0x${string}`
|
|
588
|
+
data: `0x${string}`
|
|
589
|
+
chainId: number
|
|
590
|
+
chain: Chain
|
|
591
|
+
}>
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Hook for building and signing refund transactions
|
|
596
|
+
* @example
|
|
597
|
+
* ```tsx
|
|
598
|
+
* const { signPayload, getRefundTx } = useTrailsRefund({
|
|
599
|
+
* intentId: "0x...",
|
|
600
|
+
* walletClient: walletClient,
|
|
601
|
+
* })
|
|
602
|
+
*
|
|
603
|
+
* const signedHash = await signRefund()
|
|
604
|
+
* const refundTx = await getRefundTx({ signedHash })
|
|
605
|
+
* await walletClient.sendTransaction(refundTx)
|
|
606
|
+
* ```
|
|
607
|
+
*/
|
|
608
|
+
export function useTrailsRefund({
|
|
609
|
+
intentId,
|
|
610
|
+
walletClient,
|
|
611
|
+
refundToAddress,
|
|
612
|
+
}: UseTrailsRefundParams): UseTrailsRefundReturn {
|
|
613
|
+
const {
|
|
614
|
+
intent: intentData,
|
|
615
|
+
isLoading: isLoadingIntent,
|
|
616
|
+
error: intentError,
|
|
617
|
+
refetch: refetchIntent,
|
|
618
|
+
} = useGetIntent({
|
|
619
|
+
intentId,
|
|
620
|
+
enabled: !!intentId,
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
// Get token balances for the intent address
|
|
624
|
+
const intentAddressForBalances = useMemo<Address.Address | null>(() => {
|
|
625
|
+
if (!intentData?.originIntentAddress) return null
|
|
626
|
+
return Address.from(intentData.originIntentAddress)
|
|
627
|
+
}, [intentData?.originIntentAddress])
|
|
628
|
+
|
|
629
|
+
const {
|
|
630
|
+
tokenBalancesData,
|
|
631
|
+
isLoadingBalances,
|
|
632
|
+
balanceError: balancesError,
|
|
633
|
+
} = useTokenBalances(intentAddressForBalances)
|
|
634
|
+
|
|
635
|
+
// Check if intent address has any balance (native or ERC20)
|
|
636
|
+
const hasIntentBalance = useMemo<boolean>(() => {
|
|
637
|
+
if (!tokenBalancesData) return false
|
|
638
|
+
|
|
639
|
+
// Check native token balances
|
|
640
|
+
if (tokenBalancesData.nativeBalances) {
|
|
641
|
+
for (const nativeBalance of tokenBalancesData.nativeBalances) {
|
|
642
|
+
const balance = BigInt(nativeBalance.balance || "0")
|
|
643
|
+
if (balance > 0n) {
|
|
644
|
+
return true
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Check ERC20 token balances
|
|
650
|
+
if (tokenBalancesData.balances) {
|
|
651
|
+
for (const tokenBalance of tokenBalancesData.balances) {
|
|
652
|
+
const balance = BigInt(tokenBalance.balance || "0")
|
|
653
|
+
if (balance > 0n) {
|
|
654
|
+
return true
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return false
|
|
660
|
+
}, [tokenBalancesData])
|
|
661
|
+
|
|
662
|
+
// Get refund address (use walletClient account or provided address)
|
|
663
|
+
// Don't throw during render - validate when methods are called
|
|
664
|
+
const effectiveRefundAddress = useMemo<`0x${string}` | null>(() => {
|
|
665
|
+
const address =
|
|
666
|
+
refundToAddress || (walletClient?.account?.address as `0x${string}`)
|
|
667
|
+
if (!address || Address.isEqual(address, zeroAddress)) {
|
|
668
|
+
return null // Will be validated when used
|
|
669
|
+
}
|
|
670
|
+
return address
|
|
671
|
+
}, [refundToAddress, walletClient?.account?.address])
|
|
672
|
+
|
|
673
|
+
const signPayloadCallback = useCallback(async (): Promise<{
|
|
674
|
+
signature: string
|
|
675
|
+
payload: Payload.Calls
|
|
676
|
+
refundCall: Payload.Call
|
|
677
|
+
}> => {
|
|
678
|
+
if (!intentData || !walletClient || !walletClient.account) {
|
|
679
|
+
throw new Error("Intent data and wallet client with account required")
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Validate refund address
|
|
683
|
+
if (!effectiveRefundAddress) {
|
|
684
|
+
throw new Error(
|
|
685
|
+
"Refund address is required. Provide refundToAddress or connect a wallet with a valid address",
|
|
686
|
+
)
|
|
687
|
+
}
|
|
688
|
+
if (Address.isEqual(effectiveRefundAddress, zeroAddress)) {
|
|
689
|
+
throw new Error("Refund address cannot be zero address")
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Build calls array
|
|
693
|
+
const calls: IntentCalls[] = []
|
|
694
|
+
if (intentData.originCalls) {
|
|
695
|
+
calls.push({
|
|
696
|
+
chainId: intentData.originCalls.chainId,
|
|
697
|
+
space: intentData.originCalls.space,
|
|
698
|
+
nonce: intentData.originCalls.nonce,
|
|
699
|
+
calls: intentData.originCalls.calls.map((call: any) => ({
|
|
700
|
+
to: call.to,
|
|
701
|
+
value: call.value,
|
|
702
|
+
data: call.data,
|
|
703
|
+
gasLimit: call.gasLimit,
|
|
704
|
+
delegateCall: call.delegateCall,
|
|
705
|
+
onlyFallback: call.onlyFallback,
|
|
706
|
+
behaviorOnError: call.behaviorOnError,
|
|
707
|
+
})),
|
|
708
|
+
})
|
|
709
|
+
}
|
|
710
|
+
if (intentData.destinationCalls) {
|
|
711
|
+
calls.push({
|
|
712
|
+
chainId: intentData.destinationCalls.chainId,
|
|
713
|
+
space: intentData.destinationCalls.space,
|
|
714
|
+
nonce: intentData.destinationCalls.nonce,
|
|
715
|
+
calls: intentData.destinationCalls.calls.map((call: any) => ({
|
|
716
|
+
to: call.to,
|
|
717
|
+
value: call.value,
|
|
718
|
+
data: call.data,
|
|
719
|
+
gasLimit: call.gasLimit,
|
|
720
|
+
delegateCall: call.delegateCall,
|
|
721
|
+
onlyFallback: call.onlyFallback,
|
|
722
|
+
behaviorOnError: call.behaviorOnError,
|
|
723
|
+
})),
|
|
724
|
+
})
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Determine refund call from token balances
|
|
728
|
+
const refundCall = determineRefundCall(
|
|
729
|
+
tokenBalancesData,
|
|
730
|
+
effectiveRefundAddress,
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
// Get chain ID from origin calls (this is the chain where the refund should execute)
|
|
734
|
+
const chainId = intentData.originCalls?.chainId
|
|
735
|
+
if (!chainId) {
|
|
736
|
+
throw new Error("Chain ID not found in origin calls")
|
|
737
|
+
}
|
|
738
|
+
logger.console.log("[refund] Using chain ID from origin calls:", chainId)
|
|
739
|
+
|
|
740
|
+
// Switch to the correct chain before signing
|
|
741
|
+
if (walletClient) {
|
|
742
|
+
try {
|
|
743
|
+
await attemptSwitchChain({
|
|
744
|
+
walletClient,
|
|
745
|
+
desiredChainId: Number(chainId),
|
|
746
|
+
})
|
|
747
|
+
logger.console.log("[refund] Successfully switched to chain:", chainId)
|
|
748
|
+
} catch (error) {
|
|
749
|
+
logger.console.error(
|
|
750
|
+
"[refund] Failed to switch chain before signing:",
|
|
751
|
+
error,
|
|
752
|
+
)
|
|
753
|
+
throw new Error(
|
|
754
|
+
`Failed to switch to chain ${chainId}: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
755
|
+
)
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Get intent address (needed for buildRefundTransactionWithSignature)
|
|
760
|
+
if (!intentData.originIntentAddress) {
|
|
761
|
+
throw new Error("Intent data missing originIntentAddress")
|
|
762
|
+
}
|
|
763
|
+
const intentAddress = Address.from(intentData.originIntentAddress)
|
|
764
|
+
|
|
765
|
+
// Create payload with random space and calculate hash for signing
|
|
766
|
+
const payload = createRefundPayload(
|
|
767
|
+
refundCall,
|
|
768
|
+
BigInt(calls[0]?.nonce ?? 0),
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
// Sign the hash - call the function directly to avoid circular import
|
|
772
|
+
if (!walletClient.account) {
|
|
773
|
+
throw new Error("Wallet client account is required for signing")
|
|
774
|
+
}
|
|
775
|
+
const typedData = Payload.toTyped(intentAddress, chainId, payload)
|
|
776
|
+
const signature = await walletClient.signTypedData({
|
|
777
|
+
account: walletClient.account,
|
|
778
|
+
...typedData,
|
|
779
|
+
})
|
|
780
|
+
logger.console.log("[refund] Signature received:", signature)
|
|
781
|
+
return {
|
|
782
|
+
signature,
|
|
783
|
+
payload,
|
|
784
|
+
refundCall,
|
|
785
|
+
}
|
|
786
|
+
}, [intentData, walletClient, tokenBalancesData, effectiveRefundAddress])
|
|
787
|
+
|
|
788
|
+
// Export as signPayload for the hook return
|
|
789
|
+
const signPayload = signPayloadCallback
|
|
790
|
+
|
|
791
|
+
const getRefundTx = useCallback(
|
|
792
|
+
async (params: {
|
|
793
|
+
signedHash: string
|
|
794
|
+
payload: Payload.Calls
|
|
795
|
+
}): Promise<{
|
|
796
|
+
to: `0x${string}`
|
|
797
|
+
data: `0x${string}`
|
|
798
|
+
chainId: number
|
|
799
|
+
chain: Chain
|
|
800
|
+
}> => {
|
|
801
|
+
if (!intentData || !walletClient?.account) {
|
|
802
|
+
throw new Error("Intent data and wallet client with account required")
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Validate refund address
|
|
806
|
+
if (!effectiveRefundAddress) {
|
|
807
|
+
throw new Error(
|
|
808
|
+
"Refund address is required. Provide refundToAddress or connect a wallet with a valid address",
|
|
809
|
+
)
|
|
810
|
+
}
|
|
811
|
+
if (Address.isEqual(effectiveRefundAddress, zeroAddress)) {
|
|
812
|
+
throw new Error("Refund address cannot be zero address")
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Build calls array (needed for intent configuration)
|
|
816
|
+
const calls: IntentCalls[] = []
|
|
817
|
+
if (intentData.originCalls) {
|
|
818
|
+
calls.push({
|
|
819
|
+
chainId: intentData.originCalls.chainId,
|
|
820
|
+
space: intentData.originCalls.space,
|
|
821
|
+
nonce: intentData.originCalls.nonce,
|
|
822
|
+
calls: intentData.originCalls.calls.map((call: any) => ({
|
|
823
|
+
to: call.to,
|
|
824
|
+
value: call.value,
|
|
825
|
+
data: call.data,
|
|
826
|
+
gasLimit: call.gasLimit,
|
|
827
|
+
delegateCall: call.delegateCall,
|
|
828
|
+
onlyFallback: call.onlyFallback,
|
|
829
|
+
behaviorOnError: call.behaviorOnError,
|
|
830
|
+
})),
|
|
831
|
+
})
|
|
832
|
+
}
|
|
833
|
+
if (intentData.destinationCalls) {
|
|
834
|
+
calls.push({
|
|
835
|
+
chainId: intentData.destinationCalls.chainId,
|
|
836
|
+
space: intentData.destinationCalls.space,
|
|
837
|
+
nonce: intentData.destinationCalls.nonce,
|
|
838
|
+
calls: intentData.destinationCalls.calls.map((call: any) => ({
|
|
839
|
+
to: call.to,
|
|
840
|
+
value: call.value,
|
|
841
|
+
data: call.data,
|
|
842
|
+
gasLimit: call.gasLimit,
|
|
843
|
+
delegateCall: call.delegateCall,
|
|
844
|
+
onlyFallback: call.onlyFallback,
|
|
845
|
+
behaviorOnError: call.behaviorOnError,
|
|
846
|
+
})),
|
|
847
|
+
})
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Use the refund call from the payload (passed from signPayload)
|
|
851
|
+
const refundCall = params.payload.calls[0]
|
|
852
|
+
if (!refundCall) {
|
|
853
|
+
throw new Error("Refund call not found in payload")
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Get chain ID from origin calls (this is the chain where the refund should execute)
|
|
857
|
+
const chainId = intentData.originCalls?.chainId
|
|
858
|
+
if (!chainId) {
|
|
859
|
+
throw new Error("Chain ID not found in origin calls")
|
|
860
|
+
}
|
|
861
|
+
logger.console.log("[refund] Using chain ID from origin calls:", chainId)
|
|
862
|
+
|
|
863
|
+
// Get intent address
|
|
864
|
+
if (!intentData.originIntentAddress) {
|
|
865
|
+
throw new Error("Intent data missing originIntentAddress")
|
|
866
|
+
}
|
|
867
|
+
const intentAddress = Address.from(intentData.originIntentAddress)
|
|
868
|
+
|
|
869
|
+
// Build transaction with signature using the payload passed from signPayload
|
|
870
|
+
const mainSigner = walletClient.account.address as Address.Address
|
|
871
|
+
const result = await buildRefundTransactionWithSignature({
|
|
872
|
+
mainSigner,
|
|
873
|
+
calls,
|
|
874
|
+
payload: params.payload,
|
|
875
|
+
chainId: Number(chainId),
|
|
876
|
+
intentAddress,
|
|
877
|
+
signature: params.signedHash,
|
|
878
|
+
})
|
|
879
|
+
|
|
880
|
+
// Final validation: ensure result "to" address is not zeroAddress
|
|
881
|
+
if (!result.to || Address.isEqual(result.to, zeroAddress)) {
|
|
882
|
+
throw new Error(
|
|
883
|
+
"Refund transaction 'to' address cannot be zero address",
|
|
884
|
+
)
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Get chain info for the transaction
|
|
888
|
+
const chainInfo = getChainInfo(Number(chainId))
|
|
889
|
+
if (!chainInfo) {
|
|
890
|
+
throw new Error(`Chain info not found for chain ID: ${chainId}`)
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return {
|
|
894
|
+
to: result.to,
|
|
895
|
+
data: result.data,
|
|
896
|
+
chainId: Number(chainId),
|
|
897
|
+
chain: chainInfo as Chain,
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
[intentData, walletClient, effectiveRefundAddress],
|
|
901
|
+
)
|
|
902
|
+
|
|
903
|
+
return {
|
|
904
|
+
intent: intentData,
|
|
905
|
+
isLoadingIntent,
|
|
906
|
+
isLoadingBalances,
|
|
907
|
+
intentError: intentError as Error | null,
|
|
908
|
+
balancesError: balancesError as Error | null,
|
|
909
|
+
hasIntentBalance,
|
|
910
|
+
refetchIntent,
|
|
911
|
+
signPayload,
|
|
912
|
+
getRefundTx,
|
|
913
|
+
}
|
|
914
|
+
}
|