@lifi/sdk 3.10.0 → 3.11.0-beta.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/package.json +1 -1
- package/src/_cjs/core/EVM/EVMStepExecutor.js +122 -131
- package/src/_cjs/core/EVM/EVMStepExecutor.js.map +1 -1
- package/src/_cjs/core/EVM/abi.js +1 -0
- package/src/_cjs/core/EVM/abi.js.map +1 -1
- package/src/_cjs/core/EVM/checkAllowance.js +126 -44
- package/src/_cjs/core/EVM/checkAllowance.js.map +1 -1
- package/src/_cjs/core/EVM/permits/getNativePermit.js +123 -16
- package/src/_cjs/core/EVM/permits/getNativePermit.js.map +1 -1
- package/src/_cjs/core/EVM/permits/isNativePermitValid.js +34 -0
- package/src/_cjs/core/EVM/permits/isNativePermitValid.js.map +1 -0
- package/src/_cjs/core/EVM/switchChain.js +8 -14
- package/src/_cjs/core/EVM/switchChain.js.map +1 -1
- package/src/_cjs/core/EVM/utils.js +10 -1
- package/src/_cjs/core/EVM/utils.js.map +1 -1
- package/src/_cjs/core/execution.js +1 -1
- package/src/_cjs/core/execution.js.map +1 -1
- package/src/_cjs/core/prepareRestart.js +5 -2
- package/src/_cjs/core/prepareRestart.js.map +1 -1
- package/src/_cjs/core/processMessages.js +4 -8
- package/src/_cjs/core/processMessages.js.map +1 -1
- package/src/_cjs/index.js +2 -1
- package/src/_cjs/index.js.map +1 -1
- package/src/_cjs/version.js +1 -1
- package/src/_cjs/version.js.map +1 -1
- package/src/_esm/core/EVM/EVMStepExecutor.js +136 -148
- package/src/_esm/core/EVM/EVMStepExecutor.js.map +1 -1
- package/src/_esm/core/EVM/abi.js +2 -0
- package/src/_esm/core/EVM/abi.js.map +1 -1
- package/src/_esm/core/EVM/checkAllowance.js +141 -45
- package/src/_esm/core/EVM/checkAllowance.js.map +1 -1
- package/src/_esm/core/EVM/permits/getNativePermit.js +144 -21
- package/src/_esm/core/EVM/permits/getNativePermit.js.map +1 -1
- package/src/_esm/core/EVM/permits/isNativePermitValid.js +41 -0
- package/src/_esm/core/EVM/permits/isNativePermitValid.js.map +1 -0
- package/src/_esm/core/EVM/switchChain.js +8 -15
- package/src/_esm/core/EVM/switchChain.js.map +1 -1
- package/src/_esm/core/EVM/utils.js +12 -0
- package/src/_esm/core/EVM/utils.js.map +1 -1
- package/src/_esm/core/execution.js +1 -1
- package/src/_esm/core/execution.js.map +1 -1
- package/src/_esm/core/prepareRestart.js +6 -3
- package/src/_esm/core/prepareRestart.js.map +1 -1
- package/src/_esm/core/processMessages.js +4 -8
- package/src/_esm/core/processMessages.js.map +1 -1
- package/src/_esm/index.js +1 -1
- package/src/_esm/index.js.map +1 -1
- package/src/_esm/version.js +1 -1
- package/src/_esm/version.js.map +1 -1
- package/src/_types/core/EVM/EVMStepExecutor.d.ts +3 -2
- package/src/_types/core/EVM/EVMStepExecutor.d.ts.map +1 -1
- package/src/_types/core/EVM/abi.d.ts +20 -0
- package/src/_types/core/EVM/abi.d.ts.map +1 -1
- package/src/_types/core/EVM/checkAllowance.d.ts +10 -7
- package/src/_types/core/EVM/checkAllowance.d.ts.map +1 -1
- package/src/_types/core/EVM/permits/getNativePermit.d.ts +2 -2
- package/src/_types/core/EVM/permits/getNativePermit.d.ts.map +1 -1
- package/src/_types/core/EVM/permits/isNativePermitValid.d.ts +6 -0
- package/src/_types/core/EVM/permits/isNativePermitValid.d.ts.map +1 -0
- package/src/_types/core/EVM/switchChain.d.ts +2 -2
- package/src/_types/core/EVM/switchChain.d.ts.map +1 -1
- package/src/_types/core/EVM/utils.d.ts +6 -1
- package/src/_types/core/EVM/utils.d.ts.map +1 -1
- package/src/_types/core/prepareRestart.d.ts +1 -1
- package/src/_types/core/prepareRestart.d.ts.map +1 -1
- package/src/_types/core/processMessages.d.ts.map +1 -1
- package/src/_types/core/types.d.ts +2 -2
- package/src/_types/core/types.d.ts.map +1 -1
- package/src/_types/index.d.ts +1 -1
- package/src/_types/index.d.ts.map +1 -1
- package/src/_types/version.d.ts +1 -1
- package/src/_types/version.d.ts.map +1 -1
- package/src/core/EVM/EVMStepExecutor.ts +203 -197
- package/src/core/EVM/abi.ts +2 -0
- package/src/core/EVM/checkAllowance.ts +206 -63
- package/src/core/EVM/permits/getNativePermit.ts +189 -22
- package/src/core/EVM/permits/isNativePermitValid.ts +57 -0
- package/src/core/EVM/switchChain.ts +14 -22
- package/src/core/EVM/utils.ts +17 -1
- package/src/core/execution.ts +1 -1
- package/src/core/prepareRestart.ts +6 -3
- package/src/core/processMessages.ts +4 -8
- package/src/core/types.ts +1 -2
- package/src/index.ts +1 -0
- 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 {
|
|
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
|
-
|
|
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'
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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 (
|
|
166
|
+
if (sharedProcess.txHash && sharedProcess.status !== 'DONE') {
|
|
64
167
|
await waitForApprovalTransaction(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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,
|
|
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
|
-
|
|
188
|
+
updatedClient,
|
|
86
189
|
step.action.fromToken.address as Address,
|
|
87
|
-
|
|
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,
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
232
|
+
if (!existingValidPermit) {
|
|
233
|
+
statusManager.updateProcess(step, sharedProcess.type, 'ACTION_REQUIRED')
|
|
127
234
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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,
|
|
295
|
+
statusManager.updateProcess(step, sharedProcess.type, 'DONE')
|
|
163
296
|
return {
|
|
164
297
|
status: 'BATCH_APPROVAL',
|
|
165
298
|
data: {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
310
|
+
updatedClient,
|
|
175
311
|
approveTxHash,
|
|
176
|
-
|
|
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
|
-
|
|
185
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
267
|
-
|
|
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
|
}
|