@lifi/sdk 3.10.1 → 3.11.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.
Files changed (89) hide show
  1. package/package.json +6 -6
  2. package/src/_cjs/core/EVM/EVMStepExecutor.js +122 -131
  3. package/src/_cjs/core/EVM/EVMStepExecutor.js.map +1 -1
  4. package/src/_cjs/core/EVM/abi.js +1 -0
  5. package/src/_cjs/core/EVM/abi.js.map +1 -1
  6. package/src/_cjs/core/EVM/checkAllowance.js +126 -44
  7. package/src/_cjs/core/EVM/checkAllowance.js.map +1 -1
  8. package/src/_cjs/core/EVM/permits/getNativePermit.js +123 -16
  9. package/src/_cjs/core/EVM/permits/getNativePermit.js.map +1 -1
  10. package/src/_cjs/core/EVM/permits/isNativePermitValid.js +34 -0
  11. package/src/_cjs/core/EVM/permits/isNativePermitValid.js.map +1 -0
  12. package/src/_cjs/core/EVM/switchChain.js +8 -14
  13. package/src/_cjs/core/EVM/switchChain.js.map +1 -1
  14. package/src/_cjs/core/EVM/utils.js +10 -1
  15. package/src/_cjs/core/EVM/utils.js.map +1 -1
  16. package/src/_cjs/core/execution.js +1 -1
  17. package/src/_cjs/core/execution.js.map +1 -1
  18. package/src/_cjs/core/prepareRestart.js +5 -2
  19. package/src/_cjs/core/prepareRestart.js.map +1 -1
  20. package/src/_cjs/core/processMessages.js +4 -8
  21. package/src/_cjs/core/processMessages.js.map +1 -1
  22. package/src/_cjs/services/api.js +6 -5
  23. package/src/_cjs/services/api.js.map +1 -1
  24. package/src/_cjs/services/balance.js +9 -9
  25. package/src/_cjs/services/balance.js.map +1 -1
  26. package/src/_cjs/version.js +1 -1
  27. package/src/_esm/core/EVM/EVMStepExecutor.js +136 -148
  28. package/src/_esm/core/EVM/EVMStepExecutor.js.map +1 -1
  29. package/src/_esm/core/EVM/abi.js +2 -0
  30. package/src/_esm/core/EVM/abi.js.map +1 -1
  31. package/src/_esm/core/EVM/checkAllowance.js +141 -45
  32. package/src/_esm/core/EVM/checkAllowance.js.map +1 -1
  33. package/src/_esm/core/EVM/permits/getNativePermit.js +144 -21
  34. package/src/_esm/core/EVM/permits/getNativePermit.js.map +1 -1
  35. package/src/_esm/core/EVM/permits/isNativePermitValid.js +41 -0
  36. package/src/_esm/core/EVM/permits/isNativePermitValid.js.map +1 -0
  37. package/src/_esm/core/EVM/switchChain.js +8 -15
  38. package/src/_esm/core/EVM/switchChain.js.map +1 -1
  39. package/src/_esm/core/EVM/utils.js +12 -0
  40. package/src/_esm/core/EVM/utils.js.map +1 -1
  41. package/src/_esm/core/execution.js +1 -1
  42. package/src/_esm/core/execution.js.map +1 -1
  43. package/src/_esm/core/prepareRestart.js +6 -3
  44. package/src/_esm/core/prepareRestart.js.map +1 -1
  45. package/src/_esm/core/processMessages.js +4 -8
  46. package/src/_esm/core/processMessages.js.map +1 -1
  47. package/src/_esm/services/api.js +3 -8
  48. package/src/_esm/services/api.js.map +1 -1
  49. package/src/_esm/services/balance.js +4 -18
  50. package/src/_esm/services/balance.js.map +1 -1
  51. package/src/_esm/version.js +1 -1
  52. package/src/_types/core/EVM/EVMStepExecutor.d.ts +3 -2
  53. package/src/_types/core/EVM/EVMStepExecutor.d.ts.map +1 -1
  54. package/src/_types/core/EVM/abi.d.ts +20 -0
  55. package/src/_types/core/EVM/abi.d.ts.map +1 -1
  56. package/src/_types/core/EVM/checkAllowance.d.ts +10 -7
  57. package/src/_types/core/EVM/checkAllowance.d.ts.map +1 -1
  58. package/src/_types/core/EVM/permits/getNativePermit.d.ts +2 -2
  59. package/src/_types/core/EVM/permits/getNativePermit.d.ts.map +1 -1
  60. package/src/_types/core/EVM/permits/isNativePermitValid.d.ts +6 -0
  61. package/src/_types/core/EVM/permits/isNativePermitValid.d.ts.map +1 -0
  62. package/src/_types/core/EVM/switchChain.d.ts +2 -2
  63. package/src/_types/core/EVM/switchChain.d.ts.map +1 -1
  64. package/src/_types/core/EVM/utils.d.ts +6 -1
  65. package/src/_types/core/EVM/utils.d.ts.map +1 -1
  66. package/src/_types/core/prepareRestart.d.ts +1 -1
  67. package/src/_types/core/prepareRestart.d.ts.map +1 -1
  68. package/src/_types/core/processMessages.d.ts.map +1 -1
  69. package/src/_types/core/types.d.ts +2 -2
  70. package/src/_types/core/types.d.ts.map +1 -1
  71. package/src/_types/services/api.d.ts +7 -2
  72. package/src/_types/services/api.d.ts.map +1 -1
  73. package/src/_types/services/balance.d.ts +5 -5
  74. package/src/_types/services/balance.d.ts.map +1 -1
  75. package/src/_types/version.d.ts +1 -1
  76. package/src/core/EVM/EVMStepExecutor.ts +203 -197
  77. package/src/core/EVM/abi.ts +2 -0
  78. package/src/core/EVM/checkAllowance.ts +206 -63
  79. package/src/core/EVM/permits/getNativePermit.ts +189 -22
  80. package/src/core/EVM/permits/isNativePermitValid.ts +57 -0
  81. package/src/core/EVM/switchChain.ts +14 -22
  82. package/src/core/EVM/utils.ts +17 -1
  83. package/src/core/execution.ts +1 -1
  84. package/src/core/prepareRestart.ts +6 -3
  85. package/src/core/processMessages.ts +4 -8
  86. package/src/core/types.ts +1 -2
  87. package/src/services/api.ts +17 -8
  88. package/src/services/balance.ts +20 -8
  89. package/src/version.ts +1 -1
@@ -4,19 +4,28 @@ import { signTypedData } from 'viem/actions'
4
4
  import { getAction } from 'viem/utils'
5
5
  import { MaxUint256 } from '../../constants.js'
6
6
  import type { StatusManager } from '../StatusManager.js'
7
- import type { ExecutionOptions, Process, ProcessType } from '../types.js'
7
+ import type {
8
+ ExecutionOptions,
9
+ LiFiStepExtended,
10
+ Process,
11
+ ProcessType,
12
+ } from '../types.js'
8
13
  import { getActionWithFallback } from './getActionWithFallback.js'
9
14
  import { getAllowance } from './getAllowance.js'
10
15
  import { parseEVMErrors } from './parseEVMErrors.js'
11
16
  import { getNativePermit } from './permits/getNativePermit.js'
17
+ import { isNativePermitValid } from './permits/isNativePermitValid.js'
12
18
  import type { NativePermitData } from './permits/types.js'
13
19
  import { setAllowance } from './setAllowance.js'
14
- import { isRelayerStep } from './typeguards.js'
15
20
  import type { Call } from './types.js'
16
21
  import { waitForTransactionReceipt } from './waitForTransactionReceipt.js'
17
22
 
18
23
  export type CheckAllowanceParams = {
19
- client: Client
24
+ checkClient(
25
+ step: LiFiStepExtended,
26
+ process: Process,
27
+ targetChainId?: number
28
+ ): Promise<Client | undefined>
20
29
  chain: ExtendedChain
21
30
  step: LiFiStep
22
31
  statusManager: StatusManager
@@ -29,19 +38,19 @@ export type CheckAllowanceParams = {
29
38
 
30
39
  export type AllowanceResult =
31
40
  | {
32
- status: 'ACTION_REQUIRED' | 'DONE'
41
+ status: 'ACTION_REQUIRED'
33
42
  }
34
43
  | {
35
44
  status: 'BATCH_APPROVAL'
36
- data: Call
45
+ data: { call: Call; signedTypedData: SignedTypedData[] }
37
46
  }
38
47
  | {
39
- status: 'NATIVE_PERMIT'
40
- data: SignedTypedData
48
+ status: 'NATIVE_PERMIT' | 'DONE'
49
+ data: SignedTypedData[]
41
50
  }
42
51
 
43
52
  export const checkAllowance = async ({
44
- client,
53
+ checkClient,
45
54
  chain,
46
55
  step,
47
56
  statusManager,
@@ -51,29 +60,123 @@ export const checkAllowance = async ({
51
60
  permit2Supported = false,
52
61
  disableMessageSigning = false,
53
62
  }: CheckAllowanceParams): Promise<AllowanceResult> => {
54
- // Find existing or create new allowance process
55
- const allowanceProcess: Process = statusManager.findOrCreateProcess({
56
- step,
57
- type: 'TOKEN_ALLOWANCE',
58
- chainId: step.action.fromChainId,
59
- })
60
-
63
+ let sharedProcess: Process | undefined
64
+ let signedTypedData: SignedTypedData[] = []
61
65
  try {
66
+ // First, try to sign all permits in step.typedData
67
+ const permitTypedData = step.typedData?.filter(
68
+ (typedData) => typedData.primaryType === 'Permit'
69
+ )
70
+ if (!disableMessageSigning && permitTypedData?.length) {
71
+ sharedProcess = statusManager.findOrCreateProcess({
72
+ step,
73
+ type: 'PERMIT',
74
+ chainId: step.action.fromChainId,
75
+ })
76
+ signedTypedData = sharedProcess.signedTypedData ?? signedTypedData
77
+ for (const typedData of permitTypedData) {
78
+ const permitChainId = typedData.domain.chainId as number
79
+
80
+ // Check if we already have a valid permit for this chain and requirements
81
+ const signedTypedDataForChain = signedTypedData.find(
82
+ (signedTypedData) => signedTypedData.domain.chainId === permitChainId
83
+ )
84
+ const existingValidPermit =
85
+ signedTypedDataForChain &&
86
+ isNativePermitValid(signedTypedDataForChain, typedData)
87
+
88
+ if (existingValidPermit) {
89
+ // Skip signing if we already have a valid permit
90
+ continue
91
+ }
92
+
93
+ sharedProcess = statusManager.updateProcess(
94
+ step,
95
+ sharedProcess.type,
96
+ 'ACTION_REQUIRED'
97
+ )
98
+ if (!allowUserInteraction) {
99
+ return { status: 'ACTION_REQUIRED' }
100
+ }
101
+
102
+ // Switch to the permit's chain if needed
103
+ const permitClient = await checkClient(
104
+ step,
105
+ sharedProcess,
106
+ permitChainId
107
+ )
108
+ if (!permitClient) {
109
+ return { status: 'ACTION_REQUIRED' }
110
+ }
111
+
112
+ const signature = await getAction(
113
+ permitClient,
114
+ signTypedData,
115
+ 'signTypedData'
116
+ )({
117
+ account: permitClient.account!,
118
+ domain: typedData.domain,
119
+ types: typedData.types,
120
+ primaryType: 'Permit',
121
+ message: typedData.message,
122
+ })
123
+ const signedPermit: SignedTypedData = {
124
+ ...typedData,
125
+ signature,
126
+ }
127
+ signedTypedData.push(signedPermit)
128
+ sharedProcess = statusManager.updateProcess(
129
+ step,
130
+ sharedProcess.type,
131
+ 'ACTION_REQUIRED',
132
+ {
133
+ signedTypedData,
134
+ }
135
+ )
136
+ }
137
+
138
+ statusManager.updateProcess(step, sharedProcess.type, 'DONE', {
139
+ signedTypedData,
140
+ })
141
+ // Check if there's a signed permit for the source transaction chain
142
+ const matchingPermit = signedTypedData.find(
143
+ (signedTypedData) => signedTypedData.domain.chainId === chain.id
144
+ )
145
+ if (matchingPermit) {
146
+ return {
147
+ status: 'NATIVE_PERMIT',
148
+ data: signedTypedData,
149
+ }
150
+ }
151
+ }
152
+
153
+ // Find existing or create new allowance process
154
+ sharedProcess = statusManager.findOrCreateProcess({
155
+ step,
156
+ type: 'TOKEN_ALLOWANCE',
157
+ chainId: step.action.fromChainId,
158
+ })
159
+
160
+ const updatedClient = await checkClient(step, sharedProcess)
161
+ if (!updatedClient) {
162
+ return { status: 'ACTION_REQUIRED' }
163
+ }
164
+
62
165
  // Handle existing pending transaction
63
- if (allowanceProcess.txHash && allowanceProcess.status !== 'DONE') {
166
+ if (sharedProcess.txHash && sharedProcess.status !== 'DONE') {
64
167
  await waitForApprovalTransaction(
65
- client,
66
- allowanceProcess.txHash as Address,
67
- allowanceProcess.type,
168
+ updatedClient,
169
+ sharedProcess.txHash as Address,
170
+ sharedProcess.type,
68
171
  step,
69
172
  chain,
70
173
  statusManager
71
174
  )
72
- return { status: 'DONE' }
175
+ return { status: 'DONE', data: signedTypedData }
73
176
  }
74
177
 
75
178
  // Start new allowance check
76
- statusManager.updateProcess(step, allowanceProcess.type, 'STARTED')
179
+ statusManager.updateProcess(step, sharedProcess.type, 'STARTED')
77
180
 
78
181
  const spenderAddress = permit2Supported
79
182
  ? chain.permit2
@@ -82,32 +185,26 @@ export const checkAllowance = async ({
82
185
  const fromAmount = BigInt(step.action.fromAmount)
83
186
 
84
187
  const approved = await getAllowance(
85
- client,
188
+ updatedClient,
86
189
  step.action.fromToken.address as Address,
87
- client.account!.address,
190
+ updatedClient.account!.address,
88
191
  spenderAddress as Address
89
192
  )
90
193
 
91
194
  // Return early if already approved
92
195
  if (fromAmount <= approved) {
93
- statusManager.updateProcess(step, allowanceProcess.type, 'DONE')
94
- return { status: 'DONE' }
196
+ statusManager.updateProcess(step, sharedProcess.type, 'DONE')
197
+ return { status: 'DONE', data: signedTypedData }
95
198
  }
96
199
 
97
- const isRelayerTransaction = isRelayerStep(step)
98
-
99
200
  // Check if proxy contract is available and message signing is not disabled, also not available for atomic batch
100
201
  const isNativePermitAvailable =
101
202
  !!chain.permit2Proxy && !batchingSupported && !disableMessageSigning
102
203
 
103
204
  let nativePermitData: NativePermitData | undefined
104
- if (isRelayerTransaction) {
105
- nativePermitData = step.typedData.find(
106
- (p) => p.primaryType === 'Permit'
107
- ) as NativePermitData
108
- } else if (isNativePermitAvailable) {
205
+ if (isNativePermitAvailable) {
109
206
  nativePermitData = await getActionWithFallback(
110
- client,
207
+ updatedClient,
111
208
  getNativePermit,
112
209
  'getNativePermit',
113
210
  {
@@ -119,70 +216,116 @@ export const checkAllowance = async ({
119
216
  )
120
217
  }
121
218
 
122
- statusManager.updateProcess(step, allowanceProcess.type, 'ACTION_REQUIRED')
219
+ if (isNativePermitAvailable && nativePermitData) {
220
+ signedTypedData = signedTypedData.length
221
+ ? signedTypedData
222
+ : sharedProcess.signedTypedData || []
223
+ // Check if we already have a valid permit for this chain and requirements
224
+ const signedTypedDataForChain = signedTypedData.find(
225
+ (signedTypedData) =>
226
+ signedTypedData.domain.chainId === nativePermitData.domain.chainId
227
+ )
228
+ const existingValidPermit =
229
+ signedTypedDataForChain &&
230
+ isNativePermitValid(signedTypedDataForChain, nativePermitData)
123
231
 
124
- if (!allowUserInteraction) {
125
- return { status: 'ACTION_REQUIRED' }
126
- }
232
+ if (!existingValidPermit) {
233
+ statusManager.updateProcess(step, sharedProcess.type, 'ACTION_REQUIRED')
127
234
 
128
- if (isNativePermitAvailable && nativePermitData) {
129
- const signature = await getAction(
130
- client,
131
- signTypedData,
132
- 'signTypedData'
133
- )({
134
- account: client.account!,
135
- domain: nativePermitData.domain,
136
- types: nativePermitData.types,
137
- primaryType: 'Permit',
138
- message: nativePermitData.message,
235
+ if (!allowUserInteraction) {
236
+ return { status: 'ACTION_REQUIRED' }
237
+ }
238
+
239
+ // Sign the permit
240
+ const signature = await getAction(
241
+ updatedClient,
242
+ signTypedData,
243
+ 'signTypedData'
244
+ )({
245
+ account: updatedClient.account!,
246
+ domain: nativePermitData.domain,
247
+ types: nativePermitData.types,
248
+ primaryType: 'Permit',
249
+ message: nativePermitData.message,
250
+ })
251
+
252
+ // Add the new permit to the signed permits array
253
+ const signedPermit: SignedTypedData = {
254
+ ...nativePermitData,
255
+ signature,
256
+ }
257
+ signedTypedData.push(signedPermit)
258
+ }
259
+
260
+ statusManager.updateProcess(step, sharedProcess.type, 'DONE', {
261
+ signedTypedData,
139
262
  })
140
- statusManager.updateProcess(step, allowanceProcess.type, 'DONE')
141
263
  return {
142
264
  status: 'NATIVE_PERMIT',
143
- data: {
144
- ...nativePermitData,
145
- signature,
146
- },
265
+ data: signedTypedData,
147
266
  }
148
267
  }
149
268
 
269
+ // Clear the txHash and txLink from potential previous approval transaction
270
+ statusManager.updateProcess(step, sharedProcess.type, 'ACTION_REQUIRED', {
271
+ txHash: undefined,
272
+ txLink: undefined,
273
+ })
274
+
275
+ if (!allowUserInteraction) {
276
+ return { status: 'ACTION_REQUIRED' }
277
+ }
278
+
150
279
  // Set new allowance
151
280
  const approveAmount = permit2Supported ? MaxUint256 : fromAmount
152
281
  const approveTxHash = await setAllowance(
153
- client,
282
+ updatedClient,
154
283
  step.action.fromToken.address as Address,
155
284
  spenderAddress as Address,
156
285
  approveAmount,
157
286
  executionOptions,
287
+ // We need to return the populated transaction is batching is supported
288
+ // instead of executing transaction on-chain
158
289
  batchingSupported
159
290
  )
160
291
 
292
+ // If batching is supported, we need to return the batch approval data
293
+ // because allowance was't set by standard approval transaction
161
294
  if (batchingSupported) {
162
- statusManager.updateProcess(step, allowanceProcess.type, 'DONE')
295
+ statusManager.updateProcess(step, sharedProcess.type, 'DONE')
163
296
  return {
164
297
  status: 'BATCH_APPROVAL',
165
298
  data: {
166
- to: step.action.fromToken.address as Address,
167
- data: approveTxHash,
168
- chainId: step.action.fromToken.chainId,
299
+ call: {
300
+ to: step.action.fromToken.address as Address,
301
+ data: approveTxHash,
302
+ chainId: step.action.fromToken.chainId,
303
+ },
304
+ signedTypedData,
169
305
  },
170
306
  }
171
307
  }
172
308
 
173
309
  await waitForApprovalTransaction(
174
- client,
310
+ updatedClient,
175
311
  approveTxHash,
176
- allowanceProcess.type,
312
+ sharedProcess.type,
177
313
  step,
178
314
  chain,
179
315
  statusManager
180
316
  )
181
317
 
182
- return { status: 'DONE' }
318
+ return { status: 'DONE', data: signedTypedData }
183
319
  } catch (e: any) {
184
- const error = await parseEVMErrors(e, step, allowanceProcess)
185
- statusManager.updateProcess(step, allowanceProcess.type, 'FAILED', {
320
+ if (!sharedProcess) {
321
+ sharedProcess = statusManager.findOrCreateProcess({
322
+ step,
323
+ type: 'TOKEN_ALLOWANCE',
324
+ chainId: step.action.fromChainId,
325
+ })
326
+ }
327
+ const error = await parseEVMErrors(e, step, sharedProcess)
328
+ statusManager.updateProcess(step, sharedProcess.type, 'FAILED', {
186
329
  error: {
187
330
  message: error.cause.message,
188
331
  code: error.code,
@@ -6,6 +6,7 @@ import {
6
6
  parseAbiParameters,
7
7
  toBytes,
8
8
  toHex,
9
+ zeroHash,
9
10
  } from 'viem'
10
11
  import { multicall, readContract } from 'viem/actions'
11
12
  import { eip2612Abi } from '../abi.js'
@@ -67,10 +68,6 @@ function makeDomainSeparator({
67
68
  return keccak256(encoded)
68
69
  }
69
70
 
70
- // TODO: Add support for EIP-5267 when adoption increases
71
- // This EIP provides a standard way to query domain separator and permit type hash
72
- // via eip712Domain() function, which would simplify permit validation
73
- // https://eips.ethereum.org/EIPS/eip-5267
74
71
  function validateDomainSeparator({
75
72
  name,
76
73
  version,
@@ -125,12 +122,168 @@ function validateDomainSeparator({
125
122
  }
126
123
  }
127
124
 
125
+ /**
126
+ * Attempts to retrieve contract data using EIP-5267 eip712Domain() function
127
+ * @link https://eips.ethereum.org/EIPS/eip-5267
128
+ * @param client - The Viem client instance
129
+ * @param chainId - The chain ID
130
+ * @param tokenAddress - The token contract address
131
+ * @returns Contract data if EIP-5267 is supported, undefined otherwise
132
+ */
133
+ const getEIP712DomainData = async (
134
+ client: Client,
135
+ chainId: number,
136
+ tokenAddress: Address
137
+ ) => {
138
+ try {
139
+ const multicallAddress = await getMulticallAddress(chainId)
140
+
141
+ const contractCalls = [
142
+ {
143
+ address: tokenAddress,
144
+ abi: eip2612Abi,
145
+ functionName: 'eip712Domain',
146
+ },
147
+ {
148
+ address: tokenAddress,
149
+ abi: eip2612Abi,
150
+ functionName: 'nonces',
151
+ args: [client.account!.address],
152
+ },
153
+ ] as const
154
+
155
+ if (multicallAddress) {
156
+ try {
157
+ const [eip712DomainResult, noncesResult] = await getActionWithFallback(
158
+ client,
159
+ multicall,
160
+ 'multicall',
161
+ {
162
+ contracts: contractCalls,
163
+ multicallAddress,
164
+ }
165
+ )
166
+
167
+ if (
168
+ eip712DomainResult.status !== 'success' ||
169
+ noncesResult.status !== 'success' ||
170
+ !eip712DomainResult.result ||
171
+ noncesResult.result === undefined
172
+ ) {
173
+ // Fall back to individual calls if multicall fails
174
+ throw new Error('EIP-5267 multicall failed')
175
+ }
176
+
177
+ const [, name, version, tokenChainId, verifyingContract, salt] =
178
+ eip712DomainResult.result
179
+
180
+ if (
181
+ Number(tokenChainId) !== chainId ||
182
+ verifyingContract.toLowerCase() !== tokenAddress.toLowerCase()
183
+ ) {
184
+ return undefined
185
+ }
186
+
187
+ // Build domain object directly from EIP-5267 data
188
+ // Use the actual salt value returned by EIP-5267 - this is the canonical salt that the contract uses
189
+ const hasSalt = salt !== zeroHash
190
+ const domain = hasSalt
191
+ ? {
192
+ name,
193
+ version,
194
+ verifyingContract: tokenAddress,
195
+ salt,
196
+ }
197
+ : {
198
+ name,
199
+ version,
200
+ chainId,
201
+ verifyingContract: tokenAddress,
202
+ }
203
+
204
+ return {
205
+ name,
206
+ version,
207
+ domain,
208
+ permitTypehash: undefined, // EIP-5267 doesn't provide permit typehash directly
209
+ nonce: noncesResult.result,
210
+ }
211
+ } catch {
212
+ // Fall through to individual calls
213
+ }
214
+ }
215
+
216
+ // Fallback to individual contract calls
217
+ const [eip712DomainResult, noncesResult] = (await Promise.allSettled(
218
+ contractCalls.map((call) =>
219
+ getActionWithFallback(client, readContract, 'readContract', call)
220
+ )
221
+ )) as [
222
+ PromiseSettledResult<
223
+ [Hex, string, string, bigint, Address, Hex, bigint[]]
224
+ >,
225
+ PromiseSettledResult<bigint>,
226
+ ]
227
+
228
+ if (
229
+ eip712DomainResult.status !== 'fulfilled' ||
230
+ noncesResult.status !== 'fulfilled'
231
+ ) {
232
+ return undefined
233
+ }
234
+
235
+ const [, name, version, tokenChainId, verifyingContract, salt] =
236
+ eip712DomainResult.value
237
+
238
+ if (
239
+ Number(tokenChainId) !== chainId ||
240
+ verifyingContract.toLowerCase() !== tokenAddress.toLowerCase()
241
+ ) {
242
+ return undefined
243
+ }
244
+
245
+ // Build domain object directly from EIP-5267 data
246
+ // Use the actual salt value returned by EIP-5267 - this is the canonical salt that the contract uses
247
+ const hasSalt = salt !== zeroHash
248
+ const domain = hasSalt
249
+ ? {
250
+ name,
251
+ version,
252
+ verifyingContract: tokenAddress,
253
+ salt,
254
+ }
255
+ : {
256
+ name,
257
+ version,
258
+ chainId,
259
+ verifyingContract: tokenAddress,
260
+ }
261
+
262
+ return {
263
+ name,
264
+ version,
265
+ domain,
266
+ permitTypehash: undefined, // EIP-5267 doesn't provide permit typehash directly
267
+ nonce: noncesResult.value,
268
+ }
269
+ } catch {
270
+ return undefined
271
+ }
272
+ }
273
+
128
274
  export const getContractData = async (
129
275
  client: Client,
130
276
  chainId: number,
131
277
  tokenAddress: Address
132
278
  ) => {
133
279
  try {
280
+ // First try EIP-5267 approach - returns domain object directly
281
+ const eip5267Data = await getEIP712DomainData(client, chainId, tokenAddress)
282
+ if (eip5267Data) {
283
+ return eip5267Data
284
+ }
285
+
286
+ // Fallback to legacy approach - validates and returns domain object
134
287
  const multicallAddress = await getMulticallAddress(chainId)
135
288
 
136
289
  const contractCalls = [
@@ -187,9 +340,22 @@ export const getContractData = async (
187
340
  throw new Error('Multicall failed')
188
341
  }
189
342
 
190
- return {
343
+ // Validate domain separator and create domain object
344
+ const { isValid, domain } = validateDomainSeparator({
191
345
  name: nameResult.result,
346
+ version: versionResult.result ?? '1',
347
+ chainId,
348
+ verifyingContract: tokenAddress,
192
349
  domainSeparator: domainSeparatorResult.result,
350
+ })
351
+
352
+ if (!isValid) {
353
+ return undefined
354
+ }
355
+
356
+ return {
357
+ name: nameResult.result,
358
+ domain,
193
359
  permitTypehash: permitTypehashResult.result,
194
360
  nonce: noncesResult.result,
195
361
  version: versionResult.result ?? '1',
@@ -229,9 +395,22 @@ export const getContractData = async (
229
395
  const version =
230
396
  versionResult.status === 'fulfilled' ? versionResult.value : '1'
231
397
 
232
- return {
398
+ // Validate domain separator and create domain object
399
+ const { isValid, domain } = validateDomainSeparator({
233
400
  name,
401
+ version,
402
+ chainId,
403
+ verifyingContract: tokenAddress,
234
404
  domainSeparator: domainSeparatorResult.value,
405
+ })
406
+
407
+ if (!isValid) {
408
+ return undefined
409
+ }
410
+
411
+ return {
412
+ name,
413
+ domain,
235
414
  permitTypehash:
236
415
  permitTypehashResult.status === 'fulfilled'
237
416
  ? permitTypehashResult.value
@@ -260,22 +439,10 @@ export const getNativePermit = async (
260
439
  if (!contractData) {
261
440
  return undefined
262
441
  }
263
- const { name, domainSeparator, permitTypehash, nonce, version } = contractData
264
442
 
265
443
  // We don't support DAI-like permits yet (e.g. DAI on Ethereum)
266
- if (permitTypehash === DAI_LIKE_PERMIT_TYPEHASH) {
267
- return undefined
268
- }
269
-
270
- const { isValid, domain } = validateDomainSeparator({
271
- name,
272
- version,
273
- chainId,
274
- verifyingContract: tokenAddress,
275
- domainSeparator,
276
- })
277
-
278
- if (!isValid) {
444
+ // https://eips.ethereum.org/EIPS/eip-2612#backwards-compatibility
445
+ if (contractData.permitTypehash === DAI_LIKE_PERMIT_TYPEHASH) {
279
446
  return undefined
280
447
  }
281
448
 
@@ -285,13 +452,13 @@ export const getNativePermit = async (
285
452
  owner: client.account!.address,
286
453
  spender: spenderAddress,
287
454
  value: amount.toString(),
288
- nonce: nonce.toString(),
455
+ nonce: contractData.nonce.toString(),
289
456
  deadline,
290
457
  }
291
458
 
292
459
  return {
293
460
  primaryType: 'Permit',
294
- domain,
461
+ domain: contractData.domain,
295
462
  types: eip2612Types,
296
463
  message,
297
464
  }