@cowprotocol/permit-utils 0.7.0-RC.1 → 2.1.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/CHANGELOG.md +205 -0
- package/LICENSE +674 -0
- package/README.md +6 -7
- package/jest.config.ts +11 -0
- package/package.json +25 -14
- package/project.json +42 -0
- package/src/abi/erc20.json +59 -0
- package/src/abi/versionAbis.ts +37 -0
- package/src/const.ts +20 -0
- package/src/consts/1inchPermitUtils.ts +68 -0
- package/src/imports/1inchPermitUtils.ts +10 -0
- package/src/index.ts +11 -0
- package/src/lib/checkIsCallDataAValidPermit.ts +64 -0
- package/src/lib/generatePermitHook.ts +133 -0
- package/src/lib/getPermitUtilsInstance.ts +51 -0
- package/src/lib/getTokenPermitInfo.ts +281 -0
- package/src/types.ts +61 -0
- package/src/utils/PermitProviderConnector.ts +75 -0
- package/src/utils/buildPermitCallData.ts +26 -0
- package/src/utils/getContract.ts +6 -0
- package/src/utils/getEip712Domain.ts +70 -0
- package/src/utils/getPermitDeadline.ts +7 -0
- package/src/utils/getTokenName.ts +13 -0
- package/src/utils/getTokenPermitVersion.ts +35 -0
- package/src/utils/isSupportedPermitInfo.ts +5 -0
- package/tsconfig.json +22 -0
- package/tsconfig.lib.json +10 -0
- package/tsconfig.spec.json +9 -0
- package/vite.config.mts +51 -0
- package/abi/versionAbis.d.ts +0 -15
- package/const.d.ts +0 -6
- package/index.cjs +0 -1
- package/index.d.ts +0 -7
- package/index.js +0 -489
- package/lib/checkIsCallDataAValidPermit.d.ts +0 -3
- package/lib/generatePermitHook.d.ts +0 -2
- package/lib/getPermitUtilsInstance.d.ts +0 -3
- package/lib/getTokenPermitInfo.d.ts +0 -2
- package/types.d.ts +0 -45
- package/utils/PermitProviderConnector.d.ts +0 -13
- package/utils/buildPermitCallData.d.ts +0 -3
- package/utils/getContract.d.ts +0 -3
- package/utils/getEip712Domain.d.ts +0 -13
- package/utils/getPermitDeadline.d.ts +0 -1
- package/utils/getTokenName.d.ts +0 -2
- package/utils/getTokenPermitVersion.d.ts +0 -2
- package/utils/isSupportedPermitInfo.d.ts +0 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Collection of utils for handling token permits.
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
npm install @cowprotocol/permit-utils
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
@@ -26,10 +26,10 @@ const permitInfo = await getTokenPermitInfo({
|
|
|
26
26
|
import { getPermitUtilsInstance } from "@cowprotocol/permit-utils"
|
|
27
27
|
|
|
28
28
|
// Using a static account defined in the library
|
|
29
|
-
const staticEip2612PermitUtils = getPermitUtilsInstance(chainId, provider)
|
|
29
|
+
const staticEip2612PermitUtils = await getPermitUtilsInstance(chainId, provider)
|
|
30
30
|
|
|
31
31
|
// Using a provided account address
|
|
32
|
-
const accountEip2612PermitUtils = getPermitUtilsInstance(chainId, provider, account)
|
|
32
|
+
const accountEip2612PermitUtils = await getPermitUtilsInstance(chainId, provider, account)
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
### `generatePermitHook`
|
|
@@ -76,8 +76,7 @@ To illustrate, we'll show the flow of placing an order with a permit hook using
|
|
|
76
76
|
|
|
77
77
|
```typescript
|
|
78
78
|
import { checkIsCallDataAValidPermit, generatePermitHook, getPermitUtilsInstance, getTokenPermitInfo } from '@cowprotocol/permit-utils'
|
|
79
|
-
import { stringifyDeterministic } from '@cowprotocol/
|
|
80
|
-
import { OrderBookApi } from '@cowprotocol/cow-sdk'
|
|
79
|
+
import { stringifyDeterministic, OrderBookApi } from '@cowprotocol/cow-sdk'
|
|
81
80
|
|
|
82
81
|
|
|
83
82
|
// Check whether token is permittable.
|
|
@@ -94,7 +93,7 @@ if (!permitInfo) {
|
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
// Pass in an account address as we'll need the user to sign the actual permit
|
|
97
|
-
const eip2612Utils = getPermitUtilsInstance(chainId, provider, account)
|
|
96
|
+
const eip2612Utils = await getPermitUtilsInstance(chainId, provider, account)
|
|
98
97
|
|
|
99
98
|
// Need to know what the current permit nonce is
|
|
100
99
|
const nonce = await eip2612Utils.getTokenNonce(inputToken.address, account)
|
|
@@ -112,7 +111,7 @@ const hookData = await generatePermitHook({
|
|
|
112
111
|
})
|
|
113
112
|
|
|
114
113
|
// Add the hookData to the order's appData
|
|
115
|
-
// See the full reference on https://
|
|
114
|
+
// See the full reference on https://www.npmjs.com/package/@cowprotocol/sdk-app-data?activeTab=code
|
|
116
115
|
const appData = { version: '0.10.0', metadata: { hooks: { pre: [hookData] } } }
|
|
117
116
|
|
|
118
117
|
// The order expects the stringified JSON doc
|
package/jest.config.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
displayName: 'permit-utils',
|
|
3
|
+
preset: '../../jest.preset.js',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
transform: {
|
|
6
|
+
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
|
7
|
+
},
|
|
8
|
+
setupFilesAfterEnv: ['../../jest.setup.ts'],
|
|
9
|
+
moduleFileExtensions: ['ts', 'js', 'html'],
|
|
10
|
+
coverageDirectory: '../../coverage/libs/permit-utils',
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cowprotocol/permit-utils",
|
|
3
|
-
"version": "
|
|
4
|
-
"type": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"type": "commonjs",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.d.ts",
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
5
11
|
"dependencies": {
|
|
6
|
-
"
|
|
7
|
-
"@1inch/permit-signed-approvals-utils": "^1.
|
|
8
|
-
"@
|
|
9
|
-
"
|
|
12
|
+
"@cowprotocol/cow-sdk": "7.3.4",
|
|
13
|
+
"@1inch/permit-signed-approvals-utils": "^1.5.1",
|
|
14
|
+
"@ethersproject/abi": "5.7.0",
|
|
15
|
+
"@ethersproject/abstract-signer": "5.7.0",
|
|
16
|
+
"@ethersproject/address": "5.7.0",
|
|
17
|
+
"@ethersproject/bignumber": "5.7.0",
|
|
18
|
+
"@ethersproject/constants": "5.7.0",
|
|
19
|
+
"@ethersproject/contracts": "5.7.0",
|
|
20
|
+
"@ethersproject/providers": "5.7.0",
|
|
21
|
+
"@ethersproject/wallet": "5.7.0",
|
|
22
|
+
"ethers": "5.7.2",
|
|
23
|
+
"ms.macro": "^2.0.0",
|
|
24
|
+
"viem": "^2.42.1",
|
|
25
|
+
"@cowprotocol/hook-dapp-lib": "2.0.2"
|
|
10
26
|
},
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
"exports": {
|
|
14
|
-
".": {
|
|
15
|
-
"import": "./index.js",
|
|
16
|
-
"require": "./index.cjs"
|
|
17
|
-
}
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/ms.macro": "^2.0.0"
|
|
18
29
|
}
|
|
19
|
-
}
|
|
30
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "permit-utils",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "libs/permit-utils/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"targets": {
|
|
7
|
+
"build": {
|
|
8
|
+
"executor": "@nx/js:tsc",
|
|
9
|
+
"outputs": ["{options.outputPath}"],
|
|
10
|
+
"options": {
|
|
11
|
+
"outputPath": "dist/libs/permit-utils",
|
|
12
|
+
"main": "libs/permit-utils/src/index.ts",
|
|
13
|
+
"tsConfig": "libs/permit-utils/tsconfig.lib.json",
|
|
14
|
+
"assets": ["libs/permit-utils/*.md"],
|
|
15
|
+
"updateBuildableProjectDepsInPackageJson": true,
|
|
16
|
+
"buildableProjectDepsInPackageJsonType": "dependencies"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"lint": {
|
|
20
|
+
"executor": "@nx/eslint:lint",
|
|
21
|
+
"outputs": ["{options.outputFile}"],
|
|
22
|
+
"options": {
|
|
23
|
+
"lintFilePatterns": ["libs/permit-utils/**/*.ts"]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"test": {
|
|
27
|
+
"executor": "@nx/jest:jest",
|
|
28
|
+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
|
29
|
+
"options": {
|
|
30
|
+
"jestConfig": "libs/permit-utils/jest.config.ts",
|
|
31
|
+
"passWithNoTests": true
|
|
32
|
+
},
|
|
33
|
+
"configurations": {
|
|
34
|
+
"ci": {
|
|
35
|
+
"ci": true,
|
|
36
|
+
"codeCoverage": true
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"tags": []
|
|
42
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"constant": true,
|
|
4
|
+
"inputs": [],
|
|
5
|
+
"name": "name",
|
|
6
|
+
"outputs": [
|
|
7
|
+
{
|
|
8
|
+
"name": "",
|
|
9
|
+
"type": "string"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"payable": false,
|
|
13
|
+
"stateMutability": "view",
|
|
14
|
+
"type": "function"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"inputs": [],
|
|
18
|
+
"name": "eip712Domain",
|
|
19
|
+
"outputs": [
|
|
20
|
+
{
|
|
21
|
+
"internalType": "bytes1",
|
|
22
|
+
"name": "fields",
|
|
23
|
+
"type": "bytes1"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"internalType": "string",
|
|
27
|
+
"name": "name",
|
|
28
|
+
"type": "string"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"internalType": "string",
|
|
32
|
+
"name": "version",
|
|
33
|
+
"type": "string"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"internalType": "uint256",
|
|
37
|
+
"name": "chainId",
|
|
38
|
+
"type": "uint256"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"internalType": "address",
|
|
42
|
+
"name": "verifyingContract",
|
|
43
|
+
"type": "address"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"internalType": "bytes32",
|
|
47
|
+
"name": "salt",
|
|
48
|
+
"type": "bytes32"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"internalType": "uint256[]",
|
|
52
|
+
"name": "extensions",
|
|
53
|
+
"type": "uint256[]"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"stateMutability": "view",
|
|
57
|
+
"type": "function"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ABIs for fetching the version of different permit flavours
|
|
3
|
+
*/
|
|
4
|
+
export const VERSION_ABIS = [
|
|
5
|
+
[
|
|
6
|
+
{
|
|
7
|
+
constant: true,
|
|
8
|
+
inputs: [],
|
|
9
|
+
name: 'version',
|
|
10
|
+
outputs: [
|
|
11
|
+
{
|
|
12
|
+
name: '',
|
|
13
|
+
type: 'string',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
payable: false,
|
|
17
|
+
stateMutability: 'view',
|
|
18
|
+
type: 'function',
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
[
|
|
22
|
+
{
|
|
23
|
+
constant: true,
|
|
24
|
+
inputs: [],
|
|
25
|
+
name: 'ERC712_VERSION',
|
|
26
|
+
outputs: [
|
|
27
|
+
{
|
|
28
|
+
name: '',
|
|
29
|
+
type: 'string',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
payable: false,
|
|
33
|
+
stateMutability: 'view',
|
|
34
|
+
type: 'function',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
]
|
package/src/const.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { MaxUint256 } from '@ethersproject/constants'
|
|
2
|
+
import { Wallet } from '@ethersproject/wallet'
|
|
3
|
+
|
|
4
|
+
import ms from 'ms.macro'
|
|
5
|
+
import { privateKeyToAccount } from 'viem/accounts'
|
|
6
|
+
|
|
7
|
+
// PK used only for signing permit requests for quoting and identifying token 'permittability'
|
|
8
|
+
// Do not use or try to send funds to it. Or do. It'll be your funds 🤷
|
|
9
|
+
const PERMIT_PK = '0xa50dc0f7fc051309434deb3b1c71e927dbb711759231d8ecbf630c85d94a42fe' // address: 0xDa5F16F4ab0410096a4403e7223988649fac38cF
|
|
10
|
+
|
|
11
|
+
export const PERMIT_ACCOUNT = privateKeyToAccount(PERMIT_PK)
|
|
12
|
+
export const PERMIT_SIGNER = new Wallet(PERMIT_PK)
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_MIN_GAS_LIMIT = 55_000
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_PERMIT_GAS_LIMIT = '80000'
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_PERMIT_VALUE = MaxUint256.toBigInt()
|
|
19
|
+
|
|
20
|
+
export const DEFAULT_PERMIT_DURATION = ms`5 years`
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @1inch/permit-signed-approvals-utils has a lot of extra variables and increases bundle size.
|
|
3
|
+
*
|
|
4
|
+
* At the same time it's repo is archived.
|
|
5
|
+
*
|
|
6
|
+
* Therefore we just copied needed constants, and when it's not possible, we should use import(...)
|
|
7
|
+
*
|
|
8
|
+
* You also can use `import type ... from '@1inch/permit-signed-approvals-utils'`
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const DAI_EIP_2612_PERMIT_ABI = [
|
|
12
|
+
{
|
|
13
|
+
constant: false,
|
|
14
|
+
inputs: [
|
|
15
|
+
{
|
|
16
|
+
internalType: 'address',
|
|
17
|
+
name: 'holder',
|
|
18
|
+
type: 'address',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
internalType: 'address',
|
|
22
|
+
name: 'spender',
|
|
23
|
+
type: 'address',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
internalType: 'uint256',
|
|
27
|
+
name: 'nonce',
|
|
28
|
+
type: 'uint256',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
internalType: 'uint256',
|
|
32
|
+
name: 'expiry',
|
|
33
|
+
type: 'uint256',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
internalType: 'bool',
|
|
37
|
+
name: 'allowed',
|
|
38
|
+
type: 'bool',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
internalType: 'uint8',
|
|
42
|
+
name: 'v',
|
|
43
|
+
type: 'uint8',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
internalType: 'bytes32',
|
|
47
|
+
name: 'r',
|
|
48
|
+
type: 'bytes32',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
internalType: 'bytes32',
|
|
52
|
+
name: 's',
|
|
53
|
+
type: 'bytes32',
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
name: 'permit',
|
|
57
|
+
outputs: [],
|
|
58
|
+
payable: false,
|
|
59
|
+
stateMutability: 'nonpayable',
|
|
60
|
+
type: 'function',
|
|
61
|
+
},
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
export const EIP_2612_PERMIT_SELECTOR = '0xd505accf'
|
|
65
|
+
|
|
66
|
+
export const DAI_PERMIT_SELECTOR = '0x8fcbaf0c'
|
|
67
|
+
|
|
68
|
+
export const DAI_LIKE_PERMIT_TYPEHASH = '0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @1inch/permit-signed-approvals-utils has a lot of extra variables and increases bundle size, we should import in dynamically.
|
|
3
|
+
*
|
|
4
|
+
* It uses esm, but dymaic import breaks tree shaking, so we re-export here only used variables (642kb -> 256kb).
|
|
5
|
+
*
|
|
6
|
+
* You also can use `import type ... from '@1inch/permit-signed-approvals-utils'`
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
|
10
|
+
export { Eip2612PermitUtils } from '@1inch/permit-signed-approvals-utils'
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { DEFAULT_MIN_GAS_LIMIT, PERMIT_ACCOUNT, PERMIT_SIGNER } from './const'
|
|
2
|
+
|
|
3
|
+
export { checkIsCallDataAValidPermit } from './lib/checkIsCallDataAValidPermit'
|
|
4
|
+
export { generatePermitHook } from './lib/generatePermitHook'
|
|
5
|
+
export { getPermitUtilsInstance } from './lib/getPermitUtilsInstance'
|
|
6
|
+
export { getTokenPermitInfo } from './lib/getTokenPermitInfo'
|
|
7
|
+
export { isSupportedPermitInfo } from './utils/isSupportedPermitInfo'
|
|
8
|
+
|
|
9
|
+
export * as oneInchPermitUtilsConsts from './consts/1inchPermitUtils'
|
|
10
|
+
|
|
11
|
+
export type { GetTokenPermitIntoResult, PermitHookData, PermitHookParams, PermitInfo, PermitType } from './types'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { oneInchPermitUtilsConsts } from '..'
|
|
2
|
+
import { PermitInfo } from '../types'
|
|
3
|
+
|
|
4
|
+
import type { Eip2612PermitUtils } from '@1inch/permit-signed-approvals-utils'
|
|
5
|
+
|
|
6
|
+
export async function checkIsCallDataAValidPermit(
|
|
7
|
+
owner: string,
|
|
8
|
+
chainId: number,
|
|
9
|
+
eip2612Utils: Eip2612PermitUtils,
|
|
10
|
+
tokenAddress: string,
|
|
11
|
+
_tokenName: string | undefined,
|
|
12
|
+
callData: string,
|
|
13
|
+
{ version, type, name }: PermitInfo,
|
|
14
|
+
): Promise<boolean | undefined> {
|
|
15
|
+
// TODO: take name only from PermitInfo
|
|
16
|
+
const tokenName = name || _tokenName
|
|
17
|
+
|
|
18
|
+
if (type === 'unsupported') {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!tokenName) {
|
|
23
|
+
throw new Error(`No token name for ${tokenAddress}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const params = { chainId, tokenName, tokenAddress, callData, version }
|
|
27
|
+
|
|
28
|
+
let recoverPermitOwnerPromise: Promise<string> | undefined = undefined
|
|
29
|
+
|
|
30
|
+
// If pre-hook doesn't start with either selector, it's not a permit
|
|
31
|
+
if (callData.startsWith(oneInchPermitUtilsConsts.EIP_2612_PERMIT_SELECTOR)) {
|
|
32
|
+
recoverPermitOwnerPromise = eip2612Utils.recoverPermitOwnerFromCallData({
|
|
33
|
+
...params,
|
|
34
|
+
// I don't know why this was removed, ok?
|
|
35
|
+
// We added it back on buildPermitCallData.ts
|
|
36
|
+
// But it looks like this is needed 🤷
|
|
37
|
+
// Check the test for this method https://github.com/1inch/permit-signed-approvals-utils/blob/master/src/eip-2612-permit.test.ts#L85-L106
|
|
38
|
+
callData: callData.replace(oneInchPermitUtilsConsts.EIP_2612_PERMIT_SELECTOR, '0x'),
|
|
39
|
+
})
|
|
40
|
+
} else if (callData.startsWith(oneInchPermitUtilsConsts.DAI_PERMIT_SELECTOR)) {
|
|
41
|
+
recoverPermitOwnerPromise = eip2612Utils.recoverDaiLikePermitOwnerFromCallData({
|
|
42
|
+
...params,
|
|
43
|
+
callData: callData.replace(oneInchPermitUtilsConsts.DAI_PERMIT_SELECTOR, '0x'),
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!recoverPermitOwnerPromise) {
|
|
48
|
+
// The callData doesn't match any known permit type
|
|
49
|
+
return undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const recoveredOwner = await recoverPermitOwnerPromise
|
|
54
|
+
|
|
55
|
+
// Permit is valid when recovered owner matches order owner
|
|
56
|
+
return recoveredOwner.toLowerCase() === owner.toLowerCase()
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.debug(
|
|
59
|
+
`[checkHasValidPendingPermit] Failed to check permit validity for owner ${owner} with callData ${callData}`,
|
|
60
|
+
e,
|
|
61
|
+
)
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { PERMIT_HOOK_DAPP_ID } from '@cowprotocol/hook-dapp-lib'
|
|
2
|
+
import { JsonRpcProvider } from '@ethersproject/providers'
|
|
3
|
+
|
|
4
|
+
import { DEFAULT_PERMIT_GAS_LIMIT, DEFAULT_PERMIT_VALUE, PERMIT_SIGNER } from '../const'
|
|
5
|
+
import { PermitHookData, PermitHookParams } from '../types'
|
|
6
|
+
import { buildDaiLikePermitCallData, buildEip2612PermitCallData } from '../utils/buildPermitCallData'
|
|
7
|
+
import { getPermitDeadline } from '../utils/getPermitDeadline'
|
|
8
|
+
import { isSupportedPermitInfo } from '../utils/isSupportedPermitInfo'
|
|
9
|
+
|
|
10
|
+
const REQUESTS_CACHE: { [permitKey: string]: Promise<PermitHookData | undefined> } = {}
|
|
11
|
+
|
|
12
|
+
export async function generatePermitHook(params: PermitHookParams): Promise<PermitHookData | undefined> {
|
|
13
|
+
const permitKey = getCacheKey(params)
|
|
14
|
+
|
|
15
|
+
const cachedRequest = REQUESTS_CACHE[permitKey]
|
|
16
|
+
|
|
17
|
+
if (cachedRequest) {
|
|
18
|
+
return await cachedRequest
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const request = generatePermitHookRaw(params)
|
|
22
|
+
.catch((e) => {
|
|
23
|
+
console.debug(`[generatePermitHook] cached request failed`, e)
|
|
24
|
+
return undefined
|
|
25
|
+
})
|
|
26
|
+
.finally(() => {
|
|
27
|
+
// Remove consumed request to avoid stale data
|
|
28
|
+
delete REQUESTS_CACHE[permitKey]
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
REQUESTS_CACHE[permitKey] = request
|
|
32
|
+
|
|
33
|
+
return request
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function generatePermitHookRaw(params: PermitHookParams): Promise<PermitHookData> {
|
|
37
|
+
const { inputToken, spender, chainId, permitInfo, provider, account, eip2612Utils, nonce: preFetchedNonce } = params
|
|
38
|
+
|
|
39
|
+
const tokenAddress = inputToken.address
|
|
40
|
+
// TODO: remove the need for `name` from input token. Should come from permitInfo instead
|
|
41
|
+
const tokenName = permitInfo.name || inputToken.name
|
|
42
|
+
|
|
43
|
+
if (!isSupportedPermitInfo(permitInfo)) {
|
|
44
|
+
throw new Error(`Trying to generate permit hook for unsupported token: ${tokenAddress}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!tokenName) {
|
|
48
|
+
throw new Error(`No token name for token: ${tokenAddress}`)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const owner = account || PERMIT_SIGNER.address
|
|
52
|
+
|
|
53
|
+
// Only fetch the nonce in case it wasn't pre-fetched before
|
|
54
|
+
// That's the case for static account
|
|
55
|
+
const nonce = preFetchedNonce === undefined ? await eip2612Utils.getTokenNonce(tokenAddress, owner) : preFetchedNonce
|
|
56
|
+
|
|
57
|
+
const deadline = getPermitDeadline()
|
|
58
|
+
const value = params.amount || DEFAULT_PERMIT_VALUE
|
|
59
|
+
|
|
60
|
+
const callData =
|
|
61
|
+
permitInfo.type === 'eip-2612'
|
|
62
|
+
? await buildEip2612PermitCallData({
|
|
63
|
+
eip2612Utils,
|
|
64
|
+
callDataParams: [
|
|
65
|
+
{
|
|
66
|
+
owner,
|
|
67
|
+
spender,
|
|
68
|
+
value: value.toString(),
|
|
69
|
+
nonce,
|
|
70
|
+
deadline,
|
|
71
|
+
},
|
|
72
|
+
chainId as number,
|
|
73
|
+
tokenName,
|
|
74
|
+
tokenAddress,
|
|
75
|
+
permitInfo.version,
|
|
76
|
+
],
|
|
77
|
+
})
|
|
78
|
+
: await buildDaiLikePermitCallData({
|
|
79
|
+
eip2612Utils,
|
|
80
|
+
callDataParams: [
|
|
81
|
+
{
|
|
82
|
+
holder: owner,
|
|
83
|
+
spender,
|
|
84
|
+
allowed: true,
|
|
85
|
+
value: value.toString(),
|
|
86
|
+
nonce,
|
|
87
|
+
expiry: deadline,
|
|
88
|
+
},
|
|
89
|
+
chainId as number,
|
|
90
|
+
tokenName,
|
|
91
|
+
tokenAddress,
|
|
92
|
+
permitInfo.version,
|
|
93
|
+
],
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const gasLimit = await calculateGasLimit(callData, owner, tokenAddress, provider, !!account)
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
target: tokenAddress,
|
|
100
|
+
callData,
|
|
101
|
+
gasLimit,
|
|
102
|
+
dappId: PERMIT_HOOK_DAPP_ID,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function calculateGasLimit(
|
|
107
|
+
data: string,
|
|
108
|
+
from: string,
|
|
109
|
+
to: string,
|
|
110
|
+
provider: JsonRpcProvider,
|
|
111
|
+
isUserAccount: boolean,
|
|
112
|
+
): Promise<string> {
|
|
113
|
+
try {
|
|
114
|
+
// Query the actual gas estimate
|
|
115
|
+
const actual = await provider.estimateGas({ data, from, to })
|
|
116
|
+
|
|
117
|
+
// Add 10% to actual value to account for minor differences with real account
|
|
118
|
+
// Do not add it if this is the real user's account
|
|
119
|
+
const gasLimit = !isUserAccount ? actual.add(actual.div(10)) : actual
|
|
120
|
+
|
|
121
|
+
// Pick the biggest between estimated and default
|
|
122
|
+
return gasLimit.gt(DEFAULT_PERMIT_GAS_LIMIT) ? gasLimit.toString() : DEFAULT_PERMIT_GAS_LIMIT
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.debug(`[calculatePermitGasLimit] Failed to estimateGas, using default`, e)
|
|
125
|
+
|
|
126
|
+
return DEFAULT_PERMIT_GAS_LIMIT
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getCacheKey(params: PermitHookParams): string {
|
|
131
|
+
const { inputToken, chainId, account, amount } = params
|
|
132
|
+
return `${inputToken.address.toLowerCase()}-${chainId}${account ? `-${account.toLowerCase()}` : ''}${amount ? `-${amount.toString()}` : ''}`
|
|
133
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { JsonRpcProvider } from '@ethersproject/providers'
|
|
2
|
+
|
|
3
|
+
import { PERMIT_SIGNER } from '../const'
|
|
4
|
+
import { PermitProviderConnector } from '../utils/PermitProviderConnector'
|
|
5
|
+
|
|
6
|
+
import type { Eip2612PermitUtils } from '@1inch/permit-signed-approvals-utils'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Cache by network. Here we don't care about the provider as a static account will be used for the signature
|
|
10
|
+
*/
|
|
11
|
+
const CHAIN_UTILS_CACHE = new Map<number, Eip2612PermitUtils>()
|
|
12
|
+
/**
|
|
13
|
+
* Cache by provider. Here we cache per provider as each account should have its own instance
|
|
14
|
+
*/
|
|
15
|
+
const PROVIDER_UTILS_CACHE = new Map<string, Eip2612PermitUtils>()
|
|
16
|
+
|
|
17
|
+
export async function getPermitUtilsInstance(
|
|
18
|
+
chainId: number,
|
|
19
|
+
provider: JsonRpcProvider,
|
|
20
|
+
account?: string | undefined,
|
|
21
|
+
): Promise<Eip2612PermitUtils> {
|
|
22
|
+
const chainCache = CHAIN_UTILS_CACHE.get(chainId)
|
|
23
|
+
|
|
24
|
+
if (!account && chainCache) {
|
|
25
|
+
return chainCache
|
|
26
|
+
}
|
|
27
|
+
const providerCacheKey = `${chainId}-${account}`
|
|
28
|
+
const providerCache = PROVIDER_UTILS_CACHE.get(providerCacheKey)
|
|
29
|
+
|
|
30
|
+
if (providerCache) {
|
|
31
|
+
return providerCache
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// TODO: allow to receive the signer as a parameter
|
|
35
|
+
const web3ProviderConnector = new PermitProviderConnector(provider, account ? undefined : PERMIT_SIGNER)
|
|
36
|
+
const Eip2612PermitUtilsClass = await import('../imports/1inchPermitUtils').then((r) => r.Eip2612PermitUtils)
|
|
37
|
+
const eip2612PermitUtils = new Eip2612PermitUtilsClass(web3ProviderConnector, { enabledCheckSalt: true })
|
|
38
|
+
|
|
39
|
+
if (!account) {
|
|
40
|
+
console.log(`[getPermitUtilsInstance] Set cached chain utils for chain ${chainId}`, eip2612PermitUtils)
|
|
41
|
+
CHAIN_UTILS_CACHE.set(chainId, eip2612PermitUtils)
|
|
42
|
+
} else {
|
|
43
|
+
console.log(
|
|
44
|
+
`[getPermitUtilsInstance] Set cached provider utils for chain ${chainId}-${account}`,
|
|
45
|
+
eip2612PermitUtils,
|
|
46
|
+
)
|
|
47
|
+
PROVIDER_UTILS_CACHE.set(providerCacheKey, eip2612PermitUtils)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return eip2612PermitUtils
|
|
51
|
+
}
|