├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
├── 1inch_github_b.svg
├── 1inch_github_w.svg
├── CODEOWNERS
└── workflows
│ ├── pr.yml
│ └── release.yml
├── .gitignore
├── .gitmodules
├── .husky
├── .gitignore
└── pre-commit
├── .prettierignore
├── .prettierrc.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── babel.config.js
├── hardhat.config.ts
├── jest.config.ts
├── package.json
├── src
├── __snapshots__
│ ├── limit-order-predicate-v3.builder.test.ts.snap
│ ├── limit-order-protocol-v3.facade.test.ts.snap
│ ├── limit-order-v3.builder.test.ts.snap
│ ├── series-nonce-manager-predicate.builder.test.ts.snap
│ └── series-nonce-manager.facade.test.ts.snap
├── abi
│ ├── ERC20ABI.json
│ ├── LimitOrderProtocol.json
│ ├── LimitOrderProtocolV3.json
│ └── SeriesNonceManagerABI.json
├── base-limit-order.builder.ts
├── connector
│ ├── __snapshots__
│ │ └── private-key-provider.connector.test.ts.snap
│ ├── private-key-provider.connector.test.ts
│ ├── private-key-provider.connector.ts
│ ├── provider.connector.ts
│ ├── web3-provider.connector.test.ts
│ └── web3-provider.connector.ts
├── e2e-tests
│ └── tests
│ │ ├── helpers
│ │ ├── eip712.ts
│ │ ├── fixtures.ts
│ │ └── utils.ts
│ │ └── limit-order-protocol.test.ts
├── erc20.facade.ts
├── index.ts
├── limit-order-predicate-v3.builder.test.ts
├── limit-order-predicate-v3.builder.ts
├── limit-order-predicate.builder.ts
├── limit-order-predicate.decoder.test.ts
├── limit-order-predicate.decoder.ts
├── limit-order-protocol-addresses.const.ts
├── limit-order-protocol-v3.facade.test.ts
├── limit-order-protocol-v3.facade.ts
├── limit-order-protocol.const.ts
├── limit-order-protocol.facade.ts
├── limit-order-signature.builder.ts
├── limit-order-v3.builder.test.ts
├── limit-order-v3.builder.ts
├── limit-order.builder.ts
├── limit-order.decoder.test.ts
├── limit-order.decoder.ts
├── model
│ ├── abi.model.ts
│ ├── eip712.model.ts
│ ├── eth.model.ts
│ ├── limit-order-protocol.model.ts
│ └── series-nonce-manager.model.ts
├── series-nonce-manager-predicate.builder.test.ts
├── series-nonce-manager-predicate.builder.ts
├── series-nonce-manager.const.ts
├── series-nonce-manager.facade.test.ts
├── series-nonce-manager.facade.ts
├── test
│ ├── helpers.ts
│ └── mocks.ts
└── utils
│ ├── abi.ts
│ ├── abstract-facade.ts
│ ├── abstract-predicate-builder.ts
│ ├── build-taker-traits.ts
│ ├── decoders
│ ├── limit-order-predicate-decoders.ts
│ └── series-nonce-manager-decoders.ts
│ ├── generate-salt.ts
│ ├── get-rpc-code.test.ts
│ ├── get-rpc-code.ts
│ ├── helpers.ts
│ ├── limit-order-rfq.utils.ts
│ ├── limit-order.utils.test.ts
│ ├── limit-order.utils.ts
│ ├── maker-traits.const.ts
│ ├── rpc-url.const.ts
│ └── series-nonce-manager.utils.ts
├── tsconfig.hardhat.json
├── tsconfig.json
├── tsconfig.scripts.json
├── yarn-error.log
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.{js,ts,md,json}]
4 | indent_style = space
5 | indent_size = 4
6 |
7 | [*.yml]
8 | indent_size = 2
9 |
10 | [*.ts]
11 | ij_typescript_use_double_quotes = false
12 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | coverage
4 | jest.config.ts
5 | .eslintrc.js
6 | test
7 | src/e2e-tests/smart-contracts/
8 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | plugins: ['@typescript-eslint', 'unused-imports', 'import'],
5 | extends: [
6 | 'eslint:recommended',
7 | 'plugin:@typescript-eslint/recommended',
8 | 'prettier',
9 | ],
10 | rules: {
11 | '@typescript-eslint/member-ordering': 'error',
12 | 'lines-between-class-members': 'error',
13 | 'padding-line-between-statements': 'error',
14 | 'no-unused-vars': 'off',
15 | 'max-len': ['error', {code: 100}],
16 | 'max-depth': ['error', 3],
17 | 'max-lines-per-function': ['error', 50],
18 | 'max-params': ['error', 5],
19 | '@typescript-eslint/no-explicit-any': 'error',
20 | '@typescript-eslint/no-unused-vars': 'error',
21 | 'unused-imports/no-unused-imports': 'error',
22 | 'unused-imports/no-unused-vars': 0,
23 | 'import/no-cycle': 'error'
24 | },
25 | overrides: [
26 | {
27 | files: ['src/**/*.test.ts'],
28 | rules: {
29 | 'max-lines-per-function': ['error', 400],
30 | 'max-len': ['error', {code: 130}],
31 | },
32 | },
33 | {
34 | files: ['src/e2e-tests/**/*.test.ts'],
35 | rules: {
36 | 'max-lines-per-function': ['error', 2000],
37 | 'max-len': ['error', {code: 130}],
38 | '@typescript-eslint/no-explicit-any': 'warn',
39 | },
40 | },
41 | ],
42 | };
43 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @artall64 @limitofzero
2 |
--------------------------------------------------------------------------------
/.github/workflows/pr.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | ci:
7 | runs-on: ubuntu-20.04
8 | steps:
9 | - name: Checkout code into workspace directory
10 | uses: actions/checkout@v2
11 |
12 | - name: Setup Node
13 | uses: actions/setup-node@v1
14 | with:
15 | node-version: '20.x'
16 |
17 | - name: Install yarn
18 | run: npm install --global yarn
19 |
20 | - name: Install dependency
21 | run: yarn install
22 |
23 | - name: CI
24 | env:
25 | AUTHKEY: ${{ secrets.NODE_AUTH_KEY }}
26 | run: yarn run ci-pipeline
27 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Publish package
2 |
3 | on:
4 | push:
5 | tags:
6 | - v*.*.*
7 |
8 | jobs:
9 | publish-to-npm:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v3
14 |
15 | - uses: actions/setup-node@v3
16 | with:
17 | node-version: 20
18 | registry-url: 'https://registry.npmjs.org'
19 | scope: ${{ github.repository_owner }}
20 |
21 | - name: Update package.json
22 | run: |
23 | TAG_NAME=${GITHUB_REF/refs\/tags\//}
24 | PACKAGE_VERSION=${TAG_NAME#v}
25 | echo "Updating package.json to version $PACKAGE_VERSION"
26 | cat <<< $(jq -r ".version=\"$PACKAGE_VERSION\"" package.json) > package.json
27 | cat package.json
28 |
29 | - name: Install dependencies
30 | run: yarn install --frozen-lockfile
31 |
32 | - name: Build
33 | run: yarn build
34 | env:
35 | AUTHKEY: ${{ secrets.NODE_AUTH_KEY }}
36 |
37 | - name: Publish
38 | run: yarn publish
39 | working-directory: dist
40 | env:
41 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
42 |
43 | publish-to-github:
44 | runs-on: ubuntu-latest
45 | permissions:
46 | contents: read
47 | packages: write
48 | steps:
49 | - name: Checkout
50 | uses: actions/checkout@v3
51 |
52 | - uses: actions/setup-node@v3
53 | with:
54 | node-version: 20
55 | registry-url: 'https://npm.pkg.github.com'
56 | scope: ${{ github.repository_owner }}
57 |
58 | - name: Update package.json
59 | run: |
60 | TAG_NAME=${GITHUB_REF/refs\/tags\//}
61 | PACKAGE_VERSION=${TAG_NAME#v}
62 | echo "Updating package.json to version $PACKAGE_VERSION"
63 | cat <<< $(jq -r ".version=\"$PACKAGE_VERSION\"" package.json) > package.json
64 | cat package.json
65 |
66 | - name: Install dependencies
67 | run: yarn install --frozen-lockfile
68 |
69 | - name: Build
70 | run: yarn build
71 | env:
72 | AUTHKEY: ${{ secrets.NODE_AUTH_KEY }}
73 |
74 | - name: Publish
75 | run: yarn publish
76 | working-directory: dist
77 | env:
78 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
79 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea
3 | coverage
4 | cache
5 | artifacts
6 |
7 | dist
8 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "src/e2e-tests/smart-contracts"]
2 | path = src/e2e-tests/smart-contracts
3 | url = https://github.com/1inch/limit-order-protocol.git
4 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .github
2 | CHANGELOG.md
3 | coverage
4 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "bracketSpacing": false,
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ### [2.0.4](https://github.com/1inch/limit-order-protocol-utils/compare/v2.0.3...v2.0.4) (2022-06-27)
6 |
7 | ### [2.0.3](https://github.com/1inch/limit-order-protocol-utils/compare/v2.0.2...v2.0.3) (2022-01-24)
8 |
9 |
10 | ### Features
11 |
12 | * add limitOrderToWithPermit method ([#24](https://github.com/1inch/limit-order-protocol-utils/issues/24)) ([fe5c427](https://github.com/1inch/limit-order-protocol-utils/commit/fe5c4278fdbd3294b07e6032f7b416cd09b7b3a2))
13 |
14 | ### [2.0.2](https://github.com/1inch/limit-order-protocol-utils/compare/v2.0.1...v2.0.2) (2022-01-20)
15 |
16 |
17 | ### Bug Fixes
18 |
19 | * add checking for hex response for gnosis chain ([#23](https://github.com/1inch/limit-order-protocol-utils/issues/23)) ([335985d](https://github.com/1inch/limit-order-protocol-utils/commit/335985df21efb9bd99df2d664091530da1da5f21))
20 |
21 | ### [2.0.1](https://github.com/1inch/limit-order-protocol-utils/compare/v2.0.0...v2.0.1) (2021-12-24)
22 |
23 |
24 | ### Features
25 |
26 | * **limit-order-builder:** add arbitraryStaticCall method ([6e6655d](https://github.com/1inch/limit-order-protocol-utils/commit/6e6655d4c33ae9f05b2ad2e9fcdd365f7f5731fa))
27 |
28 | ## [2.0.0](https://github.com/1inch/limit-order-protocol-utils/compare/v1.5.0...v2.0.0) (2021-12-10)
29 |
30 |
31 | ### ⚠ BREAKING CHANGES
32 |
33 | * Limit order V2 (#22)
34 |
35 | ### Features
36 |
37 | * Limit order V2 ([#22](https://github.com/1inch/limit-order-protocol-utils/issues/22)) ([4037b77](https://github.com/1inch/limit-order-protocol-utils/commit/4037b775f3dcdcaae80fc38546f325b5997036a9))
38 |
39 | ## [1.5.0](https://github.com/1inch/limit-order-protocol-utils/compare/v1.4.1...v1.5.0) (2021-11-23)
40 |
41 |
42 | ### Features
43 |
44 | * add support for Optimism and Arbitrum networks ([610b49e](https://github.com/1inch/limit-order-protocol-utils/commit/610b49e55d4112f2747efd59845e02af7d9d2327))
45 |
46 | ### [1.4.1](https://github.com/1inch/limit-order-protocol-utils/compare/v1.4.0...v1.4.1) (2021-10-12)
47 |
48 | ## [1.4.0](https://github.com/1inch/limit-order-protocol-utils/compare/v1.3.0...v1.4.0) (2021-08-09)
49 |
50 |
51 | ### ⚠ BREAKING CHANGES
52 |
53 | * change web3 libs version to 1.5.1 (because 2.0.0 is deprecated)
54 |
55 | * change web3 libs version to 1.5.1 (because 2.0.0 is deprecated) ([2e24ee3](https://github.com/1inch/limit-order-protocol-utils/commit/2e24ee308dae235f0eafb00266d50abdd970b1a0))
56 |
57 | ## [1.3.0](https://github.com/1inch/limit-order-protocol-utils/compare/v1.2.1...v1.3.0) (2021-06-14)
58 |
59 |
60 | ### Features
61 |
62 | * **limit-order-rfq-utils:** plain output when proccess is running with argv ([6adfc41](https://github.com/1inch/limit-order-protocol-utils/commit/6adfc412faea42ce37539023440404c39f5136c4))
63 |
64 | ### [1.2.1](https://github.com/1inch/limit-order-protocol-utils/compare/v1.2.0...v1.2.1) (2021-06-10)
65 |
66 | ## [1.2.0](https://github.com/1inch/limit-order-protocol-utils/compare/v1.1.2...v1.2.0) (2021-06-10)
67 |
68 |
69 | ### Features
70 |
71 | * **limit-order-builder:** add possibility to set different taker address for maker asset and taker asset for RFQ order ([44ae7b8](https://github.com/1inch/limit-order-protocol-utils/commit/44ae7b8c9496f264f522ffc8809de6810a86305b))
72 |
73 | ### [1.1.2](https://github.com/1inch/limit-order-protocol-utils/compare/v1.1.1...v1.1.2) (2021-06-10)
74 |
75 |
76 | ### Bug Fixes
77 |
78 | * fix path to limit-order-rfq-utils script ([ae878b3](https://github.com/1inch/limit-order-protocol-utils/commit/ae878b3cab0dc027c7798e3fed70317a22cddad6))
79 |
80 | ### [1.1.1](https://github.com/1inch/limit-order-protocol-utils/compare/v1.1.0...v1.1.1) (2021-06-10)
81 |
82 | ## [1.1.0](https://github.com/1inch/limit-order-protocol-utils/compare/v1.0.2...v1.1.0) (2021-06-10)
83 |
84 |
85 | ### Features
86 |
87 | * PrivateKeyProviderConnector - to create a signature of typed data for a limit order using the private key ([3bbb39c](https://github.com/1inch/limit-order-protocol-utils/commit/3bbb39cb609eb720a2f4465a1935d25371bcbb0a))
88 |
89 | ### [1.0.2](https://github.com/1inch/limit-order-protocol-utils/compare/v1.0.1...v1.0.2) (2021-06-09)
90 |
91 | ### [1.0.1](https://github.com/1inch/limit-order-protocol-utils/compare/v1.0.0...v1.0.1) (2021-06-09)
92 |
93 | ## [1.0.0](https://github.com/1inch/limit-order-protocol-utils/compare/v0.6.1...v1.0.0) (2021-06-09)
94 |
95 | ### [0.6.1](https://github.com/1inch/limit-order-protocol-utils/compare/v0.6.0...v0.6.1) (2021-06-09)
96 |
97 | ## [0.6.0](https://github.com/1inch/limit-order-protocol-utils/compare/v0.5.4...v0.6.0) (2021-06-09)
98 |
99 |
100 | ### ⚠ BREAKING CHANGES
101 |
102 | * renamed many methods and models
103 |
104 | * refactor all docs. Use LimitOrder and RFQOrder terms ([dd58d6d](https://github.com/1inch/limit-order-protocol-utils/commit/dd58d6d8905c099ea3bd062a09e6fefa6355f36e))
105 |
106 | ### [0.5.4](https://github.com/1inch/limit-order-protocol-utils/compare/v0.5.3...v0.5.4) (2021-06-08)
107 |
108 | ### [0.5.3](https://github.com/1inch/limit-order-protocol-utils/compare/v0.5.2...v0.5.3) (2021-06-08)
109 |
110 |
111 | ### Bug Fixes
112 |
113 | * expiresInTimestampMs to expiresInTimestamp (seconds) ([5cb8816](https://github.com/1inch/limit-order-protocol-utils/commit/5cb8816899c7f8a6a6cafbad71961527e27311a9))
114 |
115 | ### [0.5.2](https://github.com/1inch/limit-order-protocol-utils/compare/v0.5.1...v0.5.2) (2021-06-08)
116 |
117 | ### [0.5.1](https://github.com/1inch/limit-order-protocol-utils/compare/v0.5.0...v0.5.1) (2021-06-08)
118 |
119 | ## [0.5.0](https://github.com/1inch/limit-order-protocol-utils/compare/v0.4.0...v0.5.0) (2021-06-08)
120 |
121 |
122 | ### Features
123 |
124 | * **limit-order-rfq:** util for create,fill,cancel limit order RFQ ([aa61ed2](https://github.com/1inch/limit-order-protocol-utils/commit/aa61ed24a5f9285902f8d694b198a1ec1aa5bd00))
125 | * RFQ limit orders ([b4a670e](https://github.com/1inch/limit-order-protocol-utils/commit/b4a670ede66ff4de410a4566bf5cef8662becfba))
126 |
127 | ## [0.4.0](https://github.com/1inch/limit-order-protocol-utils/compare/v0.3.0...v0.4.0) (2021-06-03)
128 |
129 |
130 | ### ⚠ BREAKING CHANGES
131 |
132 | * **limit-order-protocol-facade:** LimitOrderProtocolFacade.simulateTransferFroms() renamed to simulateCalls()
133 |
134 | ### Features
135 |
136 | * **limit-order-protocol-facade:** update limit order protocol contracts ([7be31d8](https://github.com/1inch/limit-order-protocol-utils/commit/7be31d8093cc989bacbbef6a9a2f25764885b9ae))
137 |
138 | ## [0.3.0](https://github.com/1inch/limit-order-protocol-utils/compare/v0.2.1...v0.3.0) (2021-05-20)
139 |
140 |
141 | ### ⚠ BREAKING CHANGES
142 |
143 | * **limit-order-protocol-facade:** LimitOrderProtocolFacade.fillOrder() now has one more argument - thresholdAmount, LimitOrderProtocolFacade.nonces() renamed to LimitOrderProtocolFacade.nonce(), LimitOrderProtocolFacade.advanceNonce() now receive argument - nonce increment count - the number to increase the nonce, new method - LimitOrderProtocolFacade.increaseNonce() - increase nonce by 1
144 |
145 | ### Features
146 |
147 | * **limit-order-protocol-facade:** improve errors parsing for simulateTransferFroms() and checkPredicate() ([24e2db8](https://github.com/1inch/limit-order-protocol-utils/commit/24e2db8572b20ed0ece64f6c8153487b2c9d22e6))
148 | * **limit-order-protocol-facade:** update limit order protocol contracts ([5d2d1c6](https://github.com/1inch/limit-order-protocol-utils/commit/5d2d1c6cb1fa34a3b70b74a42c5788de937453b2))
149 |
150 | ### [0.2.1](https://github.com/1inch/limit-order-protocol-utils/compare/v0.2.0...v0.2.1) (2021-05-15)
151 |
152 |
153 | ### Bug Fixes
154 |
155 | * **limit-order-protocol-facade:** improve parseSimulateTransferError() for any errors type ([3c40dac](https://github.com/1inch/limit-order-protocol-utils/commit/3c40daccdb833f6d525f6f2e55fc7d6fad75ecaf))
156 |
157 | ## [0.2.0](https://github.com/1inch/limit-order-protocol-utils/compare/v0.1.3...v0.2.0) (2021-05-14)
158 |
159 |
160 | ### Features
161 |
162 | * **limit-order-builder:** buildOrderSignature() now provides a hash of typed data to providerConnector.signTypedData(walletAddress, typedData, dataHash) ([ebe7479](https://github.com/1inch/limit-order-protocol-utils/commit/ebe7479daba635b893c5d77a51ab363377b33e37))
163 | * **limit-order-protocol-facade:** domainSeparator() for getting hash of domain separator ([3d462cd](https://github.com/1inch/limit-order-protocol-utils/commit/3d462cde6b02e83b61e85d7ad08b43cea3f9ca40))
164 |
165 | ### [0.1.3](https://github.com/1inch/limit-order-protocol-utils/compare/v0.1.2...v0.1.3) (2021-05-07)
166 |
167 |
168 | ### Bug Fixes
169 |
170 | * **limit-order-protocol-facade:** improved parsing of simulate transfer error ([34bb9d3](https://github.com/1inch/limit-order-protocol-utils/commit/34bb9d355c1e0a581f21536b1848af53a90ccb62))
171 |
172 | ### [0.1.2](https://github.com/1inch/limit-order-protocol-utils/compare/v0.1.1...v0.1.2) (2021-05-04)
173 |
174 | ### [0.1.1](https://github.com/1inch/limit-order-protocol-utils/compare/v0.1.0...v0.1.1) (2021-05-04)
175 |
176 | ## [0.1.0](https://github.com/1inch/limit-order-protocol-utils/compare/v0.0.7...v0.1.0) (2021-05-01)
177 |
178 |
179 | ### ⚠ BREAKING CHANGES
180 |
181 | * **limit-order-predicate-builder:** LimitOrderProtocolFacade now doesn't include methods: andPredicate, timestampBelow, nonceEquals. Use LimitOrderPredicateBuilder instead
182 |
183 | ### Features
184 |
185 | * **limit-order-predicate-builder:** LimitOrderPredicateBuilder for create predicates for limit orders ([881beb0](https://github.com/1inch/limit-order-protocol-utils/commit/881beb0acc50c210befa310d02092e83b346dcbd))
186 |
187 | ### [0.0.7](https://github.com/1inch/limit-order-protocol-utils/compare/v0.0.6...v0.0.7) (2021-04-30)
188 |
189 |
190 | ### Features
191 |
192 | * **limit-order-protocol-facade:** improve parsing result of simulateTransferFroms for ethereum mainnet ([69b41ac](https://github.com/1inch/limit-order-protocol-utils/commit/69b41ac54e5ffbe29715652c84dc8c3190fb23da))
193 |
194 | ### [0.0.6](https://github.com/1inch/limit-order-protocol-utils/compare/v0.0.5...v0.0.6) (2021-04-26)
195 |
196 |
197 | ### Features
198 |
199 | * **limit-order-protocol-facade:** methods for parse contract response ([ff00a78](https://github.com/1inch/limit-order-protocol-utils/commit/ff00a7809ef56b153500d6fef1d2543944285f24))
200 | * **limit-order-protocol-facade:** methods for parse remaining response ([b3f9912](https://github.com/1inch/limit-order-protocol-utils/commit/b3f99126c1d0ab15e4a2aa63d3e68a591ddfa675))
201 |
202 | ### [0.0.5](https://github.com/1inch/limit-order-protocol-utils/compare/v0.0.4...v0.0.5) (2021-04-26)
203 |
204 |
205 | ### Features
206 |
207 | * **limit-order-protocol-facade:** remove unused remainingsRaw() method ([627e084](https://github.com/1inch/limit-order-protocol-utils/commit/627e084b2df6072e920e04e9900a973bd4e60f05))
208 |
209 |
210 | ### Bug Fixes
211 |
212 | * **limit-order-protocol-facade:** fix return type of remaining() method ([4cbcc5d](https://github.com/1inch/limit-order-protocol-utils/commit/4cbcc5d1d8254ac1af90085b960746300b680fe8))
213 |
214 | ### [0.0.4](https://github.com/1inch/limit-order-protocol-utils/compare/v0.0.3...v0.0.4) (2021-04-26)
215 |
216 | ### [0.0.3](https://github.com/1inch/limit-order-protocol-utils/compare/v0.0.2...v0.0.3) (2021-04-26)
217 |
218 |
219 | ### Features
220 |
221 | * **limit-order-protocol-facade:** simulateTransferFroms() check order validity for filling ([263040c](https://github.com/1inch/limit-order-protocol-utils/commit/263040ce1485afdbcc6a5694c483f26aa73642a5))
222 |
223 | ### [0.0.2](https://github.com/1inch/limit-order-protocol-utils/compare/v0.0.1...v0.0.2) (2021-04-23)
224 |
225 |
226 | ### Features
227 |
228 | * **limit-order-protocol-facade:** checkPredicate() for validate predicates ([e8d2eed](https://github.com/1inch/limit-order-protocol-utils/commit/e8d2eedafb0c04d79e91cb05bc72649a47e70ae7))
229 |
230 | ### 0.0.1 (2021-04-23)
231 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | > Thank you for considering contributing to our project. Your help is very much appreciated!
4 |
5 | When contributing, it's better to first discuss the change you wish to make via issue or discussion, or any other method with the owners of this repository before making a change.
6 |
7 | ## Getting started
8 |
9 | In order to make your contribution please make a fork of the repository. After you've pulled
10 | the code, follow these steps to kick start the development:
11 |
12 | 1. Run `yarn ci` to install dependencies
13 | 2. Run `yarn run ci-pipeline` to launch linter, unit tests and build project
14 |
15 | ## Pull Request Process
16 |
17 | 1. We follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.4/) and [Semver](https://semver.org)
18 | in our commit messages, i.e. `feat(core): improve typing`
19 | 2. Make sure you cover all code changes with unit tests
20 | 3. When you are ready, create Pull Request of your fork into original repository
21 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019-2022 1inch
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |

4 |
5 |
6 | # Utils library for 1inch Limit Orders Protocol V4
7 |
8 | 
9 | 
10 | 
11 |
12 | This is the package of utilities for working with the `1inch Limit Orders Protocol`
13 |
14 | You can find general overview and docs on 1inch limit orders protocol [here](https://docs.1inch.io/limit-order-protocol/).
15 | ## Docs
16 | - Read about [1inch Limit Order Protocol](https://docs.1inch.io/docs/limit-order-protocol/introduction) before getting started
17 | - [Docs on `limit-order-protocol-utils`](https://docs.1inch.io/docs/limit-order-protocol/utils/about)
18 |
19 |
20 |
21 | ## Smart contract addresses
22 |
23 | - Latest addresses is always [here](https://github.com/1inch/limit-order-protocol-utils/blob/master/src/limit-order-protocol.const.ts)
24 | - [Smart contracts source code](https://github.com/1inch/limit-order-protocol)
25 |
26 |
27 |
28 | ## Installation
29 | ```sh
30 | npm install @1inch/limit-order-protocol-utils
31 | # or
32 | yarn add @1inch/limit-order-protocol-utils
33 | ```
34 |
35 | > ***Note***
36 | >
37 | > `@1inch/limit-order-protocol` package is now used for smart contract distribution and no linger contains this library.
38 |
39 |
40 |
41 | ---
42 |
43 |
44 | ## Test coverage
45 |
46 | | Statements | Branches | Functions | Lines |
47 | | ------------------------------------------------------------------------ | --------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------- |
48 | |  |  |  |  |
49 |
50 |
51 |
52 | ## Contributing
53 |
54 | See [CONTRIBUTING.md](./CONTRIBUTING.md)
55 |
56 | ## Changelog
57 |
58 | See [CHANGELOG.md](./CHANGELOG.md)
59 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', {targets: {node: 'current'}}],
4 | '@babel/preset-typescript',
5 | ],
6 | };
7 |
--------------------------------------------------------------------------------
/hardhat.config.ts:
--------------------------------------------------------------------------------
1 | import "@nomicfoundation/hardhat-verify";
2 | import "@nomicfoundation/hardhat-chai-matchers";
3 | import "hardhat-dependency-compiler";
4 | import "@nomicfoundation/hardhat-ethers";
5 | import "hardhat-deploy";
6 | import { HardhatUserConfig } from "hardhat/types";
7 |
8 | const config: HardhatUserConfig = {
9 | solidity: {
10 | version: '0.8.23',
11 | settings: {
12 | optimizer: {
13 | enabled: true,
14 | runs: 100,
15 | },
16 | viaIR: true,
17 | },
18 | },
19 | networks: {
20 | hardhat: {
21 | chainId: 1337
22 | }
23 | },
24 | defaultNetwork: "hardhat",
25 | namedAccounts: {
26 | deployer: {
27 | default: 0,
28 | },
29 | },
30 | dependencyCompiler: {
31 | paths: [
32 | '@1inch/solidity-utils/contracts/mocks/TokenCustomDecimalsMock.sol',
33 | '@1inch/solidity-utils/contracts/mocks/TokenMock.sol',
34 | ],
35 | },
36 | paths: {
37 | sources: "./src/e2e-tests/smart-contracts/contracts",
38 | tests: "./src/e2e-tests/tests",
39 | cache: "./cache",
40 | artifacts: "./artifacts"
41 | },
42 | };
43 |
44 | export default config;
45 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * For a detailed explanation regarding each configuration property and type check, visit:
3 | * https://jestjs.io/docs/en/configuration.html
4 | */
5 | import {defaults} from 'jest-config';
6 |
7 | export default {
8 | clearMocks: true,
9 | coverageDirectory: 'coverage',
10 | coveragePathIgnorePatterns: ['/node_modules/', 'src/index.ts'],
11 | coverageProvider: 'v8',
12 | coverageReporters: [
13 | 'json-summary',
14 | // "json",
15 | // "text",
16 | // "lcov",
17 | // "clover"
18 | ],
19 | testEnvironment: 'node',
20 | testMatch: [
21 | '**/__tests__/**/*.[jt]s?(x)',
22 | '**/?(*.)+(spec|test).[tj]s?(x)',
23 | ],
24 | testPathIgnorePatterns: [
25 | 'src/e2e-tests/'
26 | ],
27 | moduleFileExtensions: [...defaults.moduleFileExtensions],
28 | };
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@1inch/limit-order-protocol-utils",
3 | "version": "3.2.3",
4 | "description": "Utility for limit order protocol",
5 | "license": "MIT",
6 | "main": "index.js",
7 | "types": "index.d.ts",
8 | "repository": {
9 | "type": "git",
10 | "url": "ssh://git@github.com:1inch/limit-order-protocol-utils.git",
11 | "directory": "@1inch/limit-order-protocol"
12 | },
13 | "scripts": {
14 | "prebuild": "rm -rf dist && yarn run test:coverage && yarn run test:e2e:ci && yarn run make-badges && yarn run lint",
15 | "build": "tsc --module es2020",
16 | "test-build": "yarn run lint && rm -rf dist && yarn run test:e2e:ci && yarn run make-badges && tsc --module es2020 && yarn run postbuild",
17 | "install-submodules": "git submodule update --init --recursive",
18 | "test:e2e": "hardhat test --tsconfig tsconfig.hardhat.json",
19 | "test:e2e:ci": "yarn run install-submodules && yarn run test:e2e",
20 | "test:stack-trace": "hardhat test --show-stack-traces",
21 | "postbuild": "cp package.json dist && cp README.md dist && chmod +x dist/utils/limit-order-rfq.utils.js",
22 | "lint": "eslint ./src --ext .js,.ts",
23 | "release": "standard-version",
24 | "test": "jest",
25 | "test:coverage": "jest --collectCoverage",
26 | "typecheck": "tsc --noEmit --skipLibCheck",
27 | "prettier": "prettier --write .",
28 | "make-badges": "istanbul-badges-readme",
29 | "ci-pipeline": "yarn run lint && yarn run test && yarn run test:e2e:ci && yarn run typecheck",
30 | "limit-order-rfq-utils": "ts-node --project tsconfig.scripts.json ./src/utils/limit-order-rfq.utils.ts"
31 | },
32 | "dependencies": {
33 | "@1inch/solidity-utils": "3.5.5",
34 | "@chainlink/contracts": "0.6.1",
35 | "@ethersproject/abi": "^5.1.1",
36 | "@ethersproject/bignumber": "^5.1.1",
37 | "@metamask/eth-sig-util": "^4.0.1",
38 | "ethers": "6.9.0",
39 | "prompts": "^2.4.1",
40 | "web3": "^1.8.1",
41 | "yargs": "^17.0.1"
42 | },
43 | "devDependencies": {
44 | "@babel/core": "7.23.7",
45 | "@babel/helper-get-function-arity": "^7.16.7",
46 | "@babel/preset-env": "7.23.8",
47 | "@babel/preset-typescript": "7.23.3",
48 | "@nomicfoundation/hardhat-chai-matchers": "2.0.3",
49 | "@nomicfoundation/hardhat-ethers": "3.0.5",
50 | "@nomicfoundation/hardhat-network-helpers": "1.0.10",
51 | "@nomicfoundation/hardhat-verify": "^2.0.3",
52 | "@openzeppelin/contracts": "5.0.1",
53 | "@types/jest": "^29.5.11",
54 | "@types/prompts": "^2.4.9",
55 | "@typescript-eslint/eslint-plugin": "6.18.1",
56 | "babel-jest": "^29.7.0",
57 | "chai": "4.3.7",
58 | "eslint": "8.56.0",
59 | "eslint-config-prettier": "^9.1.0",
60 | "eslint-config-standard-with-typescript": "^43.0.0",
61 | "eslint-plugin-import": "^2.29.1",
62 | "eslint-plugin-node": "11.1.0",
63 | "eslint-plugin-promise": "6.1.1",
64 | "eslint-plugin-unused-imports": "^3.0.0",
65 | "hardhat": "2.19.4",
66 | "hardhat-dependency-compiler": "1.1.3",
67 | "hardhat-deploy": "0.11.45",
68 | "hardhat-tracer": "2.7.0",
69 | "husky": "^6.0.0",
70 | "istanbul-badges-readme": "^1.8.5",
71 | "jest": "^29.7.0",
72 | "lint-staged": "^10.5.4",
73 | "prettier": "^2.2.1",
74 | "standard-version": "^9.2.0",
75 | "ts-loader": "^9.0.2",
76 | "ts-mockito": "^2.6.1",
77 | "ts-node": "10.9.2",
78 | "tslib": "2.6.2",
79 | "typescript": "5.3.3"
80 | },
81 | "bin": {
82 | "limit-order-rfq-utils": "./utils/limit-order-rfq.utils.js"
83 | },
84 | "husky": {
85 | "hooks": {
86 | "pre-commit": "lint-staged && yarn run typecheck"
87 | }
88 | },
89 | "lint-staged": {
90 | "*.{js,ts,md,json}": [
91 | "yarn run prettier"
92 | ],
93 | "*.{js,ts}": [
94 | "yarn run lint"
95 | ]
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/__snapshots__/limit-order-predicate-v3.builder.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`PredicateBuilderV3 - for build limit order v3 predicate Simple predicate must includes all values and match the snapshot 1`] = `"0x63592c2b00000000000000000000000000000000000000000000000000000000608d1a46"`;
4 |
5 | exports[`PredicateBuilderV3 - for build limit order v3 predicate Simplyfied predicate must includes all values and match the snapshot 1`] = `"0x2cc2878d0000608d1a4600000000000efb3c7eb936caa12b5a884d612393969a557d4307"`;
6 |
--------------------------------------------------------------------------------
/src/__snapshots__/limit-order-protocol-v3.facade.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`LimitOrderProtocolV3Facade - facade for Limit order protocol contract cancelLimitOrder Must create a call data for order canceling 1`] = `"0x2d9a56f600000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000e9e7cea3dedca5984780bafc599bd69add087d56000000000000000000000000111111111117dc0aa78b770fa6a738034120c302000000000000000000000000fb3c7eb936caa12b5a884d612393969a557d4307000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a764000000000024000000240000002400000024000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000002463592c2b00000000000000000000000000000000000000000000000000000000609f1be800000000000000000000000000000000000000000000000000000000"`;
4 |
--------------------------------------------------------------------------------
/src/__snapshots__/limit-order-v3.builder.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`LimitOrderV3Builder - for build new limit order Normal limit order buildLimitOrder() must create a limit order instance according to the given parameters 1`] = `
4 | {
5 | "getMakingAmount": "0x",
6 | "getTakingAmount": "0x",
7 | "makerAssetData": "0x",
8 | "permit": "0x22",
9 | "postInteraction": "0x33",
10 | "preInteraction": "0x",
11 | "predicate": "0x11",
12 | "takerAssetData": "0x",
13 | }
14 | `;
15 |
16 | exports[`LimitOrderV3Builder - for build new limit order Normal limit order buildLimitOrder() must create a limit order instance according to the given parameters 2`] = `
17 | {
18 | "allowedSender": "0x0000000000000000000000000000000000000000",
19 | "interactions": "0x112233",
20 | "maker": "0xdddd91605c18a9999c1d47abfeed5daaaa700000",
21 | "makerAsset": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
22 | "makingAmount": "3",
23 | "offsets": "0x300000002000000020000000100000000000000000000000000000000",
24 | "receiver": "0x0000000000000000000000000000000000000000",
25 | "salt": "1",
26 | "takerAsset": "0x111111111117dc0aa78b770fa6a738034120c302",
27 | "takingAmount": "100",
28 | }
29 | `;
30 |
31 | exports[`LimitOrderV3Builder - for build new limit order Normal limit order buildLimitOrderHash() must create a hash of order with 0x prefix 1`] = `"0xb817fcd255f7253abdc56c0686d42eb49fe2fd91efa5148953d23dfa46f9fd81"`;
32 |
33 | exports[`LimitOrderV3Builder - for build new limit order Normal limit order buildOrderSignature() must call the provider signTypedData method 1`] = `"0x05712732e0cd9dfb4fdfcda3be3f62a734c1169474c5824816420d313cf6b33b35a91cf113ba11693793785914e408543b4c9418a8d10fc467b2709ed38c011a1b"`;
34 |
--------------------------------------------------------------------------------
/src/__snapshots__/series-nonce-manager-predicate.builder.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`SeriesNonceManagerFacade nonce 1`] = `"0x4a7f2a4b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000001c667c6308d6c9c8ce5bd207f524041f67dbc65e"`;
4 |
--------------------------------------------------------------------------------
/src/__snapshots__/series-nonce-manager.facade.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`SeriesNonceManagerFacade advanceNonce 1`] = `"0x5d3a09dc00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003"`;
4 |
5 | exports[`SeriesNonceManagerFacade increaseNonce 1`] = `"0x7a37dc2c0000000000000000000000000000000000000000000000000000000000000001"`;
6 |
7 | exports[`SeriesNonceManagerFacade nonceEquals 1`] = `"0x9762222100000000000000000000000000000000000000000000000000000000000000010000000000000000000000001c667c6308d6c9c8ce5bd207f524041f67dbc65e0000000000000000000000000000000000000000000000000000000000000065"`;
8 |
--------------------------------------------------------------------------------
/src/abi/ERC20ABI.json:
--------------------------------------------------------------------------------
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 | "constant": false,
18 | "inputs": [
19 | {
20 | "name": "_spender",
21 | "type": "address"
22 | },
23 | {
24 | "name": "_value",
25 | "type": "uint256"
26 | }
27 | ],
28 | "name": "approve",
29 | "outputs": [
30 | {
31 | "name": "",
32 | "type": "bool"
33 | }
34 | ],
35 | "payable": false,
36 | "stateMutability": "nonpayable",
37 | "type": "function"
38 | },
39 | {
40 | "constant": true,
41 | "inputs": [],
42 | "name": "totalSupply",
43 | "outputs": [
44 | {
45 | "name": "",
46 | "type": "uint256"
47 | }
48 | ],
49 | "payable": false,
50 | "stateMutability": "view",
51 | "type": "function"
52 | },
53 | {
54 | "constant": false,
55 | "inputs": [
56 | {
57 | "name": "_from",
58 | "type": "address"
59 | },
60 | {
61 | "name": "_to",
62 | "type": "address"
63 | },
64 | {
65 | "name": "_value",
66 | "type": "uint256"
67 | }
68 | ],
69 | "name": "transferFrom",
70 | "outputs": [
71 | {
72 | "name": "",
73 | "type": "bool"
74 | }
75 | ],
76 | "payable": false,
77 | "stateMutability": "nonpayable",
78 | "type": "function"
79 | },
80 | {
81 | "constant": true,
82 | "inputs": [],
83 | "name": "decimals",
84 | "outputs": [
85 | {
86 | "name": "",
87 | "type": "uint8"
88 | }
89 | ],
90 | "payable": false,
91 | "stateMutability": "view",
92 | "type": "function"
93 | },
94 | {
95 | "constant": true,
96 | "inputs": [
97 | {
98 | "name": "_owner",
99 | "type": "address"
100 | }
101 | ],
102 | "name": "balanceOf",
103 | "outputs": [
104 | {
105 | "name": "balance",
106 | "type": "uint256"
107 | }
108 | ],
109 | "payable": false,
110 | "stateMutability": "view",
111 | "type": "function"
112 | },
113 | {
114 | "constant": true,
115 | "inputs": [],
116 | "name": "symbol",
117 | "outputs": [
118 | {
119 | "name": "",
120 | "type": "string"
121 | }
122 | ],
123 | "payable": false,
124 | "stateMutability": "view",
125 | "type": "function"
126 | },
127 | {
128 | "constant": false,
129 | "inputs": [
130 | {
131 | "name": "_to",
132 | "type": "address"
133 | },
134 | {
135 | "name": "_value",
136 | "type": "uint256"
137 | }
138 | ],
139 | "name": "transfer",
140 | "outputs": [
141 | {
142 | "name": "",
143 | "type": "bool"
144 | }
145 | ],
146 | "payable": false,
147 | "stateMutability": "nonpayable",
148 | "type": "function"
149 | },
150 | {
151 | "constant": true,
152 | "inputs": [
153 | {
154 | "name": "_owner",
155 | "type": "address"
156 | },
157 | {
158 | "name": "_spender",
159 | "type": "address"
160 | }
161 | ],
162 | "name": "allowance",
163 | "outputs": [
164 | {
165 | "name": "",
166 | "type": "uint256"
167 | }
168 | ],
169 | "payable": false,
170 | "stateMutability": "view",
171 | "type": "function"
172 | },
173 | {
174 | "payable": true,
175 | "stateMutability": "payable",
176 | "type": "fallback"
177 | },
178 | {
179 | "anonymous": false,
180 | "inputs": [
181 | {
182 | "indexed": true,
183 | "name": "owner",
184 | "type": "address"
185 | },
186 | {
187 | "indexed": true,
188 | "name": "spender",
189 | "type": "address"
190 | },
191 | {
192 | "indexed": false,
193 | "name": "value",
194 | "type": "uint256"
195 | }
196 | ],
197 | "name": "Approval",
198 | "type": "event"
199 | },
200 | {
201 | "anonymous": false,
202 | "inputs": [
203 | {
204 | "indexed": true,
205 | "name": "from",
206 | "type": "address"
207 | },
208 | {
209 | "indexed": true,
210 | "name": "to",
211 | "type": "address"
212 | },
213 | {
214 | "indexed": false,
215 | "name": "value",
216 | "type": "uint256"
217 | }
218 | ],
219 | "name": "Transfer",
220 | "type": "event"
221 | }
222 | ]
223 |
--------------------------------------------------------------------------------
/src/abi/SeriesNonceManagerABI.json:
--------------------------------------------------------------------------------
1 | [
2 | {"inputs": [], "name": "AdvanceNonceFailed", "type": "error"},
3 | {
4 | "anonymous": false,
5 | "inputs": [
6 | {
7 | "indexed": true,
8 | "internalType": "address",
9 | "name": "maker",
10 | "type": "address"
11 | },
12 | {
13 | "indexed": false,
14 | "internalType": "uint256",
15 | "name": "series",
16 | "type": "uint256"
17 | },
18 | {
19 | "indexed": false,
20 | "internalType": "uint256",
21 | "name": "newNonce",
22 | "type": "uint256"
23 | }
24 | ],
25 | "name": "NonceIncreased",
26 | "type": "event"
27 | },
28 | {
29 | "inputs": [
30 | {"internalType": "uint256", "name": "series", "type": "uint256"},
31 | {"internalType": "uint256", "name": "amount", "type": "uint256"}
32 | ],
33 | "name": "advanceNonce",
34 | "outputs": [],
35 | "stateMutability": "nonpayable",
36 | "type": "function"
37 | },
38 | {
39 | "inputs": [
40 | {"internalType": "uint8", "name": "series", "type": "uint8"}
41 | ],
42 | "name": "increaseNonce",
43 | "outputs": [],
44 | "stateMutability": "nonpayable",
45 | "type": "function"
46 | },
47 | {
48 | "inputs": [
49 | {"internalType": "uint256", "name": "", "type": "uint256"},
50 | {"internalType": "address", "name": "", "type": "address"}
51 | ],
52 | "name": "nonce",
53 | "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
54 | "stateMutability": "view",
55 | "type": "function"
56 | },
57 | {
58 | "inputs": [
59 | {"internalType": "uint256", "name": "series", "type": "uint256"},
60 | {
61 | "internalType": "address",
62 | "name": "makerAddress",
63 | "type": "address"
64 | },
65 | {"internalType": "uint256", "name": "makerNonce", "type": "uint256"}
66 | ],
67 | "name": "nonceEquals",
68 | "outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
69 | "stateMutability": "view",
70 | "type": "function"
71 | },
72 | {
73 | "inputs": [
74 | {"internalType": "uint256", "name": "time", "type": "uint256"}
75 | ],
76 | "name": "timestampBelow",
77 | "outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
78 | "stateMutability": "view",
79 | "type": "function"
80 | },
81 | {
82 | "inputs": [
83 | {
84 | "internalType": "uint256",
85 | "name": "timeNonceSeriesAccount",
86 | "type": "uint256"
87 | }
88 | ],
89 | "name": "timestampBelowAndNonceEquals",
90 | "outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
91 | "stateMutability": "view",
92 | "type": "function"
93 | }
94 | ]
95 |
--------------------------------------------------------------------------------
/src/base-limit-order.builder.ts:
--------------------------------------------------------------------------------
1 | import {ProviderConnector} from "./connector/provider.connector";
2 | import {EIP712ParamsExtended} from "./limit-order.builder";
3 | import {LimitOrderSignatureBuilder} from "./limit-order-signature.builder";
4 | import {LimitOrderHash, LimitOrderSignature} from "./model/limit-order-protocol.model";
5 | import {Address} from "./model/eth.model";
6 | import {EIP712Object, EIP712TypedData, MessageTypes} from "./model/eip712.model";
7 | import {SignTypedDataVersion, TypedDataUtils, TypedMessage} from "@metamask/eth-sig-util";
8 | import {bufferToHex} from "ethereumjs-util";
9 | import {EIP712_DOMAIN, ZX} from "./limit-order-protocol.const";
10 | import {getOffsets, trim0x} from "./utils/limit-order.utils";
11 |
12 | export abstract class BaseLimitOrderBuilder {
13 | private readonly signatureBuilder: LimitOrderSignatureBuilder;
14 |
15 | constructor(
16 | protected readonly providerConnector: ProviderConnector,
17 | protected readonly eip712ParamsExtended: EIP712ParamsExtended,
18 | ) {
19 | this.signatureBuilder = new LimitOrderSignatureBuilder(
20 | this.providerConnector,
21 | this.eip712ParamsExtended,
22 | );
23 | }
24 |
25 | static joinStaticCalls(data: string[]): { offsets: bigint, data: string } {
26 | const trimmed = data.map(trim0x);
27 |
28 | return {
29 | offsets: getOffsets(trimmed),
30 | data: ZX + trimmed.join(''),
31 | };
32 | }
33 |
34 | buildLimitOrderTypedData(
35 | order: OrderType,
36 | chainId: bigint,
37 | verifyingContract: Address,
38 | ): EIP712TypedData {
39 | return {
40 | primaryType: 'Order',
41 | types: {
42 | EIP712Domain: EIP712_DOMAIN,
43 | Order: this.eip712ParamsExtended.orderStructure,
44 | },
45 | domain: {
46 | name: this.eip712ParamsExtended.domainName,
47 | version: this.eip712ParamsExtended.version,
48 | chainId: Number(chainId),
49 | verifyingContract: verifyingContract,
50 | },
51 | message: order,
52 | };
53 | }
54 |
55 | buildTypedDataAndSign(
56 | order: OrderType,
57 | chainId: bigint,
58 | verifyingContract: Address,
59 | wallet: Address,
60 | ): Promise {
61 | const typedData = this.buildLimitOrderTypedData(order, chainId, verifyingContract);
62 | return this.signatureBuilder.buildOrderSignature(
63 | wallet,
64 | typedData,
65 | );
66 | }
67 |
68 | buildOrderSignature(
69 | wallet: Address,
70 | typedData: EIP712TypedData
71 | ): Promise {
72 | return this.signatureBuilder.buildOrderSignature(
73 | wallet,
74 | typedData,
75 | );
76 | }
77 |
78 | buildLimitOrderHash(orderTypedData: EIP712TypedData): LimitOrderHash {
79 | const message = orderTypedData as TypedMessage;
80 | const hash = bufferToHex(TypedDataUtils.eip712Hash(message, SignTypedDataVersion.V4));
81 | return ZX + hash.substring(2);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/connector/__snapshots__/private-key-provider.connector.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`PrivateKeyProviderConnector signTypedData() must sign typed data by private key 1`] = `"0x3aa68183623054d13de78a66c257aea98b5a45722b330d5d9f424d87482553987d5bfe04d492e35d0031b91ba06904ffa295d9e9914ce0cb653709a439e94f6d1b"`;
4 |
--------------------------------------------------------------------------------
/src/connector/private-key-provider.connector.test.ts:
--------------------------------------------------------------------------------
1 | import Web3 from 'web3';
2 | import {instance, mock, verify, when} from 'ts-mockito';
3 | import {
4 | EIP712_DOMAIN,
5 | LIMIT_ORDER_PROTOCOL_V3_ABI,
6 | } from '../limit-order-protocol.const';
7 | import {Eth} from 'web3-eth';
8 | import {PrivateKeyProviderConnector} from './private-key-provider.connector';
9 | import {LimitOrderLegacy} from '../model/limit-order-protocol.model';
10 | import {EIP712TypedData, ORDER_STRUCTURE_LEGACY} from '../model/eip712.model';
11 |
12 | describe('PrivateKeyProviderConnector', () => {
13 | let web3Provider: Web3;
14 | let privateKeyProviderConnector: PrivateKeyProviderConnector;
15 |
16 | const testPrivateKey =
17 | 'd8d1f95deb28949ea0ecc4e9a0decf89e98422c2d76ab6e5f736792a388c56c7';
18 | const limitOrder: LimitOrderLegacy = {
19 | salt: "618054093254",
20 | makerAsset: "0xe9e7cea3dedca5984780bafc599bd69add087d56",
21 | takerAsset: "0x111111111117dc0aa78b770fa6a738034120c302",
22 | maker: "0xfb3c7eb936cAA12B5A884d612393969A557d4307",
23 | receiver: "0x0000000000000000000000000000000000000000",
24 | allowedSender: "0x0000000000000000000000000000000000000000",
25 | makingAmount: "1000000000000000000",
26 | takingAmount: "1000000000000000000",
27 | offsets: "9813420589127697917471531885823684359047649055178615469676279994777600",
28 | // eslint-disable-next-line max-len
29 | interactions: "0x20b83f2d0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a76400007e2d21830000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000bfa7514300000000000000000000000000000000000000000000000000000068000000240000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006863592c2b0000000000000000000000000000000000000000000000000000000063593ad9cf6fc6e3000000000000000000000000fb3c7eb936caa12b5a884d612393969a557d43070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
30 | };
31 | const typedData: EIP712TypedData = {
32 | primaryType: 'Order',
33 | types: {
34 | EIP712Domain: EIP712_DOMAIN,
35 | Order: ORDER_STRUCTURE_LEGACY,
36 | },
37 | domain: {
38 | name: '1inch Aggregation Router',
39 | version: '5',
40 | chainId: 1,
41 | verifyingContract: '',
42 | },
43 | message: limitOrder,
44 | };
45 |
46 | beforeEach(() => {
47 | web3Provider = mock();
48 | privateKeyProviderConnector = new PrivateKeyProviderConnector(
49 | testPrivateKey,
50 | instance(web3Provider)
51 | );
52 | });
53 |
54 | it('signTypedData() must sign typed data by private key', async () => {
55 | const walletAddress = '0xa07c1d51497fb6e66aa2329cecb86fca0a957fdb';
56 |
57 | const signature = await privateKeyProviderConnector.signTypedData(
58 | walletAddress,
59 | typedData
60 | );
61 |
62 | expect(signature).toMatchSnapshot();
63 | });
64 |
65 | it('contractEncodeABI() changed address from null to undefined for contract instance', async () => {
66 | const eth = mock();
67 | class ContractMock {
68 | methods = {
69 | foo: () => ({encodeABI: () => ''}),
70 | };
71 | }
72 |
73 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
74 | when(eth.Contract).thenReturn(ContractMock as any);
75 | when(web3Provider.eth).thenReturn(instance(eth));
76 |
77 | privateKeyProviderConnector.contractEncodeABI(
78 | LIMIT_ORDER_PROTOCOL_V3_ABI,
79 | null,
80 | 'foo',
81 | []
82 | );
83 |
84 | verify(eth.Contract).once();
85 | });
86 | });
87 |
--------------------------------------------------------------------------------
/src/connector/private-key-provider.connector.ts:
--------------------------------------------------------------------------------
1 | import {ProviderConnector, AbiDecodeResult} from './provider.connector';
2 | import Web3 from 'web3';
3 | import {EIP712TypedData} from '../model/eip712.model';
4 | import {AbiItem, AbiOutput} from '../model/abi.model';
5 | import {AbiItem as Web3AbiItem} from 'web3-utils';
6 | import {signTypedData, SignTypedDataVersion} from '@metamask/eth-sig-util';
7 | import { trim0x } from '../utils/limit-order.utils';
8 |
9 | export class PrivateKeyProviderConnector implements ProviderConnector {
10 | constructor(
11 | private readonly privateKey: string,
12 | protected readonly web3Provider: Web3
13 | ) {}
14 |
15 | contractEncodeABI(
16 | abi: AbiItem[],
17 | address: string | null,
18 | methodName: string,
19 | methodParams: unknown[]
20 | ): string {
21 | const contract = new this.web3Provider.eth.Contract(
22 | abi as Web3AbiItem[],
23 | address === null ? undefined : address
24 | );
25 |
26 | return contract.methods[methodName](...methodParams).encodeABI();
27 | }
28 |
29 | signTypedData(
30 | _walletAddress: string,
31 | typedData: EIP712TypedData,
32 | /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
33 | _typedDataHash = ''
34 | ): Promise {
35 | const result = signTypedData({
36 | privateKey: Buffer.from(this.privateKey, 'hex'),
37 | data: typedData,
38 | version: SignTypedDataVersion.V4,
39 | });
40 |
41 | return Promise.resolve(result);
42 | }
43 |
44 | ethCall(contractAddress: string, callData: string): Promise {
45 | return this.web3Provider.eth.call({
46 | to: contractAddress,
47 | data: callData,
48 | });
49 | }
50 |
51 | decodeABIParameter(type: AbiOutput | string, hex: string): T {
52 | return this.web3Provider.eth.abi.decodeParameter(type, hex) as T;
53 | }
54 |
55 | decodeABICallParameters(types: Array, callData: string): AbiDecodeResult {
56 | const parameters = trim0x(callData).substring(8);
57 | return this.web3Provider.eth.abi.decodeParameters([...types], parameters);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/connector/provider.connector.ts:
--------------------------------------------------------------------------------
1 | import {EIP712TypedData} from '../model/eip712.model';
2 | import {AbiItem, AbiOutput} from '../model/abi.model';
3 |
4 | export interface AbiDecodeResult {
5 | [key: string]: unknown,
6 | }
7 |
8 | export interface ProviderConnector {
9 | contractEncodeABI(
10 | abi: AbiItem[],
11 | address: string | null,
12 | methodName: string,
13 | methodParams: unknown[]
14 | ): string;
15 |
16 | signTypedData(
17 | walletAddress: string,
18 | typedData: EIP712TypedData,
19 | typedDataHash: string
20 | ): Promise;
21 |
22 | ethCall(contractAddress: string, callData: string): Promise;
23 |
24 | decodeABIParameter(type: AbiOutput | string, hex: string): T;
25 |
26 | decodeABICallParameters(types: Array, callData: string): AbiDecodeResult;
27 | }
28 |
--------------------------------------------------------------------------------
/src/connector/web3-provider.connector.test.ts:
--------------------------------------------------------------------------------
1 | import {Web3ProviderConnector} from './web3-provider.connector';
2 | import Web3 from 'web3';
3 | import {anything, instance, mock, verify, when} from 'ts-mockito';
4 | import {
5 | EIP712_DOMAIN,
6 | LIMIT_ORDER_PROTOCOL_ABI,
7 | } from '../limit-order-protocol.const';
8 | import {Eth} from 'web3-eth';
9 | import {ORDER_STRUCTURE_LEGACY} from "../model/eip712.model";
10 |
11 | describe('Web3ProviderConnector', () => {
12 | let web3Provider: Web3;
13 | let web3ProviderConnector: Web3ProviderConnector;
14 |
15 | const typedData = {
16 | primaryType: 'Order',
17 | types: {
18 | EIP712Domain: EIP712_DOMAIN,
19 | Order: ORDER_STRUCTURE_LEGACY
20 | },
21 | domain: {
22 | name: '1inch Aggregation Router',
23 | version: '5',
24 | chainId: 1,
25 | verifyingContract: '',
26 | },
27 | message: {},
28 | };
29 |
30 | beforeEach(() => {
31 | web3Provider = mock();
32 | web3ProviderConnector = new Web3ProviderConnector(
33 | instance(web3Provider)
34 | );
35 | });
36 |
37 | it('signTypedData() must call eth_signTypedData_v4 rpc method', async () => {
38 | const walletAddress = '0xasd';
39 |
40 | const extendedWeb3 = {
41 | signTypedDataV4: jest.fn(),
42 | };
43 |
44 | when(web3Provider.extend(anything())).thenReturn(extendedWeb3);
45 |
46 | await web3ProviderConnector.signTypedData(walletAddress, typedData, '');
47 |
48 | expect(extendedWeb3.signTypedDataV4).toHaveBeenCalledWith(
49 | walletAddress,
50 | JSON.stringify(typedData)
51 | );
52 | });
53 |
54 | it('contractEncodeABI() changed address from null to undefined for contract instance', async () => {
55 | const eth = mock();
56 | class ContractMock {
57 | methods = {
58 | foo: () => ({encodeABI: () => ''}),
59 | };
60 | }
61 |
62 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
63 | when(eth.Contract).thenReturn(ContractMock as any);
64 | when(web3Provider.eth).thenReturn(instance(eth));
65 |
66 | web3ProviderConnector.contractEncodeABI(
67 | LIMIT_ORDER_PROTOCOL_ABI,
68 | null,
69 | 'foo',
70 | []
71 | );
72 |
73 | verify(eth.Contract).once();
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/src/connector/web3-provider.connector.ts:
--------------------------------------------------------------------------------
1 | import {AbiDecodeResult, ProviderConnector} from './provider.connector';
2 | import Web3 from 'web3';
3 | import {EIP712TypedData} from '../model/eip712.model';
4 | import {AbiItem, AbiOutput} from '../model/abi.model';
5 | import {AbiItem as Web3AbiItem} from 'web3-utils';
6 | import { trim0x } from '../utils/limit-order.utils';
7 |
8 | interface ExtendedWeb3 extends Web3 {
9 | signTypedDataV4(walletAddress: string, typedData: string): Promise;
10 | }
11 |
12 | export class Web3ProviderConnector implements ProviderConnector {
13 | constructor(protected readonly web3Provider: Web3) {}
14 |
15 | contractEncodeABI(
16 | abi: AbiItem[],
17 | address: string | null,
18 | methodName: string,
19 | methodParams: unknown[]
20 | ): string {
21 | const contract = new this.web3Provider.eth.Contract(
22 | abi as Web3AbiItem[],
23 | address === null ? undefined : address
24 | );
25 |
26 | return contract.methods[methodName](...methodParams).encodeABI();
27 | }
28 |
29 | signTypedData(
30 | walletAddress: string,
31 | typedData: EIP712TypedData,
32 | /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
33 | _typedDataHash: string
34 | ): Promise {
35 | const extendedWeb3: ExtendedWeb3 = this.web3Provider.extend({
36 | methods: [
37 | {
38 | name: 'signTypedDataV4',
39 | call: 'eth_signTypedData_v4',
40 | params: 2,
41 | },
42 | ],
43 | });
44 |
45 | return extendedWeb3.signTypedDataV4(
46 | walletAddress,
47 | JSON.stringify(typedData)
48 | );
49 | }
50 |
51 | ethCall(contractAddress: string, callData: string): Promise {
52 | return this.web3Provider.eth.call({
53 | to: contractAddress,
54 | data: callData,
55 | });
56 | }
57 |
58 | decodeABIParameter(type: AbiOutput | string, hex: string): T {
59 | return this.web3Provider.eth.abi.decodeParameter(type, hex) as T;
60 | }
61 |
62 | decodeABICallParameters(types: Array, callData: string): AbiDecodeResult {
63 | const parameters = trim0x(callData).substring(8);
64 | return this.web3Provider.eth.abi.decodeParameters([...types], parameters);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/e2e-tests/tests/helpers/eip712.ts:
--------------------------------------------------------------------------------
1 | import { cutSelector } from './utils';
2 | import {Address} from "../../../model/limit-order-protocol.model";
3 | import {Contract, Signature} from "ethers";
4 | import {ethers} from "hardhat";
5 |
6 | type Signer = Awaited>[0];
7 |
8 | const Permit = [
9 | { name: 'owner', type: 'address' },
10 | { name: 'spender', type: 'address' },
11 | { name: 'value', type: 'uint256' },
12 | { name: 'nonce', type: 'uint256' },
13 | { name: 'deadline', type: 'uint256' },
14 | ];
15 |
16 | // eslint-disable-next-line max-params
17 | function buildData(
18 | owner: Address,
19 | name: string,
20 | version: string,
21 | chainId: number,
22 | verifyingContract: Address,
23 | spender: Address,
24 | nonce: number,
25 | value: string,
26 | deadline: string
27 | ) {
28 | return {
29 | domain: { name, version, chainId, verifyingContract },
30 | types: { Permit },
31 | value: { owner, spender, value, nonce, deadline },
32 | };
33 | }
34 |
35 | const defaultDeadline = '18446744073709551615';
36 |
37 | // eslint-disable-next-line max-params
38 | export async function getPermit(
39 | owner: Address,
40 | wallet: Signer,
41 | token: Contract,
42 | tokenVersion: string,
43 | chainId: bigint,
44 | spender: Address,
45 | value: string,
46 | deadline = defaultDeadline
47 | ): Promise {
48 | const nonce = await token.nonces(owner);
49 | const name = await token.name();
50 | const data = buildData(
51 | owner,
52 | name,
53 | tokenVersion,
54 | Number(chainId),
55 | await token.getAddress(),
56 | spender,
57 | nonce,
58 | value,
59 | deadline
60 | );
61 | const signature = await wallet.signTypedData(data.domain, data.types, data.value);
62 | const { v, r, s } = Signature.from(signature);
63 | const permitCall = token.interface.encodeFunctionData(
64 | 'permit', [owner, spender, value, deadline, v, r, s]
65 | );
66 | return cutSelector(permitCall);
67 | }
68 |
--------------------------------------------------------------------------------
/src/e2e-tests/tests/helpers/fixtures.ts:
--------------------------------------------------------------------------------
1 | import { ethers } from "hardhat";
2 |
3 | export async function deploySwapTokens () {
4 | const TokenMock = await ethers.getContractFactory('TokenMock');
5 | const dai = await TokenMock.deploy('DAI', 'DAI');
6 | await dai.waitForDeployment();
7 | const WrappedTokenMock = await ethers.getContractFactory('WrappedTokenMock');
8 | const weth = await WrappedTokenMock.deploy('WETH', 'WETH');
9 |
10 | await weth.waitForDeployment();
11 | const LimitOrderProtocol = await ethers.getContractFactory('LimitOrderProtocol');
12 | const swap = await LimitOrderProtocol.deploy(await weth.getAddress());
13 | await swap.waitForDeployment();
14 | const chainId = (await ethers.provider.getNetwork()).chainId;
15 | return { dai, weth, swap, chainId };
16 | };
17 |
18 | export async function deployArbitraryPredicate () {
19 | const ArbitraryPredicateMock = await ethers.getContractFactory('ArbitraryPredicateMock');
20 | const arbitraryPredicate = await ArbitraryPredicateMock.deploy();
21 | await arbitraryPredicate.waitForDeployment();
22 | return { arbitraryPredicate };
23 | };
24 |
25 |
--------------------------------------------------------------------------------
/src/e2e-tests/tests/helpers/utils.ts:
--------------------------------------------------------------------------------
1 | import {EIP712TypedData} from "../../../model/eip712.model";
2 | import {setN} from "../../../utils/limit-order.utils";
3 | import {
4 | LimitOrderProtocolFacade
5 | } from "../../../limit-order-protocol.facade";
6 | import {ProviderConnector} from "../../../connector/provider.connector";
7 | import {AbiItem} from "../../../model/abi.model";
8 | import {LimitOrderBuilder} from "../../../limit-order.builder";
9 | import {LimitOrderPredicateBuilder} from "../../../limit-order-predicate.builder";
10 | import {
11 | ExtensionParamsWithCustomData,
12 | LimitOrderData,
13 | LimitOrderWithExtension,
14 | } from "../../../model/limit-order-protocol.model";
15 | import { ethers } from 'hardhat'
16 | import {buildTakerTraits} from "../../../utils/build-taker-traits";
17 | import {Contract} from "ethers";
18 |
19 | type Signer = Awaited>[0];
20 |
21 | const testDomainSettings = {
22 | domainName: '1inch Limit Order Protocol',
23 | version: '4',
24 | };
25 | export async function signOrder(
26 | typedData: EIP712TypedData,
27 | wallet: Signer,
28 | ): Promise {
29 | return await wallet.signTypedData(
30 | typedData.domain,
31 | { Order: typedData.types.Order },
32 | typedData.message
33 | );
34 | }
35 |
36 | export function cutSelector(data: string): string {
37 | const hexPrefix = '0x';
38 | return hexPrefix + data.substring(hexPrefix.length + 8);
39 | }
40 |
41 | export function ether(num: string): bigint {
42 | return ethers.parseUnits(num);
43 | }
44 |
45 | export function fillWithMakingAmount(amount: bigint): string {
46 | const result = BigInt(amount) | buildTakerTraits({ makingAmount: true }).traits;
47 | return `0x${result.toString(16)}`;
48 | }
49 |
50 | export function skipMakerPermit (amount: bigint): string {
51 | return setN(amount, 253, true).toString();
52 | }
53 |
54 | export function compactSignature (signature: string): { r: string, vs: string } {
55 | const sig = ethers.Signature.from(signature);//
56 | return {
57 | r: sig.r,
58 | vs: sig.yParityAndS,
59 | };
60 | }
61 |
62 | export function getProviderConnector(signer: Signer): ProviderConnector {
63 | return {
64 | signTypedData(
65 | _: string,
66 | typedData: EIP712TypedData,
67 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
68 | _typedDataHash: string
69 | ): Promise {
70 | return signOrder(typedData, signer)
71 | },
72 | contractEncodeABI(
73 | abi: AbiItem[],
74 | _: string | null,
75 | methodName: string,
76 | methodParams: unknown[]
77 | ): string {
78 | const iface = new ethers.Interface(abi);
79 | return iface.encodeFunctionData(methodName, methodParams);
80 | },
81 | ethCall(contractAddress: string, callData: string): Promise {
82 | const provider = ethers.provider;
83 | return provider.call({
84 | to: contractAddress,
85 | data: callData,
86 | });
87 | }
88 | } as ProviderConnector;
89 | }
90 |
91 | export function getOrderFacade(
92 | contractAddress: string,
93 | chainId: bigint,
94 | wallet: Signer,
95 | ): LimitOrderProtocolFacade {
96 | const takerProviderConnector = getProviderConnector(wallet);
97 | return new LimitOrderProtocolFacade(
98 | contractAddress,
99 | chainId,
100 | takerProviderConnector
101 | );
102 | }
103 |
104 | export function getPredicateBuilder(
105 | contractAddress: string,
106 | chainId: bigint,
107 | wallet: Signer,
108 | ): LimitOrderPredicateBuilder {
109 | const facade = getOrderFacade(contractAddress, chainId, wallet);
110 | return new LimitOrderPredicateBuilder(facade);
111 | }
112 |
113 | export function getOrderBuilder(
114 | wallet: Signer
115 | ): LimitOrderBuilder {
116 | const makerProviderConnector = getProviderConnector(wallet);
117 |
118 | return new LimitOrderBuilder(
119 | makerProviderConnector,
120 | testDomainSettings
121 | );
122 | }
123 |
124 | type FacadeTxMethods = Pick<
125 | LimitOrderProtocolFacade,
126 | 'fillLimitOrder' | 'fillLimitOrderArgs' | 'permitAndCall' | 'increaseEpoch' | 'cancelLimitOrder'
127 | >;
128 | type AllowedFacadeTxMethods = keyof FacadeTxMethods;
129 |
130 | export async function getFacadeTx(
131 | method: M,
132 | txParams: Parameters,
133 | filler: Signer,
134 | chainId: bigint,
135 | swap: Contract,
136 | ) {
137 | const facade = getOrderFacade(await swap.getAddress(), chainId, filler);
138 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
139 | const callData = (facade as any)[method](...txParams);
140 | return filler.sendTransaction({
141 | to: await swap.getAddress(),
142 | data: callData
143 | });
144 | }
145 |
146 | // export function getFacadeViewCall;
156 | type AllowedFacadeViewCallMethods = keyof FacadeViewCallMethods;
157 |
158 | export async function getFacadeViewCall(
159 | method: M,
160 | txParams: Parameters,
161 | filler: Signer,
162 | chainId: bigint,
163 | swap: Contract,
164 | ): Promise> {
165 | const facade = getOrderFacade(await swap.getAddress(), chainId, filler);
166 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
167 | return (facade as any)[method](...txParams);
168 | }
169 |
170 | export async function getSignedOrder(
171 | wallet: Signer,
172 | orderData: Omit,
173 | {
174 | chainId,
175 | verifyingContract,
176 | }: { chainId: bigint, verifyingContract: string },
177 | extensionData?: ExtensionParamsWithCustomData,
178 | ): Promise<{ order: LimitOrderWithExtension, signature: string, orderHash: string }> {
179 | const builder = getOrderBuilder(wallet);
180 | const order = builder.buildLimitOrder({
181 | ...orderData,
182 | salt: '1',
183 | }, extensionData);
184 |
185 | const typedData = builder.buildLimitOrderTypedData(
186 | order.order, chainId, verifyingContract
187 | );
188 |
189 | const signature = await builder.buildOrderSignature(await wallet.getAddress(), typedData);
190 | const orderHash = builder.buildLimitOrderHash(typedData);
191 |
192 | return {
193 | order: order,
194 | signature,
195 | orderHash,
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/src/erc20.facade.ts:
--------------------------------------------------------------------------------
1 | import {ERC20_ABI} from './limit-order-protocol.const';
2 | import {ProviderConnector} from './connector/provider.connector';
3 |
4 | export enum Erc20Methods {
5 | transferFrom = 'transferFrom',
6 | balanceOf = 'balanceOf',
7 | }
8 |
9 | export class Erc20Facade {
10 | constructor(private readonly providerConnector: ProviderConnector) {}
11 |
12 | balanceOf(tokenAddress: string, walletAddress: string): string {
13 | return this.providerConnector.contractEncodeABI(
14 | ERC20_ABI,
15 | tokenAddress,
16 | Erc20Methods.balanceOf,
17 | [walletAddress]
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './erc20.facade';
2 |
3 | export * from './limit-order-protocol.const';
4 | export * from './limit-order.builder';
5 | export * from './limit-order-v3.builder';
6 | export * from './limit-order-protocol.facade';
7 | export * from './limit-order-protocol-v3.facade';
8 | export * from './limit-order-predicate.builder';
9 | export * from './limit-order-predicate-v3.builder';
10 |
11 | export * from './limit-order.decoder';
12 | export * from './limit-order-predicate.decoder';
13 |
14 | export * from './series-nonce-manager.const';
15 | export * from './series-nonce-manager.facade';
16 | export * from './series-nonce-manager-predicate.builder';
17 |
18 | export * from './connector/provider.connector';
19 | export * from './connector/web3-provider.connector';
20 | export * from './connector/private-key-provider.connector';
21 |
22 | export * from './model/abi.model';
23 | export * from './model/eip712.model';
24 | export * from './model/limit-order-protocol.model';
25 | export * from './model/series-nonce-manager.model';
26 | export {EXPIRY_SHIFT} from "./utils/maker-traits.const";
27 | export {NONCE_SHIFT} from "./utils/maker-traits.const";
28 | export {SERIES_SHIFT} from "./utils/maker-traits.const";
29 | export {_UNWRAP_WETH_FLAG} from "./utils/maker-traits.const";
30 | export {_USE_PERMIT2_FLAG} from "./utils/maker-traits.const";
31 | export {_HAS_EXTENSION_FLAG} from "./utils/maker-traits.const";
32 | export {_NEED_EPOCH_CHECK_FLAG} from "./utils/maker-traits.const";
33 | export {_NEED_POSTINTERACTION_FLAG} from "./utils/maker-traits.const";
34 | export {_NEED_PREINTERACTION_FLAG} from "./utils/maker-traits.const";
35 | export {_NO_PRICE_IMPROVEMENT_FLAG} from "./utils/maker-traits.const";
36 | export {_ALLOW_MULTIPLE_FILLS_FLAG} from "./utils/maker-traits.const";
37 | export {_NO_PARTIAL_FILLS_FLAG} from "./utils/maker-traits.const";
38 | export * from './utils/build-taker-traits';
39 | export {ChainId} from "./limit-order-protocol-addresses.const";
40 |
--------------------------------------------------------------------------------
/src/limit-order-predicate-v3.builder.test.ts:
--------------------------------------------------------------------------------
1 | import {mocksForV3Chain} from './test/helpers';
2 | import {LimitOrderPredicateV3Builder} from "./limit-order-predicate-v3.builder";
3 | import {ChainId} from "./limit-order-protocol-addresses.const";
4 |
5 | describe('PredicateBuilderV3 - for build limit order v3 predicate', () => {
6 | const chainId = ChainId.binanceMainnet;
7 | const walletAddress = '0xfb3c7eb936caa12b5a884d612393969a557d4307';
8 |
9 | let predicateBuilder: LimitOrderPredicateV3Builder;
10 |
11 | beforeEach(() => {
12 | const mocks = mocksForV3Chain(chainId);
13 | predicateBuilder = mocks.limitOrderPredicateBuilder;
14 | });
15 |
16 | it('Simple predicate must includes all values and match the snapshot', () => {
17 | const timestampBelow = 1619860038;
18 |
19 | const valuesInPredicate = [
20 | timestampBelow.toString(16),
21 | ];
22 |
23 | const predicate = predicateBuilder.timestampBelow(timestampBelow);
24 |
25 | const allValuesCheck = valuesInPredicate.every((value) =>
26 | predicate.includes(value)
27 | );
28 |
29 | expect(predicate).toMatchSnapshot();
30 | expect(allValuesCheck).toBe(true);
31 | });
32 |
33 | it('Simplyfied predicate must includes all values and match the snapshot', () => {
34 | const nonce = 14;
35 | const timestampBelow = 1619860038;
36 |
37 | const valuesInPredicate = [
38 | nonce.toString(16),
39 | timestampBelow.toString(16),
40 | ];
41 |
42 | const predicate = predicateBuilder.timestampBelowAndNonceEquals(
43 | timestampBelow,
44 | nonce,
45 | walletAddress,
46 | );
47 |
48 | const allValuesCheck = valuesInPredicate.every((value) =>
49 | predicate.includes(value)
50 | );
51 |
52 | expect(predicate).toMatchSnapshot();
53 | expect(allValuesCheck).toBe(true);
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/src/limit-order-predicate-v3.builder.ts:
--------------------------------------------------------------------------------
1 | import {
2 | LimitOrderProtocolMethodsV3,
3 | Nonce,
4 | PredicateTimestamp
5 | } from "./model/limit-order-protocol.model";
6 | import {ZX} from "./limit-order-protocol.const";
7 | import {LimitOrderPredicateCallData} from "./limit-order-predicate.builder";
8 | import {LimitOrderBuilder} from "./limit-order.builder";
9 | import {AbstractSmartcontractFacade} from "./utils/abstract-facade";
10 | import {LimitOrderProtocolV3Facade} from "./limit-order-protocol-v3.facade";
11 |
12 | export class LimitOrderPredicateV3Builder {
13 | constructor(private readonly facade: LimitOrderProtocolV3Facade) {}
14 |
15 | and = (
16 | ...predicates: LimitOrderPredicateCallData[]
17 | ): LimitOrderPredicateCallData => {
18 | const { offsets, data } = LimitOrderBuilder.joinStaticCalls(predicates);
19 |
20 | return this.facade.getContractCallData(LimitOrderProtocolMethodsV3.and, [
21 | offsets,
22 | data,
23 | ]);
24 | };
25 |
26 | or = (
27 | ...predicates: LimitOrderPredicateCallData[]
28 | ): LimitOrderPredicateCallData => {
29 | const { offsets, data } = LimitOrderBuilder.joinStaticCalls(predicates);
30 |
31 | return this.facade.getContractCallData(LimitOrderProtocolMethodsV3.or, [
32 | offsets,
33 | data,
34 | ]);
35 | };
36 |
37 | eq = (
38 | value: string,
39 | callData: string
40 | ): LimitOrderPredicateCallData => {
41 | return this.facade.getContractCallData(LimitOrderProtocolMethodsV3.eq, [
42 | value,
43 | callData,
44 | ]);
45 | };
46 |
47 | lt = (
48 | value: string,
49 | callData: string
50 | ): LimitOrderPredicateCallData => {
51 | return this.facade.getContractCallData(LimitOrderProtocolMethodsV3.lt, [
52 | value,
53 | callData,
54 | ]);
55 | };
56 |
57 | gt = (
58 | value: string,
59 | callData: string
60 | ): LimitOrderPredicateCallData => {
61 | return this.facade.getContractCallData(LimitOrderProtocolMethodsV3.gt, [
62 | value,
63 | callData,
64 | ]);
65 | };
66 |
67 | nonce = (makerAddress: string): LimitOrderPredicateCallData => {
68 | return this.facade.getContractCallData(
69 | LimitOrderProtocolMethodsV3.nonce,
70 | [makerAddress]
71 | );
72 | }
73 |
74 | nonceEquals = (
75 | makerAddress: string,
76 | makerNonce: Nonce,
77 | ): LimitOrderPredicateCallData => {
78 | return this.facade.getContractCallData(
79 | LimitOrderProtocolMethodsV3.nonceEquals,
80 | [makerAddress, makerNonce]
81 | );
82 | };
83 |
84 | /**
85 | * @param timestamp seconds unit
86 | */
87 | timestampBelow = (timestamp: PredicateTimestamp): LimitOrderPredicateCallData => {
88 | return this.facade.getContractCallData(
89 | LimitOrderProtocolMethodsV3.timestampBelow,
90 | [ZX + timestamp.toString(16)]
91 | );
92 | };
93 |
94 | /**
95 | * @param timestamp seconds unit
96 | */
97 | timestampBelowAndNonceEquals = (
98 | timestamp: PredicateTimestamp,
99 | nonce: Nonce,
100 | address: string,
101 | ): LimitOrderPredicateCallData => {
102 | const predicateValue = BigInt(address)
103 | + (BigInt(nonce) << BigInt(160))
104 | + (BigInt(timestamp) << BigInt(208));
105 |
106 | return this.facade.getContractCallData(
107 | LimitOrderProtocolMethodsV3.timestampBelowAndNonceEquals,
108 | [ZX + predicateValue.toString(16)]
109 | );
110 | }
111 |
112 | arbitraryStaticCall = (
113 | target: string | AbstractSmartcontractFacade,
114 | callData: string
115 | ): LimitOrderPredicateCallData => {
116 | const address = target instanceof AbstractSmartcontractFacade
117 | ? target.contractAddress
118 | : target;
119 |
120 | if (address.toLowerCase() === this.facade.contractAddress.toLowerCase()) {
121 | console.warn(
122 | 'Unnecessary arbitraryStaticCall(). '
123 | + 'Omit it when interacting with limit-order-protocol methods.'
124 | );
125 |
126 | return callData;
127 | }
128 |
129 | return this.facade.getContractCallData(LimitOrderProtocolMethodsV3.arbitraryStaticCall, [
130 | address,
131 | callData,
132 | ]);
133 | };
134 | }
135 |
--------------------------------------------------------------------------------
/src/limit-order-predicate.builder.ts:
--------------------------------------------------------------------------------
1 | import {
2 | LimitOrderProtocolMethods,
3 | } from './model/limit-order-protocol.model';
4 | import {LimitOrderProtocolFacade} from './limit-order-protocol.facade';
5 | import { AbstractSmartcontractFacade } from './utils/abstract-facade';
6 | import { LimitOrderBuilder } from './limit-order.builder';
7 |
8 | export type LimitOrderPredicateCallData = string;
9 |
10 | /**
11 | * All methods is lambdas to preserve `this` context and allow DSL-like usage
12 | */
13 | export class LimitOrderPredicateBuilder {
14 | constructor(private readonly facade: LimitOrderProtocolFacade) {}
15 |
16 | and = (
17 | ...predicates: LimitOrderPredicateCallData[]
18 | ): LimitOrderPredicateCallData => {
19 | const { offsets, data } = LimitOrderBuilder.joinStaticCalls(predicates);
20 |
21 | return this.facade.getContractCallData(LimitOrderProtocolMethods.and, [
22 | offsets,
23 | data,
24 | ]);
25 | };
26 |
27 | or = (
28 | ...predicates: LimitOrderPredicateCallData[]
29 | ): LimitOrderPredicateCallData => {
30 | const { offsets, data } = LimitOrderBuilder.joinStaticCalls(predicates);
31 |
32 | return this.facade.getContractCallData(LimitOrderProtocolMethods.or, [
33 | offsets,
34 | data,
35 | ]);
36 | };
37 |
38 | eq = (
39 | value: string,
40 | callData: string
41 | ): LimitOrderPredicateCallData => {
42 | return this.facade.getContractCallData(LimitOrderProtocolMethods.eq, [
43 | value,
44 | callData,
45 | ]);
46 | };
47 |
48 | lt = (
49 | value: string,
50 | callData: string
51 | ): LimitOrderPredicateCallData => {
52 | return this.facade.getContractCallData(LimitOrderProtocolMethods.lt, [
53 | value,
54 | callData,
55 | ]);
56 | };
57 |
58 | gt = (
59 | value: string,
60 | callData: string
61 | ): LimitOrderPredicateCallData => {
62 | return this.facade.getContractCallData(LimitOrderProtocolMethods.gt, [
63 | value,
64 | callData,
65 | ]);
66 | };
67 |
68 | arbitraryStaticCall = (
69 | target: string | AbstractSmartcontractFacade,
70 | callData: string
71 | ): LimitOrderPredicateCallData => {
72 | const address = target instanceof AbstractSmartcontractFacade
73 | ? target.contractAddress
74 | : target;
75 |
76 | if (address.toLowerCase() === this.facade.contractAddress.toLowerCase()) {
77 | console.warn(
78 | 'Unnecessary arbitraryStaticCall(). '
79 | + 'Omit it when interacting with limit-order-protocol methods.'
80 | );
81 |
82 | return callData;
83 | }
84 |
85 | return this.facade.getContractCallData(LimitOrderProtocolMethods.arbitraryStaticCall, [
86 | address,
87 | callData,
88 | ]);
89 | };
90 | }
91 |
--------------------------------------------------------------------------------
/src/limit-order-predicate.decoder.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | LimitOrderPredicateDecoder,
3 | PredicateAstArguments,
4 | PredicateAstNode,
5 | PredicateAstMatcher,
6 | } from './limit-order-predicate.decoder';
7 | import { NonceSeriesV2 } from './model/series-nonce-manager.model';
8 | import { SeriesNonceManagerPredicateBuilder } from './series-nonce-manager-predicate.builder';
9 | import {mocksForV3Chain} from './test/helpers';
10 | import {LimitOrderPredicateV3Builder} from "./limit-order-predicate-v3.builder";
11 | import {
12 | LIMIT_ORDER_PROTOCOL_V3_ABI
13 | } from "./limit-order-protocol.const";
14 | import {SERIES_NONCE_MANAGER_ABI} from "./series-nonce-manager.const";
15 | import {ChainId, limitOrderProtocolAddresses} from "./limit-order-protocol-addresses.const";
16 |
17 |
18 | // eslint-disable-next-line max-len
19 | const GASLESS_PREDICATE = `0x2cc2878d00006377dffd0000000000001c667c6308d6c9c8ce5bd207f524041f67dbc65e`;
20 | const GASLESS_AST = {
21 | name: "timestampBelowAndNonceEquals",
22 | type: "function",
23 | meta: {
24 | source: "0x1111111254eeb25477b68fb85ed929f73a960582"
25 | },
26 | args: {
27 | address: {
28 | bytes: "0x1c667c6308d6c9c8ce5bd207f524041f67dbc65e",
29 | meta: {
30 | source: "0x1111111254eeb25477b68fb85ed929f73a960582"
31 | },
32 | type: "bytes"
33 | },
34 | nonce: {
35 | bytes: "0",
36 | meta: {
37 | source: "0x1111111254eeb25477b68fb85ed929f73a960582"
38 | },
39 | type: "bytes"
40 | },
41 | timestamp: {
42 | bytes: "1668800509",
43 | meta: {
44 | source: "0x1111111254eeb25477b68fb85ed929f73a960582"
45 | },
46 | type: "bytes"
47 | }
48 | },
49 | };
50 |
51 | describe("LimitOrderPredicateDecoder", () => {
52 | const chainId = ChainId.ethereumMainnet;
53 | const limitOrderPredicateDecoder = new LimitOrderPredicateDecoder(
54 | limitOrderProtocolAddresses[chainId],
55 | chainId,
56 | LIMIT_ORDER_PROTOCOL_V3_ABI,
57 | SERIES_NONCE_MANAGER_ABI,
58 | );
59 |
60 | let predicateBuilder: LimitOrderPredicateV3Builder;
61 | // let erc20Facade: Erc20Facade;
62 | let seriesNonceManagerContractAddress: string;
63 | let seriesNonceManagerPredicateBuilder: SeriesNonceManagerPredicateBuilder;
64 |
65 | describe("AST parsing", () => {
66 |
67 | it("simple gasless predicate", () => {
68 | expect(limitOrderPredicateDecoder.decode(GASLESS_PREDICATE)).toMatchObject(GASLESS_AST);
69 | });
70 |
71 | });
72 |
73 | describe("search util", () => {
74 | beforeEach(() => {
75 | const mocks = mocksForV3Chain(chainId);
76 | predicateBuilder = mocks.limitOrderPredicateBuilder;
77 | seriesNonceManagerPredicateBuilder = mocks.seriesNonceManagerPredicateBuilder;
78 | seriesNonceManagerContractAddress = mocks.seriesNonceManagerContractAddress;
79 | });
80 |
81 | it("findFirstDFS", () => {
82 | const address = '0x6e359d196494f7c172ca91c7b9ebbbed62a5f10a';
83 | const { and, or, timestampBelowAndNonceEquals, arbitraryStaticCall, nonce, nonceEquals, eq } = predicateBuilder
84 | const predicate = or(
85 | and(
86 | timestampBelowAndNonceEquals(1, 1, address),
87 |
88 | ),
89 | and(
90 | eq(
91 | '2',
92 | nonce(address),
93 | ),
94 | nonceEquals(address, 3),
95 | timestampBelowAndNonceEquals(2, 4, address),
96 | ),
97 | and(
98 | arbitraryStaticCall(
99 | seriesNonceManagerContractAddress,
100 | seriesNonceManagerPredicateBuilder.timestampBelowAndNonceEquals(
101 | NonceSeriesV2.LimitOrderV3,
102 | 3,
103 | 5,
104 | address,
105 | ),
106 | ),
107 | nonceEquals(address, 6),
108 | ),
109 | );
110 |
111 | const ast = limitOrderPredicateDecoder.decode(predicate);
112 |
113 | const timestampBelowAndNonceEqualsMatcher: PredicateAstMatcher = (node) => (
114 | node.type === "function"
115 | && 'name' in node
116 | && node.name === 'timestampBelowAndNonceEquals'
117 | );
118 |
119 | expect(limitOrderPredicateDecoder.findFirstDFS(ast, timestampBelowAndNonceEqualsMatcher)).toMatchObject({
120 | name: "timestampBelowAndNonceEquals",
121 | type: "function",
122 | meta: {
123 | source: "0x1111111254eeb25477b68fb85ed929f73a960582"
124 | },
125 | args: {
126 | address: {
127 | bytes: address,
128 | },
129 | nonce: {
130 | bytes: '1',
131 | },
132 | timestamp: {
133 | bytes: '1',
134 | },
135 | },
136 | });
137 |
138 | const anyNonceMatcher: PredicateAstMatcher = (node) => (
139 | node.type === "function"
140 | && 'name' in node
141 | && ['nonce', 'nonceEquals'].includes(node.name as string)
142 | );
143 |
144 | expect(limitOrderPredicateDecoder.findFirstDFS(ast, anyNonceMatcher)).toMatchObject({
145 | name: "nonce",
146 | type: "function",
147 | meta: {
148 | source: "0x1111111254eeb25477b68fb85ed929f73a960582"
149 | },
150 | args: {
151 | makerAddress: {
152 | bytes: "0x6e359D196494F7C172CA91c7B9eBBBed62a5F10A",
153 | },
154 | },
155 | });
156 |
157 | const arbitraryStaticCallMatcher: PredicateAstMatcher = (node) => {
158 | if (node.type !== "function" || node.name !== 'arbitraryStaticCall') return false;
159 |
160 | const target = (node.args as PredicateAstArguments)["target"]["bytes"];
161 | return target.toLowerCase() === seriesNonceManagerContractAddress.toLowerCase();
162 | };
163 |
164 | expect(
165 | limitOrderPredicateDecoder.findFirstDFS(
166 | limitOrderPredicateDecoder.findFirstDFS(ast, arbitraryStaticCallMatcher) as PredicateAstNode,
167 | timestampBelowAndNonceEqualsMatcher,
168 | )
169 | ).toMatchObject({
170 | name: "timestampBelowAndNonceEquals",
171 | type: "function",
172 | meta: {
173 | source: seriesNonceManagerContractAddress,
174 | },
175 | args: {
176 | address: {
177 | bytes: address,
178 | },
179 | nonce: {
180 | bytes: '5',
181 | },
182 | timestamp: {
183 | bytes: '3',
184 | },
185 | },
186 | });
187 | })
188 | })
189 | });
190 |
--------------------------------------------------------------------------------
/src/limit-order-predicate.decoder.ts:
--------------------------------------------------------------------------------
1 | import { ZX } from "./limit-order-protocol.const";
2 | import {
3 | seriesNonceManagerContractAddresses
4 | } from "./series-nonce-manager.const";
5 | import { FunctionFragment, Interface, Result } from '@ethersproject/abi';
6 |
7 | import { Address } from "./model/eth.model";
8 | import { AbiItem } from "./model/abi.model";
9 | import { isIterable, mapObject } from "./utils/helpers";
10 | import { LimitOrderPredicateDecoders } from "./utils/decoders/limit-order-predicate-decoders";
11 | import { BigNumber } from "@ethersproject/bignumber";
12 | import { SeriesNonceManagerDecoders } from "./utils/decoders/series-nonce-manager-decoders";
13 | import { trim0x } from "./utils/limit-order.utils";
14 | import {ChainId} from "./limit-order-protocol-addresses.const";
15 |
16 | type Bytes = string;
17 |
18 | export interface PredicateAstNode {
19 | type: "function" | "bytes",
20 | name?: string,
21 | args?: PredicateAstArguments,
22 | bytes?: Bytes,
23 | meta: {
24 | source: Address | null,
25 | }
26 | }
27 |
28 | export type PredicateAstArguments = Record | Array;
29 |
30 | export class DecodableCall {
31 | constructor(
32 | readonly calldata: string,
33 | readonly target: Address,
34 | ) {}
35 | }
36 |
37 | export type DecodableArguments =
38 | Record
39 | | Array;
40 |
41 | interface DecodablePredicateAstNode {
42 | type: PredicateAstNode["type"],
43 | name?: PredicateAstNode["name"],
44 | bytes?: PredicateAstNode["bytes"],
45 | meta: PredicateAstNode["meta"],
46 | args?: PredicateAstNode["args"] | DecodableArguments,
47 | }
48 |
49 | export class PredicateBytes implements DecodablePredicateAstNode {
50 | readonly meta: DecodablePredicateAstNode["meta"];
51 |
52 | readonly type = "bytes";
53 |
54 | constructor(
55 | readonly bytes: Bytes,
56 | source: DecodablePredicateAstNode["meta"]["source"],
57 | ) {
58 | this.meta = {
59 | source,
60 | }
61 | }
62 | }
63 |
64 | export class PredicateFn implements DecodablePredicateAstNode {
65 | readonly meta: DecodablePredicateAstNode["meta"];
66 |
67 | readonly type = "function";
68 |
69 | constructor(
70 | readonly name: NonNullable,
71 | readonly args: NonNullable,
72 | source: DecodablePredicateAstNode["meta"]["source"],
73 | ) {
74 | this.meta = {
75 | source,
76 | }
77 | }
78 | }
79 |
80 | export type ABI = AbiItem[];
81 |
82 | /**
83 | * An ifaceContext.decodeFunctionData result.
84 | *
85 | * Object contains arguments by indexed and named keys.
86 | * Eg:
87 | *
88 | * ```
89 | * solMethod(255, '0xff') // solMethod(uint8 count, bytes data)
90 | * // ->
91 | * {
92 | * 0: 255,
93 | * 1: '0xff',
94 | * count: 255,
95 | * data: '0xff',
96 | * }
97 | * ```
98 | */
99 | export type CallArguments = Result | { [key: string]: string | BigNumber };
100 |
101 | /**
102 | * See [CallArguments] for details.
103 | *
104 | * @param interfaces access it in case if you want implement arbitraryStaticCall-like method
105 | */
106 | type Decoder = (
107 | fn: FunctionFragment,
108 | data: Required,
109 | address: Address,
110 | ) => DecodablePredicateAstNode;
111 |
112 | /**
113 | * {
114 | * methodName: data => PredicateAstNode,
115 | * // or
116 | * methodSignature0x: data => PredicateAstNode,
117 | * }
118 | */
119 | // https://en.wikipedia.org/wiki/Bounded_quantification#F-bounded_quantification
120 | export type DecodersImplementation = {
121 | [K in keyof Implementation]: (
122 | // eslint-disable-next-line @typescript-eslint/ban-types
123 | Implementation[K] extends Function
124 | ? Decoder
125 | : Implementation[K]
126 | )
127 | }
128 |
129 | type DecodableInterfaces = Record<
130 | Address,
131 | {
132 | iface: Interface,
133 | decoders: DecodersImplementation,
134 | }
135 | >
136 |
137 | type DecodableContracts = Record<
138 | Address,
139 | {
140 | abi: ABI,
141 | decoders: DecodersImplementation;
142 | }
143 | >
144 |
145 | export type PredicateAstMatcher = (node: PredicateAstNode) => boolean;
146 |
147 | export class LimitOrderPredicateDecoder<
148 | T extends ChainId,
149 | Decoders extends DecodersImplementation,
150 | > {
151 | decodableInterfaces:
152 | DecodableInterfaces
153 | | DecodableInterfaces
154 | | DecodableInterfaces;
155 |
156 | defaultAddress: string;
157 |
158 | private readonly limitOrderABI: AbiItem[];
159 |
160 | private readonly seriesNonceManagerABI: AbiItem[];
161 |
162 | constructor(
163 | private readonly contractAddress: Address,
164 | private readonly chainId: T,
165 | limitOrderABI: AbiItem[],
166 | seriesNonceManagerABI: AbiItem[],
167 | decodableContracts: DecodableContracts = {},
168 | ) {
169 | this.limitOrderABI = limitOrderABI;
170 | this.seriesNonceManagerABI = seriesNonceManagerABI;
171 | this.defaultAddress = this.contractAddress;
172 |
173 | this.decodableInterfaces = {
174 | [this.defaultAddress]: {
175 | iface: new Interface(this.limitOrderABI),
176 | decoders: new LimitOrderPredicateDecoders(),
177 | },
178 | [seriesNonceManagerContractAddresses[this.chainId].toLowerCase()]: {
179 | iface: new Interface(this.seriesNonceManagerABI),
180 | decoders: new SeriesNonceManagerDecoders(),
181 | },
182 |
183 | }
184 |
185 | // User defined decoders
186 | Object.assign(
187 | this.decodableInterfaces,
188 | this.decodableContractsToDecodableInterfaces(decodableContracts),
189 | );
190 | }
191 |
192 | decode(calldata: string): PredicateAstNode {
193 | return this.parseCalldata(calldata, this.defaultAddress);
194 | }
195 |
196 | findFirstDFS = (
197 | tree: PredicateAstNode,
198 | matcher: PredicateAstMatcher,
199 | ): PredicateAstNode | null => {
200 | if (matcher(tree)) return tree;
201 |
202 | if (tree.args) {
203 | const args = isIterable(tree.args)
204 | ? Array.from(tree.args as ArrayLike)
205 | : Object.values(tree.args);
206 |
207 | for (const arg of args) {
208 | const result = this.findFirstDFS(arg, matcher);
209 |
210 | if (result) return result;
211 | }
212 | }
213 |
214 | return null;
215 | }
216 |
217 | private decodableContractsToDecodableInterfaces(
218 | decodableContracts: DecodableContracts,
219 | ): DecodableInterfaces {
220 | return Object.assign({}, ...Object.entries(decodableContracts).map(
221 | ([address, { abi, decoders }]) => {
222 | return [
223 | address.toLowerCase(),
224 | {
225 | iface: new Interface(abi),
226 | decoders,
227 | }
228 | ]
229 | }
230 | ))
231 | }
232 |
233 | // eslint-disable-next-line
234 | private parseCalldata = (calldata: string, address: Address): PredicateAstNode => {
235 | const selector = calldata.substring(0, 10);
236 | const decodableIface = this.decodableInterfaces[address];
237 |
238 | if (!decodableIface) return new PredicateBytes(calldata, address);
239 |
240 | let fn: FunctionFragment;
241 | try {
242 | fn = decodableIface.iface.getFunction(selector);
243 | } catch (e) {
244 | // eslint-disable-next-line max-len
245 | console.warn(`Tried to decode unknown function with signature ${selector} on ${address}.`);
246 | return new PredicateBytes(calldata, address);
247 | }
248 |
249 | const data = decodableIface.iface.decodeFunctionData(fn, calldata);
250 |
251 | type decoderKey = keyof typeof decodableIface.decoders
252 | const decoder = (
253 | decodableIface.decoders[fn.name as decoderKey]
254 | || decodableIface.decoders[selector as decoderKey]
255 | || decodableIface.decoders[selector.substring(2) as decoderKey]
256 | ) as Decoder;
257 |
258 | if (!decoder) return new PredicateBytes(calldata, address);
259 |
260 |
261 | const decoded = decoder(fn, data, address);
262 | const result = {
263 | ...decoded,
264 | } as PredicateAstNode;
265 |
266 | const { args } = decoded;
267 | if (args) {
268 | if (isIterable(args)) {
269 | result.args = Array.from(args as ArrayLike).map(this.mapArgs);
270 | } else {
271 | result.args = mapObject(
272 | args as Record,
273 | this.mapArgs,
274 | );
275 | }
276 | }
277 |
278 | return result
279 | }
280 |
281 | private parseDecodableCall = (call: DecodableCall): PredicateAstNode => {
282 | return this.parseCalldata(ZX + trim0x(call.calldata), call.target.toLowerCase());
283 | }
284 |
285 | private mapArgs = (arg: DecodableCall | PredicateAstNode): PredicateAstNode => {
286 | if (arg instanceof DecodableCall) {
287 | return this.parseDecodableCall(arg);
288 | }
289 |
290 | return arg;
291 | }
292 |
293 | }
294 |
--------------------------------------------------------------------------------
/src/limit-order-protocol-addresses.const.ts:
--------------------------------------------------------------------------------
1 | export enum ChainId {
2 | ethereumMainnet = 1,
3 | binanceMainnet = 56,
4 | polygonMainnet = 137,
5 | optimismMainnet = 10,
6 | arbitrumMainnet = 42161,
7 | gnosisMainnet = 100,
8 | avalancheMainnet = 43114,
9 | fantomMainnet = 250,
10 | auroraMainnet = 1313161554,
11 | klaytnMainnet = 8217,
12 | zkSyncEraMainnet = 324,
13 | baseMainnet = 8453,
14 | }
15 |
16 | export const limitOrderProtocolAddresses: { [key in ChainId]: string } = {
17 | [ChainId.ethereumMainnet]: '0x1111111254eeb25477b68fb85ed929f73a960582',
18 | [ChainId.binanceMainnet]: '0x1111111254eeb25477b68fb85ed929f73a960582',
19 | [ChainId.polygonMainnet]: '0x1111111254eeb25477b68fb85ed929f73a960582',
20 | [ChainId.optimismMainnet]: '0x1111111254eeb25477b68fb85ed929f73a960582',
21 | [ChainId.arbitrumMainnet]: '0x1111111254eeb25477b68fb85ed929f73a960582',
22 | [ChainId.auroraMainnet]: '0x1111111254eeb25477b68fb85ed929f73a960582',
23 | [ChainId.gnosisMainnet]: '0x1111111254eeb25477b68fb85ed929f73a960582',
24 | [ChainId.avalancheMainnet]: '0x1111111254eeb25477b68fb85ed929f73a960582',
25 | [ChainId.fantomMainnet]: '0x1111111254eeb25477b68fb85ed929f73a960582',
26 | [ChainId.klaytnMainnet]: '0x1111111254eeb25477b68fb85ed929f73a960582',
27 | [ChainId.zkSyncEraMainnet]: '0x6e2b76966cbd9cf4cc2fa0d76d24d5241e0abc2f',
28 | [ChainId.baseMainnet]: '0x1111111254eeb25477b68fb85ed929f73a960582',
29 | } as const;
30 |
--------------------------------------------------------------------------------
/src/limit-order-protocol-v3.facade.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | LimitOrderLegacy,
3 | LimitOrderProtocolMethodsV3,
4 | } from './model/limit-order-protocol.model';
5 | import {BETA_CONTRACT_ADDRESSES, mocksForV3Chain} from './test/helpers';
6 | import {LimitOrderProtocolV3Facade} from "./limit-order-protocol-v3.facade";
7 | import {LimitOrderPredicateV3Builder} from "./limit-order-predicate-v3.builder";
8 | import {LimitOrderV3Builder} from "./limit-order-v3.builder";
9 | import {ChainId} from "./limit-order-protocol-addresses.const";
10 |
11 |
12 | // eslint-disable-next-line max-lines-per-function
13 | describe('LimitOrderProtocolV3Facade - facade for Limit order protocol contract', () => {
14 | const walletAddress = '0xfb3c7eb936cAA12B5A884d612393969A557d4307';
15 |
16 | let facade: LimitOrderProtocolV3Facade;
17 | let contractAddress: string;
18 | let limitOrderPredicateBuilder: LimitOrderPredicateV3Builder;
19 | let limitOrderBuilder: LimitOrderV3Builder;
20 |
21 | function createOrderWithPredicate(predicate: string): LimitOrderLegacy {
22 | return limitOrderBuilder.buildLimitOrder({
23 | makerAssetAddress: '0xe9e7cea3dedca5984780bafc599bd69add087d56',
24 | takerAssetAddress: '0x111111111117dc0aa78b770fa6a738034120c302',
25 | makerAddress: walletAddress,
26 | makingAmount: '1000000000000000000',
27 | takingAmount: '1000000000000000000',
28 | predicate,
29 | });
30 | }
31 |
32 | beforeEach(() => {
33 | const chainId = ChainId.ethereumMainnet;
34 |
35 | const mocks = mocksForV3Chain(chainId, BETA_CONTRACT_ADDRESSES[chainId]);
36 | facade = mocks.facade;
37 | limitOrderBuilder = mocks.limitOrderBuilder;
38 | limitOrderPredicateBuilder = mocks.limitOrderPredicateBuilder;
39 | contractAddress = mocks.contractAddress;
40 |
41 | jest.spyOn(console, 'error').mockImplementation();
42 | });
43 |
44 | describe('cancelLimitOrder', () => {
45 | it('Must create a call data for order canceling', async () => {
46 | const timestamp = 1621040104;
47 | const timestampBelow = limitOrderPredicateBuilder.timestampBelow(
48 | timestamp
49 | );
50 | const order = createOrderWithPredicate(timestampBelow);
51 |
52 | order.salt = '1';
53 |
54 | const callData = facade.cancelLimitOrder(order);
55 |
56 | expect(callData).toMatchSnapshot();
57 | });
58 | });
59 |
60 | describe('nonce()', () => {
61 | it('Return the nonce number of address (for real wallet address)', async () => {
62 | const nonce = await facade.nonce(
63 | '0x401394cd75d731e07658203fff34722a68316fca'
64 | );
65 |
66 | expect(nonce).toBe(1n);
67 | });
68 |
69 | it('Return 0 when address never called advanceNonce (for contract address)', async () => {
70 | const nonce = await facade.nonce(contractAddress);
71 |
72 | expect(nonce).toBe(0n);
73 | });
74 |
75 | it('Valid zero nonce on Aurora', async () => {
76 | const chainId = ChainId.auroraMainnet;
77 | const mocks = mocksForV3Chain(chainId, BETA_CONTRACT_ADDRESSES[chainId]);
78 | facade = mocks.facade;
79 |
80 | const nonce = await facade.nonce(walletAddress);
81 |
82 | expect(nonce).toBe(0n);
83 | });
84 |
85 | it('Valid non-zero nonce on Aurora', async () => {
86 | const chainId = ChainId.auroraMainnet;
87 | const mocks = mocksForV3Chain(chainId, BETA_CONTRACT_ADDRESSES[chainId]);
88 | facade = mocks.facade;
89 |
90 | const walletAddress = '0x401394CD75D731e07658203fFF34722A68316FCa';
91 | const nonce = await facade.nonce(walletAddress); // real nonce
92 |
93 | expect(nonce).toBe(1n);
94 | });
95 | });
96 |
97 | describe('advanceNonce()', () => {
98 | it('Must create a call data for advance nonce', async () => {
99 | const callData = await facade.advanceNonce(2);
100 |
101 | expect(callData).toBe(
102 | '0x72c244a80000000000000000000000000000000000000000000000000000000000000002'
103 | );
104 | });
105 | });
106 |
107 | describe('increaseNonce()', () => {
108 | it('Must create a call data for increase nonce', async () => {
109 | const callData = await facade.increaseNonce();
110 |
111 | expect(callData).toBe('0xc53a0292');
112 | });
113 | });
114 |
115 | describe('checkPredicate()', () => {
116 | it('When the order predicates are valid then return true', async () => {
117 | const timestamp = Math.floor(Date.now() / 1000) + 600;
118 | const timestampBelow = limitOrderPredicateBuilder.timestampBelow(
119 | timestamp
120 | ); // valid value
121 | const predicate = timestampBelow;
122 |
123 | const order = createOrderWithPredicate(predicate);
124 |
125 | const result = await facade.checkPredicate(order);
126 |
127 | expect(result).toBe(true);
128 | });
129 |
130 | it('When the order predicates are NOT valid then return false', async () => {
131 | const timestampBelow = facade.getContractCallData(
132 | LimitOrderProtocolMethodsV3.timestampBelow,
133 | [12000000] // must be 0x0000...
134 | );
135 | const predicate = timestampBelow;
136 |
137 | const order = createOrderWithPredicate(predicate);
138 |
139 | const result = await facade.checkPredicate(order);
140 |
141 | expect(result).toBe(false);
142 | });
143 | });
144 | });
145 |
--------------------------------------------------------------------------------
/src/limit-order-protocol-v3.facade.ts:
--------------------------------------------------------------------------------
1 | import {AbstractSmartcontractFacade} from "./utils/abstract-facade";
2 | import {
3 | LimitOrderHash,
4 | LimitOrderLegacy,
5 | LimitOrderProtocolMethodsV3
6 | } from "./model/limit-order-protocol.model";
7 | import {LIMIT_ORDER_PROTOCOL_V3_ABI} from "./limit-order-protocol.const";
8 | import {BigNumber} from "@ethersproject/bignumber";
9 |
10 | export class LimitOrderProtocolV3Facade
11 | extends AbstractSmartcontractFacade {
12 | ABI = LIMIT_ORDER_PROTOCOL_V3_ABI;
13 |
14 | checkPredicate(order: LimitOrderLegacy): Promise {
15 | const callData = this.getContractCallData(
16 | LimitOrderProtocolMethodsV3.checkPredicate,
17 | [order]
18 | );
19 |
20 | return this.providerConnector
21 | .ethCall(this.contractAddress, callData)
22 | .catch((error) => {
23 | console.error(error);
24 |
25 | return false;
26 | })
27 | .then((result) => {
28 | try {
29 | return BigNumber.from(result).toNumber() === 1;
30 | } catch (e) {
31 | console.error(e);
32 |
33 | return false;
34 | }
35 | });
36 | }
37 |
38 | cancelLimitOrder(order: LimitOrderLegacy): string {
39 | return this.getContractCallData(LimitOrderProtocolMethodsV3.cancelOrder, [
40 | order,
41 | ]);
42 | }
43 |
44 | async nonce(makerAddress: string): Promise {
45 | const callData = this.getContractCallData(
46 | LimitOrderProtocolMethodsV3.nonce,
47 | [makerAddress]
48 | );
49 |
50 | const nonce = await this.providerConnector
51 | .ethCall(this.contractAddress, callData);
52 | return BigInt(nonce);
53 | }
54 |
55 | advanceNonce(count: number): string {
56 | return this.getContractCallData(
57 | LimitOrderProtocolMethodsV3.advanceNonce,
58 | [count]
59 | );
60 | }
61 |
62 | increaseNonce(): string {
63 | return this.getContractCallData(
64 | LimitOrderProtocolMethodsV3.increaseNonce
65 | );
66 | }
67 |
68 | remaining(orderHash: LimitOrderHash): Promise {
69 | const callData = this.getContractCallData(
70 | LimitOrderProtocolMethodsV3.remaining,
71 | [orderHash]
72 | );
73 |
74 | return this.providerConnector
75 | .ethCall(this.contractAddress, callData)
76 | .then((result) => {
77 | const response = this.parseRemainingResponse(result);
78 |
79 | if (response !== null) {
80 | return response;
81 | }
82 |
83 | return Promise.reject(result);
84 | });
85 | }
86 |
87 | parseRemainingResponse(response: string): BigNumber | null {
88 | if (response.length === 66) {
89 | return BigNumber.from(response);
90 | }
91 |
92 | return null;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/limit-order-protocol.const.ts:
--------------------------------------------------------------------------------
1 | import {AbiItem} from './model/abi.model';
2 | import LimitOrderProtocolABISource from './abi/LimitOrderProtocol.json';
3 | import LimitOrderProtocolV3ABISource from './abi/LimitOrderProtocolV3.json';
4 | import ERC20ABISource from './abi/ERC20ABI.json';
5 | import {SignTypedDataVersion} from '@metamask/eth-sig-util';
6 |
7 | export const PROTOCOL_NAME = '1inch Aggregation Router';
8 |
9 | export const PROTOCOL_VERSION = '6';
10 |
11 | export const TypedDataVersion = SignTypedDataVersion.V4;
12 |
13 | export const ZX = '0x';
14 |
15 | export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
16 |
17 | export const CALL_RESULTS_PREFIX = 'CALL_RESULTS_';
18 |
19 | export const LIMIT_ORDER_PROTOCOL_ABI: AbiItem[] = LimitOrderProtocolABISource;
20 |
21 | export const LIMIT_ORDER_PROTOCOL_V3_ABI: AbiItem[] = LimitOrderProtocolV3ABISource;
22 |
23 | export const ERC20_ABI: AbiItem[] = ERC20ABISource;
24 |
25 | export const EIP712_DOMAIN = [
26 | {name: 'name', type: 'string'},
27 | {name: 'version', type: 'string'},
28 | {name: 'chainId', type: 'uint256'},
29 | {name: 'verifyingContract', type: 'address'},
30 | ];
31 |
--------------------------------------------------------------------------------
/src/limit-order-protocol.facade.ts:
--------------------------------------------------------------------------------
1 | import {
2 | EIP712_DOMAIN,
3 | LIMIT_ORDER_PROTOCOL_ABI,
4 | PROTOCOL_NAME,
5 | PROTOCOL_VERSION,
6 | TypedDataVersion,
7 | } from './limit-order-protocol.const';
8 | import {
9 | Address,
10 | LimitOrder,
11 | LimitOrderProtocolMethods,
12 | LimitOrderProtocolMethodsV3,
13 | LimitOrderSignature,
14 | MakerTraits,
15 | TakerTraits,
16 | } from './model/limit-order-protocol.model';
17 | import {compactSignature,} from './utils/limit-order.utils';
18 | import {TypedDataUtils} from '@metamask/eth-sig-util';
19 | import {AbstractSmartcontractFacade} from './utils/abstract-facade';
20 | import {Series} from "./model/series-nonce-manager.model";
21 | import { solidityPacked } from "ethers"
22 |
23 |
24 | export interface FillOrderParamsWithTakerTraits {
25 | order: LimitOrder;
26 | signature: LimitOrderSignature;
27 | amount: string;
28 | takerTraits: TakerTraits;
29 | }
30 |
31 | export type FillOrderArgs = FillOrderParamsWithTakerTraits & {
32 | args: string;
33 | }
34 |
35 | export type PermitAndCallParams = FillOrderArgs & {
36 | target: Address;
37 | permitToken: Address;
38 | permit: string;
39 | interaction: string;
40 | }
41 |
42 | export interface ErrorResponse extends Error {
43 | data: string,
44 | }
45 |
46 |
47 | export class LimitOrderProtocolFacade
48 | extends AbstractSmartcontractFacade
49 | {
50 | ABI = LIMIT_ORDER_PROTOCOL_ABI;
51 |
52 | fillLimitOrder(params: FillOrderParamsWithTakerTraits): string {
53 | const {
54 | order,
55 | signature,
56 | amount,
57 | takerTraits
58 | } = params;
59 |
60 | const { r, vs } = compactSignature(signature);
61 |
62 | return this.getContractCallData(LimitOrderProtocolMethods.fillOrder, [
63 | order,
64 | r,
65 | vs,
66 | amount,
67 | takerTraits,
68 | ]);
69 | }
70 |
71 | fillLimitOrderArgs(params: FillOrderArgs): string {
72 | const {
73 | order,
74 | signature,
75 | amount,
76 | takerTraits,
77 | args,
78 | } = params;
79 |
80 | const { r, vs } = compactSignature(signature);
81 |
82 |
83 | return this.getContractCallData(LimitOrderProtocolMethods.fillOrderArgs, [
84 | order,
85 | r,
86 | vs,
87 | amount,
88 | takerTraits,
89 | args,
90 | ]);
91 | }
92 |
93 | permitAndCall(params: PermitAndCallParams): string {
94 | const {
95 | permit,
96 | permitToken,
97 | } = params;
98 |
99 | const packedPermit = solidityPacked(
100 | ['address', 'bytes'],
101 | [permitToken, permit],
102 | );
103 |
104 | const fillOrderArgsCalldata = this.fillLimitOrderArgs(params);
105 |
106 | return this.getContractCallData(
107 | LimitOrderProtocolMethods.permitAndCall,
108 | [packedPermit, fillOrderArgsCalldata]
109 | );
110 | }
111 |
112 | cancelLimitOrder(makerTraits: MakerTraits, orderHash: string): string {
113 | return this.getContractCallData(LimitOrderProtocolMethods.cancelOrder, [
114 | makerTraits,
115 | orderHash,
116 | ]);
117 | }
118 |
119 | increaseEpoch(series: Series): string {
120 | return this.getContractCallData(LimitOrderProtocolMethods.increaseEpoch, [
121 | series
122 | ]);
123 | }
124 |
125 | epoch(maker: Address, series: Series): Promise {
126 | const calldata = this.getContractCallData(LimitOrderProtocolMethods.epoch, [
127 | maker,
128 | series,
129 | ]);
130 |
131 | return this.makeViewCall(calldata, BigInt);
132 | }
133 |
134 | async checkPredicate(predicate: string): Promise {
135 | const callData = this.getContractCallData(
136 | LimitOrderProtocolMethods.checkPredicate,
137 | [predicate]
138 | );
139 |
140 | const result = await this.makeViewCall(callData, BigInt);
141 | try {
142 | return result === BigInt(1);
143 | } catch (e) {
144 | console.error(e);
145 | return false;
146 | }
147 | }
148 |
149 | remainingInvalidatorForOrder(maker: Address, orderHash: string): Promise {
150 | const calldata = this.getContractCallData(
151 | LimitOrderProtocolMethods.remainingInvalidatorForOrder,
152 | [
153 | maker,
154 | orderHash,
155 | ]);
156 |
157 | return this.makeViewCall(calldata, BigInt);
158 | }
159 |
160 | rawRemainingInvalidatorForOrder(maker: Address, orderHash: string): Promise {
161 | const calldata = this.getContractCallData(
162 | LimitOrderProtocolMethods.rawRemainingInvalidatorForOrder,
163 | [
164 | maker,
165 | orderHash,
166 | ]);
167 |
168 | return this.makeViewCall(calldata, BigInt);
169 | }
170 |
171 | // https://github.com/1inch/limit-order-protocol/blob/v3-prerelease/test/helpers/eip712.js#L22
172 | domainSeparator(): Promise {
173 | const hex = '0x' + TypedDataUtils.hashStruct(
174 | 'EIP712Domain',
175 | {
176 | name: PROTOCOL_NAME,
177 | version: PROTOCOL_VERSION,
178 | chainId: this.chainId,
179 | verifyingContract: this.contractAddress,
180 | },
181 | { EIP712Domain: EIP712_DOMAIN },
182 | TypedDataVersion,
183 | ).toString('hex')
184 |
185 | return Promise.resolve(hex);
186 | }
187 |
188 | orderHash(order: LimitOrder): Promise {
189 | const calldata = this.getContractCallData(
190 | LimitOrderProtocolMethods.hashOrder,
191 | [
192 | order
193 | ]);
194 |
195 | return this.makeViewCall(calldata);
196 | }
197 |
198 | private makeViewCall(
199 | calldata: string,
200 | parser?: (result: string) => T
201 | ): Promise {
202 | return this.providerConnector
203 | .ethCall(this.contractAddress, calldata)
204 | .then(parser ? parser : undefined);
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/limit-order-signature.builder.ts:
--------------------------------------------------------------------------------
1 | import {LimitOrder, LimitOrderSignature} from "./model/limit-order-protocol.model";
2 | import {Address} from "./model/eth.model";
3 | import {EIP712ParamsExtended} from "./limit-order.builder";
4 | import {EIP712TypedData} from "./model/eip712.model";
5 | import {EIP712_DOMAIN} from "./limit-order-protocol.const";
6 | import {SignTypedDataVersion, TypedDataUtils} from "@metamask/eth-sig-util";
7 | import {ProviderConnector} from "./connector/provider.connector";
8 |
9 | export class LimitOrderSignatureBuilder {
10 | constructor(
11 | private readonly providerConnector: ProviderConnector,
12 | private readonly eip712Params: EIP712ParamsExtended,
13 | ) {
14 | }
15 |
16 | buildLimitOrderTypedData(
17 | order: LimitOrder,
18 | chainId: number,
19 | verifyingContract: Address,
20 | ): EIP712TypedData {
21 | return {
22 | primaryType: 'Order',
23 | types: {
24 | EIP712Domain: EIP712_DOMAIN,
25 | Order: this.eip712Params.orderStructure,
26 | },
27 | domain: {
28 | name: this.eip712Params.domainName,
29 | version: this.eip712Params.version,
30 | chainId: chainId,
31 | verifyingContract: verifyingContract,
32 | },
33 | message: order,
34 | };
35 | }
36 |
37 | buildTypedDataAndSign(
38 | order: LimitOrder,
39 | chainId: number,
40 | verifyingContract: Address,
41 | wallet: Address,
42 | ): Promise {
43 | const typedData = this.buildLimitOrderTypedData(
44 | order,
45 | chainId,
46 | verifyingContract,
47 | );
48 | return this.buildOrderSignature(wallet, typedData);
49 | }
50 |
51 | buildOrderSignature(
52 | wallet: Address,
53 | typedData: EIP712TypedData
54 | ): Promise {
55 | const dataHash = TypedDataUtils.hashStruct(
56 | typedData.primaryType,
57 | typedData.message,
58 | typedData.types,
59 | SignTypedDataVersion.V4
60 | ).toString('hex');
61 |
62 | return this.providerConnector.signTypedData(
63 | wallet,
64 | typedData,
65 | dataHash,
66 | );
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/limit-order-v3.builder.test.ts:
--------------------------------------------------------------------------------
1 | import {LimitOrderDecoder} from './limit-order.decoder';
2 | import {LimitOrderLegacy} from './model/limit-order-protocol.model';
3 | import Web3 from 'web3';
4 | import {PrivateKeyProviderConnector} from './connector/private-key-provider.connector';
5 | import {largeInteractions, largeResult} from './test/mocks';
6 | import {LimitOrderV3Builder} from "./limit-order-v3.builder";
7 | import {limitOrderProtocolAddresses} from "./limit-order-protocol-addresses.const";
8 |
9 | describe('LimitOrderV3Builder - for build new limit order', () => {
10 | const chainId = 56;
11 | const contractAddress = limitOrderProtocolAddresses[chainId];
12 |
13 | const web3 = new Web3('https://bsc-dataseed.binance.org');
14 | const privateKey =
15 | '552be66668d14242eeeb0e84600f0946ddddc77777777c3761ea5906e9ddcccc';
16 | const providerConnector = new PrivateKeyProviderConnector(privateKey, web3);
17 |
18 | let limitOrderBuilder: LimitOrderV3Builder;
19 |
20 | beforeEach(() => {
21 | limitOrderBuilder = new LimitOrderV3Builder(
22 | providerConnector,
23 | {
24 | domainName: '1inch Aggregation Router',
25 | version: '5'
26 | }
27 | );
28 | });
29 |
30 | describe('Normal limit order', () => {
31 | it('buildOrderSignature() must call the provider signTypedData method', async () => {
32 | const walletAddress = '0x1548dAdf412Eaaf3c80bEad35CDa83a4bf7dF6ce';
33 | const dataHash =
34 | '7cb7e268d5a5f0d8da9a5904a0084b3c4f17a7826413e83d69784a50d4154878';
35 |
36 | const { interactions, offsets } = LimitOrderV3Builder.packInteractions({
37 | makerAssetData: '0xf0',
38 | takerAssetData: '0xf1',
39 | getMakingAmount: '0xf2',
40 | getTakingAmount: '0xf3',
41 | predicate: '0xf4',
42 | permit: '0xf5',
43 | preInteraction: '0xf6',
44 | postInteraction: '0xf7',
45 | });
46 |
47 | const order: LimitOrderLegacy = {
48 | salt: '1',
49 | makerAsset: 'makerAsset',
50 | takerAsset: 'takerAsset',
51 | maker: 'maker',
52 | receiver: 'receiver',
53 | allowedSender: 'allowedSender',
54 | makingAmount: 'makingAmount',
55 | takingAmount: 'takingAmount',
56 |
57 | offsets,
58 | interactions,
59 | };
60 | const typedData = limitOrderBuilder.buildLimitOrderTypedData(
61 | order,
62 | chainId,
63 | contractAddress
64 | );
65 |
66 | const signTypedDataSpy = jest.spyOn(
67 | providerConnector,
68 | 'signTypedData'
69 | );
70 |
71 | const signature = await limitOrderBuilder.buildOrderSignature(
72 | walletAddress,
73 | typedData
74 | );
75 |
76 | expect(signature).toMatchSnapshot();
77 | expect(signTypedDataSpy).toHaveBeenCalledTimes(1);
78 | expect(signTypedDataSpy).toHaveBeenCalledWith(
79 | walletAddress,
80 | typedData,
81 | dataHash,
82 | );
83 | });
84 |
85 | it('buildLimitOrderHash() must create a hash of order with 0x prefix', () => {
86 | const { interactions, offsets } = LimitOrderV3Builder.packInteractions({
87 | makerAssetData: '0xf0',
88 | takerAssetData: '0xf1',
89 | getMakingAmount: '0xf2',
90 | getTakingAmount: '0xf3',
91 | predicate: '0xf4',
92 | permit: '0xf5',
93 | preInteraction: '0xf6',
94 | postInteraction: '0xf7',
95 | });
96 |
97 | const order: LimitOrderLegacy = {
98 | salt: '1',
99 | makerAsset: 'makerAsset',
100 | takerAsset: 'takerAsset',
101 | maker: 'maker',
102 | receiver: 'receiver',
103 | allowedSender: 'allowedSender',
104 | makingAmount: 'makingAmount',
105 | takingAmount: 'takingAmount',
106 |
107 | offsets,
108 | interactions,
109 | };
110 | const typedData = limitOrderBuilder.buildLimitOrderTypedData(
111 | order,
112 | chainId,
113 | contractAddress
114 | );
115 |
116 | const hash = limitOrderBuilder.buildLimitOrderHash(typedData);
117 |
118 | expect(hash).toMatchSnapshot();
119 | });
120 |
121 | it('buildLimitOrder() must create a limit order instance according to the given parameters', async () => {
122 | const makerAddress = '0xdddd91605c18a9999c1d47abfeed5daaaa700000';
123 |
124 | const order = limitOrderBuilder.buildLimitOrder({
125 | makerAssetAddress: '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c',
126 | takerAssetAddress: '0x111111111117dc0aa78b770fa6a738034120c302',
127 | makerAddress,
128 | makingAmount: '3',
129 | takingAmount: '100',
130 | predicate: '0x11',
131 | permit: '0x22',
132 | postInteraction: '0x33',
133 | });
134 |
135 | expect(
136 | LimitOrderDecoder.unpackInteractionsV3(order.offsets, order.interactions)
137 | ).toMatchSnapshot();
138 |
139 | expect(+order.salt).toBeGreaterThan(1);
140 |
141 | order.salt = '1';
142 |
143 | expect(order).toMatchSnapshot();
144 | });
145 | });
146 |
147 | describe("packInteractions", () => {
148 | it("should pack", () => {
149 | const { offsets, interactions } = LimitOrderV3Builder.packInteractions(largeInteractions);
150 |
151 | expect(offsets).toBe(largeResult.offsets);
152 | expect(interactions).toBe(largeResult.interactions);
153 | })
154 | })
155 | });
156 |
--------------------------------------------------------------------------------
/src/limit-order-v3.builder.ts:
--------------------------------------------------------------------------------
1 | import {ProviderConnector} from './connector/provider.connector';
2 | import {
3 | InteractionsV3,
4 | LimitOrderDataLegacy,
5 | LimitOrderInteractions,
6 | LimitOrderLegacy,
7 | } from './model/limit-order-protocol.model';
8 | import {
9 | ZERO_ADDRESS,
10 | ZX
11 | } from './limit-order-protocol.const';
12 | import {ORDER_STRUCTURE_LEGACY} from './model/eip712.model';
13 | import {EIP712Params, generateOrderSalt} from './limit-order.builder';
14 | import {BaseLimitOrderBuilder} from "./base-limit-order.builder";
15 |
16 | export class LimitOrderV3Builder extends BaseLimitOrderBuilder {
17 | constructor(
18 | protected readonly providerConnector: ProviderConnector,
19 | protected readonly eip712Params: EIP712Params,
20 | ) {
21 | super(
22 | providerConnector,
23 | {
24 | ...eip712Params,
25 | orderStructure: ORDER_STRUCTURE_LEGACY,
26 | }
27 | )
28 | }
29 |
30 | static packInteractions({
31 | makerAssetData = ZX,
32 | takerAssetData = ZX,
33 | getMakingAmount = ZX,
34 | getTakingAmount = ZX,
35 | predicate = ZX,
36 | permit = ZX,
37 | preInteraction = ZX,
38 | postInteraction = ZX,
39 | }: Partial): LimitOrderInteractions {
40 | const allInteractions = [
41 | makerAssetData,
42 | takerAssetData,
43 | getMakingAmount,
44 | getTakingAmount,
45 | predicate,
46 | permit,
47 | preInteraction,
48 | postInteraction,
49 | ];
50 |
51 | const {
52 | offsets, data: interactions
53 | } = BaseLimitOrderBuilder.joinStaticCalls(allInteractions);
54 | return { offsets: ZX + offsets.toString(16), interactions };
55 | }
56 |
57 | buildLimitOrder({
58 | makerAssetAddress,
59 | takerAssetAddress,
60 | makerAddress,
61 | receiver = ZERO_ADDRESS,
62 | allowedSender = ZERO_ADDRESS,
63 | makingAmount,
64 | takingAmount,
65 | predicate = ZX,
66 | permit = ZX,
67 | getMakingAmount = ZX,
68 | getTakingAmount = ZX,
69 | preInteraction = ZX,
70 | postInteraction = ZX,
71 | salt = generateOrderSalt(),
72 | }: LimitOrderDataLegacy
73 | ): LimitOrderLegacy {
74 |
75 | const makerAssetData = ZX;
76 | const takerAssetData = ZX;
77 |
78 | const { offsets, interactions } = LimitOrderV3Builder.packInteractions({
79 | makerAssetData,
80 | takerAssetData,
81 | getMakingAmount,
82 | getTakingAmount,
83 | predicate,
84 | permit,
85 | preInteraction,
86 | postInteraction,
87 | })
88 |
89 | return {
90 | salt,
91 | makerAsset: makerAssetAddress,
92 | takerAsset: takerAssetAddress,
93 | maker: makerAddress,
94 | receiver,
95 | allowedSender,
96 | makingAmount,
97 | takingAmount,
98 | offsets,
99 | interactions,
100 | };
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/limit-order.builder.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ZERO_ADDRESS,
3 | ZX,
4 | } from './limit-order-protocol.const';
5 | import {
6 | ExtensionParams,
7 | ExtensionParamsWithCustomData,
8 | LimitOrder,
9 | LimitOrderData,
10 | LimitOrderInteractions,
11 | LimitOrderWithExtension,
12 | } from './model/limit-order-protocol.model';
13 | import {
14 | EIP712Parameter,
15 | ORDER_STRUCTURE,
16 | } from './model/eip712.model';
17 | import {ProviderConnector} from './connector/provider.connector';
18 | import {setN, trim0x} from './utils/limit-order.utils';
19 | import Web3 from 'web3';
20 | import {
21 | _ALLOW_MULTIPLE_FILLS_FLAG,
22 | _HAS_EXTENSION_FLAG,
23 | _NEED_EPOCH_CHECK_FLAG,
24 | _NEED_POSTINTERACTION_FLAG,
25 | _NEED_PREINTERACTION_FLAG,
26 | _NO_PARTIAL_FILLS_FLAG,
27 | _NO_PRICE_IMPROVEMENT_FLAG,
28 | _UNWRAP_WETH_FLAG,
29 | _USE_PERMIT2_FLAG,
30 | EXPIRY_SHIFT,
31 | NONCE_SHIFT,
32 | SERIES_SHIFT
33 | } from "./utils/maker-traits.const";
34 | import {BaseLimitOrderBuilder} from "./base-limit-order.builder";
35 | import {generateSalt} from './utils/generate-salt';
36 |
37 | export function generateOrderSalt(): string {
38 | return Math.round(Math.random() * Date.now()) + '';
39 | }
40 |
41 | export interface EIP712Params {
42 | domainName: string;
43 | version: string;
44 | }
45 |
46 | export interface EIP712ParamsExtended extends EIP712Params {
47 | orderStructure: EIP712Parameter[];
48 | }
49 |
50 | export class LimitOrderBuilder extends BaseLimitOrderBuilder {
51 | constructor(
52 | protected readonly providerConnector: ProviderConnector,
53 | protected readonly eip712Params: EIP712Params,
54 | ) {
55 | super(providerConnector, {
56 | ...eip712Params,
57 | orderStructure: ORDER_STRUCTURE,
58 | });
59 | }
60 |
61 | static packInteractions(
62 | {
63 | makerAssetSuffix,
64 | takerAssetSuffix,
65 | makingAmountGetter,
66 | takingAmountGetter,
67 | predicate,
68 | permit,
69 | preInteraction,
70 | postInteraction,
71 | }: ExtensionParams): LimitOrderInteractions {
72 | const allInteractions = [
73 | makerAssetSuffix,
74 | takerAssetSuffix,
75 | makingAmountGetter,
76 | takingAmountGetter,
77 | predicate,
78 | permit,
79 | preInteraction,
80 | postInteraction,
81 | ];
82 |
83 | const { offsets, data: interactions } = this.joinStaticCalls(allInteractions);
84 | return { offsets: ZX + offsets.toString(16), interactions };
85 | }
86 |
87 | static buildMakerTraits (
88 | {
89 | allowedSender = ZERO_ADDRESS,
90 | shouldCheckEpoch = false,
91 | allowPartialFill = true,
92 | allowPriceImprovement = true,
93 | allowMultipleFills = true,
94 | usePermit2 = false,
95 | unwrapWeth = false,
96 | expiry = 0,
97 | nonce = BigInt(0),
98 | series = BigInt(0),
99 | } = {}
100 | ): string {
101 | return '0x' + (
102 | (series << BigInt(SERIES_SHIFT)) |
103 | (nonce << BigInt(NONCE_SHIFT)) |
104 | (BigInt(expiry) << BigInt(EXPIRY_SHIFT)) |
105 | (BigInt(allowedSender) & ((BigInt(1) << BigInt(80)) - BigInt(1))) |
106 | // 247 - 255
107 | setN(BigInt(0), _UNWRAP_WETH_FLAG, unwrapWeth) |
108 | setN(BigInt(0), _ALLOW_MULTIPLE_FILLS_FLAG, allowMultipleFills) |
109 | setN(BigInt(0), _NO_PARTIAL_FILLS_FLAG, !allowPartialFill) |
110 | setN(BigInt(0), _NO_PRICE_IMPROVEMENT_FLAG, !allowPriceImprovement) |
111 | setN(BigInt(0), _NEED_EPOCH_CHECK_FLAG, shouldCheckEpoch) |
112 | setN(BigInt(0), _USE_PERMIT2_FLAG, usePermit2)
113 | // 256 bit value
114 | ).toString(16).padStart(64, '0');
115 | }
116 |
117 | /**
118 | * @param allowedSender formerly `takerAddress`
119 | * @returns
120 | */
121 | // eslint-disable-next-line max-lines-per-function
122 | buildLimitOrder(
123 | {
124 | maker,
125 | receiver = ZERO_ADDRESS,
126 | makerAsset,
127 | takerAsset,
128 | makingAmount,
129 | takingAmount,
130 | makerTraits = LimitOrderBuilder.buildMakerTraits(),
131 | salt = generateSalt(),
132 | }: LimitOrderData,
133 | {
134 | makerAssetSuffix = '0x',
135 | takerAssetSuffix = '0x',
136 | makingAmountGetter = '0x',
137 | takingAmountGetter = '0x',
138 | predicate = '0x',
139 | permit = '0x',
140 | preInteraction = '0x',
141 | postInteraction = '0x',
142 | customData = '0x'
143 | }: ExtensionParamsWithCustomData = {}
144 | ): LimitOrderWithExtension {
145 |
146 | const { offsets, interactions } = LimitOrderBuilder.packInteractions({
147 | makerAssetSuffix,
148 | takerAssetSuffix,
149 | makingAmountGetter,
150 | takingAmountGetter,
151 | predicate,
152 | permit,
153 | preInteraction,
154 | postInteraction,
155 | });
156 |
157 | const allInteractionsConcat = trim0x(interactions) + trim0x(customData);
158 |
159 | let extension = '0x';
160 | if (allInteractionsConcat.length > 0) {
161 | // increase offsets to 256 uint + interactions + customData
162 | extension += BigInt(offsets).toString(16).padStart(64, '0') + allInteractionsConcat;
163 | }
164 |
165 | salt = BigInt(salt);
166 | // if extension exists - put its hash to salt and set flag
167 | if (trim0x(extension).length > 0) {
168 | salt = BigInt(Web3.utils.keccak256(extension))
169 | & ((BigInt(1) << BigInt(160)) - BigInt(1));
170 | makerTraits = BigInt(makerTraits) | (BigInt(1) << _HAS_EXTENSION_FLAG);
171 | }
172 |
173 | makerTraits = BigInt(makerTraits);
174 | if (trim0x(preInteraction).length > 0) {
175 | makerTraits = BigInt(makerTraits) | (BigInt(1) << _NEED_PREINTERACTION_FLAG);
176 | }
177 |
178 | if (trim0x(postInteraction).length > 0) {
179 | makerTraits = BigInt(makerTraits) | (BigInt(1) << _NEED_POSTINTERACTION_FLAG);
180 | }
181 |
182 | return {
183 | order: {
184 | salt: salt.toString(),
185 | maker,
186 | receiver,
187 | makerAsset,
188 | takerAsset,
189 | makingAmount,
190 | takingAmount,
191 | makerTraits: `0x${makerTraits.toString(16)}`,
192 | },
193 | extension
194 | };
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/src/limit-order.decoder.test.ts:
--------------------------------------------------------------------------------
1 | import { ZX } from './limit-order-protocol.const';
2 | import { LimitOrderBuilder } from './limit-order.builder';
3 | import {LimitOrderDecoder} from './limit-order.decoder';
4 | import {
5 | commonMakerTraits,
6 | difficultMakerTraits,
7 | extensionWithPermit,
8 | extensionWithPermitAndPredicate,
9 | extensionWithPermitOnly,
10 | extensionWithPredicate,
11 | largeInteractions,
12 | largeResult,
13 | orderWithExtension,
14 | } from './test/mocks';
15 |
16 | describe('LimitOrderDecoder', () => {
17 |
18 | describe('unpackInteractionsV3', () => {
19 | describe("unpackInteractionsV3", () => {
20 | it("should unpack", () => {
21 | const interactions = LimitOrderDecoder.unpackInteractionsV3(largeResult.offsets, largeResult.interactions);
22 | expect(interactions).toMatchObject(largeInteractions);
23 | })
24 | })
25 | });
26 |
27 | describe('unpackInteractions', () => {
28 | describe("unpackInteractions", () => {
29 | it("should unpack predicate", () => {
30 | const interactions = LimitOrderDecoder.unpackExtension(extensionWithPredicate.extension);
31 | expect(interactions).toMatchObject(extensionWithPredicate.result);
32 | });
33 |
34 | it("should unpack permit", () => {
35 | const interactions = LimitOrderDecoder.unpackExtension(extensionWithPermit.extension);
36 | expect(interactions).toMatchObject(extensionWithPermit.result);
37 | });
38 |
39 | it("should unpack permit & predicate", () => {
40 | const interactions = LimitOrderDecoder.unpackExtension(extensionWithPermitAndPredicate.extension);
41 | expect(interactions).toMatchObject(extensionWithPermitAndPredicate.result);
42 | });
43 |
44 | it("should unpack permit", () => {
45 | const interactions = LimitOrderDecoder.unpackExtension(extensionWithPermitOnly.extension);
46 | expect(interactions).toMatchObject(extensionWithPermitOnly.result);
47 | });
48 |
49 | it("should unpack permit & predicate", () => {
50 | const interactions = LimitOrderDecoder.unpackExtension(extensionWithPermitAndPredicate.extension);
51 | expect(interactions).toMatchObject(extensionWithPermitAndPredicate.result);
52 | });
53 | })
54 | });
55 |
56 | describe('unpackMakerTraits', () => {
57 | it('should unpack default maker traits', () => {
58 | expect(LimitOrderDecoder.unpackMakerTraits(commonMakerTraits.hex)).toMatchObject(commonMakerTraits.result)
59 | });
60 |
61 | it('should unpack with allowedSender and expiry, nonce, series', () => {
62 | expect(LimitOrderDecoder.unpackMakerTraits(difficultMakerTraits.hex)).toMatchObject(difficultMakerTraits.result)
63 | });
64 | });
65 |
66 | describe('isSaltCorrect', () => {
67 | it('check that the salt contains 160 lowest bits of extension hash', () => {
68 | expect(LimitOrderDecoder.isSaltCorrect(orderWithExtension.order.salt, orderWithExtension.extension))
69 | .toBeTruthy()
70 | });
71 |
72 | it('should unpack with allowedSender and expiry, nonce, series', () => {
73 | expect(LimitOrderDecoder.unpackMakerTraits(difficultMakerTraits.hex)).toMatchObject(difficultMakerTraits.result)
74 | });
75 | });
76 |
77 | describe("unpackStaticCalls", () => {
78 | const offsets = '14388460379039638487364';
79 | // eslint-disable-next-line max-len
80 | const data = '0x6fe7b0ba0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c4bf15fcd80000000000000000000000002dadf9264db7eb9e24470a2e6c73efbc4bdf01aa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004462534ddf0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000401394cd75d731e07658203fff34722a68316fca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063592c2b00000000000000000000000000000000000000000000000000000000636fd73fca4ece2200000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000124bf15fcd8000000000000000000000000ea8977b0567d353d622add6e5872bf42dd43d07e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a4331f9d1b000000000000000000000000aed0c38402a5d19df6e4c03f4e2dced6e29c1ee90000000000000000000000005f4ec3df9cbd43714fe2740f5e3616155c5b8419000000000000000000000000000000000000000000000000000000003b02338000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
81 |
82 | it("and() data", () => {
83 | expect(
84 | LimitOrderDecoder.unpackStaticCalls(offsets, data)
85 | ).toMatchObject([
86 | // eslint-disable-next-line max-len
87 | '6fe7b0ba0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c4bf15fcd80000000000000000000000002dadf9264db7eb9e24470a2e6c73efbc4bdf01aa0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004462534ddf0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000401394cd75d731e07658203fff34722a68316fca0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
88 | '63592c2b00000000000000000000000000000000000000000000000000000000636fd73f',
89 | // eslint-disable-next-line max-len
90 | 'ca4ece2200000000000000000000000000000000000000000000000000038d7ea4c6800000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000124bf15fcd8000000000000000000000000ea8977b0567d353d622add6e5872bf42dd43d07e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a4331f9d1b000000000000000000000000aed0c38402a5d19df6e4c03f4e2dced6e29c1ee90000000000000000000000005f4ec3df9cbd43714fe2740f5e3616155c5b8419000000000000000000000000000000000000000000000000000000003b02338000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
91 | ]);
92 | });
93 |
94 | it("radnom data", () => {
95 | const MAX_STRING_SIZE = 2**29-24;
96 | const REST_STRINGS_LENGTH = 30;
97 | const source = [
98 | '0x11',
99 | '0x2222',
100 | '0x333333',
101 | ZX + 'F'.repeat(MAX_STRING_SIZE - REST_STRINGS_LENGTH),
102 | '0x44444444',
103 | ];
104 |
105 |
106 | const { offsets, data } = LimitOrderBuilder.joinStaticCalls(source);
107 |
108 | const calls = LimitOrderDecoder.unpackStaticCalls(offsets, data);
109 |
110 | expect(calls.slice(0, -2)).toMatchObject([
111 | '11',
112 | '2222',
113 | '333333',
114 | ]);
115 | expect(calls[3].length).toBe(MAX_STRING_SIZE - REST_STRINGS_LENGTH);
116 | expect(calls[4]).toBe('44444444');
117 | })
118 |
119 | });
120 | });
121 |
--------------------------------------------------------------------------------
/src/limit-order.decoder.ts:
--------------------------------------------------------------------------------
1 | import {
2 | InteractionV3Name,
3 | InteractionsV3,
4 | InteractionsFieldsV3,
5 | UnpackedExtension,
6 | AllInteractions,
7 | Interactions,
8 | InteractionsFields,
9 | LimitOrderLegacy,
10 | MakerTraits,
11 | ParsedMakerTraits,
12 | } from "./model/limit-order-protocol.model";
13 | import {
14 | parseInteractionForField,
15 | trim0x,
16 | UINT32_BITMASK,
17 | UINT32_BITS,
18 | getN,
19 | } from "./utils/limit-order.utils";
20 | import { ZX } from "./limit-order-protocol.const";
21 |
22 | import {
23 | _ALLOW_MULTIPLE_FILLS_FLAG,
24 | _HAS_EXTENSION_FLAG,
25 | _NEED_EPOCH_CHECK_FLAG,
26 | _NO_PARTIAL_FILLS_FLAG,
27 | _NO_PRICE_IMPROVEMENT_FLAG,
28 | _UNWRAP_WETH_FLAG,
29 | _USE_PERMIT2_FLAG, ALLOWED_SENDER_MASK,
30 | EXPIRY_MASK,
31 | EXPIRY_SHIFT,
32 | NONCE_MASK,
33 | NONCE_SHIFT,
34 | SERIES_MASK,
35 | SERIES_SHIFT
36 | } from "./utils/maker-traits.const";
37 | import Web3 from "web3";
38 |
39 |
40 | export class LimitOrderDecoder {
41 | static unpackExtension(extension: string): UnpackedExtension {
42 | extension = trim0x(extension);
43 | const offsetsInHex = ZX + extension.slice(0, 64);
44 | const offsets = BigInt(offsetsInHex);
45 |
46 | const interactions = LimitOrderDecoder.unpackInteractions(
47 | offsets,
48 | extension.slice(64, extension.length)
49 | );
50 |
51 | const extensionBigInt = BigInt(ZX + extension);
52 | const offset = (extensionBigInt >> BigInt(224)) + BigInt(0x20);
53 |
54 | const customData = ZX + extension.slice(
55 | Number(offset),
56 | extension.length
57 | );
58 |
59 | return {
60 | interactions: interactions ?? ZX,
61 | customData: customData ?? ZX,
62 | }
63 | }
64 |
65 | static unpackMakerTraits(makerTraits: MakerTraits): ParsedMakerTraits {
66 | const makerTraitsAsBigInt = BigInt(makerTraits);
67 | const series =
68 | (makerTraitsAsBigInt >> BigInt(SERIES_SHIFT)) & SERIES_MASK;
69 |
70 | const nonce =
71 | (makerTraitsAsBigInt >> BigInt(NONCE_SHIFT)) & NONCE_MASK;
72 |
73 | const expiry =
74 | (makerTraitsAsBigInt >> BigInt(EXPIRY_SHIFT)) & EXPIRY_MASK;
75 |
76 | const allowedSender = makerTraitsAsBigInt & ALLOWED_SENDER_MASK;
77 |
78 | const unwrapWeth = !!getN(makerTraitsAsBigInt, _UNWRAP_WETH_FLAG);
79 | const allowMultipleFills = !!getN(makerTraitsAsBigInt, _ALLOW_MULTIPLE_FILLS_FLAG);
80 | const allowPartialFill = !getN(makerTraitsAsBigInt, _NO_PARTIAL_FILLS_FLAG);
81 | const allowPriceImprovement = !getN(makerTraitsAsBigInt, _NO_PRICE_IMPROVEMENT_FLAG);
82 | const shouldCheckEpoch = !!getN(makerTraitsAsBigInt, _NEED_EPOCH_CHECK_FLAG);
83 | const usePermit2 = !!getN(makerTraitsAsBigInt, _USE_PERMIT2_FLAG);
84 | const hasExtension = !!getN(makerTraitsAsBigInt, _HAS_EXTENSION_FLAG)
85 |
86 | return {
87 | series,
88 | nonce,
89 | expiry: Number(expiry),
90 | allowedSender: allowedSender.toString(16).padEnd(20, '0'),
91 | unwrapWeth,
92 | allowMultipleFills,
93 | allowPartialFill,
94 | allowPriceImprovement,
95 | shouldCheckEpoch,
96 | usePermit2,
97 | hasExtension,
98 | }
99 | }
100 |
101 | static unpackInteractionsV3(offsets: string | bigint, interactions: string): InteractionsV3 {
102 | return LimitOrderDecoder.unpackAllInteractions(
103 | offsets,
104 | interactions,
105 | InteractionsFieldsV3
106 | ) as InteractionsV3;
107 | }
108 |
109 | static unpackInteractions(offsets: string | bigint, interactions: string): Interactions {
110 | return LimitOrderDecoder.unpackAllInteractions(
111 | offsets,
112 | interactions,
113 | InteractionsFields
114 | ) as Interactions;
115 | }
116 |
117 | static unpackInteraction(
118 | order: LimitOrderLegacy,
119 | name: T,
120 | ): InteractionsV3[T] {
121 | return parseInteractionForField(
122 | BigInt(order.offsets),
123 | order.interactions,
124 | InteractionsFieldsV3[name],
125 | )
126 | }
127 |
128 | static isSaltCorrect(salt: string, extension: string): boolean {
129 | const extensionHash = BigInt(Web3.utils.keccak256(extension))
130 | & ((BigInt(1) << BigInt(160)) - BigInt(1));
131 |
132 | return BigInt(salt) === extensionHash;
133 | }
134 |
135 | static unpackStaticCalls(offsets: string | bigint, interactions: string): string[] {
136 | const offsetsBI = BigInt(offsets);
137 | const data = trim0x(interactions);
138 |
139 | const result: string[] = [];
140 | let previous = BigInt(0);
141 | let current = BigInt(0);
142 | // See PredicateHelper.and in limit-order-protocol
143 | for (
144 | let i = BigInt(0);
145 | (current = (offsetsBI >> i) & UINT32_BITMASK);
146 | i += UINT32_BITS
147 | ) {
148 | const calldata = data.slice(Number(previous) * 2, Number(current) * 2);
149 | result.push(calldata);
150 | previous = current;
151 | }
152 |
153 | return result;
154 | }
155 |
156 | private static unpackAllInteractions(
157 | offsets: string | bigint,
158 | interactions: string,
159 | interactionsFields: AllInteractions,
160 | ): InteractionsV3 | Interactions {
161 | const offsetsBN = BigInt(offsets);
162 |
163 | const parsedInteractions = {} as Partial;
164 |
165 | Object.entries(interactionsFields).forEach(([name, position]) => {
166 | parsedInteractions[name as keyof AllInteractions] = parseInteractionForField(
167 | offsetsBN,
168 | interactions,
169 | position as number,
170 | );
171 | });
172 |
173 | return parsedInteractions as Interactions | InteractionsV3;
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/model/abi.model.ts:
--------------------------------------------------------------------------------
1 | export interface AbiItem {
2 | anonymous?: boolean;
3 | constant?: boolean;
4 | inputs?: AbiInput[];
5 | name?: string;
6 | outputs?: AbiOutput[];
7 | payable?: boolean;
8 | stateMutability?: string;
9 | type: string;
10 | }
11 |
12 | export interface AbiInput {
13 | name: string;
14 | type: string;
15 | indexed?: boolean;
16 | components?: AbiInput[];
17 | }
18 |
19 | export interface AbiOutput {
20 | name: string;
21 | type: string;
22 | components?: AbiOutput[];
23 | }
24 |
--------------------------------------------------------------------------------
/src/model/eip712.model.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export const ORDER_STRUCTURE = [
4 | { name: 'salt', type: 'uint256' },
5 | { name: 'maker', type: 'address' },
6 | { name: 'receiver', type: 'address' },
7 | { name: 'makerAsset', type: 'address' },
8 | { name: 'takerAsset', type: 'address' },
9 | { name: 'makingAmount', type: 'uint256' },
10 | { name: 'takingAmount', type: 'uint256' },
11 | { name: 'makerTraits', type: 'uint256' },
12 | ];
13 |
14 | export const ORDER_STRUCTURE_LEGACY = [
15 | { name: 'salt', type: 'uint256' },
16 | { name: 'makerAsset', type: 'address' },
17 | { name: 'takerAsset', type: 'address' },
18 | { name: 'maker', type: 'address' },
19 | { name: 'receiver', type: 'address' },
20 | { name: 'allowedSender', type: 'address' },
21 | { name: 'makingAmount', type: 'uint256' },
22 | { name: 'takingAmount', type: 'uint256' },
23 | { name: 'offsets', type: 'uint256' },
24 | { name: 'interactions', type: 'bytes' },
25 | ];
26 |
27 | export interface EIP712TypedData {
28 | types: EIP712Types & { EIP712Domain: EIP712Parameter[] };
29 | domain: EIP712Object;
30 | message: EIP712Object;
31 | primaryType: string;
32 | }
33 |
34 | export interface EIP712Types {
35 | [key: string]: EIP712Parameter[];
36 | }
37 |
38 | export interface EIP712Parameter {
39 | name: string;
40 | type: string;
41 | }
42 |
43 | export declare type EIP712ObjectValue = string | number | EIP712Object;
44 |
45 | export interface EIP712Object {
46 | [key: string]: EIP712ObjectValue;
47 | }
48 |
49 | export interface MessageTypes {
50 | [additionalProperties: string]: MessageTypeProperty[];
51 | EIP712Domain: MessageTypeProperty[];
52 | }
53 |
54 | export interface MessageTypeProperty {
55 | name: string;
56 | type: string;
57 | }
58 |
--------------------------------------------------------------------------------
/src/model/eth.model.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Eth address 160bits
3 | */
4 | export type Address = string;
--------------------------------------------------------------------------------
/src/model/limit-order-protocol.model.ts:
--------------------------------------------------------------------------------
1 | export type LimitOrderSignature = string;
2 |
3 | export type LimitOrderHash = string;
4 |
5 | export interface LimitOrderData {
6 | maker: Address,
7 | receiver?: Address,
8 | makerAsset: Address,
9 | takerAsset: Address,
10 | makingAmount: string,
11 | takingAmount: string,
12 | makerTraits?: bigint | string,
13 | salt?: string | bigint,
14 | }
15 |
16 | export type LimitOrderDataLegacy = {
17 | makerAddress: string;
18 | receiver?: string; // Optional, by default = ZERO_ADDRESS
19 | allowedSender?: string; // Optional, by default = ZERO_ADDRESS
20 | makerAssetAddress: string;
21 | takerAssetAddress: string;
22 | makingAmount: string;
23 | takingAmount: string;
24 | predicate?: string;
25 | permit?: string;
26 | getMakingAmount?: string;
27 | getTakingAmount?: string;
28 | preInteraction?: string;
29 | postInteraction?: string;
30 | salt?: string;
31 | };
32 |
33 | export type ExtensionParamsWithCustomData = Partial & {
34 | customData?: string;
35 | }
36 |
37 | export interface ExtensionParams {
38 | makerAssetSuffix: string;
39 | takerAssetSuffix: string;
40 | makingAmountGetter: string;
41 | takingAmountGetter: string;
42 | predicate: string;
43 | permit: string;
44 | preInteraction: string;
45 | postInteraction: string;
46 | }
47 |
48 | export type Address = string;
49 |
50 | export type MakerTraits = string;
51 |
52 | /**
53 | * Compatible with EIP712Object
54 | */
55 | export type LimitOrder = {
56 | salt: string;
57 | maker: Address; // maker address
58 | receiver: Address;
59 | makerAsset: Address; // maker asset address
60 | takerAsset: Address; // taker asset address
61 | makingAmount: string;
62 | takingAmount: string;
63 | makerTraits: MakerTraits;
64 | }
65 |
66 | export type LimitOrderWithExtension = {
67 | order: LimitOrder;
68 | extension: string;
69 | }
70 |
71 | /**
72 | * Compatible with EIP712Object
73 | */
74 | export type LimitOrderLegacy = {
75 | salt: string;
76 | makerAsset: string; // maker asset address
77 | takerAsset: string; // taker asset address
78 | maker: string; // maker address
79 | receiver: string;
80 | allowedSender: string;
81 | makingAmount: string;
82 | takingAmount: string;
83 | offsets: string;
84 | interactions: string;
85 | } & LimitOrderInteractions
86 |
87 | /**
88 | * Partial from LimitOrder
89 | */
90 | export type LimitOrderInteractions = {
91 | offsets: string;
92 | interactions: string;
93 | }
94 |
95 | /**
96 | * uint40
97 | */
98 | export type Nonce = number | bigint;
99 |
100 | /**
101 | * seconds unit40
102 | */
103 | export type PredicateTimestamp = number | bigint;
104 |
105 | export const InteractionsFieldsV3 = {
106 | makerAssetData: 0,
107 | takerAssetData: 1,
108 | getMakingAmount: 2,
109 | getTakingAmount: 3,
110 | predicate: 4,
111 | permit: 5,
112 | preInteraction: 6,
113 | postInteraction: 7,
114 | // cuz enum has numeric keys also
115 | } as const;
116 |
117 | export const InteractionsFields = {
118 | makerAssetSuffix: 0,
119 | takerAssetSuffix: 1,
120 | makingAmountGetter: 2,
121 | takingAmountGetter: 3,
122 | predicate: 4,
123 | permit: 5,
124 | preInteraction: 6,
125 | postInteraction: 7
126 | } as const;
127 |
128 | export interface ParsedMakerTraits {
129 | allowedSender: Address;
130 | shouldCheckEpoch: boolean;
131 | allowPartialFill: boolean;
132 | allowPriceImprovement: boolean;
133 | allowMultipleFills: boolean;
134 | usePermit2: boolean;
135 | unwrapWeth: boolean;
136 | expiry: number;
137 | nonce: bigint;
138 | series: bigint;
139 | hasExtension: boolean;
140 | }
141 |
142 | export type InteractionName = keyof typeof InteractionsFields;
143 |
144 | export type Interactions = {
145 | [key in InteractionName]: string;
146 | };
147 |
148 | export interface UnpackedExtension {
149 | interactions: Interactions;
150 | customData: string;
151 | }
152 |
153 | export type InteractionV3Name = keyof typeof InteractionsFieldsV3;
154 |
155 | export type InteractionsV3 = {
156 | [key in InteractionV3Name]: string;
157 | };
158 |
159 | export type AllInteractions = typeof InteractionsFields | typeof InteractionsFieldsV3;
160 |
161 | export enum LimitOrderProtocolMethodsV3 {
162 | cancelOrder = 'cancelOrder',
163 | timestampBelow = 'timestampBelow',
164 | timestampBelowAndNonceEquals = 'timestampBelowAndNonceEquals',
165 | checkPredicate = 'checkPredicate',
166 | increaseNonce = 'increaseNonce',
167 | nonce = 'nonce',
168 | advanceNonce = 'advanceNonce',
169 | and = 'and',
170 | or = 'or',
171 | eq = 'eq',
172 | lt = 'lt',
173 | gt = 'gt',
174 | nonceEquals = 'nonceEquals',
175 | arbitraryStaticCall = 'arbitraryStaticCall',
176 | remaining = 'remaining',
177 | }
178 |
179 | export enum LimitOrderProtocolMethods {
180 | getMakingAmount = 'getMakingAmount',
181 | getTakingAmount = 'getTakingAmount',
182 | arbitraryStaticCall = 'arbitraryStaticCall',
183 | fillOrder = 'fillOrder',
184 | fillOrderArgs = 'fillOrderArgs',
185 | cancelOrder = 'cancelOrder',
186 | permitAndCall = 'permitAndCall',
187 | increaseEpoch = 'increaseEpoch',
188 | remainingInvalidatorForOrder = 'remainingInvalidatorForOrder',
189 | rawRemainingInvalidatorForOrder = 'rawRemainingInvalidatorForOrder',
190 | epoch = 'epoch',
191 | checkPredicate = 'checkPredicate',
192 | advanceNonce = 'advanceNonce',
193 | increaseNonce = 'increaseNonce',
194 | hashOrder = 'hashOrder',
195 | and = 'and',
196 | or = 'or',
197 | eq = 'eq',
198 | lt = 'lt',
199 | gt = 'gt',
200 | nonceEquals = 'nonceEquals',
201 | transferFrom = 'transferFrom',
202 | }
203 |
204 | export type TakerTraits = string;
205 |
--------------------------------------------------------------------------------
/src/model/series-nonce-manager.model.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * uint8 (0-255)
3 | */
4 | export type Series = bigint;
5 |
6 | /**
7 | * @deprecated
8 | */
9 | export const NonceSeriesLegacyV1 = {
10 | P2Pv2: BigInt(1),
11 | } as const;
12 |
13 |
14 | export const NonceSeriesV2 = {
15 | /**
16 | * This is not a valid option.
17 | *
18 | * @deprecated Gasless should use main contract's built-in nonce manager
19 | * to avoid arbitraryStaticCall() and save gas.
20 | */
21 | GaslessV3: BigInt(-1), // should lead to "execution reverted"
22 |
23 | // Those are valid
24 | _GaslessV3: BigInt(-1), // for internal usage
25 | LimitOrderV3: BigInt(0),
26 | P2PV3: BigInt(1),
27 | } as const;
28 |
29 | export enum SeriesNonceManagerMethods {
30 | nonce = 'nonce',
31 | advanceNonce = 'advanceNonce',
32 | increaseNonce = 'increaseNonce',
33 | nonceEquals = 'nonceEquals',
34 | // Don't expose timestampBelow as it's usless.
35 | timestampBelowAndNonceEquals = 'timestampBelowAndNonceEquals',
36 | }
37 |
--------------------------------------------------------------------------------
/src/series-nonce-manager-predicate.builder.test.ts:
--------------------------------------------------------------------------------
1 |
2 | import {BETA_CONTRACT_ADDRESSES, mocksForV3Chain} from "./test/helpers";
3 | import { NonceSeriesV2 } from "./model/series-nonce-manager.model";
4 | import { SeriesNonceManagerPredicateBuilder } from "./series-nonce-manager-predicate.builder";
5 | import { ProviderConnector } from "./connector/provider.connector";
6 | import {ChainId} from "./limit-order-protocol-addresses.const";
7 |
8 |
9 | describe("SeriesNonceManagerFacade", () => {
10 | const walletAddress = '0x1c667c6308d6c9c8ce5bd207f524041f67dbc65e';
11 | let seriesNonceManagerContractAddress: string;
12 |
13 | let seriesNonceManagerPredicateBuilder: SeriesNonceManagerPredicateBuilder;
14 | let providerConnector: ProviderConnector;
15 |
16 | beforeEach(() => {
17 | const chainId = ChainId.ethereumMainnet;
18 |
19 | const mocks = mocksForV3Chain(chainId, BETA_CONTRACT_ADDRESSES[chainId]);
20 | providerConnector = mocks.providerConnector;
21 | seriesNonceManagerPredicateBuilder = mocks.seriesNonceManagerPredicateBuilder;
22 | seriesNonceManagerContractAddress = mocks.seriesNonceManagerContractAddress;
23 | });
24 |
25 | it("nonce", () => {
26 | expect(
27 | seriesNonceManagerPredicateBuilder.nonce(NonceSeriesV2.P2PV3, walletAddress),
28 | ).toMatchSnapshot();
29 | });
30 |
31 | describe("web3 calls", () => {
32 | it("nonceEquals call", async () => {
33 | const calldata = seriesNonceManagerPredicateBuilder.nonceEquals(NonceSeriesV2.LimitOrderV3, walletAddress, 4);
34 |
35 | const result = await providerConnector.ethCall(seriesNonceManagerContractAddress, calldata);
36 | expect(result).toBe('0x0000000000000000000000000000000000000000000000000000000000000001');
37 | });
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/src/series-nonce-manager-predicate.builder.ts:
--------------------------------------------------------------------------------
1 | import { Nonce, PredicateTimestamp } from "./model/limit-order-protocol.model";
2 | import { Series, SeriesNonceManagerMethods } from "./model/series-nonce-manager.model";
3 | import { AbstractPredicateBuilder } from "./utils/abstract-predicate-builder";
4 | import { SeriesNonceManagerFacade } from "./series-nonce-manager.facade";
5 | import { ZX } from "./limit-order-protocol.const";
6 | import { assertSeries } from "./utils/series-nonce-manager.utils";
7 |
8 | /**
9 | * All methods is lambdas to preserve `this` context and allow DSL-like usage
10 | */
11 | export class SeriesNonceManagerPredicateBuilder
12 | extends AbstractPredicateBuilder
13 | {
14 | nonce = (series: Series, makerAddress: string): string => {
15 | assertSeries(series);
16 |
17 | return this.facade.getContractCallData(
18 | SeriesNonceManagerMethods.nonce,
19 | [series, makerAddress],
20 | );
21 | }
22 |
23 | nonceEquals = (
24 | series: Series,
25 | makerAddress: string,
26 | makerNonce: Nonce,
27 | ): string => {
28 | assertSeries(series);
29 |
30 | return this.facade.getContractCallData(
31 | SeriesNonceManagerMethods.nonceEquals,
32 | [series, makerAddress, makerNonce],
33 | );
34 | }
35 |
36 | timestampBelowAndNonceEquals = (
37 | series: Series,
38 | timestamp: PredicateTimestamp,
39 | makerNonce: Nonce,
40 | makerAddress: string,
41 | ): string => {
42 | assertSeries(series);
43 |
44 | const predicateValue = BigInt(makerAddress)
45 | + (BigInt(series) << BigInt(160))
46 | + (BigInt(makerNonce) << BigInt(160 + 16))
47 | + (BigInt(timestamp) << BigInt(160 + 16 + 40));
48 |
49 | return this.facade.getContractCallData(
50 | SeriesNonceManagerMethods.timestampBelowAndNonceEquals,
51 | [ZX + predicateValue.toString(16)],
52 | );
53 | }
54 | }
--------------------------------------------------------------------------------
/src/series-nonce-manager.const.ts:
--------------------------------------------------------------------------------
1 | import { AbiItem } from "src/model/abi.model";
2 | import SeriesNonceManagerABISource from './abi/SeriesNonceManagerABI.json';
3 | import {ChainId} from "./limit-order-protocol-addresses.const";
4 |
5 | export const SERIES_NONCE_MANAGER_ABI: AbiItem[] = SeriesNonceManagerABISource;
6 |
7 | export const seriesNonceManagerContractAddresses: {[key in ChainId]: string} = {
8 | [ChainId.ethereumMainnet]: '0x303389f541ff2d620e42832f180a08e767b28e10',
9 | [ChainId.binanceMainnet]: '0x58ce0e6ef670c9a05622f4188faa03a9e12ee2e4',
10 | [ChainId.polygonMainnet]: '0xa5eb255ef45dfb48b5d133d08833def69871691d',
11 | [ChainId.optimismMainnet]: '0x32d12a25f539e341089050e2d26794f041fc9df8',
12 | [ChainId.arbitrumMainnet]: '0xd7936052d1e096d48c81ef3918f9fd6384108480',
13 | [ChainId.gnosisMainnet]: '0x11431a89893025d2a48dca4eddc396f8c8117187',
14 | [ChainId.avalancheMainnet]: '0x2ec255797fef7669fa243509b7a599121148ffba',
15 | [ChainId.auroraMainnet]: '0x7f069df72b7a39bce9806e3afaf579e54d8cf2b9',
16 | [ChainId.fantomMainnet]: '0x7871769b3816b23db12e83a482aac35f1fd35d4b',
17 | [ChainId.klaytnMainnet]: '0x7871769b3816b23db12e83a482aac35f1fd35d4b',
18 | [ChainId.zkSyncEraMainnet]: '0xce3cf049b99ca75d520287c8f9c35e5bdbf0376b',
19 | [ChainId.baseMainnet]: '0xd9cc0a957cac93135596f98c20fbaca8bf515909',
20 | };
21 |
--------------------------------------------------------------------------------
/src/series-nonce-manager.facade.test.ts:
--------------------------------------------------------------------------------
1 |
2 | import { SeriesNonceManagerFacade } from "./series-nonce-manager.facade";
3 | import {BETA_CONTRACT_ADDRESSES, mocksForV3Chain} from "./test/helpers";
4 | import { NonceSeriesV2 } from "./model/series-nonce-manager.model";
5 | import { SeriesNonceManagerPredicateBuilder } from "./series-nonce-manager-predicate.builder";
6 | import {ChainId} from "./limit-order-protocol-addresses.const";
7 |
8 |
9 | describe("SeriesNonceManagerFacade", () => {
10 | const walletAddress = '0x1c667c6308d6c9c8ce5bd207f524041f67dbc65e';
11 |
12 | let seriesNonceManagerFacade: SeriesNonceManagerFacade;
13 | let seriesNonceManagerPredicateBuilder: SeriesNonceManagerPredicateBuilder;
14 |
15 |
16 | beforeEach(() => {
17 | const chainId = ChainId.ethereumMainnet;
18 |
19 | const mocks = mocksForV3Chain(chainId, BETA_CONTRACT_ADDRESSES[chainId]);
20 | seriesNonceManagerFacade = mocks.seriesNonceManagerFacade;
21 | seriesNonceManagerPredicateBuilder = mocks.seriesNonceManagerPredicateBuilder;
22 | });
23 |
24 | it("advanceNonce", () => {
25 | expect(
26 | seriesNonceManagerFacade.advanceNonce(NonceSeriesV2.P2PV3, 3),
27 | ).toMatchSnapshot();
28 | });
29 |
30 | it("increaseNonce", () => {
31 | expect(
32 | seriesNonceManagerFacade.increaseNonce(NonceSeriesV2.P2PV3),
33 | ).toMatchSnapshot();
34 | });
35 |
36 | it("nonceEquals", async () => {
37 | expect(
38 | seriesNonceManagerPredicateBuilder.nonceEquals(NonceSeriesV2.P2PV3, walletAddress, 101),
39 | ).toMatchSnapshot();
40 | });
41 |
42 | describe("web3 calls", () => {
43 | it("nonce", async () => {
44 | const nonce = await seriesNonceManagerFacade.getNonce(NonceSeriesV2.LimitOrderV3, walletAddress);
45 |
46 | expect(nonce).toBe(4n);
47 | });
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/src/series-nonce-manager.facade.ts:
--------------------------------------------------------------------------------
1 | import { SERIES_NONCE_MANAGER_ABI } from "./series-nonce-manager.const";
2 | import { Nonce } from "./model/limit-order-protocol.model";
3 | import { Series, SeriesNonceManagerMethods } from "./model/series-nonce-manager.model";
4 | import { AbstractSmartcontractFacade } from "./utils/abstract-facade";
5 |
6 | export class SeriesNonceManagerFacade
7 | extends AbstractSmartcontractFacade
8 | {
9 | ABI = SERIES_NONCE_MANAGER_ABI;
10 |
11 | async getNonce(
12 | series: Series,
13 | makerAddress: string,
14 | ): Promise {
15 | const callData = this.getContractCallData(
16 | SeriesNonceManagerMethods.nonce,
17 | [
18 | series,
19 | makerAddress,
20 | ],
21 | );
22 |
23 | return this.providerConnector
24 | .ethCall(this.contractAddress, callData)
25 | .then((nonce) => BigInt(nonce));
26 | }
27 |
28 | advanceNonce = (series: Series, count: Nonce): string => {
29 | return this.getContractCallData(
30 | SeriesNonceManagerMethods.advanceNonce,
31 | [series, count],
32 | );
33 | }
34 |
35 | increaseNonce(series: Series): string {
36 | return this.getContractCallData(
37 | SeriesNonceManagerMethods.increaseNonce,
38 | [series],
39 | );
40 | }
41 | }
--------------------------------------------------------------------------------
/src/test/helpers.ts:
--------------------------------------------------------------------------------
1 | import { PrivateKeyProviderConnector } from "../connector/private-key-provider.connector";
2 | import {EIP712Params} from "../limit-order.builder";
3 | import Web3 from "web3";
4 | import { Erc20Facade } from "../erc20.facade";
5 | import { SeriesNonceManagerFacade } from "../series-nonce-manager.facade";
6 | import { seriesNonceManagerContractAddresses } from "../series-nonce-manager.const";
7 | import { SeriesNonceManagerPredicateBuilder } from "../series-nonce-manager-predicate.builder";
8 | import {LimitOrderPredicateV3Builder} from "../limit-order-predicate-v3.builder";
9 | import {LimitOrderProtocolV3Facade} from "../limit-order-protocol-v3.facade";
10 | import {LimitOrderV3Builder} from "../limit-order-v3.builder";
11 | import {rpcUrls} from "../utils/rpc-url.const";
12 | import {ChainId, limitOrderProtocolAddresses} from "../limit-order-protocol-addresses.const";
13 |
14 |
15 | export function mocksForV3Chain(
16 | chainId: ChainId,
17 | contractAddressOverride?: string,
18 | seriesNonceManagerContractAddressOverride?: string,
19 | domainSettings: EIP712Params = { domainName: 'Limit Order Protocol', version: '4' }
20 | ) {
21 | const web3Provider = new Web3.providers.HttpProvider(
22 | rpcUrls[chainId],
23 | { headers: [{ name: 'auth-key', value: process.env.AUTHKEY || '' }] }
24 | );
25 | const web3 = new Web3(web3Provider);
26 | const privateKey = '552be66668d14242eeeb0e84600f0946ddddc77777777c3761ea5906e9ddcccc';
27 |
28 | const contractAddress = contractAddressOverride || limitOrderProtocolAddresses[chainId];
29 | const seriesNonceManagerContractAddress = seriesNonceManagerContractAddressOverride || seriesNonceManagerContractAddresses[chainId];
30 |
31 | const providerConnector = new PrivateKeyProviderConnector(privateKey, web3);
32 | const facade = new LimitOrderProtocolV3Facade(
33 | contractAddress,
34 | chainId,
35 | providerConnector,
36 | );
37 | const limitOrderPredicateBuilder = new LimitOrderPredicateV3Builder(facade);
38 |
39 | const limitOrderBuilder = new LimitOrderV3Builder(
40 | providerConnector,
41 | domainSettings,
42 | );
43 |
44 | const erc20Facade = new Erc20Facade(providerConnector);
45 |
46 | const seriesNonceManagerFacade = new SeriesNonceManagerFacade(
47 | seriesNonceManagerContractAddress,
48 | chainId,
49 | providerConnector,
50 | );
51 | const seriesNonceManagerPredicateBuilder = new SeriesNonceManagerPredicateBuilder(seriesNonceManagerFacade);
52 |
53 | return {
54 | facade,
55 | erc20Facade,
56 | limitOrderPredicateBuilder,
57 | limitOrderBuilder,
58 | seriesNonceManagerFacade,
59 | seriesNonceManagerPredicateBuilder,
60 | contractAddress,
61 | seriesNonceManagerContractAddress,
62 | providerConnector,
63 | };
64 | }
65 |
66 | export const BETA_CONTRACT_ADDRESSES = {
67 | // V3 prerelease
68 | [ChainId.ethereumMainnet]: '0x9b934b33fef7a899f502bc191e820ae655797ed3',
69 | [ChainId.auroraMainnet]: '0x8266c553f269b2eEb2370539193bCD0Eff8cC2De'.toLowerCase(),
70 | }
71 |
--------------------------------------------------------------------------------
/src/test/mocks.ts:
--------------------------------------------------------------------------------
1 |
2 | /* eslint-disable max-len */
3 | import {ZERO_ADDRESS} from "../limit-order-protocol.const";
4 |
5 | export const largeInteractions = {
6 | getMakingAmount: '0x20b83f2d000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000001b1ae4d6e2ef500000',
7 | getTakingAmount: '0x7e2d2183000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000001b1ae4d6e2ef500000',
8 | predicate: '0xbfa75143000000000000000000000000000000000000000000000000000000680000004400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000068cf6fc6e3000000000000000000000000f9f0419b7ead1807c257adddd3e2956e8663c3ca000000000000000000000000000000000000000000000000000000000000000163592c2b0000000000000000000000000000000000000000000000000000000073187537000000000000000000000000000000000000000000000000',
9 | permit: '0x',
10 | preInteraction: '0x',
11 | postInteraction: '0x1282d0c06368c40c8d4a4d818d78f258d982437b',
12 | makerAssetData: '0x',
13 | takerAssetData: '0x',
14 | }
15 |
16 | export const largeResult = {
17 | offsets: '0x1800000016c0000016c0000016c00000088000000440000000000000000',
18 | interactions: '0x20b83f2d000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000001b1ae4d6e2ef5000007e2d2183000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000001b1ae4d6e2ef500000bfa75143000000000000000000000000000000000000000000000000000000680000004400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000068cf6fc6e3000000000000000000000000f9f0419b7ead1807c257adddd3e2956e8663c3ca000000000000000000000000000000000000000000000000000000000000000163592c2b00000000000000000000000000000000000000000000000000000000731875370000000000000000000000000000000000000000000000001282d0c06368c40c8d4a4d818d78f258d982437b',
19 | }
20 |
21 |
22 | export const extensionWithPredicate = {
23 | extension: '0x00000124000001240000012400000124000000000000000000000000000000004f38e2b8000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a4bf15fcd80000000000000000000000005eb3bc0a489c5a8288765d2336659ebca68fcd00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000241ae4f1b600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
24 | result: {
25 | interactions: {
26 | "makerAssetSuffix": "0x",
27 | "makingAmountGetter": "0x",
28 | "permit": "0x",
29 | "postInteraction": "0x",
30 | "preInteraction": "0x",
31 | predicate: '0x4f38e2b8000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a4bf15fcd80000000000000000000000005eb3bc0a489c5a8288765d2336659ebca68fcd00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000241ae4f1b600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
32 | "takerAssetSuffix": "0x",
33 | "takingAmountGetter": "0x",
34 | },
35 | customData: '0x'
36 | }
37 | }
38 |
39 | export const extensionWithPermit = {
40 | extension: '0x000000f4000000f4000000f400000000000000000000000000000000000000005081a39b8A5f0E35a8D959395a630b68B74Dd30f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000dbc43ba45381e02825b14322cddd15ec4b3164e60000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000ffffffffffffffff000000000000000000000000000000000000000000000000000000000000001cca1d0c388f013a43f8673da3a0db06db14538e18f8291c9b436db0c287d3e7e85ff2e8bcb86ec41549954b6fb970f9d24c55b3338be2402e1383da7ef33d332d',
41 | result: {
42 | interactions: {
43 | "makerAssetSuffix": "0x",
44 | "makingAmountGetter": "0x",
45 | "permit": "0x5081a39b8A5f0E35a8D959395a630b68B74Dd30f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000dbc43ba45381e02825b14322cddd15ec4b3164e60000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000ffffffffffffffff000000000000000000000000000000000000000000000000000000000000001cca1d0c388f013a43f8673da3a0db06db14538e18f8291c9b436db0c287d3e7e85ff2e8bcb86ec41549954b6fb970f9d24c55b3338be2402e1383da7ef33d332d",
46 | "postInteraction": "0x",
47 | "preInteraction": "0x",
48 | "predicate": '0x',
49 | "takerAssetSuffix": "0x",
50 | "takingAmountGetter": "0x",
51 | },
52 | customData: '0x'
53 | }
54 | }
55 |
56 | export const extensionWithPermitAndPredicate = {
57 | extension: '0x0000021800000218000002180000012400000000000000000000000000000000ca4ece22000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a4bf15fcd8000000000000000000000000c351628eb244ec633d5f21fbd6621e1a683b1181000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000241ae4f1b60000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082e01223d51Eb87e16A03E24687EDF0F294da6f1000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000007969c5ed335650692bc04293b07f5bf2e7a673c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000ffffffffffffffff000000000000000000000000000000000000000000000000000000000000001c2313d1c7727e6a79cb4cd1cde3fc3937ac9ae91da28697b159dcb5d9329d952d7a23e0e603be86a5a0f58a77af5b5ee53e962482b54a74b313c37c24c5960ef6',
58 | result: {
59 | interactions: {
60 | "makerAssetSuffix": "0x",
61 | "makingAmountGetter": "0x",
62 | "permit": "0x82e01223d51Eb87e16A03E24687EDF0F294da6f1000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000007969c5ed335650692bc04293b07f5bf2e7a673c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000ffffffffffffffff000000000000000000000000000000000000000000000000000000000000001c2313d1c7727e6a79cb4cd1cde3fc3937ac9ae91da28697b159dcb5d9329d952d7a23e0e603be86a5a0f58a77af5b5ee53e962482b54a74b313c37c24c5960ef6",
63 | "postInteraction": "0x",
64 | "preInteraction": "0x",
65 | "predicate": '0xca4ece22000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a4bf15fcd8000000000000000000000000c351628eb244ec633d5f21fbd6621e1a683b1181000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000241ae4f1b600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
66 | "takerAssetSuffix": "0x",
67 | "takingAmountGetter": "0x",
68 | },
69 | customData: '0x'
70 | }
71 | }
72 |
73 | export const commonMakerTraits = {
74 | hex: '0x4000000000000000000000000000000000000000000000000000000000000000',
75 | result: {
76 | allowedSender: ZERO_ADDRESS.slice(-20),
77 | shouldCheckEpoch: false,
78 | allowPartialFill: true,
79 | allowPriceImprovement: true,
80 | allowMultipleFills: true,
81 | usePermit2: false,
82 | unwrapWeth: false,
83 | expiry: 0,
84 | nonce: BigInt(0),
85 | series: BigInt(0),
86 | hasExtension: false,
87 | }
88 | }
89 |
90 | export const difficultMakerTraits = {
91 | hex: '0x4580000000000000000000020000000003006500c7429e03e53415d37aa96045',
92 | result: {
93 | allowedSender: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045'.slice(-20),
94 | shouldCheckEpoch: true,
95 | allowPartialFill: true,
96 | allowPriceImprovement: true,
97 | allowMultipleFills: true,
98 | usePermit2: true,
99 | unwrapWeth: true,
100 | expiry: 1694549826,
101 | nonce: BigInt(3),
102 | series: BigInt(2),
103 | hasExtension: false,
104 | }
105 | }
106 |
107 | export const orderWithExtension = {
108 | order: {
109 | "salt": "706003369302049690217199455279061851344801602493",
110 | "maker": "0xcd4060fa7b5164281f150fa290181401524ef76f",
111 | "receiver": "0x0000000000000000000000000000000000000000",
112 | "makerAsset": "0x6b175474e89094c44da98b954eedeac495271d0f",
113 | "takerAsset": "0xa68dd8cb83097765263adad881af6eed479c4a33",
114 | "makingAmount": "1000000000000000000",
115 | "takingAmount": "5338989685580281000000",
116 | "makerTraits": "0x4200000000000000000000000000000000006516d36200000000000000000000"
117 | },
118 | extension: "0x00000114000001140000011400000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000cd4060fa7b5164281f150fa290181401524ef76f00000000000000000000000012e427aafe3bd8dd8543cdd944970d0ae453b6780000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006516d35e0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000001b1d7d58603be66ecc1afe1c29152f2be24cad32b88c217529faf00f38275248920ad5b1416cbc672de66c58f8e86c44fab41665cb3fc766e0034c7b09a7b94136"
119 | }
120 |
121 | export const extensionWithPermitOnly = {
122 | extension: '0x00000114000001140000011400000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000cd4060fa7b5164281f150fa290181401524ef76f00000000000000000000000012e427aafe3bd8dd8543cdd944970d0ae453b6780000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006516d35e0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000001b1d7d58603be66ecc1afe1c29152f2be24cad32b88c217529faf00f38275248920ad5b1416cbc672de66c58f8e86c44fab41665cb3fc766e0034c7b09a7b94136',
123 | result: {
124 | interactions: {
125 | "makerAssetSuffix": "0x",
126 | "makingAmountGetter": "0x",
127 | "permit": "0x6b175474e89094c44da98b954eedeac495271d0f000000000000000000000000cd4060fa7b5164281f150fa290181401524ef76f00000000000000000000000012e427aafe3bd8dd8543cdd944970d0ae453b6780000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006516d35e0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000001b1d7d58603be66ecc1afe1c29152f2be24cad32b88c217529faf00f38275248920ad5b1416cbc672de66c58f8e86c44fab41665cb3fc766e0034c7b09a7b94136",
128 | "postInteraction": "0x",
129 | "preInteraction": "0x",
130 | "predicate": '0x',
131 | "takerAssetSuffix": "0x",
132 | "takingAmountGetter": "0x",
133 | },
134 | customData: '0x'
135 | }
136 | }
137 |
138 |
139 | /* eslint-enable max-len */
140 |
--------------------------------------------------------------------------------
/src/utils/abi.ts:
--------------------------------------------------------------------------------
1 | import { AbiItem } from "../model/abi.model";
2 |
3 |
4 | export function getABIFor(abi: AbiItem[], methodName: string): AbiItem | void {
5 | return abi.find(({name}) => name === methodName)
6 | }
7 |
--------------------------------------------------------------------------------
/src/utils/abstract-facade.ts:
--------------------------------------------------------------------------------
1 | import { ProviderConnector } from "src/connector/provider.connector";
2 | import { AbiItem } from "src/model/abi.model";
3 |
4 | import {ChainId} from "../limit-order-protocol-addresses.const";
5 |
6 |
7 | export abstract class AbstractSmartcontractFacade {
8 |
9 | abstract ABI: AbiItem[];
10 |
11 | constructor(
12 | public readonly contractAddress: string,
13 | public readonly chainId: ChainId | number,
14 | public readonly providerConnector: ProviderConnector,
15 | ) {}
16 |
17 | getContractCallData(
18 | methodName: ABI_METHODS,
19 | methodParams: unknown[] = []
20 | ): string {
21 | return this.providerConnector.contractEncodeABI(
22 | this.ABI,
23 | this.contractAddress,
24 | methodName,
25 | methodParams
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/abstract-predicate-builder.ts:
--------------------------------------------------------------------------------
1 | import { AbstractSmartcontractFacade } from "./abstract-facade";
2 |
3 | export abstract class AbstractPredicateBuilder> {
4 |
5 | constructor(
6 | readonly facade: T,
7 | ) {}
8 | }
--------------------------------------------------------------------------------
/src/utils/build-taker-traits.ts:
--------------------------------------------------------------------------------
1 | import {trim0x} from "./limit-order.utils";
2 | import {solidityPacked} from "ethers";
3 |
4 | const TakerTraitsConstants = {
5 | _MAKER_AMOUNT_FLAG: BigInt(1) << BigInt(255),
6 | _UNWRAP_WETH_FLAG: BigInt(1) << BigInt(254),
7 | _SKIP_ORDER_PERMIT_FLAG: BigInt(1) << BigInt(253),
8 | _USE_PERMIT2_FLAG: BigInt(1) << BigInt(252),
9 | _ARGS_HAS_TARGET: BigInt(1) << BigInt(251),
10 |
11 | _ARGS_EXTENSION_LENGTH_OFFSET: BigInt(224),
12 | _ARGS_EXTENSION_LENGTH_MASK: 0xffffff,
13 | _ARGS_INTERACTION_LENGTH_OFFSET: BigInt(200),
14 | _ARGS_INTERACTION_LENGTH_MASK: 0xffffff,
15 | };
16 |
17 | export function buildTakerTraits({
18 | makingAmount = false,
19 | unwrapWeth = false,
20 | skipMakerPermit = false,
21 | usePermit2 = false,
22 | target = '0x',
23 | extension = '0x',
24 | interaction = '0x',
25 | threshold = BigInt(0),
26 | } = {}): { traits: bigint, args: string } {
27 | return {
28 | traits: BigInt(threshold) | (
29 | (makingAmount ? TakerTraitsConstants._MAKER_AMOUNT_FLAG : BigInt(0)) |
30 | (unwrapWeth ? TakerTraitsConstants._UNWRAP_WETH_FLAG : BigInt(0)) |
31 | (skipMakerPermit ? TakerTraitsConstants._SKIP_ORDER_PERMIT_FLAG : BigInt(0)) |
32 | (usePermit2 ? TakerTraitsConstants._USE_PERMIT2_FLAG : BigInt(0)) |
33 | (trim0x(target).length > 0 ? TakerTraitsConstants._ARGS_HAS_TARGET : BigInt(0)) |
34 | (
35 | BigInt(trim0x(extension).length / 2)
36 | << TakerTraitsConstants._ARGS_EXTENSION_LENGTH_OFFSET
37 | ) |
38 | (
39 | BigInt(trim0x(interaction).length / 2)
40 | << TakerTraitsConstants._ARGS_INTERACTION_LENGTH_OFFSET
41 | )
42 | ),
43 | args: solidityPacked(
44 | ['bytes', 'bytes', 'bytes'],
45 | [target, extension, interaction],
46 | ),
47 | };
48 | }
49 |
--------------------------------------------------------------------------------
/src/utils/decoders/limit-order-predicate-decoders.ts:
--------------------------------------------------------------------------------
1 | import { LimitOrderDecoder } from "../../limit-order.decoder";
2 | import {
3 | CallArguments,
4 | DecodableCall,
5 | DecodersImplementation,
6 | PredicateBytes,
7 | PredicateFn,
8 | } from "../../limit-order-predicate.decoder";
9 | import { FunctionFragment } from "@ethersproject/abi";
10 | import { unpackTimestampAndNoncePredicate } from "../limit-order.utils";
11 |
12 |
13 | export class LimitOrderPredicateDecoders
14 | implements DecodersImplementation
15 | {
16 | or = this.logicalDecoder;
17 |
18 | and = this.logicalDecoder;
19 |
20 | lt = this.comparingDecoder;
21 |
22 | gt = this.comparingDecoder;
23 |
24 | eq = this.comparingDecoder;
25 |
26 | arbitraryStaticCall(fn: FunctionFragment, data: CallArguments, contract: string): PredicateFn {
27 | return new PredicateFn(
28 | fn.name,
29 | {
30 | target: new PredicateBytes(data.target, contract),
31 | data: new DecodableCall(data.data, data.target),
32 | },
33 | contract,
34 | );
35 | }
36 |
37 | timestampBelow(fn: FunctionFragment, data: CallArguments, contract: string): PredicateFn {
38 | return new PredicateFn(
39 | fn.name,
40 | {
41 | timestamp: new PredicateBytes(data.time, contract),
42 | },
43 | contract,
44 | );
45 | }
46 |
47 | timestampBelowAndNonceEquals(
48 | fn: FunctionFragment,
49 | data: CallArguments,
50 | contract: string,
51 | ): PredicateFn {
52 | const {
53 | address,
54 | nonce,
55 | timestamp,
56 | } = unpackTimestampAndNoncePredicate(
57 | data.timeNonceAccount.toHexString(),
58 | false,
59 | );
60 |
61 | return new PredicateFn(
62 | fn.name,
63 | {
64 | timestamp: new PredicateBytes(timestamp.toString(), contract),
65 | address: new PredicateBytes(address, contract),
66 | nonce: new PredicateBytes(nonce.toString(), contract),
67 | },
68 | contract,
69 | );
70 | }
71 |
72 | nonceEquals(fn: FunctionFragment, data: CallArguments, contract: string): PredicateFn {
73 | return new PredicateFn(
74 | fn.name,
75 | {
76 | makerAddress: new PredicateBytes(data.makerAddress, contract),
77 | nonce: new PredicateBytes(data.makerNonce.toHexString(), contract),
78 | },
79 | contract,
80 | );
81 | }
82 |
83 | nonce(fn: FunctionFragment, data: CallArguments, contract: string): PredicateFn {
84 | return new PredicateFn(
85 | fn.name,
86 | {
87 | makerAddress: new PredicateBytes(data[0], contract),
88 | },
89 | contract,
90 | );
91 | }
92 |
93 | private logicalDecoder(
94 | fn: FunctionFragment,
95 | data: CallArguments,
96 | contract: string,
97 | ): PredicateFn {
98 | const args = LimitOrderDecoder.unpackStaticCalls(data.offsets, data.data);
99 |
100 | return new PredicateFn(
101 | fn.name,
102 | args.map(calldata => new DecodableCall(calldata, contract)),
103 | contract,
104 | )
105 | }
106 |
107 |
108 | private comparingDecoder(
109 | fn: FunctionFragment,
110 | data: CallArguments,
111 | contract: string,
112 | ): PredicateFn {
113 | return new PredicateFn(
114 | fn.name,
115 | {
116 | value: new PredicateBytes(data.value.toString(), contract),
117 | data: new DecodableCall(data.data, contract),
118 | },
119 | contract,
120 | )
121 | }
122 | }
--------------------------------------------------------------------------------
/src/utils/decoders/series-nonce-manager-decoders.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CallArguments,
3 | DecodersImplementation,
4 | PredicateBytes,
5 | PredicateFn,
6 | } from "../../limit-order-predicate.decoder";
7 | import { FunctionFragment } from "@ethersproject/abi";
8 | import { unpackTimestampAndNoncePredicate } from "../limit-order.utils";
9 | import { Series } from "../../model/series-nonce-manager.model";
10 |
11 |
12 | export class SeriesNonceManagerDecoders
13 | implements DecodersImplementation
14 | {
15 |
16 | timestampBelowAndNonceEquals(
17 | fn: FunctionFragment,
18 | data: CallArguments,
19 | contract: string,
20 | ): PredicateFn {
21 | const {
22 | address,
23 | nonce,
24 | series,
25 | timestamp,
26 | } = unpackTimestampAndNoncePredicate(
27 | data.timeNonceSeriesAccount.toHexString(),
28 | true,
29 | );
30 |
31 | return new PredicateFn(
32 | fn.name,
33 | {
34 | series: new PredicateBytes((series as Series).toString(), contract),
35 | timestamp: new PredicateBytes(timestamp.toString(), contract),
36 | address: new PredicateBytes(address, contract),
37 | nonce: new PredicateBytes(nonce.toString(), contract),
38 | },
39 | contract,
40 | );
41 | }
42 |
43 | nonceEquals(fn: FunctionFragment, data: CallArguments, contract: string): PredicateFn {
44 | return new PredicateFn(
45 | fn.name,
46 | {
47 | makerAddress: new PredicateBytes(data.makerAddress, contract),
48 | nonce: new PredicateBytes(data.makerNonce.toHexString(), contract),
49 | },
50 | contract,
51 | );
52 | }
53 |
54 | nonce(fn: FunctionFragment, data: CallArguments, contract: string): PredicateFn {
55 | return new PredicateFn(
56 | fn.name,
57 | {
58 | makerAddress: new PredicateBytes(data[0], contract),
59 | },
60 | contract,
61 | );
62 | }
63 | }
--------------------------------------------------------------------------------
/src/utils/generate-salt.ts:
--------------------------------------------------------------------------------
1 | export function generateSalt(): bigint {
2 | // uint256 is 32 bytes
3 | const buffer = new Uint8Array(32);
4 | window.crypto.getRandomValues(buffer);
5 | const slicedBytes = Array.from(buffer)
6 | .map(byte => byte.toString(16).padStart(2, '0'))
7 | .join('');
8 | return BigInt(`0x${slicedBytes}`);
9 | }
10 |
--------------------------------------------------------------------------------
/src/utils/get-rpc-code.test.ts:
--------------------------------------------------------------------------------
1 | import {getRPCCode} from './get-rpc-code';
2 |
3 | describe('getRPCCode', () => {
4 | it('empty string', () => {
5 | expect(getRPCCode('')).toBeNull();
6 | });
7 |
8 | it('string without object', () => {
9 | expect(getRPCCode('test str')).toBeNull();
10 | });
11 |
12 | it('empty object', () => {
13 | expect(getRPCCode('test str {}')).toBeNull();
14 | });
15 |
16 | it('incorrect object', () => {
17 | expect(
18 | getRPCCode('test str { "code": 1, "message": "msg" }')
19 | ).toBeNull();
20 | });
21 |
22 | it('incorrect data', () => {
23 | expect(
24 | getRPCCode(
25 | 'test str { "code": 1, "message": "msg", "data": "test" }'
26 | )
27 | ).toBeNull();
28 | });
29 |
30 | it('correct string', () => {
31 | const string = `
32 | Internal JSON-RPC error.
33 | {
34 | "code": -32015,
35 | "message": "VM execution error.",
36 | "data": "Reverted 0x43414c4c5f524553554c54535f31"
37 | }
38 | `;
39 |
40 | expect(getRPCCode(string)).toEqual('CALL_RESULTS_1');
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/src/utils/get-rpc-code.ts:
--------------------------------------------------------------------------------
1 | import Web3 from 'web3';
2 |
3 | interface RPCError {
4 | code: number;
5 | data: string;
6 | message: string;
7 | }
8 |
9 | export function getRPCCode(response: string): string | null {
10 | const objectRegexp = /{(\n*|.*)*}/gi; // take all between { }
11 | const match = response.match(objectRegexp);
12 | const matched = match ? match[0] : null;
13 |
14 | const rpcError = matched ? parseErrorObject(matched) : null;
15 | const data = rpcError ? rpcError.data : null;
16 | return data ? extractCodeFromData(data) : null;
17 | }
18 |
19 | function extractCodeFromData(data: string): string | null {
20 | const hexRegexp = /0[xX][0-9a-fA-F]+/;
21 | const matched = data.match(hexRegexp);
22 | const hex = matched?.[0];
23 |
24 | return hex ? Web3.utils.hexToAscii(hex) : null;
25 | }
26 |
27 | function parseErrorObject(errorObjectString: string): RPCError | null {
28 | try {
29 | return JSON.parse(errorObjectString) as RPCError;
30 | } catch (e) {
31 | return null;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/utils/helpers.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Determine whether the given `input` is iterable.
3 | *
4 | * @returns {Boolean}
5 | */
6 | export function isIterable(input: unknown): input is Iterable {
7 | if (input === null || typeof input !== 'object') {
8 | return false
9 | }
10 |
11 | if (input instanceof Array) return true;
12 |
13 | return typeof (input as Iterable)[Symbol.iterator] === 'function'
14 | }
15 |
16 |
17 | export function mapObject(
18 | obj: Record,
19 | callbackfn: (value: ValueBefore, key: Index) => ValueAfter,
20 | ): {
21 | [key in Index]: ValueAfter
22 | } {
23 | const result: Record = {} as Record;
24 |
25 | Object.entries(obj).forEach(
26 | ([key, value]) => {
27 | result[key as Index] = callbackfn(value as ValueBefore, key as Index);
28 | }
29 | );
30 |
31 | return result;
32 | }
33 |
--------------------------------------------------------------------------------
/src/utils/limit-order-rfq.utils.ts:
--------------------------------------------------------------------------------
1 | // #!/usr/bin/env node
2 | //
3 | // import prompts from 'prompts';
4 | // import yargs from 'yargs';
5 | // import {
6 | // cancelOrderSchema,
7 | // createOrderSchema,
8 | // fillOrderSchema,
9 | // operationSchema,
10 | // } from './limit-order-rfq.const';
11 | // import {OperationParams} from './limit-order-rfq.model';
12 | // import {
13 | // cancelOrderOperation,
14 | // createOrderOperation,
15 | // fillOrderOperation,
16 | // } from './limit-order-rfq.helpers';
17 | //
18 | // const allSchemas = [
19 | // cancelOrderSchema,
20 | // createOrderSchema,
21 | // fillOrderSchema,
22 | // operationSchema,
23 | // ];
24 | //
25 | // (async () => {
26 | // const argvKeys = Object.keys(yargs.argv);
27 | // const isRunningWithArgv = allSchemas.some((schema) => {
28 | // return schema
29 | // .map((i) => i.name as string)
30 | // .every((param) => argvKeys.includes(param));
31 | // });
32 | //
33 | // prompts.override(yargs.argv);
34 | //
35 | // const operationResult = (await prompts(operationSchema)) as OperationParams;
36 | //
37 | // switch (operationResult.operation) {
38 | // case 'create':
39 | // await createOrderOperation(isRunningWithArgv);
40 | // break;
41 | // case 'fill':
42 | // await fillOrderOperation(isRunningWithArgv);
43 | // break;
44 | // case 'cancel':
45 | // await cancelOrderOperation(isRunningWithArgv);
46 | // break;
47 | // default:
48 | // console.log('Unknown operation: ', operationResult.operation);
49 | // break;
50 | // }
51 | // })();
52 |
--------------------------------------------------------------------------------
/src/utils/limit-order.utils.test.ts:
--------------------------------------------------------------------------------
1 | import { ZX } from "../limit-order-protocol.const";
2 | import {mocksForV3Chain} from "../test/helpers";
3 | import {
4 | packSkipPermitAndThresholdAmount,
5 | UINT48_BITMASK,
6 | unpackTimestampAndNoncePredicate,
7 | } from "./limit-order.utils";
8 | import {LimitOrderPredicateV3Builder} from "../limit-order-predicate-v3.builder";
9 | import {ChainId} from "../limit-order-protocol-addresses.const";
10 |
11 | describe("limit-order.utils", () => {
12 | const walletAddress = '0xfb3c7eb936cAA12B5A884d612393969A557d4307';
13 |
14 | let limitOrderPredicateBuilder: LimitOrderPredicateV3Builder;
15 |
16 |
17 | beforeAll(() => {
18 | const chainId = ChainId.ethereumMainnet;
19 |
20 | const mocks = mocksForV3Chain(chainId);
21 | limitOrderPredicateBuilder = mocks.limitOrderPredicateBuilder;
22 |
23 | jest.spyOn(console, 'error').mockImplementation();
24 | });
25 |
26 | describe("unpackTimestampAndNoncePredicate", () => {
27 | it("call with calldata of predicate", () => {
28 | expect(
29 | unpackTimestampAndNoncePredicate('608d1a4600000000000efb3c7eb936caa12b5a884d612393969a557d4307')
30 | ).toMatchObject({
31 | address: '0xfb3c7eb936caa12b5a884d612393969a557d4307',
32 | nonce: 14n,
33 | timestamp: 1619860038n,
34 | })
35 | });
36 |
37 | it("call with ZX calldata", () => {
38 | expect(
39 | unpackTimestampAndNoncePredicate('0x608d1a4600000000000efb3c7eb936caa12b5a884d612393969a557d4307')
40 | ).toMatchObject({
41 | address: '0xfb3c7eb936caa12b5a884d612393969a557d4307',
42 | nonce: 14n,
43 | timestamp: 1619860038n,
44 | })
45 | });
46 |
47 | it("call with complete predicate", () => {
48 | expect(
49 | unpackTimestampAndNoncePredicate('0x2cc2878d0000608d1a4600000000000efb3c7eb936caa12b5a884d612393969a557d4307')
50 | ).toMatchObject({
51 | address: '0xfb3c7eb936caa12b5a884d612393969a557d4307',
52 | nonce: 14n,
53 | timestamp: 1619860038n,
54 | })
55 | });
56 |
57 | it("call with wrapping data in predicate", () => {
58 | expect(
59 | // eslint-disable-next-line max-len
60 | unpackTimestampAndNoncePredicate('0xFFFFFFFFFFFFFFFF2cc2878d0000608d1a4600000000000efb3c7eb936caa12b5a884d612393969a557d4307FFFFFFFFFFFFFFFF')
61 | ).toMatchObject({
62 | address: '0xfb3c7eb936caa12b5a884d612393969a557d4307',
63 | nonce: 14n,
64 | timestamp: 1619860038n,
65 | })
66 | });
67 |
68 | it("call with complex order predicate", () => {
69 | const nonce = 14n;
70 | const timestamp = 1619860038n;
71 | const { and, eq, timestampBelowAndNonceEquals, arbitraryStaticCall } = limitOrderPredicateBuilder;
72 | const predicate = and(
73 | eq(
74 | '0',
75 | arbitraryStaticCall(
76 | walletAddress,
77 | ZX,
78 | )
79 | ),
80 | timestampBelowAndNonceEquals(timestamp, nonce, walletAddress),
81 | eq(
82 | '0',
83 | arbitraryStaticCall(
84 | walletAddress,
85 | ZX,
86 | )
87 | ),
88 | );
89 |
90 | expect(
91 | unpackTimestampAndNoncePredicate(predicate)
92 | ).toMatchObject({
93 | address: walletAddress.toLowerCase(),
94 | nonce,
95 | timestamp,
96 | })
97 | });
98 |
99 | it("maximum possible values", () => {
100 | const predicate = limitOrderPredicateBuilder.timestampBelowAndNonceEquals(
101 | UINT48_BITMASK,
102 | UINT48_BITMASK,
103 | walletAddress,
104 | )
105 |
106 | expect(
107 | unpackTimestampAndNoncePredicate(predicate)
108 | ).toMatchObject({
109 | address: walletAddress.toLowerCase(),
110 | nonce: UINT48_BITMASK,
111 | timestamp: UINT48_BITMASK,
112 | })
113 | });
114 | });
115 |
116 |
117 | describe("packSkipPermitAndThresholdAmount when thresholdAmount is hex string", () => {
118 | const thresholdAmount = BigInt(2)**BigInt(254);
119 | const skipPermit = (BigInt(1) << BigInt(255));
120 | it("with skip", () => {
121 | expect(
122 | packSkipPermitAndThresholdAmount(ZX + thresholdAmount.toString(16), true)
123 | ).toBe(
124 | ZX + (thresholdAmount + skipPermit).toString(16),
125 | );
126 | });
127 |
128 | it("without skip", () => {
129 | expect(
130 | packSkipPermitAndThresholdAmount(ZX + thresholdAmount.toString(16), false)
131 | ).toBe((ZX + thresholdAmount.toString(16)));
132 | });
133 | });
134 |
135 | describe("packSkipPermitAndThresholdAmount when thresholdAmount is 10 radix string", () => {
136 | const thresholdAmount = BigInt(2)**BigInt(254);
137 | const skipPermit = (BigInt(1) << BigInt(255));
138 | it("with skip", () => {
139 | expect(
140 | packSkipPermitAndThresholdAmount(thresholdAmount.toString(), true)
141 | ).toBe(
142 | ZX + (thresholdAmount + skipPermit).toString(16),
143 | );
144 | });
145 |
146 | it("without skip", () => {
147 | expect(
148 | packSkipPermitAndThresholdAmount(thresholdAmount.toString(), false)
149 | ).toBe((ZX + thresholdAmount.toString(16)));
150 | });
151 | });
152 |
153 | });
154 |
--------------------------------------------------------------------------------
/src/utils/limit-order.utils.ts:
--------------------------------------------------------------------------------
1 | import { Series } from '../model/series-nonce-manager.model';
2 | import {ZX} from '../limit-order-protocol.const';
3 | import { ErrorResponse } from '../limit-order-protocol.facade';
4 | import {Signature} from "ethers";
5 |
6 | export const UINT32_BITS = BigInt(32);
7 | export const UINT32_BITMASK = BigInt('0xFFFFFFFF');
8 | export const UINT16_BITMASK = BigInt('0xFFFF');
9 | export const UINT40_BITMASK = BigInt('0xFFFFFFFFFF');
10 | export const UINT48_BITMASK = BigInt('0xFFFFFFFFFFFF');
11 | export const ADDRESS_MASK = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF');
12 |
13 | export function trim0x(hexString: string): string {
14 | if (hexString.startsWith('0x')) {
15 | return hexString.substring(2);
16 | }
17 | return hexString;
18 | }
19 |
20 | export function getOffsets(data: string[]): bigint {
21 | const cumulativeSum = ((sum: bigint) => (value: bigint) => {
22 | sum += value;
23 | return sum;
24 | })
25 | (BigInt(0));
26 |
27 | return data
28 | .map((hex) => {
29 | if (hex.startsWith(ZX))
30 | return BigInt(hex.length / 2 - 1);
31 |
32 | return BigInt(hex.length / 2);
33 | })
34 | .map(cumulativeSum)
35 | .reduce((bytesAccumularot, offset, index) => {
36 | return bytesAccumularot + (BigInt(offset) << ((UINT32_BITS * BigInt(index))));
37 | }, BigInt(0))
38 | }
39 |
40 | export function parseInteractionForField(
41 | offsets: bigint,
42 | interactions: string,
43 | field: number,
44 | ): string {
45 | const {fromByte, toByte} = getOffsetForInteraction(offsets, field)
46 |
47 | return '0x' + trim0x(interactions).slice(fromByte * 2, toByte * 2)
48 | }
49 |
50 | export function getOffsetForInteraction(offsets: bigint, field: number) {
51 | const fromByteBN = field === 0
52 | ? '0'
53 | : offsets >> BigInt((field - 1) * 32) & UINT32_BITMASK;
54 | const toByteBN = offsets >> BigInt(field * 32) & UINT32_BITMASK;
55 |
56 | return {
57 | fromByte: parseInt(fromByteBN.toString()),
58 | toByte: parseInt(toByteBN.toString())
59 | }
60 | }
61 |
62 |
63 | export function getMakingAmountForRFQ(amount: string): string {
64 | return setN(BigInt(amount), 255, true).toString();
65 | }
66 |
67 | export function setN(value: bigint, bitNumber: number | bigint, flag: boolean): bigint {
68 | const bit = flag ? 1 : 0;
69 | return value | (BigInt(bit) << BigInt(bitNumber));
70 | }
71 |
72 | export function getN(value: bigint, n: bigint): bigint {
73 | return (value >> BigInt(n)) & BigInt(1);
74 | }
75 |
76 | export const TIMESTAMP_AND_NOUNCE_SELECTOR = '2cc2878d'; // timestampBelowAndNonceEquals(uint256)
77 | export const ARBITRARY_STATIC_CALL_SELECTOR = '7638f1fe'; // timestampBelowAndNonceEquals(uint256)
78 | const TIMESTAMP_AND_NOUNCE_ARGS_SIZE = 256 / 4;
79 | const PREDICATE_REGEX = new RegExp(`^\\w*${TIMESTAMP_AND_NOUNCE_SELECTOR}`, 'g');
80 |
81 | /**
82 | *
83 | * @param calldata Any variant of calldata, such as
84 | * - complete predicate
85 | * - full method calldata
86 | * - arguments calldata
87 | * - argument value as hex or bigint
88 | * @param isSeriesNonceManager Omit if you dont know exacly.
89 | * Loose `arbitraryStaticCall` check will be performed
90 | * @returns
91 | */
92 | // eslint-disable-next-line max-lines-per-function
93 | export function unpackTimestampAndNoncePredicate(
94 | calldata: string | bigint,
95 | isSeriesNonceManager: boolean | null = null,
96 | ): {
97 | series?: Series,
98 | address: string,
99 | nonce: bigint,
100 | timestamp: bigint,
101 | } {
102 | const hex = trim0x(
103 | typeof calldata === 'string'
104 | ? calldata
105 | : BigInt(calldata).toString(16)
106 | );
107 | const timeNonceSeriesAccount = hex.length <= TIMESTAMP_AND_NOUNCE_ARGS_SIZE
108 | ? hex
109 | : hex.replace(
110 | PREDICATE_REGEX,
111 | '',
112 | ).substring(0, TIMESTAMP_AND_NOUNCE_ARGS_SIZE);
113 |
114 | const timeNonceAccount = BigInt(ZX + timeNonceSeriesAccount);
115 |
116 | const arbitraryStaticCallIndex = hex.indexOf(ARBITRARY_STATIC_CALL_SELECTOR);
117 | if (
118 | isSeriesNonceManager
119 | || (
120 | isSeriesNonceManager !== null
121 | && arbitraryStaticCallIndex < hex.indexOf(TIMESTAMP_AND_NOUNCE_SELECTOR)
122 | )
123 | ) {
124 | return {
125 | address: ZX + (timeNonceAccount >> BigInt(0) & ADDRESS_MASK).toString(16),
126 | series: timeNonceAccount >> BigInt(160) & UINT16_BITMASK,
127 | nonce: timeNonceAccount >> BigInt(160 + 16) & UINT40_BITMASK,
128 | timestamp: timeNonceAccount >> BigInt(160 + 16 + 40) & UINT40_BITMASK,
129 | }
130 | }
131 |
132 | return {
133 | address: ZX + (timeNonceAccount >> BigInt(0) & ADDRESS_MASK).toString(16),
134 | nonce: timeNonceAccount >> BigInt(160) & UINT48_BITMASK,
135 | timestamp: timeNonceAccount >> BigInt(208) & UINT48_BITMASK,
136 | }
137 | }
138 |
139 | function setBit(num: bigint, bitPosition: number, bitValue: boolean): bigint {
140 | if (bitValue) {
141 | return BigInt(num) | (BigInt(1) << BigInt(bitPosition));
142 | } else {
143 | return BigInt(num) & (~(BigInt(1) << BigInt(bitPosition)));
144 | }
145 | }
146 |
147 | export function packSkipPermitAndThresholdAmount(
148 | thresholdAmount: string,
149 | skipPermit: boolean,
150 | ): string {
151 | const thresholdBigInt = BigInt(thresholdAmount);
152 | const skipPermitAndThresholdAmount = setBit(thresholdBigInt, 255, skipPermit)
153 | return '0x' + skipPermitAndThresholdAmount.toString(16);
154 | }
155 |
156 | export function extractWeb3OriginalErrorData(error: ErrorResponse | Error | string): string | null {
157 | if (error && typeof error !== 'string' && (error as ErrorResponse).data) {
158 | return (error as ErrorResponse).data;
159 | }
160 |
161 | const message = (error && typeof error !== 'string')
162 | ? error.message
163 | : error as string;
164 |
165 | const bracesIndexStart = message.indexOf('{');
166 | const bracesIndexEnd = message.lastIndexOf('}');
167 |
168 | if ((bracesIndexStart + 1) && (bracesIndexEnd + 1)) {
169 | try {
170 | const json = JSON.parse(message.substring(bracesIndexStart, bracesIndexEnd + 1));
171 |
172 | if (json.originalError) {
173 | return json.originalError.data;
174 | } else if (json.data) {
175 | return json.data;
176 | }
177 |
178 | return null;
179 | } catch (e) {
180 | return null;
181 | }
182 | }
183 |
184 | if (message.startsWith(ZX)) {
185 | return message;
186 | }
187 |
188 | return null;
189 | }
190 |
191 | export function compactSignature(signature: string): { r: string, vs: string } {
192 | const sig = Signature.from(signature);
193 | return {
194 | r: sig.r,
195 | vs: sig.yParityAndS,
196 | };
197 | }
198 |
--------------------------------------------------------------------------------
/src/utils/maker-traits.const.ts:
--------------------------------------------------------------------------------
1 | export const _NO_PARTIAL_FILLS_FLAG = BigInt(255);
2 | export const _ALLOW_MULTIPLE_FILLS_FLAG = BigInt(254);
3 | export const _NO_PRICE_IMPROVEMENT_FLAG = BigInt(253);
4 | export const _NEED_PREINTERACTION_FLAG = BigInt(252);
5 | export const _NEED_POSTINTERACTION_FLAG = BigInt(251);
6 | export const _NEED_EPOCH_CHECK_FLAG = BigInt(250);
7 | export const _HAS_EXTENSION_FLAG = BigInt(249);
8 | export const _USE_PERMIT2_FLAG = BigInt(248);
9 | export const _UNWRAP_WETH_FLAG = BigInt(247);
10 | export const SERIES_SHIFT = BigInt(160);
11 |
12 | export const SERIES_MASK = (BigInt(1) << BigInt(40)) - BigInt(1);
13 | export const NONCE_SHIFT = BigInt(120);
14 | export const NONCE_MASK = (BigInt(1) << BigInt(40)) - BigInt(1);
15 | export const EXPIRY_SHIFT = BigInt(80);
16 | export const EXPIRY_MASK = (BigInt(1) << BigInt(40)) - BigInt(1);
17 |
18 | export const ALLOWED_SENDER_MASK = (BigInt(1) << BigInt(80)) - BigInt(1);
19 |
--------------------------------------------------------------------------------
/src/utils/rpc-url.const.ts:
--------------------------------------------------------------------------------
1 | import {ChainId} from "../limit-order-protocol-addresses.const";
2 |
3 | export const rpcUrls: {[key in ChainId]: string} = {
4 | [ChainId.ethereumMainnet]: 'https://web3-node.1inch.io',
5 | [ChainId.binanceMainnet]: 'https://bsc-dataseed.binance.org',
6 | [ChainId.polygonMainnet]: 'https://bor-nodes.1inch.io',
7 | [ChainId.optimismMainnet]: 'https://optimism-nodes.1inch.io',
8 | [ChainId.arbitrumMainnet]: 'https://arbitrum-nodes.1inch.io',
9 | [ChainId.gnosisMainnet]: 'https://gnosis-nodes.1inch.io',
10 | [ChainId.avalancheMainnet]: 'https://avalanche-nodes.1inch.io',
11 | [ChainId.fantomMainnet]: 'https://fantom-nodes.1inch.io',
12 | [ChainId.auroraMainnet]: 'https://aurora-nodes.1inch.io',
13 | [ChainId.klaytnMainnet]: 'https://klaytn-nodes.1inch.io',
14 | [ChainId.zkSyncEraMainnet]: 'https://mainnet.era.zksync.io',
15 | [ChainId.baseMainnet]: 'https://mainnet.base.org',
16 | };
17 |
--------------------------------------------------------------------------------
/src/utils/series-nonce-manager.utils.ts:
--------------------------------------------------------------------------------
1 | import { NonceSeriesV2, Series } from "../model/series-nonce-manager.model";
2 |
3 | export function assertSeries(series: Series): void {
4 | if (series === NonceSeriesV2._GaslessV3) {
5 | throw new TypeError(
6 | 'SeriesNonceManagerPredicateBuilder is unnecessary for Gasless. '
7 | + 'Use LimitOrderPredicateBuilder.',
8 | );
9 | }
10 |
11 | if (series < 0) {
12 | throw new RangeError('Series should be valid value and greater than zero. Check the docs.');
13 | }
14 | }
--------------------------------------------------------------------------------
/tsconfig.hardhat.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "include": ["src/**/*"],
4 | "exclude": ["src/**/*.test.ts", "test/*", "src/e2e-tests/*"],
5 | "compilerOptions": {
6 | "baseUrl": "./",
7 | "module": "commonjs",
8 | "target": "es2015",
9 | "outDir": "./dist",
10 | "sourceMap": true,
11 | "declaration": true,
12 | "moduleResolution": "node",
13 | "esModuleInterop": true,
14 | "experimentalDecorators": true,
15 | "allowSyntheticDefaultImports": true,
16 | "importHelpers": true,
17 | "strict": true,
18 | "noFallthroughCasesInSwitch": true,
19 | "noUnusedLocals": true,
20 | "noImplicitReturns": true,
21 | "noUnusedParameters": true,
22 | "resolveJsonModule": true,
23 | "strictNullChecks": true,
24 | "typeRoots": ["node_modules/@types"],
25 | "lib": ["es2018", "dom"]
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "include": ["src/**/*"],
4 | "exclude": ["src/**/*.test.ts", "test/*", "src/e2e-tests/*"],
5 | "compilerOptions": {
6 | "baseUrl": "./",
7 | "module": "es2020",
8 | "target": "es2015",
9 | "outDir": "./dist",
10 | "sourceMap": true,
11 | "declaration": true,
12 | "moduleResolution": "node",
13 | "esModuleInterop": true,
14 | "experimentalDecorators": true,
15 | "allowSyntheticDefaultImports": true,
16 | "importHelpers": true,
17 | "strict": true,
18 | "noFallthroughCasesInSwitch": true,
19 | "noUnusedLocals": true,
20 | "noImplicitReturns": true,
21 | "noUnusedParameters": true,
22 | "resolveJsonModule": true,
23 | "strictNullChecks": true,
24 | "typeRoots": ["node_modules/@types"],
25 | "lib": ["es2018", "dom"]
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tsconfig.scripts.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "experimentalDecorators": true,
4 | "moduleResolution": "node",
5 | "importHelpers": true,
6 | "module": "commonjs",
7 | "target": "ES2017",
8 | "resolveJsonModule": true,
9 | "esModuleInterop": true,
10 | "allowSyntheticDefaultImports": true,
11 | "lib": ["es2018", "dom", "es2019", "esnext"],
12 | "types": ["node"]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------