From 4f52795f78561e593faaa89d6bd39a85d9d7d05b Mon Sep 17 00:00:00 2001 From: Hayden Fowler Date: Tue, 24 Sep 2024 16:08:20 +1000 Subject: [PATCH 1/4] fix: ID-2517 Fixed session activity call not firing with device flow (#2218) --- .../sdk/src/zkEvm/zkEvmProvider.test.ts | 110 ++++++++++++++++-- .../passport/sdk/src/zkEvm/zkEvmProvider.ts | 20 +++- 2 files changed, 117 insertions(+), 13 deletions(-) diff --git a/packages/passport/sdk/src/zkEvm/zkEvmProvider.test.ts b/packages/passport/sdk/src/zkEvm/zkEvmProvider.test.ts index 58edb9f9e2..7a930291b1 100644 --- a/packages/passport/sdk/src/zkEvm/zkEvmProvider.test.ts +++ b/packages/passport/sdk/src/zkEvm/zkEvmProvider.test.ts @@ -10,7 +10,7 @@ import { RelayerClient } from './relayerClient'; import { Provider, RequestArguments } from './types'; import { PassportEventMap, PassportEvents } from '../types'; import TypedEventEmitter from '../utils/typedEventEmitter'; -import { mockUserZkEvm, testConfig } from '../test/mocks'; +import { mockUser, mockUserZkEvm, testConfig } from '../test/mocks'; import { signTypedDataV4 } from './signTypedDataV4'; import MagicAdapter from '../magicAdapter'; @@ -62,13 +62,91 @@ describe('ZkEvmProvider', () => { return new ZkEvmProvider(constructorParameters as ZkEvmProviderInput); }; - describe('eth_requestAccounts', () => { - it('constructor tries to automatically connect existing user session when provider is instantiated', async () => { - authManager.getUser.mockReturnValue(Promise.resolve(mockUserZkEvm)); - getProvider(); - expect(authManager.getUser).toHaveBeenCalledTimes(1); + describe('constructor', () => { + describe('when an application session exists', () => { + it('initialises the signer', async () => { + authManager.getUser.mockResolvedValue(mockUserZkEvm); + getProvider(); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(authManager.getUser).toBeCalledTimes(1); + expect(magicAdapter.login).toBeCalledTimes(1); + expect(Web3Provider).toBeCalledTimes(1); + }); + + describe('and the user has not registered before', () => { + it('does not call session activity', async () => { + const onAccountsRequested = jest.fn(); + passportEventEmitter.on(PassportEvents.ACCOUNTS_REQUESTED, onAccountsRequested); + authManager.getUser.mockResolvedValue(mockUser); + getProvider(); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(authManager.getUser).toBeCalledTimes(1); + expect(onAccountsRequested).not.toHaveBeenCalled(); + }); + }); + describe('and the user has registered before', () => { + it('calls session activity', async () => { + const onAccountsRequested = jest.fn(); + passportEventEmitter.on(PassportEvents.ACCOUNTS_REQUESTED, onAccountsRequested); + authManager.getUser.mockResolvedValue(mockUserZkEvm); + getProvider(); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(authManager.getUser).toBeCalledTimes(1); + expect(onAccountsRequested).toHaveBeenCalledTimes(1); + }); + }); + }); + + describe('when a login occurs outside of the zkEvm provider', () => { + beforeEach(() => { + authManager.getUser.mockResolvedValue(null); + }); + + it('initialises the signer', async () => { + getProvider(); + passportEventEmitter.emit(PassportEvents.LOGGED_IN, mockUserZkEvm); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(magicAdapter.login).toBeCalledTimes(1); + expect(Web3Provider).toBeCalledTimes(1); + }); + + describe('and the user has not registered before', () => { + it('does not call session activity', async () => { + const onAccountsRequested = jest.fn(); + passportEventEmitter.on(PassportEvents.ACCOUNTS_REQUESTED, onAccountsRequested); + getProvider(); + passportEventEmitter.emit(PassportEvents.LOGGED_IN, mockUser); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(onAccountsRequested).not.toHaveBeenCalled(); + }); + + describe('and the user has registered before', () => { + it('calls session activity', async () => { + const onAccountsRequested = jest.fn(); + passportEventEmitter.on(PassportEvents.ACCOUNTS_REQUESTED, onAccountsRequested); + getProvider(); + passportEventEmitter.emit(PassportEvents.LOGGED_IN, mockUserZkEvm); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(onAccountsRequested).toHaveBeenCalledTimes(1); + }); + }); + }); }); + }); + describe('eth_requestAccounts', () => { it('should return the ethAddress if already logged in', async () => { authManager.getUser.mockReturnValue(Promise.resolve(mockUserZkEvm)); const provider = getProvider(); @@ -103,7 +181,7 @@ describe('ZkEvmProvider', () => { it('should throw an error if the signer initialisation fails', async () => { authManager.getUserOrLogin.mockReturnValue(mockUserZkEvm); - authManager.getUser.mockReturnValue(Promise.resolve(mockUserZkEvm)); + authManager.getUser.mockResolvedValue(mockUserZkEvm); (Web3Provider as unknown as jest.Mock).mockImplementation(() => ({ getSigner: () => { @@ -117,6 +195,24 @@ describe('ZkEvmProvider', () => { new JsonRpcError(RpcErrorCode.INTERNAL_ERROR, 'Something went wrong'), ); }); + + it('should not reinitialise the ethSigner when it has been set during the constructor', async () => { + authManager.getUser.mockResolvedValue(mockUserZkEvm); + const provider = getProvider(); + + await new Promise(process.nextTick); // https://immutable.atlassian.net/browse/ID-2516 + + expect(magicAdapter.login).toBeCalledTimes(1); + expect(Web3Provider).toBeCalledTimes(1); + + await provider.request({ method: 'eth_requestAccounts' }); + + // Add a delay so that we can check if the ethSigner is initialised again + await new Promise(process.nextTick); + + expect(magicAdapter.login).toBeCalledTimes(1); + expect(Web3Provider).toBeCalledTimes(1); + }); }); describe('eth_sendTransaction', () => { diff --git a/packages/passport/sdk/src/zkEvm/zkEvmProvider.ts b/packages/passport/sdk/src/zkEvm/zkEvmProvider.ts index f4c9f760cf..d7469824e3 100644 --- a/packages/passport/sdk/src/zkEvm/zkEvmProvider.ts +++ b/packages/passport/sdk/src/zkEvm/zkEvmProvider.ts @@ -116,14 +116,20 @@ export class ZkEvmProvider implements Provider { // Automatically connect an existing user session to Passport this.#authManager.getUser().then((user) => { - if (user && isZkEvmUser(user)) { + if (user) { this.#initialiseEthSigner(user); + if (isZkEvmUser(user)) { + this.#callSessionActivity(user.zkEvm.ethAddress); + } } - }).catch(() => { - // User does not exist, don't initialise an eth signer - }); + }).catch(); // User does not exist, don't initialise an eth signer - passportEventEmitter.on(PassportEvents.LOGGED_IN, (user: User) => this.#initialiseEthSigner(user)); + passportEventEmitter.on(PassportEvents.LOGGED_IN, (user: User) => { + this.#initialiseEthSigner(user); + if (isZkEvmUser(user)) { + this.#callSessionActivity(user.zkEvm.ethAddress); + } + }); passportEventEmitter.on(PassportEvents.LOGGED_OUT, this.#handleLogout); passportEventEmitter.on( PassportEvents.ACCOUNTS_REQUESTED, @@ -231,7 +237,9 @@ export class ZkEvmProvider implements Provider { const user = await this.#authManager.getUserOrLogin(); flow.addEvent('endGetUserOrLogin'); - this.#initialiseEthSigner(user); + if (!this.#ethSigner) { + this.#initialiseEthSigner(user); + } let userZkEvmEthAddress; From 8b1ceba6ef590bec95494d047625e720bb4446b7 Mon Sep 17 00:00:00 2001 From: zaidarain1 Date: Tue, 24 Sep 2024 17:48:07 +1000 Subject: [PATCH 2/4] chore: [DX-3280] Fix missing dependencies, flaky configs, tests and update NX parallel count (#2220) --- .github/workflows/pr.yaml | 2 +- build-dependents.js | 2 +- dev.sh | 2 +- examples/README.md | 4 +- .../_deprecated/next-connect-kit/package.json | 2 +- .../_deprecated/next-rainbow-kit/package.json | 2 +- examples/_deprecated/next-wagmi/package.json | 2 +- .../_deprecated/next-web3-modal/package.json | 2 +- .../_deprecated/vite-connect-kit/package.json | 2 +- .../_deprecated/vite-rainbow-kit/package.json | 2 +- examples/_deprecated/vite-wagmi/package.json | 2 +- .../_deprecated/vite-web3-modal/package.json | 2 +- .../create-listing-with-nextjs/package.json | 1 + .../identity-with-nextjs/package.json | 3 +- .../package.json | 3 +- .../primary-sales-backend-api/package.json | 2 +- nx.json | 2 +- package.json | 6 +- packages/blockchain-data/sdk/jest.config.ts | 1 + packages/blockchain-data/sdk/package.json | 2 +- .../checkout/sdk-sample-app/jest.config.ts | 1 + packages/checkout/sdk-sample-app/package.json | 3 + packages/checkout/sdk/jest.config.ts | 1 + packages/checkout/sdk/package.json | 4 +- packages/checkout/widgets-lib/jest.config.ts | 1 + packages/checkout/widgets-lib/package.json | 4 ++ .../checkout/widgets-sample-app/package.json | 1 + packages/config/jest.config.ts | 1 + packages/config/package.json | 2 +- .../bridge/bridge-sample-app/package.json | 2 +- packages/internal/bridge/sdk/jest.config.ts | 1 + packages/internal/bridge/sdk/package.json | 2 +- packages/internal/cryptofiat/jest.config.ts | 1 + packages/internal/cryptofiat/package.json | 2 +- .../internal/dex/sdk-sample-app/package.json | 2 +- packages/internal/dex/sdk/jest.config.ts | 1 + packages/internal/dex/sdk/package.json | 8 ++- .../internal/generated-clients/jest.config.ts | 1 + .../internal/generated-clients/package.json | 7 +- packages/internal/metrics/jest.config.ts | 1 + packages/internal/metrics/package.json | 3 +- packages/internal/toolkit/jest.config.ts | 1 + packages/internal/toolkit/package.json | 3 +- packages/minting-backend/sdk/jest.config.ts | 1 + packages/minting-backend/sdk/package.json | 6 +- packages/orderbook/jest.config.ts | 1 + packages/orderbook/package.json | 5 +- packages/passport/sdk-sample-app/package.json | 1 + packages/passport/sdk/jest.config.ts | 1 + packages/passport/sdk/package.json | 5 +- packages/webhook/sdk/jest.config.ts | 1 + packages/webhook/sdk/package.json | 2 +- packages/x-client/jest.config.cjs | 2 +- packages/x-client/package.json | 7 +- packages/x-provider/jest.config.ts | 1 + packages/x-provider/package.json | 7 +- .../x-provider/src/sample-app/package.json | 1 + .../completeERC20Withdrawal.test.ts | 1 + .../completeEthWithdrawal.test.ts | 1 + sdk/package.json | 11 ++++ tests/func-tests/imx/jest.config.ts | 1 + tests/func-tests/imx/package.json | 3 - tests/func-tests/zkevm/jest.config.ts | 2 + tests/func-tests/zkevm/tsconfig.json | 4 +- yarn.lock | 66 ++++++++++++++++++- 65 files changed, 182 insertions(+), 47 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index f2076f2832..2d60e61a1b 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -67,7 +67,7 @@ jobs: - name: setup uses: ./.github/actions/setup - name: Build - run: yarn nx affected --target=build --parallel=5 + run: yarn nx affected --target=build typecheck-sdk: name: Typecheck SDK diff --git a/build-dependents.js b/build-dependents.js index d0cf7cade7..9a0b3129a2 100755 --- a/build-dependents.js +++ b/build-dependents.js @@ -23,7 +23,7 @@ try { if (isDependent || changedProject === currentProject) { // Rebuild the current project - const command = `nx run-many --target=d --projects=${currentProject} --parallel=5 --no-cloud`; + const command = `nx run-many --target=d --projects=${currentProject} --no-cloud`; console.log(`Running command: ${command}`); execSync(command, { stdio: 'inherit' }); diff --git a/dev.sh b/dev.sh index 19f7726e37..f6b2fd756b 100755 --- a/dev.sh +++ b/dev.sh @@ -28,5 +28,5 @@ fi # Run nx commands with the selected or provided package name echo "Running commands for package: $PACKAGE_NAME" -nx run $PACKAGE_NAME:d --parallel=5 --no-cloud +nx run $PACKAGE_NAME:d --no-cloud nx watch --all -- node ./build-dependents.js \$NX_PROJECT_NAME $(echo $PACKAGE_NAME) \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 58dabdeace..17ecafd5b9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -262,7 +262,7 @@ export default defineConfig({ fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, + workers: "80%", reporter: "html", use: { @@ -280,7 +280,7 @@ export default defineConfig({ ], webServer: { - command: "yarn dev", + command: "yarn start", url: "http://localhost:3000", reuseExistingServer: !process.env.CI, }, diff --git a/examples/_deprecated/next-connect-kit/package.json b/examples/_deprecated/next-connect-kit/package.json index 533badd3f9..f893197ad7 100644 --- a/examples/_deprecated/next-connect-kit/package.json +++ b/examples/_deprecated/next-connect-kit/package.json @@ -17,7 +17,7 @@ "@types/react-dom": "^18.0.11", "eslint": "^8", "eslint-config-next": "14.2.3", - "typescript": "^5.5.4" + "typescript": "^5.6.2" }, "private": true, "scripts": { diff --git a/examples/_deprecated/next-rainbow-kit/package.json b/examples/_deprecated/next-rainbow-kit/package.json index f3c65e4fa1..5aa58008cd 100644 --- a/examples/_deprecated/next-rainbow-kit/package.json +++ b/examples/_deprecated/next-rainbow-kit/package.json @@ -17,7 +17,7 @@ "@types/react-dom": "^18.0.11", "eslint": "^8", "eslint-config-next": "14.2.3", - "typescript": "^5.5.4" + "typescript": "^5.6.2" }, "private": true, "scripts": { diff --git a/examples/_deprecated/next-wagmi/package.json b/examples/_deprecated/next-wagmi/package.json index 9b911c5dd5..9defc88fb9 100644 --- a/examples/_deprecated/next-wagmi/package.json +++ b/examples/_deprecated/next-wagmi/package.json @@ -16,7 +16,7 @@ "@types/react-dom": "^18.0.11", "eslint": "^8", "eslint-config-next": "14.2.3", - "typescript": "^5.5.4" + "typescript": "^5.6.2" }, "private": true, "scripts": { diff --git a/examples/_deprecated/next-web3-modal/package.json b/examples/_deprecated/next-web3-modal/package.json index 926dc8e4be..4592885ec1 100644 --- a/examples/_deprecated/next-web3-modal/package.json +++ b/examples/_deprecated/next-web3-modal/package.json @@ -17,7 +17,7 @@ "@types/react-dom": "^18.0.11", "eslint": "^8", "eslint-config-next": "14.2.3", - "typescript": "^5.5.4" + "typescript": "^5.6.2" }, "private": true, "scripts": { diff --git a/examples/_deprecated/vite-connect-kit/package.json b/examples/_deprecated/vite-connect-kit/package.json index 20aa93cde2..73b2980253 100644 --- a/examples/_deprecated/vite-connect-kit/package.json +++ b/examples/_deprecated/vite-connect-kit/package.json @@ -16,7 +16,7 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^4.3.1", - "typescript": "^5.5.4", + "typescript": "^5.6.2", "vite": "^5.2.14", "vite-plugin-node-polyfills": "^0.22.0" }, diff --git a/examples/_deprecated/vite-rainbow-kit/package.json b/examples/_deprecated/vite-rainbow-kit/package.json index 14a7a78c48..a7d64acbd5 100644 --- a/examples/_deprecated/vite-rainbow-kit/package.json +++ b/examples/_deprecated/vite-rainbow-kit/package.json @@ -16,7 +16,7 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^4.3.1", - "typescript": "^5.5.4", + "typescript": "^5.6.2", "vite": "^5.2.14", "vite-plugin-node-polyfills": "^0.22.0" }, diff --git a/examples/_deprecated/vite-wagmi/package.json b/examples/_deprecated/vite-wagmi/package.json index 3953dddd20..2d4f5e8629 100644 --- a/examples/_deprecated/vite-wagmi/package.json +++ b/examples/_deprecated/vite-wagmi/package.json @@ -15,7 +15,7 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^4.3.1", - "typescript": "^5.5.4", + "typescript": "^5.6.2", "vite": "^5.2.14", "vite-plugin-node-polyfills": "^0.22.0" }, diff --git a/examples/_deprecated/vite-web3-modal/package.json b/examples/_deprecated/vite-web3-modal/package.json index e6542fc8b6..dad9bdcb23 100644 --- a/examples/_deprecated/vite-web3-modal/package.json +++ b/examples/_deprecated/vite-web3-modal/package.json @@ -16,7 +16,7 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^4.3.1", - "typescript": "^5.5.4", + "typescript": "^5.6.2", "vite": "^5.2.14", "vite-plugin-node-polyfills": "^0.22.0" }, diff --git a/examples/orderbook/create-listing-with-nextjs/package.json b/examples/orderbook/create-listing-with-nextjs/package.json index 3c875940b5..b8f8aa6f98 100644 --- a/examples/orderbook/create-listing-with-nextjs/package.json +++ b/examples/orderbook/create-listing-with-nextjs/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@biom3/react": "^0.26.1", + "@ethersproject/providers": "^5.7.2", "@imtbl/sdk": "latest", "ethers": "^5.7.2", "next": "14.2.7", diff --git a/examples/passport/identity-with-nextjs/package.json b/examples/passport/identity-with-nextjs/package.json index e72359d4c5..7ccf1589b1 100644 --- a/examples/passport/identity-with-nextjs/package.json +++ b/examples/passport/identity-with-nextjs/package.json @@ -2,12 +2,13 @@ "name": "@examples/identity-with-nextjs", "version": "0.1.0", "dependencies": { + "@imtbl/sdk": "latest", + "ethers": "^5.7.2", "next": "14.2.5", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { - "@imtbl/sdk": "latest", "@playwright/test": "^1.45.3", "@types/node": "^20", "@types/react": "^18.0.28", diff --git a/examples/passport/wallets-transactions-with-nextjs/package.json b/examples/passport/wallets-transactions-with-nextjs/package.json index 0b41edbe1a..897eddd754 100644 --- a/examples/passport/wallets-transactions-with-nextjs/package.json +++ b/examples/passport/wallets-transactions-with-nextjs/package.json @@ -2,12 +2,13 @@ "name": "@examples/wallets-transactions-with-nextjs", "version": "0.1.0", "dependencies": { + "@imtbl/sdk": "latest", + "ethers": "^5.7.2", "next": "14.2.10", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { - "@imtbl/sdk": "latest", "@playwright/test": "^1.45.3", "@types/node": "^20", "@types/react": "^18.0.28", diff --git a/examples/primary-sales-backend-api/package.json b/examples/primary-sales-backend-api/package.json index 459bf355ea..acf1b0ff1c 100644 --- a/examples/primary-sales-backend-api/package.json +++ b/examples/primary-sales-backend-api/package.json @@ -5,7 +5,7 @@ "nodemon": "^3.1.4", "prisma": "^5.18.0", "ts-node": "^10.9.2", - "typescript": "^5.5.4" + "typescript": "^5.6.2" }, "dependencies": { "@fastify/autoload": "^5.10.0", diff --git a/nx.json b/nx.json index 0de4e46318..ec3bc4527e 100644 --- a/nx.json +++ b/nx.json @@ -37,6 +37,6 @@ } }, "defaultBase": "main", - "parallel": 5, + "parallel": 8, "nxCloudAccessToken": "Mzg3ZGY1MWUtYmYyNy00ZmE4LTkyNDAtYjYxZmJmYmE4NWQ3fHJlYWQ=" } diff --git a/package.json b/package.json index bd0e9550bf..688807c1b5 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "dev": "./dev.sh", "docs:build": "typedoc", "docs:serve": "http-server ./docs --cors -p 8080 -c-1", - "lint": "nx affected -t lint --parallel=5 --no-error-on-unmatched-pattern", + "lint": "nx affected -t lint", "lint:examples": "yarn workspaces foreach -Apt --include='@examples/**' run lint", "nx": "nx", "postinstall": "husky install", @@ -62,10 +62,10 @@ "syncpack:check": "yarn syncpack list-mismatches", "syncpack:fix": "yarn syncpack fix-mismatches", "syncpack:format": "yarn syncpack format", - "test": "nx affected -t test --parallel=5", + "test": "nx affected -t test", "test:examples": "yarn workspaces foreach -At --include='@examples/**' run test", "test:vpn": "RUN_VPN_TESTS=1 wsrun --exclude-missing -e test", - "typecheck": "nx affected -t typecheck --parallel=5", + "typecheck": "nx affected -t typecheck", "yalc:sdk:publish": "yarn workspace @imtbl/sdk exec yarn dlx yalc publish" }, "workspaces": { diff --git a/packages/blockchain-data/sdk/jest.config.ts b/packages/blockchain-data/sdk/jest.config.ts index 05ef77a17a..7be21beeca 100644 --- a/packages/blockchain-data/sdk/jest.config.ts +++ b/packages/blockchain-data/sdk/jest.config.ts @@ -10,6 +10,7 @@ const config: Config = { '^.+\\.(t|j)sx?$': '@swc/jest', }, transformIgnorePatterns: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/blockchain-data/sdk/package.json b/packages/blockchain-data/sdk/package.json index 7f94aca11d..0694f961f1 100644 --- a/packages/blockchain-data/sdk/package.json +++ b/packages/blockchain-data/sdk/package.json @@ -53,7 +53,7 @@ "test": "jest", "test:e2e": "jest --runInBand --testMatch \"**/?(*.)+(e2e).[jt]s?(x)\"", "test:watch": "jest --watch", - "typecheck": "tsc --noEmit --jsx preserve" + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" }, "type": "module" } diff --git a/packages/checkout/sdk-sample-app/jest.config.ts b/packages/checkout/sdk-sample-app/jest.config.ts index ca731690af..fb77dcd583 100644 --- a/packages/checkout/sdk-sample-app/jest.config.ts +++ b/packages/checkout/sdk-sample-app/jest.config.ts @@ -3,6 +3,7 @@ import type { Config } from 'jest'; const config: Config = { verbose: true, testEnvironment: 'jsdom', + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/checkout/sdk-sample-app/package.json b/packages/checkout/sdk-sample-app/package.json index a0525b6328..0401b459d7 100644 --- a/packages/checkout/sdk-sample-app/package.json +++ b/packages/checkout/sdk-sample-app/package.json @@ -14,7 +14,9 @@ ] }, "dependencies": { + "@biom3/design-tokens": "^0.4.2", "@biom3/react": "^0.25.0", + "@ethersproject/providers": "^5.7.2", "@imtbl/checkout-sdk": "0.0.0", "@imtbl/checkout-widgets": "0.0.0", "@imtbl/config": "0.0.0", @@ -42,6 +44,7 @@ "crypto-browserify": "^3.12.0", "jest": "^29.4.3", "jest-environment-jsdom": "^29.4.3", + "process": "^0.11.10", "react-app-rewired": "^2.2.1", "react-scripts": "5.0.1", "stream-browserify": "^3.0.0", diff --git a/packages/checkout/sdk/jest.config.ts b/packages/checkout/sdk/jest.config.ts index 8201335503..62f6426b0c 100644 --- a/packages/checkout/sdk/jest.config.ts +++ b/packages/checkout/sdk/jest.config.ts @@ -11,6 +11,7 @@ const config: Config = { }, transformIgnorePatterns: [], setupFiles: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/checkout/sdk/package.json b/packages/checkout/sdk/package.json index 59b5615083..560e2af3bd 100644 --- a/packages/checkout/sdk/package.json +++ b/packages/checkout/sdk/package.json @@ -31,10 +31,12 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", "@swc/core": "^1.3.36", + "@types/jest": "^29.4.3", "@types/uuid": "^8.3.4", "babel-jest": "^29.5.0", "eslint": "^8.40.0", "jest": "^29.4.3", + "jest-environment-jsdom": "^29.4.3", "parcel": "^2.8.3", "rollup": "^4.19.1", "rollup-plugin-dts": "^6.1.1", @@ -79,7 +81,7 @@ "start:local": "CHECKOUT_LOCAL_MODE=true yarn start", "test": "jest test", "test:watch": "jest test --watch", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --customConditions default --noEmit" }, "source": "src/index.ts", "type": "module" diff --git a/packages/checkout/widgets-lib/jest.config.ts b/packages/checkout/widgets-lib/jest.config.ts index 6df7a4897f..71fd9b541b 100644 --- a/packages/checkout/widgets-lib/jest.config.ts +++ b/packages/checkout/widgets-lib/jest.config.ts @@ -13,6 +13,7 @@ const config: Config = { transformIgnorePatterns: [ "node_modules/(?!axios|@biom3/design-tokens)", ], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/checkout/widgets-lib/package.json b/packages/checkout/widgets-lib/package.json index 90f378a61e..c49e316cfe 100644 --- a/packages/checkout/widgets-lib/package.json +++ b/packages/checkout/widgets-lib/package.json @@ -17,6 +17,7 @@ "@0xsquid/sdk": "^2.8.24", "@biom3/design-tokens": "^0.4.2", "@biom3/react": "^0.25.0", + "@ethersproject/bignumber": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@imtbl/bridge-sdk": "0.0.0", "@imtbl/checkout-sdk": "0.0.0", @@ -29,9 +30,11 @@ "@walletconnect/ethereum-provider": "^2.11.1", "@walletconnect/modal": "^2.6.2", "assert": "^2.0.0", + "axios": "^1.6.5", "buffer": "^6.0.3", "crypto-browserify": "^3.12.0", "ethers": "^5.7.2", + "framer-motion": "^11.0.6", "https-browserify": "^1.0.0", "i18next": "^23.7.6", "i18next-browser-languagedetector": "^7.2.0", @@ -45,6 +48,7 @@ }, "devDependencies": { "@0xsquid/squid-types": "^0.1.104", + "@jest/globals": "^29.5.0", "@rollup/plugin-commonjs": "^26.0.1", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", diff --git a/packages/checkout/widgets-sample-app/package.json b/packages/checkout/widgets-sample-app/package.json index c8db3749de..c191e7a959 100644 --- a/packages/checkout/widgets-sample-app/package.json +++ b/packages/checkout/widgets-sample-app/package.json @@ -16,6 +16,7 @@ "dependencies": { "@biom3/design-tokens": "^0.4.2", "@biom3/react": "^0.25.0", + "@ethersproject/providers": "^5.7.2", "@imtbl/checkout-sdk": "0.0.0", "@imtbl/checkout-widgets": "0.0.0", "@imtbl/config": "0.0.0", diff --git a/packages/config/jest.config.ts b/packages/config/jest.config.ts index 5a141d28a7..5d9ee7fce3 100644 --- a/packages/config/jest.config.ts +++ b/packages/config/jest.config.ts @@ -10,6 +10,7 @@ const config: Config = { '^.+\\.(t|j)sx?$': '@swc/jest', }, transformIgnorePatterns: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/config/package.json b/packages/config/package.json index 9a8fbbb8ed..dc5256fddb 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -53,7 +53,7 @@ "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest", "test:watch": "jest --watch", - "typecheck": "tsc --noEmit --jsx preserve" + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" }, "type": "module" } diff --git a/packages/internal/bridge/bridge-sample-app/package.json b/packages/internal/bridge/bridge-sample-app/package.json index 8da52a811b..5157ce77b2 100644 --- a/packages/internal/bridge/bridge-sample-app/package.json +++ b/packages/internal/bridge/bridge-sample-app/package.json @@ -25,7 +25,7 @@ "flowRateInfo": "node --loader ts-node/esm ./src/flowRateInfo.ts", "getMapping": "node --loader ts-node/esm ./src/getMapping.ts", "issueUSDC": "node --loader ts-node/esm ./src/issueUSDC.ts", - "lint": "eslint ./src --ext .ts --max-warnings=0", + "lint": "eslint ./src --ext .ts --max-warnings=0 --no-error-on-unmatched-pattern", "mapToken": "node --loader ts-node/esm ./src/mapToken.ts", "pending": "node --loader ts-node/esm ./src/pending.ts", "setFlowRate": "node --loader ts-node/esm ./src/setFlowRate.ts", diff --git a/packages/internal/bridge/sdk/jest.config.ts b/packages/internal/bridge/sdk/jest.config.ts index b28e638f08..e1ef120106 100644 --- a/packages/internal/bridge/sdk/jest.config.ts +++ b/packages/internal/bridge/sdk/jest.config.ts @@ -11,6 +11,7 @@ const config: Config = { '^.+\\.(t|j)sx?$': '@swc/jest', }, transformIgnorePatterns: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/internal/bridge/sdk/package.json b/packages/internal/bridge/sdk/package.json index 2da635ef97..c4a92004e2 100644 --- a/packages/internal/bridge/sdk/package.json +++ b/packages/internal/bridge/sdk/package.json @@ -63,7 +63,7 @@ "lint:fix": "cd ../../../../ && yarn wsrun -p @imtbl/bridge-sdk -c lint --fix", "test": "jest test -- --silent=false", "test:watch": "jest --watch", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --customConditions default --noEmit" }, "source": "src/index.ts", "type": "module" diff --git a/packages/internal/cryptofiat/jest.config.ts b/packages/internal/cryptofiat/jest.config.ts index 7c27f4f311..cbe3783042 100644 --- a/packages/internal/cryptofiat/jest.config.ts +++ b/packages/internal/cryptofiat/jest.config.ts @@ -11,6 +11,7 @@ const config: Config = { '^.+\\.(t|j)sx?$': '@swc/jest', }, transformIgnorePatterns: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/internal/cryptofiat/package.json b/packages/internal/cryptofiat/package.json index 7beb1d5aef..8d7cdb8a59 100644 --- a/packages/internal/cryptofiat/package.json +++ b/packages/internal/cryptofiat/package.json @@ -57,7 +57,7 @@ "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0 --fix", "test": "jest", "test:watch": "jest --watch", - "typecheck": "tsc --noEmit --jsx preserve" + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" }, "type": "module" } diff --git a/packages/internal/dex/sdk-sample-app/package.json b/packages/internal/dex/sdk-sample-app/package.json index 69f25d77f8..d4103f9551 100644 --- a/packages/internal/dex/sdk-sample-app/package.json +++ b/packages/internal/dex/sdk-sample-app/package.json @@ -20,7 +20,7 @@ "private": true, "scripts": { "dev": "concurrently 'next dev' 'yarn run --top-level dev @imtbl/dex-sdk'", - "lint": "eslint ./src --ext .ts --max-warnings=0", + "lint": "eslint ./src --ext .ts --max-warnings=0 --no-error-on-unmatched-pattern", "start": "next start" } } diff --git a/packages/internal/dex/sdk/jest.config.ts b/packages/internal/dex/sdk/jest.config.ts index 25942679c5..bb754030fd 100644 --- a/packages/internal/dex/sdk/jest.config.ts +++ b/packages/internal/dex/sdk/jest.config.ts @@ -12,6 +12,7 @@ const config: Config = { }, coveragePathIgnorePatterns:['node_modules', 'src/contracts/', 'src/test/'], transformIgnorePatterns: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/internal/dex/sdk/package.json b/packages/internal/dex/sdk/package.json index 387e0516fb..570301ed4d 100644 --- a/packages/internal/dex/sdk/package.json +++ b/packages/internal/dex/sdk/package.json @@ -5,6 +5,12 @@ "author": "Immutable", "bugs": "https://github.com/immutable/ts-immutable-sdk/issues", "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/contracts": "^5.7.0", + "@ethersproject/providers": "^5.7.2", + "@ethersproject/solidity": "^5.7.0", "@imtbl/config": "0.0.0", "@uniswap/router-sdk": "^1.4.0", "@uniswap/sdk-core": "^3.0.1", @@ -62,7 +68,7 @@ "lint:fix": "cd ../../../.. && yarn wsrun -p @imtbl/dex-sdk -c lint --fix", "test": "jest test", "test:watch": "jest --watch", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --customConditions default --noEmit" }, "source": "src/index.ts", "type": "module" diff --git a/packages/internal/generated-clients/jest.config.ts b/packages/internal/generated-clients/jest.config.ts index 33b57b1652..1a38b54d73 100644 --- a/packages/internal/generated-clients/jest.config.ts +++ b/packages/internal/generated-clients/jest.config.ts @@ -9,6 +9,7 @@ const config: Config = { '^.+\\.(t|j)sx?$': '@swc/jest', }, transformIgnorePatterns: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/internal/generated-clients/package.json b/packages/internal/generated-clients/package.json index 2cf1884b7c..fca7028047 100644 --- a/packages/internal/generated-clients/package.json +++ b/packages/internal/generated-clients/package.json @@ -4,10 +4,15 @@ "version": "0.0.0", "author": "Immutable", "bugs": "https://github.com/immutable/ts-immutable-sdk/issues", + "dependencies": { + "axios": "^1.6.5" + }, "devDependencies": { "@openapitools/openapi-generator-cli": "^2.13.4", "@rollup/plugin-typescript": "^11.1.6", "@swc/core": "^1.3.36", + "@types/jest": "^29.4.3", + "@types/node": "^18.14.2", "jest": "^29.4.3", "rimraf": "^6.0.1", "rollup": "^4.19.1", @@ -41,7 +46,7 @@ "build": "NODE_ENV=production rollup --config rollup.config.js", "d": "rollup --config rollup.config.js", "test": "jest", - "typecheck": "tsc --noEmit --jsx preserve", + "typecheck": "tsc --customConditions default --noEmit --jsx preserve", "view-generators": "openapi-generator-cli author template -g typescript-axios -o src/templates" }, "type": "module" diff --git a/packages/internal/metrics/jest.config.ts b/packages/internal/metrics/jest.config.ts index 3477a5b67c..a1f80169f7 100644 --- a/packages/internal/metrics/jest.config.ts +++ b/packages/internal/metrics/jest.config.ts @@ -13,6 +13,7 @@ const config: Config = { // url: "http://localhost", // }, // verbose: true, + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/internal/metrics/package.json b/packages/internal/metrics/package.json index d420e5855c..71db9a821f 100644 --- a/packages/internal/metrics/package.json +++ b/packages/internal/metrics/package.json @@ -14,6 +14,7 @@ "@swc/core": "^1.3.36", "@swc/jest": "^0.2.24", "@types/jest": "^29.4.3", + "@types/node": "^18.14.2", "eslint": "^8.40.0", "jest": "^29.4.3", "jest-environment-jsdom": "^29.4.3", @@ -49,7 +50,7 @@ "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest", - "typecheck": "tsc --noEmit --jsx preserve" + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" }, "type": "module" } diff --git a/packages/internal/toolkit/jest.config.ts b/packages/internal/toolkit/jest.config.ts index 33b57b1652..1a38b54d73 100644 --- a/packages/internal/toolkit/jest.config.ts +++ b/packages/internal/toolkit/jest.config.ts @@ -9,6 +9,7 @@ const config: Config = { '^.+\\.(t|j)sx?$': '@swc/jest', }, transformIgnorePatterns: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/internal/toolkit/package.json b/packages/internal/toolkit/package.json index 4d7bc46a4b..1925214697 100644 --- a/packages/internal/toolkit/package.json +++ b/packages/internal/toolkit/package.json @@ -23,6 +23,7 @@ "@swc/core": "^1.3.36", "@swc/jest": "^0.2.24", "@types/axios": "^0.14.0", + "@types/bn.js": "^5.1.6", "@types/jest": "^29.4.3", "@types/node": "^18.14.2", "@types/react": "^18.0.28", @@ -67,7 +68,7 @@ "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest", "test:watch": "jest --watch", - "typecheck": "tsc --noEmit --jsx preserve" + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" }, "type": "module" } diff --git a/packages/minting-backend/sdk/jest.config.ts b/packages/minting-backend/sdk/jest.config.ts index 05ef77a17a..7be21beeca 100644 --- a/packages/minting-backend/sdk/jest.config.ts +++ b/packages/minting-backend/sdk/jest.config.ts @@ -10,6 +10,7 @@ const config: Config = { '^.+\\.(t|j)sx?$': '@swc/jest', }, transformIgnorePatterns: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/minting-backend/sdk/package.json b/packages/minting-backend/sdk/package.json index 622f7e5688..e8bbb1a1dc 100644 --- a/packages/minting-backend/sdk/package.json +++ b/packages/minting-backend/sdk/package.json @@ -9,7 +9,8 @@ "@imtbl/config": "0.0.0", "@imtbl/generated-clients": "0.0.0", "@imtbl/metrics": "0.0.0", - "@imtbl/webhook": "0.0.0" + "@imtbl/webhook": "0.0.0", + "uuid": "^8.3.2" }, "devDependencies": { "@rollup/plugin-typescript": "^11.1.6", @@ -18,6 +19,7 @@ "@testcontainers/postgresql": "^10.9.0", "@types/jest": "^29.4.3", "@types/pg": "^8.11.5", + "@types/uuid": "^8.3.4", "dotenv": "^16.0.3", "eslint": "^8.40.0", "jest": "^29.4.3", @@ -55,7 +57,7 @@ "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest --passWithNoTests", "test:watch": "jest --watch", - "typecheck": "tsc --noEmit --jsx preserve" + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" }, "type": "module" } diff --git a/packages/orderbook/jest.config.ts b/packages/orderbook/jest.config.ts index 33b57b1652..1a38b54d73 100644 --- a/packages/orderbook/jest.config.ts +++ b/packages/orderbook/jest.config.ts @@ -9,6 +9,7 @@ const config: Config = { '^.+\\.(t|j)sx?$': '@swc/jest', }, transformIgnorePatterns: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/orderbook/package.json b/packages/orderbook/package.json index f09f622f5e..534a22bb09 100644 --- a/packages/orderbook/package.json +++ b/packages/orderbook/package.json @@ -4,12 +4,15 @@ "author": "Immutable", "bugs": "https://github.com/immutable/ts-immutable-sdk/issues", "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/providers": "^5.7.2", "@imtbl/config": "0.0.0", "@imtbl/metrics": "0.0.0", "@opensea/seaport-js": "4.0.3", "axios": "^1.6.5", "ethers": "^5.7.2", "ethers-v6": "npm:ethers@6.11.1", + "form-data": "^4.0.0", "merkletreejs": "^0.3.11" }, "devDependencies": { @@ -59,7 +62,7 @@ "test": "jest", "test:e2e": "jest --runInBand --testMatch \"**/?(*.)+(e2e).[jt]s?(x)\"", "test:watch": "jest --watch", - "typecheck": "tsc --noEmit --jsx preserve" + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" }, "type": "module" } diff --git a/packages/passport/sdk-sample-app/package.json b/packages/passport/sdk-sample-app/package.json index e5acdca042..08e65c7361 100644 --- a/packages/passport/sdk-sample-app/package.json +++ b/packages/passport/sdk-sample-app/package.json @@ -15,6 +15,7 @@ "bootstrap": "^5.2.3", "bootstrap-icons": "^1.10.3", "embla-carousel-react": "^8.1.5", + "enc-utils": "^3.0.0", "ethers": "^5.7.2", "framer-motion": "^11.0.6", "next": "14.2.10", diff --git a/packages/passport/sdk/jest.config.ts b/packages/passport/sdk/jest.config.ts index e166d4ce55..bb680f31b7 100644 --- a/packages/passport/sdk/jest.config.ts +++ b/packages/passport/sdk/jest.config.ts @@ -10,6 +10,7 @@ const config: Config = { '^.+\\.(t|j)sx?$': '@swc/jest', }, transformIgnorePatterns: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/passport/sdk/package.json b/packages/passport/sdk/package.json index 0da4ff5e84..9657d95472 100644 --- a/packages/passport/sdk/package.json +++ b/packages/passport/sdk/package.json @@ -7,6 +7,8 @@ "dependencies": { "@0xsequence/abi": "^1.4.3", "@0xsequence/core": "^1.4.3", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@imtbl/config": "0.0.0", "@imtbl/generated-clients": "0.0.0", @@ -15,6 +17,7 @@ "@imtbl/x-client": "0.0.0", "@imtbl/x-provider": "0.0.0", "@magic-ext/oidc": "4.3.1", + "@magic-sdk/provider": "^21.2.0", "@metamask/detect-provider": "^2.0.0", "axios": "^1.6.5", "ethers": "^5.7.2", @@ -78,7 +81,7 @@ "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest", "test:watch": "jest --watch", - "typecheck": "tsc --noEmit --jsx preserve" + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" }, "type": "module" } diff --git a/packages/webhook/sdk/jest.config.ts b/packages/webhook/sdk/jest.config.ts index 05ef77a17a..7be21beeca 100644 --- a/packages/webhook/sdk/jest.config.ts +++ b/packages/webhook/sdk/jest.config.ts @@ -10,6 +10,7 @@ const config: Config = { '^.+\\.(t|j)sx?$': '@swc/jest', }, transformIgnorePatterns: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/webhook/sdk/package.json b/packages/webhook/sdk/package.json index 95b92a1999..33b528ebcc 100644 --- a/packages/webhook/sdk/package.json +++ b/packages/webhook/sdk/package.json @@ -53,7 +53,7 @@ "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest --passWithNoTests", "test:e2e": "jest --runInBand --testMatch \"**/?(*.)+(e2e).[jt]s?(x)\"", - "typecheck": "tsc --noEmit --jsx preserve" + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" }, "type": "module" } diff --git a/packages/x-client/jest.config.cjs b/packages/x-client/jest.config.cjs index 96f065a123..769d8b965b 100644 --- a/packages/x-client/jest.config.cjs +++ b/packages/x-client/jest.config.cjs @@ -1,6 +1,6 @@ module.exports = { testEnvironment: 'node', - moduleDirectories: ['node_modules', '/src'], + moduleDirectories: ['node_modules', '/src', '/.yalc'], modulePathIgnorePatterns: ['/dist/', '/backup/'], moduleNameMapper: { '^@imtbl/(.*)$': '/../../node_modules/@imtbl/$1/src' }, testRegex: '^.+\\.test\\.(js|ts|jsx|tsx)$', diff --git a/packages/x-client/package.json b/packages/x-client/package.json index 7d2480f19a..19436ca471 100644 --- a/packages/x-client/package.json +++ b/packages/x-client/package.json @@ -7,6 +7,7 @@ "dependencies": { "@ethersproject/abi": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", "@ethersproject/keccak256": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@ethersproject/strings": "^5.7.0", @@ -18,12 +19,14 @@ "elliptic": "^6.5.7", "enc-utils": "^3.0.0", "ethereumjs-wallet": "^1.0.2", - "ethers": "^5.7.2" + "ethers": "^5.7.2", + "hash.js": "^1.1.7" }, "devDependencies": { "@rollup/plugin-typescript": "^11.1.6", "@swc/core": "^1.3.36", "@swc/jest": "^0.2.24", + "@types/bn.js": "^5.1.6", "@types/jest": "^29.4.3", "eslint": "^8.40.0", "jest": "^29.4.3", @@ -60,7 +63,7 @@ "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest", - "typecheck": "tsc --noEmit --jsx preserve" + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" }, "type": "module" } diff --git a/packages/x-provider/jest.config.ts b/packages/x-provider/jest.config.ts index 5a141d28a7..5d9ee7fce3 100644 --- a/packages/x-provider/jest.config.ts +++ b/packages/x-provider/jest.config.ts @@ -10,6 +10,7 @@ const config: Config = { '^.+\\.(t|j)sx?$': '@swc/jest', }, transformIgnorePatterns: [], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/packages/x-provider/package.json b/packages/x-provider/package.json index b16ece6753..5fa33dac2b 100644 --- a/packages/x-provider/package.json +++ b/packages/x-provider/package.json @@ -5,6 +5,10 @@ "author": "Immutable", "bugs": "https://github.com/immutable/ts-immutable-sdk/issues", "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/providers": "^5.7.2", + "@ethersproject/units": "^5.7.0", "@imtbl/config": "0.0.0", "@imtbl/generated-clients": "0.0.0", "@imtbl/toolkit": "0.0.0", @@ -12,6 +16,7 @@ "@magic-ext/oidc": "4.3.1", "@metamask/detect-provider": "^2.0.0", "axios": "^1.6.5", + "enc-utils": "^3.0.0", "ethers": "^5.7.2", "magic-sdk": "^21.2.0", "oidc-client-ts": "2.4.0" @@ -65,7 +70,7 @@ "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest", "test:watch": "jest --watch", - "typecheck": "tsc --noEmit --jsx preserve" + "typecheck": "tsc --customConditions default --noEmit --jsx preserve" }, "type": "module" } diff --git a/packages/x-provider/src/sample-app/package.json b/packages/x-provider/src/sample-app/package.json index 4f691bd329..dbf85d2754 100644 --- a/packages/x-provider/src/sample-app/package.json +++ b/packages/x-provider/src/sample-app/package.json @@ -23,6 +23,7 @@ "@types/node": "^18.14.2", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", + "process": "^0.11.10", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", diff --git a/packages/x-provider/src/signable-actions/withdrawal-actions/completeERC20Withdrawal.test.ts b/packages/x-provider/src/signable-actions/withdrawal-actions/completeERC20Withdrawal.test.ts index 13bd3d3710..280ec2d0bf 100644 --- a/packages/x-provider/src/signable-actions/withdrawal-actions/completeERC20Withdrawal.test.ts +++ b/packages/x-provider/src/signable-actions/withdrawal-actions/completeERC20Withdrawal.test.ts @@ -58,6 +58,7 @@ describe('completeERC20Withdrawal action', () => { beforeEach(() => { jest.restoreAllMocks(); (getEncodeAssetInfo as jest.Mock).mockResolvedValue(encodeAssetResponse); + (isRegisteredOnChain as jest.Mock).mockResolvedValue(false); (getSignableRegistrationOnchain as jest.Mock).mockResolvedValue({ operator_signature: 'operator-signature', payload_hash: 'payload hash', diff --git a/packages/x-provider/src/signable-actions/withdrawal-actions/completeEthWithdrawal.test.ts b/packages/x-provider/src/signable-actions/withdrawal-actions/completeEthWithdrawal.test.ts index 39ca97bf19..d0153c5c8f 100644 --- a/packages/x-provider/src/signable-actions/withdrawal-actions/completeEthWithdrawal.test.ts +++ b/packages/x-provider/src/signable-actions/withdrawal-actions/completeEthWithdrawal.test.ts @@ -54,6 +54,7 @@ describe('completeEthWithdrawal action', () => { beforeEach(() => { jest.restoreAllMocks(); (getEncodeAssetInfo as jest.Mock).mockResolvedValue(encodeAssetResponse); + (isRegisteredOnChain as jest.Mock).mockResolvedValue(false); (getSignableRegistrationOnchain as jest.Mock).mockResolvedValue({ operator_signature: 'operator-signature', payload_hash: 'payload hash', diff --git a/sdk/package.json b/sdk/package.json index c8c7228bf9..16374798f9 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -12,14 +12,22 @@ "@biom3/design-tokens": "^0.4.2", "@biom3/react": "^0.25.0", "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/contracts": "^5.7.0", "@ethersproject/keccak256": "^5.7.0", "@ethersproject/providers": "^5.7.2", + "@ethersproject/solidity": "^5.7.0", "@ethersproject/strings": "^5.7.0", + "@ethersproject/units": "^5.7.0", "@ethersproject/wallet": "^5.7.0", "@imtbl/react-analytics": "0.2.1-alpha", "@jest/globals": "^29.5.0", "@magic-ext/oidc": "4.3.1", + "@magic-sdk/provider": "^21.2.0", "@metamask/detect-provider": "^2.0.0", "@opensea/seaport-js": "4.0.3", "@rive-app/react-canvas-lite": "^4.9.0", @@ -40,7 +48,10 @@ "ethers": "^5.7.2", "ethers-v6": "npm:ethers@6.11.1", "events": "^3.3.0", + "form-data": "^4.0.0", + "framer-motion": "^11.0.6", "global-const": "^0.1.2", + "hash.js": "^1.1.7", "https-browserify": "^1.0.0", "i18next": "^23.7.6", "i18next-browser-languagedetector": "^7.2.0", diff --git a/tests/func-tests/imx/jest.config.ts b/tests/func-tests/imx/jest.config.ts index 5542733063..b46b9f078f 100644 --- a/tests/func-tests/imx/jest.config.ts +++ b/tests/func-tests/imx/jest.config.ts @@ -23,6 +23,7 @@ const config: Config = { "^.+\\.module\\.(css|sass|scss)$", ], setupFilesAfterEnv: ['./jest.setup.ts'], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/tests/func-tests/imx/package.json b/tests/func-tests/imx/package.json index 892dd7d33d..5124491a1f 100644 --- a/tests/func-tests/imx/package.json +++ b/tests/func-tests/imx/package.json @@ -17,9 +17,6 @@ "ts-node": "^10.9.1", "typescript": "^5.6.2" }, - "resolutions": { - "@openzeppelin/contracts": "3.4.2-solc-0.7" - }, "scripts": { "func-test": "jest", "func-test:ci": "TAGS=\"not @skip and not @slow\" jest", diff --git a/tests/func-tests/zkevm/jest.config.ts b/tests/func-tests/zkevm/jest.config.ts index 4d3c411e55..ad8f252b50 100644 --- a/tests/func-tests/zkevm/jest.config.ts +++ b/tests/func-tests/zkevm/jest.config.ts @@ -9,6 +9,7 @@ const config: Config = { rootDir: ".", testMatch:["**/*.steps.ts"], testTimeout: 60000, + roots: ["step-definitions"], moduleDirectories: ["node_modules", ""], moduleNameMapper: { "@imtbl/sdk/provider": "/../../../node_modules/@imtbl/sdk/dist/provider", @@ -24,6 +25,7 @@ const config: Config = { "^.+\\.module\\.(css|sass|scss)$", ], setupFilesAfterEnv: ['./jest.setup.ts'], + modulePathIgnorePatterns: ['/.yalc'], }; export default config; diff --git a/tests/func-tests/zkevm/tsconfig.json b/tests/func-tests/zkevm/tsconfig.json index e05ca5f05c..4ec237a4d3 100644 --- a/tests/func-tests/zkevm/tsconfig.json +++ b/tests/func-tests/zkevm/tsconfig.json @@ -1,9 +1,9 @@ { "extends": "../../../tsconfig.base.json", "compilerOptions": { - "rootDirs": ["src"], + "rootDirs": ["utils", "step-definitions"], }, - "include": ["src"], + "include": ["utils", "step-definitions"], "exclude": [ "node_modules", "dist" diff --git a/yarn.lock b/yarn.lock index fdd31dfcf1..df5736c16f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3644,7 +3644,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/contracts@npm:5.7.0": +"@ethersproject/contracts@npm:5.7.0, @ethersproject/contracts@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/contracts@npm:5.7.0" dependencies: @@ -3838,7 +3838,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/solidity@npm:5.7.0, @ethersproject/solidity@npm:^5.0.0, @ethersproject/solidity@npm:^5.0.9": +"@ethersproject/solidity@npm:5.7.0, @ethersproject/solidity@npm:^5.0.0, @ethersproject/solidity@npm:^5.0.9, @ethersproject/solidity@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/solidity@npm:5.7.0" dependencies: @@ -3945,6 +3945,7 @@ __metadata: resolution: "@examples/create-listing-with-nextjs@workspace:examples/orderbook/create-listing-with-nextjs" dependencies: "@biom3/react": ^0.26.1 + "@ethersproject/providers": ^5.7.2 "@imtbl/sdk": latest "@playwright/test": ^1.45.3 "@types/node": ^20 @@ -3992,6 +3993,7 @@ __metadata: autoprefixer: ^10.4.19 eslint: ^8 eslint-config-next: 14.2.5 + ethers: ^5.7.2 next: 14.2.5 postcss: ^8.4.39 react: ^18.2.0 @@ -4101,6 +4103,7 @@ __metadata: autoprefixer: ^10.4.19 eslint: ^8 eslint-config-next: 14.2.5 + ethers: ^5.7.2 next: 14.2.10 postcss: ^8.4.39 react: ^18.2.0 @@ -4227,7 +4230,9 @@ __metadata: version: 0.0.0-use.local resolution: "@imtbl/checkout-sdk-sample-app@workspace:packages/checkout/sdk-sample-app" dependencies: + "@biom3/design-tokens": ^0.4.2 "@biom3/react": ^0.25.0 + "@ethersproject/providers": ^5.7.2 "@imtbl/checkout-sdk": 0.0.0 "@imtbl/checkout-widgets": 0.0.0 "@imtbl/config": 0.0.0 @@ -4250,6 +4255,7 @@ __metadata: framer-motion: ^11.0.6 jest: ^29.4.3 jest-environment-jsdom: ^29.4.3 + process: ^0.11.10 react: ^18.2.0 react-app-rewired: ^2.2.1 react-dom: ^18.2.0 @@ -4286,12 +4292,14 @@ __metadata: "@rollup/plugin-terser": ^0.4.4 "@rollup/plugin-typescript": ^11.1.6 "@swc/core": ^1.3.36 + "@types/jest": ^29.4.3 "@types/uuid": ^8.3.4 axios: ^1.6.5 babel-jest: ^29.5.0 eslint: ^8.40.0 ethers: ^5.7.2 jest: ^29.4.3 + jest-environment-jsdom: ^29.4.3 parcel: ^2.8.3 rollup: ^4.19.1 rollup-plugin-dts: ^6.1.1 @@ -4311,6 +4319,7 @@ __metadata: dependencies: "@biom3/design-tokens": ^0.4.2 "@biom3/react": ^0.25.0 + "@ethersproject/providers": ^5.7.2 "@imtbl/checkout-sdk": 0.0.0 "@imtbl/checkout-widgets": 0.0.0 "@imtbl/config": 0.0.0 @@ -4346,6 +4355,7 @@ __metadata: "@0xsquid/squid-types": ^0.1.104 "@biom3/design-tokens": ^0.4.2 "@biom3/react": ^0.25.0 + "@ethersproject/bignumber": ^5.7.0 "@ethersproject/providers": ^5.7.2 "@imtbl/bridge-sdk": 0.0.0 "@imtbl/checkout-sdk": 0.0.0 @@ -4354,6 +4364,7 @@ __metadata: "@imtbl/dex-sdk": 0.0.0 "@imtbl/passport": 0.0.0 "@imtbl/react-analytics": 0.2.1-alpha + "@jest/globals": ^29.5.0 "@rive-app/react-canvas-lite": ^4.9.0 "@rollup/plugin-commonjs": ^26.0.1 "@rollup/plugin-json": ^6.1.0 @@ -4373,11 +4384,13 @@ __metadata: "@walletconnect/ethereum-provider": ^2.11.1 "@walletconnect/modal": ^2.6.2 assert: ^2.0.0 + axios: ^1.6.5 buffer: ^6.0.3 crypto-browserify: ^3.12.0 cypress: 12.8.1 eslint: ^8.40.0 ethers: ^5.7.2 + framer-motion: ^11.0.6 https-browserify: ^1.0.0 i18next: ^23.7.6 i18next-browser-languagedetector: ^7.2.0 @@ -4476,6 +4489,12 @@ __metadata: version: 0.0.0-use.local resolution: "@imtbl/dex-sdk@workspace:packages/internal/dex/sdk" dependencies: + "@ethersproject/abi": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/contracts": ^5.7.0 + "@ethersproject/providers": ^5.7.2 + "@ethersproject/solidity": ^5.7.0 "@imtbl/config": 0.0.0 "@rollup/plugin-json": ^6.1.0 "@rollup/plugin-typescript": ^11.1.6 @@ -4521,6 +4540,9 @@ __metadata: "@openapitools/openapi-generator-cli": ^2.13.4 "@rollup/plugin-typescript": ^11.1.6 "@swc/core": ^1.3.36 + "@types/jest": ^29.4.3 + "@types/node": ^18.14.2 + axios: ^1.6.5 jest: ^29.4.3 rimraf: ^6.0.1 rollup: ^4.19.1 @@ -4546,6 +4568,7 @@ __metadata: "@swc/core": ^1.3.36 "@swc/jest": ^0.2.24 "@types/jest": ^29.4.3 + "@types/node": ^18.14.2 axios: ^1.6.5 eslint: ^8.40.0 global-const: ^0.1.2 @@ -4574,6 +4597,7 @@ __metadata: "@testcontainers/postgresql": ^10.9.0 "@types/jest": ^29.4.3 "@types/pg": ^8.11.5 + "@types/uuid": ^8.3.4 dotenv: ^16.0.3 eslint: ^8.40.0 jest: ^29.4.3 @@ -4585,6 +4609,7 @@ __metadata: ts-mockito: ^2.6.1 typescript: ^5.6.2 unplugin-swc: ^1.5.1 + uuid: ^8.3.2 dependenciesMeta: pg: optional: true @@ -4597,6 +4622,8 @@ __metadata: version: 0.0.0-use.local resolution: "@imtbl/orderbook@workspace:packages/orderbook" dependencies: + "@ethersproject/abi": ^5.7.0 + "@ethersproject/providers": ^5.7.2 "@imtbl/config": 0.0.0 "@imtbl/metrics": 0.0.0 "@opensea/seaport-js": 4.0.3 @@ -4611,6 +4638,7 @@ __metadata: eslint: ^8.40.0 ethers: ^5.7.2 ethers-v6: "npm:ethers@6.11.1" + form-data: ^4.0.0 jest: ^29.4.3 jest-environment-jsdom: ^29.4.3 merkletreejs: ^0.3.11 @@ -4647,6 +4675,7 @@ __metadata: bootstrap-icons: ^1.10.3 concurrently: ^8.2.2 embla-carousel-react: ^8.1.5 + enc-utils: ^3.0.0 eslint: ^8.40.0 eslint-config-next: 13.3.1 ethers: ^5.7.2 @@ -4665,6 +4694,8 @@ __metadata: dependencies: "@0xsequence/abi": ^1.4.3 "@0xsequence/core": ^1.4.3 + "@ethersproject/abstract-provider": ^5.7.0 + "@ethersproject/abstract-signer": ^5.7.0 "@ethersproject/providers": ^5.7.2 "@imtbl/config": 0.0.0 "@imtbl/generated-clients": 0.0.0 @@ -4673,6 +4704,7 @@ __metadata: "@imtbl/x-client": 0.0.0 "@imtbl/x-provider": 0.0.0 "@magic-ext/oidc": 4.3.1 + "@magic-sdk/provider": ^21.2.0 "@metamask/detect-provider": ^2.0.0 "@rollup/plugin-typescript": ^11.1.6 "@swc/core": ^1.3.36 @@ -4730,10 +4762,17 @@ __metadata: "@biom3/design-tokens": ^0.4.2 "@biom3/react": ^0.25.0 "@ethersproject/abi": ^5.7.0 + "@ethersproject/abstract-provider": ^5.7.0 "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/address": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 + "@ethersproject/contracts": ^5.7.0 "@ethersproject/keccak256": ^5.7.0 "@ethersproject/providers": ^5.7.2 + "@ethersproject/solidity": ^5.7.0 "@ethersproject/strings": ^5.7.0 + "@ethersproject/units": ^5.7.0 "@ethersproject/wallet": ^5.7.0 "@imtbl/blockchain-data": 0.0.0 "@imtbl/checkout-sdk": 0.0.0 @@ -4750,6 +4789,7 @@ __metadata: "@imtbl/x-provider": 0.0.0 "@jest/globals": ^29.5.0 "@magic-ext/oidc": 4.3.1 + "@magic-sdk/provider": ^21.2.0 "@metamask/detect-provider": ^2.0.0 "@opensea/seaport-js": 4.0.3 "@rive-app/react-canvas-lite": ^4.9.0 @@ -4778,8 +4818,11 @@ __metadata: ethers: ^5.7.2 ethers-v6: "npm:ethers@6.11.1" events: ^3.3.0 + form-data: ^4.0.0 + framer-motion: ^11.0.6 glob: ^10.2.3 global-const: ^0.1.2 + hash.js: ^1.1.7 https-browserify: ^1.0.0 i18next: ^23.7.6 i18next-browser-languagedetector: ^7.2.0 @@ -4894,6 +4937,7 @@ __metadata: "@swc/core": ^1.3.36 "@swc/jest": ^0.2.24 "@types/axios": ^0.14.0 + "@types/bn.js": ^5.1.6 "@types/jest": ^29.4.3 "@types/node": ^18.14.2 "@types/react": ^18.0.28 @@ -4948,6 +4992,7 @@ __metadata: dependencies: "@ethersproject/abi": ^5.7.0 "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/bytes": ^5.7.0 "@ethersproject/keccak256": ^5.7.0 "@ethersproject/providers": ^5.7.2 "@ethersproject/strings": ^5.7.0 @@ -4957,6 +5002,7 @@ __metadata: "@rollup/plugin-typescript": ^11.1.6 "@swc/core": ^1.3.36 "@swc/jest": ^0.2.24 + "@types/bn.js": ^5.1.6 "@types/jest": ^29.4.3 axios: ^1.6.5 bn.js: ^5.2.1 @@ -4965,6 +5011,7 @@ __metadata: eslint: ^8.40.0 ethereumjs-wallet: ^1.0.2 ethers: ^5.7.2 + hash.js: ^1.1.7 jest: ^29.4.3 jest-environment-jsdom: ^29.4.3 rollup: ^4.19.1 @@ -4977,6 +5024,10 @@ __metadata: version: 0.0.0-use.local resolution: "@imtbl/x-provider@workspace:packages/x-provider" dependencies: + "@ethersproject/abstract-signer": ^5.7.0 + "@ethersproject/bignumber": ^5.7.0 + "@ethersproject/providers": ^5.7.2 + "@ethersproject/units": ^5.7.0 "@imtbl/config": 0.0.0 "@imtbl/generated-clients": 0.0.0 "@imtbl/toolkit": 0.0.0 @@ -4994,6 +5045,7 @@ __metadata: "@typescript-eslint/eslint-plugin": ^5.57.1 "@typescript-eslint/parser": ^5.57.1 axios: ^1.6.5 + enc-utils: ^3.0.0 eslint: ^8.40.0 ethers: ^5.7.2 jest: ^29.4.3 @@ -12091,6 +12143,15 @@ __metadata: languageName: node linkType: hard +"@types/bn.js@npm:^5.1.6": + version: 5.1.6 + resolution: "@types/bn.js@npm:5.1.6" + dependencies: + "@types/node": "*" + checksum: 887411126d40e3d28aef2df8075cda2832db2b0e926bb4046039bbb026f2e3cfbcf1a3ce90bd935be0fcc039f8009e32026dfbb84a11c1f5d051cd7f8194ba23 + languageName: node + linkType: hard + "@types/body-parser@npm:*": version: 1.19.2 resolution: "@types/body-parser@npm:1.19.2" @@ -40234,6 +40295,7 @@ __metadata: assert-browserify: ^2.0.0 buffer: ^6.0.3 crypto-browserify: ^3.12.0 + process: ^0.11.10 react: ^18.2.0 react-app-rewired: ^2.2.1 react-dom: ^18.2.0 From b475a9ad280458bbfdfcf906b53017cffa436e16 Mon Sep 17 00:00:00 2001 From: "Craig M." Date: Wed, 25 Sep 2024 13:18:05 +1200 Subject: [PATCH 3/4] refactor: UI ported to Biome (#2203) --- .../app/connect-with-eip1193/page.tsx | 63 +++++++++++-------- .../app/connect-with-etherjs/page.tsx | 62 +++++++++++------- .../app/connect-with-wagmi/account.tsx | 40 ++++++++---- .../app/connect-with-wagmi/page.tsx | 14 ++--- .../app/connect-with-wagmi/wallet-options.tsx | 18 ++++-- .../app/globals.css | 21 ++++++- .../app/layout.tsx | 3 + .../app/logout/page.tsx | 18 +++--- .../wallets-connect-with-nextjs/app/page.tsx | 54 ++++++++-------- .../app/redirect/page.tsx | 7 +-- .../app/{utils.ts => utils/passport.ts} | 0 .../app/utils/wrapper.tsx | 18 ++++++ .../wallets-connect-with-nextjs/package.json | 1 + .../tests/base.spec.ts | 15 ++--- yarn.lock | 3 +- 15 files changed, 209 insertions(+), 128 deletions(-) rename examples/passport/wallets-connect-with-nextjs/app/{utils.ts => utils/passport.ts} (100%) create mode 100644 examples/passport/wallets-connect-with-nextjs/app/utils/wrapper.tsx diff --git a/examples/passport/wallets-connect-with-nextjs/app/connect-with-eip1193/page.tsx b/examples/passport/wallets-connect-with-nextjs/app/connect-with-eip1193/page.tsx index 60dc8df591..3741a3b536 100644 --- a/examples/passport/wallets-connect-with-nextjs/app/connect-with-eip1193/page.tsx +++ b/examples/passport/wallets-connect-with-nextjs/app/connect-with-eip1193/page.tsx @@ -2,7 +2,9 @@ import { useState } from 'react'; import { ProviderEvent } from '@imtbl/sdk/passport'; -import { passportInstance } from '../utils'; +import { passportInstance } from '../utils/passport'; +import { Button, Heading, Link, Table } from '@biom3/react'; +import NextLink from 'next/link'; export default function ConnectWithEtherJS() { // setup the accounts state @@ -48,39 +50,50 @@ export default function ConnectWithEtherJS() { // render the view to login/logout and show the connected accounts return ( -
-

Passport Connect with EIP-1193

+ <> + Passport Connect with EIP-1193 {accountsState.length === 0 && ( - + )} {accountsState.length >= 1 && ( - + )} -
- {loading - ?

Loading...

- : ( -

- Connected Account: - {accountsState.length >= 1 ? accountsState : '(not connected)'} -

- )} -
- Return to Examples -
+ + + + Item + Value + + + + + Connected Account + + {accountsState.length === 0 && ( + (not connected) + ) + } + {accountsState.length > 0 && accountsState[0]} + + + +
+
+ }>Return to Examples + ); } diff --git a/examples/passport/wallets-connect-with-nextjs/app/connect-with-etherjs/page.tsx b/examples/passport/wallets-connect-with-nextjs/app/connect-with-etherjs/page.tsx index a3e63b693e..981a36241c 100644 --- a/examples/passport/wallets-connect-with-nextjs/app/connect-with-etherjs/page.tsx +++ b/examples/passport/wallets-connect-with-nextjs/app/connect-with-etherjs/page.tsx @@ -3,7 +3,9 @@ import { useState } from 'react'; import { ethers } from 'ethers'; import { ProviderEvent } from '@imtbl/sdk/passport'; -import { passportInstance } from '../utils'; +import { passportInstance } from '../utils/passport'; +import { Button, Heading, Link, Table } from '@biom3/react'; +import NextLink from 'next/link'; export default function ConnectWithEtherJS() { // setup the accounts state @@ -53,39 +55,51 @@ export default function ConnectWithEtherJS() { // render the view to login/logout and show the connected accounts return ( -
-

Passport Connect with EtherJS

+ <> + Passport Connect with EtherJS {accountsState.length === 0 && ( - + )} {accountsState.length >= 1 && ( - + )}
- {loading - ?

Loading...

- : ( -

- Connected Account: - {accountsState.length >= 1 ? accountsState : '(not connected)'} -

- )} -
- Return to Examples -
+ + + + Item + Value + + + + + Connected Account + + {accountsState.length === 0 && ( + (not connected) + ) + } + {accountsState.length > 0 && accountsState[0]} + + + +
+
+ }>Return to Examples + ); } diff --git a/examples/passport/wallets-connect-with-nextjs/app/connect-with-wagmi/account.tsx b/examples/passport/wallets-connect-with-nextjs/app/connect-with-wagmi/account.tsx index bfb63fb24e..3d4a012561 100644 --- a/examples/passport/wallets-connect-with-nextjs/app/connect-with-wagmi/account.tsx +++ b/examples/passport/wallets-connect-with-nextjs/app/connect-with-wagmi/account.tsx @@ -2,7 +2,8 @@ import { useAccount, useDisconnect, useEnsName, } from 'wagmi'; import { useState } from 'react'; -import { passportInstance } from '../utils'; +import { passportInstance } from '../utils/passport'; +import { Button, Table } from '@biom3/react'; export function Account() { const { address } = useAccount(); @@ -24,23 +25,36 @@ export function Account() { // render the view to show the connected accounts and logout return ( <> - -
- {loading - ?

Loading...

- : ( -

- Connected Account: - {address && {ensName ? `${ensName} (${address})` : address}} -

- )} + + + + + Item + Value + + + + + Connected Account + + {!address && ( + (not connected) + ) + } + {address && ( + {ensName ? `${ensName} (${address})` : address} + )} + + + +
+
); } diff --git a/examples/passport/wallets-connect-with-nextjs/app/connect-with-wagmi/page.tsx b/examples/passport/wallets-connect-with-nextjs/app/connect-with-wagmi/page.tsx index 085dd0f62a..50bb420d40 100644 --- a/examples/passport/wallets-connect-with-nextjs/app/connect-with-wagmi/page.tsx +++ b/examples/passport/wallets-connect-with-nextjs/app/connect-with-wagmi/page.tsx @@ -2,9 +2,11 @@ import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { passportInstance } from '../utils'; +import { passportInstance } from '../utils/passport'; import { config } from './config'; import { ConnectWallet } from './connect'; +import { Heading, Link } from '@biom3/react'; +import NextLink from 'next/link'; // initialise the QueryClient for the Provider const queryClient = new QueryClient(); @@ -16,16 +18,14 @@ export default function ConnectWithWagmi() { // render the ConnectWallet component // wrapping it in the Wagami and QueryClient Providers return ( -
- -

Passport Connect with Wagmi

+ <> + Passport Connect with Wagmi -
- Return to Examples -
+ }>Return to Examples + ); } diff --git a/examples/passport/wallets-connect-with-nextjs/app/connect-with-wagmi/wallet-options.tsx b/examples/passport/wallets-connect-with-nextjs/app/connect-with-wagmi/wallet-options.tsx index 9578bd9b15..06a229ab88 100644 --- a/examples/passport/wallets-connect-with-nextjs/app/connect-with-wagmi/wallet-options.tsx +++ b/examples/passport/wallets-connect-with-nextjs/app/connect-with-wagmi/wallet-options.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; import { Connector, useConnect } from 'wagmi'; +import { Button } from '@biom3/react'; export function WalletOptions() { // get the available connectors and the connect function from Wagmi @@ -30,18 +31,25 @@ export function WalletOptions() { return ( <> {filteredConnectors.map((connector) => ( - + + ))} -
- {loading &&

Loading...

} + + {loading && ( + <> +

Loading...

+ + )} ); } diff --git a/examples/passport/wallets-connect-with-nextjs/app/globals.css b/examples/passport/wallets-connect-with-nextjs/app/globals.css index b5c61c9567..cd0bc82b29 100644 --- a/examples/passport/wallets-connect-with-nextjs/app/globals.css +++ b/examples/passport/wallets-connect-with-nextjs/app/globals.css @@ -1,3 +1,18 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +html, body { + height: 100%; +} +body { + margin: 0; +} +.flex-container { + height: 100%; + padding: 0; + margin: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.mb-1 { + margin-bottom: 1rem; +} \ No newline at end of file diff --git a/examples/passport/wallets-connect-with-nextjs/app/layout.tsx b/examples/passport/wallets-connect-with-nextjs/app/layout.tsx index 883283b6e3..6cc4b8846c 100644 --- a/examples/passport/wallets-connect-with-nextjs/app/layout.tsx +++ b/examples/passport/wallets-connect-with-nextjs/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import './globals.css'; +import AppWrapper from './utils/wrapper'; const inter = Inter({ subsets: ['latin'] }); @@ -17,7 +18,9 @@ export default function RootLayout({ return ( + {children} + ); diff --git a/examples/passport/wallets-connect-with-nextjs/app/logout/page.tsx b/examples/passport/wallets-connect-with-nextjs/app/logout/page.tsx index 47163946b1..65a71f6d19 100644 --- a/examples/passport/wallets-connect-with-nextjs/app/logout/page.tsx +++ b/examples/passport/wallets-connect-with-nextjs/app/logout/page.tsx @@ -1,18 +1,14 @@ 'use client'; +import { Heading, Link } from '@biom3/react'; +import NextLink from 'next/link'; + export default function Logout() { // render the view for after the logout is complete return ( -
-

Logged Out

- -
+ <> + Logged Out + }>Return to Examples + ); } diff --git a/examples/passport/wallets-connect-with-nextjs/app/page.tsx b/examples/passport/wallets-connect-with-nextjs/app/page.tsx index 8ff6bf3e1c..495318ba2c 100644 --- a/examples/passport/wallets-connect-with-nextjs/app/page.tsx +++ b/examples/passport/wallets-connect-with-nextjs/app/page.tsx @@ -1,27 +1,31 @@ +'use client'; +import { Button, Heading } from '@biom3/react'; +import NextLink from 'next/link'; + export default function Home() { - return ( - - ); + return (<> + + Passport Connect Examples + + + + + ); } diff --git a/examples/passport/wallets-connect-with-nextjs/app/redirect/page.tsx b/examples/passport/wallets-connect-with-nextjs/app/redirect/page.tsx index 5ba3432c0d..a48fb92f49 100644 --- a/examples/passport/wallets-connect-with-nextjs/app/redirect/page.tsx +++ b/examples/passport/wallets-connect-with-nextjs/app/redirect/page.tsx @@ -1,7 +1,8 @@ 'use client'; import { useEffect } from 'react'; -import { passportInstance } from '../utils'; +import { passportInstance } from '../utils/passport'; +import { Heading } from '@biom3/react'; export default function Redirect() { useEffect(() => { @@ -11,8 +12,6 @@ export default function Redirect() { // render the view for the login popup after the login is complete return ( -
-

Logged in

-
+ Logged in ); } diff --git a/examples/passport/wallets-connect-with-nextjs/app/utils.ts b/examples/passport/wallets-connect-with-nextjs/app/utils/passport.ts similarity index 100% rename from examples/passport/wallets-connect-with-nextjs/app/utils.ts rename to examples/passport/wallets-connect-with-nextjs/app/utils/passport.ts diff --git a/examples/passport/wallets-connect-with-nextjs/app/utils/wrapper.tsx b/examples/passport/wallets-connect-with-nextjs/app/utils/wrapper.tsx new file mode 100644 index 0000000000..b31df440dc --- /dev/null +++ b/examples/passport/wallets-connect-with-nextjs/app/utils/wrapper.tsx @@ -0,0 +1,18 @@ +'use client'; +import { BiomeCombinedProviders, Stack } from '@biom3/react'; + +export default function AppWrapper({ + children, + }: Readonly<{ + children: React.ReactNode; + }>) { + return ( +
+ + + { children } + + +
+ ); +} \ No newline at end of file diff --git a/examples/passport/wallets-connect-with-nextjs/package.json b/examples/passport/wallets-connect-with-nextjs/package.json index 903b946c50..3047661b3e 100644 --- a/examples/passport/wallets-connect-with-nextjs/package.json +++ b/examples/passport/wallets-connect-with-nextjs/package.json @@ -2,6 +2,7 @@ "name": "@examples/wallets-connect-with-nextjs", "version": "0.1.0", "dependencies": { + "@biom3/react": "^0.26.2", "@ethersproject/providers": "^5.7.2", "@imtbl/sdk": "latest", "@tanstack/react-query": "^5.51.11", diff --git a/examples/passport/wallets-connect-with-nextjs/tests/base.spec.ts b/examples/passport/wallets-connect-with-nextjs/tests/base.spec.ts index c677fa39f9..eb32ecdcaa 100644 --- a/examples/passport/wallets-connect-with-nextjs/tests/base.spec.ts +++ b/examples/passport/wallets-connect-with-nextjs/tests/base.spec.ts @@ -7,9 +7,7 @@ test.beforeEach(async ({ page }) => { test.describe("home page", () => { test("has title, heading and link", async ({ page }) => { await expect(page).toHaveTitle("Passport Connect Examples"); - await expect(page.getByRole("heading", { name: "Passport Connect Examples" })).toBeVisible(); - await expect(page.getByRole("link", { name: "Connect with EtherJS" })).toBeVisible(); await expect(page.getByRole("link", { name: "Connect with EIP-1193" })).toBeVisible(); await expect(page.getByRole("link", { name: "Connect with Wagmi" })).toBeVisible(); @@ -19,11 +17,10 @@ test.describe("home page", () => { test.describe("connect wallet with etherjs", () => { test("has heading, login button and initial account status set correctly", async ({ page }) => { await page.click("text=Connect with EtherJS"); - await expect(page.getByRole("heading", { name: "Passport Connect with EtherJS" })).toBeVisible(); - await expect(page.getByRole("button", { name: "Passport Login" })).toBeVisible(); - await expect(page.getByText("Connected Account:")).toBeVisible(); + await expect(page.getByText("Connected Account")).toBeVisible(); + await expect(page.getByText("(not connected)")).toBeVisible(); await expect(page.getByRole("link", { name: "Return to Examples" })).toBeVisible(); }); }); @@ -31,11 +28,10 @@ test.describe("connect wallet with etherjs", () => { test.describe("connect wallet with eip1193", () => { test("has heading, login button and initial account status set correctly", async ({ page }) => { await page.click("text=Connect with EIP-1193"); - await expect(page.getByRole("heading", { name: "Passport Connect with EIP-1193" })).toBeVisible(); - await expect(page.getByRole("button", { name: "Passport Login" })).toBeVisible(); - await expect(page.getByText("Connected Account:")).toBeVisible(); + await expect(page.getByText("Connected Account")).toBeVisible(); + await expect(page.getByText("(not connected)")).toBeVisible(); await expect(page.getByRole("link", { name: "Return to Examples" })).toBeVisible(); }); }); @@ -43,9 +39,8 @@ test.describe("connect wallet with eip1193", () => { test.describe("connect wallet with wagmi", () => { test("has heading and login button set correctly", async ({ page }) => { await page.click("text=Connect with Wagmi"); - await expect(page.getByRole("heading", { name: "Passport Connect with Wagmi" })).toBeVisible(); - + await expect(page.getByText("Connect with:")).toBeVisible(); await expect(page.getByRole("button", { name: "Immutable Passport" })).toBeVisible(); await expect(page.getByRole("link", { name: "Return to Examples" })).toBeVisible(); }); diff --git a/yarn.lock b/yarn.lock index df5736c16f..c0df17f3d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2617,7 +2617,7 @@ __metadata: languageName: node linkType: hard -"@biom3/react@npm:^0.26.1": +"@biom3/react@npm:^0.26.1, @biom3/react@npm:^0.26.2": version: 0.26.2 resolution: "@biom3/react@npm:0.26.2" dependencies: @@ -4047,6 +4047,7 @@ __metadata: version: 0.0.0-use.local resolution: "@examples/wallets-connect-with-nextjs@workspace:examples/passport/wallets-connect-with-nextjs" dependencies: + "@biom3/react": ^0.26.2 "@ethersproject/providers": ^5.7.2 "@imtbl/sdk": latest "@playwright/test": ^1.45.2 From bc5626dbcf670e655af4b70f4b27a9f3ff1d54d5 Mon Sep 17 00:00:00 2001 From: Leslie Fung Date: Wed, 25 Sep 2024 12:04:44 +1000 Subject: [PATCH 4/4] feat: [TD-1677] Add support for bids (#2186) --- .../src/api-client/api-client.test.ts | 14 +- .../orderbook/src/api-client/api-client.ts | 140 +++-- packages/orderbook/src/openapi/mapper.ts | 8 +- packages/orderbook/src/orderbook.ts | 147 +++++- packages/orderbook/src/seaport/components.ts | 111 +++- .../src/seaport/map-to-immutable-order.ts | 61 +++ .../orderbook/src/seaport/seaport.test.ts | 154 +++++- packages/orderbook/src/seaport/seaport.ts | 25 +- packages/orderbook/src/types.ts | 45 ++ tests/func-tests/zkevm/README.md | 42 +- .../zkevm/contracts/TestERC20Token.sol | 35 ++ tests/func-tests/zkevm/jest.config.ts | 4 +- .../func-tests/zkevm/specs/orderbook.spec.ts | 477 ++++++++++++++++++ .../zkevm/step-definitions/shared.ts | 4 +- tests/func-tests/zkevm/utils/orderbook/bid.ts | 51 ++ .../zkevm/utils/orderbook/deploy-erc20.ts | 16 + .../func-tests/zkevm/utils/orderbook/erc20.ts | 37 ++ .../func-tests/zkevm/utils/orderbook/index.ts | 3 +- .../zkevm/utils/orderbook/listing.ts | 51 ++ .../func-tests/zkevm/utils/orderbook/math.ts | 11 + .../func-tests/zkevm/utils/orderbook/order.ts | 33 ++ .../func-tests/zkevm/utils/orderbook/retry.ts | 21 + .../zkevm/utils/orderbook/switch.ts | 3 + 23 files changed, 1346 insertions(+), 147 deletions(-) create mode 100644 packages/orderbook/src/seaport/map-to-immutable-order.ts create mode 100644 tests/func-tests/zkevm/contracts/TestERC20Token.sol create mode 100644 tests/func-tests/zkevm/specs/orderbook.spec.ts create mode 100644 tests/func-tests/zkevm/utils/orderbook/bid.ts create mode 100644 tests/func-tests/zkevm/utils/orderbook/deploy-erc20.ts create mode 100644 tests/func-tests/zkevm/utils/orderbook/erc20.ts create mode 100644 tests/func-tests/zkevm/utils/orderbook/listing.ts create mode 100644 tests/func-tests/zkevm/utils/orderbook/math.ts create mode 100644 tests/func-tests/zkevm/utils/orderbook/retry.ts create mode 100644 tests/func-tests/zkevm/utils/orderbook/switch.ts diff --git a/packages/orderbook/src/api-client/api-client.test.ts b/packages/orderbook/src/api-client/api-client.test.ts index 4669cfed69..bf703d3ebf 100644 --- a/packages/orderbook/src/api-client/api-client.test.ts +++ b/packages/orderbook/src/api-client/api-client.test.ts @@ -2,6 +2,7 @@ import { anything, deepEqual, instance, mock, when, } from 'ts-mockito'; import type { OrderComponents } from '@opensea/seaport-js/lib/types'; +import { OrderType } from '@opensea/seaport-js/lib/constants'; import { ListingResult, OrdersService } from '../openapi/sdk'; import { ItemType } from '../seaport'; import { ImmutableApiClient } from './api-client'; @@ -172,19 +173,12 @@ describe('ImmutableApiClient', () => { itemType: ItemType.NATIVE, endAmount: '1', startAmount: '1', - identifierOrCriteria: '456', - token: '0x123', - recipient: '0x123', - }, - { - itemType: ItemType.NATIVE, - endAmount: '1', - startAmount: '1', - identifierOrCriteria: '456', - token: '0x123', + identifierOrCriteria: '0', + token: '0x', recipient: '0x123', }, ]; + orderComponents.orderType = OrderType.FULL_RESTRICTED; orderComponents.endTime = new Date().getTime() / 1000; orderComponents.startTime = new Date().getTime() / 1000; diff --git a/packages/orderbook/src/api-client/api-client.ts b/packages/orderbook/src/api-client/api-client.ts index 0a4a26e1ed..571830f7de 100644 --- a/packages/orderbook/src/api-client/api-client.ts +++ b/packages/orderbook/src/api-client/api-client.ts @@ -1,23 +1,26 @@ import { + BidResult, + CancelOrdersResult, Fee, + ListBidsResult, ListingResult, ListListingsResult, ListTradeResult, OrdersService, - ProtocolData, TradeResult, - CancelOrdersResult, } from '../openapi/sdk'; +import { FulfillableOrder } from '../openapi/sdk/models/FulfillableOrder'; +import { FulfillmentDataRequest } from '../openapi/sdk/models/FulfillmentDataRequest'; +import { UnfulfillableOrder } from '../openapi/sdk/models/UnfulfillableOrder'; +import { ItemType, SEAPORT_CONTRACT_VERSION_V1_5 } from '../seaport'; +import { mapSeaportItemToImmutableItem, mapSeaportOrderTypeToImmutableProtocolDataOrderType } from '../seaport/map-to-immutable-order'; import { + CreateBidParams, CreateListingParams, - FeeType, + ListBidsParams, ListListingsParams, ListTradesParams, } from '../types'; -import { FulfillableOrder } from '../openapi/sdk/models/FulfillableOrder'; -import { UnfulfillableOrder } from '../openapi/sdk/models/UnfulfillableOrder'; -import { FulfillmentDataRequest } from '../openapi/sdk/models/FulfillmentDataRequest'; -import { ItemType, SEAPORT_CONTRACT_VERSION_V1_5 } from '../seaport'; export class ImmutableApiClient { constructor( @@ -47,6 +50,13 @@ export class ImmutableApiClient { }); } + async getBid(bidId: string): Promise { + return this.orderbookService.getBid({ + chainName: this.chainName, + bidId, + }); + } + async getTrade(tradeId: string): Promise { return this.orderbookService.getTrade({ chainName: this.chainName, @@ -63,6 +73,15 @@ export class ImmutableApiClient { }); } + async listBids( + listOrderParams: ListBidsParams, + ): Promise { + return this.orderbookService.listBids({ + chainName: this.chainName, + ...listOrderParams, + }); + } + async listTrades( listTradesParams: ListTradesParams, ): Promise { @@ -94,66 +113,99 @@ export class ImmutableApiClient { makerFees, }: CreateListingParams): Promise { if (orderComponents.offer.length !== 1) { - throw new Error('Only one item can be listed at a time'); + throw new Error('Only one item can be listed for a listing'); } - if (Number(orderComponents.offer[0].itemType) !== ItemType.ERC721 - && Number(orderComponents.offer[0].itemType) !== ItemType.ERC1155) { + if (orderComponents.consideration.length !== 1) { + throw new Error('Only one item can be used as currency for a listing'); + } + + if (![ItemType.ERC721, ItemType.ERC1155].includes(orderComponents.offer[0].itemType)) { throw new Error('Only ERC721 / ERC1155 tokens can be listed'); } - const orderTypes = [ - ...orderComponents.consideration.map((c) => c.itemType), - ]; - const isSameConsiderationType = new Set(orderTypes).size === 1; - if (!isSameConsiderationType) { - throw new Error('All consideration items must be of the same type'); + if (![ItemType.NATIVE, ItemType.ERC20].includes(orderComponents.consideration[0].itemType)) { + throw new Error('Only Native / ERC20 tokens can be used as currency items in a listing'); } return this.orderbookService.createListing({ chainName: this.chainName, requestBody: { account_address: orderComponents.offerer, - buy: [ - { - type: - Number(orderComponents.consideration[0].itemType) - === ItemType.NATIVE - ? 'NATIVE' - : 'ERC20', - amount: orderComponents.consideration[0].startAmount, - contract_address: orderComponents.consideration[0].token, - }, - ], - fees: makerFees.map((x) => ({ - amount: x.amount, - type: FeeType.MAKER_ECOSYSTEM as unknown as Fee.type, - recipient_address: x.recipientAddress, + buy: orderComponents.consideration.map(mapSeaportItemToImmutableItem), + fees: makerFees.map((f) => ({ + type: Fee.type.MAKER_ECOSYSTEM, + amount: f.amount, + recipient_address: f.recipientAddress, + })), + end_at: new Date( + parseInt(`${orderComponents.endTime.toString()}000`, 10), + ).toISOString(), + order_hash: orderHash, + protocol_data: { + order_type: + mapSeaportOrderTypeToImmutableProtocolDataOrderType(orderComponents.orderType), + zone_address: orderComponents.zone, + seaport_address: this.seaportAddress, + seaport_version: SEAPORT_CONTRACT_VERSION_V1_5, + counter: orderComponents.counter.toString(), + }, + salt: orderComponents.salt, + sell: orderComponents.offer.map(mapSeaportItemToImmutableItem), + signature: orderSignature, + start_at: new Date( + parseInt(`${orderComponents.startTime.toString()}000`, 10), + ).toISOString(), + }, + }); + } + + async createBid({ + orderHash, + orderComponents, + orderSignature, + makerFees, + }: CreateBidParams): Promise { + if (orderComponents.offer.length !== 1) { + throw new Error('Only one item can be listed for a bid'); + } + + if (orderComponents.consideration.length !== 1) { + throw new Error('Only one item can be used as currency for a bid'); + } + + if (ItemType.ERC20 !== orderComponents.offer[0].itemType) { + throw new Error('Only ERC20 tokens can be used as the currency item in a bid'); + } + + if (![ItemType.ERC721, ItemType.ERC1155].includes(orderComponents.consideration[0].itemType)) { + throw new Error('Only ERC721 / ERC1155 tokens can be bid against'); + } + + return this.orderbookService.createBid({ + chainName: this.chainName, + requestBody: { + account_address: orderComponents.offerer, + buy: orderComponents.consideration.map(mapSeaportItemToImmutableItem), + fees: makerFees.map((f) => ({ + type: Fee.type.MAKER_ECOSYSTEM, + amount: f.amount, + recipient_address: f.recipientAddress, })), end_at: new Date( parseInt(`${orderComponents.endTime.toString()}000`, 10), ).toISOString(), order_hash: orderHash, protocol_data: { - order_type: Number(orderComponents.offer[0].itemType) === ItemType.ERC1155 - ? ProtocolData.order_type.PARTIAL_RESTRICTED : ProtocolData.order_type.FULL_RESTRICTED, + order_type: + mapSeaportOrderTypeToImmutableProtocolDataOrderType(orderComponents.orderType), zone_address: orderComponents.zone, seaport_address: this.seaportAddress, seaport_version: SEAPORT_CONTRACT_VERSION_V1_5, counter: orderComponents.counter.toString(), }, salt: orderComponents.salt, - sell: Number(orderComponents.offer[0].itemType) === ItemType.ERC1155 - ? [{ - contract_address: orderComponents.offer[0].token, - token_id: orderComponents.offer[0].identifierOrCriteria, - type: 'ERC1155', - amount: orderComponents.offer[0].startAmount, - }] : [{ - contract_address: orderComponents.offer[0].token, - token_id: orderComponents.offer[0].identifierOrCriteria, - type: 'ERC721', - }], + sell: orderComponents.offer.map(mapSeaportItemToImmutableItem), signature: orderSignature, start_at: new Date( parseInt(`${orderComponents.startTime.toString()}000`, 10), diff --git a/packages/orderbook/src/openapi/mapper.ts b/packages/orderbook/src/openapi/mapper.ts index f61b6fd364..ca5a2c621f 100644 --- a/packages/orderbook/src/openapi/mapper.ts +++ b/packages/orderbook/src/openapi/mapper.ts @@ -99,7 +99,7 @@ export function mapBidFromOpenApiOrder(order: OpenApiOrder): Bid { throw new Error('Order type must be BID'); } - const sellItems: ERC20Item[] = order.buy.map((item) => { + const sellItems: ERC20Item[] = order.sell.map((item) => { if (item.type === 'ERC20') { return { type: 'ERC20', @@ -111,7 +111,7 @@ export function mapBidFromOpenApiOrder(order: OpenApiOrder): Bid { throw new Error('Bid sell items must be ERC20'); }); - const buyItems: (ERC721Item | ERC1155Item)[] = order.sell.map((item) => { + const buyItems: (ERC721Item | ERC1155Item)[] = order.buy.map((item) => { if (item.type === 'ERC721') { return { type: 'ERC721', @@ -168,7 +168,7 @@ export function mapCollectionBidFromOpenApiOrder(order: OpenApiOrder): Collectio throw new Error('Order type must be COLLECTION_BID'); } - const sellItems: ERC20Item[] = order.buy.map((item) => { + const sellItems: ERC20Item[] = order.sell.map((item) => { if (item.type === 'ERC20') { return { type: 'ERC20', @@ -180,7 +180,7 @@ export function mapCollectionBidFromOpenApiOrder(order: OpenApiOrder): Collectio throw new Error('Collection bid sell items must be ERC20'); }); - const buyItems: (ERC721CollectionItem | ERC1155CollectionItem)[] = order.sell.map((item) => { + const buyItems: (ERC721CollectionItem | ERC1155CollectionItem)[] = order.buy.map((item) => { if (item.type === 'ERC721_COLLECTION') { return { type: 'ERC721_COLLECTION', diff --git a/packages/orderbook/src/orderbook.ts b/packages/orderbook/src/orderbook.ts index 49c57345f3..515fae687c 100644 --- a/packages/orderbook/src/orderbook.ts +++ b/packages/orderbook/src/orderbook.ts @@ -8,31 +8,38 @@ import { OrderbookOverrides, } from './config/config'; import { + mapBidFromOpenApiOrder, mapFromOpenApiPage, mapFromOpenApiTrade, mapListingFromOpenApiOrder, mapOrderFromOpenApiOrder, } from './openapi/mapper'; -import { CancelOrdersResult, Fee as OpenApiFee } from './openapi/sdk'; +import { ApiError, CancelOrdersResult, Fee as OpenApiFee } from './openapi/sdk'; import { Seaport } from './seaport'; import { getBulkSeaportOrderSignatures } from './seaport/components'; import { SeaportLibFactory } from './seaport/seaport-lib-factory'; import { Action, ActionType, + BidResult, CancelOrdersOnChainResponse, + CreateBidParams, CreateListingParams, FeeValue, FulfillBulkOrdersResponse, FulfillmentListing, FulfillmentOrder, FulfillOrderResponse, + ListBidsParams, + ListBidsResult, ListingResult, ListListingsParams, ListListingsResult, ListTradesParams, ListTradesResult, OrderStatusName, + PrepareBidParams, + PrepareBidResponse, PrepareBulkListingsParams, PrepareBulkListingsResponse, PrepareCancelOrdersResponse, @@ -127,6 +134,18 @@ export class Orderbook { }; } + /** + * Get a bid by ID + * @param {string} bidId - The bidId to find. + * @return {BidResult} The returned bid result. + */ + async getBid(bidId: string): Promise { + const apiBid = await this.apiClient.getBid(bidId); + return { + result: mapBidFromOpenApiOrder(apiBid.result), + }; + } + /** * Get a trade by ID * @param {string} tradeId - The tradeId to find. @@ -155,6 +174,22 @@ export class Orderbook { }; } + /** + * List bids. This method is used to get a list of bids filtered by conditions specified + * in the params object. + * @param {ListBidsParams} listOrderParams - Filtering, ordering and page parameters. + * @return {ListBidsResult} The paged bids. + */ + async listBids( + listOrderParams: ListBidsParams, + ): Promise { + const apiBids = await this.apiClient.listBids(listOrderParams); + return { + page: mapFromOpenApiPage(apiBids.page), + result: apiBids.result.map(mapBidFromOpenApiOrder), + }; + } + /** * List trades. This method is used to get a list of trades filtered by conditions specified * in the params object @@ -210,7 +245,7 @@ export class Orderbook { listingParams[0].sell, listingParams[0].buy, listingParams[0].sell.type === 'ERC1155', - new Date(), + listingParams[0].orderStart || new Date(), listingParams[0].orderExpiry || Orderbook.defaultOrderExpiry(), ); @@ -248,7 +283,7 @@ export class Orderbook { listing.sell, listing.buy, listing.sell.type === 'ERC1155', - new Date(), + listing.orderStart || new Date(), listing.orderExpiry || Orderbook.defaultOrderExpiry(), ))); @@ -309,12 +344,12 @@ export class Orderbook { track('orderbookmr', 'bulkListings', { walletType: 'EOA', makerAddress, listingsCount: listingParams.length }); const { actions, preparedOrders } = await this.seaport.prepareBulkSeaportOrders( makerAddress, - listingParams.map((orderParam) => ({ - offerItem: orderParam.sell, - considerationItem: orderParam.buy, - allowPartialFills: orderParam.sell.type === 'ERC1155', - orderStart: new Date(), - orderExpiry: orderParam.orderExpiry || Orderbook.defaultOrderExpiry(), + listingParams.map((listing) => ({ + offerItem: listing.sell, + considerationItem: listing.buy, + allowPartialFills: listing.sell.type === 'ERC1155', + orderStart: listing.orderStart || new Date(), + orderExpiry: listing.orderExpiry || Orderbook.defaultOrderExpiry(), })), ); @@ -373,6 +408,7 @@ export class Orderbook { makerAddress, sell, buy, + orderStart, orderExpiry, }: PrepareListingParams): Promise { return this.seaport.prepareSeaportOrder( @@ -381,7 +417,7 @@ export class Orderbook { buy, sell.type === 'ERC1155', // Default order start to now - new Date(), + orderStart || new Date(), // Default order expiry to 2 years from now orderExpiry || Orderbook.defaultOrderExpiry(), ); @@ -404,6 +440,50 @@ export class Orderbook { }; } + /** + * Get required transactions and messages for signing prior to creating a bid + * through the {@linkcode createBid} method + * @param {PrepareBidParams} prepareBidParams - Details about the bid to be created. + * @return {PrepareBidResponse} PrepareBidResponse includes + * the unsigned approval transaction, the typed order message for signing and + * the order components that can be submitted to {@linkcode createBid} with a signature. + */ + async prepareBid({ + makerAddress, + sell, + buy, + orderStart, + orderExpiry, + }: PrepareBidParams): Promise { + return this.seaport.prepareSeaportOrder( + makerAddress, + sell, + buy, + buy.type === 'ERC1155', + // Default order start to now + orderStart || new Date(), + // Default order expiry to 2 years from now + orderExpiry || Orderbook.defaultOrderExpiry(), + ); + } + + /** + * Create a bid + * @param {CreateBidParams} createBidParams - create a bid with the given params. + * @return {BidResult} The result of the bid created in the Immutable services. + */ + async createBid( + createBidParams: CreateBidParams, + ): Promise { + const apiBidResponse = await this.apiClient.createBid({ + ...createBidParams, + }); + + return { + result: mapBidFromOpenApiOrder(apiBidResponse.result), + }; + } + /** * Get unsigned transactions that can be submitted to fulfil an open order. If the approval * transaction exists it must be signed and submitted to the chain before the fulfilment @@ -594,7 +674,8 @@ export class Orderbook { * to get the signature required for this call. * @param {string[]} orderIds - The orderIds to attempt to cancel. * @param {string} accountAddress - The address of the account cancelling the orders. - * @param {string} accountAddress - The address of the account cancelling the orders. + * @param {string} signature - The signature obtained by signing the + * message obtained from {@linkcode prepareOrderCancellations}. * @return {CancelOrdersResult} The result of the off-chain cancellation request */ async cancelOrders( @@ -622,28 +703,42 @@ export class Orderbook { orderIds: string[], accountAddress: string, ): Promise { - const orderResults = await Promise.all(orderIds.map((id) => this.apiClient.getListing(id))); + const listingResultsPromises = Promise.all( + orderIds.map((id) => this.apiClient.getListing(id).catch((e: ApiError) => { + if (e.status === 404) { + return undefined; + } + throw e; + })), + ); - // eslint-disable-next-line no-restricted-syntax - for (const orderResult of orderResults) { - if ( - orderResult.result.status.name !== OrderStatusName.ACTIVE - && orderResult.result.status.name !== OrderStatusName.INACTIVE - && orderResult.result.status.name !== OrderStatusName.PENDING - ) { - throw new Error( - `Cannot cancel order with status ${orderResult.result.status}`, - ); - } + const bidResultsPromises = Promise.all( + orderIds.map((id) => this.apiClient.getBid(id).catch((e: ApiError) => { + if (e.status === 404) { + return undefined; + } + throw e; + })), + ); + + const orders = [ + await Promise.all([listingResultsPromises, bidResultsPromises]), + ].flat(2).filter((r) => r !== undefined).map((r) => r.result); - if (orderResult.result.account_address !== accountAddress.toLowerCase()) { + if (orders.length !== orderIds.length) { + const notFoundOrderIds = orderIds.filter((oi) => !orders.some((o) => o.id === oi)); + throw new Error(`Orders ${notFoundOrderIds} not found`); + } + + // eslint-disable-next-line no-restricted-syntax + for (const order of orders) { + if (order.account_address !== accountAddress.toLowerCase()) { throw new Error( - `Only account ${orderResult.result.account_address} can cancel order ${orderResult.result.id}`, + `Only account ${order.account_address} can cancel order ${order.id}`, ); } } - const orders = orderResults.map((orderResult) => orderResult.result); const seaportAddresses = orders.map((o) => o.protocol_data.seaport_address); const distinctSeaportAddresses = new Set(...[seaportAddresses]); if (distinctSeaportAddresses.size !== 1) { diff --git a/packages/orderbook/src/seaport/components.ts b/packages/orderbook/src/seaport/components.ts index c6ea833c7d..98605f2074 100644 --- a/packages/orderbook/src/seaport/components.ts +++ b/packages/orderbook/src/seaport/components.ts @@ -1,33 +1,110 @@ -import type { OrderComponents } from '@opensea/seaport-js/lib/types'; +import { ItemType, OrderType } from '@opensea/seaport-js/lib/constants'; +import type { + ConsiderationItem, + OfferItem, + OrderComponents, +} from '@opensea/seaport-js/lib/types'; import { BigNumber } from 'ethers'; import { getBulkOrderTree } from './lib/bulk-orders'; -export function getOrderComponentsFromMessage(orderMessage: string): OrderComponents { - const data = JSON.parse(orderMessage); - const orderComponents: OrderComponents = data.message; +function orderTypeStringToEnum(orderTypeString: string): OrderType { + if ( + [ + OrderType.FULL_OPEN, + OrderType.PARTIAL_OPEN, + OrderType.FULL_RESTRICTED, + OrderType.PARTIAL_RESTRICTED, + ].includes(Number(orderTypeString)) + ) { + return Number(orderTypeString); + } + + throw new Error(`Unknown order type ${orderTypeString}`); +} - orderComponents.salt = BigNumber.from(orderComponents.salt).toHexString(); +function itemTypeStringToEnum(itemTypeString: string): ItemType { + if ( + [ + ItemType.NATIVE, + ItemType.ERC20, + ItemType.ERC721, + ItemType.ERC1155, + ItemType.ERC721_WITH_CRITERIA, + ItemType.ERC1155_WITH_CRITERIA, + ].includes(Number(itemTypeString)) + ) { + return Number(itemTypeString); + } + + throw new Error(`Unknown item type ${itemTypeString}`); +} - return orderComponents; +interface OrderComponentsMessage + extends Omit { + orderType: string; + offer: (Omit & { itemType: string })[]; + consideration: (Omit & { itemType: string })[]; +} + +export function getOrderComponentsFromMessage( + orderMessage: string, +): OrderComponents { + const data = JSON.parse(orderMessage); + const message = data.message as OrderComponentsMessage; + + return { + ...message, + orderType: orderTypeStringToEnum(message.orderType), + salt: BigNumber.from(message.salt).toHexString(), + offer: message.offer.map( + (i): OfferItem => ({ + ...i, + itemType: itemTypeStringToEnum(i.itemType), + }), + ), + consideration: message.consideration.map( + (i): ConsiderationItem => ({ + ...i, + itemType: itemTypeStringToEnum(i.itemType), + }), + ), + }; } export function getBulkOrderComponentsFromMessage(orderMessage: string): { - components: OrderComponents[], - types: any, - value: any + components: OrderComponents[]; + types: any; + value: any; } { const data = JSON.parse(orderMessage); - const orderComponents: OrderComponents[] = data.message.tree.flat(Infinity) + const orderComponents: OrderComponents[] = (data.message.tree as OrderComponentsMessage[]) + .flat(Infinity) // Filter off the zero nodes in the tree. The will get rebuilt bu `getBulkOrderTree` // when creating the listings - .filter((o: OrderComponents) => o.offerer !== '0x0000000000000000000000000000000000000000'); - - // eslint-disable-next-line no-restricted-syntax - for (const orderComponent of orderComponents) { - orderComponent.salt = BigNumber.from(orderComponent.salt).toHexString(); - } + .filter((o) => o.offerer !== '0x0000000000000000000000000000000000000000') + .map((orderComponentMessage): OrderComponents => ({ + ...orderComponentMessage, + orderType: orderTypeStringToEnum(orderComponentMessage.orderType), + salt: BigNumber.from(orderComponentMessage.salt).toHexString(), + offer: orderComponentMessage.offer.map( + (i): OfferItem => ({ + ...i, + itemType: itemTypeStringToEnum(i.itemType), + }), + ), + consideration: orderComponentMessage.consideration.map( + (i): ConsiderationItem => ({ + ...i, + itemType: itemTypeStringToEnum(i.itemType), + }), + ), + })); - return { components: orderComponents, types: data.types, value: data.message }; + return { + components: orderComponents, + types: data.types, + value: data.message, + }; } export function getBulkSeaportOrderSignatures( diff --git a/packages/orderbook/src/seaport/map-to-immutable-order.ts b/packages/orderbook/src/seaport/map-to-immutable-order.ts new file mode 100644 index 0000000000..2651686400 --- /dev/null +++ b/packages/orderbook/src/seaport/map-to-immutable-order.ts @@ -0,0 +1,61 @@ +import { ConsiderationItem, OfferItem } from '@opensea/seaport-js/lib/types'; +import { ItemType, OrderType } from '@opensea/seaport-js/lib/constants'; +import { Item, ProtocolData } from '../openapi/sdk'; +import { exhaustiveSwitch } from '../utils'; + +export function mapSeaportItemToImmutableItem(item: OfferItem | ConsiderationItem): Item { + switch (item.itemType) { + case ItemType.NATIVE: + return { + type: 'NATIVE', + amount: item.startAmount, + }; + case ItemType.ERC20: + return { + type: 'ERC20', + contract_address: item.token, + amount: item.startAmount, + }; + case ItemType.ERC721: + return { + type: 'ERC721', + contract_address: item.token, + token_id: item.identifierOrCriteria, + }; + case ItemType.ERC1155: + return { + type: 'ERC1155', + contract_address: item.token, + token_id: item.identifierOrCriteria, + amount: item.startAmount, + }; + case ItemType.ERC721_WITH_CRITERIA: + return { + type: 'ERC721_COLLECTION', + contract_address: item.token, + amount: item.startAmount, + }; + case ItemType.ERC1155_WITH_CRITERIA: + return { + type: 'ERC1155_COLLECTION', + contract_address: item.token, + amount: item.startAmount, + }; + default: + return exhaustiveSwitch(item.itemType); + } +} + +export function mapSeaportOrderTypeToImmutableProtocolDataOrderType(ot: OrderType) { + switch (ot) { + case OrderType.FULL_RESTRICTED: + return ProtocolData.order_type.FULL_RESTRICTED; + case OrderType.PARTIAL_RESTRICTED: + return ProtocolData.order_type.PARTIAL_RESTRICTED; + case OrderType.FULL_OPEN: + case OrderType.PARTIAL_OPEN: + throw new Error(`Unsupported order type ${ot}`); + default: + return exhaustiveSwitch(ot); + } +} diff --git a/packages/orderbook/src/seaport/seaport.test.ts b/packages/orderbook/src/seaport/seaport.test.ts index e7d049ae6a..5c32841681 100644 --- a/packages/orderbook/src/seaport/seaport.test.ts +++ b/packages/orderbook/src/seaport/seaport.test.ts @@ -2,15 +2,18 @@ import { anything, deepEqual, instance, mock, when, } from 'ts-mockito'; import type { TransactionMethods } from '@opensea/seaport-js/lib/utils/usecase'; -import { ContractTransaction } from 'ethers-v6'; +import { ContractTransaction, ZeroHash, ZeroAddress } from 'ethers-v6'; import { Seaport as SeaportLib } from '@opensea/seaport-js'; import type { ApprovalAction, + ConsiderationItem, CreateOrderAction, ExchangeAction, + OfferItem, OrderComponents, } from '@opensea/seaport-js/lib/types'; import { BigNumber, providers } from 'ethers'; +import { OrderType } from '@opensea/seaport-js/lib/constants'; import { ActionType, TransactionAction, @@ -43,12 +46,6 @@ describe('Seaport', () => { let sut: Seaport; const network = 1; - const orderComponents = { salt: '123' } as OrderComponents; - // Salt is encoded as hex from original order components, so use this - // to compare against the expected order components - const orderComponentsWithHexSalt = { - salt: BigNumber.from(orderComponents.salt).toHexString(), - }; const zoneAddress = randomAddress(); const seaportContractAddress = randomAddress(); @@ -69,6 +66,66 @@ describe('Seaport', () => { const orderExpiry = new Date(); const orderHash = randomAddress(); + const orderComponents: OrderComponents = { + orderType: OrderType.FULL_RESTRICTED, + offerer, + offer: [ + { + itemType: ItemType.ERC721, + token: listingItem.contractAddress, + identifierOrCriteria: listingItem.tokenId, + startAmount: '1', + endAmount: '1', + }, + ], + consideration: [ + { + itemType: ItemType.ERC20, + token: considerationItem.contractAddress, + identifierOrCriteria: '0', + startAmount: considerationItem.amount, + endAmount: considerationItem.amount, + recipient: offerer, + }, + ], + startTime: (orderStart.getTime() / 1000).toFixed(0), + endTime: (orderExpiry.getTime() / 1000).toFixed(0), + salt: BigNumber.from('123').toHexString(), + counter: 0, + zone: zoneAddress, + zoneHash: ZeroHash, + conduitKey: ZeroHash, + totalOriginalConsiderationItems: 1, + }; + + const orderComponentsMessage: Omit & { + orderType: string; + offer: (Omit & { itemType: string })[]; + consideration: (Omit & { itemType: string })[]; + } = { + ...orderComponents, + orderType: OrderType.FULL_RESTRICTED.toString(), + offer: [ + { + itemType: ItemType.ERC721.toString(), + token: listingItem.contractAddress, + identifierOrCriteria: listingItem.tokenId, + startAmount: '1', + endAmount: '1', + }, + ], + consideration: [ + { + itemType: ItemType.ERC20.toString(), + token: considerationItem.contractAddress, + identifierOrCriteria: '0', + startAmount: considerationItem.amount, + endAmount: considerationItem.amount, + recipient: offerer, + }, + ], + }; + beforeEach(() => { const mockedSeaportJs = mock(SeaportLib); const mockedSeaportLibFactory = mock(SeaportLibFactory); @@ -85,10 +142,10 @@ describe('Seaport', () => { instance(mockedSeaportJs), ); when( - mockedSeaportJs.getOrderHash(deepEqual(orderComponentsWithHexSalt as OrderComponents)), + mockedSeaportJs.getOrderHash(deepEqual(orderComponents)), ).thenReturn(orderHash); when(createAction.getMessageToSign()).thenReturn( - Promise.resolve(JSON.stringify({ message: orderComponents })), + Promise.resolve(JSON.stringify({ message: orderComponentsMessage })), ); when( mockedSeaportJs.createOrder( @@ -167,7 +224,7 @@ describe('Seaport', () => { expect(signableAction.message).toEqual({ domain: domainData, types: EIP_712_ORDER_TYPE, - value: orderComponentsWithHexSalt, + value: orderComponents, }); }); @@ -180,7 +237,7 @@ describe('Seaport', () => { orderStart, orderExpiry, ); - expect(orderComponentsRes).toEqual(orderComponentsWithHexSalt); + expect(orderComponentsRes).toEqual(orderComponents); }); it('returns the expected order hash', async () => { @@ -200,12 +257,6 @@ describe('Seaport', () => { let sut: Seaport; const network = 1; - const orderComponents = { salt: '123' } as OrderComponents; - // Salt is encoded as hex from original order components, so use this - // to compare against the expected order components - const orderComponentsWithHexSalt = { - salt: BigNumber.from(orderComponents.salt).toHexString(), - }; const zoneAddress = randomAddress(); const seaportContractAddress = randomAddress(); @@ -224,6 +275,67 @@ describe('Seaport', () => { const orderStart = new Date(); const orderExpiry = new Date(); const orderHash = randomAddress(); + + const orderComponents: OrderComponents = { + orderType: OrderType.FULL_RESTRICTED, + offerer, + offer: [ + { + itemType: ItemType.ERC721, + token: listingItem.contractAddress, + identifierOrCriteria: listingItem.tokenId, + startAmount: '1', + endAmount: '1', + }, + ], + consideration: [ + { + itemType: ItemType.NATIVE, + token: ZeroAddress, + identifierOrCriteria: '0', + startAmount: considerationItem.amount, + endAmount: considerationItem.amount, + recipient: offerer, + }, + ], + startTime: (orderStart.getTime() / 1000).toFixed(0), + endTime: (orderExpiry.getTime() / 1000).toFixed(0), + salt: BigNumber.from('123').toHexString(), + counter: 0, + zone: zoneAddress, + zoneHash: ZeroHash, + conduitKey: ZeroHash, + totalOriginalConsiderationItems: 1, + }; + + const orderComponentsMessage: Omit & { + orderType: string; + offer: (Omit & { itemType: string })[]; + consideration: (Omit & { itemType: string })[]; + } = { + ...orderComponents, + orderType: OrderType.FULL_RESTRICTED.toString(), + offer: [ + { + itemType: ItemType.ERC721.toString(), + token: listingItem.contractAddress, + identifierOrCriteria: listingItem.tokenId, + startAmount: '1', + endAmount: '1', + }, + ], + consideration: [ + { + itemType: ItemType.NATIVE.toString(), + token: ZeroAddress, + identifierOrCriteria: '0', + startAmount: considerationItem.amount, + endAmount: considerationItem.amount, + recipient: offerer, + }, + ], + }; + const approvalGas = BigInt(1000000); const approvalTransaction: ContractTransaction = { from: offerer, @@ -240,7 +352,7 @@ describe('Seaport', () => { const createActionInstance = instance(createAction); createActionInstance.type = 'create'; when(createAction.getMessageToSign()).thenReturn( - Promise.resolve(JSON.stringify({ message: orderComponents })), + Promise.resolve(JSON.stringify({ message: orderComponentsMessage })), ); const transactionMethods = mock>(); @@ -260,7 +372,7 @@ describe('Seaport', () => { instance(mockedSeaportJs), ); when( - mockedSeaportJs.getOrderHash(deepEqual(orderComponentsWithHexSalt as OrderComponents)), + mockedSeaportJs.getOrderHash(deepEqual(orderComponents)), ).thenReturn(orderHash); when( mockedSeaportJs.createOrder( @@ -346,7 +458,7 @@ describe('Seaport', () => { expect(signableAction.message).toEqual({ domain: domainData, types: EIP_712_ORDER_TYPE, - value: orderComponentsWithHexSalt, + value: orderComponents, }); }); @@ -359,7 +471,7 @@ describe('Seaport', () => { orderStart, orderExpiry, ); - expect(orderComponentsRes).toEqual(orderComponentsWithHexSalt); + expect(orderComponentsRes).toEqual(orderComponents); }); it('returns the expected order hash', async () => { diff --git a/packages/orderbook/src/seaport/seaport.ts b/packages/orderbook/src/seaport/seaport.ts index c7f065a02e..1f68c40b21 100644 --- a/packages/orderbook/src/seaport/seaport.ts +++ b/packages/orderbook/src/seaport/seaport.ts @@ -268,6 +268,7 @@ export class Seaport { ): Promise { const { orderComponents, tips } = mapImmutableOrderToSeaportOrderComponents(order); const seaportLib = this.getSeaportLib(order); + const chainID = (await this.provider.getNetwork()).chainId; const { actions: seaportActions } = await seaportLib.fulfillOrders({ accountAddress: account, @@ -286,19 +287,21 @@ export class Seaport { const fulfillmentActions: TransactionAction[] = []; - const approvalAction = seaportActions.find( + const approvalActions = seaportActions.filter( (action) => action.type === 'approval', ); - if (approvalAction) { - fulfillmentActions.push({ - type: ActionType.TRANSACTION, - buildTransaction: prepareTransaction( - approvalAction.transactionMethods, - (await this.provider.getNetwork()).chainId, - account, - ), - purpose: TransactionPurpose.APPROVAL, + if (approvalActions.length > 0) { + approvalActions.forEach((approvalAction) => { + fulfillmentActions.push({ + type: ActionType.TRANSACTION, + buildTransaction: prepareTransaction( + approvalAction.transactionMethods, + chainID, + account, + ), + purpose: TransactionPurpose.APPROVAL, + }); }); } @@ -314,7 +317,7 @@ export class Seaport { type: ActionType.TRANSACTION, buildTransaction: prepareTransaction( fulfilOrderAction.transactionMethods, - (await this.provider.getNetwork()).chainId, + chainID, account, ), purpose: TransactionPurpose.FULFILL_ORDER, diff --git a/packages/orderbook/src/types.ts b/packages/orderbook/src/types.ts index c0e082e9ca..f6a9035ebc 100644 --- a/packages/orderbook/src/types.ts +++ b/packages/orderbook/src/types.ts @@ -188,6 +188,7 @@ export interface PrepareListingParams { makerAddress: string; sell: ERC721Item | ERC1155Item; buy: NativeItem | ERC20Item; + orderStart?: Date; orderExpiry?: Date; } @@ -199,6 +200,7 @@ export interface PrepareBulkListingsParams { sell: ERC721Item | ERC1155Item; buy: NativeItem | ERC20Item; makerFees: FeeValue[]; + orderStart?: Date; orderExpiry?: Date; }[]; } @@ -228,6 +230,49 @@ export interface BulkListingsResult { }[]; } +/* Bid Ops */ + +// Expose the list order filtering and ordering directly from the openAPI SDK, except +// chainName is omitted as its configured as a part of the client +export type ListBidsParams = Omit< +Parameters[0], +'chainName' +>; + +export interface BidResult { + result: Bid; +} + +export interface ListBidsResult { + page: Page; + result: Bid[]; +} + +export interface PrepareBidParams { + makerAddress: string; + sell: ERC20Item; + buy: ERC721Item | ERC1155Item; + orderStart?: Date; + orderExpiry?: Date; +} + +export type PrepareBidResponse = PrepareOrderResponse; + +export interface CreateBidParams { + orderComponents: OrderComponents; + orderHash: string; + orderSignature: string; + makerFees: FeeValue[]; +} + +export interface BulkBidsResult { + result: { + success: boolean; + orderHash: string; + order?: Bid; + }[]; +} + /* Fulfilment Ops */ export interface FulfillmentOrder { diff --git a/tests/func-tests/zkevm/README.md b/tests/func-tests/zkevm/README.md index b3e59ae823..b3ba224d12 100644 --- a/tests/func-tests/zkevm/README.md +++ b/tests/func-tests/zkevm/README.md @@ -13,16 +13,38 @@ Functional tests using Cucumber and Gherkin ### Required ENV values ``` -ZKEVM_ORDERBOOK_BANKER=0x // banker private key used to fund accounts for listings and trades -ZKEVM_ORDERBOOK_ERC721=0x // Address of the ERC721 contract that the bank can mint (can be redeployed with `npx ts-node utils/orderbook/deploy-erc721.ts`) -ZKEVM_ORDERBOOK_ERC1155=0x // Address of the ERC1155 contract that the bank can mint (can be redeployed with `npx ts-node utils/orderbook/deploy-erc1155.ts`) -SEAPORT_CONTRACT_ADDRESS=0x -ZONE_CONTRACT_ADDRESS=0x - -// The following are devnet values, if running against testnet need to modify -ZKEVM_RPC_ENDPOINT=https://rpc.dev.immutable.com -ORDERBOOK_MR_API_URL=https://order-book-mr.dev.imtbl.com -ZKEVM_CHAIN_NAME=imtbl-zkevm-devnet +# Example +# ZKEVM_RPC_ENDPOINT= // Chain RPC endpoint +# ORDERBOOK_MR_API_URL= // Immutable zkEVM API endpoint +# ZKEVM_CHAIN_NAME= // Immutable zkEVM chain name +# ZKEVM_ORDERBOOK_BANKER= // Banker private key used to fund accounts +# ZKEVM_ORDERBOOK_ERC20= // Address of the ERC20 contract that the banker can mint from (can be redeployed with `yarn ts-node utils/orderbook/deploy-erc20.ts`) +# ZKEVM_ORDERBOOK_ERC721= // Address of the ERC721 contract that the banker can mint from (can be redeployed with `yarn ts-node utils/orderbook/deploy-erc721.ts`) +# ZKEVM_ORDERBOOK_ERC1155= // Address of the ERC1155 contract that the banker can mint from (can be redeployed with `yarn ts-node utils/orderbook/deploy-erc1155.ts`) +# SEAPORT_CONTRACT_ADDRESS= // Seaport contract +# ZONE_CONTRACT_ADDRESS= // Seaport zone contract + +# Devnet +# ZKEVM_RPC_ENDPOINT=https://rpc.dev.immutable.com +# ORDERBOOK_MR_API_URL=https://api.dev.immutable.com +# ZKEVM_CHAIN_NAME=imtbl-zkevm-devnet +# ZKEVM_ORDERBOOK_BANKER= +# ZKEVM_ORDERBOOK_ERC20=0x37ad85c4c4091a05c4cf70D4c07AA13085bfc465 +# ZKEVM_ORDERBOOK_ERC721=0xb13469072875a98dF256D3A309cF06e09c020294 +# ZKEVM_ORDERBOOK_ERC1155=0xD984dB6E4Ed6539323BcF9A91aE0C4326d89e6D5 +# SEAPORT_CONTRACT_ADDRESS=0xbA22c310787e9a3D74343B17AB0Ab946c28DFB52 +# ZONE_CONTRACT_ADDRESS=0xb71EB38e6B51Ee7A45A632d46f17062e249580bE + +# Testnet +ZKEVM_RPC_ENDPOINT=https://rpc.testnet.immutable.com +ORDERBOOK_MR_API_URL=https://api.sandbox.immutable.com +ZKEVM_CHAIN_NAME=imtbl-zkevm-testnet +ZKEVM_ORDERBOOK_BANKER= +ZKEVM_ORDERBOOK_ERC20=0x70dCEF6C22F50497eafc77D252E8E175af21bF75 +ZKEVM_ORDERBOOK_ERC721=0xBE8B131f39825282Ace9eFf99C0Bb14972417b49 +ZKEVM_ORDERBOOK_ERC1155=0x2efB9B7810B1d1520c0822aa20F1889ABd2c2146 +SEAPORT_CONTRACT_ADDRESS=0x7d117aA8BD6D31c4fa91722f246388f38ab1942c +ZONE_CONTRACT_ADDRESS=0x1004f9615E79462c711Ff05a386BdbA91a7628C3 ``` ## Running the tests diff --git a/tests/func-tests/zkevm/contracts/TestERC20Token.sol b/tests/func-tests/zkevm/contracts/TestERC20Token.sol new file mode 100644 index 0000000000..cf27d75816 --- /dev/null +++ b/tests/func-tests/zkevm/contracts/TestERC20Token.sol @@ -0,0 +1,35 @@ +pragma solidity ^0.8.0; + +import "@imtbl/contracts/contracts/token/erc20/preset/ImmutableERC20MinterBurnerPermit.sol"; + +contract TestERC20Token is ImmutableERC20MinterBurnerPermit { + constructor( + address owner, + string memory name, + string memory symbol, + uint256 maxTokenSupply + ) ImmutableERC20MinterBurnerPermit( + owner, + owner, + owner, + name, + symbol, + maxTokenSupply + ) + { + } + + function mintBatch(address[] memory to, uint256[] memory values) external { + require(to.length == values.length, "Arrays must have the same length"); + for (uint256 i = 0; i < to.length; i++) { + _mint(to[i], values[i]); + } + } + + function transferBatch(address[] memory to, uint256[] memory values) external { + require(to.length == values.length, "Arrays must have the same length"); + for (uint256 i = 0; i < to.length; i++) { + _transfer(msg.sender, to[i], values[i]); + } + } +} diff --git a/tests/func-tests/zkevm/jest.config.ts b/tests/func-tests/zkevm/jest.config.ts index ad8f252b50..9330dcbc4a 100644 --- a/tests/func-tests/zkevm/jest.config.ts +++ b/tests/func-tests/zkevm/jest.config.ts @@ -7,8 +7,8 @@ import type {Config} from 'jest'; const config: Config = { rootDir: ".", - testMatch:["**/*.steps.ts"], - testTimeout: 60000, + testMatch:["**/*.steps.ts", "**/*.spec.ts"], + testTimeout: 120000, roots: ["step-definitions"], moduleDirectories: ["node_modules", ""], moduleNameMapper: { diff --git a/tests/func-tests/zkevm/specs/orderbook.spec.ts b/tests/func-tests/zkevm/specs/orderbook.spec.ts new file mode 100644 index 0000000000..cb08bfc68e --- /dev/null +++ b/tests/func-tests/zkevm/specs/orderbook.spec.ts @@ -0,0 +1,477 @@ +import { orderbook } from "@imtbl/sdk"; +import { Environment } from "@imtbl/sdk/config"; +import { BigNumber, Wallet } from "ethers"; +import { withBankerRetry } from "../step-definitions/shared"; +import { + TestERC1155Token, + TestERC20Token, + TestERC721Token, +} from "../typechain-types"; +import { + connectToTestERC1155Token, + connectToTestERC20Token, + connectToTestERC721Token, + getConfigFromEnv, + getRandomTokenId, +} from "../utils/orderbook"; +import { actionAll } from "../utils/orderbook/actions"; +import { waitForBidToBeOfStatus } from "../utils/orderbook/bid"; +import { GAS_OVERRIDES } from "../utils/orderbook/gas"; +import { waitForListingToBeOfStatus } from "../utils/orderbook/listing"; +import { RetryProvider } from "../utils/orderbook/retry-provider"; + +describe.skip("Orderbook", () => { + const imxForApproval = 0.03 * 1e18; + const imxForFulfillment = 0.08 * 1e18; + const transferTxnFee = 0.0035 * 1e18; + + let orderBookSdk: orderbook.Orderbook; + let provider: RetryProvider; + + let erc20Contract: TestERC20Token; + let erc721Contract: TestERC721Token; + let erc1155Contract: TestERC1155Token; + + let banker: Wallet; + let maker: Wallet; + let taker: Wallet; + + beforeAll(async () => { + const rpcUrl = process.env.ZKEVM_RPC_ENDPOINT; + const bankerPrivateKey = process.env.ZKEVM_ORDERBOOK_BANKER; + const erc20ContractAddress = process.env.ZKEVM_ORDERBOOK_ERC20; + const erc721ContractAddress = process.env.ZKEVM_ORDERBOOK_ERC721; + const erc1155ContractAddress = process.env.ZKEVM_ORDERBOOK_ERC1155; + + if ( + !rpcUrl || + !bankerPrivateKey || + !erc20ContractAddress || + !erc721ContractAddress || + !erc1155ContractAddress + ) { + throw new Error("missing config for orderbook tests"); + } + + provider = new RetryProvider(rpcUrl); + + banker = new Wallet(bankerPrivateKey, provider); + + erc20Contract = await connectToTestERC20Token(banker, erc20ContractAddress); + erc721Contract = await connectToTestERC721Token( + banker, + erc721ContractAddress + ); + erc1155Contract = await connectToTestERC1155Token( + banker, + erc1155ContractAddress + ); + + orderBookSdk = new orderbook.Orderbook({ + baseConfig: { + environment: Environment.SANDBOX, + }, + overrides: { + ...getConfigFromEnv(), + }, + }); + }); + + beforeEach(() => { + maker = new Wallet(Wallet.createRandom().privateKey, provider); + taker = new Wallet(Wallet.createRandom().privateKey, provider); + }); + + afterEach(async () => { + await withBankerRetry(async () => { + const makerBalance = await maker.getBalance(); + const takerBalance = await taker.getBalance(); + + if (makerBalance.gt(BigNumber.from(transferTxnFee))) { + // maker returns funds + await ( + await maker.sendTransaction({ + to: banker.address, + value: `${makerBalance + .sub(BigNumber.from(transferTxnFee)) + .toString()}`, + ...GAS_OVERRIDES, + }) + ).wait(1); + } + + if (takerBalance.gt(BigNumber.from(transferTxnFee))) { + // taker returns funds + await ( + await taker.sendTransaction({ + to: banker.address, + value: `${takerBalance + .sub(BigNumber.from(transferTxnFee)) + .toString()}`, + ...GAS_OVERRIDES, + }) + ).wait(1); + } + }); + }); + + it("create and fulfill ERC721 bid", async () => { + const erc721TokenId = getRandomTokenId(); + + // maker funds + await withBankerRetry(async () => { + await ( + await erc20Contract.mint(maker.address, 100, GAS_OVERRIDES) + ).wait(1); + }); + await withBankerRetry(async () => { + await ( + await banker.sendTransaction({ + to: maker.address, + value: `${imxForApproval}`, + ...GAS_OVERRIDES, + }) + ).wait(1); + }); + + // taker funds + await withBankerRetry(async () => { + await ( + await erc721Contract.mint(taker.address, erc721TokenId, GAS_OVERRIDES) + ).wait(1); + }); + await withBankerRetry(async () => { + await ( + await banker.sendTransaction({ + to: taker.address, + value: `${imxForApproval + imxForFulfillment}`, + ...GAS_OVERRIDES, + }) + ).wait(1); + }); + + const { + actions: bidCreateActions, + orderComponents, + orderHash, + } = await orderBookSdk.prepareBid({ + makerAddress: maker.address, + sell: { + type: "ERC20", + contractAddress: erc20Contract.address, + amount: "100", + }, + buy: { + type: "ERC721", + contractAddress: erc721Contract.address, + tokenId: erc721TokenId, + }, + orderStart: new Date(2000, 1, 15), + }); + + const signatures = await actionAll(bidCreateActions, maker); + + const { result } = await orderBookSdk.createBid({ + orderComponents, + orderHash, + orderSignature: signatures[0], + makerFees: [], + }); + + await waitForBidToBeOfStatus(orderBookSdk, result.id, { + name: orderbook.OrderStatusName.ACTIVE, + }); + + const { actions: fulfillActions } = await orderBookSdk.fulfillOrder( + result.id, + taker.address, + [] + ); + await actionAll(fulfillActions, taker); + + await waitForBidToBeOfStatus(orderBookSdk, result.id, { + name: orderbook.OrderStatusName.FILLED, + }); + }); + + describe("create and fulfill ERC1155 bid", () => { + let bidId: string; + + // create the bid + beforeEach(async () => { + const erc1155TokenId = getRandomTokenId(); + + // maker funds + await withBankerRetry(async () => { + await ( + await erc20Contract.mint(maker.address, 100, GAS_OVERRIDES) + ).wait(1); + }); + await withBankerRetry(async () => { + await ( + await banker.sendTransaction({ + to: maker.address, + value: `${imxForApproval}`, + ...GAS_OVERRIDES, + }) + ).wait(1); + }); + + // taker funds + await withBankerRetry(async () => { + await ( + await erc1155Contract.safeMint( + taker.address, + erc1155TokenId, + 50, + "0x", + GAS_OVERRIDES + ) + ).wait(1); + }); + await withBankerRetry(async () => { + await ( + await banker.sendTransaction({ + to: taker.address, + value: `${imxForApproval + imxForFulfillment}`, + ...GAS_OVERRIDES, + }) + ).wait(1); + }); + + const { + actions: bidCreateActions, + orderComponents, + orderHash, + } = await orderBookSdk.prepareBid({ + makerAddress: maker.address, + sell: { + type: "ERC20", + contractAddress: erc20Contract.address, + amount: "100", + }, + buy: { + type: "ERC1155", + contractAddress: erc1155Contract.address, + tokenId: erc1155TokenId, + amount: "50", + }, + }); + + const signatures = await actionAll(bidCreateActions, maker); + const { result } = await orderBookSdk.createBid({ + orderComponents, + orderHash, + orderSignature: signatures[0], + makerFees: [], + }); + + await waitForBidToBeOfStatus(orderBookSdk, result.id, { + name: orderbook.OrderStatusName.ACTIVE, + }); + + bidId = result.id; + }); + + it("fulfill fully without explicit fulfill amount", async () => { + const { actions: fulfillActions } = await orderBookSdk.fulfillOrder( + bidId, + taker.address, + [] + ); + await actionAll(fulfillActions, taker); + + await waitForBidToBeOfStatus(orderBookSdk, bidId, { + name: orderbook.OrderStatusName.FILLED, + }); + }); + + it("fulfill partially", async () => { + const { actions: fulfillActions } = await orderBookSdk.fulfillOrder( + bidId, + taker.address, + [], + "10" + ); + await actionAll(fulfillActions, taker); + + await waitForBidToBeOfStatus( + orderBookSdk, + bidId, + { + name: orderbook.OrderStatusName.ACTIVE, + }, + { + numerator: 10, + denominator: 50, + } + ); + }); + + it("fulfill partially, then fully without explicit fulfill amount", async () => { + const { actions: fulfillActionsA } = await orderBookSdk.fulfillOrder( + bidId, + taker.address, + [], + "10" + ); + await actionAll(fulfillActionsA, taker); + + await waitForBidToBeOfStatus( + orderBookSdk, + bidId, + { + name: orderbook.OrderStatusName.ACTIVE, + }, + { + numerator: 10, + denominator: 50, + } + ); + + const { actions: fulfillActionsB } = await orderBookSdk.fulfillOrder( + bidId, + taker.address, + [] + ); + await actionAll(fulfillActionsB, taker); + + await waitForBidToBeOfStatus(orderBookSdk, bidId, { + name: orderbook.OrderStatusName.FILLED, + }); + }); + }); + + it("create and cancel listing & bid", async () => { + const erc721TokenIdForListing = getRandomTokenId(); + const erc721TokenIdForBid = getRandomTokenId(); + + // maker funds + await withBankerRetry(async () => { + await ( + await erc20Contract.mint(maker.address, 100, GAS_OVERRIDES) + ).wait(1); + }); + await withBankerRetry(async () => { + await ( + await erc721Contract.mintBatch( + [ + { + to: maker.address, + tokenIds: [erc721TokenIdForListing, erc721TokenIdForBid], + }, + ], + GAS_OVERRIDES + ) + ).wait(1); + }); + await withBankerRetry(async () => { + await ( + await banker.sendTransaction({ + to: maker.address, + value: `${imxForApproval * 2}`, + ...GAS_OVERRIDES, + }) + ).wait(1); + }); + + // listing + const listingId = await (async () => { + const { + actions: listingCreateActions, + orderComponents, + orderHash, + } = await orderBookSdk.prepareListing({ + makerAddress: maker.address, + sell: { + type: "ERC721", + contractAddress: erc721Contract.address, + tokenId: erc721TokenIdForListing, + }, + buy: { + type: "ERC20", + contractAddress: erc20Contract.address, + amount: "100", + }, + }); + + const signatures = await actionAll(listingCreateActions, maker); + const { result } = await orderBookSdk.createListing({ + orderComponents, + orderHash, + orderSignature: signatures[0], + makerFees: [], + }); + + await waitForListingToBeOfStatus(orderBookSdk, result.id, { + name: orderbook.OrderStatusName.ACTIVE, + }); + + return result.id; + })(); + + // bid + const bidId = await (async () => { + const { + actions: bidCreateActions, + orderComponents, + orderHash, + } = await orderBookSdk.prepareBid({ + makerAddress: maker.address, + sell: { + type: "ERC20", + contractAddress: erc20Contract.address, + amount: "100", + }, + buy: { + type: "ERC721", + contractAddress: erc721Contract.address, + tokenId: erc721TokenIdForBid, + }, + }); + + const signatures = await actionAll(bidCreateActions, maker); + const { result } = await orderBookSdk.createBid({ + orderComponents, + orderHash, + orderSignature: signatures[0], + makerFees: [], + }); + + await waitForBidToBeOfStatus(orderBookSdk, result.id, { + name: orderbook.OrderStatusName.ACTIVE, + }); + + return result.id; + })(); + + // cancel listing & bid + const { signableAction } = await orderBookSdk.prepareOrderCancellations([ + listingId, + bidId, + ]); + const signatures = await actionAll([signableAction], maker); + const { result } = await orderBookSdk.cancelOrders( + [listingId, bidId], + maker.address, + signatures[0] + ); + + expect(result.successful_cancellations).toEqual( + expect.arrayContaining([listingId, bidId]) + ); + + await Promise.all([ + waitForListingToBeOfStatus(orderBookSdk, listingId, { + name: orderbook.OrderStatusName.CANCELLED, + cancellation_type: "OFF_CHAIN" as any, // Cancellation type enum is not exported + pending: false, + }), + waitForBidToBeOfStatus(orderBookSdk, bidId, { + name: orderbook.OrderStatusName.CANCELLED, + cancellation_type: "OFF_CHAIN" as any, // Cancellation type enum is not exported + pending: false, + }), + ]); + }); +}); diff --git a/tests/func-tests/zkevm/step-definitions/shared.ts b/tests/func-tests/zkevm/step-definitions/shared.ts index af12739852..f078e78a6e 100644 --- a/tests/func-tests/zkevm/step-definitions/shared.ts +++ b/tests/func-tests/zkevm/step-definitions/shared.ts @@ -18,7 +18,7 @@ const transferTxnFee = 0.0035 * 1e18; // Workaround to retry banker on-chain actions which can race with test runs on other PRs // eslint-disable-next-line consistent-return -async function withBankerRetry(func: () => Promise, attempt = 1): Promise { +export async function withBankerRetry(func: () => Promise, attempt = 1): Promise { try { await func(); } catch (e) { @@ -142,6 +142,7 @@ export const whenICreateAListing = ( type: 'NATIVE', }, sell: sellItem, + orderStart: new Date(2000, 1, 15), }); const signatures = await actionAll(listing.actions, offerer); @@ -190,6 +191,7 @@ export const whenICreateABulkListing = ( }, sell: sellItem, makerFees: [], + orderStart: new Date(2000, 1, 15), }); } diff --git a/tests/func-tests/zkevm/utils/orderbook/bid.ts b/tests/func-tests/zkevm/utils/orderbook/bid.ts new file mode 100644 index 0000000000..bd4ae0b7f4 --- /dev/null +++ b/tests/func-tests/zkevm/utils/orderbook/bid.ts @@ -0,0 +1,51 @@ +import { orderbook } from "@imtbl/sdk"; +import { reduceFraction } from "./math"; +import { statusIsEqual } from "./order"; +import { retry } from "./retry"; + +export async function waitForBidToBeOfStatus( + orderBookSdk: orderbook.Orderbook, + bidId: string, + status: orderbook.Order["status"], + fillStatus?: { numerator: number; denominator: number } +) { + if (status.name === orderbook.OrderStatusName.FILLED) { + fillStatus = fillStatus ?? { numerator: 1, denominator: 1 }; + } + + await retry(async () => { + const { result } = await orderBookSdk.getBid(bidId); + const equalStatus = statusIsEqual(result.status, status); + let equalFillStatus = true; + + const reducedLastCheckedFillStatus = reduceFraction( + Number(result.fillStatus.numerator), + Number(result.fillStatus.denominator) + ); + + if (fillStatus) { + const reducedFillStatus = reduceFraction( + fillStatus.numerator, + fillStatus.denominator + ); + + if ( + reducedFillStatus[0] !== reducedLastCheckedFillStatus[0] || + reducedFillStatus[1] !== reducedLastCheckedFillStatus[1] + ) { + equalFillStatus = false; + } + } + if (!equalStatus || !equalFillStatus) { + throw new Error( + `Bid ${bidId} is of status [${JSON.stringify({ + status: result.status, + fillStatus: { + numerator: reducedLastCheckedFillStatus[0], + denominator: reducedLastCheckedFillStatus[1], + }, + })}] not expected status [${JSON.stringify({ status, fillStatus })}]` + ); + } + }); +} diff --git a/tests/func-tests/zkevm/utils/orderbook/deploy-erc20.ts b/tests/func-tests/zkevm/utils/orderbook/deploy-erc20.ts new file mode 100644 index 0000000000..ca8f868e42 --- /dev/null +++ b/tests/func-tests/zkevm/utils/orderbook/deploy-erc20.ts @@ -0,0 +1,16 @@ +import { config } from 'dotenv'; +import { Wallet, providers } from 'ethers'; +import { deployERC20Token } from './erc20'; + +config(); + +const deployerKey = process.env.ZKEVM_ORDERBOOK_BANKER; +const rpcUrl = process.env.ZKEVM_RPC_ENDPOINT; + +if (!deployerKey || !rpcUrl) { + throw new Error('missing config'); +} + +const deployerWallet = new Wallet(deployerKey, new providers.JsonRpcProvider(rpcUrl)); + +deployERC20Token(deployerWallet); diff --git a/tests/func-tests/zkevm/utils/orderbook/erc20.ts b/tests/func-tests/zkevm/utils/orderbook/erc20.ts new file mode 100644 index 0000000000..8ff69d9c78 --- /dev/null +++ b/tests/func-tests/zkevm/utils/orderbook/erc20.ts @@ -0,0 +1,37 @@ +import { Wallet } from 'ethers'; +import hre from 'hardhat'; +import { TestERC20Token, TestERC20Token__factory } from '../../typechain-types'; +import { GAS_OVERRIDES } from './gas'; + +export async function connectToTestERC20Token(deployer: Wallet, tokenAddress: string): Promise { + const hreEthers = (hre as any).ethers; + const testTokenContractFactory = await hreEthers.getContractFactory("TestERC20Token") as TestERC20Token__factory; + return testTokenContractFactory.connect(deployer).attach(tokenAddress) as unknown as TestERC20Token +} + +/** + * Deploys the TestToken ERC20 contract to the hardhat network. + * + * @returns the TestToken contract + */ +export async function deployERC20Token(deployer: Wallet): Promise { + const hreEthers = (hre as any).ethers; + const deployerAddress = await deployer.getAddress(); + + const testTokenContractFactory = await hreEthers.getContractFactory("TestERC20Token") as TestERC20Token__factory; + const testTokenContract = await testTokenContractFactory.connect(deployer).deploy( + deployerAddress, + 'TestERC20', + 'TST', + '100000000000000000000000000000', + GAS_OVERRIDES + ); + + await testTokenContract.deployed(); + console.log(`Test ERC20 token contract deployed: ${testTokenContract.address}`) + + const minterRoleTx = await testTokenContract.grantMinterRole(deployerAddress, GAS_OVERRIDES); + await minterRoleTx.wait() + + console.log(`Minter role granted to ${deployerAddress}`) +} diff --git a/tests/func-tests/zkevm/utils/orderbook/index.ts b/tests/func-tests/zkevm/utils/orderbook/index.ts index eaabff70b5..4333a9ba31 100644 --- a/tests/func-tests/zkevm/utils/orderbook/index.ts +++ b/tests/func-tests/zkevm/utils/orderbook/index.ts @@ -1,6 +1,7 @@ export * from './config'; -export * from './erc721'; export * from './erc1155'; +export * from './erc20'; +export * from './erc721'; export * from './order'; export * from './sign-and-submit'; export * from './signers'; diff --git a/tests/func-tests/zkevm/utils/orderbook/listing.ts b/tests/func-tests/zkevm/utils/orderbook/listing.ts new file mode 100644 index 0000000000..f3cb517c24 --- /dev/null +++ b/tests/func-tests/zkevm/utils/orderbook/listing.ts @@ -0,0 +1,51 @@ +import { orderbook } from "@imtbl/sdk"; +import { reduceFraction } from "./math"; +import { statusIsEqual } from "./order"; +import { retry } from "./retry"; + +export async function waitForListingToBeOfStatus( + orderBookSdk: orderbook.Orderbook, + listingId: string, + status: orderbook.Order["status"], + fillStatus?: { numerator: number; denominator: number } +) { + if (status.name === orderbook.OrderStatusName.FILLED) { + fillStatus = fillStatus ?? { numerator: 1, denominator: 1 }; + } + + await retry(async () => { + const { result } = await orderBookSdk.getListing(listingId); + const equalStatus = statusIsEqual(result.status, status); + let equalFillStatus = true; + + const reducedLastCheckedFillStatus = reduceFraction( + Number(result.fillStatus.numerator), + Number(result.fillStatus.denominator) + ); + + if (fillStatus) { + const reducedFillStatus = reduceFraction( + fillStatus.numerator, + fillStatus.denominator + ); + + if ( + reducedFillStatus[0] !== reducedLastCheckedFillStatus[0] || + reducedFillStatus[1] !== reducedLastCheckedFillStatus[1] + ) { + equalFillStatus = false; + } + } + if (!equalStatus || !equalFillStatus) { + throw new Error( + `Listing ${listingId} is of status [${JSON.stringify({ + status: result.status, + fillStatus: { + numerator: reducedLastCheckedFillStatus[0], + denominator: reducedLastCheckedFillStatus[1], + }, + })}] not expected status [${JSON.stringify({ status, fillStatus })}]` + ); + } + }); +} diff --git a/tests/func-tests/zkevm/utils/orderbook/math.ts b/tests/func-tests/zkevm/utils/orderbook/math.ts new file mode 100644 index 0000000000..cb8edbeea4 --- /dev/null +++ b/tests/func-tests/zkevm/utils/orderbook/math.ts @@ -0,0 +1,11 @@ +export function reduceFraction( + numerator: number, + denominator: number +): [number, number] { + const gcdVal = gcd(numerator, denominator) + return [numerator / gcdVal, denominator / gcdVal] +} + +export function gcd(a: number, b: number): number { + return b ? gcd(b, a % b) : a +} diff --git a/tests/func-tests/zkevm/utils/orderbook/order.ts b/tests/func-tests/zkevm/utils/orderbook/order.ts index f364e9ddc1..68372c5a9d 100644 --- a/tests/func-tests/zkevm/utils/orderbook/order.ts +++ b/tests/func-tests/zkevm/utils/orderbook/order.ts @@ -1,6 +1,7 @@ import { orderbook } from '@imtbl/sdk'; import { Wallet } from 'ethers'; import { actionAll } from './actions'; +import { exhaustiveSwitch } from './switch'; const orderStatusMap = new Map([ ['pending', orderbook.OrderStatusName.PENDING], @@ -142,3 +143,35 @@ export async function getTrades( // eslint-disable-next-line @typescript-eslint/no-loop-func return trades.result.filter((t) => t.orderId === listingId); } + +export function statusIsEqual(a: orderbook.Order['status'], b: orderbook.Order['status']): boolean { + if (a.name != b.name) { + return false + } + + switch (a.name) { + case 'PENDING': + return ( + (b as orderbook.Order['status'] & { name: 'PENDING' }).evaluated == a.evaluated && + (b as orderbook.Order['status'] & { name: 'PENDING' }).started == a.started + ) + case 'ACTIVE': + return true + case 'INACTIVE': + return ( + (b as orderbook.Order['status'] & { name: 'INACTIVE' }).sufficient_approvals == a.sufficient_approvals && + (b as orderbook.Order['status'] & { name: 'INACTIVE' }).sufficient_balances == a.sufficient_balances + ) + case 'FILLED': + return true + case 'CANCELLED': + return ( + (b as orderbook.Order['status'] & { name: 'CANCELLED' }).cancellation_type == a.cancellation_type && + (b as orderbook.Order['status'] & { name: 'CANCELLED' }).pending == a.pending + ) + case 'EXPIRED': + return true + default: + exhaustiveSwitch(a) + } +} diff --git a/tests/func-tests/zkevm/utils/orderbook/retry.ts b/tests/func-tests/zkevm/utils/orderbook/retry.ts new file mode 100644 index 0000000000..0302d36f20 --- /dev/null +++ b/tests/func-tests/zkevm/utils/orderbook/retry.ts @@ -0,0 +1,21 @@ +export async function retry(fn: () => Promise, retryCount = 50, retryDelay = 1000): Promise { + return new Promise((resolve, reject) => { + let retries = 0; + + const attempt = async () => { + try { + await fn(); + resolve(); + } catch (error) { + retries += 1; + if (retries >= retryCount) { + reject(error); + } else { + setTimeout(attempt, retryDelay); + } + } + }; + + attempt(); + }); +} diff --git a/tests/func-tests/zkevm/utils/orderbook/switch.ts b/tests/func-tests/zkevm/utils/orderbook/switch.ts new file mode 100644 index 0000000000..e1b3f3aaac --- /dev/null +++ b/tests/func-tests/zkevm/utils/orderbook/switch.ts @@ -0,0 +1,3 @@ +export function exhaustiveSwitch(param: never): never { + throw new Error('Unreachable') +}