@lifi/sdk 3.6.0-beta.1 → 3.6.0-beta.3

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 (39) hide show
  1. package/package.json +1 -1
  2. package/src/_cjs/core/EVM/EVMStepExecutor.js +14 -5
  3. package/src/_cjs/core/EVM/EVMStepExecutor.js.map +1 -1
  4. package/src/_cjs/core/EVM/getNativePermit.js +127 -59
  5. package/src/_cjs/core/EVM/getNativePermit.js.map +1 -1
  6. package/src/_cjs/core/EVM/signPermitMessage.js +1 -7
  7. package/src/_cjs/core/EVM/signPermitMessage.js.map +1 -1
  8. package/src/_cjs/core/EVM/typeguards.js +1 -1
  9. package/src/_cjs/core/UTXO/getUTXOAPIPublicClient.js +3 -3
  10. package/src/_cjs/core/UTXO/getUTXOAPIPublicClient.js.map +1 -1
  11. package/src/_cjs/index.js +4 -2
  12. package/src/_cjs/index.js.map +1 -1
  13. package/src/_cjs/version.js +1 -1
  14. package/src/_esm/core/EVM/EVMStepExecutor.js +14 -5
  15. package/src/_esm/core/EVM/EVMStepExecutor.js.map +1 -1
  16. package/src/_esm/core/EVM/getNativePermit.js +147 -60
  17. package/src/_esm/core/EVM/getNativePermit.js.map +1 -1
  18. package/src/_esm/core/EVM/signPermitMessage.js +1 -7
  19. package/src/_esm/core/EVM/signPermitMessage.js.map +1 -1
  20. package/src/_esm/core/EVM/typeguards.js +1 -1
  21. package/src/_esm/core/UTXO/getUTXOAPIPublicClient.js +3 -3
  22. package/src/_esm/core/UTXO/getUTXOAPIPublicClient.js.map +1 -1
  23. package/src/_esm/index.js +1 -0
  24. package/src/_esm/index.js.map +1 -1
  25. package/src/_esm/version.js +1 -1
  26. package/src/_types/core/EVM/EVMStepExecutor.d.ts.map +1 -1
  27. package/src/_types/core/EVM/getNativePermit.d.ts +2 -0
  28. package/src/_types/core/EVM/getNativePermit.d.ts.map +1 -1
  29. package/src/_types/core/EVM/signPermitMessage.d.ts.map +1 -1
  30. package/src/_types/index.d.ts +1 -0
  31. package/src/_types/index.d.ts.map +1 -1
  32. package/src/_types/version.d.ts +1 -1
  33. package/src/core/EVM/EVMStepExecutor.ts +15 -6
  34. package/src/core/EVM/getNativePermit.ts +219 -66
  35. package/src/core/EVM/signPermitMessage.ts +1 -8
  36. package/src/core/EVM/typeguards.ts +1 -1
  37. package/src/core/UTXO/getUTXOAPIPublicClient.ts +3 -3
  38. package/src/index.ts +1 -0
  39. package/src/version.ts +1 -1
@@ -1,5 +1,14 @@
1
1
  import type { ExtendedChain } from '@lifi/types'
2
- import type { Address, Client } from 'viem'
2
+ import {
3
+ encodeAbiParameters,
4
+ keccak256,
5
+ pad,
6
+ parseAbiParameters,
7
+ toBytes,
8
+ toHex,
9
+ } from 'viem'
10
+ import type { Address, Client, Hex } from 'viem'
11
+ import type { TypedDataDomain } from 'viem'
3
12
  import { multicall, readContract } from 'viem/actions'
4
13
  import { eip2612Abi } from './abi.js'
5
14
  import { getMulticallAddress } from './utils.js'
@@ -9,6 +18,136 @@ export type NativePermitData = {
9
18
  version: string
10
19
  nonce: bigint
11
20
  supported: boolean
21
+ domain: TypedDataDomain
22
+ }
23
+
24
+ /**
25
+ * EIP-712 domain typehash with chainId
26
+ * @link https://eips.ethereum.org/EIPS/eip-712#specification
27
+ *
28
+ * keccak256(toBytes(
29
+ * 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
30
+ * ))
31
+ */
32
+ const EIP712_DOMAIN_TYPEHASH =
33
+ '0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f' as Hex
34
+
35
+ /**
36
+ * EIP-712 domain typehash with salt (e.g. USDC.e on Polygon)
37
+ * @link https://eips.ethereum.org/EIPS/eip-712#specification
38
+ *
39
+ * keccak256(toBytes(
40
+ * 'EIP712Domain(string name,string version,address verifyingContract,bytes32 salt)'
41
+ * ))
42
+ */
43
+ const EIP712_DOMAIN_TYPEHASH_WITH_SALT =
44
+ '0x36c25de3e541d5d970f66e4210d728721220fff5c077cc6cd008b3a0c62adab7' as Hex
45
+
46
+ function makeDomainSeparator({
47
+ name,
48
+ version,
49
+ chainId,
50
+ verifyingContract,
51
+ withSalt = false,
52
+ }: {
53
+ name: string
54
+ version: string
55
+ chainId: bigint
56
+ verifyingContract: Address
57
+ withSalt?: boolean
58
+ }): Hex {
59
+ const nameHash = keccak256(toBytes(name))
60
+ const versionHash = keccak256(toBytes(version))
61
+
62
+ const encoded = withSalt
63
+ ? encodeAbiParameters(
64
+ parseAbiParameters('bytes32, bytes32, bytes32, address, bytes32'),
65
+ [
66
+ EIP712_DOMAIN_TYPEHASH_WITH_SALT,
67
+ nameHash,
68
+ versionHash,
69
+ verifyingContract,
70
+ pad(toHex(chainId), { size: 32 }),
71
+ ]
72
+ )
73
+ : encodeAbiParameters(
74
+ parseAbiParameters('bytes32, bytes32, bytes32, uint256, address'),
75
+ [
76
+ EIP712_DOMAIN_TYPEHASH,
77
+ nameHash,
78
+ versionHash,
79
+ chainId,
80
+ verifyingContract,
81
+ ]
82
+ )
83
+
84
+ return keccak256(encoded)
85
+ }
86
+
87
+ // TODO: Add support for EIP-5267 when adoption increases
88
+ // This EIP provides a standard way to query domain separator and permit type hash
89
+ // via eip712Domain() function, which would simplify permit validation
90
+ // https://eips.ethereum.org/EIPS/eip-5267
91
+ function validateDomainSeparator({
92
+ name,
93
+ version,
94
+ chainId,
95
+ verifyingContract,
96
+ domainSeparator,
97
+ }: {
98
+ name: string
99
+ version: string
100
+ chainId: bigint
101
+ verifyingContract: Address
102
+ domainSeparator: Hex
103
+ }): { isValid: boolean; domain: TypedDataDomain } {
104
+ if (!name || !domainSeparator) {
105
+ return {
106
+ isValid: false,
107
+ domain: {},
108
+ }
109
+ }
110
+
111
+ for (const withSalt of [false, true]) {
112
+ const computedDS = makeDomainSeparator({
113
+ name,
114
+ version,
115
+ chainId,
116
+ verifyingContract,
117
+ withSalt,
118
+ })
119
+ if (domainSeparator.toLowerCase() === computedDS.toLowerCase()) {
120
+ return {
121
+ isValid: true,
122
+ domain: withSalt
123
+ ? {
124
+ name,
125
+ version,
126
+ verifyingContract,
127
+ salt: pad(toHex(chainId), { size: 32 }),
128
+ }
129
+ : {
130
+ name,
131
+ version,
132
+ chainId,
133
+ verifyingContract,
134
+ },
135
+ }
136
+ }
137
+ }
138
+
139
+ return {
140
+ isValid: false,
141
+ domain: {},
142
+ }
143
+ }
144
+
145
+ const defaultPermit: NativePermitData = {
146
+ name: '',
147
+ version: '1',
148
+ nonce: 0n,
149
+ supported: false,
150
+ domain: {},
12
151
  }
13
152
 
14
153
  /**
@@ -27,88 +166,102 @@ export const getNativePermit = async (
27
166
  try {
28
167
  const multicallAddress = await getMulticallAddress(chain.id)
29
168
 
30
- if (multicallAddress) {
31
- const [nameResult, domainSeparatorResult, noncesResult, versionResult] =
32
- await multicall(client, {
33
- contracts: [
34
- {
35
- address: tokenAddress,
36
- abi: eip2612Abi,
37
- functionName: 'name',
38
- },
39
- {
40
- address: tokenAddress,
41
- abi: eip2612Abi,
42
- functionName: 'DOMAIN_SEPARATOR',
43
- },
44
- {
45
- address: tokenAddress,
46
- abi: eip2612Abi,
47
- functionName: 'nonces',
48
- args: [client.account!.address],
49
- },
50
- {
51
- address: tokenAddress,
52
- abi: eip2612Abi,
53
- functionName: 'version',
54
- },
55
- ],
56
- multicallAddress,
57
- })
58
-
59
- const supported =
60
- nameResult.status === 'success' &&
61
- domainSeparatorResult.status === 'success' &&
62
- noncesResult.status === 'success' &&
63
- !!nameResult.result &&
64
- !!domainSeparatorResult.result &&
65
- noncesResult.result !== undefined
66
-
67
- return {
68
- name: nameResult.result!,
69
- version: versionResult.result ?? '1',
70
- nonce: noncesResult.result!,
71
- supported,
72
- }
73
- }
74
-
75
- // Fallback to individual calls
76
- const [name, domainSeparator, nonce, version] = await Promise.all([
77
- readContract(client, {
169
+ const contractCalls = [
170
+ {
78
171
  address: tokenAddress,
79
172
  abi: eip2612Abi,
80
173
  functionName: 'name',
81
- }),
82
- readContract(client, {
174
+ },
175
+ {
83
176
  address: tokenAddress,
84
177
  abi: eip2612Abi,
85
178
  functionName: 'DOMAIN_SEPARATOR',
86
- }),
87
- readContract(client, {
179
+ },
180
+ {
88
181
  address: tokenAddress,
89
182
  abi: eip2612Abi,
90
183
  functionName: 'nonces',
91
184
  args: [client.account!.address],
92
- }),
93
- readContract(client, {
185
+ },
186
+ {
94
187
  address: tokenAddress,
95
188
  abi: eip2612Abi,
96
189
  functionName: 'version',
97
- }),
98
- ])
190
+ },
191
+ ] as const
192
+
193
+ if (multicallAddress) {
194
+ const [nameResult, domainSeparatorResult, noncesResult, versionResult] =
195
+ await multicall(client, {
196
+ contracts: contractCalls,
197
+ multicallAddress,
198
+ })
199
+
200
+ if (
201
+ nameResult.status !== 'success' ||
202
+ domainSeparatorResult.status !== 'success' ||
203
+ noncesResult.status !== 'success' ||
204
+ !nameResult.result ||
205
+ !domainSeparatorResult.result ||
206
+ noncesResult.result === undefined
207
+ ) {
208
+ return defaultPermit
209
+ }
210
+
211
+ const { isValid, domain } = validateDomainSeparator({
212
+ name: nameResult.result,
213
+ version: versionResult.result ?? '1',
214
+ chainId: BigInt(chain.id),
215
+ verifyingContract: tokenAddress,
216
+ domainSeparator: domainSeparatorResult.result,
217
+ })
218
+
219
+ return {
220
+ name: nameResult.result,
221
+ version: versionResult.result ?? '1',
222
+ nonce: noncesResult.result,
223
+ supported: isValid,
224
+ domain,
225
+ }
226
+ }
227
+
228
+ const [nameResult, domainSeparatorResult, noncesResult, versionResult] =
229
+ (await Promise.allSettled(
230
+ contractCalls.map((call) => readContract(client, call))
231
+ )) as [
232
+ PromiseSettledResult<string>,
233
+ PromiseSettledResult<Hex>,
234
+ PromiseSettledResult<bigint>,
235
+ PromiseSettledResult<string>,
236
+ ]
237
+
238
+ if (
239
+ nameResult.status !== 'fulfilled' ||
240
+ domainSeparatorResult.status !== 'fulfilled' ||
241
+ noncesResult.status !== 'fulfilled'
242
+ ) {
243
+ return defaultPermit
244
+ }
245
+
246
+ const name = nameResult.value
247
+ const version =
248
+ versionResult.status === 'fulfilled' ? versionResult.value : '1'
249
+ const { isValid, domain } = validateDomainSeparator({
250
+ name,
251
+ version,
252
+ chainId: BigInt(chain.id),
253
+ verifyingContract: tokenAddress,
254
+ domainSeparator: domainSeparatorResult.value,
255
+ })
99
256
 
100
257
  return {
101
258
  name,
102
- version: version ?? '1',
103
- nonce,
104
- supported: !!name && !!domainSeparator && nonce !== undefined,
259
+ version,
260
+ nonce: noncesResult.value,
261
+ supported: isValid,
262
+ domain,
105
263
  }
106
264
  } catch {
107
- return {
108
- name: '',
109
- version: '1',
110
- nonce: 0n,
111
- supported: false,
112
- }
265
+ return defaultPermit
113
266
  }
114
267
  }
@@ -28,13 +28,6 @@ export const signNativePermitMessage = async (
28
28
  ): Promise<PermitSignature> => {
29
29
  const deadline = BigInt(Math.floor(Date.now() / 1000) + 30 * 60) // 30 minutes
30
30
 
31
- const domain = {
32
- name: nativePermit.name,
33
- version: nativePermit.version,
34
- chainId: chain.id,
35
- verifyingContract: tokenAddress,
36
- }
37
-
38
31
  const message = {
39
32
  owner: client.account!.address,
40
33
  spender: chain.permit2Proxy as Address,
@@ -49,7 +42,7 @@ export const signNativePermitMessage = async (
49
42
  'signTypedData'
50
43
  )({
51
44
  account: client.account!,
52
- domain,
45
+ domain: nativePermit.domain,
53
46
  types: eip2612Types,
54
47
  primaryType: 'Permit',
55
48
  message,
@@ -3,5 +3,5 @@ import type { EVMPermitStep } from './types.js'
3
3
 
4
4
  export function isEVMPermitStep(step: LiFiStepExtended): step is EVMPermitStep {
5
5
  const evmStep = step as EVMPermitStep
6
- return 'permit' in evmStep || 'permitData' in evmStep || 'witness' in evmStep
6
+ return 'permit' in evmStep && 'permitData' in evmStep && 'witness' in evmStep
7
7
  }
@@ -49,15 +49,15 @@ export const getUTXOAPIPublicClient = async (chainId: number) => {
49
49
  key: 'blockchair',
50
50
  includeChainToURL: true,
51
51
  }),
52
- utxo('https://rpc.ankr.com/http/btc_blockbook/api/v2', {
53
- key: 'ankr',
54
- }),
55
52
  utxo('https://api.blockcypher.com/v1/btc/main', {
56
53
  key: 'blockcypher',
57
54
  }),
58
55
  utxo('https://mempool.space/api', {
59
56
  key: 'mempool',
60
57
  }),
58
+ utxo('https://rpc.ankr.com/http/btc_blockbook/api/v2', {
59
+ key: 'ankr',
60
+ }),
61
61
  ]),
62
62
  }).extend(UTXOAPIActions)
63
63
  publicAPIClients[chainId] = client
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ export {
12
12
  setTokenAllowance,
13
13
  } from './core/EVM/setAllowance.js'
14
14
  export { isEVM } from './core/EVM/types.js'
15
+ export { isEVMPermitStep } from './core/EVM/typeguards.js'
15
16
  export type {
16
17
  EVMProvider,
17
18
  EVMProviderOptions,
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export const name = '@lifi/sdk'
2
- export const version = '3.6.0-beta.1'
2
+ export const version = '3.6.0-beta.3'