diff --git a/.eslintrc b/.eslintrc index dd819129f9..d0f3978f40 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,11 +7,13 @@ "node_modules/", "dist/", "**sample-app**/", - "**playground**/" + "**playground**/", + "*.cjs", + "tests/func-tests/" ], "parser": "@typescript-eslint/parser", "parserOptions": { - "project": "./tsconfig.json", + "project": "./tsconfig.base.json", "tsconfigRootDir": "." }, "rules": { diff --git a/.husky/pre-commit b/.husky/pre-commit index 5a182ef106..5571608bdd 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,7 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" +# prevent heap limit allocation errors +export NODE_OPTIONS="--max-old-space-size=4096" + yarn lint-staged diff --git a/build-dependents.js b/build-dependents.js index fb53b404a1..d0cf7cade7 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`; + const command = `nx run-many --target=d --projects=${currentProject} --parallel=5 --no-cloud`; console.log(`Running command: ${command}`); execSync(command, { stdio: 'inherit' }); diff --git a/dev.sh b/dev.sh index 44ad4a48d0..19f7726e37 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 +nx run $PACKAGE_NAME:d --parallel=5 --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 f7d57ec1b4..139facd03b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,15 +1,75 @@ -## Contribution Guidelines +

+ + + -Important information on how to create examples, pull them through to the docs site and ensure they are covered by tests in the CI CD pipeline. +

Immutable Code Examples

+

+ Immutable's code examples as used for the code snippets in the Immutable Docs +
+ Explore the docs » +
+

+

+
-## Example Scope +**Table of Contents** -When creating an example app it should contain only examples about one particular feature to prevent overloading the example. If there are multiple ways to use the feature then it is okay to include those in one sample app. +- [Introduction](#introduction) +- [Running examples locally](#running-examples-locally) +- [Contribution guidelines](#contribution-guidelines) +- [Adding examples](#adding-examples) +- [End to end testing](#end-to-end-testing) +- [Using code examples in the docs site](#using-code-examples-in-the-docs-site) +
-For example; +# Introduction -the app in `examples/passport/wallets-connect-with-nextjs` show how to connect passport in the nextjs framework. It contains a default route that has links to each of the examples. Inside there are three routes, one for each way to connect (EIP-1193, EtherJS and Wagmi). These are okay to be part of one sample app since they show how to use one feature but using 3 different libraries. +The example apps in this examples directory are compiled and tested as part of our CI CD pipeline. The goal of this is to ensure the examples found here are always able to run against the latest version of the Immutable Typescript SDK. + +Selected portions of code from these example apps are then displayed as code snippets in our docs site, which means our code snippets in the docs are inherently always accurate as well. How to include the code from here in the docs site is explained below. + +These example apps can also be used directly as a reference and run locally to test and develop with. + +# Running examples locally + +First you need to clone or fork the `ts-immutable-sdk` repo. The examples are a part of the root workspace and running yarn install from anywhere in the workspace will install the node modules required by the examples to the root `node_modules` directory. By default, the examples pull the latest sdk version from NPM instead of building the SDK locally. + +If you want to run the examples against your local build of the SDK instead, from the project root run; + +```bash +yarn prepare:examples +``` + +and it will build the SDK first then use that build instead of what is on NPM. This is what our CI CD pipeline does to ensure it's compiling and testing the examples against the latest changes rather than what's already on NPM. + +Take a look at the README.md file of the example you want to run e.g. the [Passport Connect Readme](/examples/passport/wallets-connect-with-nextjs/README.md) + +This should include the steps required to run the example. Generally you will need to; + +1. Copy the `.env.example` file to `.env` and populate it with the environment variables as per the readme. +1. Then `yarn install` and `yarn dev` + +And the app should be served on https://localhost:3000 unless otherwise stated in the example app. + +# Contribution guidelines + +This section explains some of the core concepts for how we want to structure the examples so they can be more easily digested in the docs and by our partners. + +The goal is to have easy to read, well commented examples that focus on showing how to use a singular feature without being overly long or complicated. They should not include overly opinionated framework implementations as it can make it unclear to the reader what is required and what is optional. They should be compilable and tested with e2e tests as much as is practical. + +In the long run we want these apps to run in a code blitz frame inside the docs site much like on the [Checkout Connect Widget](https://docs.immutable.com/products/zkEVM/checkout/widgets/connect#sample-code) docs page. So aim to create an example which can be runnable like this. + +We also want to eventually index these examples and serve them in a searchable and filterable page on the docs site, so feel free to add more examples here which are not necessarily used as code snippets in the docs site. + +## Examples scope + +When creating an example app it should contain only examples about one particular feature to prevent overloading the example. If there are multiple ways to use the feature then it is okay to include those in one example app. + +For example, the [Passport Connect with NextJS](/examples/passport/wallets-connect-with-nextjs) app shows how to connect passport in the NextJS framework. + +It contains a default route that has links to each of the examples. Inside there are three routes, one for each way to connect (EIP-1193, EtherJS and Wagmi). These are okay to be part of one example app since they show how to use one feature but using 3 different libraries. If you want to show a different feature e.g signing with passport, you should create a new example app. Even though it also requires passport to connect you should not add the signing example to the connect example app. @@ -44,20 +104,57 @@ examples │ └── ... ``` -If you need to create a new example follow the steps below; +# Adding examples -## Creating a New Example +Depending on the scope of the example you're trying to add, you may be able to add it to an existing example app, or you might have to create a new example app. If you're not sure, read the [Examples Scope](#examples-scope) section above to help you decide. -create the nextjs project +If you need to add a new example or add to an existing example, please follow the [Folder Structure](#folder-structure) guidelines above. -``` +## Add to an existing example app + +The process may differ depending on how the example app is setup, but the recommendations we have made around creating a new example should follow the same structure as we've implemented for the [[Passport Connect with NextJS](/examples/passport/wallets-connect-with-nextjs) and [Passport Signing with NextJS](/examples/passport/wallets-signing-with-nextjs) examples. + +So if you need to add a new example to an existing example app you should create a branch in `ts-immutable-sdk` to add your example to and follow these steps; + +### Setup example in existing app + +1. Create new route folder inside the example app and add the `page.tsx` file. +1. Add the new route you created to the app's home page +1. Add any packages you require +1. Add any environment variables your example requires to the `.env.example` file +1. Add instructions in the `README.md` file about how to populate the `.env` file and any other build instructions. + +### Add your example + +Once you've done this you can go ahead and write your example code in the `page.tsx` file. + +You can run the example locally to test your code by following the steps in the [Running examples locally](#running-examples-locally) section. + +You will also need to create e2e tests for your example as covered in the [End to end testing](#end-to-end-testing) section. + +Creating code snippets in the docs site using your example code is covered in the [Using code examples in the docs site](#using-code-examples-in-the-docs-site) section. + +If your code example is going to be used as code snippets in the docs site, before merging your branch to main in `ts-immutable-sdk` you should follow the steps in [Using code examples in the docs site](#using-code-examples-in-the-docs-site). These steps help you to test the code snippets in the docs site before you merge your PR which avoids double handling and multiple pull requests across both repos. + +## Creating a new example app + +The process may differ if you're using a different Javascript Framework, but this assumes you will be creating a new example app using NextJS. These steps which were implemented to create the [Passport Connect](/examples/passport/wallets-connect-with-nextjs) example. It is a good reference point in terms of route structure and page layouts. If you need to copy code from there to help with your layouts then please go ahead. + +### Setup example app + +If you want to make a new NextJS app from scratch this is the process; + +Create a branch in `ts-immutable-sdk` to add your example to. + +In the console navigate to the product directory where your example will live (or create one if it doesn't exist). Then use `yarn dlx` to create the app. +```bash cd examples/ yarn dlx create-next-app@latest ``` -use the default options -``` -✔ What is your project named? -with- e.g. wallets-with-nextjs +use the default options; +```bash +✔ What is your project named? @examples//-with- e.g. @examples/passport/connect-with-nextjs ✔ Would you like to use TypeScript? … Yes ✔ Would you like to use ESLint? … Yes ✔ Would you like to use Tailwind CSS? … Yes @@ -66,162 +163,85 @@ use the default options ✔ Would you like to customize the default import alias (@/*)? No ``` -install dependencies +Install `@imtbl/sdk` and any other dependencies your example needs e.g. -``` -yarn install -``` - -install `@imtbl/sdk` and any other dependencies your example needs e.g. - -``` +```bash yarn add @imtbl/sdk yarn add @ethersproject/providers@^5.7.2 ``` -create a `.env.example` file in the root of the example. This will be committed to git so don't fill in the values +Create a `.env.example` file in the root of the example. This will be committed to git so don't fill in the values -add a template for any environment variables you need to the `.env.example` file e.g. +Add a template for any environment variables you need to the `.env.example` file e.g. -``` +```js NEXT_PUBLIC_PUBLISHABLE_KEY= NEXT_PUBLIC_CLIENT_ID= ``` -copy the `.env.example` file to `.env` in the root of the example. The `.env` file should be automatically ignored by git. - -populate any API keys and secrets e.g. - -``` -NEXT_PUBLIC_PUBLISHABLE_KEY="ABC" -NEXT_PUBLIC_CLIENT_ID="XYZ" -``` +Copy the `.env.example` file to `.env` in the root of the example (the `.env` file should be automatically ignored by git). Then populate any API keys and secrets you need to use in your application into the `.env` file. -note: variables must be prefixed with `NEXT_PUBLIC_` to be piped into the browser env. +Note: variables must be prefixed with `NEXT_PUBLIC_` to be piped into the browser env. -Update the readme with any instructions required to run the app, and include what required env variables there are with any instructions on what to populate there. +Update the readme with any instructions required to run the app, and include what required env variables there are with any instructions on what to populate there e.g. -``` +```md ## Required Environment Variables - NEXT_PUBLIC_PUBLISHABLE_KEY // replace with your publishable API key from Hub - NEXT_PUBLIC_CLIENT_ID // replace with your client ID from Hub ``` -start the project with hot reloading +Delete the any unused imports in `app/page.tsx` -``` -yarn dev -``` +Delete the contents of the return statement in `app/page.tsx` and replace with `

My Example

` or whatever you like, just render something to the screen so you can tell its working when you run the app. -check `http://localhost:3000/` in the browser to confirm it compiled and ran +Start the project with hot reloading by running; -delete the contents of `src/app/globals.css` - -delete the any unused imports in `src/app/page.tsx` +```bash +yarn dev +``` -delete the contents of the return statement in `src/app/page.tsx` and replace with `<>` +Check `http://localhost:3000/` in the browser to confirm it compiled and ran -update the title and description in `src/app/layout.tsx` to match the examples in your app e.g. +Update the title and description in `app/layout.tsx` to match the examples in your app e.g. -``` +```ts export const metadata: Metadata = { - title: "Passport Wallets Connect", + title: "Passport Connect", description: "Examples of how to connect wallets to Passport with NextJS", }; ``` -create a home page for your example app with links to all the examples in `src/app/page.tsx` - -e.g. `examples/passport/wallets-connect-with-nextjs/src/app/page.tsx` +Create a home page for your example app with links to all the examples in `src/app/page.tsx` e.g. [Passport Connect Home Page](/examples/passport/wallets-connect-with-nextjs/app/page.tsx) -create a route for each example using the naming convention `-with-` e.g. `wallets-with-etherjs` +Create a route for each example using the naming convention `-with-` e.g. `wallets-with-etherjs` -start building your examplesin the `page.tsx` in each of your example's route folders +### Add your example -e.g. `examples/passport/wallets-connect-with-nextjs/src/app/connect-with-etherjs/page.tsx` +Once you've done this you can go ahead and write your example code in the `page.tsx` file in your examples route. +You can run the example locally to test your code by following the steps in the [Running examples locally](#running-examples-locally) section. -## Creating Code Snippets +You will also need to create e2e tests for your example as covered in the [End to end testing](#end-to-end-testing) section. -In your examples find the parts of the code you want to use as code snippets and wrap them in the `#doc` and `#enddoc` comments while providing a unique label. +Creating code snippets in the docs site using your example code is covered in the [Using code examples in the docs site](#using-code-examples-in-the-docs-site) section. -Labels only have to be unique in the file, but they should be verbose so it makes it easy to know what they are e.g. +If your code example is going to be used as code snippets in the docs site, before merging your branch to main in `ts-immutable-sdk` you should follow the steps in [Using code examples in the docs site](#using-code-examples-in-the-docs-site). These steps help you to test the code snippets in the docs site before you merge your PR which avoids double handling and multiple pull requests across both repos. -e.g. `-----` - -``` -// #doc passport-wallets-nextjs-connect-eip1193-create -const passportProvider = passportInstance.connectEvm() -// #enddoc passport-wallets-nextjs-connect-eip1193-create -``` - -## Using Code Snippets in the Docs site - -It's very simple, you just add a code block with the reference to the file you want to display e.g. - -```` -```js reference=examples/passport/wallets-connect-with-nextjs/src/app/connect-with-etherjs/page.tsx title="Connect Passport to Immutable zkEVM and create the Provider" -``` -```` - -Or if you only want to display part of the file, add the `#` label of the snippet you want to display e.g. - -```` -```js reference=examples/passport/wallets-connect-with-nextjs/src/app/connect-with-etherjs/page.tsx#passport-wallets-nextjs-connect-etherjs-create title="Connect Passport to Immutable zkEVM and create the Provider" -``` -```` - -All snippets should have a title, usually this can just be the file name the snippet comes from. Don't be shy adding extra context before or after the snippet explaining any key points which are necessary. - -## Development process - -Since the docs site by default is pulling the code examples from the main branch of `ts-immutable-sdk` they will not be available until they are merged. To get around this and view the snippets in the docs site before you merge the example to main you can point the docs site to use the branch you are working on in the sdk repo while you work on them. - -Create a branch for your example in `ts-immutable-sdk` repo and a branch for your code snippets in `docs` repo. - -Create your example in your `ts-immutable-sdk` branch and push it up to GitHub. - -In your `docs` branch modify the file `/src/remark/import-code.mjs` - -Update the `BRANCH` constant from `main` to your branch name e.g. - -``` -const BRANCH = 'DVR-193-passport-signing-example'; -``` - -Now your docs branch will be pulling the code examples from your branch in `ts-immutable-sdk` and you can use them locally in your `docs` branch to make sure they make sense in the page. - -Once you're happy with your examples, make the PR for your `ts-immutable-sdk` and get it merged into main. - -Then change the `BRANCH` constant back to `main` in the `/src/remark/import-code.mjs` file. - -Now your examples are merged they will appear locally in your `docs` branch from main on `ts-immutable-sdk` and you can make any other updates you need to give the code examples context in the docs site. - -Create your PR for your `docs` branch and get it merged into main. - -### WARNING - -Do **NOT** merge your `docs` branch to main without pointing the code import back to the main branch on `ts-immutable-sdk` or it will break things when the branch is deleted and new code examples merged to main will not show in the docs site. - - -## Comments - -All examples should be heavily commented and the comments should make sense in the context the snippet is appearing in the docs site. - -## Tests +# End to end testing All examples should be covered by basic e2e tests to ensure they at least render the examples. Ideally they would also have e2e tests that prove the functionality that you're trying to show works. Depending on what you're doing in the examples, it may be difficult to e2e test certain things e.g. logging in with Passport. For this reason, testing of functionality with e2e testing is recommended if practical, but not required. Install `@playwright/test` as a dev dependency for the e2e tests. -``` +```bash yarn add -D @playwright/test ``` Create a `playwright.config.ts` file in the root of the example app and add this configuration; -``` +```ts import { defineConfig, devices } from "@playwright/test"; export default defineConfig({ @@ -258,12 +278,101 @@ Make sure you update the localhost urls `http://localhost:3000` in the above exa Create a `tests` directory in the root of the example app and start adding tests. -Example of the base level of testing required can be found in `/examples/passport/wallets-signing-with-nextjs/tests/base.spec.ts` +Example of the base level of testing required can be found in the [Passport Connect e2e Tests](/examples/passport/wallets-connect-with-nextjs/tests/base.spec.ts) spec file. Add the test runner to the scripts in your package.json -``` +```json "test": "playwright test" ``` Run your tests with `yarn test` + +# Using code examples in the docs site + +Creating and using code snippets is relatively straight forward. You can pull in a whole file or by adding some comments you can pull in just a particular few lines of a file as necessary. + +To streamline the PR process, you should create a branch in the `docs` repo to add your code snippets before your branch for the code example in `ts-immutable-sdk` is merged to main. This way we can point the docs site to your branch for the code snippets and you can see your code example in situ and make any changes required to your code example before going through the PR process. + +## Update import code branch + +In your `docs` branch modify the file `/src/remark/import-code.mjs` and update the `BRANCH` constant from `main` to the name of your branch on `ts-immutable-sdk` e.g. + +```ts +const BRANCH = 'DVR-193-passport-signing-example'; +``` + +Now your `docs` branch will be pulling the code examples from your branch in `ts-immutable-sdk` and you can use them locally in your `docs` branch to make sure they make sense in the page. + +⚠️ ⚠️ ⚠️ **IMPORTANT** ⚠️ ⚠️ ⚠️ + +Make sure to change the branch back to `main` before merging your `docs` branch or it will **BREAK** the docs site. + +## Creating code snippets + +In your code examples in `ts-immutable-sdk` find the parts of the code you want to use as code snippets and wrap them in the `#doc` and `#enddoc` comments while providing a label. + +Labels have to be unique within a file so you should use a simple naming convention to avoid clashes within the file e.g. + +e.g. `-` + +```ts +// #doc eip1193-create +const passportProvider = passportInstance.connectEvm() +// #enddoc eip1193-create +``` + +Make sure to commit and push the labels to your `ts-immutable-sdk` branch on GitHub so they can be pulled down into your `docs` local build from there. + +## Using code snippets + +It's very simple to use the code snippet in the docs site, you just add a code block with the reference to the file and the `#` of the label you want to display e.g. + +````md +```tsx reference=examples/passport/wallets-connect-with-nextjs/src/app/connect-with-eip1193/page.tsx#eip1193-create title="Create the Passport Provider" +``` +```` +Or if you want to display the whole file just don't include a `#` label at the end of the file reference e.g. + +````md +```tsx reference=examples/passport/wallets-connect-with-nextjs/src/app/connect-with-eip1193/page.tsx title="Create the Passport Provider" +``` +```` + +Just like regular code snippets, you can set language for syntax highlighting by adding it's alias directly after the opening backticks. In the above example we are setting the syntax highlighting to be for `tsx`. For more information on syntax highlighting, visit the [GitHub Documentation](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks). + +Some popular syntaxes you are likely to want to use syntax highlighting for are; + +| Language | Alias | +|------------|-------------| +| Javascript | js | +| Typescript | ts | +| Typescript with JSX | tsx +| Solidity | solidity | +| C# | csharp | +| C++ | cpp | + +For the full list of supported syntaxes and their aliases [Linguist's languages YAML file](https://github.com/github-linguist/linguist/blob/master/lib/linguist/languages.yml). + + +The other available parameters are; + +| parameter | type | description | +|------------|---------|-------------| +| reference | string | the location of the code example file in the `ts-immutable-sdk` with optional `#` label of the snippet | +| title | string | the title that will appear above the code snippet block in the docs website | +| githubLink | boolean | true by default, set to false to hide the link to file on GitHub in the header | + +Make sure the language and parameters are all set on the first line of the code snippet otherwise it will display the text as a regular inline code snippet. + +## Finishing up + +Once you're happy with your code examples on `ts-immutable-sdk` and you've tried them locally in the `docs` site from your branch on Github. Create a PR for your code examples branch in `ts-immutable-sdk` and get it merged to main. + +Now in your `docs` branch change the `BRANCH` constant in the `/src/remark/import-code.mjs` file from your branch name, back to `main`. + +You can now double check the code snippets in your `docs` branch are all pulling through correctly from main on `ts-immutable-sdk` and create the PR for your `docs` branch. + +Once your `docs` PR is merged, Netlify should automatically build and deploy the docs site. If your updates are not reflected on the docs site within 10 minutes of the PR being merged, it's likely the build has failed in Netlify. Because we are now pulling in content dynamically for the code snippets, the GET requests to fetch the code examples can sometimes randomly timeout which fails the build. + +If this happens you will need to log into the Netlify site, check the error and retry the build. Usually this will fix the deployment issue, otherwise follow up on the error message shown by Netlify. \ No newline at end of file diff --git a/examples/passport/identity-with-nextjs/.eslintrc.json b/examples/passport/identity-with-nextjs/.eslintrc.json index bffb357a71..a2569c2c7c 100644 --- a/examples/passport/identity-with-nextjs/.eslintrc.json +++ b/examples/passport/identity-with-nextjs/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "next/core-web-vitals" } diff --git a/examples/passport/next-connect-kit/.eslintrc.json b/examples/passport/next-connect-kit/.eslintrc.json index bffb357a71..a2569c2c7c 100644 --- a/examples/passport/next-connect-kit/.eslintrc.json +++ b/examples/passport/next-connect-kit/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "next/core-web-vitals" } diff --git a/examples/passport/next-rainbow-kit/.eslintrc.json b/examples/passport/next-rainbow-kit/.eslintrc.json index bffb357a71..a2569c2c7c 100644 --- a/examples/passport/next-rainbow-kit/.eslintrc.json +++ b/examples/passport/next-rainbow-kit/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "next/core-web-vitals" } diff --git a/examples/passport/next-wagmi/.eslintrc.json b/examples/passport/next-wagmi/.eslintrc.json index bffb357a71..a2569c2c7c 100644 --- a/examples/passport/next-wagmi/.eslintrc.json +++ b/examples/passport/next-wagmi/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "next/core-web-vitals" } diff --git a/examples/passport/next-web3-modal/.eslintrc.json b/examples/passport/next-web3-modal/.eslintrc.json index bffb357a71..a2569c2c7c 100644 --- a/examples/passport/next-web3-modal/.eslintrc.json +++ b/examples/passport/next-web3-modal/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "next/core-web-vitals" } diff --git a/examples/passport/wallets-connect-with-nextjs/.eslintrc.json b/examples/passport/wallets-connect-with-nextjs/.eslintrc.json index 6cb73ba8c6..e7564bddde 100644 --- a/examples/passport/wallets-connect-with-nextjs/.eslintrc.json +++ b/examples/passport/wallets-connect-with-nextjs/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": ["next/core-web-vitals", "next"] } diff --git a/examples/passport/wallets-signing-with-nextjs/.eslintrc.json b/examples/passport/wallets-signing-with-nextjs/.eslintrc.json index 6cb73ba8c6..e7564bddde 100644 --- a/examples/passport/wallets-signing-with-nextjs/.eslintrc.json +++ b/examples/passport/wallets-signing-with-nextjs/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": ["next/core-web-vitals", "next"] } diff --git a/examples/passport/wallets-transactions-with-nextjs/.eslintrc.json b/examples/passport/wallets-transactions-with-nextjs/.eslintrc.json index bffb357a71..a2569c2c7c 100644 --- a/examples/passport/wallets-transactions-with-nextjs/.eslintrc.json +++ b/examples/passport/wallets-transactions-with-nextjs/.eslintrc.json @@ -1,3 +1,4 @@ { + "root": true, "extends": "next/core-web-vitals" } diff --git a/examples/primary-sales-backend-api/.env.example b/examples/primary-sales-backend-api/.env.example new file mode 100644 index 0000000000..028f05c759 --- /dev/null +++ b/examples/primary-sales-backend-api/.env.example @@ -0,0 +1,9 @@ +PORT=3000 + +# Environment variables declared in this file are automatically made available to Prisma. +# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema + +# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. +# See the documentation for all the connection string options: https://pris.ly/d/connection-strings + +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/primarysales?schema=public" diff --git a/examples/primary-sales-backend-api/.gitignore b/examples/primary-sales-backend-api/.gitignore new file mode 100644 index 0000000000..12a6c18dc0 --- /dev/null +++ b/examples/primary-sales-backend-api/.gitignore @@ -0,0 +1,4 @@ +node_modules +# Keep environment variables out of version control +.env +build \ No newline at end of file diff --git a/examples/primary-sales-backend-api/.nvmrc b/examples/primary-sales-backend-api/.nvmrc new file mode 100644 index 0000000000..b427e2ae2f --- /dev/null +++ b/examples/primary-sales-backend-api/.nvmrc @@ -0,0 +1 @@ +v20.16.0 \ No newline at end of file diff --git a/examples/primary-sales-backend-api/README.md b/examples/primary-sales-backend-api/README.md new file mode 100644 index 0000000000..0dee9cc627 --- /dev/null +++ b/examples/primary-sales-backend-api/README.md @@ -0,0 +1,40 @@ +# Example Primary Sales Webhook API + +This example shows how to implement the webhooks required for the [primary sales backend config](https://docs.immutable.com/products/zkEVM/checkout/widgets/primary-sales/backend/byo). + +## Pre-requisites + +* [NodeJS >= v20](https://nodejs.org/en) +* [Docker](https://www.docker.com/) + +### Install dependencies + +Run `npm i` + +### Set environment variables + +Copy the `.env.example` file and rename it to `.env`. + +## Running the app + +1. Run `docker-compose up -d` to start the postgres DB at port 5432. +2. Run `npx prisma migrate dev` and `npm run seed` to initialise the DB schema and seed it with data. +3. `npm run dev` to start your server on port 3000 + +## Webhook endpoints + +To see the list of endpoints this example serves, go to [the Swagger UI](http://localhost:3000/docs). + +Apart from the `/api/v1/products` endpoint which is used to list the products available in the DB, the rest of the endpoints correspond to the [Primary Sales backend config documentation](https://docs.immutable.com/products/zkEVM/checkout/widgets/primary-sales/backend/byo). + + +## Example requests + +For your convenience, we have also added a postman collection under the `postman` folder. These contain sample requests for each endpoint, using the seeded products data. + +To run the requests, download [Postman](https://www.postman.com/) and import the collection. + + +## TO-DO list + +* Add authentication for each endpoint, as per the [webhook authentication section](https://docs.immutable.com/products/zkEVM/checkout/widgets/primary-sales/backend/byo#webhook-authentication) \ No newline at end of file diff --git a/examples/primary-sales-backend-api/docker-compose.yml b/examples/primary-sales-backend-api/docker-compose.yml new file mode 100644 index 0000000000..b15f216412 --- /dev/null +++ b/examples/primary-sales-backend-api/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.8' + +services: + primary-sales-db: + image: postgres:14 + ports: + - 5432:5432 + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=primarysales + restart: always + volumes: + - primary-sales-db-data:/data/postgres + +volumes: + primary-sales-db-data: + diff --git a/examples/primary-sales-backend-api/package.json b/examples/primary-sales-backend-api/package.json new file mode 100644 index 0000000000..459bf355ea --- /dev/null +++ b/examples/primary-sales-backend-api/package.json @@ -0,0 +1,29 @@ +{ + "devDependencies": { + "@types/node": "^22.2.0", + "fastify-tsconfig": "^2.0.0", + "nodemon": "^3.1.4", + "prisma": "^5.18.0", + "ts-node": "^10.9.2", + "typescript": "^5.5.4" + }, + "dependencies": { + "@fastify/autoload": "^5.10.0", + "@fastify/env": "^4.4.0", + "@fastify/swagger": "^8.15.0", + "@fastify/swagger-ui": "^4.1.0", + "@fastify/type-provider-typebox": "^4.0.0", + "@paralleldrive/cuid2": "^2.2.2", + "@prisma/client": "^5.18.0", + "fastify": "^4.28.1" + }, + "prisma": { + "seed": "ts-node prisma/seed.ts" + }, + "scripts": { + "build": "rm -rf build ; tsc", + "start": "node build/src/server.js", + "dev": "nodemon --exec ts-node src/server.ts", + "seed": "prisma db seed" + } +} diff --git a/examples/primary-sales-backend-api/postman/Primary sales BE.postman_collection.json b/examples/primary-sales-backend-api/postman/Primary sales BE.postman_collection.json new file mode 100644 index 0000000000..4df0afbcdd --- /dev/null +++ b/examples/primary-sales-backend-api/postman/Primary sales BE.postman_collection.json @@ -0,0 +1,189 @@ +{ + "info": { + "_postman_id": "c754b503-fb61-4722-8fac-47fecc2efb3e", + "name": "Primary sales BE", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "25754861" + }, + "item": [ + { + "name": "Quote", + "request": { + "method": "POST", + "header": [ + { + "key": "QUOTE_API_KEY", + "value": "test_api_key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"recipient_address\": \"0xdd9AAE1C317eE6EFEb0F3DB0A068e9Ed952a6CEB\",\n \"products\": [\n {\n \"product_id\": \"vi7age4ku18qynwbk4wx90ge\",\n \"quantity\": 1\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:3000/api/v1/orders/quotes", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "v1", + "orders", + "quotes" + ] + } + }, + "response": [] + }, + { + "name": "Create order", + "request": { + "method": "POST", + "header": [ + { + "key": "QUOTE_API_KEY", + "value": "test_api_key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"recipient_address\": \"0xdd9AAE1C317eE6EFEb0F3DB0A068e9Ed952a6CEB\",\n \"currency\": \"USDC\",\n \"products\": [\n {\n \"product_id\": \"vi7age4ku18qynwbk4wx90ge\",\n \"quantity\": 1\n },\n {\n \"product_id\": \"jtwrclpj0v1zab865ne893hb\",\n \"quantity\": 1\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:3000/api/v1/sale-authorization", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "v1", + "sale-authorization" + ] + } + }, + "response": [] + }, + { + "name": "Expire an order", + "request": { + "method": "POST", + "header": [ + { + "key": "QUOTE_API_KEY", + "value": "test_api_key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"reference\": \"cm02a70000001updhnudm7bop\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:3000/api/v1/expire", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "v1", + "expire" + ] + } + }, + "response": [] + }, + { + "name": "Confirm", + "request": { + "method": "POST", + "header": [ + { + "key": "QUOTE_API_KEY", + "value": "test_api_key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"reference\": \"cm02apig000035o3ipwszr02z\",\n \"tx_hash\": \"test\",\n \"recipient_address\": \"0xdd9AAE1C317eE6EFEb0F3DB0A068e9Ed952a6CEB\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:3000/api/v1/confirm", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "v1", + "confirm" + ] + } + }, + "response": [] + }, + { + "name": "Products", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "QUOTE_API_KEY", + "value": "test_api_key", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"reference\": \"cm02a70000001updhnudm7bop\",\n \"tx_hash\": \"test\",\n \"recipient_address\": \"0xdd9AAE1C317eE6EFEb0F3DB0A068e9Ed952a6CEB\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:3000/api/v1/products", + "host": [ + "localhost" + ], + "port": "3000", + "path": [ + "api", + "v1", + "products" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/examples/primary-sales-backend-api/prisma/migrations/20240820102407_init/migration.sql b/examples/primary-sales-backend-api/prisma/migrations/20240820102407_init/migration.sql new file mode 100644 index 0000000000..00a238fd17 --- /dev/null +++ b/examples/primary-sales-backend-api/prisma/migrations/20240820102407_init/migration.sql @@ -0,0 +1,65 @@ +-- CreateEnum +CREATE TYPE "CurrencyType" AS ENUM ('crypto', 'fiat'); + +-- CreateEnum +CREATE TYPE "OrderStatus" AS ENUM ('reserved', 'completed', 'expired', 'failed'); + +-- CreateTable +CREATE TABLE "Product" ( + "id" TEXT NOT NULL, + "collectionAddress" TEXT NOT NULL, + "contractType" TEXT NOT NULL, + "stockQuantity" INTEGER NOT NULL, + + CONSTRAINT "Product_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Currency" ( + "name" TEXT NOT NULL, + "type" "CurrencyType" NOT NULL, + + CONSTRAINT "Currency_pkey" PRIMARY KEY ("name") +); + +-- CreateTable +CREATE TABLE "ProductPrice" ( + "product_id" TEXT NOT NULL, + "currency_name" TEXT NOT NULL, + "amount" DOUBLE PRECISION NOT NULL, + + CONSTRAINT "ProductPrice_pkey" PRIMARY KEY ("product_id","currency_name") +); + +-- CreateTable +CREATE TABLE "Order" ( + "id" TEXT NOT NULL, + "status" "OrderStatus" NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "transactionHash" TEXT, + "recipientAddress" TEXT NOT NULL, + + CONSTRAINT "Order_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "OrderLineItem" ( + "order_id" TEXT NOT NULL, + "product_id" TEXT NOT NULL, + "quantity" INTEGER NOT NULL, + + CONSTRAINT "OrderLineItem_pkey" PRIMARY KEY ("order_id","product_id") +); + +-- AddForeignKey +ALTER TABLE "ProductPrice" ADD CONSTRAINT "ProductPrice_product_id_fkey" FOREIGN KEY ("product_id") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProductPrice" ADD CONSTRAINT "ProductPrice_currency_name_fkey" FOREIGN KEY ("currency_name") REFERENCES "Currency"("name") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OrderLineItem" ADD CONSTRAINT "OrderLineItem_order_id_fkey" FOREIGN KEY ("order_id") REFERENCES "Order"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OrderLineItem" ADD CONSTRAINT "OrderLineItem_product_id_fkey" FOREIGN KEY ("product_id") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/examples/primary-sales-backend-api/prisma/migrations/migration_lock.toml b/examples/primary-sales-backend-api/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000000..fbffa92c2b --- /dev/null +++ b/examples/primary-sales-backend-api/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/examples/primary-sales-backend-api/prisma/schema.prisma b/examples/primary-sales-backend-api/prisma/schema.prisma new file mode 100644 index 0000000000..3beb46bab9 --- /dev/null +++ b/examples/primary-sales-backend-api/prisma/schema.prisma @@ -0,0 +1,72 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" + binaryTargets = ["native", "linux-musl-arm64-openssl-3.0.x"] +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Product { + id String @id @default(cuid()) + productPrices ProductPrice[] + collectionAddress String + contractType String + stockQuantity Int + orders OrderLineItem[] +} + +enum CurrencyType { + crypto + fiat +} + +model Currency { + name String @id + type CurrencyType + productPrices ProductPrice[] +} + +model ProductPrice { + product Product @relation(fields: [product_id], references: [id]) + product_id String + currency Currency @relation(fields: [currency_name], references: [name]) + currency_name String + amount Float + + @@id([product_id, currency_name]) +} + +enum OrderStatus { + reserved + completed + expired + failed +} + +model Order { + id String @id @default(cuid()) + status OrderStatus + lineItems OrderLineItem[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + transactionHash String? + recipientAddress String +} + +model OrderLineItem { + order_id String + product_id String + quantity Int + order Order @relation(fields: [order_id], references: [id]) + product Product @relation(fields: [product_id], references: [id]) + + @@id([order_id, product_id]) +} diff --git a/examples/primary-sales-backend-api/prisma/seed.ts b/examples/primary-sales-backend-api/prisma/seed.ts new file mode 100644 index 0000000000..c0400387c0 --- /dev/null +++ b/examples/primary-sales-backend-api/prisma/seed.ts @@ -0,0 +1,86 @@ +import { PrismaClient } from "@prisma/client"; +import 'dotenv/config' + +const prisma = new PrismaClient(); + +// For this seed script, we will create a few products and currencies +// that we will use to demonstrate the API functionality + +// This is a fake collection address. In a real scenario, this would be the address of the product's collection address. +const collectionAddress = '0x00'; + +async function main() { + const usdc = await prisma.currency.upsert({ + where: { name: 'USDC' }, + update: {}, + create: { + name: 'USDC', + type: 'crypto' + } + }); + + const eth = await prisma.currency.upsert({ + where: { name: 'ETH' }, + update: {}, + create: { + name: 'ETH', + type: 'crypto' + } + }); + + const productId1 = 'vi7age4ku18qynwbk4wx90ge'; + + await prisma.product.upsert({ + where: { id: productId1 }, + update: {}, + create: { + id: productId1, + collectionAddress: collectionAddress, + contractType: 'ERC721', + stockQuantity: 100, + productPrices: { + create: [ + { + currency_name: usdc.name, + amount: 1000 + }, + { + currency_name: eth.name, + amount: 0.205 + }, + ] + } + } + }) + + const productId2 = 'jtwrclpj0v1zab865ne893hb'; + + await prisma.product.upsert({ + where: { id: productId2 }, + update: {}, + create: { + id: productId2, + collectionAddress: collectionAddress, + contractType: 'ERC721', + stockQuantity: 50, + productPrices: { + create: [ + { + currency_name: usdc.name, + amount: 20 + }, + ] + } + } + }) +} + +main() + .then(async () => { + await prisma.$disconnect() + }) + .catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) + }) \ No newline at end of file diff --git a/examples/primary-sales-backend-api/src/errors.ts b/examples/primary-sales-backend-api/src/errors.ts new file mode 100644 index 0000000000..825fe2083a --- /dev/null +++ b/examples/primary-sales-backend-api/src/errors.ts @@ -0,0 +1,8 @@ +export class ApiError extends Error { + constructor(readonly statusCode: number, message: string) { + super(message); + this.statusCode = statusCode; + + Error.captureStackTrace(this, this.constructor); + } +} \ No newline at end of file diff --git a/examples/primary-sales-backend-api/src/plugins/errorHander.ts b/examples/primary-sales-backend-api/src/plugins/errorHander.ts new file mode 100644 index 0000000000..309db96f7a --- /dev/null +++ b/examples/primary-sales-backend-api/src/plugins/errorHander.ts @@ -0,0 +1,45 @@ +import { Prisma } from '@prisma/client'; +import { FastifyInstance, FastifyPluginAsync } from 'fastify'; +import fp from 'fastify-plugin'; + +const handlePrismaError = (err: Prisma.PrismaClientKnownRequestError): { + statusCode: number, + message: string +} => { + switch (err.code) { + case 'P2025': + return { + statusCode: 400, + message: `Not found: ${err.meta?.modelName}` + }; + default: + // handling all other errors + return { + statusCode: 500, + message: 'Internal Server Error' + }; + + } +}; + +const errorHandlerPlugin: FastifyPluginAsync = fp(async (fastify: FastifyInstance) => { + fastify.setErrorHandler(function (error, request, reply) { + this.log.error(error); + + if (error instanceof Prisma.PrismaClientKnownRequestError) { + const mappedError = handlePrismaError(error); + + reply.status(mappedError.statusCode).send({ message: mappedError.message }); + return; + } + + if (!error.statusCode) { + reply.status(500).send({ message: 'Internal Server Error' }); + return; + } + + reply.status(error.statusCode).send({ message: error.message }); + }); +}); + +export default errorHandlerPlugin; \ No newline at end of file diff --git a/examples/primary-sales-backend-api/src/plugins/prisma.ts b/examples/primary-sales-backend-api/src/plugins/prisma.ts new file mode 100644 index 0000000000..97da5f1229 --- /dev/null +++ b/examples/primary-sales-backend-api/src/plugins/prisma.ts @@ -0,0 +1,17 @@ +import { PrismaClient } from "@prisma/client"; +import { FastifyPluginAsync } from "fastify"; +import fp from 'fastify-plugin' + +const PrismaPlugin: FastifyPluginAsync = fp(async (fastify) => { + const prisma = new PrismaClient(); + + await prisma.$connect(); + + fastify.decorate('prisma', prisma) + + fastify.addHook('onClose', async () => { + await prisma.$disconnect(); + }); +}) + +export default PrismaPlugin; \ No newline at end of file diff --git a/examples/primary-sales-backend-api/src/plugins/swagger.ts b/examples/primary-sales-backend-api/src/plugins/swagger.ts new file mode 100644 index 0000000000..428651e679 --- /dev/null +++ b/examples/primary-sales-backend-api/src/plugins/swagger.ts @@ -0,0 +1,33 @@ +import fp from 'fastify-plugin' +import SwaggerUI from '@fastify/swagger-ui' +import Swagger, { type FastifyDynamicSwaggerOptions } from '@fastify/swagger' + +export default fp(async (fastify, opts) => { + await fastify.register(Swagger, { + openapi: { + info: { + title: 'Primary Sales Webhooks Backend', + description: 'Example API endpoints for the Primary Sales Webhooks Backend', + version: '0.1.0', + }, + servers: [ + { + url: `http://localhost:${fastify.config.PORT}`, + }, + ], + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + }, + }, + }, + }, + }) + + await fastify.register(SwaggerUI, { + routePrefix: '/docs', + }) +}) \ No newline at end of file diff --git a/examples/primary-sales-backend-api/src/routes/authorize.ts b/examples/primary-sales-backend-api/src/routes/authorize.ts new file mode 100644 index 0000000000..d2a1a988ac --- /dev/null +++ b/examples/primary-sales-backend-api/src/routes/authorize.ts @@ -0,0 +1,127 @@ +import { FastifyPluginAsyncTypebox, Type } from "@fastify/type-provider-typebox"; +import { OrderLineItem, OrderStatus, PrismaClient } from '@prisma/client'; +import { ApiError } from '../errors'; +import { ProductRequestSchema, ProductRequestType } from "../schemas/product"; + +const createOrder = async (prisma: PrismaClient, recipientAddress: string, orderProducts: ProductRequestType[]) => { + return await prisma.$transaction(async tx => { + const updatedProducts = []; + + for (const orderProduct of orderProducts) { + + // For each product in the order, we decrement the stock quantity by the order quantity since we are reserving stock for the order. + const updatedProduct = await tx.product.update({ + where: { id: orderProduct.product_id }, + data: { + stockQuantity: { decrement: orderProduct.quantity } + }, + include: { + productPrices: true + } + }) + + if (updatedProduct.stockQuantity < 0) { + throw new ApiError(400, `Product with id ${orderProduct.product_id} has insufficient stock for this order`); + } + updatedProducts.push(updatedProduct); + } + + // Create an order with a 'reserved' status alongside the order line items. + const order = await tx.order.create({ + data: { + status: OrderStatus.reserved, + recipientAddress: recipientAddress, + lineItems: { + create: updatedProducts.map((product) => ({ + product_id: product.id, + quantity: orderProducts.find((orderProduct) => orderProduct.product_id === product.id)?.quantity ?? 0 + })), + } + }, + include: { + lineItems: { + include: { + product: { + include: { + productPrices: true + } + } + } + } + } + }) + return order; + }) +} + +const SaleAuthorizationRoutes: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.post('/orders/authorize', { + schema: { + description: 'Authorize a sale and reserve stock for the order', + body: Type.Object({ + products: Type.Array(ProductRequestSchema), + currency: Type.String(), + recipient_address: Type.String() + }), + response: { + 200: Type.Object({ + reference: Type.String(), + currency: Type.String(), + products: Type.Array(Type.Object({ + product_id: Type.String(), + collection_address: Type.String(), + contract_type: Type.String(), + detail: Type.Array(Type.Object({ + token_id: Type.String(), + amount: Type.Number() + }) + ) + })) + }), + 400: Type.Object({ + message: Type.String() + }), + 404: Type.Object({ + message: Type.String() + }), + } + } + }, async (request, _) => { + const order = await createOrder(fastify.prisma, request.body.recipient_address, request.body.products); + + const populateDetails = (amount: number, lineItem: OrderLineItem) => { + const details = []; + for (let i = 0; i < lineItem.quantity; i++) { + details.push({ + // We don't persist token ID in this example, so we generate a random one. + // For real life use cases, consider whether you need to persist token_id or not during order creation. + token_id: String(Math.floor(Math.random() * 10000000000)), + amount + }) + } + + return details; + } + + return { + reference: order.id, + currency: request.body.currency, + products: order.lineItems.map(lineItem => { + const pricing = lineItem.product.productPrices.find((productPrice) => productPrice.currency_name === request.body.currency); + if (!pricing) { + throw new ApiError(404, `Product with id ${lineItem.product_id} does not have pricing for currency ${request.body.currency}`); + } + const productDetails = populateDetails(pricing.amount, lineItem); + + return { + product_id: lineItem.product_id, + collection_address: lineItem.product.collectionAddress, + contract_type: lineItem.product.contractType, + detail: productDetails + } + }) + } + }); +} + +export default SaleAuthorizationRoutes; diff --git a/examples/primary-sales-backend-api/src/routes/confirm.ts b/examples/primary-sales-backend-api/src/routes/confirm.ts new file mode 100644 index 0000000000..0dfa2e722f --- /dev/null +++ b/examples/primary-sales-backend-api/src/routes/confirm.ts @@ -0,0 +1,45 @@ +import { FastifyPluginAsyncTypebox, Type } from "@fastify/type-provider-typebox"; +import { OrderStatus } from "@prisma/client"; + +const ConfirmOrderRequestSchema = Type.Object({ + // This example doesn't need the rest of the fields in the request + // Consider what fields you'll need for your own implementation and adjust accordingly + reference: Type.String(), + tx_hash: Type.String(), +}) + + +const OrderConfirmationRoutes: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.post('/orders/confirm', { + schema: { + description: 'Endpoint that will be called after a successful transation.', + body: ConfirmOrderRequestSchema, + response: { + 200: Type.Null(), + 404: Type.Object({ + message: Type.String() + }), + 400: Type.Object({ + message: Type.String() + }), + } + } + }, async (request, reply) => { + const { reference, tx_hash } = request.body; + + // We only update the order status to completed and store the transaction hash + // In real life scenarios you can use this endpoint for randomised metadata generation (such as lootboxes), to transfer the NFTs to the user in-game, etc. + await fastify.prisma.order.update({ + where: { + id: reference + }, + data: { + status: OrderStatus.completed, + transactionHash: tx_hash, + } + }); + return reply.status(200).send(); + }); +} + +export default OrderConfirmationRoutes; \ No newline at end of file diff --git a/examples/primary-sales-backend-api/src/routes/expire.ts b/examples/primary-sales-backend-api/src/routes/expire.ts new file mode 100644 index 0000000000..3d5fd54d7f --- /dev/null +++ b/examples/primary-sales-backend-api/src/routes/expire.ts @@ -0,0 +1,78 @@ +import { FastifyPluginAsyncTypebox, Type } from "@fastify/type-provider-typebox"; +import { OrderStatus, PrismaClient } from "@prisma/client"; +import { ApiError } from "../errors"; + +const expireOrder = async (prisma: PrismaClient, reference: string) => { + return await prisma.$transaction(async tx => { + const order = await tx.order.findUnique({ + where: { + id: reference + } + }); + + if (!order) { + throw new ApiError(404, 'Order not found'); + } + + if (order.status !== OrderStatus.reserved) { + throw new ApiError(400, 'Order is not reserved'); + } + + await tx.order.update({ + where: { + id: reference + }, + data: { + status: OrderStatus.expired + } + }); + + const lineItems = await tx.orderLineItem.findMany({ + where: { + order_id: reference + } + }); + + for (const lineItem of lineItems) { + await tx.product.update({ + where: { + id: lineItem.product_id + }, + // Since the order is expired, the quantity reserved by that order should be released back + // so we increase the stock back by the original order quantity + data: { + stockQuantity: { + increment: lineItem.quantity + } + } + }); + } + }); +} + + +const OrderExpiryRoutes: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.post('/orders/expire', { + schema: { + description: 'Expire an order and release the reserved stock back to the product', + body: Type.Object({ + reference: Type.String(), + }), + response: { + 200: Type.Null(), + 404: Type.Object({ + message: Type.String() + }), + 400: Type.Object({ + message: Type.String() + }), + } + } + }, async (request, reply) => { + await expireOrder(fastify.prisma, request.body.reference); + + return reply.status(200).send(); + }); +} + +export default OrderExpiryRoutes; \ No newline at end of file diff --git a/examples/primary-sales-backend-api/src/routes/products.ts b/examples/primary-sales-backend-api/src/routes/products.ts new file mode 100644 index 0000000000..e3a1106c3c --- /dev/null +++ b/examples/primary-sales-backend-api/src/routes/products.ts @@ -0,0 +1,45 @@ +import { FastifyPluginAsyncTypebox, Type } from "@fastify/type-provider-typebox"; + +const OrderConfirmationRoutes: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.get('/products', { + schema: { + description: 'Get a list of products with their pricing', + response: { + 200: Type.Array(Type.Object({ + product_id: Type.String(), + quantity: Type.Number(), + pricing: Type.Array(Type.Object({ + currency: Type.String(), + amount: Type.Number() + })) + })), + 404: Type.Object({ + message: Type.String() + }), + 400: Type.Object({ + message: Type.String() + }), + } + } + }, async () => { + const products = await fastify.prisma.product.findMany({ + include: { + productPrices: true + } + }); + return products.map(p => { + return { + product_id: p.id, + quantity: p.stockQuantity, + pricing: p.productPrices.map(pp => { + return { + currency: pp.currency_name, + amount: pp.amount + } + }) + } + }) + }); +} + +export default OrderConfirmationRoutes; \ No newline at end of file diff --git a/examples/primary-sales-backend-api/src/routes/quotes.ts b/examples/primary-sales-backend-api/src/routes/quotes.ts new file mode 100644 index 0000000000..88decc4b24 --- /dev/null +++ b/examples/primary-sales-backend-api/src/routes/quotes.ts @@ -0,0 +1,124 @@ +import { FastifyPluginAsyncTypebox, Type } from "@fastify/type-provider-typebox"; +import { PrismaClient, Currency, CurrencyType } from "@prisma/client"; +import { CurrencySchema } from "../schemas/currency"; +import { ProductsResponseSchema, ProductsResponseType } from "../schemas/product"; +import { ApiError } from "../errors"; + +const getProductsWithPrices = (ids: string[], prisma: PrismaClient) => { + return prisma.product.findMany({ + where: { + id: { + in: ids + } + }, + include: { + productPrices: { + include: { + currency: true + } + } + } + }); +} + +const calculateTotals = (products: ProductsResponseType): Map => { + const currencyTotalsMap = new Map(); + + products.forEach((product) => { + product.pricing.forEach((productPrice) => { + const { currency, amount, currency_type } = productPrice; + + const currentTotal = currencyTotalsMap.get(currency)?.total || 0; + + currencyTotalsMap.set(currency, { + type: currency_type as CurrencyType, + name: currency, + total: currentTotal + amount + }); + }); + }); + + return currencyTotalsMap; +} + +const QuoteRoutes: FastifyPluginAsyncTypebox = async (fastify) => { + fastify.post('/quotes', { + schema: { + description: 'Get a quote for a list of products. Used to calculate the total cost of an order and show it to the user.', + body: Type.Object({ + recipient_address: Type.String(), + products: Type.Array(Type.Object({ + product_id: Type.String(), + quantity: Type.Integer() + })), + }), + response: { + 200: Type.Object({ + products: ProductsResponseSchema, + totals: Type.Array(CurrencySchema) + }), + 404: Type.Object({ + message: Type.String() + }), + 400: Type.Object({ + message: Type.String() + }), + } + } + }, async (request, _) => { + const { products: requestedProducts } = request.body; + + const products = await getProductsWithPrices(requestedProducts.map((product) => product.product_id), fastify.prisma); + + if (!products.length) { + return { products: [], totals: [] } + } + + const productsResponse: ProductsResponseType = []; + + for (const requestedProduct of requestedProducts) { + const product = products.find(p => p.id === requestedProduct.product_id); + + if (!product) { + throw new ApiError(404, `Product with id ${requestedProduct.product_id} not found`); + } + + if (requestedProduct.quantity > product.stockQuantity) { + throw new ApiError(400, `Not enough stock for product with id ${requestedProduct.product_id}`); + } + + // Get the pricing for the product, separated by currency + const productPrices = product.productPrices.map((productPrice) => ({ + currency: productPrice.currency.name, + amount: productPrice.amount * requestedProduct.quantity, + currency_type: String(productPrice.currency.type), + })); + + productsResponse.push({ + product_id: product.id, + quantity: requestedProduct.quantity, + pricing: productPrices + }); + } + + // Calculate totals for each currency + const currencyTotalsMap = calculateTotals(productsResponse); + + const totalsResponse = Array.from(currencyTotalsMap.values()).map(total => ({ + currency: total.name, + currency_type: String(total.type), + amount: total.total + })); + + return { + products: productsResponse, + totals: totalsResponse + } + }); +} + +export default QuoteRoutes; \ No newline at end of file diff --git a/examples/primary-sales-backend-api/src/schemas/currency.ts b/examples/primary-sales-backend-api/src/schemas/currency.ts new file mode 100644 index 0000000000..f6539dafd9 --- /dev/null +++ b/examples/primary-sales-backend-api/src/schemas/currency.ts @@ -0,0 +1,7 @@ +import { Type } from "@fastify/type-provider-typebox"; + +export const CurrencySchema = Type.Object({ + currency: Type.String(), + amount: Type.Number(), + currency_type: Type.String(), +}) \ No newline at end of file diff --git a/examples/primary-sales-backend-api/src/schemas/product.ts b/examples/primary-sales-backend-api/src/schemas/product.ts new file mode 100644 index 0000000000..f1d005d170 --- /dev/null +++ b/examples/primary-sales-backend-api/src/schemas/product.ts @@ -0,0 +1,17 @@ +import { Type, Static } from "@fastify/type-provider-typebox"; +import { CurrencySchema } from "./currency"; + +export const ProductRequestSchema = Type.Object({ + product_id: Type.String(), + quantity: Type.Integer() +}); + +export type ProductRequestType = Static; + +export const ProductsResponseSchema = Type.Array(Type.Object({ + product_id: Type.String(), + quantity: Type.Number(), + pricing: Type.Array(CurrencySchema) +})); + +export type ProductsResponseType = Static; \ No newline at end of file diff --git a/examples/primary-sales-backend-api/src/server.ts b/examples/primary-sales-backend-api/src/server.ts new file mode 100644 index 0000000000..6ec408ff6c --- /dev/null +++ b/examples/primary-sales-backend-api/src/server.ts @@ -0,0 +1,60 @@ +import fastify from "fastify"; +import fastifyEnv from "@fastify/env"; +import AutoLoad from '@fastify/autoload'; +import { PrismaClient } from "@prisma/client"; +import { Static, Type } from "@fastify/type-provider-typebox"; + +export const ConfigSchema = Type.Object({ + PORT: Type.String({ default: '3000' }), +}); + +export type Config = Static; + +declare module 'fastify' { + interface FastifyInstance { + config: Config; + prisma: PrismaClient; + } +} + +const options = { + schema: ConfigSchema, + dotenv: true, + data: process.env +} + +const server = fastify({ logger: true }); + +const initialize = async () => { + await server + .register(fastifyEnv, options); + + server + .register(AutoLoad, { + dir: `${__dirname}/plugins`, + ignorePattern: /.test.(t|j)s/, + }) + .register(AutoLoad, { + dir: `${__dirname}/routes`, + ignorePattern: /.test.(t|j)s/, + dirNameRoutePrefix: true, + routeParams: true, + options: { prefix: "/api/v1" }, + }); + + await server.after(); +} + +(async () => { + try { + await initialize(); + await server.ready(); + await server.listen({ + port: Number(server.config.PORT), + host: '0.0.0.0' + }); + } catch (error) { + server.log.error(error); + process.exit(1); + } +})(); diff --git a/examples/primary-sales-backend-api/tsconfig.json b/examples/primary-sales-backend-api/tsconfig.json new file mode 100644 index 0000000000..96b6b0e58f --- /dev/null +++ b/examples/primary-sales-backend-api/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "fastify-tsconfig", + "compilerOptions": { + "outDir": "build", + "lib": ["es2018"], + "target": "es2018", + "strict": true, + "forceConsistentCasingInFileNames": true, + "typeRoots": ["./node_modules/@types", "./src/types"] + } +} \ No newline at end of file diff --git a/package.json b/package.json index 166433557e..37d137199f 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,6 @@ "packages/passport/sdk-sample-app", "packages/orderbook", "packages/internal/metrics", - "packages/internal/contracts", "packages/internal/toolkit", "packages/internal/cryptofiat", "packages/internal/dex/sdk", @@ -106,4 +105,4 @@ "tests/**" ] } -} \ No newline at end of file +} diff --git a/packages/blockchain-data/sdk/.eslintrc b/packages/blockchain-data/sdk/.eslintrc.cjs similarity index 79% rename from packages/blockchain-data/sdk/.eslintrc rename to packages/blockchain-data/sdk/.eslintrc.cjs index f77729693e..3193df85c8 100644 --- a/packages/blockchain-data/sdk/.eslintrc +++ b/packages/blockchain-data/sdk/.eslintrc.cjs @@ -1,9 +1,9 @@ -{ +module.exports = { "extends": ["../../../.eslintrc"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname }, "rules": { "@typescript-eslint/comma-dangle": "off" diff --git a/packages/blockchain-data/sdk/jest.config.ts b/packages/blockchain-data/sdk/jest.config.ts index c64fb9b6e6..05ef77a17a 100644 --- a/packages/blockchain-data/sdk/jest.config.ts +++ b/packages/blockchain-data/sdk/jest.config.ts @@ -4,10 +4,7 @@ const config: Config = { clearMocks: true, coverageProvider: 'v8', moduleDirectories: ['node_modules', 'src'], - moduleNameMapper: { - '@imtbl/config': '../../config/src', - '@imtbl/generated-clients': '../../internal/generated-clients/src' - }, + moduleNameMapper: { '^@imtbl/(.*)$': '/../../../node_modules/@imtbl/$1/src' }, testEnvironment: 'node', transform: { '^.+\\.(t|j)sx?$': '@swc/jest', diff --git a/packages/blockchain-data/sdk/package.json b/packages/blockchain-data/sdk/package.json index 67b3ab7fcd..68f36afc65 100644 --- a/packages/blockchain-data/sdk/package.json +++ b/packages/blockchain-data/sdk/package.json @@ -25,7 +25,20 @@ "rollup": "^4.19.1", "ts-mockito": "^2.6.1", "typechain": "^8.1.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" + }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } }, "homepage": "https://github.com/immutable/ts-immutable-sdk#readme", "license": "Apache-2.0", @@ -34,7 +47,7 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "generate-types": "typechain --target=ethers-v5 --out-dir=src/typechain/types 'abi/*.json'", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest", @@ -42,6 +55,5 @@ "test:watch": "jest --watch", "typecheck": "tsc --noEmit --jsx preserve" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/blockchain-data/sdk/rollup.config.js b/packages/blockchain-data/sdk/rollup.config.js index 16a7d58d7e..0269dcdc15 100644 --- a/packages/blockchain-data/sdk/rollup.config.js +++ b/packages/blockchain-data/sdk/rollup.config.js @@ -1,4 +1,7 @@ import typescript from '@rollup/plugin-typescript'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: 'src/index.ts', @@ -6,5 +9,5 @@ export default { dir: 'dist', format: 'es', }, - plugins: [typescript()], + plugins: [isProduction ? typescript({customConditions: ["default"]}) : swc.rollup()], }; diff --git a/packages/blockchain-data/sdk/src/index.ts b/packages/blockchain-data/sdk/src/index.ts index e86427ab7c..c41275b82d 100644 --- a/packages/blockchain-data/sdk/src/index.ts +++ b/packages/blockchain-data/sdk/src/index.ts @@ -14,7 +14,9 @@ type ActivityType = mr.ActivityType; export { Types, APIError, - BlockchainData, + BlockchainData +}; +export type { BlockchainDataModuleConfiguration, - ActivityType, + ActivityType }; diff --git a/packages/blockchain-data/sdk/tsconfig.json b/packages/blockchain-data/sdk/tsconfig.json index 56c27e347a..5184ceffeb 100644 --- a/packages/blockchain-data/sdk/tsconfig.json +++ b/packages/blockchain-data/sdk/tsconfig.json @@ -1,20 +1,9 @@ { + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": true, - "resolveJsonModule": true, - "skipLibCheck": true + "customConditions": ["development"] }, "include": ["src"], "exclude": ["node_modules", "dist"] diff --git a/packages/checkout/sdk-sample-app/.env b/packages/checkout/sdk-sample-app/.env new file mode 100644 index 0000000000..02269f00d9 --- /dev/null +++ b/packages/checkout/sdk-sample-app/.env @@ -0,0 +1 @@ +DISABLE_ESLINT_PLUGIN=true diff --git a/packages/checkout/sdk-sample-app/.gitignore b/packages/checkout/sdk-sample-app/.gitignore index 4d29575de8..e72ae06a01 100644 --- a/packages/checkout/sdk-sample-app/.gitignore +++ b/packages/checkout/sdk-sample-app/.gitignore @@ -17,6 +17,7 @@ .env.development.local .env.test.local .env.production.local +!.env npm-debug.log* yarn-debug.log* diff --git a/packages/checkout/sdk-sample-app/package.json b/packages/checkout/sdk-sample-app/package.json index 84511563c1..76d89989b7 100644 --- a/packages/checkout/sdk-sample-app/package.json +++ b/packages/checkout/sdk-sample-app/package.json @@ -18,6 +18,8 @@ "@imtbl/checkout-sdk": "0.0.0", "@imtbl/checkout-widgets": "0.0.0", "@imtbl/config": "0.0.0", + "@imtbl/orderbook": "0.0.0", + "@imtbl/passport": "0.0.0", "embla-carousel-react": "^8.1.5", "ethers": "^5.7.2", "framer-motion": "^11.0.6", diff --git a/packages/checkout/sdk-sample-app/src/components/Swap.tsx b/packages/checkout/sdk-sample-app/src/components/Swap.tsx new file mode 100644 index 0000000000..20ac799146 --- /dev/null +++ b/packages/checkout/sdk-sample-app/src/components/Swap.tsx @@ -0,0 +1,227 @@ +import { ChainId, Checkout, GetBalanceResult, TokenInfo } from '@imtbl/checkout-sdk'; +import { Web3Provider } from '@ethersproject/providers'; +import LoadingButton from './LoadingButton'; +import { useEffect, useState } from 'react'; +import { SuccessMessage, ErrorMessage, WarningMessage } from './messages'; +import { Box, FormControl, TextInput } from '@biom3/react'; +import React from 'react'; + +interface SwapProps { + checkout: Checkout | undefined; + provider: Web3Provider | undefined; +} + +export default function Swap(props: SwapProps) { + const { provider, checkout } = props; + + const [fromToken, setFromToken] = useState(); + const [toToken, setToToken] = useState(); + const [fromAmount, setFromAmount] = useState(''); + const [toAmount, setToAmount] = useState(''); + const [slippagePercent, setSlippagePercent] = useState('0.1'); + const [maxHops, setMaxHops] = useState('2'); + const [deadline, setDeadline] = useState(() => { + const fifteenMinutesInSeconds = 15 * 60; + return Math.floor(Date.now() / 1000 + fifteenMinutesInSeconds).toString(); + }); + + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const [validationError, setValidationError] = useState(null); + + const [fromTokenDecimals, setFromTokenDecimals] = useState(18); + const [toTokenDecimals, setToTokenDecimals] = useState(18); + + const updateFromToken = (event: React.ChangeEvent) => { + setFromToken({ address: event.target.value, symbol: '', name: '', decimals: fromTokenDecimals }); + setError(null); + }; + + const updateToToken = (event: React.ChangeEvent) => { + setToToken({ address: event.target.value, symbol: '', name: '', decimals: toTokenDecimals }); + setError(null); + }; + + const updateFromTokenDecimals = (event: React.ChangeEvent) => { + const decimals = parseInt(event.target.value) || 18; + setFromTokenDecimals(decimals); + setFromToken(prevToken => prevToken ? { ...prevToken, decimals } : undefined); + setError(null); + }; + + const updateToTokenDecimals = (event: React.ChangeEvent) => { + const decimals = parseInt(event.target.value) || 18; + setToTokenDecimals(decimals); + setToToken(prevToken => prevToken ? { ...prevToken, decimals } : undefined); + setError(null); + }; + + const updateFromAmount = (event: React.ChangeEvent) => { + const newFromAmount = event.target.value; + setFromAmount(newFromAmount); + setError(null); + validateAmounts(newFromAmount, toAmount); + }; + + const updateToAmount = (event: React.ChangeEvent) => { + const newToAmount = event.target.value; + setToAmount(newToAmount); + setError(null); + validateAmounts(fromAmount, newToAmount); + }; + + const validateAmounts = (from: string, to: string) => { + if (from !== '' && to !== '') { + setValidationError('Please provide either From Amount or To Amount, not both.'); + } else { + setValidationError(null); + } + }; + + const updateSlippagePercent = (event: React.ChangeEvent) => { + setSlippagePercent(event.target.value); + setError(null); + }; + + const updateMaxHops = (event: React.ChangeEvent) => { + setMaxHops(event.target.value); + setError(null); + }; + + const updateDeadline = (event: React.ChangeEvent) => { + setDeadline(event.target.value); + setError(null); + }; + + async function performSwap() { + if (validationError) { + setError(new Error(validationError)); + return; + } + if (!checkout) { + console.error('missing checkout, please connect first'); + return; + } + if (!provider) { + console.error('missing provider, please connect first'); + return; + } + if (!fromToken || !toToken) { + console.error('missing token information'); + return; + } + setError(null); + setLoading(true); + setSuccess(false); + + try { + const result = await checkout.swap({ + provider, + fromToken, + toToken, + fromAmount, + toAmount, + slippagePercent: slippagePercent.trim() !== '' ? parseFloat(slippagePercent) : undefined, + maxHops: maxHops.trim() !== '' ? parseInt(maxHops) : undefined, + deadline: deadline.trim() !== '' ? parseInt(deadline) : undefined, + }); + console.log('Swap result:', result); + setSuccess(true); + setLoading(false); + } catch (err: any) { + setError(err); + setLoading(false); + console.log(err.message); + console.log(err.type); + console.log(err.data); + console.log(err.stack); + } + } + + useEffect(() => { + setError(null); + setLoading(false); + setSuccess(false); + }, [checkout]); + + return ( +
+ {!provider && Not connected.} + + + + + + + + + + + + + + + + + + +
From Token AddressDecimalsTo Token AddressDecimals
+ + + + + + + + + + + + + + + +
+ + From Amount + + + + To Amount + + + {validationError && {validationError}} + + Slippage Percent + + + + Max Hops + + + + Deadline (minutes) + + + + + Swap + + + {success && !error && ( + Swap successful. Check console for details. + )} + {error && ( + + {error.message}. Check console logs for more details. + + )} +
+
+ ); +} \ No newline at end of file diff --git a/packages/checkout/sdk-sample-app/src/pages/ConnectWidget.tsx b/packages/checkout/sdk-sample-app/src/pages/ConnectWidget.tsx index 046693dc12..2c207ac1fd 100644 --- a/packages/checkout/sdk-sample-app/src/pages/ConnectWidget.tsx +++ b/packages/checkout/sdk-sample-app/src/pages/ConnectWidget.tsx @@ -12,6 +12,7 @@ import { Environment } from '@imtbl/config'; import Provider from '../components/Provider'; import SendTransaction from '../components/SendTransaction'; import GetInjectedProviders from '../components/GetInjectedProviders'; +import Swap from '../components/Swap'; export default function ConnectWidget() { const [environment, setEnvironment] = useState(Environment.SANDBOX); @@ -168,6 +169,17 @@ export default function ConnectWidget() { Get injected providers + + + Swap + + + ); } diff --git a/packages/checkout/sdk/.eslintrc b/packages/checkout/sdk/.eslintrc.cjs similarity index 77% rename from packages/checkout/sdk/.eslintrc rename to packages/checkout/sdk/.eslintrc.cjs index b4d4cad6cc..62a8693da0 100644 --- a/packages/checkout/sdk/.eslintrc +++ b/packages/checkout/sdk/.eslintrc.cjs @@ -1,10 +1,10 @@ -{ - "extends": ["../.eslintrc"], +module.exports = { + "extends": ["../../../.eslintrc"], "ignorePatterns": ["jest.config.*", "rollup.config.*"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname }, "rules": { "prefer-promise-reject-errors": "off", diff --git a/packages/checkout/sdk/jest.config.ts b/packages/checkout/sdk/jest.config.ts index 493abbcc01..8201335503 100644 --- a/packages/checkout/sdk/jest.config.ts +++ b/packages/checkout/sdk/jest.config.ts @@ -4,14 +4,7 @@ const config: Config = { clearMocks: true, coverageProvider: 'v8', moduleDirectories: ['node_modules', 'src'], - moduleNameMapper: { - '@imtbl/bridge-sdk': '../../internal/bridge/sdk/src', - '@imtbl/config': '../../config/src', - '@imtbl/dex-sdk': '../../internal/dex/sdk/src', - '@imtbl/orderbook': '../../orderbook/src', - '@imtbl/blockchain-data': '../../blockchain-data/sdk/src', - '@imtbl/generated-clients': '../../internal/generated-clients/src' - }, + moduleNameMapper: { '^@imtbl/(.*)$': '/../../../node_modules/@imtbl/$1/src' }, testEnvironment: 'jsdom', transform: { '^.+\\.(t|j)sx?$': '@swc/jest', diff --git a/packages/checkout/sdk/package.json b/packages/checkout/sdk/package.json index ed51d66a2a..b3d7bfe772 100644 --- a/packages/checkout/sdk/package.json +++ b/packages/checkout/sdk/package.json @@ -10,6 +10,7 @@ "@imtbl/bridge-sdk": "0.0.0", "@imtbl/config": "0.0.0", "@imtbl/dex-sdk": "0.0.0", + "@imtbl/generated-clients": "0.0.0", "@imtbl/metrics": "0.0.0", "@imtbl/orderbook": "0.0.0", "@imtbl/passport": "0.0.0", @@ -29,6 +30,7 @@ "@rollup/plugin-replace": "^5.0.7", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", + "@swc/core": "^1.3.36", "@types/uuid": "^8.3.4", "babel-jest": "^29.5.0", "eslint": "^8.40.0", @@ -40,7 +42,22 @@ "text-encoding": "^0.7.0", "typedoc": "^0.26.5", "typedoc-plugin-markdown": "^4.2.3", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" + }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "module": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "module": "./dist/index.js", + "import": "./dist/index.js" + } }, "homepage": "https://github.com/immutable/ts-immutable-sdk", "keywords": [ @@ -51,9 +68,9 @@ "module": "dist/index.js", "repository": "immutable/ts-immutable-sdk.git", "scripts": { - "build": "rollup --config rollup.config.js", + "build": "NODE_ENV=production rollup --config rollup.config.js", "build:dev": "CHECKOUT_DEV_MODE=true yarn build", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "docs": "typedoc --plugin typedoc-plugin-markdown --skipErrorChecking --disableSources --out docs src/index.ts", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "lint:fix": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0 --fix", @@ -65,6 +82,5 @@ "typecheck": "tsc --noEmit" }, "source": "src/index.ts", - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/checkout/sdk/rollup.config.js b/packages/checkout/sdk/rollup.config.js index 51e6601052..b8e0150f1c 100644 --- a/packages/checkout/sdk/rollup.config.js +++ b/packages/checkout/sdk/rollup.config.js @@ -5,6 +5,9 @@ import json from '@rollup/plugin-json'; import terser from '@rollup/plugin-terser'; import replace from '@rollup/plugin-replace'; import nodePolyfills from 'rollup-plugin-polyfill-node'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; const commonPlugins = [ replace({ @@ -14,7 +17,7 @@ const commonPlugins = [ 'process.env.CHECKOUT_LOCAL_MODE': JSON.stringify(process.env.CHECKOUT_LOCAL_MODE || 'false'), 'process.versions': JSON.stringify(process.versions || {}), }), - typescript() + isProduction ? typescript({customConditions: ["default"]}) : swc.rollup() ] export default [ @@ -38,10 +41,10 @@ export default [ context: 'window', plugins: [ json(), - nodeResolve({ browser: true }), + nodeResolve({ browser: true, exportConditions: ['browser'] }), commonjs(), nodePolyfills(), - terser(), + terser({ keep_fnames: /./ }), ...commonPlugins, ], } diff --git a/packages/checkout/sdk/src/api/blockscout/blockscoutType.ts b/packages/checkout/sdk/src/api/blockscout/blockscoutType.ts index 3378d0d71a..9bdab8c11c 100644 --- a/packages/checkout/sdk/src/api/blockscout/blockscoutType.ts +++ b/packages/checkout/sdk/src/api/blockscout/blockscoutType.ts @@ -23,6 +23,7 @@ export interface BlockscoutTokenData { decimals: string name: string symbol: string + icon_url: string; type: BlockscoutTokenType } diff --git a/packages/checkout/sdk/src/balances/balances.test.ts b/packages/checkout/sdk/src/balances/balances.test.ts index 9790bf0033..af2ade1b4a 100644 --- a/packages/checkout/sdk/src/balances/balances.test.ts +++ b/packages/checkout/sdk/src/balances/balances.test.ts @@ -315,15 +315,13 @@ describe('balances', () => { }); it('should fail if no wallet address or provider are given', async () => { + jest.spyOn(Blockscout, 'isChainSupported').mockReturnValue(false); + let message; try { await getAllBalances( { - remote: { - getTokensConfig: () => ({ - blockscout: false, - }), - }, + remote: {}, networkMap: testCheckoutConfig.networkMap, } as unknown as CheckoutConfiguration, undefined, @@ -337,15 +335,13 @@ describe('balances', () => { }); it('should fail if no provider is given and indexer is disabled', async () => { + jest.spyOn(Blockscout, 'isChainSupported').mockReturnValue(false); + let message; try { await getAllBalances( { - remote: { - getTokensConfig: () => ({ - blockscout: false, - }), - }, + remote: {}, networkMap: testCheckoutConfig.networkMap, } as unknown as CheckoutConfiguration, undefined, @@ -359,12 +355,11 @@ describe('balances', () => { }); it('should call getBalance and getERC20Balance functions with native and ERC20 tokens', async () => { + jest.spyOn(Blockscout, 'isChainSupported').mockReturnValue(false); + const getAllBalancesResult = await getAllBalances( { remote: { - getTokensConfig: () => ({ - blockscout: false, - }), getHttpClient: () => mockedHttpClient, }, networkMap: testCheckoutConfig.networkMap, @@ -415,7 +410,24 @@ describe('balances', () => { ); }); - it('should call getIndexerBalance', async () => { + it('should call getBlockscoutBalance', async () => { + (tokens.getTokenAllowList as jest.Mock).mockReturnValue({ + tokens: [ + { + name: 'Immutable X', + address: 'native', + symbol: 'IMX', + decimals: 18, + } as TokenInfo, + { + name: ChainName.ETHEREUM, + address: '0x65AA7a21B0f3ce9B478aAC3408fE75b423939b1F', + symbol: 'ETH', + decimals: 18, + } as TokenInfo, + ], + }); + getTokensByWalletAddressMock = jest.fn().mockResolvedValue({ items: [ { @@ -451,9 +463,6 @@ describe('balances', () => { const getAllBalancesResult = await getAllBalances( { remote: { - getTokensConfig: () => ({ - blockscout: true, - }), getHttpClient: () => mockedHttpClient, }, networkMap: testCheckoutConfig.networkMap, @@ -491,7 +500,7 @@ describe('balances', () => { ]); }); - it('should call getIndexerBalance with undefined filterTokens', async () => { + it('should call getBlockscoutBalance with undefined filterTokens', async () => { getTokenAllowListMock = jest.fn().mockReturnValue({ tokens: [], } as GetTokenAllowListResult); @@ -534,9 +543,6 @@ describe('balances', () => { const getAllBalancesResult = await getAllBalances( { remote: { - getTokensConfig: () => ({ - blockscout: true, - }), getHttpClient: () => mockedHttpClient, }, networkMap: new CheckoutConfiguration( @@ -557,7 +563,7 @@ describe('balances', () => { expect(getAllBalancesResult.balances).toEqual([]); }); - it('should call getIndexerBalance and return native balance on ERC20 404', async () => { + it('should call getBlockscoutBalance and return native balance on ERC20 404', async () => { getTokensByWalletAddressMock = jest.fn().mockRejectedValue( { code: HttpStatusCode.NotFound, message: 'not found' }, ); @@ -580,9 +586,6 @@ describe('balances', () => { const getAllBalancesResult = await getAllBalances( { remote: { - getTokensConfig: () => ({ - blockscout: true, - }), getHttpClient: () => mockedHttpClient, }, networkMap: testCheckoutConfig.networkMap, @@ -609,7 +612,18 @@ describe('balances', () => { ]); }); - it('should call getIndexerBalance and return ERC20 balances on native 404', async () => { + it('should call getBlockscoutBalance and return ERC20 balances on native 404', async () => { + (tokens.getTokenAllowList as jest.Mock).mockReturnValue({ + tokens: [ + { + name: ChainName.ETHEREUM, + address: '0x65AA7a21B0f3ce9B478aAC3408fE75b423939b1F', + symbol: 'ETH', + decimals: 18, + } as TokenInfo, + ], + }); + getTokensByWalletAddressMock = jest.fn().mockResolvedValue({ items: [ { @@ -639,9 +653,6 @@ describe('balances', () => { const getAllBalancesResult = await getAllBalances( { remote: { - getTokensConfig: () => ({ - blockscout: true, - }), getHttpClient: () => mockedHttpClient, }, networkMap: testCheckoutConfig.networkMap, @@ -669,7 +680,7 @@ describe('balances', () => { ]); }); - it('should call getIndexerBalance and return empty balance due to 404', async () => { + it('should call getBlockscoutBalance and return empty balance due to 404', async () => { getTokensByWalletAddressMock = jest.fn().mockRejectedValue( { code: HttpStatusCode.NotFound, message: 'not found' }, ); @@ -686,9 +697,6 @@ describe('balances', () => { const getAllBalancesResult = await getAllBalances( { remote: { - getTokensConfig: () => ({ - blockscout: true, - }), getHttpClient: () => mockedHttpClient, }, networkMap: testCheckoutConfig.networkMap, @@ -720,9 +728,6 @@ describe('balances', () => { getAllBalancesResult = await getAllBalances( { remote: { - getTokensConfig: () => ({ - blockscout: true, - }), getHttpClient: () => mockedHttpClient, }, networkMap: testCheckoutConfig.networkMap, @@ -780,7 +785,7 @@ describe('balances', () => { }]; testCases.forEach(async (testCase) => { - it('should call getIndexerBalance and throw error', async () => { + it('should call getBlockscoutBalance and throw error', async () => { getTokensByWalletAddressMock = jest.fn().mockRejectedValue( { code: HttpStatusCode.Forbidden, message: testCase.errorMessage }, ); @@ -797,9 +802,6 @@ describe('balances', () => { await getAllBalances( { remote: { - getTokensConfig: () => ({ - blockscout: true, - }), getHttpClient: () => mockedHttpClient, }, networkMap: testCheckoutConfig.networkMap, @@ -831,11 +833,7 @@ describe('balances', () => { try { await getAllBalances( { - remote: { - getTokensConfig: () => ({ - blockscout: true, - }), - }, + remote: {}, networkMap: testCheckoutConfig.networkMap, } as unknown as CheckoutConfiguration, jest.fn() as unknown as Web3Provider, diff --git a/packages/checkout/sdk/src/balances/balances.ts b/packages/checkout/sdk/src/balances/balances.ts index 35d724b25e..5874f4ec22 100644 --- a/packages/checkout/sdk/src/balances/balances.ts +++ b/packages/checkout/sdk/src/balances/balances.ts @@ -12,10 +12,11 @@ import { import { CheckoutError, CheckoutErrorType, withCheckoutError } from '../errors'; import { getNetworkInfo } from '../network'; import { getERC20TokenInfo, getTokenAllowList } from '../tokens'; -import { CheckoutConfiguration, getL1ChainId } from '../config'; +import { CheckoutConfiguration } from '../config'; import { Blockscout, BlockscoutToken, + BlockscoutTokenData, BlockscoutTokens, BlockscoutTokenType, } from '../api/blockscout'; @@ -96,7 +97,7 @@ const blockscoutClientMap: Map = new Map(); // blockscout map and therefore clear all the cache. export const resetBlockscoutClientMap = () => blockscoutClientMap.clear(); -export const getIndexerBalance = async ( +export const getBlockscoutBalance = async ( config: CheckoutConfiguration, walletAddress: string, chainId: ChainId, @@ -107,7 +108,7 @@ export const getIndexerBalance = async ( const shouldFilter = filterTokens !== undefined; const mapFilterTokens = Object.assign( {}, - ...((filterTokens ?? []).map((t) => ({ [t.address || NATIVE]: t }))), + ...((filterTokens ?? []).map((t) => ({ [t.address?.toLowerCase() || NATIVE]: t }))), ); // Get blockscout client for the given chain @@ -186,7 +187,8 @@ export const getIndexerBalance = async ( const balances: GetBalanceResult[] = []; items.forEach((item) => { - if (shouldFilter && !mapFilterTokens[item.token.address]) return; + const allowlistedToken = mapFilterTokens[item.token.address.toLowerCase()]; + if (shouldFilter && !allowlistedToken) return; const tokenData = item.token || {}; @@ -196,9 +198,12 @@ export const getIndexerBalance = async ( let decimals = parseInt(tokenData.decimals, 10); if (Number.isNaN(decimals)) decimals = DEFAULT_TOKEN_DECIMALS; + const icon = (tokenData as BlockscoutTokenData).icon_url ?? allowlistedToken.icon; + const token = { ...tokenData, decimals, + icon, }; const formattedBalance = utils.formatUnits(item.value, token.decimals); @@ -304,35 +309,18 @@ export const getAllBalances = async ( }, ); - // In order to prevent unnecessary RPC calls - // let's use the Indexer if available for the - // given chain. - let flag = false; - try { - flag = (await config.remote.getTokensConfig(chainId)).blockscout || flag; - } catch (err: any) { - // eslint-disable-next-line no-console - console.error(err); - } - if (forceFetch) { resetBlockscoutClientMap(); } - let address = walletAddress; - if (flag && Blockscout.isChainSupported(chainId)) { - // This is a hack because the widgets are still using the tokens symbol - // to drive the conversions. If we remove all the token symbols from e.g. zkevm - // then we would not have fiat conversions. - // Please remove this hack once https://immutable.atlassian.net/browse/WT-1710 - // is done. - const isL1Chain = getL1ChainId(config) === chainId; - if (!address) address = await web3Provider?.getSigner().getAddress(); + if (Blockscout.isChainSupported(chainId)) { + const address = walletAddress ?? await web3Provider?.getSigner().getAddress(); + try { return await measureAsyncExecution( config, `Time to fetch balances using blockscout for ${chainId}`, - getIndexerBalance(config, address!, chainId, isL1Chain ? tokens : undefined), + getBlockscoutBalance(config, address!, chainId, tokens), ); } catch (error) { // Blockscout rate limiting, fallback to RPC node diff --git a/packages/checkout/sdk/src/config/config.ts b/packages/checkout/sdk/src/config/config.ts index 489e2fb679..3ee0ca5005 100644 --- a/packages/checkout/sdk/src/config/config.ts +++ b/packages/checkout/sdk/src/config/config.ts @@ -14,6 +14,7 @@ import { SANDBOX_CHAIN_ID_NETWORK_MAP, } from '../env'; import { HttpClient } from '../api/http/httpClient'; +import { TokensFetcher } from './tokensFetcher'; export class CheckoutConfigurationError extends Error { public message: string; @@ -67,6 +68,8 @@ export class CheckoutConfiguration { readonly remote: RemoteConfigFetcher; + readonly tokens: TokensFetcher; + readonly environment: Environment; readonly networkMap: NetworkMap; @@ -98,5 +101,10 @@ export class CheckoutConfiguration { isDevelopment: this.isDevelopment, isProduction: this.isProduction, }); + + this.tokens = new TokensFetcher(httpClient, this.remote, { + isDevelopment: this.isDevelopment, + isProduction: this.isProduction, + }); } } diff --git a/packages/checkout/sdk/src/config/remoteConfigFetcher.test.ts b/packages/checkout/sdk/src/config/remoteConfigFetcher.test.ts index 718fb92bfc..53b5be9f69 100644 --- a/packages/checkout/sdk/src/config/remoteConfigFetcher.test.ts +++ b/packages/checkout/sdk/src/config/remoteConfigFetcher.test.ts @@ -121,66 +121,5 @@ describe('RemoteConfig', () => { ); }); }); - - describe('tokens', () => { - it(`should fetch tokens and cache them [${env}]`, async () => { - const mockResponse = { - status: 200, - data: { - connect: { - walletConnect: false, - }, - [ChainId.IMTBL_ZKEVM_DEVNET]: { - allowed: [ - { - address: '0xd686c80dc76766fa16eb95a4ad63d17937c7723c', - decimals: 18, - name: 'token-aa-testnet', - symbol: 'AA', - }, - ], - }, - [ChainId.SEPOLIA]: { - metadata: [ - { - address: '0xd686c80dc76766fa16eb95a4ad63d17937c7723c', - decimals: 18, - name: 'token-aa-testnet', - symbol: 'AA', - }, - ], - }, - }, - } as AxiosResponse; - mockedHttpClient.get.mockResolvedValueOnce(mockResponse); - - const fetcher = new RemoteConfigFetcher(mockedHttpClient, { - isDevelopment: env === ENV_DEVELOPMENT, - isProduction: env !== ENV_DEVELOPMENT && env === Environment.PRODUCTION, - }); - await fetcher.getTokensConfig(ChainId.SEPOLIA); - await fetcher.getTokensConfig(ChainId.IMTBL_ZKEVM_DEVNET); - - expect(mockedHttpClient.get).toHaveBeenCalledTimes(1); - expect(mockedHttpClient.get).toHaveBeenNthCalledWith( - 1, - `${CHECKOUT_CDN_BASE_URL[env as Environment]}/${version}/config/tokens`, - ); - }); - - it(`should return empty array if config missing [${env}]`, async () => { - const mockResponse = { - status: 200, - } as AxiosResponse; - mockedHttpClient.get.mockResolvedValueOnce(mockResponse); - - const fetcher = new RemoteConfigFetcher(mockedHttpClient, { - isDevelopment: env === ENV_DEVELOPMENT, - isProduction: env !== ENV_DEVELOPMENT && env === Environment.PRODUCTION, - }); - - expect(await fetcher.getTokensConfig(ChainId.SEPOLIA)).toEqual({}); - }); - }); }); }); diff --git a/packages/checkout/sdk/src/config/remoteConfigFetcher.ts b/packages/checkout/sdk/src/config/remoteConfigFetcher.ts index 9a8da03228..3acf7be998 100644 --- a/packages/checkout/sdk/src/config/remoteConfigFetcher.ts +++ b/packages/checkout/sdk/src/config/remoteConfigFetcher.ts @@ -1,11 +1,6 @@ import { Environment } from '@imtbl/config'; import { AxiosResponse } from 'axios'; -import { - ChainId, - ChainsTokensConfig, - RemoteConfiguration, - ChainTokensConfig, -} from '../types'; +import { RemoteConfiguration } from '../types'; import { CHECKOUT_CDN_BASE_URL, ENV_DEVELOPMENT } from '../env'; import { HttpClient } from '../api/http'; import { CheckoutError, CheckoutErrorType } from '../errors'; @@ -24,8 +19,6 @@ export class RemoteConfigFetcher { private configCache: RemoteConfiguration | undefined; - private tokensCache: ChainsTokensConfig | undefined; - private version: string = 'v1'; constructor(httpClient: HttpClient, params: RemoteConfigParams) { @@ -54,7 +47,6 @@ export class RemoteConfigFetcher { ); } } - return responseData!; } @@ -80,28 +72,6 @@ export class RemoteConfigFetcher { return this.configCache; } - private async loadConfigTokens(): Promise { - if (this.tokensCache) return this.tokensCache; - - let response: AxiosResponse; - try { - response = await this.httpClient.get( - `${this.getEndpoint()}/${this.version}/config/tokens`, - ); - } catch (err: any) { - throw new CheckoutError( - `Error: ${err.message}`, - CheckoutErrorType.API_ERROR, - { error: err }, - ); - } - - // Ensure that the configuration is valid - this.tokensCache = this.parseResponse(response); - - return this.tokensCache; - } - public async getConfig( key?: keyof RemoteConfiguration, ): Promise< @@ -115,11 +85,5 @@ export class RemoteConfigFetcher { return config[key]; } - public async getTokensConfig(chainId: ChainId): Promise { - const config = await this.loadConfigTokens(); - if (!config || !config[chainId]) return {}; - return config[chainId] ?? []; - } - public getHttpClient = () => this.httpClient; } diff --git a/packages/checkout/sdk/src/config/tokensFetcher.test.ts b/packages/checkout/sdk/src/config/tokensFetcher.test.ts new file mode 100644 index 0000000000..d0fbb00198 --- /dev/null +++ b/packages/checkout/sdk/src/config/tokensFetcher.test.ts @@ -0,0 +1,177 @@ +import { Environment } from '@imtbl/config'; +import { AxiosResponse } from 'axios'; +import { ChainId, RemoteConfiguration } from '../types'; +import { RemoteConfigFetcher } from './remoteConfigFetcher'; +import { ENV_DEVELOPMENT } from '../env'; +import { HttpClient } from '../api/http'; +import { TokensFetcher } from './tokensFetcher'; + +jest.mock('../api/http'); +jest.mock('./remoteConfigFetcher'); + +describe('TokensFetcher', () => { + let mockedHttpClient: jest.Mocked; + let mockedConfigClient: jest.Mocked; + + afterEach(() => { + jest.clearAllMocks(); + }); + + beforeEach(() => { + mockedHttpClient = new HttpClient() as jest.Mocked; + mockedConfigClient = new RemoteConfigFetcher(mockedHttpClient, { + isDevelopment: true, + isProduction: false, + }) as jest.Mocked; + + mockedConfigClient.getConfig.mockResolvedValue({ + [ChainId.IMTBL_ZKEVM_TESTNET]: 'native', + [ChainId.SEPOLIA]: '0xe2629e08f4125d14e446660028bD98ee60EE69F2', + } as unknown as RemoteConfiguration); + }); + + [Environment.PRODUCTION, Environment.SANDBOX, ENV_DEVELOPMENT].forEach( + (env) => { + describe('getTokensConfig', () => { + it(`should fetch tokens and cache them [${env}]`, async () => { + const mockTokensResponse = { + status: 200, + data: { + result: [ + { + chain: { + id: 'eip155:13473', + name: 'imtbl-zkevm-testnet', + }, + contract_address: '0xb8ee289c64c1a0dc0311364721ada8c3180d838c', + decimals: 18, + image_url: 'https://example.com/gog.svg', + is_canonical: true, + name: 'Guild of Guardians', + root_chain_id: 'eip155:11155111', + root_contract_address: '0xfe9df9ebe5fbd94b00247613b6cf7629891954e2', + symbol: 'GOG', + verification_status: 'verified', + }, + { + chain: { + id: 'eip155:13473', + name: 'imtbl-zkevm-testnet', + }, + contract_address: '0xe9E96d1aad82562b7588F03f49aD34186f996478', + decimals: 18, + image_url: 'https://example.com/eth.svg', + is_canonical: true, + name: 'Ethereum', + root_chain_id: 'eip155:11155111', + root_contract_address: '0x0000000000000000000000000000000000000eee', + symbol: 'ETH', + verification_status: 'verified', + }, + { + chain: { + id: 'eip155:13473', + name: 'imtbl-zkevm-testnet', + }, + contract_address: '0x3b2d8a1931736fc321c24864bceee981b11c3c50', + decimals: 6, + image_url: null, + is_canonical: true, + name: 'USDZ', + root_chain_id: null, + root_contract_address: null, + symbol: 'USDZ', + verification_status: 'verified', + }, + { + chain: { + id: 'eip155:13473', + }, + name: 'Invalid token', + contract_address: '0xinvalid', + symbol: null, + decimals: null, + }, + ], + }, + } as AxiosResponse; + mockedHttpClient.get.mockResolvedValueOnce(mockTokensResponse); + + const fetcher = new TokensFetcher( + mockedHttpClient, + mockedConfigClient, + { + isDevelopment: env === ENV_DEVELOPMENT, + isProduction: + env !== ENV_DEVELOPMENT && env === Environment.PRODUCTION, + }, + ); + const tokensZkEVM = await fetcher.getTokensConfig( + ChainId.IMTBL_ZKEVM_TESTNET, + ); + const tokensSepolia = await fetcher.getTokensConfig(ChainId.SEPOLIA); + + // Number of tokens per chain is correct + expect(tokensZkEVM).toHaveLength(4); + expect(tokensSepolia).toHaveLength(3); + + // Tokens are correctly populated + expect(tokensZkEVM.find((token) => token.symbol === 'GOG')).toEqual({ + address: '0xb8ee289c64c1a0dc0311364721ada8c3180d838c', + decimals: 18, + icon: 'https://example.com/gog.svg', + name: 'Guild of Guardians', + symbol: 'GOG', + }); + + // Tokens with invalid info are ignored + expect(tokensZkEVM.find((token) => token.address === '0xinvalid')).toBeUndefined(); + expect(tokensSepolia.find((token) => token.address === '0xinvalid')).toBeUndefined(); + + // IMX token is populated + expect( + tokensZkEVM.find((token) => token.symbol === 'IMX'), + ).toHaveProperty('address', 'native'); + expect( + tokensSepolia.find((token) => token.symbol === 'IMX'), + ).toHaveProperty('address', '0xe2629e08f4125d14e446660028bD98ee60EE69F2'); + + // ETH root contract is mapped to native in L1 + expect( + tokensSepolia.find((token) => token.symbol === 'ETH'), + ).toHaveProperty('address', 'native'); + expect( + tokensZkEVM.find((token) => token.symbol === 'ETH'), + ).toHaveProperty('address', '0xe9e96d1aad82562b7588f03f49ad34186f996478'); + + // HTTP request is cached after first occurrence + expect(mockedHttpClient.get).toHaveBeenCalledTimes(1); + }); + + it(`should return empty array if config missing [${env}]`, async () => { + mockedConfigClient.getConfig.mockResolvedValue({} as unknown as RemoteConfiguration); + + const mockResponse = { + status: 200, + data: { + result: [], + }, + } as AxiosResponse; + mockedHttpClient.get.mockResolvedValueOnce(mockResponse); + + const fetcher = new TokensFetcher( + mockedHttpClient, + mockedConfigClient, + { + isDevelopment: env === ENV_DEVELOPMENT, + isProduction: + env !== ENV_DEVELOPMENT && env === Environment.PRODUCTION, + }, + ); + + expect(await fetcher.getTokensConfig(ChainId.SEPOLIA)).toEqual([]); + }); + }); + }, + ); +}); diff --git a/packages/checkout/sdk/src/config/tokensFetcher.ts b/packages/checkout/sdk/src/config/tokensFetcher.ts new file mode 100644 index 0000000000..0e662fad22 --- /dev/null +++ b/packages/checkout/sdk/src/config/tokensFetcher.ts @@ -0,0 +1,182 @@ +import { Environment } from '@imtbl/config'; +import { AxiosResponse } from 'axios'; +import { + ChainId, ChainSlug, ChainTokensConfig, ImxAddressConfig, TokenInfo, +} from '../types'; +import { ENV_DEVELOPMENT, IMMUTABLE_API_BASE_URL } from '../env'; +import { HttpClient } from '../api/http'; +import { CheckoutError, CheckoutErrorType } from '../errors'; +import { RemoteConfigFetcher } from './remoteConfigFetcher'; + +const INDEXER_ETH_ROOT_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000000eee'; + +type TokensEndpointResult = { + chain: { + id: string; + name: string; + }; + contract_address: string; + decimals: number; + image_url: string | null; + is_canonical: boolean; + name: string; + symbol: string; + root_chain_id: string | null; + root_contract_address: string | null; +}; + +type TokensEndpointResponse = { + result: TokensEndpointResult[]; +}; + +export type RemoteConfigParams = { + isDevelopment: boolean; + isProduction: boolean; +}; + +export class TokensFetcher { + private httpClient: HttpClient; + + private remoteConfig: RemoteConfigFetcher; + + private readonly isDevelopment: boolean; + + private readonly isProduction: boolean; + + private tokensCache: ChainTokensConfig | undefined; + + constructor(httpClient: HttpClient, remoteConfig: RemoteConfigFetcher, params: RemoteConfigParams) { + this.isDevelopment = params.isDevelopment; + this.isProduction = params.isProduction; + this.httpClient = httpClient; + this.remoteConfig = remoteConfig; + } + + private getBaseUrl = () => { + if (this.isDevelopment) return IMMUTABLE_API_BASE_URL[ENV_DEVELOPMENT]; + if (this.isProduction) return IMMUTABLE_API_BASE_URL[Environment.PRODUCTION]; + return IMMUTABLE_API_BASE_URL[Environment.SANDBOX]; + }; + + private getChainSlug = () => { + if (this.isDevelopment) return ChainSlug.IMTBL_ZKEVM_DEVNET; + if (this.isProduction) return ChainSlug.IMTBL_ZKEVM_MAINNET; + return ChainSlug.IMTBL_ZKEVM_TESTNET; + }; + + private async loadTokens(): Promise { + if (this.tokensCache) { + return this.tokensCache; + } + + let response: AxiosResponse; + try { + response = await this.httpClient.get( + `${this.getBaseUrl()}/v1/chains/${this.getChainSlug()}/tokens?verification_status=verified&is_canonical=true`, + ); + } catch (err: any) { + throw new CheckoutError( + `Error: ${err.message}`, + CheckoutErrorType.API_ERROR, + { error: err }, + ); + } + + const responseData = this.parseResponse(response); + + this.tokensCache = await this.getMappingsForTokensResponse(responseData?.result || []); + + return this.tokensCache; + } + + public async getTokensConfig(chainId: ChainId): Promise { + const config = await this.loadTokens(); + if (!config || !config[chainId]) return []; + + return config[chainId] ?? []; + } + + private async getMappingsForTokensResponse(tokenList: TokensEndpointResult[]): Promise { + const tokens: ChainTokensConfig = {}; + + const imxMappings = await this.fetchIMXTokenMappings(); + + Object.keys(imxMappings).forEach((chain) => { + const chainId = parseInt(chain, 10) as ChainId; + tokens[chainId] = []; + + tokens[chainId]?.push({ + address: imxMappings[chain], + decimals: 18, + name: 'IMX', + symbol: 'IMX', + }); + }); + + tokenList.forEach((token) => { + const chainId = parseInt(token.chain.id.split('eip155:').pop() || '', 10) as ChainId; + + if (!token.symbol || !token.decimals) { + return; + } + + if (!tokens[chainId]) { + tokens[chainId] = []; + } + + const tokenInfo: TokenInfo = { + address: token.contract_address.toLowerCase(), + decimals: token.decimals, + name: token.name, + symbol: token.symbol, + icon: token.image_url ?? undefined, + }; + + tokens[chainId]?.push(tokenInfo); + + const rootChainId = parseInt(token.root_chain_id?.split('eip155:').pop() || '', 10) as ChainId; + let address = token.root_contract_address?.toLowerCase(); + + if (rootChainId && address) { + if (!tokens[rootChainId]) { + tokens[rootChainId] = []; + } + + if (address === INDEXER_ETH_ROOT_CONTRACT_ADDRESS) { + address = 'native'; + } + + tokens[rootChainId]?.push({ + ...tokenInfo, + address, + }); + } + }); + + return tokens; + } + + // eslint-disable-next-line class-methods-use-this + private parseResponse(response: AxiosResponse): TokensEndpointResponse | undefined { + let responseData: TokensEndpointResponse = response.data; + if (response.data && typeof response.data !== 'object') { + try { + responseData = JSON.parse(response.data); + } catch (err: any) { + throw new CheckoutError( + 'Invalid token data', + CheckoutErrorType.API_ERROR, + { error: err }, + ); + } + } + + return responseData; + } + + private async fetchIMXTokenMappings() { + return (await this.remoteConfig.getConfig( + 'imxAddressMapping', + )) as ImxAddressConfig; + } +} diff --git a/packages/checkout/sdk/src/errors/checkoutError.ts b/packages/checkout/sdk/src/errors/checkoutError.ts index 7b2f7a1221..6e1246aa48 100644 --- a/packages/checkout/sdk/src/errors/checkoutError.ts +++ b/packages/checkout/sdk/src/errors/checkoutError.ts @@ -42,6 +42,7 @@ export enum CheckoutErrorType { API_ERROR = 'API_ERROR', ORDER_EXPIRED_ERROR = 'ORDER_EXPIRED_ERROR', WIDGETS_SCRIPT_LOAD_ERROR = 'WIDGETS_SCRIPT_LOAD_ERROR', + APPROVAL_TRANSACTION_FAILED = 'APPROVAL_TRANSACTION_FAILED', } /** diff --git a/packages/checkout/sdk/src/index.ts b/packages/checkout/sdk/src/index.ts index 742b2b1f52..5e1ddf54de 100644 --- a/packages/checkout/sdk/src/index.ts +++ b/packages/checkout/sdk/src/index.ts @@ -22,11 +22,7 @@ export { ChainId, ChainName, ChainSlug, - CheckoutStatus, - EIP1193Provider, - EIP6963ProviderInfo, - EIP6963ProviderDetail, - ExchangeType, + CheckoutStatus, ExchangeType, FeeType, FundingStepType, GasEstimateType, @@ -40,6 +36,11 @@ export { WalletProviderName, WalletProviderRdns, } from './types'; +export type { + EIP1193Provider, + EIP6963ProviderInfo, + EIP6963ProviderDetail, +} from './types'; export type { AllowedNetworkConfig, @@ -131,9 +132,11 @@ export type { SmartCheckoutRouter, SmartCheckoutSufficient, SuccessfulGaslessCancellation, + SwapFees, SwapFundingStep, SwitchNetworkParams, SwitchNetworkResult, + TelemetryConfig, TokenAmountEstimate, TokenBalance, TokenFilter, @@ -144,11 +147,9 @@ export type { } from './types'; export { - PostMessageHandler, - PostMessageHandlerConfiguration, - PostMessageHandlerEventType, - PostMessageData, + PostMessageHandler, PostMessageHandlerEventType, } from './postMessageHandler'; +export type { PostMessageHandlerConfiguration, PostMessageData } from './postMessageHandler'; export { isAddressSanctioned } from './sanctions'; diff --git a/packages/checkout/sdk/src/postMessageHandler/postMessageEventTypes.ts b/packages/checkout/sdk/src/postMessageHandler/postMessageEventTypes.ts index a34e0f6d74..0c6b7eab8d 100644 --- a/packages/checkout/sdk/src/postMessageHandler/postMessageEventTypes.ts +++ b/packages/checkout/sdk/src/postMessageHandler/postMessageEventTypes.ts @@ -1,7 +1,11 @@ +import { IMTBLWidgetEvents } from '../widgets/definitions/events/widgets'; +import { WidgetEventData, WidgetType } from '../widgets/definitions/types'; + export enum PostMessageHandlerEventType { SYN = 'IMTBL_POST_MESSAGE_SYN', ACK = 'IMTBL_POST_MESSAGE_ACK', PROVIDER_RELAY = 'IMTBL_PROVIDER_RELAY', + PROVIDER_UPDATED = 'IMTBL_PROVIDER_UPDATED', EIP_6963_EVENT = 'IMTBL_EIP_6963_EVENT', WIDGET_EVENT = 'IMTBL_CHECKOUT_WIDGET_EVENT', } @@ -10,6 +14,40 @@ export type PostMessageProviderRelayData = any; export type PostMessageEIP6963Data = any; -export type PostMessagePayload = - | PostMessageProviderRelayData - | PostMessageEIP6963Data; +export type PostMessageWidgetEventData< + T extends WidgetType = WidgetType.CHECKOUT, +> = { + type: IMTBLWidgetEvents.IMTBL_CHECKOUT_WIDGET_EVENT; + detail: { + [K in keyof WidgetEventData[T]]: { + type: K; + data: WidgetEventData[T][K]; + }; + }[keyof WidgetEventData[T]]; +}; + +export type PostMessageData = + | { + type: PostMessageHandlerEventType.SYN; + payload: any; + } + | { + type: PostMessageHandlerEventType.ACK; + payload: any; + } + | { + type: PostMessageHandlerEventType.PROVIDER_RELAY; + payload: PostMessageProviderRelayData; + } + | { + type: PostMessageHandlerEventType.PROVIDER_UPDATED; + payload: any; + } + | { + type: PostMessageHandlerEventType.EIP_6963_EVENT; + payload: PostMessageEIP6963Data; + } + | { + type: PostMessageHandlerEventType.WIDGET_EVENT; + payload: PostMessageWidgetEventData; + }; diff --git a/packages/checkout/sdk/src/postMessageHandler/postMessageHandler.ts b/packages/checkout/sdk/src/postMessageHandler/postMessageHandler.ts index 7a295faf37..c79bdd135f 100644 --- a/packages/checkout/sdk/src/postMessageHandler/postMessageHandler.ts +++ b/packages/checkout/sdk/src/postMessageHandler/postMessageHandler.ts @@ -1,6 +1,6 @@ import { PostMessageHandlerEventType, - PostMessagePayload, + PostMessageData, } from './postMessageEventTypes'; export type PostMessageHandlerConfiguration = { @@ -9,11 +9,6 @@ export type PostMessageHandlerConfiguration = { eventSource?: WindowProxy; }; -export type PostMessageData = { - type: PostMessageHandlerEventType; - payload: PostMessagePayload; -}; - export class PostMessageHandler { private init: boolean = false; @@ -31,9 +26,6 @@ export class PostMessageHandler { private logger: (...args: any[]) => void; - static isSynOrAck = (type: PostMessageHandlerEventType): boolean => type === PostMessageHandlerEventType.SYN - || type === PostMessageHandlerEventType.ACK; - constructor({ targetOrigin, eventTarget, @@ -66,11 +58,14 @@ export class PostMessageHandler { this.logger = logger; } - private handshake = (): void => { + static isSynOrAck = (type: PostMessageHandlerEventType): boolean => type === PostMessageHandlerEventType.SYN + || type === PostMessageHandlerEventType.ACK; + + protected handshake = (): void => { this.postMessage(PostMessageHandlerEventType.SYN, null); }; - private onMessage = (event: MessageEvent): void => { + protected onMessage = (event: MessageEvent): void => { if (event.origin !== this.targetOrigin) return; if (this.init) { diff --git a/packages/checkout/sdk/src/sdk.test.ts b/packages/checkout/sdk/src/sdk.test.ts index 9ab48fac68..911c40514e 100644 --- a/packages/checkout/sdk/src/sdk.test.ts +++ b/packages/checkout/sdk/src/sdk.test.ts @@ -55,6 +55,8 @@ import { FiatRampParams, ExchangeType } from './types/fiatRamp'; import { getItemRequirementsFromRequirements } from './smartCheckout/itemRequirements'; import { CheckoutErrorType } from './errors'; import { availabilityService } from './availability'; +import * as swap from './swap'; +import { SwapParams, SwapResult } from './types/swap'; jest.mock('./connect'); jest.mock('./network'); @@ -72,6 +74,7 @@ jest.mock('./smartCheckout'); jest.mock('./fiatRamp'); jest.mock('./smartCheckout/itemRequirements'); jest.mock('./availability'); +jest.mock('./swap'); describe('Connect', () => { let providerMock: ExternalProvider; @@ -989,4 +992,133 @@ describe('Connect', () => { expect(checkout.availability.checkDexAvailability).toBeCalledTimes(1); }); }); + + describe('Swap', () => { + let checkout: Checkout; + let web3Provider: Web3Provider; + + beforeEach(() => { + jest.resetAllMocks(); + + providerMock.request = jest.fn().mockResolvedValue('0x1'); + + web3Provider = new Web3Provider(providerMock, ChainId.ETHEREUM); + + (validateProvider as jest.Mock).mockResolvedValue(web3Provider); + + checkout = new Checkout({ + baseConfig: { environment: Environment.PRODUCTION }, + }); + }); + + it('should call swap function with correct parameters', async () => { + const swapParams: SwapParams = { + provider: web3Provider, + fromToken: { address: '0xFromTokenAddress', decimals: 18 } as TokenInfo, + toToken: { address: '0xToTokenAddress', decimals: 18 } as TokenInfo, + fromAmount: '1000000000000000000', // 1 ETH in wei + toAmount: '1000000', // Example USDC amount + slippagePercent: 0.5, + maxHops: 3, + deadline: 1234567890, + }; + + const mockSwapResult: SwapResult = { + swap: { + transaction: { + to: '0xSwapContractAddress', + data: '0xEncodedSwapData', + value: '0', + }, + gasFeeEstimate: { + token: { + chainId: 0, + address: '', + decimals: 0, + symbol: undefined, + name: undefined, + }, + value: BigNumber.from('1000000000000000000'), + }, + }, + quote: { + slippage: 0.1, + fees: [], + amount: { + token: { + chainId: 0, + address: '', + decimals: 0, + symbol: undefined, + name: undefined, + }, + value: BigNumber.from('1000000000000000000'), + }, + amountWithMaxSlippage: { + token: { + chainId: 0, + address: '', + decimals: 0, + symbol: undefined, + name: undefined, + }, + value: BigNumber.from('1050000000000000000'), // Example value with 5% max slippage + }, + }, + swapReceipt: { + to: '0xRecipientAddress', + from: '0xSenderAddress', + contractAddress: '0xContractAddress', + transactionIndex: 1, + gasUsed: BigNumber.from('21000'), + logsBloom: '0x', + blockHash: '0xBlockHash', + transactionHash: '0xTransactionHash', + logs: [], + blockNumber: 12345, + confirmations: 2, + cumulativeGasUsed: BigNumber.from('100000'), + effectiveGasPrice: BigNumber.from('20000000000'), + status: 1, + type: 2, + byzantium: true, + }, + }; + + (swap.swap as jest.Mock).mockResolvedValue(mockSwapResult); + + const result = await checkout.swap(swapParams); + + expect(validateProvider).toHaveBeenCalledWith(checkout.config, web3Provider); + expect(swap.swap).toHaveBeenCalledWith( + checkout.config, + web3Provider, + swapParams.fromToken, + swapParams.toToken, + swapParams.fromAmount, + swapParams.toAmount, + swapParams.slippagePercent, + swapParams.maxHops, + swapParams.deadline, + ); + expect(result).toEqual(mockSwapResult); + }); + + it('should throw an error if provider validation fails', async () => { + const error = new Error('Invalid provider'); + (validateProvider as jest.Mock).mockRejectedValue(error); + + const swapParams: SwapParams = { + provider: web3Provider, + fromToken: { address: '0xFromTokenAddress', decimals: 18 } as TokenInfo, + toToken: { address: '0xToTokenAddress', decimals: 18 } as TokenInfo, + fromAmount: '1000000000000000000', + toAmount: '1000000', + slippagePercent: 0.5, + }; + + await expect(checkout.swap(swapParams)).rejects.toThrow('Invalid provider'); + expect(swap.swap).not.toHaveBeenCalled(); + }); + }); }); diff --git a/packages/checkout/sdk/src/sdk.ts b/packages/checkout/sdk/src/sdk.ts index d94cab5e25..68889abb29 100644 --- a/packages/checkout/sdk/src/sdk.ts +++ b/packages/checkout/sdk/src/sdk.ts @@ -17,6 +17,7 @@ import * as buy from './smartCheckout/buy'; import * as cancel from './smartCheckout/cancel'; import * as sell from './smartCheckout/sell'; import * as smartCheckout from './smartCheckout'; +import * as swap from './swap'; import { AddNetworkParams, BuyParams, @@ -76,6 +77,7 @@ import { WidgetConfiguration } from './widgets/definitions/configurations'; import { SemanticVersion } from './widgets/definitions/types'; import { validateAndBuildVersion } from './widgets/version'; import { InjectedProvidersManager } from './provider/injectedProvidersManager'; +import { SwapParams, SwapQuoteResult, SwapResult } from './types/swap'; const SANDBOX_CONFIGURATION = { baseConfig: { @@ -726,4 +728,50 @@ export class Checkout { public async isSwapAvailable(): Promise { return this.availability.checkDexAvailability(); } + + /** + * Fetches a quote and then performs the approval and swap transaction. + * @param {SwapParams} params - The parameters for the swap. + * @returns {Promise} - A promise that resolves to the swap result (swap tx, swap tx receipt, quote used in the swap). + */ + public async swap(params: SwapParams): Promise { + const web3Provider = await provider.validateProvider( + this.config, + params.provider, + ); + return swap.swap( + this.config, + web3Provider, + params.fromToken, + params.toToken, + params.fromAmount, + params.toAmount, + params.slippagePercent, + params.maxHops, + params.deadline, + ); + } + + /** + * Fetches a quote for the swap. + * @param {SwapParams} params - The parameters for the swap. + * @returns {Promise} - A promise that resolves to the swap quote result. + */ + public async swapQuote(params: SwapParams): Promise { + const web3Provider = await provider.validateProvider( + this.config, + params.provider, + ); + return swap.swapQuote( + this.config, + web3Provider, + params.fromToken, + params.toToken, + params.fromAmount, + params.toAmount, + params.slippagePercent, + params.maxHops, + params.deadline, + ); + } } diff --git a/packages/checkout/sdk/src/smartCheckout/allowList/allowListCheck.test.ts b/packages/checkout/sdk/src/smartCheckout/allowList/allowListCheck.test.ts index e8546eac05..c2f6b03f02 100644 --- a/packages/checkout/sdk/src/smartCheckout/allowList/allowListCheck.test.ts +++ b/packages/checkout/sdk/src/smartCheckout/allowList/allowListCheck.test.ts @@ -8,23 +8,23 @@ import { allowListCheckForSwap, } from './allowListCheck'; import { - BridgeConfig, ChainId, - DexConfig, OnRampConfig, OnRampProvider, - OnRampProviderConfig, + OnRampProviderConfig, TokenInfo, } from '../../types'; import { TokenBalanceResult } from '../routing/types'; import { RemoteConfigFetcher } from '../../config/remoteConfigFetcher'; import { HttpClient } from '../../api/http'; +import { TokensFetcher } from '../../config/tokensFetcher'; jest.mock('../../config/remoteConfigFetcher'); +jest.mock('../../config/tokensFetcher'); describe('allowListCheck', () => { let config: CheckoutConfiguration; - let dexConfig: DexConfig; - let bridgeConfig: BridgeConfig; + let tokensL1: TokenInfo[]; + let tokensL2: TokenInfo[]; let onRampConfig: OnRampConfig; let balances: Map; let mockedHttpClient: jest.Mocked; @@ -34,43 +34,60 @@ describe('allowListCheck', () => { mockedHttpClient = new HttpClient() as jest.Mocked; (RemoteConfigFetcher as unknown as jest.Mock).mockReturnValue({ getConfig: jest.fn().mockImplementation((key) => { - let remoteConfig: any; - // eslint-disable-next-line default-case - switch (key) { - case 'bridge': - remoteConfig = bridgeConfig; - break; - case 'dex': - remoteConfig = dexConfig; - break; - case 'onramp': - remoteConfig = onRampConfig; - break; + let remoteConfig: any = {}; + + if (key === 'onramp') { + remoteConfig = onRampConfig; } + return remoteConfig; }), }); + (TokensFetcher as unknown as jest.Mock).mockReturnValue({ + getTokensConfig: jest.fn().mockImplementation((chainId: ChainId) => { + if (chainId === ChainId.IMTBL_ZKEVM_TESTNET) { + return tokensL2; + } + + return tokensL1; + }), + }); + config = new CheckoutConfiguration({ baseConfig: { environment: Environment.SANDBOX }, }, mockedHttpClient); - dexConfig = { - tokens: [{ + tokensL1 = [ + { + decimals: 18, + symbol: 'ETH', + name: 'Ethereum', + address: 'native', + }, + { decimals: 18, symbol: 'IMX', name: 'IMX', - }], - }; - bridgeConfig = { - [ChainId.SEPOLIA]: { - tokens: [{ - decimals: 18, - symbol: 'ETH', - name: 'Ethereum', - }], + address: '0xe9E96d1aad82562b7588F03f49aD34186f996478', }, - }; + ]; + + tokensL2 = [ + { + decimals: 18, + symbol: 'ETH', + name: 'Ethereum', + address: '0x52A6c53869Ce09a731CD772f245b97A4401d3348', + }, + { + decimals: 18, + symbol: 'IMX', + name: 'IMX', + address: 'native', + }, + ]; + onRampConfig = { [OnRampProvider.TRANSAK]: { publishableApiKey: '', @@ -82,6 +99,7 @@ describe('allowListCheck', () => { fees: {}, } as OnRampProviderConfig, }; + balances = new Map([ [ChainId.IMTBL_ZKEVM_TESTNET, { success: true, @@ -93,6 +111,7 @@ describe('allowListCheck', () => { decimals: 18, symbol: 'IMX', name: 'IMX', + address: 'native', }, }, { @@ -102,6 +121,7 @@ describe('allowListCheck', () => { name: 'Ethereum', symbol: 'ETH', decimals: 18, + address: '0x52A6c53869Ce09a731CD772f245b97A4401d3348', }, }, ], @@ -116,6 +136,7 @@ describe('allowListCheck', () => { name: 'Ethereum', symbol: 'ETH', decimals: 18, + address: 'native', }, }, ], @@ -129,16 +150,28 @@ describe('allowListCheck', () => { const allowList = await allowListCheck(config, balances, availableRoutingOptions); expect(allowList).toEqual({ - swap: [{ - decimals: 18, - symbol: 'IMX', - name: 'IMX', - }], - bridge: [{ - name: 'Ethereum', - symbol: 'ETH', - decimals: 18, - }], + swap: [ + { + name: 'Ethereum', + symbol: 'ETH', + decimals: 18, + address: '0x52A6c53869Ce09a731CD772f245b97A4401d3348', + }, + { + decimals: 18, + symbol: 'IMX', + name: 'IMX', + address: 'native', + }, + ], + bridge: [ + { + name: 'Ethereum', + symbol: 'ETH', + decimals: 18, + address: 'native', + }, + ], onRamp: [ { decimals: 18, @@ -158,6 +191,7 @@ describe('allowListCheck', () => { name: 'Ethereum', symbol: 'ETH', decimals: 18, + address: 'native', }], onRamp: [], swap: [], @@ -171,11 +205,20 @@ describe('allowListCheck', () => { expect(allowList).toEqual({ bridge: [], onRamp: [], - swap: [{ - decimals: 18, - symbol: 'IMX', - name: 'IMX', - }], + swap: [ + { + decimals: 18, + symbol: 'ETH', + name: 'Ethereum', + address: '0x52A6c53869Ce09a731CD772f245b97A4401d3348', + }, + { + decimals: 18, + symbol: 'IMX', + name: 'IMX', + address: 'native', + }, + ], }); }); @@ -215,6 +258,7 @@ describe('allowListCheck', () => { decimals: 18, symbol: 'ETH', name: 'Ethereum', + address: 'native', }]); }); @@ -240,27 +284,27 @@ describe('allowListCheck', () => { name: 'Ethereum', symbol: 'ETH', decimals: 18, + address: 'native', }, }, ], }], ]); - bridgeConfig = { - [ChainId.SEPOLIA]: { - tokens: [{ - address: '0x0000000', - decimals: 18, - symbol: 'MEGA', - name: 'Mega', - }, - { - decimals: 18, - symbol: 'ETH', - name: 'Ethereum', - }], + tokensL1 = [ + { + address: '0x0000000', + decimals: 18, + symbol: 'MEGA', + name: 'Mega', }, - }; + { + decimals: 18, + symbol: 'ETH', + name: 'Ethereum', + address: 'native', + }, + ]; const result = await allowListCheckForBridge(config, balances, { bridge: true }); expect(result).toEqual([{ @@ -273,6 +317,7 @@ describe('allowListCheck', () => { decimals: 18, symbol: 'ETH', name: 'Ethereum', + address: 'native', }]); }); @@ -287,27 +332,19 @@ describe('allowListCheck', () => { }); it('should return an empty array if bridge allowlist is empty', async () => { - bridgeConfig = { - [ChainId.IMTBL_ZKEVM_TESTNET]: { - tokens: [], - }, - }; + tokensL1 = []; const result = await allowListCheckForBridge(config, balances, { bridge: true }); expect(result).toEqual([]); }); it('should return an empty array if allowlist tokens have no balance', async () => { - bridgeConfig = { - [ChainId.IMTBL_ZKEVM_TESTNET]: { - tokens: [{ - address: '0x0000000', - decimals: 18, - symbol: 'MEGA', - name: 'Mega', - }], - }, - }; + tokensL1 = [{ + address: '0x0000000', + decimals: 18, + symbol: 'MEGA', + name: 'Mega', + }]; const result = await allowListCheckForBridge(config, balances, { bridge: true }); expect(result).toEqual([]); @@ -317,11 +354,20 @@ describe('allowListCheck', () => { describe('allowListCheckForSwap', () => { it('should return swap allowlist', async () => { const result = await allowListCheckForSwap(config, balances, { swap: true }); - expect(result).toEqual([{ - decimals: 18, - symbol: 'IMX', - name: 'IMX', - }]); + expect(result).toEqual([ + { + decimals: 18, + symbol: 'ETH', + name: 'Ethereum', + address: '0x52A6c53869Ce09a731CD772f245b97A4401d3348', + }, + { + decimals: 18, + symbol: 'IMX', + name: 'IMX', + address: 'native', + }, + ]); }); it('should return an empty array if swap option is disabled', async () => { @@ -335,23 +381,19 @@ describe('allowListCheck', () => { }); it('should return an empty array if swap allowlist is empty', async () => { - dexConfig = { - tokens: [], - }; + tokensL2 = []; const result = await allowListCheckForSwap(config, balances, { swap: true }); expect(result).toEqual([]); }); it('should return an empty array if allowlist tokens have no balance', async () => { - dexConfig = { - tokens: [{ - address: '0x0000000', - decimals: 18, - symbol: 'MEGA', - name: 'Mega', - }], - }; + tokensL2 = [{ + address: '0x0000000', + decimals: 18, + symbol: 'MEGA', + name: 'Mega', + }]; const result = await allowListCheckForSwap(config, balances, { swap: true }); expect(result).toEqual([]); diff --git a/packages/checkout/sdk/src/smartCheckout/buy/buy.ts b/packages/checkout/sdk/src/smartCheckout/buy/buy.ts index c4d3fd7900..4f52e5a30e 100644 --- a/packages/checkout/sdk/src/smartCheckout/buy/buy.ts +++ b/packages/checkout/sdk/src/smartCheckout/buy/buy.ts @@ -12,7 +12,7 @@ import { FulfillOrderResponse, OrderStatusName, } from '@imtbl/orderbook'; -import { GetTokenResult } from '@imtbl/generated-clients/dist/multi-rollup'; +import { mr } from '@imtbl/generated-clients'; import { track } from '@imtbl/metrics'; import * as instance from '../../instance'; import { CheckoutConfiguration, getL1ChainId, getL2ChainId } from '../../config'; @@ -158,7 +158,7 @@ export const buy = async ( const buyToken = order.result.buy[0]; if (buyToken.type === 'ERC20') { - const token = await measureAsyncExecution( + const token = await measureAsyncExecution( config, 'Time to get decimals of token contract for the buy token', blockchainClient.getToken({ contractAddress: buyToken.contractAddress, chainName: orderChainName }), diff --git a/packages/checkout/sdk/src/swap/index.ts b/packages/checkout/sdk/src/swap/index.ts new file mode 100644 index 0000000000..b16905623b --- /dev/null +++ b/packages/checkout/sdk/src/swap/index.ts @@ -0,0 +1 @@ +export * from './swap'; diff --git a/packages/checkout/sdk/src/swap/swap.test.ts b/packages/checkout/sdk/src/swap/swap.test.ts new file mode 100644 index 0000000000..d233645f95 --- /dev/null +++ b/packages/checkout/sdk/src/swap/swap.test.ts @@ -0,0 +1,190 @@ +import { Web3Provider } from '@ethersproject/providers'; +import { BigNumber, utils, ethers } from 'ethers'; +import { CheckoutConfiguration } from '../config/config'; +import { TokenInfo } from '../types'; +import { swap, swapQuote } from './swap'; +import { createExchangeInstance } from '../instance'; + +jest.mock('../instance', () => ({ + createExchangeInstance: jest.fn(), +})); + +describe('swapQuote', () => { + const mockChainId = 13473; + const mockConfig = {} as unknown as CheckoutConfiguration; + const mockProvider = { + getSigner: jest.fn().mockReturnValue({ + getAddress: jest.fn().mockResolvedValue('0xmockaddress'), + }), + } as unknown as Web3Provider; + const mockFromToken: TokenInfo = { + address: '0x123', + symbol: 'FROM', + decimals: 18, + name: 'From Token', + }; + const mockToToken: TokenInfo = { + address: '0x456', + symbol: 'TO', + decimals: 18, + name: 'To Token', + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call createExchangeInstance and execute swapQuote with fromAmount', async () => { + const mockExchange = { + getUnsignedSwapTxFromAmountIn: jest.fn().mockResolvedValue({ quote: '0xquotehash' }), + }; + (createExchangeInstance as jest.Mock).mockResolvedValue(mockExchange); + + const result = await swapQuote(mockConfig, mockProvider, mockFromToken, mockToToken, '100'); + + expect(createExchangeInstance).toHaveBeenCalledWith(mockChainId, mockConfig); + expect(mockExchange.getUnsignedSwapTxFromAmountIn).toHaveBeenCalledWith( + '0xmockaddress', + mockFromToken.address, + mockToToken.address, + BigNumber.from(utils.parseUnits('100', mockFromToken.decimals)), + undefined, + undefined, + undefined, + ); + expect(result).toEqual({ quote: '0xquotehash' }); + }); + + it('should call createExchangeInstance and execute swapQuote with toAmount', async () => { + const mockExchange = { + getUnsignedSwapTxFromAmountOut: jest.fn().mockResolvedValue({ quote: '0xquotehash' }), + }; + (createExchangeInstance as jest.Mock).mockResolvedValue(mockExchange); + + const result = await swapQuote(mockConfig, mockProvider, mockFromToken, mockToToken, undefined, '200'); + + expect(createExchangeInstance).toHaveBeenCalledWith(mockChainId, mockConfig); + expect(mockExchange.getUnsignedSwapTxFromAmountOut).toHaveBeenCalledWith( + '0xmockaddress', + mockFromToken.address, + mockToToken.address, + BigNumber.from(utils.parseUnits('200', mockToToken.decimals)), + undefined, + undefined, + undefined, + ); + expect(result).toEqual({ quote: '0xquotehash' }); + }); + + it('should throw an error if fromToken address is missing', async () => { + const invalidFromToken = { ...mockFromToken, address: '' }; + await expect(swapQuote(mockConfig, mockProvider, invalidFromToken, mockToToken, '100')) + .rejects.toThrow('fromToken address or decimals is missing.'); + }); + + it('should throw an error if fromToken decimals is zero', async () => { + const invalidFromToken = { ...mockFromToken, decimals: 0 }; + await expect(swapQuote(mockConfig, mockProvider, invalidFromToken, mockToToken, '100')) + .rejects.toThrow('fromToken address or decimals is missing.'); + }); + + it('should throw an error if toToken address is missing', async () => { + const invalidToToken = { ...mockToToken, address: '' }; + await expect(swapQuote(mockConfig, mockProvider, mockFromToken, invalidToToken, '100')) + .rejects.toThrow('toToken address or decimals is missing.'); + }); + + it('should throw an error if toToken decimals is zero', async () => { + const invalidToToken = { ...mockToToken, decimals: 0 }; + await expect(swapQuote(mockConfig, mockProvider, mockFromToken, invalidToToken, '100')) + .rejects.toThrow('toToken address or decimals is missing.'); + }); + + it('should throw an error if both fromAmount and toAmount are provided', async () => { + await expect(swapQuote(mockConfig, mockProvider, mockFromToken, mockToToken, '100', '200')) + .rejects.toThrow('Only one of fromAmount or toAmount can be provided.'); + }); + + it('should throw an error if neither fromAmount nor toAmount is provided', async () => { + await expect(swapQuote(mockConfig, mockProvider, mockFromToken, mockToToken)) + .rejects.toThrow('fromAmount or toAmount must be provided.'); + }); +}); + +describe('swap', () => { + const mockChainId = 13473; + const mockConfig = {} as unknown as CheckoutConfiguration; + const mockSigner = { + getAddress: jest.fn().mockResolvedValue('0xmockaddress'), + sendTransaction: jest.fn().mockResolvedValue({ hash: '0xtxhash' }), + }; + const mockProvider = { + getSigner: jest.fn().mockReturnValue(mockSigner), + getNetwork: jest.fn().mockResolvedValue({ chainId: mockChainId }), + } as unknown as Web3Provider; + const mockFromToken: TokenInfo = { + address: '0x123', + symbol: 'FROM', + decimals: 18, + name: 'From Token', + }; + const mockToToken: TokenInfo = { + address: '0x456', + symbol: 'TO', + decimals: 18, + name: 'To Token', + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should successfully execute a swap', async () => { + const mockTransactionResponse = { + wait: jest.fn().mockResolvedValue({ status: 1 }), + }; + + const mockExchange = { + getUnsignedSwapTxFromAmountIn: jest.fn().mockResolvedValue({ + quote: '0xquotehash', + swap: { transaction: { maxFeePerGas: '0xunsignedtx' } as ethers.providers.TransactionRequest }, + approve: { transaction: { maxFeePerGas: '0xunsignedtx' } as ethers.providers.TransactionRequest }, + }), + }; + (createExchangeInstance as jest.Mock).mockResolvedValue(mockExchange); + + mockSigner.sendTransaction.mockResolvedValue({ + ...mockTransactionResponse, + hash: '0xtxhash', + }); + + const result = await swap(mockConfig, mockProvider, mockFromToken, mockToToken, '100'); + + expect(createExchangeInstance).toHaveBeenCalledWith(mockChainId, mockConfig); + expect(mockExchange.getUnsignedSwapTxFromAmountIn).toHaveBeenCalledWith( + '0xmockaddress', + mockFromToken.address, + mockToToken.address, + BigNumber.from(utils.parseUnits('100', mockFromToken.decimals)), + undefined, + undefined, + undefined, + ); + expect(mockProvider.getNetwork).toHaveBeenCalled(); + expect(mockSigner.sendTransaction).toHaveBeenCalledWith({ + maxFeePerGas: BigNumber.from('0x037e11d600'), + maxPriorityFeePerGas: BigNumber.from('0x02540be400'), + }); + expect(mockTransactionResponse.wait).toHaveBeenCalled(); + expect(result).toEqual({ + quote: '0xquotehash', + swap: { + transaction: { + maxFeePerGas: BigNumber.from('0x037e11d600'), + maxPriorityFeePerGas: BigNumber.from('0x02540be400'), + }, + }, + swapReceipt: { status: 1 }, + }); + }); +}); diff --git a/packages/checkout/sdk/src/swap/swap.ts b/packages/checkout/sdk/src/swap/swap.ts new file mode 100644 index 0000000000..990b65e43b --- /dev/null +++ b/packages/checkout/sdk/src/swap/swap.ts @@ -0,0 +1,119 @@ +import { BigNumber, utils } from 'ethers'; +import { Web3Provider } from '@ethersproject/providers'; +import { CheckoutError, CheckoutErrorType } from '../errors'; +import { TokenInfo } from '../types'; +import { createExchangeInstance } from '../instance'; +import { CheckoutConfiguration, getL2ChainId } from '../config'; +import { SwapQuoteResult, SwapResult } from '../types/swap'; +import { sendTransaction } from '../transaction/transaction'; + +const swapQuote = async ( + config: CheckoutConfiguration, + provider: Web3Provider, + fromToken: TokenInfo, + toToken: TokenInfo, + fromAmount?: string, + toAmount?: string, + slippagePercent?: number, + maxHops?: number, + deadline?: number, +): Promise => { + if (!fromToken.address || fromToken.decimals === 0) { + throw new CheckoutError( + 'fromToken address or decimals is missing.', + CheckoutErrorType.MISSING_PARAMS, + ); + } + if (!toToken.address || toToken.decimals === 0) { + throw new CheckoutError( + 'toToken address or decimals is missing.', + CheckoutErrorType.MISSING_PARAMS, + ); + } + if (fromAmount && toAmount) { + throw new CheckoutError( + 'Only one of fromAmount or toAmount can be provided.', + CheckoutErrorType.MISSING_PARAMS, + ); + } + if (!fromAmount && !toAmount) { + throw new CheckoutError( + 'fromAmount or toAmount must be provided.', + CheckoutErrorType.MISSING_PARAMS, + ); + } + const chainId = getL2ChainId(config); + const exchange = await createExchangeInstance(chainId, config); + + const address = await provider.getSigner().getAddress(); + + if (fromAmount) { + return exchange.getUnsignedSwapTxFromAmountIn( + address, + fromToken.address as string, + toToken.address as string, + BigNumber.from(utils.parseUnits(fromAmount, fromToken.decimals)), + slippagePercent, + maxHops, + deadline, + ); + } + return exchange.getUnsignedSwapTxFromAmountOut( + address, + fromToken.address as string, + toToken.address as string, + BigNumber.from(utils.parseUnits(toAmount!, toToken.decimals)), + slippagePercent, + maxHops, + deadline, + ); +}; + +const swap = async ( + config: CheckoutConfiguration, + provider: Web3Provider, + fromToken: TokenInfo, + toToken: TokenInfo, + fromAmount?: string, + toAmount?: string, + slippagePercent?: number, + maxHops?: number, + deadline?: number, +): Promise => { + const quoteResult = await swapQuote( + config, + provider, + fromToken, + toToken, + fromAmount, + toAmount, + slippagePercent, + maxHops, + deadline, + ); + if (quoteResult.approval) { + const approvalTx = await sendTransaction(provider, quoteResult.approval.transaction); + const receipt = await approvalTx.transactionResponse.wait(); + if (receipt.status === 0) { + throw new CheckoutError( + 'Approval transaction failed and was reverted', + CheckoutErrorType.APPROVAL_TRANSACTION_FAILED, + ); + } + } + const swapTx = await sendTransaction(provider, quoteResult.swap.transaction); + const receipt = await swapTx.transactionResponse.wait(); + if (receipt.status === 0) { + throw new CheckoutError( + 'Swap transaction failed and was reverted', + CheckoutErrorType.TRANSACTION_FAILED, + ); + } + return { + swapReceipt: receipt, + quote: quoteResult.quote, + swap: quoteResult.swap, + }; +}; + +export { swapQuote, swap }; diff --git a/packages/checkout/sdk/src/tokens/tokens.test.ts b/packages/checkout/sdk/src/tokens/tokens.test.ts index 1b192e2e0c..30d3d94d5f 100644 --- a/packages/checkout/sdk/src/tokens/tokens.test.ts +++ b/packages/checkout/sdk/src/tokens/tokens.test.ts @@ -8,8 +8,10 @@ import { CheckoutConfiguration } from '../config'; import { ERC20ABI, NATIVE } from '../env'; import { CheckoutErrorType } from '../errors'; import { HttpClient } from '../api/http'; +import { TokensFetcher } from '../config/tokensFetcher'; jest.mock('../config/remoteConfigFetcher'); +jest.mock('../config/tokensFetcher'); jest.mock('ethers', () => ({ ...jest.requireActual('ethers'), // eslint-disable-next-line @typescript-eslint/naming-convention @@ -23,13 +25,13 @@ describe('token related functions', () => { describe('when tokens are not configured', () => { it('should return the empty list of tokens', async () => { - (RemoteConfigFetcher as unknown as jest.Mock).mockReturnValue({ - getTokensConfig: jest.fn().mockResolvedValue({ allowed: [] }), + (TokensFetcher as unknown as jest.Mock).mockReturnValue({ + getTokensConfig: jest.fn().mockResolvedValue([]), }); config = new CheckoutConfiguration({ baseConfig: { environment: Environment.SANDBOX }, }, mockedHttpClient); - await expect( + expect( await getTokenAllowList(config, { type: TokenFilterTypes.ALL, chainId: ChainId.SEPOLIA, @@ -42,28 +44,6 @@ describe('token related functions', () => { describe('getTokenAllowList', () => { const remoteConfigMockReturn = { - getTokensConfig: jest.fn().mockResolvedValue({ - allowed: [ - { - address: '0x1', - decimals: 18, - name: 'token-aa-testnet', - symbol: 'AA', - }, - { - address: '0x2', - decimals: 18, - name: 'token-bb-testnet', - symbol: 'BB', - }, - { - address: '', - decimals: 18, - name: 'token-cc-testnet', - symbol: 'CC', - }, - ], - }), getConfig: jest.fn().mockResolvedValue({ overrides: { rpcURL: 'https://test', @@ -79,6 +59,29 @@ describe('token related functions', () => { }), }; + const remoteTokensMockReturn = { + getTokensConfig: jest.fn().mockResolvedValue([ + { + address: '0x1', + decimals: 18, + name: 'token-aa-testnet', + symbol: 'AA', + }, + { + address: '0x2', + decimals: 18, + name: 'token-bb-testnet', + symbol: 'BB', + }, + { + address: '', + decimals: 18, + name: 'token-cc-testnet', + symbol: 'CC', + }, + ]), + }; + const testcases = [ { text: 'tokens with no filters (ALL type)', @@ -106,6 +109,7 @@ describe('token related functions', () => { }, ], remoteConfigMockReturn, + remoteTokensMockReturn, }, { text: 'exclude token with address', @@ -127,6 +131,7 @@ describe('token related functions', () => { }, ], remoteConfigMockReturn, + remoteTokensMockReturn, }, { text: 'exclude empty address', @@ -148,9 +153,10 @@ describe('token related functions', () => { }, ], remoteConfigMockReturn, + remoteTokensMockReturn, }, { - text: 'tokens with SWAP filter', + text: 'tokens with SWAP filter and blocklist', type: TokenFilterTypes.SWAP, chainId: ChainId.IMTBL_ZKEVM_DEVNET, result: [ @@ -161,13 +167,41 @@ describe('token related functions', () => { symbol: 'BB', }, ], - remoteConfigMockReturn, + remoteConfigMockReturn: { + ...remoteConfigMockReturn, + getConfig: jest.fn() + .mockResolvedValue({ + blocklist: [ + { + address: '', + name: 'token-cc-testnet', + }, + { + address: '0x1', + symbol: 'AA', + }, + ], + }), + }, + remoteTokensMockReturn, }, { text: 'tokens with BRIDGE filter', type: TokenFilterTypes.BRIDGE, chainId: ChainId.IMTBL_ZKEVM_DEVNET, result: [ + { + address: '0x1', + decimals: 18, + name: 'token-aa-testnet', + symbol: 'AA', + }, + { + address: '0x2', + decimals: 18, + name: 'token-bb-testnet', + symbol: 'BB', + }, { address: '', decimals: 18, @@ -175,29 +209,18 @@ describe('token related functions', () => { symbol: 'CC', }, ], - remoteConfigMockReturn: { - ...remoteConfigMockReturn, - getConfig: jest.fn().mockResolvedValue({ - [ChainId.IMTBL_ZKEVM_DEVNET]: { - tokens: [ - { - address: '', - decimals: 18, - name: 'token-cc-testnet', - symbol: 'CC', - }, - ], - }, - }), - }, + remoteConfigMockReturn, + remoteTokensMockReturn, }, { text: 'undefined SWAP tokens list', type: TokenFilterTypes.SWAP, chainId: ChainId.IMTBL_ZKEVM_DEVNET, result: [], + remoteTokensMockReturn: { + getTokensConfig: jest.fn().mockResolvedValue([]), + }, remoteConfigMockReturn: { - getTokensConfig: jest.fn().mockResolvedValue(undefined), getConfig: jest.fn().mockResolvedValue({}), }, }, @@ -206,11 +229,12 @@ describe('token related functions', () => { testcases.forEach((testcase) => { it(`should return the filtered list of allowed tokens for a given ${testcase.text}`, async () => { (RemoteConfigFetcher as unknown as jest.Mock).mockReturnValue(testcase.remoteConfigMockReturn); + (TokensFetcher as unknown as jest.Mock).mockReturnValue(testcase.remoteTokensMockReturn); config = new CheckoutConfiguration({ baseConfig: { environment: Environment.SANDBOX }, }, mockedHttpClient); - await expect( + expect( await getTokenAllowList(config, { type: testcase.type, exclude: testcase.exclude, diff --git a/packages/checkout/sdk/src/tokens/tokens.ts b/packages/checkout/sdk/src/tokens/tokens.ts index 29afd81bdf..44209e973d 100644 --- a/packages/checkout/sdk/src/tokens/tokens.ts +++ b/packages/checkout/sdk/src/tokens/tokens.ts @@ -1,7 +1,6 @@ import { JsonRpcProvider, Web3Provider } from '@ethersproject/providers'; import { Contract } from 'ethers'; import { - BridgeConfig, ChainId, DexConfig, GetTokenAllowListResult, @@ -10,7 +9,7 @@ import { TokenFilterTypes, TokenInfo, } from '../types'; -import { CheckoutConfiguration, getL1ChainId } from '../config'; +import { CheckoutConfiguration, getL1ChainId, getL2ChainId } from '../config'; import { ERC20ABI, NATIVE } from '../env'; import { CheckoutErrorType, withCheckoutError } from '../errors'; import { isMatchingAddress } from '../utils/utils'; @@ -31,17 +30,24 @@ export const getTokenAllowList = async ( ): Promise => { let tokens: TokenInfo[] = []; let onRampConfig: OnRampConfig; - let onBridgeConfig: BridgeConfig; + let blockedTokens: string[]; const targetChainId = chainId ?? getL1ChainId(config); + const dexChainId = getL2ChainId(config); switch (type) { case TokenFilterTypes.SWAP: + tokens = (await config.tokens.getTokensConfig(dexChainId)); + // Fetch tokens from dex-tokens config because // Dex needs to have a whitelisted list of tokens due to // legal reasons. - tokens = ((await config.remote.getConfig('dex')) as DexConfig) - .tokens || []; + blockedTokens = ( + ((await config.remote.getConfig('dex')) as DexConfig)?.blocklist || [] + ).map((token) => token.address.toLowerCase()); + + tokens = tokens.filter((token) => token.address && !blockedTokens.includes(token.address)); + break; case TokenFilterTypes.ONRAMP: onRampConfig = (await config.remote.getConfig('onramp')) as OnRampConfig; @@ -50,14 +56,9 @@ export const getTokenAllowList = async ( tokens = onRampConfig[OnRampProvider.TRANSAK]?.tokens || []; break; case TokenFilterTypes.BRIDGE: - onBridgeConfig = ((await config.remote.getConfig('bridge')) as BridgeConfig); - if (!onBridgeConfig) tokens = []; - - tokens = onBridgeConfig[targetChainId]?.tokens || []; - break; case TokenFilterTypes.ALL: default: - tokens = (await config.remote.getTokensConfig(targetChainId)).allowed as TokenInfo[]; + tokens = (await config.tokens.getTokensConfig(targetChainId)); } if (!exclude || exclude?.length === 0) return { tokens }; diff --git a/packages/checkout/sdk/src/transaction/transaction.ts b/packages/checkout/sdk/src/transaction/transaction.ts index 0834de62d4..c9a3c3983b 100644 --- a/packages/checkout/sdk/src/transaction/transaction.ts +++ b/packages/checkout/sdk/src/transaction/transaction.ts @@ -1,10 +1,31 @@ -import { ethers } from 'ethers'; +import { BigNumber, ethers } from 'ethers'; import { TransactionRequest, Web3Provider } from '@ethersproject/providers'; import { CheckoutError, CheckoutErrorType } from '../errors'; import { SendTransactionResult } from '../types'; import { IMMUTABLE_ZKVEM_GAS_OVERRIDES } from '../env'; import { isZkEvmChainId } from '../utils/utils'; +export function isPassportProvider(provider?: Web3Provider | null) { + return (provider?.provider as any)?.isPassport === true; +} + +/** + * Checks conditions to operate a gas-free flow. + * + * TODO: + * - Phase 1 (2024): Allow all passport wallets to be gas-free. + * - Phase 2 & 3 (2025): Not all passport wallets will be gas-free. + * Therefore, the gas-free condition must be checked against the relayer's + * `im_getFeeOptions` endpoint, which should return zero for + * passport accounts with gas sponsorship enabled. + * + * Refer to the docs for more details: + * https://docs.immutable.com/docs/zkevm/architecture/gas-sponsorship-for-gamers/ + */ +export function isGasFree(provider?: Web3Provider | null) { + return isPassportProvider(provider); +} + export const setTransactionGasLimits = async ( web3Provider: Web3Provider, transaction: TransactionRequest, @@ -14,10 +35,12 @@ export const setTransactionGasLimits = async ( const { chainId } = await web3Provider.getNetwork(); if (!isZkEvmChainId(chainId)) return rawTx; if (typeof rawTx.gasPrice !== 'undefined') return rawTx; - - rawTx.maxFeePerGas = IMMUTABLE_ZKVEM_GAS_OVERRIDES.maxFeePerGas; - rawTx.maxPriorityFeePerGas = IMMUTABLE_ZKVEM_GAS_OVERRIDES.maxPriorityFeePerGas; - + if (isGasFree(web3Provider)) { + rawTx.gasPrice = BigNumber.from(0); + } else { + rawTx.maxFeePerGas = IMMUTABLE_ZKVEM_GAS_OVERRIDES.maxFeePerGas; + rawTx.maxPriorityFeePerGas = IMMUTABLE_ZKVEM_GAS_OVERRIDES.maxPriorityFeePerGas; + } return rawTx; }; diff --git a/packages/checkout/sdk/src/types/config.ts b/packages/checkout/sdk/src/types/config.ts index b12cf3e4e6..12c5f1163d 100644 --- a/packages/checkout/sdk/src/types/config.ts +++ b/packages/checkout/sdk/src/types/config.ts @@ -146,6 +146,16 @@ export type DexConfig = { tokens?: TokenInfo[]; /** An array of secondary fees to be applied to swaps */ secondaryFees?: SecondaryFee[]; + /** An array of tokens to be blocked from the DEX */ + blocklist?: BlockedToken[]; +}; + +/** + * A type representing a token that is blocked from the DEX. + */ +export type BlockedToken = { + address: string; + symbol: string; }; /** @@ -233,18 +243,6 @@ export type GasEstimateSwapTokenConfig = { /** * A type that represents the tokens configuration for chain. */ -export type ChainsTokensConfig = { - [key in ChainId]: ChainTokensConfig; -}; - -/** - * A type representing all the feature flags available. - * @property {TokenInfo[] | undefined} allowed - - * @property {boolean | undefined} blockscout - - */ export type ChainTokensConfig = { - /** List of allowed tokens for a given chain. */ - allowed?: TokenInfo[]; - /** Feature flag to enable/disable blockscout integration. */ - blockscout?: boolean; + [key in ChainId]?: TokenInfo[]; }; diff --git a/packages/checkout/sdk/src/types/swap.ts b/packages/checkout/sdk/src/types/swap.ts new file mode 100644 index 0000000000..5f0aab353d --- /dev/null +++ b/packages/checkout/sdk/src/types/swap.ts @@ -0,0 +1,50 @@ +import { TransactionReceipt, Web3Provider } from '@ethersproject/providers'; +import { Quote, TransactionDetails } from '@imtbl/dex-sdk'; +import { TokenInfo } from './tokenInfo'; + +/** + * Interface representing the parameters for {@link Checkout.swap}. + * @property {Web3Provider} provider - The provider used to get the wallet address. + * @property {TokenInfo} fromToken - The token to swap from. + * @property {TokenInfo} toToken - The token to swap to. + * @property {string | undefined} fromAmount - The amount to swap from. + * @property {string | undefined} toAmount - The amount to swap to. + * @property {number | undefined} slippagePercent - The percentage of slippage tolerance. Default = 0.1. Max = 50. Min = 0 + * @property {number | undefined} maxHops - Maximum hops allowed in optimal route. Default is 2 + * @property {number | undefined} deadline - Latest time swap can execute. Default is 15 minutes + */ + +export interface SwapParams { + provider: Web3Provider; + fromToken: TokenInfo, + toToken: TokenInfo, + fromAmount?: string, + toAmount?: string, + slippagePercent?: number, + maxHops?: number, + deadline?: number, +} + +/** + * Interface representing the result of {@link Checkout.swapQuote}. + * @property {TransactionDetails} approval - The approval transaction details. + * @property {TransactionDetails} swap - The swap transaction details. + * @property {Quote} quote - The quote for the swap. + */ +export interface SwapQuoteResult { + approval: TransactionDetails | null; + swap: TransactionDetails; + quote: Quote; +} + +/** + * Interface representing the result of {@link Checkout.swap}. + * @property {TransactionDetails} swap - The swap transaction details. + * @property {Quote} quote - The quote for the swap. + * @property {TransactionReceipt} swapReceipt - The receipt of the swap transaction. + */ +export interface SwapResult { + swap: TransactionDetails; + quote: Quote; + swapReceipt: TransactionReceipt; +} diff --git a/packages/checkout/sdk/src/widgets/definitions/configurations/addFunds.ts b/packages/checkout/sdk/src/widgets/definitions/configurations/addFunds.ts new file mode 100644 index 0000000000..7e2aeff97a --- /dev/null +++ b/packages/checkout/sdk/src/widgets/definitions/configurations/addFunds.ts @@ -0,0 +1,8 @@ +import { WidgetConfiguration } from './widget'; + +/** + * Add Funds Widget Configuration represents the configuration options for the Add Funds Widget. + */ +export type AddFundsWidgetConfiguration = { +// TODO : [ADD_FUNDS] - What are the configuration needed? +} & WidgetConfiguration; diff --git a/packages/checkout/sdk/src/widgets/definitions/events/addFunds.ts b/packages/checkout/sdk/src/widgets/definitions/events/addFunds.ts new file mode 100644 index 0000000000..39a624628b --- /dev/null +++ b/packages/checkout/sdk/src/widgets/definitions/events/addFunds.ts @@ -0,0 +1,11 @@ +/** + * Enum of possible Add Funds Widget event types. + */ +export enum AddFundsEventType { + CLOSE_WIDGET = 'close-widget', + LANGUAGE_CHANGED = 'language-changed', + REQUEST_BRIDGE = 'request-bridge', + REQUEST_ONRAMP = 'request-onramp', + REQUEST_SWAP = 'request-swap', + GO_BACK = 'go-back', +} diff --git a/packages/checkout/sdk/src/widgets/definitions/events/checkout.ts b/packages/checkout/sdk/src/widgets/definitions/events/checkout.ts index 19d8fb2310..11d7e17940 100644 --- a/packages/checkout/sdk/src/widgets/definitions/events/checkout.ts +++ b/packages/checkout/sdk/src/widgets/definitions/events/checkout.ts @@ -1,5 +1,4 @@ import { Web3Provider } from '@ethersproject/providers'; -import { CheckoutFlowType } from '../parameters/checkout'; import { ConnectionFailed, ConnectionSuccess } from './connect'; import { SaleFailed, @@ -30,6 +29,9 @@ export enum CheckoutEventType { } export enum CheckoutSuccessEventType { + SWAP_SUCCESS = 'SWAP_SUCCESS', + ONRAMP_SUCCESS = 'ONRAMP_SUCCESS', + CONNECT_SUCCESS = 'CONNECT_SUCCESS', SALE_SUCCESS = 'SALE_SUCCESS', SALE_TRANSACTION_SUCCESS = 'SALE_TRANSACTION_SUCCESS', BRIDGE_SUCCESS = 'BRIDGE_SUCCESS', @@ -41,11 +43,15 @@ export enum CheckoutFailureEventType { BRIDGE_CLAIM_WITHDRAWAL_FAILED = 'BRIDGE_CLAIM_WITHDRAWAL_FAILED', SWAP_FAILED = 'SWAP_FAILED', SWAP_REJECTED = 'SWAP_REJECTED', + CONNECT_FAILED = 'CONNECT_FAILED', + SALE_FAILED = 'SALE_FAILED', + ONRAMP_FAILED = 'ONRAMP_FAILED', } export enum CheckoutUserActionEventType { PAYMENT_METHOD_SELECTED = 'PAYMENT_METHOD_SELECTED', PAYMENT_TOKEN_SELECTED = 'PAYMENT_TOKEN_SELECTED', + NETWORK_SWITCH = 'NETWORK_SWITCH', } export type CheckoutProviderUpdatedEvent = { @@ -58,41 +64,37 @@ export type CheckoutProviderUpdatedEvent = { }; export type CheckoutSaleSuccessEvent = { - flow: CheckoutFlowType.SALE; type: CheckoutSuccessEventType.SALE_SUCCESS; data: SaleSuccess; }; export type CheckoutSaleSuccessfulTransactionEvent = { - flow: CheckoutFlowType.SALE; type: CheckoutSuccessEventType.SALE_TRANSACTION_SUCCESS; data: SaleTransactionSuccess; }; export type CheckoutOnRampSuccessEvent = { - flow: CheckoutFlowType.ONRAMP; + type: CheckoutSuccessEventType.ONRAMP_SUCCESS; data: OnRampSuccess; }; // FIMXE: TransactionSent export type CheckoutBridgeSuccessEvent = { - flow: CheckoutFlowType.BRIDGE; type: CheckoutSuccessEventType.BRIDGE_SUCCESS; data: BridgeTransactionSent; }; // FIMXE: TransactionSent export type CheckoutBridgeClaimWithdrawalSuccessEvent = { - flow: CheckoutFlowType.BRIDGE; type: CheckoutSuccessEventType.BRIDGE_CLAIM_WITHDRAWAL_SUCCESS; data: BridgeClaimWithdrawalSuccess; }; // FIMXE: TransactionSent export type CheckoutSwapSuccessEvent = { - flow: CheckoutFlowType.SWAP; + type: CheckoutSuccessEventType.SWAP_SUCCESS; data: SwapSuccess; }; // FIMXE: TransactionSent export type CheckoutConnectSuccessEvent = { - flow: CheckoutFlowType.CONNECT; + type: CheckoutSuccessEventType.CONNECT_SUCCESS; data: ConnectionSuccess; }; @@ -106,41 +108,37 @@ export type CheckoutSuccessEvent = | CheckoutSaleSuccessfulTransactionEvent; export type CheckoutBridgeFailureEvent = { - flow: CheckoutFlowType.BRIDGE; type: CheckoutFailureEventType.BRIDGE_FAILED; data: BridgeFailed; }; // FIMXE: Error export type CheckoutBridgeClaimWithdrawalFailedEvent = { - flow: CheckoutFlowType.BRIDGE; type: CheckoutFailureEventType.BRIDGE_CLAIM_WITHDRAWAL_FAILED; data: BridgeClaimWithdrawalFailed; }; // FIMXE: Error export type CheckoutConnectFailureEvent = { - flow: CheckoutFlowType.CONNECT; + type: CheckoutFailureEventType.CONNECT_FAILED; data: ConnectionFailed; }; // FIMXE: Error export type CheckoutOnRampFailureEvent = { - flow: CheckoutFlowType.ONRAMP; + type: CheckoutFailureEventType.ONRAMP_FAILED; data: OnRampFailed; }; // FIMXE: Error export type CheckoutSwapFailureEvent = { - flow: CheckoutFlowType.SWAP; type: CheckoutFailureEventType.SWAP_FAILED; data: SwapFailed; }; // FIMXE: Error export type CheckoutSwapRejectedEvent = { - flow: CheckoutFlowType.SWAP; type: CheckoutFailureEventType.SWAP_REJECTED; data: SwapRejected; }; // FIMXE: Error export type CheckoutSaleFailureEvent = { - flow: CheckoutFlowType.SALE; + type: CheckoutFailureEventType.SALE_FAILED; data: SaleFailed; }; @@ -154,19 +152,17 @@ export type CheckoutFailureEvent = | CheckoutSaleFailureEvent; export type CheckoutPaymentMethodSelectedEvent = { - flow: CheckoutFlowType.SALE; type: CheckoutUserActionEventType.PAYMENT_METHOD_SELECTED; data: SalePaymentMethod; }; export type CheckoutPaymentTokenSelectedEvent = { - flow: CheckoutFlowType.SALE; type: CheckoutUserActionEventType.PAYMENT_TOKEN_SELECTED; data: SalePaymentToken; }; export type CheckoutNetworkSwitchEvent = { - flow: CheckoutFlowType.WALLET; + type: CheckoutUserActionEventType.NETWORK_SWITCH; data: WalletNetworkSwitch; }; diff --git a/packages/checkout/sdk/src/widgets/definitions/events/index.ts b/packages/checkout/sdk/src/widgets/definitions/events/index.ts index 60579cdead..fecd91d320 100644 --- a/packages/checkout/sdk/src/widgets/definitions/events/index.ts +++ b/packages/checkout/sdk/src/widgets/definitions/events/index.ts @@ -7,3 +7,4 @@ export * from './bridge'; export * from './orchestration'; export * from './onramp'; export * from './checkout'; +export * from './addFunds'; diff --git a/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts b/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts index 41c48ca9e6..295e04e5de 100644 --- a/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts +++ b/packages/checkout/sdk/src/widgets/definitions/events/orchestration.ts @@ -7,6 +7,7 @@ export enum OrchestrationEventType { REQUEST_SWAP = 'request-swap', REQUEST_BRIDGE = 'request-bridge', REQUEST_ONRAMP = 'request-onramp', + REQUEST_ADD_FUNDS = 'request-add-funds', } /** @@ -66,6 +67,12 @@ export type RequestOnrampEvent = { amount: string; }; +/** + * Represents the add funds event object when the add funds widget is requested. + */ +export type RequestAddFundsEvent = { +}; + /* * Type representing the orchestration events. */ diff --git a/packages/checkout/sdk/src/widgets/definitions/events/widgets.ts b/packages/checkout/sdk/src/widgets/definitions/events/widgets.ts index e790a72a00..531a5e0d3d 100644 --- a/packages/checkout/sdk/src/widgets/definitions/events/widgets.ts +++ b/packages/checkout/sdk/src/widgets/definitions/events/widgets.ts @@ -13,6 +13,7 @@ export enum IMTBLWidgetEvents { IMTBL_ONRAMP_WIDGET_EVENT = 'imtbl-onramp-widget', IMTBL_SALE_WIDGET_EVENT = 'imtbl-sale-widget', IMTBL_CHECKOUT_WIDGET_EVENT = 'imtbl-checkout-widget', + IMTBL_ADD_FUNDS_WIDGET_EVENT = 'imtbl-add-funds-widget', } /** diff --git a/packages/checkout/sdk/src/widgets/definitions/parameters/addFunds.ts b/packages/checkout/sdk/src/widgets/definitions/parameters/addFunds.ts new file mode 100644 index 0000000000..d3f4e83bf4 --- /dev/null +++ b/packages/checkout/sdk/src/widgets/definitions/parameters/addFunds.ts @@ -0,0 +1,21 @@ +import { WidgetLanguage } from '../configurations'; + +export type AddFundsWidgetParams = { + /** The language to use for the Add Funds widget */ + language?: WidgetLanguage; + + /** Configure to show on-ramp option */ + showOnrampOption?: boolean; + + /** Configure to show swap option */ + showSwapOption?: boolean; + + /** Configure to show on bridge option */ + showBridgeOption?: boolean; + + /** Token address of the fund to be added */ + tokenAddress?: string; + + /** Amount of the fund to be added */ + amount?: string; +}; diff --git a/packages/checkout/sdk/src/widgets/definitions/parameters/index.ts b/packages/checkout/sdk/src/widgets/definitions/parameters/index.ts index 1af32c0e81..69674938a7 100644 --- a/packages/checkout/sdk/src/widgets/definitions/parameters/index.ts +++ b/packages/checkout/sdk/src/widgets/definitions/parameters/index.ts @@ -1,3 +1,5 @@ +export type { AddFundsWidgetParams } from './addFunds'; + export * from './connect'; export * from './bridge'; export * from './wallet'; diff --git a/packages/checkout/sdk/src/widgets/definitions/parameters/swap.ts b/packages/checkout/sdk/src/widgets/definitions/parameters/swap.ts index ec5fe7aa34..2c7cbdb714 100644 --- a/packages/checkout/sdk/src/widgets/definitions/parameters/swap.ts +++ b/packages/checkout/sdk/src/widgets/definitions/parameters/swap.ts @@ -1,6 +1,11 @@ import { WalletProviderName } from '../../../types'; import { WidgetLanguage } from '../configurations'; +export enum SwapDirection { + FROM = 'FROM', + TO = 'TO', +} + /** * Swap Widget parameters * @property {string | undefined} amount @@ -19,4 +24,8 @@ export type SwapWidgetParams = { walletProviderName?: WalletProviderName; /** The language to use for the swap widget */ language?: WidgetLanguage; + /** Whether the swap widget should display the form or automatically proceed with the swap */ + autoProceed?: boolean; + /** The direction of the swap */ + direction?: SwapDirection; }; diff --git a/packages/checkout/sdk/src/widgets/definitions/types.ts b/packages/checkout/sdk/src/widgets/definitions/types.ts index 1084f6a3dd..c9e9a12d0e 100644 --- a/packages/checkout/sdk/src/widgets/definitions/types.ts +++ b/packages/checkout/sdk/src/widgets/definitions/types.ts @@ -39,6 +39,7 @@ import { CheckoutSuccessEvent, CheckoutFailureEvent, CheckoutUserActionEvent, + RequestAddFundsEvent, } from './events'; import { BridgeWidgetParams, @@ -59,6 +60,9 @@ import { CheckoutWidgetConfiguration, } from './configurations'; import { WidgetTheme } from './configurations/theme'; +import { AddFundsWidgetConfiguration } from './configurations/addFunds'; +import { AddFundsWidgetParams } from './parameters/addFunds'; +import { AddFundsEventType } from './events/addFunds'; /** * Enum representing the list of widget types. @@ -71,6 +75,7 @@ export enum WidgetType { ONRAMP = 'onramp', SALE = 'sale', CHECKOUT = 'checkout', + ADD_FUNDS = 'addFunds', } /** @@ -89,6 +94,7 @@ export type WidgetConfigurations = { [WidgetType.ONRAMP]: OnrampWidgetConfiguration; [WidgetType.SALE]: SaleWidgetConfiguration; [WidgetType.CHECKOUT]: CheckoutWidgetConfiguration; + [WidgetType.ADD_FUNDS]: AddFundsWidgetConfiguration; }; // Mapping each widget type to their parameters @@ -100,6 +106,7 @@ export type WidgetParameters = { [WidgetType.ONRAMP]: OnRampWidgetParams; [WidgetType.SALE]: SaleWidgetParams; [WidgetType.CHECKOUT]: CheckoutWidgetParams; + [WidgetType.ADD_FUNDS]: AddFundsWidgetParams; }; /** @@ -113,6 +120,7 @@ export type WidgetEventTypes = { [WidgetType.ONRAMP]: OnRampEventType | OrchestrationEventType; [WidgetType.SALE]: SaleEventType | OrchestrationEventType; [WidgetType.CHECKOUT]: CheckoutEventType | OrchestrationEventType; + [WidgetType.ADD_FUNDS]: AddFundsEventType | OrchestrationEventType; }; // Mapping of Orchestration events to their payloads @@ -122,6 +130,7 @@ type OrchestrationMapping = { [OrchestrationEventType.REQUEST_SWAP]: RequestSwapEvent; [OrchestrationEventType.REQUEST_BRIDGE]: RequestBridgeEvent; [OrchestrationEventType.REQUEST_ONRAMP]: RequestOnrampEvent; + [OrchestrationEventType.REQUEST_ADD_FUNDS]: RequestAddFundsEvent; }; type ProviderEventMapping = { @@ -196,6 +205,15 @@ export type WidgetEventData = { [CheckoutEventType.DISCONNECTED]: {}; [CheckoutEventType.USER_ACTION]: CheckoutUserActionEvent; }; + + [WidgetType.ADD_FUNDS]: { + [AddFundsEventType.CLOSE_WIDGET]: {}; + [AddFundsEventType.GO_BACK]: {}; + [AddFundsEventType.REQUEST_BRIDGE]: {}; + [AddFundsEventType.REQUEST_SWAP]: {}; + [AddFundsEventType.REQUEST_ONRAMP]: {}; + } & OrchestrationMapping & + ProviderEventMapping; }; /** diff --git a/packages/checkout/sdk/tsconfig.json b/packages/checkout/sdk/tsconfig.json index 8c65238d9f..186a4d5210 100644 --- a/packages/checkout/sdk/tsconfig.json +++ b/packages/checkout/sdk/tsconfig.json @@ -1,20 +1,9 @@ { + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": true, - "resolveJsonModule": true, - "skipLibCheck": true, + "customConditions": ["development"] }, "include": ["src", "test/__mocks__/window.ts"], "exclude": ["node_modules", "dist", "test"] diff --git a/packages/checkout/widgets-lib/.eslintrc b/packages/checkout/widgets-lib/.eslintrc.cjs similarity index 97% rename from packages/checkout/widgets-lib/.eslintrc rename to packages/checkout/widgets-lib/.eslintrc.cjs index 5214d4447b..96bdd2ff19 100644 --- a/packages/checkout/widgets-lib/.eslintrc +++ b/packages/checkout/widgets-lib/.eslintrc.cjs @@ -1,10 +1,10 @@ -{ +module.exports = { "extends": ["../../../.eslintrc"], "ignorePatterns": ["jest.config.*", "rollup.config.*"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname }, "rules": { "@typescript-eslint/naming-convention": [ diff --git a/packages/checkout/widgets-lib/jest.config.ts b/packages/checkout/widgets-lib/jest.config.ts index 0f08da2db4..6df7a4897f 100644 --- a/packages/checkout/widgets-lib/jest.config.ts +++ b/packages/checkout/widgets-lib/jest.config.ts @@ -4,16 +4,7 @@ const config: Config = { clearMocks: true, coverageProvider: 'v8', moduleDirectories: ['node_modules', 'src'], - moduleNameMapper: { - '@imtbl/bridge-sdk': '../../internal/bridge/sdk/src', - '@imtbl/checkout-sdk': '../sdk/src', - '@imtbl/config': '../../config/src', - '@imtbl/cryptofiat': '../../internal/cryptofiat/src', - '@imtbl/dex-sdk': '../../internal/dex/sdk/src', - '@imtbl/orderbook': '../../orderbook/src', - '@imtbl/blockchain-data': '../../blockchain-data/sdk/src', - '@imtbl/generated-clients': '../../internal/generated-clients/src' - }, + moduleNameMapper: { '^@imtbl/(.*)$': '/../../../node_modules/@imtbl/$1/src' }, testEnvironment: 'jsdom', transform: { '^.+\\.(t|j)sx?$': '@swc/jest', diff --git a/packages/checkout/widgets-lib/package.json b/packages/checkout/widgets-lib/package.json index 629ab890e2..62b81ecde9 100644 --- a/packages/checkout/widgets-lib/package.json +++ b/packages/checkout/widgets-lib/package.json @@ -36,6 +36,7 @@ "i18next-browser-languagedetector": "^7.2.0", "os-browserify": "^0.3.0", "pako": "^2.1.0", + "pino-pretty": "^11.2.2", "react-i18next": "^13.5.0", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", @@ -49,6 +50,7 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", "@svgr/webpack": "^8.0.1", + "@swc/core": "^1.3.36", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -62,13 +64,30 @@ "local-cypress": "^1.2.6", "react-app-rewired": "^2.2.1", "react-scripts": "5.0.1", + "rimraf": "^6.0.1", "rollup": "^4.19.1", + "rollup-plugin-polyfill-node": "^0.13.0", "rollup-plugin-svg": "^2.0.0", "rollup-plugin-visualizer": "^5.12.0", "ts-jest": "^29.1.0", "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1", "web-vitals": "^2.1.4" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/main.js", + "module": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/main.js", + "module": "./dist/index.js", + "import": "./dist/index.js" + } + }, "homepage": "https://github.com/immutable/ts-immutable-sdk", "keywords": [ "immutable" @@ -78,18 +97,18 @@ "module": "dist/index.js", "private": true, "scripts": { - "build": "yarn clean && rollup --config rollup.config.js", + "build": "yarn clean && NODE_ENV=production rollup --config rollup.config.js", "build:analyse": "yarn build --plugin visualizer", "build:local": "yarn clean && yarn build && mkdir -p ../widgets-sample-app/public/lib/js && cp dist/*.js ../widgets-sample-app/public/lib/js/", - "clean": "rm -rf ./dist", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "clean": "rimraf ./dist", + "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "lint:fix": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0 --fix", "start": "yarn clean && NODE_ENV=development rollup --config rollup.config.js --watch", "test": "jest test --passWithNoTests", "test:watch": "jest test --passWithNoTests --watch", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --customConditions \"default\" --noEmit" }, "type": "module", - "types": "dist/index.d.ts" + "types": "./dist/index.d.ts" } diff --git a/packages/checkout/widgets-lib/rollup.config.js b/packages/checkout/widgets-lib/rollup.config.js index 3a4fe0d465..23a43cf842 100644 --- a/packages/checkout/widgets-lib/rollup.config.js +++ b/packages/checkout/widgets-lib/rollup.config.js @@ -2,30 +2,34 @@ import typescript from '@rollup/plugin-typescript'; import commonjs from '@rollup/plugin-commonjs'; import resolve from '@rollup/plugin-node-resolve'; import json from '@rollup/plugin-json'; -import terser from '@rollup/plugin-terser'; import replace from '@rollup/plugin-replace'; import nodePolyfills from 'rollup-plugin-polyfill-node'; +import swc from 'unplugin-swc' const DEVELOPMENT = 'development'; const PRODUCTION = 'production'; +const isProduction = process.env.NODE_ENV === PRODUCTION + const defaultPlugins = [ json(), replace({ preventAssignment: true, 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || PRODUCTION), }), - typescript(), + isProduction ? typescript({customConditions: ["default"]}) : swc.rollup({ jsc: { + transform: { react: { development: true, runtime: 'automatic' }}, + } }), ] const productionPlugins = [ resolve({ browser: true, dedupe: ['react', 'react-dom'], + exportConditions: ['default'] }), nodePolyfills(), commonjs(), - terser() ] const getPlugins = () => { diff --git a/packages/checkout/widgets-lib/src/components/WalletDrawer/WalletDrawer.tsx b/packages/checkout/widgets-lib/src/components/WalletDrawer/WalletDrawer.tsx index b749618a84..b89983c0a0 100644 --- a/packages/checkout/widgets-lib/src/components/WalletDrawer/WalletDrawer.tsx +++ b/packages/checkout/widgets-lib/src/components/WalletDrawer/WalletDrawer.tsx @@ -119,8 +119,8 @@ export function WalletDrawer({ key={providerDetail.info.rdns} testId={testId} loading={walletItemLoading} - providerDetail={providerDetail} - onWalletItemClick={handleWalletItemClick} + providerInfo={providerDetail.info} + onWalletItemClick={() => handleWalletItemClick(providerDetail)} rc={( )} diff --git a/packages/checkout/widgets-lib/src/components/WalletDrawer/WalletItem.tsx b/packages/checkout/widgets-lib/src/components/WalletDrawer/WalletItem.tsx index c95134394a..d6056a015d 100644 --- a/packages/checkout/widgets-lib/src/components/WalletDrawer/WalletItem.tsx +++ b/packages/checkout/widgets-lib/src/components/WalletDrawer/WalletItem.tsx @@ -1,15 +1,15 @@ import { MenuItem } from '@biom3/react'; import { cloneElement, ReactElement, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { EIP6963ProviderDetail } from '@imtbl/checkout-sdk'; +import { EIP6963ProviderInfo } from '@imtbl/checkout-sdk'; import { RawImage } from '../RawImage/RawImage'; export interface WalletItemProps { loading?: boolean; recommended?: boolean; testId: string; - providerDetail: EIP6963ProviderDetail; - onWalletItemClick: (providerDetail: EIP6963ProviderDetail) => Promise; + providerInfo: EIP6963ProviderInfo; + onWalletItemClick: () => void; rc?: RC; } @@ -20,7 +20,7 @@ export function WalletItem< testId, loading = false, recommended = false, - providerDetail, + providerInfo, onWalletItemClick, }: WalletItemProps) { const { t } = useTranslation(); @@ -34,27 +34,27 @@ export function WalletItem< setBusy(true); // let the parent handle errors try { - await onWalletItemClick(providerDetail); + await onWalletItemClick(); } finally { setBusy(false); } }, })} - testId={`${testId}-wallet-list-${providerDetail.info.rdns}`} + testId={`${testId}-wallet-list-${providerInfo.rdns}`} size="medium" emphasized sx={{ position: 'relative' }} > - {providerDetail.info.name} + {providerInfo.name} {((recommended || busy) && ( as Widget; } + case WidgetType.ADD_FUNDS: { + return new AddFunds(this.sdk, { + config: { ...this.widgetConfig, ...(config) }, + provider, + }) as Widget as Widget; + } default: throw new Error('widget type not supported'); } diff --git a/packages/checkout/widgets-lib/src/lib/balance.ts b/packages/checkout/widgets-lib/src/lib/balance.ts index aa1da13cc4..556914f29d 100644 --- a/packages/checkout/widgets-lib/src/lib/balance.ts +++ b/packages/checkout/widgets-lib/src/lib/balance.ts @@ -74,7 +74,7 @@ export const getAllowedBalances = async ({ ...balanceResult, token: { ...balanceResult.token, - icon: getTokenImageByAddress( + icon: balanceResult.token.icon ?? getTokenImageByAddress( checkout.config.environment as Environment, isNativeToken(balanceResult.token.address) ? balanceResult.token.symbol @@ -86,7 +86,7 @@ export const getAllowedBalances = async ({ // Map token icon assets to allowlist allowList.tokens = allowList.tokens.map((token) => ({ ...token, - icon: getTokenImageByAddress( + icon: token.icon ?? getTokenImageByAddress( checkout.config.environment as Environment, isNativeToken(token.address) ? token.symbol : token.address ?? '', ), diff --git a/packages/checkout/widgets-lib/src/lib/constants.ts b/packages/checkout/widgets-lib/src/lib/constants.ts index e830330218..a90f7768d6 100644 --- a/packages/checkout/widgets-lib/src/lib/constants.ts +++ b/packages/checkout/widgets-lib/src/lib/constants.ts @@ -20,7 +20,7 @@ export const ETH_TOKEN_SYMBOL = 'ETH'; export const ZERO_BALANCE_STRING = '0.0'; -export const FAQS_LINK = 'https://support.immutable.com/en/'; +export const FAQS_LINK = 'https://support.immutable.com/hc/en-us/categories/9383180060815-FAQs'; /** * Delay between retries (milliseconds) diff --git a/packages/checkout/widgets-lib/src/lib/walletConnect.ts b/packages/checkout/widgets-lib/src/lib/walletConnect.ts index 5c65f947fb..46383d8dfd 100644 --- a/packages/checkout/widgets-lib/src/lib/walletConnect.ts +++ b/packages/checkout/widgets-lib/src/lib/walletConnect.ts @@ -54,16 +54,17 @@ const lightThemeVariables = { '--wcm-overlay-background-color': 'rgba(255, 255, 255, 0.1)', }; -// Whitelisted wallet ids on WalletConnect explorer API +// Whitelisted wallet ids on WalletConnect explorer (https://explorer.walletconnect.com/) const metamaskId = 'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96'; const frontierId = '85db431492aa2e8672e93f4ea7acf10c88b97b867b0d373107af63dc4880f041'; const ledgerLiveId = '19177a98252e07ddfc9af2083ba8e07ef627cb6103467ffebb3f8f4205fd7927'; +const wombatId = 'cb604c517064f096976972384dc89948a5c850bca9b04866a443e10171d5965d'; // const coinbaseId = 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa'; // const phantomId = 'a797aa35c0fadbfc1a53e7f675162ed5226968b44a19ee3d24385c64d1d3c393'; // const rainbowId = '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369'; -const productionWalletWhitelist = [metamaskId, frontierId, ledgerLiveId]; -const sandboxWalletWhitelist = [metamaskId]; +const productionWalletWhitelist = [metamaskId, frontierId, ledgerLiveId, wombatId]; +const sandboxWalletWhitelist = [metamaskId, wombatId]; export class WalletConnectManager { private static instance: WalletConnectManager; diff --git a/packages/checkout/widgets-lib/src/locales/en.json b/packages/checkout/widgets-lib/src/locales/en.json index 185df5ff16..562bbf67d4 100644 --- a/packages/checkout/widgets-lib/src/locales/en.json +++ b/packages/checkout/widgets-lib/src/locales/en.json @@ -172,6 +172,11 @@ "loading": { "text": "Swap in progress" } + }, + "PREPARE_SWAP": { + "loading": { + "text": "Calculating swap" + } } }, "APPROVE_ERC20": { diff --git a/packages/checkout/widgets-lib/src/locales/ja.json b/packages/checkout/widgets-lib/src/locales/ja.json index 86f118badf..e7574189c2 100644 --- a/packages/checkout/widgets-lib/src/locales/ja.json +++ b/packages/checkout/widgets-lib/src/locales/ja.json @@ -175,6 +175,11 @@ "loading": { "text": "スワップ進行中" } + }, + "PREPARE_SWAP": { + "loading": { + "text": "スワップ計算" + } } }, "APPROVE_ERC20": { diff --git a/packages/checkout/widgets-lib/src/locales/ko.json b/packages/checkout/widgets-lib/src/locales/ko.json index b56ce6890d..2ca5791da0 100644 --- a/packages/checkout/widgets-lib/src/locales/ko.json +++ b/packages/checkout/widgets-lib/src/locales/ko.json @@ -171,6 +171,11 @@ "loading": { "text": "교환 진행 중" } + }, + "PREPARE_SWAP": { + "loading": { + "text": "스왑 계산" + } } }, "APPROVE_ERC20": { diff --git a/packages/checkout/widgets-lib/src/locales/zh.json b/packages/checkout/widgets-lib/src/locales/zh.json index 827a7d3294..abcd339ac0 100644 --- a/packages/checkout/widgets-lib/src/locales/zh.json +++ b/packages/checkout/widgets-lib/src/locales/zh.json @@ -171,6 +171,11 @@ "loading": { "text": "交换进行中" } + }, + "PREPARE_SWAP": { + "loading": { + "text": "计算交换" + } } }, "APPROVE_ERC20": { diff --git a/packages/checkout/widgets-lib/src/views/top-up/TopUpView.tsx b/packages/checkout/widgets-lib/src/views/top-up/TopUpView.tsx index 1467e3c73c..38710b0f13 100644 --- a/packages/checkout/widgets-lib/src/views/top-up/TopUpView.tsx +++ b/packages/checkout/widgets-lib/src/views/top-up/TopUpView.tsx @@ -12,7 +12,6 @@ import { import { Environment } from '@imtbl/config'; import { Web3Provider } from '@ethersproject/providers'; import { useTranslation } from 'react-i18next'; -import { $Dictionary } from 'i18next/typescript/helpers'; import { UserJourney, useAnalytics, @@ -40,6 +39,8 @@ import { OnRampWidgetViews } from '../../context/view-context/OnRampViewContextT import { EventTargetContext } from '../../context/event-target-context/EventTargetContext'; import { TopUpMenuItem, TopUpMenuItemProps } from './TopUpMenuItem'; +type $Dictionary = { [key: string]: T }; + interface TopUpViewProps { widgetEvent: IMTBLWidgetEvents; checkout?: Checkout; diff --git a/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsRoot.tsx b/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsRoot.tsx new file mode 100644 index 0000000000..13695d73cf --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsRoot.tsx @@ -0,0 +1,100 @@ +import { + ChainId, + IMTBLWidgetEvents, + WidgetConfiguration, + WidgetProperties, + WidgetTheme, + WidgetType, + AddFundsWidgetParams, +} from '@imtbl/checkout-sdk'; +import React, { Suspense } from 'react'; +import { Base } from '../BaseWidgetRoot'; +import { CustomAnalyticsProvider } from '../../context/analytics-provider/CustomAnalyticsProvider'; +import { HandoverProvider } from '../../context/handover-context/HandoverProvider'; +import i18n from '../../i18n'; +import { LoadingView } from '../../views/loading/LoadingView'; +import { ThemeProvider } from '../../components/ThemeProvider/ThemeProvider'; +import { + ConnectLoader, + ConnectLoaderParams, +} from '../../components/ConnectLoader/ConnectLoader'; +import { getL1ChainId, getL2ChainId } from '../../lib'; +import { sendAddFundsCloseEvent } from './AddFundsWidgetEvents'; + +const AddFundsWidget = React.lazy(() => import('./AddFundsWidget')); + +export class AddFunds extends Base { + protected eventTopic: IMTBLWidgetEvents = IMTBLWidgetEvents.IMTBL_ADD_FUNDS_WIDGET_EVENT; + + protected getValidatedProperties({ + config, + }: WidgetProperties): WidgetProperties { + let validatedConfig: WidgetConfiguration | undefined; + + if (config) { + validatedConfig = config; + if (config.theme === WidgetTheme.LIGHT) validatedConfig.theme = WidgetTheme.LIGHT; + else validatedConfig.theme = WidgetTheme.DARK; + } + + return { + config: validatedConfig, + }; + } + + protected getValidatedParameters( + params: AddFundsWidgetParams, + ): AddFundsWidgetParams { + const validatedParams = params; + return validatedParams; + } + + protected render() { + if (!this.reactRoot) return; + + const { t } = i18n; + const connectLoaderParams: ConnectLoaderParams = { + targetChainId: this.checkout.config.isProduction + ? ChainId.IMTBL_ZKEVM_MAINNET + : ChainId.IMTBL_ZKEVM_TESTNET, + web3Provider: this.web3Provider, + checkout: this.checkout, + allowedChains: [ + getL1ChainId(this.checkout.config), + getL2ChainId(this.checkout.config), + ], + }; + + this.reactRoot.render( + + + + + sendAddFundsCloseEvent(window)} + > + + } + > + + + + + + + , + ); + } +} diff --git a/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidget.tsx b/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidget.tsx new file mode 100644 index 0000000000..47914c9453 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidget.tsx @@ -0,0 +1,59 @@ +import { AddFundsWidgetParams, Checkout, IMTBLWidgetEvents } from '@imtbl/checkout-sdk'; +import { Web3Provider } from '@ethersproject/providers'; +import { useContext, useMemo, useReducer } from 'react'; +import { UserJourney } from '../../context/analytics-provider/SegmentAnalyticsProvider'; +import { TopUpView } from '../../views/top-up/TopUpView'; +import { + sendAddFundsCloseEvent, + sendAddFundsGoBackEvent, +} from './AddFundsWidgetEvents'; +import { EventTargetContext } from '../../context/event-target-context/EventTargetContext'; +import { + ViewContext, + initialViewState, + viewReducer, +} from '../../context/view-context/ViewContext'; + +export type AddFundsWidgetInputs = AddFundsWidgetParams & { + checkout: Checkout; + web3Provider?: Web3Provider; +}; + +export default function AddFundsWidget({ + checkout, + web3Provider, + showOnrampOption = true, + showSwapOption = true, + showBridgeOption = true, + tokenAddress, + amount, +}: AddFundsWidgetInputs) { + const [viewState, viewDispatch] = useReducer(viewReducer, initialViewState); + + const viewReducerValues = useMemo( + () => ({ viewState, viewDispatch }), + [viewState, viewReducer], + ); + + const { + eventTargetState: { eventTarget }, + } = useContext(EventTargetContext); + + return ( + + sendAddFundsCloseEvent(eventTarget)} + onBackButtonClick={() => sendAddFundsGoBackEvent(eventTarget)} + /> + + ); +} diff --git a/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidgetEvents.ts b/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidgetEvents.ts new file mode 100644 index 0000000000..6f03661e54 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/add-funds/AddFundsWidgetEvents.ts @@ -0,0 +1,38 @@ +import { + WidgetEvent, + WidgetType, + AddFundsEventType, + IMTBLWidgetEvents, +} from '@imtbl/checkout-sdk'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function sendAddFundsCloseEvent(eventTarget: Window | EventTarget) { + const closeWidgetEvent = new CustomEvent< + WidgetEvent + >(IMTBLWidgetEvents.IMTBL_ADD_FUNDS_WIDGET_EVENT, { + detail: { + type: AddFundsEventType.CLOSE_WIDGET, + data: {}, + }, + }); + // TODO: please remove or if necessary keep the eslint ignore + // eslint-disable-next-line no-console + console.log('close widget event:', closeWidgetEvent); + if (eventTarget !== undefined) eventTarget.dispatchEvent(closeWidgetEvent); +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function sendAddFundsGoBackEvent(eventTarget: Window | EventTarget) { + const closeWidgetEvent = new CustomEvent< + WidgetEvent + >(IMTBLWidgetEvents.IMTBL_ADD_FUNDS_WIDGET_EVENT, { + detail: { + type: AddFundsEventType.GO_BACK, + data: {}, + }, + }); + // TODO: please remove or if necessary keep the eslint ignore + // eslint-disable-next-line no-console + console.log('go back event:', closeWidgetEvent); + if (eventTarget !== undefined) eventTarget.dispatchEvent(closeWidgetEvent); +} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidget.tsx b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidget.tsx index 36386b6953..2b7712546a 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidget.tsx +++ b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidget.tsx @@ -1,10 +1,12 @@ -import { useMemo, useReducer } from 'react'; +import { useEffect, useMemo, useReducer } from 'react'; import { Checkout, CheckoutWidgetConfiguration, CheckoutWidgetParams, } from '@imtbl/checkout-sdk'; +import { Web3Provider } from '@ethersproject/providers'; import { + CheckoutActions, checkoutReducer, initialCheckoutState, } from './context/CheckoutContext'; @@ -16,10 +18,13 @@ export type CheckoutWidgetInputs = { checkout: Checkout; params: CheckoutWidgetParams; config: CheckoutWidgetConfiguration; + provider?: Web3Provider }; export default function CheckoutWidget(props: CheckoutWidgetInputs) { - const { config, checkout, params } = props; + const { + config, checkout, params, provider, + } = props; const { environment, publishableKey } = checkout.config; const [, iframeURL] = useMemo(() => { @@ -33,12 +38,23 @@ export default function CheckoutWidget(props: CheckoutWidgetInputs) { ); const checkoutReducerValues = useMemo( () => ({ - checkoutState: { ...checkoutState, iframeURL, checkout }, + checkoutState: { + ...checkoutState, iframeURL, checkout, + }, checkoutDispatch, }), [checkoutState, checkoutDispatch, iframeURL, checkout], ); + useEffect(() => { + checkoutDispatch({ + payload: { + type: CheckoutActions.SET_PROVIDER, + provider, + }, + }); + }, [provider]); + return ( diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetRoot.tsx b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetRoot.tsx index dd640a27fc..3704a70fc2 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetRoot.tsx +++ b/packages/checkout/widgets-lib/src/widgets/checkout/CheckoutWidgetRoot.tsx @@ -59,6 +59,7 @@ export class CheckoutWidgetRoot extends Base { } > void; + initialised: boolean; } export const initialCheckoutState: CheckoutState = { @@ -26,10 +25,10 @@ export const initialCheckoutState: CheckoutState = { iframeURL: undefined, iframeContentWindow: undefined, postMessageHandler: undefined, - providerRelay: undefined, walletProviderInfo: null, walletProviderName: null, sendCloseEvent: () => { }, + initialised: false, }; export interface CheckoutContextState { @@ -47,10 +46,10 @@ type ActionPayload = | SetIframeURLPayload | SetPostMessageHandlerPayload | SetIframeContentWindowPayload - | SetProviderRelayPayload | SetPassportPayload | SetProviderNamePayload - | SetSendCloseEventPayload; + | SetSendCloseEventPayload + | SetInitialisedPayload; export enum CheckoutActions { SET_CHECKOUT = 'SET_CHECKOUT', @@ -58,10 +57,10 @@ export enum CheckoutActions { SET_IFRAME_URL = 'SET_IFRAME_URL', SET_POST_MESSAGE_HANDLER = 'SET_POST_MESSAGE_HANDLER', SET_CHECKOUT_APP_IFRAME = 'SET_CHECKOUT_APP_IFRAME', - SET_PROVIDER_RELAY = 'SET_PROVIDER_RELAY', SET_PASSPORT = 'SET_PASSPORT', SET_WALLET_PROVIDER_NAME = 'SET_WALLET_PROVIDER_NAME', SET_SEND_CLOSE_EVENT = 'SET_SEND_CLOSE_EVENT', + SET_INITIALISED = 'SET_INITIALISED', } export interface SetCheckoutPayload { @@ -71,7 +70,7 @@ export interface SetCheckoutPayload { export interface SetProviderPayload { type: CheckoutActions.SET_PROVIDER; - provider: Web3Provider; + provider: Web3Provider | undefined; } export interface SetIframeURLPayload { @@ -89,11 +88,6 @@ export interface SetPostMessageHandlerPayload { postMessageHandler: PostMessageHandler; } -export interface SetProviderRelayPayload { - type: CheckoutActions.SET_PROVIDER_RELAY; - providerRelay: ProviderRelay; -} - export interface SetPassportPayload { type: CheckoutActions.SET_PASSPORT; passport: Passport; @@ -109,6 +103,11 @@ export interface SetSendCloseEventPayload { sendCloseEvent: () => void; } +export interface SetInitialisedPayload { + type: CheckoutActions.SET_INITIALISED; + initialised: boolean; +} + // eslint-disable-next-line @typescript-eslint/naming-convention export const CheckoutContext = createContext({ checkoutState: initialCheckoutState, @@ -154,11 +153,6 @@ export const checkoutReducer: Reducer = ( ...state, postMessageHandler: action.payload.postMessageHandler, }; - case CheckoutActions.SET_PROVIDER_RELAY: - return { - ...state, - providerRelay: action.payload.providerRelay, - }; case CheckoutActions.SET_WALLET_PROVIDER_NAME: return { ...state, @@ -169,6 +163,11 @@ export const checkoutReducer: Reducer = ( ...state, sendCloseEvent: action.payload.sendCloseEvent, }; + case CheckoutActions.SET_INITIALISED: + return { + ...state, + initialised: action.payload.initialised, + }; default: return state; } diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/context/CheckoutContextProvider.tsx b/packages/checkout/widgets-lib/src/widgets/checkout/context/CheckoutContextProvider.tsx index 995142d102..c9ceb5abde 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/context/CheckoutContextProvider.tsx +++ b/packages/checkout/widgets-lib/src/widgets/checkout/context/CheckoutContextProvider.tsx @@ -1,4 +1,4 @@ -import { PostMessageHandler } from '@imtbl/checkout-sdk'; +import { PostMessageHandler, PostMessageHandlerEventType } from '@imtbl/checkout-sdk'; import { Dispatch, ReactNode, useContext, useEffect, } from 'react'; @@ -8,7 +8,6 @@ import { CheckoutContext, CheckoutState, } from './CheckoutContext'; -import { ProviderRelay } from './ProviderRelay'; type CheckoutContextProviderProps = { values: { @@ -52,17 +51,12 @@ export function CheckoutContextProvider({ }, [iframeContentWindow, checkout, iframeURL]); useEffect(() => { - if (!provider || !postMessageHandler) return undefined; - checkoutDispatch({ - payload: { - type: CheckoutActions.SET_PROVIDER_RELAY, - providerRelay: new ProviderRelay(postMessageHandler, provider), - }, - }); + if (!provider || !postMessageHandler) return; - return () => { - postMessageHandler?.destroy(); - }; + postMessageHandler.send(PostMessageHandlerEventType.PROVIDER_UPDATED, { + isMetamask: provider.provider.isMetaMask, + isPassport: (provider.provider as any)?.isPassport, + }); }, [provider, postMessageHandler]); return ( diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/context/ProviderRelay.ts b/packages/checkout/widgets-lib/src/widgets/checkout/context/ProviderRelay.ts deleted file mode 100644 index 6d9e09ab00..0000000000 --- a/packages/checkout/widgets-lib/src/widgets/checkout/context/ProviderRelay.ts +++ /dev/null @@ -1,45 +0,0 @@ -// TODO ProviderRelay should live in a different folder - -import { Web3Provider } from '@ethersproject/providers'; -import { - PostMessageData, - PostMessageHandler, - PostMessageHandlerEventType, -} from '@imtbl/checkout-sdk'; - -export class ProviderRelay { - private provider: Web3Provider; - - private postMessageHandler: PostMessageHandler; - - constructor(postMessageHandler: PostMessageHandler, provider: Web3Provider) { - this.provider = provider; - this.postMessageHandler = postMessageHandler; - - postMessageHandler.subscribe(this.onMessage); - } - - private onMessage = ({ type, payload }: PostMessageData) => { - if (type !== PostMessageHandlerEventType.PROVIDER_RELAY) return; - - if (!this.provider.provider.request) { - throw new Error('Provider does not support request method'); - } - - this.provider.provider - .request({ method: payload.method, params: payload.params }) - .then((resp) => { - const formattedResponse = { - id: payload.id, - jsonrpc: '2.0', - result: resp, - }; - - // Relay the response back to proxied provider - this.postMessageHandler.send( - PostMessageHandlerEventType.PROVIDER_RELAY, - formattedResponse, - ); - }); - }; -} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useCheckoutEventsRelayer.ts b/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useCheckoutEventsRelayer.ts new file mode 100644 index 0000000000..43bf561c10 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useCheckoutEventsRelayer.ts @@ -0,0 +1,71 @@ +/* eslint-disable no-case-declarations */ +import { useContext, useEffect, useRef } from 'react'; +import { + CheckoutEventType, + PostMessageHandlerEventType, + CheckoutSuccessEventType, +} from '@imtbl/checkout-sdk'; +import { useCheckoutContext } from '../context/CheckoutContextProvider'; +import { EventTargetContext } from '../../../context/event-target-context/EventTargetContext'; +import { CheckoutActions } from '../context/CheckoutContext'; +import { sendCheckoutEvent } from '../CheckoutWidgetEvents'; + +export function useCheckoutEventsRelayer() { + const [{ postMessageHandler, provider }, checkoutDispatch] = useCheckoutContext(); + const { + eventTargetState: { eventTarget }, + } = useContext(EventTargetContext); + const unsubscribePostMessageHandler = useRef<(() => void) | undefined>(); + + useEffect(() => { + if (!postMessageHandler) return undefined; + unsubscribePostMessageHandler.current?.(); + + unsubscribePostMessageHandler.current = postMessageHandler.subscribe( + ({ type, payload }) => { + if (type !== PostMessageHandlerEventType.WIDGET_EVENT) { + return; + } + + const event = { ...payload }; + + if (event.detail.type === CheckoutEventType.INITIALISED) { + checkoutDispatch({ + payload: { + type: CheckoutActions.SET_INITIALISED, + initialised: true, + }, + }); + } + + if (event.detail.type === CheckoutEventType.DISCONNECTED) { + checkoutDispatch({ + payload: { + type: CheckoutActions.SET_PROVIDER, + provider: undefined, + }, + }); + } + + if ( + event.detail.type === CheckoutEventType.SUCCESS + && event.detail.data.type === CheckoutSuccessEventType.CONNECT_SUCCESS + ) { + if (!provider) { + throw new Error( + 'Provider not found, unable to send checkout connect success event', + ); + } + + event.detail.data.data.provider = provider; + } + + sendCheckoutEvent(eventTarget, event.detail); + }, + ); + + return () => { + unsubscribePostMessageHandler.current?.(); + }; + }, [postMessageHandler, checkoutDispatch, eventTarget, provider]); +} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useEip6963Relayer.ts b/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useEip6963Relayer.ts new file mode 100644 index 0000000000..81e8fa1b8b --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useEip6963Relayer.ts @@ -0,0 +1,43 @@ +import { useCallback, useEffect, useRef } from 'react'; +import { EIP6963ProviderDetail, PostMessageHandlerEventType } from '@imtbl/checkout-sdk'; +import { useCheckoutContext } from '../context/CheckoutContextProvider'; + +export function useEip6963Relayer() { + const [checkoutState] = useCheckoutContext(); + const { postMessageHandler } = checkoutState; + const unsubscribePostMessageHandler = useRef<() => void>(); + + const onAnnounce = useCallback((event: CustomEvent) => { + postMessageHandler?.send(PostMessageHandlerEventType.EIP_6963_EVENT, { + message: 'eip6963:announceProvider', + info: event.detail.info, + }); + }, [postMessageHandler]); + + useEffect( + () => { + if (!postMessageHandler) return () => { }; + + window.addEventListener('eip6963:announceProvider', onAnnounce as any); + + return () => window.removeEventListener('eip6963:announceProvider', onAnnounce as any); + }, + [postMessageHandler, onAnnounce], + ); + + const onRequest = useCallback((payload: any) => { + if (payload.message !== 'eip6963:requestProvider') return; + + window.dispatchEvent(new CustomEvent('eip6963:requestProvider')); + }, [postMessageHandler]); + + useEffect(() => { + if (!postMessageHandler) return; + unsubscribePostMessageHandler.current?.(); + unsubscribePostMessageHandler.current = postMessageHandler.subscribe((message) => { + if (message.type === PostMessageHandlerEventType.EIP_6963_EVENT) { + onRequest(message.payload); + } + }); + }, [postMessageHandler, onRequest]); +} diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useProviderRelay.ts b/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useProviderRelay.ts new file mode 100644 index 0000000000..1e5d33f703 --- /dev/null +++ b/packages/checkout/widgets-lib/src/widgets/checkout/hooks/useProviderRelay.ts @@ -0,0 +1,129 @@ +import { useCallback, useEffect, useRef } from 'react'; +import { + EIP6963ProviderInfo, + PostMessageData, + PostMessageHandlerEventType, +} from '@imtbl/checkout-sdk'; +import { Web3Provider } from '@ethersproject/providers'; +import { useCheckoutContext } from '../context/CheckoutContextProvider'; +import { CheckoutActions } from '../context/CheckoutContext'; + +// TODO these types should be in sync with Checkout App +type MessageId = number | string | null; + +interface JsonRpcRequestMessage { + type: 'dapp'; + jsonrpc: '2.0'; + // Optional in the request. + id?: MessageId; + method: string; + params?: TParams; +} + +type ProviderRelayPayload = { + jsonRpcRequestMessage: JsonRpcRequestMessage; + eip6963Info: EIP6963ProviderInfo; +}; + +export function useProviderRelay() { + const [{ checkout, postMessageHandler, provider }, checkoutDispatch] = useCheckoutContext(); + + const unsubscribePostMessageHandler = useRef<() => void>(); + + /** + * Execute a request using the provider + * and relay the response back using the postMessageHandler + */ + const execute = useCallback( + async (payload: ProviderRelayPayload, executeProvider: Web3Provider) => { + if (!executeProvider?.provider.request) { + throw new Error("Provider only supports 'request' method"); + } + + if (!postMessageHandler) { + throw new Error( + 'Provider can execute request because PostMessageHandler is not initialized', + ); + } + + const { id, params, method } = payload.jsonRpcRequestMessage; + + // Execute the request + const result = await executeProvider.provider.request({ method, params }); + const formattedResponse = { id, result, jsonrpc: '2.0' }; + + // Send the response using the postMessageHandler + postMessageHandler.send(PostMessageHandlerEventType.PROVIDER_RELAY, { + response: formattedResponse, + eip6963Info: payload.eip6963Info, + }); + }, + [postMessageHandler], + ); + + /** + * Handle incoming provider relay messages + */ + const onJsonRpcRequestMessage = useCallback( + async ({ type, payload }: PostMessageData) => { + if (!postMessageHandler || !checkout) return; + if (type !== PostMessageHandlerEventType.PROVIDER_RELAY) return; + + const providerRelayPayload = payload as ProviderRelayPayload; + + const injectedProviders = checkout.getInjectedProviders(); + const targetProvider = injectedProviders.find( + (p) => p.info.uuid === providerRelayPayload?.eip6963Info?.uuid, + ); + + if (!targetProvider && providerRelayPayload.eip6963Info !== undefined) { + // eslint-disable-next-line no-console + console.error( + 'PARENT - requested provider not found', + providerRelayPayload?.eip6963Info, + injectedProviders, + ); + return; + } + + // If provider is not defined, connect the target provider + let currentProvider = provider; + if (!currentProvider && targetProvider) { + const connectResponse = await checkout.connect({ + provider: new Web3Provider(targetProvider.provider), + }); + currentProvider = connectResponse.provider; + + // Set provider and execute the request + checkoutDispatch({ + payload: { + type: CheckoutActions.SET_PROVIDER, + provider: currentProvider, + }, + }); + } + + if (!currentProvider) { + throw new Error('Provider is not defined'); + } + + await execute(providerRelayPayload, currentProvider); + }, + + [provider, postMessageHandler, checkout, execute], + ); + + /** + * Subscribe to provider relay messages + */ + useEffect(() => { + // TODO we need to unsubscribe everywhere + if (!postMessageHandler) return; + unsubscribePostMessageHandler.current?.(); + unsubscribePostMessageHandler.current = postMessageHandler?.subscribe(onJsonRpcRequestMessage); + }, [postMessageHandler, onJsonRpcRequestMessage]); +} + +// TODO - +// 1 - commit the unsub part +// 2 - add unsubs to all postMessageHandlers subscriptions diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/utils/config.ts b/packages/checkout/widgets-lib/src/widgets/checkout/utils/config.ts index 19e9703eb3..a796a1e7fb 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/utils/config.ts +++ b/packages/checkout/widgets-lib/src/widgets/checkout/utils/config.ts @@ -2,3 +2,22 @@ * The timeout in milliseconds for the iframe to be initialized. */ export const IFRAME_INIT_TIMEOUT_MS = 10000; + +/** + * The permissions to allow on the iframe. + */ +export const IFRAME_ALLOW_PERMISSIONS = ` + accelerometer; + camera; + microphone; + geolocation; + gyroscope; + fullscreen; + autoplay; + encrypted-media; + picture-in-picture; + clipboard-write; + clipboard-read; +` + .trim() + .replace(/\n/g, ''); diff --git a/packages/checkout/widgets-lib/src/widgets/checkout/views/CheckoutAppIframe.tsx b/packages/checkout/widgets-lib/src/widgets/checkout/views/CheckoutAppIframe.tsx index b74f061938..eb8fc1f922 100644 --- a/packages/checkout/widgets-lib/src/widgets/checkout/views/CheckoutAppIframe.tsx +++ b/packages/checkout/widgets-lib/src/widgets/checkout/views/CheckoutAppIframe.tsx @@ -1,22 +1,26 @@ import { Box } from '@biom3/react'; -import { useTranslation } from 'react-i18next'; -import { - useContext, useEffect, useRef, useState, -} from 'react'; import { CheckoutEventType, - IMTBLWidgetEvents, - PostMessageHandlerEventType, - WidgetEventData, - WidgetType, } from '@imtbl/checkout-sdk'; -import { CheckoutActions } from '../context/CheckoutContext'; -import { useCheckoutContext } from '../context/CheckoutContextProvider'; -import { sendCheckoutEvent } from '../CheckoutWidgetEvents'; +import { + useContext, + useEffect, + useRef, useState, +} from 'react'; +import { useTranslation } from 'react-i18next'; import { EventTargetContext } from '../../../context/event-target-context/EventTargetContext'; -import { LoadingView } from '../../../views/loading/LoadingView'; import { ErrorView } from '../../../views/error/ErrorView'; -import { IFRAME_INIT_TIMEOUT_MS } from '../utils/config'; +import { LoadingView } from '../../../views/loading/LoadingView'; +import { sendCheckoutEvent } from '../CheckoutWidgetEvents'; +import { CheckoutActions } from '../context/CheckoutContext'; +import { useCheckoutContext } from '../context/CheckoutContextProvider'; +import { useCheckoutEventsRelayer } from '../hooks/useCheckoutEventsRelayer'; +import { useEip6963Relayer } from '../hooks/useEip6963Relayer'; +import { useProviderRelay } from '../hooks/useProviderRelay'; +import { + IFRAME_ALLOW_PERMISSIONS, + IFRAME_INIT_TIMEOUT_MS, +} from '../utils/config'; export interface LoadingHandoverProps { text: string; @@ -27,13 +31,18 @@ export interface LoadingHandoverProps { export function CheckoutAppIframe() { const { t } = useTranslation(); const iframeRef = useRef(null); - const timeoutRef = useRef(null); const [loadingError, setLoadingError] = useState(false); - const [initialised, setInitialised] = useState(false); const [ - { iframeURL, postMessageHandler, iframeContentWindow }, + { + iframeURL, iframeContentWindow, initialised, + }, checkoutDispatch, ] = useCheckoutContext(); + const timeoutRef = useRef(null); + const initialisedRef = useRef(initialised); + useCheckoutEventsRelayer(); + useEip6963Relayer(); + useProviderRelay(); const loading = !iframeURL || !iframeContentWindow || !initialised; @@ -41,6 +50,23 @@ export function CheckoutAppIframe() { eventTargetState: { eventTarget }, } = useContext(EventTargetContext); + useEffect(() => { + initialisedRef.current = initialised; + }, [initialised]); + + useEffect(() => { + timeoutRef.current = setTimeout(() => { + if (initialisedRef.current) return; + + setLoadingError(true); + clearTimeout(timeoutRef.current!); + }, IFRAME_INIT_TIMEOUT_MS); + + return () => { + clearTimeout(timeoutRef.current!); + }; + }, []); + const onIframeLoad = () => { const iframe = iframeRef.current; if (!iframe?.contentWindow) { @@ -55,46 +81,6 @@ export function CheckoutAppIframe() { }); }; - useEffect(() => { - if (!postMessageHandler) return undefined; - - // subscribe to widget events - postMessageHandler.subscribe(({ type, payload }) => { - // FIXME: improve typing - const event: { - type: IMTBLWidgetEvents.IMTBL_CHECKOUT_WIDGET_EVENT; - detail: { - type: CheckoutEventType; - data: WidgetEventData[WidgetType.CHECKOUT][keyof WidgetEventData[WidgetType.CHECKOUT]]; - }; - } = payload as any; - - if (type !== PostMessageHandlerEventType.WIDGET_EVENT) return; - - // forward events - sendCheckoutEvent(eventTarget, event.detail); - - // check if the widget has been initialised - if (event.detail.type === CheckoutEventType.INITIALISED) { - setInitialised(true); - clearTimeout(timeoutRef.current!); - } - }); - - // check if loaded correctly - timeoutRef.current = setTimeout(() => { - if (!initialised) { - setLoadingError(true); - clearTimeout(timeoutRef.current!); - } - }, IFRAME_INIT_TIMEOUT_MS); - - return () => { - postMessageHandler.destroy(); - clearTimeout(timeoutRef.current!); - }; - }, [postMessageHandler]); - if (loadingError) { return ( )} sx={{ diff --git a/packages/checkout/widgets-lib/src/widgets/connect/components/WalletItem.tsx b/packages/checkout/widgets-lib/src/widgets/connect/components/WalletItem.tsx index a14286ae83..f139bdd8dd 100644 --- a/packages/checkout/widgets-lib/src/widgets/connect/components/WalletItem.tsx +++ b/packages/checkout/widgets-lib/src/widgets/connect/components/WalletItem.tsx @@ -1,7 +1,7 @@ import { Badge, Box, MenuItem } from '@biom3/react'; import { useTranslation } from 'react-i18next'; import { cloneElement, ReactElement, useState } from 'react'; -import { EIP6963ProviderDetail, WalletProviderName } from '@imtbl/checkout-sdk'; +import { EIP6963ProviderInfo, WalletProviderName } from '@imtbl/checkout-sdk'; import { RawImage } from '../../../components/RawImage/RawImage'; import { getProviderSlugFromRdns } from '../../../lib/provider'; import useIsSmallScreen from '../../../lib/hooks/useIsSmallScreen'; @@ -9,8 +9,8 @@ import useIsSmallScreen from '../../../lib/hooks/useIsSmallScreen'; export interface WalletProps { loading?: boolean; recommended?: boolean; - onWalletItemClick: (providerDetail: EIP6963ProviderDetail) => void; - providerDetail: EIP6963ProviderDetail; + onWalletItemClick: () => void; + providerInfo: EIP6963ProviderInfo; rc?: RC; } @@ -20,13 +20,13 @@ export function WalletItem< rc = , loading = false, recommended = false, - providerDetail, + providerInfo, onWalletItemClick, }: WalletProps) { const { t } = useTranslation(); const { isSmallScreenMode } = useIsSmallScreen(); const [busy, setBusy] = useState(false); - const providerSlug = getProviderSlugFromRdns(providerDetail.info.rdns); + const providerSlug = getProviderSlugFromRdns(providerInfo.rdns); const isPassport = providerSlug === WalletProviderName.PASSPORT; const isPassportOrMetamask = isPassport || providerSlug === WalletProviderName.METAMASK; const offsetStyles = { marginLeft: '65px' }; @@ -39,13 +39,13 @@ export function WalletItem< setBusy(true); // let the parent handle errors try { - await onWalletItemClick(providerDetail); + await onWalletItemClick(); } finally { setBusy(false); } }, })} - testId={`wallet-list-${providerDetail.info.rdns}`} + testId={`wallet-list-${providerInfo.rdns}`} size="medium" emphasized sx={{ @@ -54,8 +54,8 @@ export function WalletItem< }} > ))} - {providerDetail.info.name} + {providerInfo.name} {(!busy && )} diff --git a/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx b/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx index 8ff1b32cc9..6ca219c94e 100644 --- a/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx +++ b/packages/checkout/widgets-lib/src/widgets/connect/components/WalletList.tsx @@ -372,8 +372,8 @@ export function WalletList(props: WalletListProps) { handleWalletItemClick(passportProviderDetail)} + providerInfo={passportProviderDetail.info} rc={( handleWalletItemClick(filteredProviders[0])} + providerInfo={filteredProviders[0].info} rc={( { +): FundingStep[] => { if (fundingRoutes.length === 0) { return []; } diff --git a/packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx b/packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx index ac2f84a8c7..bbff13773b 100644 --- a/packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx +++ b/packages/checkout/widgets-lib/src/widgets/swap/SwapWidget.tsx @@ -7,10 +7,9 @@ import { useState, } from 'react'; import { - DexConfig, TokenFilterTypes, IMTBLWidgetEvents, SwapWidgetParams, + TokenFilterTypes, IMTBLWidgetEvents, SwapWidgetParams, + SwapDirection, } from '@imtbl/checkout-sdk'; -import { ImmutableConfiguration } from '@imtbl/config'; -import { Exchange } from '@imtbl/dex-sdk'; import { useTranslation } from 'react-i18next'; import { SwapCoins } from './views/SwapCoins'; import { LoadingView } from '../../views/loading/LoadingView'; @@ -61,6 +60,8 @@ export default function SwapWidget({ fromTokenAddress, toTokenAddress, config, + autoProceed, + direction, }: SwapWidgetInputs) { const { t } = useTranslation(); const { @@ -169,30 +170,6 @@ export default function SwapWidget({ // connect loader handle the switch network functionality if (network.chainId !== getL2ChainId(checkout.config)) return; - let dexConfig: DexConfig | undefined; - try { - dexConfig = ( - (await checkout.config.remote.getConfig('dex')) as DexConfig - ); - } catch (err: any) { - showErrorView(err); - return; - } - - const exchange = new Exchange({ - chainId: network.chainId, - baseConfig: new ImmutableConfiguration({ environment }), - secondaryFees: dexConfig.secondaryFees, - overrides: dexConfig.overrides, - }); - - swapDispatch({ - payload: { - type: SwapActions.SET_EXCHANGE, - exchange, - }, - }); - swapDispatch({ payload: { type: SwapActions.SET_NETWORK, @@ -208,6 +185,31 @@ export default function SwapWidget({ })(); }, [checkout, provider]); + useEffect(() => { + swapDispatch({ + payload: { + type: SwapActions.SET_AUTO_PROCEED, + autoProceed: autoProceed ?? false, + direction: direction ?? SwapDirection.FROM, + }, + }); + }, [autoProceed, direction]); + + const cancelAutoProceed = useCallback(() => { + if (autoProceed) { + swapDispatch({ + payload: { + type: SwapActions.SET_AUTO_PROCEED, + autoProceed: false, + direction: SwapDirection.FROM, + }, + }); + } + }, [autoProceed, swapDispatch]); + + const fromAmount = direction === SwapDirection.FROM || direction == null ? amount : undefined; + const toAmount = direction === SwapDirection.TO ? amount : undefined; + return ( @@ -218,14 +220,11 @@ export default function SwapWidget({ {viewState.view.type === SwapWidgetViews.SWAP && ( )} {viewState.view.type === SwapWidgetViews.IN_PROGRESS && ( diff --git a/packages/checkout/widgets-lib/src/widgets/swap/SwapWidgetRoot.tsx b/packages/checkout/widgets-lib/src/widgets/swap/SwapWidgetRoot.tsx index e61aeda164..f68af157c8 100644 --- a/packages/checkout/widgets-lib/src/widgets/swap/SwapWidgetRoot.tsx +++ b/packages/checkout/widgets-lib/src/widgets/swap/SwapWidgetRoot.tsx @@ -2,6 +2,7 @@ import React, { Suspense } from 'react'; import { ChainId, IMTBLWidgetEvents, + SwapDirection, SwapWidgetParams, WalletProviderName, WidgetConfiguration, @@ -71,6 +72,10 @@ export class Swap extends Base { validatedParams.toTokenAddress = ''; } + if (params.autoProceed) { + validatedParams.autoProceed = true; + } + return validatedParams; } @@ -131,6 +136,8 @@ export class Swap extends Base { toTokenAddress={this.parameters.toTokenAddress} amount={this.parameters.amount} config={this.strongConfig()} + autoProceed={this.parameters.autoProceed} + direction={this.parameters.direction ?? SwapDirection.FROM} /> diff --git a/packages/checkout/widgets-lib/src/widgets/swap/components/SwapButton.tsx b/packages/checkout/widgets-lib/src/widgets/swap/components/SwapButton.tsx index b2e26dc937..822542f3dc 100644 --- a/packages/checkout/widgets-lib/src/widgets/swap/components/SwapButton.tsx +++ b/packages/checkout/widgets-lib/src/widgets/swap/components/SwapButton.tsx @@ -1,188 +1,27 @@ import { Box, Button } from '@biom3/react'; -import { useContext, useState } from 'react'; -import { TransactionResponse } from '@imtbl/dex-sdk'; -import { CheckoutErrorType } from '@imtbl/checkout-sdk'; import { useTranslation } from 'react-i18next'; -import { BigNumber } from 'ethers'; -import { getL2ChainId } from '../../../lib'; -import { PrefilledSwapForm, SwapWidgetViews } from '../../../context/view-context/SwapViewContextTypes'; -import { - ViewContext, - ViewActions, - SharedViews, -} from '../../../context/view-context/ViewContext'; import { swapButtonBoxStyle, swapButtonIconLoadingStyle, } from './SwapButtonStyles'; -import { SwapFormData } from './swapFormTypes'; -import { TransactionRejected } from '../../../components/TransactionRejected/TransactionRejected'; -import { ConnectLoaderContext } from '../../../context/connect-loader-context/ConnectLoaderContext'; -import { UserJourney, useAnalytics } from '../../../context/analytics-provider/SegmentAnalyticsProvider'; -import { isPassportProvider } from '../../../lib/provider'; export interface SwapButtonProps { loading: boolean - updateLoading: (value: boolean) => void validator: () => boolean - transaction: TransactionResponse | null; - data?: SwapFormData; - insufficientFundsForGas: boolean; - openNotEnoughImxDrawer: () => void; - openNetworkSwitchDrawer: () => void; + sendTransaction: () => Promise; } export function SwapButton({ loading, - updateLoading, validator, - transaction, - data, - insufficientFundsForGas, - openNotEnoughImxDrawer, - openNetworkSwitchDrawer, + sendTransaction, }: SwapButtonProps) { const { t } = useTranslation(); - const [showTxnRejectedState, setShowTxnRejectedState] = useState(false); - const { viewDispatch } = useContext(ViewContext); - const { connectLoaderState } = useContext(ConnectLoaderContext); - const { checkout, provider } = connectLoaderState; - const { track } = useAnalytics(); - const sendTransaction = async () => { - const isValid = validator(); - // Tracking swap from data here and is valid or not to understand behaviour - track({ - userJourney: UserJourney.SWAP, - screen: 'SwapCoins', - control: 'Swap', - controlType: 'Button', - extras: { - swapFromAddress: data?.fromTokenAddress, - swapFromAmount: data?.fromAmount, - swapFromTokenSymbol: data?.fromTokenSymbol, - swapToAddress: data?.toTokenAddress, - swapToAmount: data?.toAmount, - swapToTokenSymbol: data?.toTokenSymbol, - isSwapFormValid: isValid, - hasFundsForGas: !insufficientFundsForGas, - }, - }); - if (!isValid) return; - if (!checkout || !provider || !transaction) return; - if (insufficientFundsForGas) { - openNotEnoughImxDrawer(); - return; - } - - try { - // check for switch network here - const currentChainId = await (provider.provider as any).request({ method: 'eth_chainId', params: [] }); - // eslint-disable-next-line radix - const parsedChainId = parseInt(currentChainId.toString()); - if (parsedChainId !== getL2ChainId(checkout.config)) { - openNetworkSwitchDrawer(); - return; - } - } catch (err) { - // eslint-disable-next-line no-console - console.error('Current network check failed', err); - } - - if (!transaction) return; - try { - updateLoading(true); - const prefilledSwapData:PrefilledSwapForm = { - fromAmount: data?.fromAmount || '', - fromTokenAddress: data?.fromTokenAddress || '', - toTokenAddress: data?.toTokenAddress || '', - toAmount: data?.toAmount || '', - }; - - if (transaction.approval) { - // If we need to approve a spending limit first - // send user to Approve ERC20 Onbaording flow - viewDispatch({ - payload: { - type: ViewActions.UPDATE_VIEW, - view: { - type: SwapWidgetViews.APPROVE_ERC20, - data: { - approveTransaction: transaction.approval.transaction, - transaction: transaction.swap.transaction, - info: transaction.quote, - swapFormInfo: prefilledSwapData, - }, - }, - }, - }); - return; - } - const txn = await checkout.sendTransaction({ - provider, - transaction: { - ...transaction.swap.transaction, - gasPrice: (isPassportProvider(provider) ? BigNumber.from(0) : undefined), - }, - }); - - viewDispatch({ - payload: { - type: ViewActions.UPDATE_VIEW, - view: { - type: SwapWidgetViews.IN_PROGRESS, - data: { - transactionResponse: txn.transactionResponse, - swapForm: prefilledSwapData as PrefilledSwapForm, - }, - }, - }, - }); - } catch (err: any) { - // eslint-disable-next-line no-console - console.error(err); - - updateLoading(false); - if (err.type === CheckoutErrorType.USER_REJECTED_REQUEST_ERROR) { - setShowTxnRejectedState(true); - return; - } - if (err.type === CheckoutErrorType.UNPREDICTABLE_GAS_LIMIT) { - viewDispatch({ - payload: { - type: ViewActions.UPDATE_VIEW, - view: { - type: SwapWidgetViews.PRICE_SURGE, - data: data as PrefilledSwapForm, - }, - }, - }); - return; - } - if (err.type === CheckoutErrorType.TRANSACTION_FAILED - || err.type === CheckoutErrorType.INSUFFICIENT_FUNDS - || (err.receipt && err.receipt.status === 0)) { - viewDispatch({ - payload: { - type: ViewActions.UPDATE_VIEW, - view: { - type: SwapWidgetViews.FAIL, - reason: 'Transaction failed', - data: data as PrefilledSwapForm, - }, - }, - }); - return; - } - viewDispatch({ - payload: { - type: ViewActions.UPDATE_VIEW, - view: { - type: SharedViews.ERROR_VIEW, - error: err, - }, - }, - }); + const handleClick = async () => { + const canSwap = validator(); + if (canSwap) { + await sendTransaction(); } }; @@ -192,22 +31,13 @@ export function SwapButton({ testId="swap-button" disabled={loading} variant="primary" - onClick={sendTransaction} + onClick={handleClick} size="large" > {loading ? ( ) : t('views.SWAP.swapForm.buttonText')} - setShowTxnRejectedState(false)} - onRetry={() => { - sendTransaction(); - setShowTxnRejectedState(false); - }} - /> ); } diff --git a/packages/checkout/widgets-lib/src/widgets/swap/components/SwapForm.tsx b/packages/checkout/widgets-lib/src/widgets/swap/components/SwapForm.tsx index d6f359423f..5d095e3e6a 100644 --- a/packages/checkout/widgets-lib/src/widgets/swap/components/SwapForm.tsx +++ b/packages/checkout/widgets-lib/src/widgets/swap/components/SwapForm.tsx @@ -7,6 +7,7 @@ import { } from '@biom3/react'; import { BigNumber, utils } from 'ethers'; import { TokenInfo, WidgetTheme } from '@imtbl/checkout-sdk'; + import { TransactionResponse } from '@imtbl/dex-sdk'; import { useTranslation } from 'react-i18next'; import { Environment } from '@imtbl/config'; @@ -26,7 +27,6 @@ import { ESTIMATE_DEBOUNCE, getL2ChainId, } from '../../../lib'; -import { quotesProcessor } from '../functions/FetchQuote'; import { SelectInput } from '../../../components/FormComponents/SelectInput/SelectInput'; import { validateFromAmount, @@ -51,6 +51,8 @@ import { processGasFree } from '../functions/processGasFree'; import { processSecondaryFees } from '../functions/processSecondaryFees'; import { processQuoteToken } from '../functions/processQuoteToken'; import { formatQuoteConversionRate } from '../functions/swapConversionRate'; +import { PrefilledSwapForm, SwapWidgetViews } from '../../../context/view-context/SwapViewContextTypes'; +import { TransactionRejected } from '../../../components/TransactionRejected/TransactionRejected'; enum SwapDirection { FROM = 'FROM', @@ -70,18 +72,20 @@ let quoteRequest: CancellablePromise; export interface SwapFromProps { data?: SwapFormData; theme: WidgetTheme; + cancelAutoProceed: () => void; } -export function SwapForm({ data, theme }: SwapFromProps) { +export function SwapForm({ data, theme, cancelAutoProceed }: SwapFromProps) { const { t } = useTranslation(); const { swapState: { allowedTokens, - exchange, tokenBalances, network, + autoProceed, }, } = useContext(SwapContext); + const { connectLoaderState } = useContext(ConnectLoaderContext); const { checkout, provider } = connectLoaderState; const defaultTokenImage = getDefaultTokenImage(checkout?.config.environment, theme); @@ -111,6 +115,7 @@ export function SwapForm({ data, theme }: SwapFromProps) { const [toToken, setToToken] = useState(); const [toTokenError, setToTokenError] = useState(''); const [fromFiatValue, setFromFiatValue] = useState(''); + const [loadedToAndFromTokens, setLoadedToAndFromTokens] = useState(false); // Quote const [quote, setQuote] = useState(null); @@ -143,6 +148,8 @@ export function SwapForm({ data, theme }: SwapFromProps) { const [showUnableToSwapDrawer, setShowUnableToSwapDrawer] = useState(false); const [showNetworkSwitchDrawer, setShowNetworkSwitchDrawer] = useState(false); + const [showTxnRejectedState, setShowTxnRejectedState] = useState(false); + useEffect(() => { if (tokenBalances.length === 0) return; if (!network) return; @@ -190,6 +197,8 @@ export function SwapForm({ data, theme }: SwapFromProps) { isNativeToken(token.address) && data?.toTokenAddress?.toLowerCase() === NATIVE ) || (token.address?.toLowerCase() === data?.toTokenAddress?.toLowerCase()))); } + + setLoadedToAndFromTokens(true); }, [ tokenBalances, allowedTokens, @@ -246,18 +255,17 @@ export function SwapForm({ data, theme }: SwapFromProps) { const processFetchQuoteFrom = async (silently: boolean = false) => { if (!provider) return; - if (!exchange) return; + if (!checkout) return; if (!fromToken) return; if (!toToken) return; try { - const quoteResultPromise = quotesProcessor.fromAmountIn( - exchange, + const quoteResultPromise = checkout.swapQuote({ provider, fromToken, - fromAmount, toToken, - ); + fromAmount, + }); const currentQuoteRequest = CancellablePromise.all([ quoteResultPromise, @@ -332,18 +340,18 @@ export function SwapForm({ data, theme }: SwapFromProps) { const processFetchQuoteTo = async (silently: boolean = false) => { if (!provider) return; - if (!exchange) return; + if (!checkout) return; if (!fromToken) return; if (!toToken) return; try { - const quoteResultPromise = quotesProcessor.fromAmountOut( - exchange, + const quoteResultPromise = checkout.swapQuote({ provider, + fromToken, toToken, + fromAmount: undefined, toAmount, - fromToken, - ); + }); const currentQuoteRequest = CancellablePromise.all([ quoteResultPromise, @@ -461,7 +469,9 @@ export function SwapForm({ data, theme }: SwapFromProps) { }; // Silently refresh the quote - useInterval(() => fetchQuote(true), DEFAULT_QUOTE_REFRESH_INTERVAL); + useInterval(() => { + fetchQuote(true); + }, DEFAULT_QUOTE_REFRESH_INTERVAL); // Fetch quote triggers useEffect(() => { @@ -661,9 +671,106 @@ export function SwapForm({ data, theme }: SwapFromProps) { return isSwapFormValid; }; + const isFormValidForAutoProceed = useMemo(() => { + if (!autoProceed) return false; + if (!loadedToAndFromTokens) return false; + + return !loading; + }, [autoProceed, loading, loadedToAndFromTokens]); + + const canAutoSwap = useMemo(() => { + if (!autoProceed) return false; + if (!isFormValidForAutoProceed) return false; + + const isFormValid = SwapFormValidator(); + + if (!isFormValid) { + cancelAutoProceed(); + return false; + } + + return true; + }, [isFormValidForAutoProceed]); + + const sendTransaction = async () => { + if (!quote) return; + const transaction = quote; + const isValid = SwapFormValidator(); + // Tracking swap from data here and is valid or not to understand behaviour + track({ + userJourney: UserJourney.SWAP, + screen: 'SwapCoins', + control: 'Swap', + controlType: 'Button', + extras: { + swapFromAddress: data?.fromTokenAddress, + swapFromAmount: data?.fromAmount, + swapFromTokenSymbol: data?.fromTokenSymbol, + swapToAddress: data?.toTokenAddress, + swapToAmount: data?.toAmount, + swapToTokenSymbol: data?.toTokenSymbol, + isSwapFormValid: isValid, + hasFundsForGas: !insufficientFundsForGas, + }, + }); + if (!isValid) return; + if (!checkout || !provider || !transaction) return; + if (insufficientFundsForGas) { + cancelAutoProceed(); + openNotEnoughImxDrawer(); + return; + } + + try { + // check for switch network here + const currentChainId = await (provider.provider as any).request({ method: 'eth_chainId', params: [] }); + // eslint-disable-next-line radix + const parsedChainId = parseInt(currentChainId.toString()); + if (parsedChainId !== getL2ChainId(checkout.config)) { + setShowNetworkSwitchDrawer(true); + return; + } + } catch (err) { + // eslint-disable-next-line no-console + console.error('Current network check failed', err); + } + + if (!transaction) return; + + setLoading(true); + const prefilledSwapData:PrefilledSwapForm = { + fromAmount: data?.fromAmount || '', + fromTokenAddress: data?.fromTokenAddress || '', + toTokenAddress: data?.toTokenAddress || '', + toAmount: data?.toAmount || '', + }; + + viewDispatch({ + payload: { + type: ViewActions.UPDATE_VIEW, + view: { + type: SwapWidgetViews.APPROVE_ERC20, + data: { + approveTransaction: transaction.approval?.transaction, + transaction: transaction.swap.transaction, + info: transaction.quote, + swapFormInfo: prefilledSwapData, + }, + }, + }, + }); + }; + + useEffect(() => { + if (!autoProceed) return; + if (!canAutoSwap) return; + sendTransaction(); + }, [canAutoSwap, autoProceed, sendTransaction]); + return ( <> {!isPassportProvider(provider) && ( - { - track({ - userJourney: UserJourney.SWAP, - screen: 'SwapCoins', - control: 'ViewFees', - controlType: 'Button', - }); - }} - sx={{ - paddingBottom: '0', - }} - loading={loading} - /> + { + track({ + userJourney: UserJourney.SWAP, + screen: 'SwapCoins', + control: 'ViewFees', + controlType: 'Button', + }); + }} + sx={{ + paddingBottom: '0', + }} + loading={loading} + /> )} - { - setLoading(value); - }} - loading={loading} - transaction={quote} - data={{ - fromAmount, - toAmount, - fromTokenSymbol: fromToken?.symbol, - fromTokenAddress: fromToken?.address, - toTokenSymbol: toToken?.symbol, - toTokenAddress: toToken?.address, + {!autoProceed && ( + + )} + setShowTxnRejectedState(false)} + onRetry={() => { + sendTransaction(); + setShowTxnRejectedState(false); }} - insufficientFundsForGas={insufficientFundsForGas} - openNotEnoughImxDrawer={openNotEnoughImxDrawer} - openNetworkSwitchDrawer={() => setShowNetworkSwitchDrawer(true)} /> ({ swapState: initialSwapState, @@ -138,6 +149,12 @@ export const swapReducer: Reducer = ( ...state, allowedTokens: action.payload.allowedTokens, }; + case SwapActions.SET_AUTO_PROCEED: + return { + ...state, + autoProceed: action.payload.autoProceed, + direction: action.payload.direction, + }; default: return state; } diff --git a/packages/checkout/widgets-lib/src/widgets/swap/views/ApproveERC20Onboarding.tsx b/packages/checkout/widgets-lib/src/widgets/swap/views/ApproveERC20Onboarding.tsx index f92a8c9ee9..881604f315 100644 --- a/packages/checkout/widgets-lib/src/widgets/swap/views/ApproveERC20Onboarding.tsx +++ b/packages/checkout/widgets-lib/src/widgets/swap/views/ApproveERC20Onboarding.tsx @@ -37,11 +37,12 @@ export function ApproveERC20Onboarding({ data }: ApproveERC20Props) { const { eventTargetState: { eventTarget } } = useContext(EventTargetContext); const isPassport = isPassportProvider(provider); + const noApprovalTransaction = data.approveTransaction === undefined; // Local state const [actionDisabled, setActionDisabled] = useState(false); const [approvalTxnLoading, setApprovalTxnLoading] = useState(false); - const [showSwapTxnStep, setShowSwapTxnStep] = useState(false); + const [showSwapTxnStep, setShowSwapTxnStep] = useState(noApprovalTransaction); const [loading, setLoading] = useState(false); // reject transaction flags const [rejectedSpending, setRejectedSpending] = useState(false); diff --git a/packages/checkout/widgets-lib/src/widgets/swap/views/SwapCoins.tsx b/packages/checkout/widgets-lib/src/widgets/swap/views/SwapCoins.tsx index d4fae90b9a..f8da12f715 100644 --- a/packages/checkout/widgets-lib/src/widgets/swap/views/SwapCoins.tsx +++ b/packages/checkout/widgets-lib/src/widgets/swap/views/SwapCoins.tsx @@ -21,9 +21,11 @@ import { IMX_TOKEN_SYMBOL } from '../../../lib'; import { EventTargetContext } from '../../../context/event-target-context/EventTargetContext'; import { UserJourney, useAnalytics } from '../../../context/analytics-provider/SegmentAnalyticsProvider'; import { isPassportProvider } from '../../../lib/provider'; +import { LoadingView } from '../../../views/loading/LoadingView'; export interface SwapCoinsProps { theme: WidgetTheme; + cancelAutoProceed: () => void; fromAmount?: string; toAmount?: string; fromTokenAddress?: string; @@ -32,6 +34,7 @@ export interface SwapCoinsProps { export function SwapCoins({ theme, + cancelAutoProceed, fromAmount, toAmount, fromTokenAddress, @@ -44,6 +47,7 @@ export function SwapCoins({ const { swapState: { tokenBalances, + autoProceed, }, } = useContext(SwapContext); @@ -79,12 +83,12 @@ export function SwapCoins({ return ( sendSwapWidgetCloseEvent(eventTarget)} /> - )} + ) : ''} footer={} > + {autoProceed && } ); } diff --git a/packages/checkout/widgets-lib/tsconfig.json b/packages/checkout/widgets-lib/tsconfig.json index b7238c9141..ad6a165f60 100644 --- a/packages/checkout/widgets-lib/tsconfig.json +++ b/packages/checkout/widgets-lib/tsconfig.json @@ -1,26 +1,16 @@ { + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", - "target": "es2022", "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, + "customConditions": ["development"], "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "declaration": true, "jsx": "react-jsx", "noImplicitAny": false, "jsxImportSource": "@emotion/react", - "experimentalDecorators": true + "experimentalDecorators": true, }, - "include": ["src", "babel.config.js"], + "include": ["src"], "exclude": ["node_modules", "dist"] } diff --git a/packages/checkout/widgets-sample-app/package.json b/packages/checkout/widgets-sample-app/package.json index 2fc265b5c5..7cd2911ee3 100644 --- a/packages/checkout/widgets-sample-app/package.json +++ b/packages/checkout/widgets-sample-app/package.json @@ -28,6 +28,7 @@ "react-router-dom": "^6.11.0" }, "devDependencies": { + "@swc/core": "^1.3.36", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -37,13 +38,14 @@ "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^4.2.0", "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1", "vite": "^5.0.12", "vite-plugin-node-polyfills": "^0.16.0", "web-vitals": "^2.1.4" }, "private": true, "scripts": { - "build": "tsc && vite build", + "build": "tsc --customConditions \"default\" && vite build", "preview": "vite preview", "start": "vite" } diff --git a/packages/checkout/widgets-sample-app/src/App.tsx b/packages/checkout/widgets-sample-app/src/App.tsx index 605d251aaf..c210693a42 100644 --- a/packages/checkout/widgets-sample-app/src/App.tsx +++ b/packages/checkout/widgets-sample-app/src/App.tsx @@ -31,6 +31,10 @@ function App() { Checkout Widget
+ +
new Checkout(), []); + const factory = useMemo( + () => new WidgetsFactory(checkout,{}), + [checkout] + ); + const addFunds = useMemo( + () => factory.create(WidgetType.ADD_FUNDS, { + config: { theme: WidgetTheme.LIGHT }, + }), + [factory] + ); + const onRamp = useMemo(() => factory.create(WidgetType.ONRAMP), [factory]); + const swap = useMemo(() => factory.create(WidgetType.SWAP), [factory]); + const bridge = useMemo(() => factory.create(WidgetType.BRIDGE), [factory]); + + useEffect(() => { + addFunds.mount(ADD_FUNDS_TARGET_ID, { + showSwapOption:false, + amount: "10", + tokenAddress: "0x1CcCa691501174B4A623CeDA58cC8f1a76dc3439" + }); + addFunds.addListener(AddFundsEventType.GO_BACK, (data: any) => { + console.log("GO_BACK", data); + }); + addFunds.addListener(AddFundsEventType.CLOSE_WIDGET, (data: any) => { + console.log("CLOSE_WIDGET", data); + addFunds.unmount(); + }); + addFunds.addListener(AddFundsEventType.REQUEST_ONRAMP, (data: any) => { + console.log("REQUEST_ONRAMP", data); + addFunds.unmount(); + onRamp.addListener(OnRampEventType.CLOSE_WIDGET, (data: any) => { + console.log("CLOSE_WIDGET", data); + onRamp.unmount(); + }); + onRamp.mount(ADD_FUNDS_TARGET_ID, {}); + }); + addFunds.addListener(AddFundsEventType.REQUEST_SWAP, (data: any) => { + console.log("REQUEST_SWAP", data); + addFunds.unmount(); + swap.addListener(SwapEventType.CLOSE_WIDGET, (data: any) => { + console.log("CLOSE_WIDGET", data); + swap.unmount(); + }); + swap.mount(ADD_FUNDS_TARGET_ID, {}); + }); + addFunds.addListener(AddFundsEventType.REQUEST_BRIDGE, (data: any) => { + console.log("REQUEST_BRIDGE", data); + addFunds.unmount(); + bridge.addListener(BridgeEventType.CLOSE_WIDGET, (data: any) => { + console.log("CLOSE_WIDGET", data); + bridge.unmount(); + }); + bridge.mount(ADD_FUNDS_TARGET_ID, {}); + }); + }, [addFunds]); + + return ( +
+

Checkout Add Funds

+
+ + + + + +
+ ); +} + +export default AddFundsUI; diff --git a/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx b/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx index c04201c999..d252eadf94 100644 --- a/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx +++ b/packages/checkout/widgets-sample-app/src/components/ui/checkout/checkout.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Checkout, CheckoutFlowType, @@ -16,8 +16,13 @@ import { Environment } from "@imtbl/config"; import { WidgetsFactory } from "@imtbl/checkout-widgets"; import { passport } from "../marketplace-orchestrator/passport"; import { Box } from "@biom3/react"; +import { Web3Provider } from "@ethersproject/providers"; function CheckoutUI() { + const [web3Provider, setWeb3Provider] = useState( + undefined + ); + const checkout = useMemo( () => new Checkout({ @@ -30,40 +35,87 @@ function CheckoutUI() { ); const factory = useMemo( () => - new WidgetsFactory(checkout, { theme: WidgetTheme.DARK, language: "en" }), - [checkout] + web3Provider + ? new WidgetsFactory(checkout, { + theme: WidgetTheme.DARK, + language: "en", + }) + : undefined, + [checkout, web3Provider] ); const checkoutWidget = useMemo( () => - factory.create(WidgetType.CHECKOUT, { - config: { - theme: WidgetTheme.LIGHT, - wallet: { showNetworkMenu: false }, - sale: { - hideExcludedPaymentTypes: true, - }, - }, - }), - [checkout] + factory + ? factory.create(WidgetType.CHECKOUT, { + config: { + theme: WidgetTheme.LIGHT, + wallet: { showNetworkMenu: false }, + sale: { + hideExcludedPaymentTypes: true, + }, + }, + provider: web3Provider, + }) + : undefined, + [factory] ); + // Case 1: with MM + useEffect(() => { + (async () => { + const { provider: newProvider } = await checkout.createProvider({ + walletProviderName: WalletProviderName.METAMASK, + }); + + await checkout.connect({ + provider: newProvider, + }); + + const { isConnected } = await checkout.checkIsWalletConnected({ + provider: newProvider, + }); + + if (isConnected) { + setWeb3Provider(newProvider); + } + })(); + }, []); + + // Case 1: with Passport + // useEffect(() => { + // const passportProvider = passport.connectEvm(); + // setWeb3Provider(new Web3Provider(passportProvider)); + // }, []); + + // Case 2: with MM + // useEffect(() => { + // (async () => { + // const { provider: newProvider } = await checkout.createProvider({ + // walletProviderName: WalletProviderName.METAMASK, + // }); + + // setWeb3Provider(newProvider); + + // await checkout.connect({ + // provider: newProvider, + // }); + // })(); + // }, []); + const unmount = () => { - checkoutWidget.unmount(); + checkoutWidget?.unmount(); }; const update = (theme: WidgetTheme) => { - checkoutWidget.update({ config: { theme } }); + checkoutWidget?.update({ config: { theme } }); }; useEffect(() => { - passport.connectEvm(); - checkoutWidget.mount("checkout", { - flow: CheckoutFlowType.SWAP, - amount: "0.1", - fromTokenAddress: "0x3B2d8A1931736Fc321C24864BceEe981B11c3c57", // usdc - toTokenAddress: "native", + if (!checkoutWidget) return; + checkoutWidget?.mount("checkout", { + flow: CheckoutFlowType.CONNECT, }); - }, []); + }, [checkoutWidget]); useEffect(() => { if (!checkoutWidget) return; @@ -80,93 +132,81 @@ function CheckoutUI() { console.log("----------> CLOSE", data); }); - checkoutWidget.addListener(CheckoutEventType.SUCCESS, (data) => { - if ( - data.flow === CheckoutFlowType.SALE && - data.type === CheckoutSuccessEventType.SALE_SUCCESS - ) { - console.log("----------> SUCCESS SALE_SUCESS", data); + checkoutWidget.addListener(CheckoutEventType.SUCCESS, (event) => { + console.log("🐛 ~ event: ----->", event); + + if (event.type === CheckoutSuccessEventType.CONNECT_SUCCESS) { + console.log("----------> SUCCESS CONNECT_SUCCESS", event); + setWeb3Provider(event.data.provider); } - if ( - data.flow === CheckoutFlowType.SALE && - data.type === CheckoutSuccessEventType.SALE_TRANSACTION_SUCCESS - ) { - console.log("----------> SUCCESS SALE_TRANSACTION_SUCCESS", data); + if (event.type === CheckoutSuccessEventType.SALE_SUCCESS) { + console.log("----------> SUCCESS SALE_SUCESS", event); } - if (data.flow === CheckoutFlowType.ONRAMP) { - console.log("----------> SUCCESS ONRAMP", data); + if (event.type === CheckoutSuccessEventType.SALE_TRANSACTION_SUCCESS) { + console.log("----------> SUCCESS SALE_TRANSACTION_SUCCESS", event); } - if ( - data.flow === CheckoutFlowType.BRIDGE && - data.type === CheckoutSuccessEventType.BRIDGE_SUCCESS - ) { - console.log("----------> SUCCESS BRIDGE_SUCCESS", data); + if (event.type === CheckoutSuccessEventType.ONRAMP_SUCCESS) { + console.log("----------> SUCCESS ONRAMP", event); + } + if (event.type === CheckoutSuccessEventType.BRIDGE_SUCCESS) { + console.log("----------> SUCCESS BRIDGE_SUCCESS", event); } if ( - data.flow === CheckoutFlowType.BRIDGE && - data.type === CheckoutSuccessEventType.BRIDGE_CLAIM_WITHDRAWAL_SUCCESS + event.type === CheckoutSuccessEventType.BRIDGE_CLAIM_WITHDRAWAL_SUCCESS ) { console.log( "----------> SUCCESS BRIDGE_CLAIM_WITHDRAWAL_SUCCESS", - data.data + event.data ); } - - console.log("----------> SUCCESS", data); }); - checkoutWidget.addListener(CheckoutEventType.FAILURE, (data) => { - if ( - data.flow === CheckoutFlowType.BRIDGE && - data.type === CheckoutFailureEventType.BRIDGE_FAILED - ) { - console.log("----------> FAILURE BRIDGE_FAILED", data); + checkoutWidget.addListener(CheckoutEventType.FAILURE, (event) => { + if (event.type === CheckoutFailureEventType.BRIDGE_FAILED) { + console.log("----------> FAILURE BRIDGE_FAILED", event); } if ( - data.flow === CheckoutFlowType.BRIDGE && - data.type === CheckoutFailureEventType.BRIDGE_CLAIM_WITHDRAWAL_FAILED + event.type === CheckoutFailureEventType.BRIDGE_CLAIM_WITHDRAWAL_FAILED ) { console.log( "----------> FAILURE BRIDGE_CLAIM_WITHDRAWAL_FAILED", - data.data + event.data ); } - if (data.flow === CheckoutFlowType.CONNECT) { - console.log("----------> FAILURE CONNECT", data); + if (event.type === CheckoutFailureEventType.CONNECT_FAILED) { + console.log("----------> FAILURE CONNECT", event); } - if (data.flow === CheckoutFlowType.ONRAMP) { - console.log("----------> FAILURE ONRAMP", data); + if (event.type === CheckoutFailureEventType.ONRAMP_FAILED) { + console.log("----------> FAILURE ONRAMP", event); } - if (data.flow === CheckoutFlowType.SWAP) { - console.log("----------> FAILURE SWAP", data); + if (event.type === CheckoutFailureEventType.SWAP_FAILED) { + console.log("----------> FAILURE SWAP", event); } - if (data.flow === CheckoutFlowType.SALE) { - console.log("----------> FAILURE SALE", data); + if (event.type === CheckoutFailureEventType.SALE_FAILED) { + console.log("----------> FAILURE SALE", event); } - console.log("----------> FAILURE", data); + console.log("----------> FAILURE", event); }); - checkoutWidget.addListener(CheckoutEventType.DISCONNECTED, (data) => { - console.log("----------> DISCONNECTED", data); + checkoutWidget.addListener(CheckoutEventType.DISCONNECTED, (event) => { + console.log("----------> DISCONNECTED", event); }); - checkoutWidget.addListener(CheckoutEventType.USER_ACTION, (data) => { - if ( - data.flow === CheckoutFlowType.SALE && - data.type === CheckoutUserActionEventType.PAYMENT_METHOD_SELECTED - ) { - console.log("----------> USER_ACTION PAYMENT_METHOD_SELECTED", data); + checkoutWidget.addListener(CheckoutEventType.USER_ACTION, (event) => { + if (event.type === CheckoutUserActionEventType.PAYMENT_METHOD_SELECTED) { + console.log( + "----------> USER_ACTION PAYMENT_METHOD_SELECTED", + event.data.paymentMethod + ); } - if ( - data.flow === CheckoutFlowType.SALE && - data.type === CheckoutUserActionEventType.PAYMENT_TOKEN_SELECTED - ) { - console.log("----------> USER_ACTION PAYMENT_TOKEN_SELECTED", data); + if (event.type === CheckoutUserActionEventType.PAYMENT_TOKEN_SELECTED) { + console.log("----------> USER_ACTION PAYMENT_TOKEN_SELECTED", event); } - if (data.flow === CheckoutFlowType.WALLET) { - console.log("----------> USER_ACTION WALLET", data); + if (event.type === CheckoutUserActionEventType.NETWORK_SWITCH) { + console.log("----------> USER_ACTION WALLET", event); } - console.log("----------> USER_ACTION", data); + + console.log("----------> USER_ACTION", event); }); }, [checkoutWidget]); @@ -182,7 +222,7 @@ function CheckoutUI() { >
); } diff --git a/packages/checkout/widgets-sample-app/src/index.tsx b/packages/checkout/widgets-sample-app/src/index.tsx index b75af754ef..b65861d01e 100644 --- a/packages/checkout/widgets-sample-app/src/index.tsx +++ b/packages/checkout/widgets-sample-app/src/index.tsx @@ -12,6 +12,7 @@ import { PassportLoginCallback } from './components/ui/marketplace-orchestrator/ import { Marketplace } from './components/ui/marketplace-orchestrator'; import { SaleUI } from './components/ui/sale/sale'; import CheckoutUI from './components/ui/checkout/checkout'; +import AddFundsUI from './components/ui/add-funds/addFunds'; const router = createBrowserRouter([ { @@ -46,6 +47,10 @@ const router = createBrowserRouter([ path: '/checkout', element: , }, + { + path: '/add-funds', + element: , + }, { path: '/marketplace-orchestrator', element: diff --git a/packages/checkout/widgets-sample-app/tsconfig.json b/packages/checkout/widgets-sample-app/tsconfig.json index 5b77db70ce..cc2d5c6fd8 100644 --- a/packages/checkout/widgets-sample-app/tsconfig.json +++ b/packages/checkout/widgets-sample-app/tsconfig.json @@ -10,7 +10,8 @@ "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", + "customConditions": ["development"], "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, diff --git a/packages/checkout/widgets-sample-app/vite.config.ts b/packages/checkout/widgets-sample-app/vite.config.ts index 9b85be5081..a77b4b0abb 100644 --- a/packages/checkout/widgets-sample-app/vite.config.ts +++ b/packages/checkout/widgets-sample-app/vite.config.ts @@ -26,6 +26,7 @@ export default defineConfig({ resolve: { alias: { 'jsbi': path.resolve(__dirname, '../../../node_modules/jsbi'), - } + }, + conditions: ["default"] } }) diff --git a/packages/config/.eslintrc.cjs b/packages/config/.eslintrc.cjs new file mode 100644 index 0000000000..11b2a3d1ba --- /dev/null +++ b/packages/config/.eslintrc.cjs @@ -0,0 +1,11 @@ +module.exports = { + "extends": ["../../.eslintrc"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json", + "tsconfigRootDir": __dirname + }, + "rules": { + "@typescript-eslint/comma-dangle": "off" + } +} diff --git a/packages/config/jest.config.ts b/packages/config/jest.config.ts index 33b57b1652..5a141d28a7 100644 --- a/packages/config/jest.config.ts +++ b/packages/config/jest.config.ts @@ -4,6 +4,7 @@ const config: Config = { clearMocks: true, coverageProvider: 'v8', moduleDirectories: ['node_modules', 'src'], + moduleNameMapper: { '^@imtbl/(.*)$': '/../../node_modules/@imtbl/$1/src' }, testEnvironment: 'node', transform: { '^.+\\.(t|j)sx?$': '@swc/jest', diff --git a/packages/config/package.json b/packages/config/package.json index 9820740a60..5439016094 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -21,11 +21,24 @@ "prettier": "^2.8.7", "rollup": "^4.19.1", "ts-node": "^10.9.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" }, "engines": { "node": ">=20.11.0" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } + }, "files": [ "dist" ], @@ -36,12 +49,11 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest", "test:watch": "jest --watch", "typecheck": "tsc --noEmit --jsx preserve" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/config/rollup.config.js b/packages/config/rollup.config.js index 16a7d58d7e..0269dcdc15 100644 --- a/packages/config/rollup.config.js +++ b/packages/config/rollup.config.js @@ -1,4 +1,7 @@ import typescript from '@rollup/plugin-typescript'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: 'src/index.ts', @@ -6,5 +9,5 @@ export default { dir: 'dist', format: 'es', }, - plugins: [typescript()], + plugins: [isProduction ? typescript({customConditions: ["default"]}) : swc.rollup()], }; diff --git a/packages/config/tsconfig.json b/packages/config/tsconfig.json index 56c27e347a..5bd0b3f89e 100644 --- a/packages/config/tsconfig.json +++ b/packages/config/tsconfig.json @@ -1,20 +1,9 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": true, - "resolveJsonModule": true, - "skipLibCheck": true + "customConditions": ["development"] }, "include": ["src"], "exclude": ["node_modules", "dist"] diff --git a/packages/x-client/.eslintrc b/packages/game-bridge/.eslintrc.cjs similarity index 72% rename from packages/x-client/.eslintrc rename to packages/game-bridge/.eslintrc.cjs index f90c594b06..8b8a821f7e 100644 --- a/packages/x-client/.eslintrc +++ b/packages/game-bridge/.eslintrc.cjs @@ -1,8 +1,8 @@ -{ +module.exports = { "extends": ["../../.eslintrc"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname } } diff --git a/packages/game-bridge/package.json b/packages/game-bridge/package.json index cf31c2a160..3179f705d9 100644 --- a/packages/game-bridge/package.json +++ b/packages/game-bridge/package.json @@ -16,7 +16,6 @@ "scripts": { "build": "parcel build --no-cache --no-scope-hoist", "build:local": "parcel build --no-cache --no-scope-hoist && yarn updateSdkVersion", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "start": "parcel", "updateSdkVersion": "./scripts/updateSdkVersion.sh" diff --git a/packages/game-bridge/src/index.ts b/packages/game-bridge/src/index.ts index 60927c2230..e5cab98f4d 100644 --- a/packages/game-bridge/src/index.ts +++ b/packages/game-bridge/src/index.ts @@ -716,7 +716,7 @@ window.callFunction = async (jsonData: string) => { method: 'eth_getTransactionReceipt', params: [request.txHash], }); - const success = response !== null && response !== undefined; + const success = response !== undefined; if (!success) { throw new Error('Failed to get transaction receipt'); @@ -736,7 +736,7 @@ window.callFunction = async (jsonData: string) => { } case trackFunction: { const request = JSON.parse(data); - const properties = JSON.parse(request.properties); + const properties = request.properties ? JSON.parse(request.properties) : {}; track(request.moduleName, request.eventName, properties); callbackToGame({ responseFor: fxName, diff --git a/packages/game-bridge/tsconfig.json b/packages/game-bridge/tsconfig.json index fc204f35c4..569cb618e6 100644 --- a/packages/game-bridge/tsconfig.json +++ b/packages/game-bridge/tsconfig.json @@ -1,109 +1,9 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "esnext", /* Specify what module code is generated. */ - // "rootDir": "./src", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - "rootDirs": ["src"], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ - "removeComments": false, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true, /* Skip type checking all .d.ts files. */ - - /* Advanced */ - // "noEmit": true, - // "allowJs": false, - // "allowSyntheticDefaultImports": true, - // "resolveJsonModule": true, + "outDir": "./dist", + "rootDirs": ["src"], + "customConditions": ["development"] }, "include": ["src"], "exclude": ["dist", "jest.config.js", "node_modules"] diff --git a/packages/internal/.eslintrc b/packages/internal/.eslintrc index a6393a1ed1..589b3bc659 100644 --- a/packages/internal/.eslintrc +++ b/packages/internal/.eslintrc @@ -1,9 +1,4 @@ { "ignorePatterns": ["**sample-app**/"], - "extends": ["../../.eslintrc"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "./tsconfig.json", - "tsconfigRootDir": "." - } -} + "extends": ["../../.eslintrc"] +} \ No newline at end of file diff --git a/packages/internal/bridge/sdk/.eslintrc b/packages/internal/bridge/sdk/.eslintrc.cjs similarity index 75% rename from packages/internal/bridge/sdk/.eslintrc rename to packages/internal/bridge/sdk/.eslintrc.cjs index 9c72b59200..cd1f33579b 100644 --- a/packages/internal/bridge/sdk/.eslintrc +++ b/packages/internal/bridge/sdk/.eslintrc.cjs @@ -1,11 +1,9 @@ -{ - "extends": [ - "../../../../.eslintrc" - ], +module.exports = { + "extends": ["../../.eslintrc"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname }, "rules": { "max-len": [ diff --git a/packages/internal/bridge/sdk/jest.config.ts b/packages/internal/bridge/sdk/jest.config.ts index 31bd6670e8..b28e638f08 100644 --- a/packages/internal/bridge/sdk/jest.config.ts +++ b/packages/internal/bridge/sdk/jest.config.ts @@ -4,9 +4,7 @@ const config: Config = { clearMocks: true, coverageProvider: 'v8', moduleDirectories: ['node_modules', 'src'], - moduleNameMapper: { - '@imtbl/config': '../../../config/src', - }, + moduleNameMapper: { '^@imtbl/(.*)$': '/../../../../node_modules/@imtbl/$1/src' }, verbose: true, testEnvironment: 'jsdom', transform: { diff --git a/packages/internal/bridge/sdk/package.json b/packages/internal/bridge/sdk/package.json index d172c88c8d..25539d0d82 100644 --- a/packages/internal/bridge/sdk/package.json +++ b/packages/internal/bridge/sdk/package.json @@ -27,11 +27,26 @@ "rollup": "^4.19.1", "ts-node": "^10.9.1", "typechain": "^8.1.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" }, "engines": { "node": ">=20.11.0" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "module": "./dist/index.browser.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "module": "./dist/index.browser.js", + "import": "./dist/index.js" + } + }, "homepage": "https://github.com/immutable/ts-immutable-sdk#readme", "keywords": [ "immutablex" @@ -43,7 +58,7 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts --max-warnings=0", "lint:fix": "cd ../../../../ && yarn wsrun -p @imtbl/bridge-sdk -c lint --fix", "test": "jest test -- --silent=false", @@ -51,6 +66,5 @@ "typecheck": "tsc --noEmit" }, "source": "src/index.ts", - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/internal/bridge/sdk/rollup.config.js b/packages/internal/bridge/sdk/rollup.config.js index 67fbc14d3b..175501d8cd 100644 --- a/packages/internal/bridge/sdk/rollup.config.js +++ b/packages/internal/bridge/sdk/rollup.config.js @@ -2,6 +2,9 @@ import typescript from '@rollup/plugin-typescript'; import { nodeResolve } from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default [{ input: './src/index.ts', @@ -11,8 +14,8 @@ export default [{ plugins: [ json(), commonjs(), - nodeResolve(), - typescript(), + nodeResolve({ exportConditions: ["default"] }), + isProduction ? typescript({customConditions: ["default"]}) : swc.rollup() ], }, { @@ -23,8 +26,8 @@ export default [{ plugins: [ json(), commonjs(), - nodeResolve({ browser: true }), - typescript(), + nodeResolve({ browser: true, exportConditions: ["default"] }), + isProduction ? typescript({customConditions: ["default"]}) : swc.rollup() ], } ]; diff --git a/packages/internal/bridge/sdk/src/lib/tenderly.test.ts b/packages/internal/bridge/sdk/src/lib/tenderly.test.ts index 44eae39375..66d4b024ea 100644 --- a/packages/internal/bridge/sdk/src/lib/tenderly.test.ts +++ b/packages/internal/bridge/sdk/src/lib/tenderly.test.ts @@ -20,7 +20,7 @@ function generateAxiosData( return { jsonrpc: '2.0', id: 0, - method: 'tenderly_estimateGasBundle', + method: 'tenderly_simulateBundle', params: [ simulations, 'latest', @@ -89,7 +89,7 @@ describe('Tenderly Utils', () => { mockedAxios.post.mockResolvedValueOnce({ data: expectedResponse }); - const result = await submitTenderlySimulations(chainId, simulations); + const result = (await submitTenderlySimulations(chainId, simulations)).gas; expect(result.length).toEqual(1); expect(result[0]).toEqual(expectedResponse.result[0].gasUsed); @@ -128,7 +128,7 @@ describe('Tenderly Utils', () => { mockedAxios.post.mockResolvedValueOnce({ data: expectedResponse }); - const result = await submitTenderlySimulations(chainId, simulations); + const result = (await submitTenderlySimulations(chainId, simulations)).gas; expect(result.length).toEqual(2); expect(result[0]).toEqual(expectedResponse.result[0].gasUsed); @@ -176,7 +176,7 @@ describe('Tenderly Utils', () => { mockedAxios.post.mockResolvedValueOnce({ data: expectedResponse }); - const result = await submitTenderlySimulations(chainId, simulations, stateObjects); + const result = (await submitTenderlySimulations(chainId, simulations, stateObjects)).gas; expect(result.length).toEqual(2); expect(result[0]).toEqual(expectedResponse.result[0].gasUsed); diff --git a/packages/internal/bridge/sdk/src/lib/tenderly.ts b/packages/internal/bridge/sdk/src/lib/tenderly.ts index dc2eca5862..453cc340bc 100644 --- a/packages/internal/bridge/sdk/src/lib/tenderly.ts +++ b/packages/internal/bridge/sdk/src/lib/tenderly.ts @@ -2,7 +2,7 @@ import axios, { AxiosResponse } from 'axios'; import { BridgeError, BridgeErrorType } from '../errors'; -import { TenderlySimulation } from '../types/tenderly'; +import { TenderlySimulation, TenderlyResult } from '../types/tenderly'; import { getTenderlyEndpoint } from './utils'; // In the Tenderly API, state objects are mapping of contract address -> "stateDiff" -> slot -> value @@ -17,6 +17,23 @@ export type StateDiff = { value: string; }; +type Input = { + name: string; + value: string | boolean; +}; + +type Event = { + name: string; + inputs: Array; +}; + +type Trace = { + method: string; + output: string | number; +}; + +const THRESHOLD_SELECTOR = '0x84a3291a0'; + /** * We want to convert a StateObject type to the following format (Record>>): * @example An example of a state object that changes the state at slot 0xe1b959...2585e to 1 at address 0xe43215...8E31: @@ -60,7 +77,7 @@ export async function submitTenderlySimulations( chainId: string, simulations: Array, stateObjects?: StateObject[], -): Promise> { +): Promise { let axiosResponse: AxiosResponse; const tenderlyAPI = getTenderlyEndpoint(chainId); const state_objects = stateObjects ? unwrapStateObjects(stateObjects) : undefined; @@ -70,7 +87,7 @@ export async function submitTenderlySimulations( { jsonrpc: '2.0', id: 0, - method: 'tenderly_estimateGasBundle', + method: 'tenderly_simulateBundle', params: [ simulations, 'latest', @@ -103,7 +120,14 @@ export async function submitTenderlySimulations( } const gas: Array = []; + let delayWithdrawalLargeAmount: boolean = false; + let delayWithdrawalUnknownToken: boolean = false; + let withdrawalQueueActivated: boolean = false; + let largeTransferThresholds: number = 0; + let skipReadOperation = false; + // Check if simulations are for token withdrawal + const withdrawal = simulations.find((e: TenderlySimulation) => e.data?.startsWith(THRESHOLD_SELECTOR)) !== undefined; for (let i = 0; i < simResults.length; i++) { if (simResults[i].error) { throw new BridgeError( @@ -117,8 +141,34 @@ export async function submitTenderlySimulations( BridgeErrorType.TENDERLY_GAS_ESTIMATE_FAILED, ); } - gas.push(simResults[i].gasUsed); + // Attempt to extract event. + if (withdrawal && simResults[i].logs !== undefined) { + const event = simResults[i].logs.find((e: Event) => e.name === 'QueuedWithdrawal'); + if (event !== undefined) { + const inputs: Map = new Map(event.inputs.map((c: Input) => [c.name, c.value])); + delayWithdrawalLargeAmount = inputs.get('delayWithdrawalLargeAmount') as boolean || false; + delayWithdrawalUnknownToken = inputs.get('delayWithdrawalUnknownToken') as boolean || false; + withdrawalQueueActivated = inputs.get('withdrawalQueueActivated') as boolean || false; + } + } + // Check read operation. + if (withdrawal && simResults[i].trace !== undefined) { + const trace: Trace = simResults[i].trace.find((e: Trace) => e.method === 'largeTransferThresholds'); + if (trace !== undefined) { + largeTransferThresholds = trace.output as number; + skipReadOperation = true; + } + } + if (!skipReadOperation) { + gas.push(simResults[i].gasUsed); + } } - return gas; + return { + gas, + delayWithdrawalLargeAmount, + delayWithdrawalUnknownToken, + withdrawalQueueActivated, + largeTransferThresholds, + }; } diff --git a/packages/internal/bridge/sdk/src/tokenBridge.ts b/packages/internal/bridge/sdk/src/tokenBridge.ts index 46363be40d..7889d37ed6 100644 --- a/packages/internal/bridge/sdk/src/tokenBridge.ts +++ b/packages/internal/bridge/sdk/src/tokenBridge.ts @@ -17,7 +17,7 @@ import { isWrappedIMX, shouldBeDepositOrFinaliseWithdraw, } from './lib/utils'; -import { TenderlySimulation } from './types/tenderly'; +import { TenderlyResult, TenderlySimulation } from './types/tenderly'; import { calculateGasFee } from './lib/gas'; import { createContract } from './contracts/createContract'; import { getWithdrawRootToken, genAxelarWithdrawPayload, genUniqueAxelarCommandId } from './lib/axelarUtils'; @@ -679,6 +679,10 @@ export class TokenBridge { contractToApprove, unsignedApprovalTx, unsignedBridgeTx, + delayWithdrawalLargeAmount: null, + delayWithdrawalUnknownToken: null, + withdrawalQueueActivated: null, + largeTransferThresholds: null, }; } @@ -690,7 +694,7 @@ export class TokenBridge { amount: ethers.BigNumber, gasMultiplier: number | string, ): Promise { - const [allowance, feeData, rootGas] = await Promise.all([ + const [allowance, feeData, tenderlyRes] = await Promise.all([ this.getAllowance(direction, token, sender), this.config.childProvider.getFeeData(), await this.getDynamicWithdrawGasRootChain( @@ -701,6 +705,7 @@ export class TokenBridge { amount, ), ]); + const rootGas = tenderlyRes.gas[0]; // Get axelar fee const axelarFee = await this.getAxelarFee( this.config.bridgeInstance.childChainID, @@ -770,6 +775,10 @@ export class TokenBridge { contractToApprove, unsignedApprovalTx, unsignedBridgeTx, + delayWithdrawalLargeAmount: tenderlyRes.delayWithdrawalLargeAmount, + delayWithdrawalUnknownToken: tenderlyRes.delayWithdrawalUnknownToken, + withdrawalQueueActivated: tenderlyRes.withdrawalQueueActivated, + largeTransferThresholds: tenderlyRes.largeTransferThresholds, }; } @@ -828,7 +837,7 @@ export class TokenBridge { }); // TODO this specific branch does not have tests written - const gas = await submitTenderlySimulations(sourceChainId, simulations); + const { gas } = await submitTenderlySimulations(sourceChainId, simulations); const tenderlyGasEstimatesRes = {} as DynamicGasEstimatesResponse; if (gas.length === 1) { tenderlyGasEstimatesRes.approvalGas = 0; @@ -848,7 +857,7 @@ export class TokenBridge { recipient: string, token: FungibleToken, amount: ethers.BigNumber, - ): Promise { + ): Promise { const rootToken = await getWithdrawRootToken(token, destinationChainId, this.config.childProvider); const payload = genAxelarWithdrawPayload( rootToken, @@ -893,6 +902,23 @@ export class TokenBridge { data: executeData, }]; + // Read large transfer threshold for given token + const bridgeAddress = this.config.bridgeContracts.rootERC20BridgeFlowRate; + const bridgeContract = await createContract( + bridgeAddress, + ROOT_ERC20_BRIDGE_FLOW_RATE, + this.config.rootProvider, + ); + // Get current bucket state + const readData = await withBridgeError(async () => bridgeContract.interface.encodeFunctionData( + 'largeTransferThresholds', + [rootToken], + ), BridgeErrorType.INTERNAL_ERROR); + simulations.push({ + from: sender, + to: bridgeAddress, + data: readData, + }); const stateObject: StateObject = { contractAddress: axelarGateway, stateDiff: { @@ -901,8 +927,7 @@ export class TokenBridge { }, }; - const gas = await submitTenderlySimulations(destinationChainId, simulations, [stateObject]); - return gas[0]; + return await submitTenderlySimulations(destinationChainId, simulations, [stateObject]); } private async getAllowance(direction: BridgeDirection, token: string, sender: string): Promise { diff --git a/packages/internal/bridge/sdk/src/types/index.ts b/packages/internal/bridge/sdk/src/types/index.ts index 9c9321db16..7fb5462f2a 100644 --- a/packages/internal/bridge/sdk/src/types/index.ts +++ b/packages/internal/bridge/sdk/src/types/index.ts @@ -308,12 +308,20 @@ export interface BridgeBundledTxRequest { * @property {ethers.providers.TransactionRequest | null} unsignedApprovalTx - The unsigned transaction for the token approval, or null * if no approval is required. * @property {ethers.providers.TransactionRequest} unsignedBridgeTx - The unsigned transaction for the deposit / withdrawal. + * @property {boolean | null} delayWithdrawalLargeAmount - If withdrawal gets queued due to large amount. + * @property {boolean | null} delayWithdrawalUnknownToken - If withdrawal gets queued due to unknown token. + * @property {boolean | null} withdrawalQueueActivated - If withdrawal gets queued due to activated queue. + * @property {number | null} largeTransferThresholds - The configured large transfer threshold for given withdrawal token. */ export interface BridgeBundledTxResponse { feeData: BridgeFeeResponse, contractToApprove: string | null, unsignedApprovalTx: ethers.providers.TransactionRequest | null; unsignedBridgeTx: ethers.providers.TransactionRequest; + delayWithdrawalLargeAmount: boolean | null; + delayWithdrawalUnknownToken: boolean | null; + withdrawalQueueActivated: boolean | null; + largeTransferThresholds: number | null; } /** diff --git a/packages/internal/bridge/sdk/src/types/tenderly.ts b/packages/internal/bridge/sdk/src/types/tenderly.ts index 49b1693f40..b7a9306117 100644 --- a/packages/internal/bridge/sdk/src/types/tenderly.ts +++ b/packages/internal/bridge/sdk/src/types/tenderly.ts @@ -5,3 +5,11 @@ export type TenderlySimulation = { data?: string; value?: string; }; + +export type TenderlyResult = { + gas: Array; + delayWithdrawalLargeAmount: boolean; + delayWithdrawalUnknownToken: boolean; + withdrawalQueueActivated: boolean; + largeTransferThresholds: number; +}; diff --git a/packages/internal/bridge/sdk/tsconfig.json b/packages/internal/bridge/sdk/tsconfig.json index 66365daa07..0644addd54 100644 --- a/packages/internal/bridge/sdk/tsconfig.json +++ b/packages/internal/bridge/sdk/tsconfig.json @@ -1,24 +1,9 @@ { + "extends": "../../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", - "rootDirs": [ - "src" - ], - "target": "es2022", - "module": "es2022", - "moduleResolution": "node", - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": true, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": true, - "allowSyntheticDefaultImports": true, - "noFallthroughCasesInSwitch": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "declaration": true, + "rootDirs": ["src"], + "customConditions": ["development"] }, "include": [ "src" diff --git a/packages/internal/cryptofiat/jest.config.ts b/packages/internal/cryptofiat/jest.config.ts index f1fc16bbdd..7c27f4f311 100644 --- a/packages/internal/cryptofiat/jest.config.ts +++ b/packages/internal/cryptofiat/jest.config.ts @@ -4,9 +4,7 @@ const config: Config = { clearMocks: true, coverageProvider: 'v8', moduleDirectories: ['node_modules', 'src'], - moduleNameMapper: { - '@imtbl/config': '../../config/src' - }, + moduleNameMapper: { '^@imtbl/(.*)$': '/../../../node_modules/@imtbl/$1/src' }, testEnvironment: 'node', transform: { // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/packages/internal/cryptofiat/package.json b/packages/internal/cryptofiat/package.json index 2e1e34c335..0d33b03e11 100644 --- a/packages/internal/cryptofiat/package.json +++ b/packages/internal/cryptofiat/package.json @@ -25,11 +25,24 @@ "prettier": "^2.8.7", "rollup": "^4.19.1", "ts-node": "^10.9.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" }, "engines": { "node": ">=20.11.0" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } + }, "files": [ "dist" ], @@ -40,13 +53,11 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0 --fix", - "prepare": "wsrun -r build", "test": "jest", "test:watch": "jest --watch", "typecheck": "tsc --noEmit --jsx preserve" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/internal/cryptofiat/rollup.config.js b/packages/internal/cryptofiat/rollup.config.js index 16a7d58d7e..0269dcdc15 100644 --- a/packages/internal/cryptofiat/rollup.config.js +++ b/packages/internal/cryptofiat/rollup.config.js @@ -1,4 +1,7 @@ import typescript from '@rollup/plugin-typescript'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: 'src/index.ts', @@ -6,5 +9,5 @@ export default { dir: 'dist', format: 'es', }, - plugins: [typescript()], + plugins: [isProduction ? typescript({customConditions: ["default"]}) : swc.rollup()], }; diff --git a/packages/internal/cryptofiat/tsconfig.json b/packages/internal/cryptofiat/tsconfig.json index 56c27e347a..5184ceffeb 100644 --- a/packages/internal/cryptofiat/tsconfig.json +++ b/packages/internal/cryptofiat/tsconfig.json @@ -1,20 +1,9 @@ { + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": true, - "resolveJsonModule": true, - "skipLibCheck": true + "customConditions": ["development"] }, "include": ["src"], "exclude": ["node_modules", "dist"] diff --git a/packages/internal/dex/sdk-sample-app/package.json b/packages/internal/dex/sdk-sample-app/package.json index 7b25953054..789d73d187 100644 --- a/packages/internal/dex/sdk-sample-app/package.json +++ b/packages/internal/dex/sdk-sample-app/package.json @@ -10,7 +10,7 @@ "concurrently": "^8.2.2", "eslint": "^8.40.0", "ethers": "^5.7.2", - "next": "13.3.1", + "next": "13.4.11", "postcss": "8.4.31", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/internal/dex/sdk-sample-app/tsconfig.json b/packages/internal/dex/sdk-sample-app/tsconfig.json index 61c19abd68..07456f94d6 100644 --- a/packages/internal/dex/sdk-sample-app/tsconfig.json +++ b/packages/internal/dex/sdk-sample-app/tsconfig.json @@ -9,7 +9,8 @@ "noEmit": true, "esModuleInterop": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", + "customConditions": ["development"], "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", diff --git a/packages/internal/dex/sdk/.eslintrc b/packages/internal/dex/sdk/.eslintrc.cjs similarity index 87% rename from packages/internal/dex/sdk/.eslintrc rename to packages/internal/dex/sdk/.eslintrc.cjs index a623313d05..1177fce1bd 100644 --- a/packages/internal/dex/sdk/.eslintrc +++ b/packages/internal/dex/sdk/.eslintrc.cjs @@ -1,10 +1,10 @@ -{ +module.exports = { "extends": ["../../.eslintrc"], "ignorePatterns": ["jest.config.*", "rollup.config.*"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname }, "rules": { "implicit-arrow-linebreak": "off", diff --git a/packages/internal/dex/sdk/jest.config.ts b/packages/internal/dex/sdk/jest.config.ts index 7f5d5a76e0..25942679c5 100644 --- a/packages/internal/dex/sdk/jest.config.ts +++ b/packages/internal/dex/sdk/jest.config.ts @@ -4,9 +4,7 @@ const config: Config = { clearMocks: true, coverageProvider: 'v8', moduleDirectories: ['node_modules', 'src'], - moduleNameMapper: { - '@imtbl/config': '../../../config/src' - }, + moduleNameMapper: { '^@imtbl/(.*)$': '/../../../../node_modules/@imtbl/$1/src' }, verbose: true, testEnvironment: 'jsdom', transform: { diff --git a/packages/internal/dex/sdk/package.json b/packages/internal/dex/sdk/package.json index 9a30a5189d..dd7960c557 100644 --- a/packages/internal/dex/sdk/package.json +++ b/packages/internal/dex/sdk/package.json @@ -13,6 +13,7 @@ }, "devDependencies": { "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-typescript": "^11.1.6", "@swc/core": "^1.3.36", "@swc/jest": "^0.2.24", "@typechain/ethers-v5": "^10.2.0", @@ -24,11 +25,24 @@ "rollup": "^4.19.1", "ts-node": "^10.9.1", "typechain": "^8.1.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" }, "engines": { "node": ">=20.11.0" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } + }, "files": [ "dist" ], @@ -42,7 +56,7 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "generate-types": "typechain --target ethers-v5 --out-dir ./src/contracts/types ./src/contracts/ABIs/*.json", "lint": "eslint ./src --ext .ts --max-warnings=0", "lint:fix": "cd ../../../.. && yarn wsrun -p @imtbl/dex-sdk -c lint --fix", @@ -51,6 +65,5 @@ "typecheck": "tsc --noEmit" }, "source": "src/index.ts", - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/internal/dex/sdk/rollup.config.js b/packages/internal/dex/sdk/rollup.config.js index a2756273d8..a13a9d4161 100644 --- a/packages/internal/dex/sdk/rollup.config.js +++ b/packages/internal/dex/sdk/rollup.config.js @@ -1,5 +1,8 @@ import typescript from '@rollup/plugin-typescript'; import json from '@rollup/plugin-json'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: './src/index.ts', @@ -8,8 +11,11 @@ export default { }, plugins: [ json(), + isProduction ? typescript({ + customConditions: ["default"], exclude: ['**/ABIs/*', '**/*.test.*', '**/utils/testUtils.ts'], - }), + }) : + swc.rollup({ exclude: ['**/ABIs/*', '**/*.test.*', '**/utils/testUtils.ts'] }), ], }; diff --git a/packages/internal/dex/sdk/tsconfig.json b/packages/internal/dex/sdk/tsconfig.json index 7a7f818705..72cb790dbb 100644 --- a/packages/internal/dex/sdk/tsconfig.json +++ b/packages/internal/dex/sdk/tsconfig.json @@ -1,24 +1,9 @@ { + "extends": "../../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", - "rootDirs": [ - "src" - ], - "target": "es2022", - "module": "es2022", - "moduleResolution": "node", - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": true, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": true, - "allowSyntheticDefaultImports": true, - "noFallthroughCasesInSwitch": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "declaration": true + "rootDirs": ["src"], + "customConditions": ["development"] }, "include": ["src"], "exclude": ["node_modules", "dist"] diff --git a/packages/internal/factory/sdk/.eslintrc b/packages/internal/factory/sdk/.eslintrc index 9c72b59200..e3881a722e 100644 --- a/packages/internal/factory/sdk/.eslintrc +++ b/packages/internal/factory/sdk/.eslintrc @@ -1,6 +1,6 @@ { "extends": [ - "../../../../.eslintrc" + "../../.eslintrc" ], "parser": "@typescript-eslint/parser", "parserOptions": { diff --git a/packages/internal/factory/sdk/jest.config.ts b/packages/internal/factory/sdk/jest.config.ts index 31bd6670e8..b28e638f08 100644 --- a/packages/internal/factory/sdk/jest.config.ts +++ b/packages/internal/factory/sdk/jest.config.ts @@ -4,9 +4,7 @@ const config: Config = { clearMocks: true, coverageProvider: 'v8', moduleDirectories: ['node_modules', 'src'], - moduleNameMapper: { - '@imtbl/config': '../../../config/src', - }, + moduleNameMapper: { '^@imtbl/(.*)$': '/../../../../node_modules/@imtbl/$1/src' }, verbose: true, testEnvironment: 'jsdom', transform: { diff --git a/packages/internal/factory/sdk/package.json b/packages/internal/factory/sdk/package.json index b54cd5d750..86a35e7c3e 100644 --- a/packages/internal/factory/sdk/package.json +++ b/packages/internal/factory/sdk/package.json @@ -24,11 +24,24 @@ "rollup": "^4.19.1", "ts-node": "^10.9.1", "typechain": "^8.1.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" }, "engines": { "node": ">=20.11.0" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } + }, "homepage": "https://github.com/immutable/ts-immutable-sdk#readme", "keywords": [ "immutablex" @@ -39,13 +52,12 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts --max-warnings=0", "lint:fix": "cd ../../../ && yarn wsrun -p @imtbl/factory-sdk -c lint --fix", "test": "jest test", "test:watch": "jest --watch" }, "source": "src/index.ts", - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/internal/factory/sdk/rollup.config.js b/packages/internal/factory/sdk/rollup.config.js index 1f9bf1f17a..8b946d3e0f 100644 --- a/packages/internal/factory/sdk/rollup.config.js +++ b/packages/internal/factory/sdk/rollup.config.js @@ -2,6 +2,9 @@ import typescript from '@rollup/plugin-typescript'; import { nodeResolve } from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: './src/index.ts', @@ -11,9 +14,7 @@ export default { plugins: [ json(), commonjs(), - nodeResolve(), - typescript({ - exclude: [], - }), + nodeResolve({ exportConditions: ["default"] }), + isProduction ? typescript({customConditions: ["default"]}) : swc.rollup() ], }; diff --git a/packages/internal/factory/sdk/tsconfig.json b/packages/internal/factory/sdk/tsconfig.json index 66365daa07..0644addd54 100644 --- a/packages/internal/factory/sdk/tsconfig.json +++ b/packages/internal/factory/sdk/tsconfig.json @@ -1,24 +1,9 @@ { + "extends": "../../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", - "rootDirs": [ - "src" - ], - "target": "es2022", - "module": "es2022", - "moduleResolution": "node", - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": true, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": true, - "allowSyntheticDefaultImports": true, - "noFallthroughCasesInSwitch": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "declaration": true, + "rootDirs": ["src"], + "customConditions": ["development"] }, "include": [ "src" diff --git a/packages/internal/metrics/.eslintrc b/packages/internal/generated-clients/.eslintrc.cjs similarity index 55% rename from packages/internal/metrics/.eslintrc rename to packages/internal/generated-clients/.eslintrc.cjs index 9b6c8a5aef..cc10d70796 100644 --- a/packages/internal/metrics/.eslintrc +++ b/packages/internal/generated-clients/.eslintrc.cjs @@ -1,8 +1,8 @@ -{ - "extends": ["../../../.eslintrc"], +module.exports = { + "extends": ["../.eslintrc"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname } } diff --git a/packages/internal/generated-clients/Makefile b/packages/internal/generated-clients/Makefile index 49d91a89a1..56d7bd61a7 100644 --- a/packages/internal/generated-clients/Makefile +++ b/packages/internal/generated-clients/Makefile @@ -23,7 +23,7 @@ get-mr-openapi: .PHONY: generate-imx-api-client generate-imx-api-client: - rm -rf src/imx && \ + rimraf src/imx && \ mkdir src/imx && \ docker run --rm -v $(shell pwd):/app openapitools/openapi-generator-cli:v6.2.1 generate \ -i ./app/src/imx-openapi.json \ @@ -33,7 +33,7 @@ generate-imx-api-client: .PHONY: generate-mr-api-client generate-mr-api-client: - rm -rf src/multi-rollup && \ + rimraf src/multi-rollup && \ mkdir src/multi-rollup && \ docker run --rm -v $(shell pwd):/app openapitools/openapi-generator-cli:v7.0.1 generate \ --inline-schema-options REFACTOR_ALLOF_INLINE_SCHEMAS=true \ @@ -45,7 +45,7 @@ generate-mr-api-client: # When running this command, ensure files not relevant to blockchain data are manually removed .PHONY: generate-blockchain-data-types generate-blockchain-data-types: - rm -rf src/blockchain-data && \ + rimraf src/blockchain-data && \ mkdir src/blockchain-data && \ docker run --rm -v $(shell pwd):/app openapitools/openapi-generator-cli:v7.0.1 generate \ --inline-schema-options REFACTOR_ALLOF_INLINE_SCHEMAS=true \ diff --git a/packages/internal/generated-clients/package.json b/packages/internal/generated-clients/package.json index 62e5cd3359..670648c966 100644 --- a/packages/internal/generated-clients/package.json +++ b/packages/internal/generated-clients/package.json @@ -6,13 +6,29 @@ "bugs": "https://github.com/immutable/ts-immutable-sdk/issues", "devDependencies": { "@openapitools/openapi-generator-cli": "^2.13.4", + "@rollup/plugin-typescript": "^11.1.6", + "@swc/core": "^1.3.36", "jest": "^29.4.3", + "rimraf": "^6.0.1", "rollup": "^4.19.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" }, "engines": { "node": ">=20.11.0" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } + }, "files": [ "dist" ], @@ -23,12 +39,10 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", - "prepare": "wsrun -r build", + "d": "rollup --config rollup.config.js", "test": "jest", "typecheck": "tsc --noEmit --jsx preserve", "view-generators": "openapi-generator-cli author template -g typescript-axios -o src/templates" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/internal/generated-clients/rollup.config.js b/packages/internal/generated-clients/rollup.config.js index 7e22038124..0269dcdc15 100644 --- a/packages/internal/generated-clients/rollup.config.js +++ b/packages/internal/generated-clients/rollup.config.js @@ -1,4 +1,7 @@ import typescript from '@rollup/plugin-typescript'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: 'src/index.ts', @@ -6,7 +9,5 @@ export default { dir: 'dist', format: 'es', }, - plugins: [ - typescript(), - ], + plugins: [isProduction ? typescript({customConditions: ["default"]}) : swc.rollup()], }; diff --git a/packages/internal/generated-clients/src/blockchain-data/domain/activities-api.ts b/packages/internal/generated-clients/src/blockchain-data/domain/activities-api.ts index 808018af17..2f64b7644c 100644 --- a/packages/internal/generated-clients/src/blockchain-data/domain/activities-api.ts +++ b/packages/internal/generated-clients/src/blockchain-data/domain/activities-api.ts @@ -26,17 +26,17 @@ import { GetActivityResult } from '../models'; // @ts-ignore import { ListActivitiesResult } from '../models'; // @ts-ignore -export { APIError400 } from '../models'; +export type { APIError400 } from '../models'; // @ts-ignore -export { APIError404 } from '../models'; +export type { APIError404 } from '../models'; // @ts-ignore -export { APIError500 } from '../models'; +export type { APIError500 } from '../models'; // @ts-ignore -export { ActivityType } from '../models'; +export type { ActivityType } from '../models'; // @ts-ignore -export { GetActivityResult } from '../models'; +export type { GetActivityResult } from '../models'; // @ts-ignore -export { ListActivitiesResult } from '../models'; +export type { ListActivitiesResult } from '../models'; /** * Request parameters for getActivity operation in ActivitiesApi. diff --git a/packages/internal/generated-clients/src/blockchain-data/domain/chains-api.ts b/packages/internal/generated-clients/src/blockchain-data/domain/chains-api.ts index d44c0d5ad8..5eb89d1eb1 100644 --- a/packages/internal/generated-clients/src/blockchain-data/domain/chains-api.ts +++ b/packages/internal/generated-clients/src/blockchain-data/domain/chains-api.ts @@ -22,13 +22,13 @@ import { APIError500 } from '../models'; // @ts-ignore import { ListChainsResult } from '../models'; // @ts-ignore -export { APIError400 } from '../models'; +export type { APIError400 } from '../models'; // @ts-ignore -export { APIError404 } from '../models'; +export type { APIError404 } from '../models'; // @ts-ignore -export { APIError500 } from '../models'; +export type { APIError500 } from '../models'; // @ts-ignore -export { ListChainsResult } from '../models'; +export type { ListChainsResult } from '../models'; /** * Request parameters for listChains operation in ChainsApi. diff --git a/packages/internal/generated-clients/src/blockchain-data/domain/collections-api.ts b/packages/internal/generated-clients/src/blockchain-data/domain/collections-api.ts index 9b84131921..733abc88b2 100644 --- a/packages/internal/generated-clients/src/blockchain-data/domain/collections-api.ts +++ b/packages/internal/generated-clients/src/blockchain-data/domain/collections-api.ts @@ -34,25 +34,25 @@ import { RefreshCollectionMetadataRequest } from '../models'; // @ts-ignore import { RefreshCollectionMetadataResult } from '../models'; // @ts-ignore -export { APIError400 } from '../models'; +export type { APIError400 } from '../models'; // @ts-ignore -export { APIError401 } from '../models'; +export type { APIError401 } from '../models'; // @ts-ignore -export { APIError403 } from '../models'; +export type { APIError403 } from '../models'; // @ts-ignore -export { APIError404 } from '../models'; +export type { APIError404 } from '../models'; // @ts-ignore -export { APIError500 } from '../models'; +export type { APIError500 } from '../models'; // @ts-ignore -export { AssetVerificationStatus } from '../models'; +export type { AssetVerificationStatus } from '../models'; // @ts-ignore -export { GetCollectionResult } from '../models'; +export type { GetCollectionResult } from '../models'; // @ts-ignore -export { ListCollectionsResult } from '../models'; +export type { ListCollectionsResult } from '../models'; // @ts-ignore -export { RefreshCollectionMetadataRequest } from '../models'; +export type { RefreshCollectionMetadataRequest } from '../models'; // @ts-ignore -export { RefreshCollectionMetadataResult } from '../models'; +export type { RefreshCollectionMetadataResult } from '../models'; /** * Request parameters for getCollection operation in CollectionsApi. diff --git a/packages/internal/generated-clients/src/blockchain-data/domain/metadata-api.ts b/packages/internal/generated-clients/src/blockchain-data/domain/metadata-api.ts index 1810a16444..eb7ca9cf37 100644 --- a/packages/internal/generated-clients/src/blockchain-data/domain/metadata-api.ts +++ b/packages/internal/generated-clients/src/blockchain-data/domain/metadata-api.ts @@ -36,27 +36,27 @@ import { RefreshMetadataByIDRequest } from '../models'; // @ts-ignore import { RefreshNFTMetadataByTokenIDRequest } from '../models'; // @ts-ignore -export { APIError400 } from '../models'; +export type { APIError400 } from '../models'; // @ts-ignore -export { APIError401 } from '../models'; +export type { APIError401 } from '../models'; // @ts-ignore -export { APIError403 } from '../models'; +export type { APIError403 } from '../models'; // @ts-ignore -export { APIError404 } from '../models'; +export type { APIError404 } from '../models'; // @ts-ignore -export { APIError429 } from '../models'; +export type { APIError429 } from '../models'; // @ts-ignore -export { APIError500 } from '../models'; +export type { APIError500 } from '../models'; // @ts-ignore -export { GetMetadataResult } from '../models'; +export type { GetMetadataResult } from '../models'; // @ts-ignore -export { ListMetadataResult } from '../models'; +export type { ListMetadataResult } from '../models'; // @ts-ignore -export { MetadataRefreshRateLimitResult } from '../models'; +export type { MetadataRefreshRateLimitResult } from '../models'; // @ts-ignore -export { RefreshMetadataByIDRequest } from '../models'; +export type { RefreshMetadataByIDRequest } from '../models'; // @ts-ignore -export { RefreshNFTMetadataByTokenIDRequest } from '../models'; +export type { RefreshNFTMetadataByTokenIDRequest } from '../models'; /** * Request parameters for getMetadata operation in MetadataApi. diff --git a/packages/internal/generated-clients/src/blockchain-data/domain/nft-owners-api.ts b/packages/internal/generated-clients/src/blockchain-data/domain/nft-owners-api.ts index f024dfbe44..157d7a7fba 100644 --- a/packages/internal/generated-clients/src/blockchain-data/domain/nft-owners-api.ts +++ b/packages/internal/generated-clients/src/blockchain-data/domain/nft-owners-api.ts @@ -24,15 +24,15 @@ import { ListCollectionOwnersResult } from '../models'; // @ts-ignore import { ListNFTOwnersResult } from '../models'; // @ts-ignore -export { APIError400 } from '../models'; +export type { APIError400 } from '../models'; // @ts-ignore -export { APIError404 } from '../models'; +export type { APIError404 } from '../models'; // @ts-ignore -export { APIError500 } from '../models'; +export type { APIError500 } from '../models'; // @ts-ignore -export { ListCollectionOwnersResult } from '../models'; +export type { ListCollectionOwnersResult } from '../models'; // @ts-ignore -export { ListNFTOwnersResult } from '../models'; +export type { ListNFTOwnersResult } from '../models'; /** * Request parameters for listAllNFTOwners operation in NftOwnersApi. diff --git a/packages/internal/generated-clients/src/blockchain-data/domain/nfts-api.ts b/packages/internal/generated-clients/src/blockchain-data/domain/nfts-api.ts index 200ef8db87..3ac3dbd9e2 100644 --- a/packages/internal/generated-clients/src/blockchain-data/domain/nfts-api.ts +++ b/packages/internal/generated-clients/src/blockchain-data/domain/nfts-api.ts @@ -42,33 +42,33 @@ import { ListNFTsResult } from '../models'; // @ts-ignore import { MintRequestStatus } from '../models'; // @ts-ignore -export { APIError400 } from '../models'; +export type { APIError400 } from '../models'; // @ts-ignore -export { APIError401 } from '../models'; +export type { APIError401 } from '../models'; // @ts-ignore -export { APIError403 } from '../models'; +export type { APIError403 } from '../models'; // @ts-ignore -export { APIError404 } from '../models'; +export type { APIError404 } from '../models'; // @ts-ignore -export { APIError409 } from '../models'; +export type { APIError409 } from '../models'; // @ts-ignore -export { APIError429 } from '../models'; +export type { APIError429 } from '../models'; // @ts-ignore -export { APIError500 } from '../models'; +export type { APIError500 } from '../models'; // @ts-ignore -export { CreateMintRequestRequest } from '../models'; +export type { CreateMintRequestRequest } from '../models'; // @ts-ignore -export { CreateMintRequestResult } from '../models'; +export type { CreateMintRequestResult } from '../models'; // @ts-ignore -export { GetNFTResult } from '../models'; +export type { GetNFTResult } from '../models'; // @ts-ignore -export { ListMintRequestsResult } from '../models'; +export type { ListMintRequestsResult } from '../models'; // @ts-ignore -export { ListNFTsByOwnerResult } from '../models'; +export type { ListNFTsByOwnerResult } from '../models'; // @ts-ignore -export { ListNFTsResult } from '../models'; +export type { ListNFTsResult } from '../models'; // @ts-ignore -export { MintRequestStatus } from '../models'; +export type { MintRequestStatus } from '../models'; /** * Request parameters for createMintRequest operation in NftsApi. diff --git a/packages/internal/generated-clients/src/blockchain-data/domain/tokens-api.ts b/packages/internal/generated-clients/src/blockchain-data/domain/tokens-api.ts index 082ca8972a..20f123327b 100644 --- a/packages/internal/generated-clients/src/blockchain-data/domain/tokens-api.ts +++ b/packages/internal/generated-clients/src/blockchain-data/domain/tokens-api.ts @@ -26,17 +26,17 @@ import { GetTokenResult } from '../models'; // @ts-ignore import { ListTokensResult } from '../models'; // @ts-ignore -export { APIError400 } from '../models'; +export type { APIError400 } from '../models'; // @ts-ignore -export { APIError404 } from '../models'; +export type { APIError404 } from '../models'; // @ts-ignore -export { APIError500 } from '../models'; +export type { APIError500 } from '../models'; // @ts-ignore -export { AssetVerificationStatus } from '../models'; +export type { AssetVerificationStatus } from '../models'; // @ts-ignore -export { GetTokenResult } from '../models'; +export type { GetTokenResult } from '../models'; // @ts-ignore -export { ListTokensResult } from '../models'; +export type { ListTokensResult } from '../models'; /** * Request parameters for getERC20Token operation in TokensApi. diff --git a/packages/internal/generated-clients/src/index.ts b/packages/internal/generated-clients/src/index.ts index 03e1bcb449..949fc689d4 100644 --- a/packages/internal/generated-clients/src/index.ts +++ b/packages/internal/generated-clients/src/index.ts @@ -4,9 +4,7 @@ export * as BlockchainData from './blockchain-data/index'; export { ImxApiClients } from './imx-api-clients'; export { MultiRollupApiClients } from './mr-api-clients'; export { - ImmutableAPIConfiguration, imxApiConfig, - multiRollupConfig, - MultiRollupAPIConfiguration, - createConfig, + multiRollupConfig, createConfig, } from './config'; +export type { ImmutableAPIConfiguration, MultiRollupAPIConfiguration } from './config'; diff --git a/packages/internal/generated-clients/tsconfig.json b/packages/internal/generated-clients/tsconfig.json index 56c27e347a..5184ceffeb 100644 --- a/packages/internal/generated-clients/tsconfig.json +++ b/packages/internal/generated-clients/tsconfig.json @@ -1,20 +1,9 @@ { + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": true, - "resolveJsonModule": true, - "skipLibCheck": true + "customConditions": ["development"] }, "include": ["src"], "exclude": ["node_modules", "dist"] diff --git a/packages/checkout/sdk-sample-app/.eslintrc b/packages/internal/guardian/.eslintrc.cjs similarity index 55% rename from packages/checkout/sdk-sample-app/.eslintrc rename to packages/internal/guardian/.eslintrc.cjs index 664e0e3da5..cc10d70796 100644 --- a/packages/checkout/sdk-sample-app/.eslintrc +++ b/packages/internal/guardian/.eslintrc.cjs @@ -1,9 +1,8 @@ -{ +module.exports = { + "extends": ["../.eslintrc"], "parser": "@typescript-eslint/parser", - "ignorePatterns": ".", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": ".", - "sourceType": "module" + "tsconfigRootDir": __dirname } } diff --git a/packages/internal/guardian/package.json b/packages/internal/guardian/package.json index 3dd8fb8719..93a6433b17 100644 --- a/packages/internal/guardian/package.json +++ b/packages/internal/guardian/package.json @@ -9,6 +9,8 @@ }, "devDependencies": { "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-typescript": "^11.1.6", "@swc/core": "^1.3.36", "@swc/jest": "^0.2.24", "@typechain/ethers-v5": "^10.2.0", @@ -20,11 +22,24 @@ "rollup": "^4.19.1", "ts-node": "^10.9.1", "typechain": "^8.1.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" }, "engines": { "node": ">=20.11.0" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } + }, "files": [ "dist" ], @@ -38,8 +53,7 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'" + "d": "rollup --config rollup.config.js" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/internal/guardian/rollup.config.js b/packages/internal/guardian/rollup.config.js index 64e243fc04..83edafd0f8 100644 --- a/packages/internal/guardian/rollup.config.js +++ b/packages/internal/guardian/rollup.config.js @@ -1,6 +1,9 @@ import typescript from '@rollup/plugin-typescript'; import resolve from '@rollup/plugin-node-resolve'; import json from '@rollup/plugin-json'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: './src/index.ts', @@ -9,9 +12,12 @@ export default { }, plugins: [ json(), - resolve({ browser: true }), + resolve({ browser: true, exportConditions: ["default"] }), + isProduction ? typescript({ + customConditions: ["default"], exclude: ['**/ABIs/*', '**/*.test.*', '**/utils/testUtils.ts'], - }), + }) : + swc.rollup({exclude: ['**/ABIs/*', '**/*.test.*', '**/utils/testUtils.ts']}), ], }; diff --git a/packages/internal/guardian/src/client/api.ts b/packages/internal/guardian/src/client/api.ts index 50723da672..09de3aa670 100644 --- a/packages/internal/guardian/src/client/api.ts +++ b/packages/internal/guardian/src/client/api.ts @@ -15,6 +15,5 @@ export * from './domain/messages-api'; -export * from './domain/starkex-transactions-api'; export * from './domain/transactions-api'; diff --git a/packages/internal/guardian/src/client/domain/starkex-transactions-api.ts b/packages/internal/guardian/src/client/domain/starkex-transactions-api.ts deleted file mode 100644 index 7800d5a3f5..0000000000 --- a/packages/internal/guardian/src/client/domain/starkex-transactions-api.ts +++ /dev/null @@ -1,157 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * Guardian - * Guardian API - * - * The version of the OpenAPI document: 1.0.0 - * Contact: support@immutable.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - -import type { Configuration } from '../configuration'; -import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios'; -import globalAxios from 'axios'; -// Some imports not used depending on template conditions -// @ts-ignore -import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '../common'; -// @ts-ignore -import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base'; -// @ts-ignore -import type { APIError404 } from '../models'; -// @ts-ignore -import type { APIError500 } from '../models'; -// @ts-ignore -import type { TransactionEvaluationResponse } from '../models'; -/** - * StarkexTransactionsApi - axios parameter creator - * @export - */ -export const StarkexTransactionsApiAxiosParamCreator = function (configuration?: Configuration) { - return { - /** - * Check if it is a valid transaction by payload hash - * @summary Evaluate if it is an valid transaction - * @param {string} payloadHash Hash for the payload - * @param {*} [options] Override http request option. - * @deprecated - * @throws {RequiredError} - */ - evaluateStarkexTransaction: async (payloadHash: string, options: RawAxiosRequestConfig = {}): Promise => { - // verify required parameter 'payloadHash' is not null or undefined - assertParamExists('evaluateStarkexTransaction', 'payloadHash', payloadHash) - const localVarPath = `/guardian/v1/starkex/evaluate/{payloadHash}` - .replace(`{${"payloadHash"}}`, encodeURIComponent(String(payloadHash))); - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication BearerAuth required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - } -}; - -/** - * StarkexTransactionsApi - functional programming interface - * @export - */ -export const StarkexTransactionsApiFp = function(configuration?: Configuration) { - const localVarAxiosParamCreator = StarkexTransactionsApiAxiosParamCreator(configuration) - return { - /** - * Check if it is a valid transaction by payload hash - * @summary Evaluate if it is an valid transaction - * @param {string} payloadHash Hash for the payload - * @param {*} [options] Override http request option. - * @deprecated - * @throws {RequiredError} - */ - async evaluateStarkexTransaction(payloadHash: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.evaluateStarkexTransaction(payloadHash, options); - const localVarOperationServerIndex = configuration?.serverIndex ?? 0; - const localVarOperationServerBasePath = operationServerMap['StarkexTransactionsApi.evaluateStarkexTransaction']?.[localVarOperationServerIndex]?.url; - return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); - }, - } -}; - -/** - * StarkexTransactionsApi - factory interface - * @export - */ -export const StarkexTransactionsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { - const localVarFp = StarkexTransactionsApiFp(configuration) - return { - /** - * Check if it is a valid transaction by payload hash - * @summary Evaluate if it is an valid transaction - * @param {StarkexTransactionsApiEvaluateStarkexTransactionRequest} requestParameters Request parameters. - * @param {*} [options] Override http request option. - * @deprecated - * @throws {RequiredError} - */ - evaluateStarkexTransaction(requestParameters: StarkexTransactionsApiEvaluateStarkexTransactionRequest, options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.evaluateStarkexTransaction(requestParameters.payloadHash, options).then((request) => request(axios, basePath)); - }, - }; -}; - -/** - * Request parameters for evaluateStarkexTransaction operation in StarkexTransactionsApi. - * @export - * @interface StarkexTransactionsApiEvaluateStarkexTransactionRequest - */ -export interface StarkexTransactionsApiEvaluateStarkexTransactionRequest { - /** - * Hash for the payload - * @type {string} - * @memberof StarkexTransactionsApiEvaluateStarkexTransaction - */ - readonly payloadHash: string -} - -/** - * StarkexTransactionsApi - object-oriented interface - * @export - * @class StarkexTransactionsApi - * @extends {BaseAPI} - */ -export class StarkexTransactionsApi extends BaseAPI { - /** - * Check if it is a valid transaction by payload hash - * @summary Evaluate if it is an valid transaction - * @param {StarkexTransactionsApiEvaluateStarkexTransactionRequest} requestParameters Request parameters. - * @param {*} [options] Override http request option. - * @deprecated - * @throws {RequiredError} - * @memberof StarkexTransactionsApi - */ - public evaluateStarkexTransaction(requestParameters: StarkexTransactionsApiEvaluateStarkexTransactionRequest, options?: RawAxiosRequestConfig) { - return StarkexTransactionsApiFp(this.configuration).evaluateStarkexTransaction(requestParameters.payloadHash, options).then((request) => request(this.axios, this.basePath)); - } -} - diff --git a/packages/internal/guardian/tsconfig.json b/packages/internal/guardian/tsconfig.json index f587a4939e..b609792807 100644 --- a/packages/internal/guardian/tsconfig.json +++ b/packages/internal/guardian/tsconfig.json @@ -1,24 +1,9 @@ { + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", - "rootDirs": [ - "src" - ], - "target": "es2022", - "module": "es2022", - "moduleResolution": "node", - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": true, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": true, - "allowSyntheticDefaultImports": true, - "noFallthroughCasesInSwitch": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "declaration": true + "rootDirs": ["src"], + "customConditions": ["development"] }, "include": [ "src" diff --git a/packages/game-bridge/.eslintrc b/packages/internal/metrics/.eslintrc.cjs similarity index 55% rename from packages/game-bridge/.eslintrc rename to packages/internal/metrics/.eslintrc.cjs index f90c594b06..cc10d70796 100644 --- a/packages/game-bridge/.eslintrc +++ b/packages/internal/metrics/.eslintrc.cjs @@ -1,8 +1,8 @@ -{ - "extends": ["../../.eslintrc"], +module.exports = { + "extends": ["../.eslintrc"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname } } diff --git a/packages/internal/metrics/package.json b/packages/internal/metrics/package.json index f1bdd50e9b..9d9ffd4666 100644 --- a/packages/internal/metrics/package.json +++ b/packages/internal/metrics/package.json @@ -11,6 +11,7 @@ }, "devDependencies": { "@rollup/plugin-typescript": "^11.1.6", + "@swc/core": "^1.3.36", "@swc/jest": "^0.2.24", "@types/jest": "^29.4.3", "eslint": "^8.40.0", @@ -18,11 +19,24 @@ "jest-environment-jsdom": "^29.4.3", "rollup": "^4.19.1", "ts-jest": "^29.1.0", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" }, "engines": { "node": ">=20.11.0" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } + }, "files": [ "dist" ], @@ -32,11 +46,10 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest", "typecheck": "tsc --noEmit --jsx preserve" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/internal/metrics/rollup.config.js b/packages/internal/metrics/rollup.config.js index 7e22038124..0269dcdc15 100644 --- a/packages/internal/metrics/rollup.config.js +++ b/packages/internal/metrics/rollup.config.js @@ -1,4 +1,7 @@ import typescript from '@rollup/plugin-typescript'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: 'src/index.ts', @@ -6,7 +9,5 @@ export default { dir: 'dist', format: 'es', }, - plugins: [ - typescript(), - ], + plugins: [isProduction ? typescript({customConditions: ["default"]}) : swc.rollup()], }; diff --git a/packages/internal/metrics/src/index.ts b/packages/internal/metrics/src/index.ts index 323f48a335..e5ea7db07d 100644 --- a/packages/internal/metrics/src/index.ts +++ b/packages/internal/metrics/src/index.ts @@ -3,7 +3,8 @@ import * as localStorage from './utils/localStorage'; export { track } from './track'; export { trackDuration } from './performance'; -export { Flow, trackFlow } from './flow'; +export { trackFlow } from './flow'; +export type { Flow } from './flow'; export { trackError } from './error'; export { identify } from './identify'; export { diff --git a/packages/internal/metrics/tsconfig.json b/packages/internal/metrics/tsconfig.json index fc204f35c4..0bbea58dc3 100644 --- a/packages/internal/metrics/tsconfig.json +++ b/packages/internal/metrics/tsconfig.json @@ -1,109 +1,9 @@ { + "extends": "../../../tsconfig.base.json", "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "esnext", /* Specify what module code is generated. */ - // "rootDir": "./src", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - "rootDirs": ["src"], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ - "removeComments": false, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true, /* Skip type checking all .d.ts files. */ - - /* Advanced */ - // "noEmit": true, - // "allowJs": false, - // "allowSyntheticDefaultImports": true, - // "resolveJsonModule": true, + "outDir": "./dist", + "rootDirs": ["src"], + "customConditions": ["development"] }, "include": ["src"], "exclude": ["dist", "jest.config.js", "node_modules"] diff --git a/packages/internal/toolkit/package.json b/packages/internal/toolkit/package.json index e015d69f35..11bd2fb9eb 100644 --- a/packages/internal/toolkit/package.json +++ b/packages/internal/toolkit/package.json @@ -9,7 +9,7 @@ "@ethersproject/providers": "^5.7.2", "@ethersproject/wallet": "^5.7.0", "@imtbl/x-client": "0.0.0", - "@magic-ext/oidc": "4.2.0", + "@magic-ext/oidc": "4.3.1", "@metamask/detect-provider": "^2.0.0", "axios": "^1.6.5", "bn.js": "^5.2.1", @@ -35,11 +35,24 @@ "prettier": "^2.8.7", "rollup": "^4.19.1", "ts-node": "^10.9.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" }, "engines": { "node": ">=20.11.0" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } + }, "files": [ "dist" ], @@ -50,13 +63,11 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", - "prepare": "wsrun -r build", "test": "jest", "test:watch": "jest --watch", "typecheck": "tsc --noEmit --jsx preserve" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/internal/toolkit/rollup.config.js b/packages/internal/toolkit/rollup.config.js index 16a7d58d7e..0269dcdc15 100644 --- a/packages/internal/toolkit/rollup.config.js +++ b/packages/internal/toolkit/rollup.config.js @@ -1,4 +1,7 @@ import typescript from '@rollup/plugin-typescript'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: 'src/index.ts', @@ -6,5 +9,5 @@ export default { dir: 'dist', format: 'es', }, - plugins: [typescript()], + plugins: [isProduction ? typescript({customConditions: ["default"]}) : swc.rollup()], }; diff --git a/packages/internal/toolkit/tsconfig.json b/packages/internal/toolkit/tsconfig.json index 7dbb9f1fb9..d8f173b231 100644 --- a/packages/internal/toolkit/tsconfig.json +++ b/packages/internal/toolkit/tsconfig.json @@ -1,20 +1,9 @@ { + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": true, - "resolveJsonModule": true, - "skipLibCheck": true + "customConditions": ["development"] }, "include": ["src"], "exclude": ["node_modules", "dist", "src/sample-app"] diff --git a/packages/minting-backend/sdk/.eslintrc b/packages/minting-backend/sdk/.eslintrc.cjs similarity index 79% rename from packages/minting-backend/sdk/.eslintrc rename to packages/minting-backend/sdk/.eslintrc.cjs index f77729693e..3193df85c8 100644 --- a/packages/minting-backend/sdk/.eslintrc +++ b/packages/minting-backend/sdk/.eslintrc.cjs @@ -1,9 +1,9 @@ -{ +module.exports = { "extends": ["../../../.eslintrc"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname }, "rules": { "@typescript-eslint/comma-dangle": "off" diff --git a/packages/minting-backend/sdk/jest.config.ts b/packages/minting-backend/sdk/jest.config.ts index c64fb9b6e6..05ef77a17a 100644 --- a/packages/minting-backend/sdk/jest.config.ts +++ b/packages/minting-backend/sdk/jest.config.ts @@ -4,10 +4,7 @@ const config: Config = { clearMocks: true, coverageProvider: 'v8', moduleDirectories: ['node_modules', 'src'], - moduleNameMapper: { - '@imtbl/config': '../../config/src', - '@imtbl/generated-clients': '../../internal/generated-clients/src' - }, + moduleNameMapper: { '^@imtbl/(.*)$': '/../../../node_modules/@imtbl/$1/src' }, testEnvironment: 'node', transform: { '^.+\\.(t|j)sx?$': '@swc/jest', diff --git a/packages/minting-backend/sdk/package.json b/packages/minting-backend/sdk/package.json index d7a0469d24..624feccf5b 100644 --- a/packages/minting-backend/sdk/package.json +++ b/packages/minting-backend/sdk/package.json @@ -7,6 +7,7 @@ "dependencies": { "@imtbl/blockchain-data": "0.0.0", "@imtbl/config": "0.0.0", + "@imtbl/generated-clients": "0.0.0", "@imtbl/metrics": "0.0.0", "@imtbl/webhook": "0.0.0" }, @@ -24,7 +25,20 @@ "rollup": "^4.19.1", "testcontainers": "^10.9.0", "ts-mockito": "^2.6.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" + }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } }, "homepage": "https://github.com/immutable/ts-immutable-sdk#readme", "license": "Apache-2.0", @@ -37,12 +51,11 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest --passWithNoTests", "test:watch": "jest --watch", "typecheck": "tsc --noEmit --jsx preserve" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/minting-backend/sdk/rollup.config.js b/packages/minting-backend/sdk/rollup.config.js index 16a7d58d7e..0269dcdc15 100644 --- a/packages/minting-backend/sdk/rollup.config.js +++ b/packages/minting-backend/sdk/rollup.config.js @@ -1,4 +1,7 @@ import typescript from '@rollup/plugin-typescript'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: 'src/index.ts', @@ -6,5 +9,5 @@ export default { dir: 'dist', format: 'es', }, - plugins: [typescript()], + plugins: [isProduction ? typescript({customConditions: ["default"]}) : swc.rollup()], }; diff --git a/packages/minting-backend/sdk/tsconfig.json b/packages/minting-backend/sdk/tsconfig.json index 56c27e347a..5184ceffeb 100644 --- a/packages/minting-backend/sdk/tsconfig.json +++ b/packages/minting-backend/sdk/tsconfig.json @@ -1,20 +1,9 @@ { + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": true, - "resolveJsonModule": true, - "skipLibCheck": true + "customConditions": ["development"] }, "include": ["src"], "exclude": ["node_modules", "dist"] diff --git a/packages/orderbook/.eslintrc b/packages/orderbook/.eslintrc.cjs similarity index 92% rename from packages/orderbook/.eslintrc rename to packages/orderbook/.eslintrc.cjs index 38e5c14091..aaaaae145f 100644 --- a/packages/orderbook/.eslintrc +++ b/packages/orderbook/.eslintrc.cjs @@ -1,10 +1,10 @@ -{ - "extends": ["airbnb", "airbnb-typescript"], +module.exports = { + "extends": ["../../.eslintrc", "airbnb", "airbnb-typescript"], "ignorePatterns": ["jest.config.*"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname }, "rules": { "@typescript-eslint/naming-convention": [ diff --git a/packages/orderbook/package.json b/packages/orderbook/package.json index cf8713368c..8aa287a583 100644 --- a/packages/orderbook/package.json +++ b/packages/orderbook/package.json @@ -5,6 +5,7 @@ "bugs": "https://github.com/immutable/ts-immutable-sdk/issues", "dependencies": { "@imtbl/config": "0.0.0", + "@imtbl/metrics": "0.0.0", "@opensea/seaport-js": "4.0.3", "axios": "^1.6.5", "ethers": "^5.7.2", @@ -13,6 +14,7 @@ }, "devDependencies": { "@rollup/plugin-typescript": "^11.1.6", + "@swc/core": "^1.3.36", "@swc/jest": "^0.2.24", "@typechain/ethers-v5": "^10.2.0", "@types/jest": "^29.4.3", @@ -24,7 +26,20 @@ "rollup": "^4.19.1", "ts-mockito": "^2.6.1", "typechain": "^8.1.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" + }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } }, "homepage": "https://github.com/immutable/ts-immutable-sdk#readme", "license": "Apache-2.0", @@ -33,7 +48,7 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "generate-types": "typechain --target=ethers-v5 --out-dir=src/typechain/types 'abi/*.json'", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "lint:fix": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0 --fix", @@ -46,6 +61,5 @@ "test:watch": "jest --watch", "typecheck": "tsc --noEmit --jsx preserve" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/orderbook/rollup.config.js b/packages/orderbook/rollup.config.js index 16a7d58d7e..0269dcdc15 100644 --- a/packages/orderbook/rollup.config.js +++ b/packages/orderbook/rollup.config.js @@ -1,4 +1,7 @@ import typescript from '@rollup/plugin-typescript'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: 'src/index.ts', @@ -6,5 +9,5 @@ export default { dir: 'dist', format: 'es', }, - plugins: [typescript()], + plugins: [isProduction ? typescript({customConditions: ["default"]}) : swc.rollup()], }; diff --git a/packages/orderbook/src/index.ts b/packages/orderbook/src/index.ts index 2d42a79ff8..e92417f45d 100644 --- a/packages/orderbook/src/index.ts +++ b/packages/orderbook/src/index.ts @@ -1,4 +1,4 @@ export { Orderbook } from './orderbook'; export { constants } from './constants'; -export { OrderbookModuleConfiguration, OrderbookOverrides } from './config'; +export type { OrderbookModuleConfiguration, OrderbookOverrides } from './config'; export * from './types'; diff --git a/packages/orderbook/src/orderbook.ts b/packages/orderbook/src/orderbook.ts index 29501dd650..74b5a7e039 100644 --- a/packages/orderbook/src/orderbook.ts +++ b/packages/orderbook/src/orderbook.ts @@ -1,4 +1,5 @@ import { ModuleConfiguration } from '@imtbl/config'; +import { track } from '@imtbl/metrics'; import { ImmutableApiClient, ImmutableApiClientFactory } from './api-client'; import { getConfiguredProvider, @@ -36,7 +37,7 @@ import { PrepareBulkListingsResponse, PrepareListingResponse, SignablePurpose, - TradeResult, + TradeResult, Action, } from './types'; /** @@ -99,6 +100,11 @@ export class Orderbook { ); } + // Default order expiry to 2 years from now + static defaultOrderExpiry(): Date { + return new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 2); + } + /** * Return the configuration for the orderbook module. * @return {OrderbookModuleConfiguration} The configuration for the orderbook module. @@ -168,11 +174,16 @@ export class Orderbook { * Once the transactions are submitted and the message signed, call the completeListings method * provided in the return type with the signature. This method supports up to 20 listing creations * at a time. It can also be used for individual listings to simplify integration code paths. + * + * Bulk listings created using an EOA (Metamask) will require a single listing confirmation + * signature. + * Bulk listings creating using a smart contract wallet will require multiple listing confirmation + * signatures(as many as the number of orders). * @param {PrepareBulkListingsParams} prepareBulkListingsParams - Details about the listings * to be created. * @return {PrepareBulkListingsResponse} PrepareListingResponse includes * any unsigned approval transactions, the typed bulk order message for signing and - * the createListings method that can be called with the signature to create the listings. + * the createListings method that can be called with the signature(s) to create the listings. */ async prepareBulkListings( { @@ -185,6 +196,8 @@ export class Orderbook { throw new Error('Bulk listing creation is limited to 20 orders'); } + // Bulk listings (with single listing) code path common for both Smart contract + // wallets and EOAs. // In the event of a single order, delegate to prepareListing as the signature is more // gas efficient if (listingParams.length === 1) { @@ -193,17 +206,17 @@ export class Orderbook { listingParams[0].sell, listingParams[0].buy, new Date(), - listingParams[0].orderExpiry || new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 2), + listingParams[0].orderExpiry || Orderbook.defaultOrderExpiry(), ); return { actions: prepareListingResponse.actions, - completeListings: async (signature: string) => { + completeListings: async (signatures: string | string[]) => { const createListingResult = await this.createListing({ makerFees: listingParams[0].makerFees, orderComponents: prepareListingResponse.orderComponents, orderHash: prepareListingResponse.orderHash, - orderSignature: signature, + orderSignature: typeof signatures === 'string' ? signatures : signatures[0], }); return { @@ -217,28 +230,105 @@ export class Orderbook { }; } + // Bulk listings (with multiple listings) code path for Smart contract wallets. + // Code check to determine wallet type is not fool-proof but scenarios where smart + // contract wallet is not deployed will be an edge case + const isSmartContractWallet: boolean = await this.orderbookConfig.provider.getCode(makerAddress) !== '0x'; + if (isSmartContractWallet) { + track('orderbookmr', 'bulkListings', { walletType: 'Passport', makerAddress, listingsCount: listingParams.length }); + + // eslint-disable-next-line max-len + const prepareListingResponses = await Promise.all(listingParams.map((listing) => this.seaport.prepareSeaportOrder( + makerAddress, + listing.sell, + listing.buy, + new Date(), + listing.orderExpiry || Orderbook.defaultOrderExpiry(), + ))); + + const pendingApproval: string[] = []; + const actions = prepareListingResponses.flatMap((response) => { + // de-dupe approval transactions to ensure every contract has + // a maximum of 1 approval transaction + const dedupedActions: Action[] = []; + response.actions.forEach((action) => { + if (action.type === ActionType.TRANSACTION) { + // Assuming only a single item is on offer per listing + const contractAddress = response.orderComponents.offer[0].token; + if (!pendingApproval.includes(contractAddress)) { + pendingApproval.push(contractAddress); + dedupedActions.push(action); + } + } else { + dedupedActions.push(action); + } + }); + return dedupedActions; + }); + + return { + actions, + completeListings: async (signatures: string | string[]) => { + const signatureIsString = typeof signatures === 'string'; + if (signatureIsString) { + throw new Error('A signature per listing must be provided for smart contract wallets'); + } + + const createListingsApiResponses = await Promise.all( + prepareListingResponses.map((prepareListingResponse, i) => { + const signature = signatures[i]; + return this.apiClient.createListing({ + makerFees: listingParams[i].makerFees, + orderComponents: prepareListingResponse.orderComponents, + orderHash: prepareListingResponse.orderHash, + orderSignature: signature, + // Swallow failed creations,this gets mapped in the response to the caller as failed + }).catch(() => undefined); + }), + ); + + return { + result: createListingsApiResponses.map((apiListingResponse, i) => ({ + success: !!apiListingResponse, + orderHash: prepareListingResponses[i].orderHash, + // eslint-disable-next-line max-len + order: apiListingResponse ? mapFromOpenApiOrder(apiListingResponse.result) : undefined, + })), + }; + }, + }; + } + + // Bulk listings (with multiple listings) code path for EOA wallets. + track('orderbookmr', 'bulkListings', { walletType: 'EOA', makerAddress, listingsCount: listingParams.length }); const { actions, preparedListings } = await this.seaport.prepareBulkSeaportOrders( makerAddress, listingParams.map((orderParam) => ({ listingItem: orderParam.sell, considerationItem: orderParam.buy, orderStart: new Date(), - orderExpiry: orderParam.orderExpiry || new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 2), + orderExpiry: orderParam.orderExpiry || Orderbook.defaultOrderExpiry(), })), ); return { actions, - completeListings: async (bulkOrderSignature: string) => { + completeListings: async (signatures: string | string[]) => { + const signatureIsArray = typeof signatures === 'object'; + if (signatureIsArray && signatures.length !== 1) { + throw new Error('Only a single signature is expected for bulk listing creation'); + } + const orderComponents = preparedListings.map((orderParam) => orderParam.orderComponents); - const signatures = getBulkSeaportOrderSignatures( - bulkOrderSignature, + const signature = signatureIsArray ? signatures[0] : signatures; + const bulkOrderSignatures = getBulkSeaportOrderSignatures( + signature, orderComponents, ); const createOrdersApiListingResponse = await Promise.all( orderComponents.map((orderComponent, i) => { - const sig = signatures[i]; + const sig = bulkOrderSignatures[i]; const listing = preparedListings[i]; const listingParam = listingParams[i]; return this.apiClient.createListing({ @@ -283,7 +373,7 @@ export class Orderbook { // Default order start to now new Date(), // Default order expiry to 2 years from now - orderExpiry || new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 2), + orderExpiry || Orderbook.defaultOrderExpiry(), ); } diff --git a/packages/orderbook/src/test/bulk-listings.e2e.ts b/packages/orderbook/src/test/bulk-listings.e2e.ts index bb0896c769..6d10582a32 100644 --- a/packages/orderbook/src/test/bulk-listings.e2e.ts +++ b/packages/orderbook/src/test/bulk-listings.e2e.ts @@ -10,6 +10,7 @@ import { waitForOrderToBeOfStatus } from './helpers/order'; import { getConfigFromEnv } from './helpers'; import { actionAll } from './helpers/actions'; import { PrepareBulkListingsParams } from '../types'; +import { GAS_OVERRIDES } from './helpers/gas'; // An array of each number between 1 and 20 const supportedListings = Array.from({ length: 20 }, (_, i) => i + 1); @@ -35,7 +36,7 @@ describe('prepareListing and createOrder bulk e2e', () => { const orderParams: PrepareBulkListingsParams['listingParams'] = []; let i = 0; while (i < numberOfListings) { - await contract.safeMint(offerer.address); + await contract.safeMint(offerer.address, GAS_OVERRIDES); orderParams.push({ buy: { @@ -56,15 +57,15 @@ describe('prepareListing and createOrder bulk e2e', () => { i++; } - await contract.safeMint(offerer.address); + await contract.safeMint(offerer.address, GAS_OVERRIDES); const { actions, completeListings } = await sdk.prepareBulkListings({ makerAddress: offerer.address, listingParams: orderParams, }); - const [bulkOrderSignature] = await actionAll(actions, offerer); - const res = await completeListings(bulkOrderSignature); + const bulkOrderSignatures = await actionAll(actions, offerer); + const res = await completeListings(bulkOrderSignatures); for (const result of res.result) { if (!result.order) { @@ -72,7 +73,7 @@ describe('prepareListing and createOrder bulk e2e', () => { } await waitForOrderToBeOfStatus(sdk, result.order.id, OrderStatusName.ACTIVE); } - }, 30_000); + }, 60_000); it('should create fail to prepare more than 20 listings', async () => { const provider = getLocalhostProvider(); @@ -95,7 +96,7 @@ describe('prepareListing and createOrder bulk e2e', () => { let i = 0; const tooManyListings = 21; while (i < tooManyListings) { - await contract.safeMint(offerer.address); + await contract.safeMint(offerer.address, GAS_OVERRIDES); orderParams.push({ buy: { @@ -113,11 +114,11 @@ describe('prepareListing and createOrder bulk e2e', () => { i++; } - await contract.safeMint(offerer.address); + await contract.safeMint(offerer.address, GAS_OVERRIDES); await expect(sdk.prepareBulkListings({ makerAddress: offerer.address, listingParams: orderParams, })).rejects.toEqual(new Error('Bulk listing creation is limited to 20 orders')); - }, 30_000); + }, 60_000); }); diff --git a/packages/orderbook/src/types.ts b/packages/orderbook/src/types.ts index 7e103390fa..d33328958b 100644 --- a/packages/orderbook/src/types.ts +++ b/packages/orderbook/src/types.ts @@ -59,7 +59,12 @@ export interface PrepareBulkListingsParams { export interface PrepareBulkListingsResponse { actions: Action[]; - completeListings: (signature: string) => Promise; + completeListings(signatures: string[]): Promise; + /** + * @deprecated Pass a string[] to `completeListings` instead to enable + * smart contract wallets + */ + completeListings(signature: string): Promise; } export interface PrepareBulkSeaportOrders { diff --git a/packages/orderbook/tsconfig.json b/packages/orderbook/tsconfig.json index 56c27e347a..5bd0b3f89e 100644 --- a/packages/orderbook/tsconfig.json +++ b/packages/orderbook/tsconfig.json @@ -1,20 +1,9 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": true, - "resolveJsonModule": true, - "skipLibCheck": true + "customConditions": ["development"] }, "include": ["src"], "exclude": ["node_modules", "dist"] diff --git a/packages/passport/sdk-sample-app/next.config.js b/packages/passport/sdk-sample-app/next.config.js index 4490769749..df318b8450 100644 --- a/packages/passport/sdk-sample-app/next.config.js +++ b/packages/passport/sdk-sample-app/next.config.js @@ -12,6 +12,9 @@ if (basePath) { /** @type {import('next').NextConfig} */ const nextConfig = { ...pathConfig, + typescript: { + tsconfigPath: './tsconfig.build.json', + }, reactStrictMode: true, }; diff --git a/packages/passport/sdk-sample-app/package.json b/packages/passport/sdk-sample-app/package.json index 2ad884d7b1..42b57164fb 100644 --- a/packages/passport/sdk-sample-app/package.json +++ b/packages/passport/sdk-sample-app/package.json @@ -6,6 +6,7 @@ "@biom3/react": "^0.25.0", "@imtbl/blockchain-data": "0.0.0", "@imtbl/config": "0.0.0", + "@imtbl/generated-clients": "0.0.0", "@imtbl/orderbook": "0.0.0", "@imtbl/passport": "0.0.0", "@imtbl/x-client": "0.0.0", @@ -16,13 +17,14 @@ "embla-carousel-react": "^8.1.5", "ethers": "^5.7.2", "framer-motion": "^11.0.6", - "next": "13.3.1", + "next": "13.4.11", "react": "^18.2.0", "react-bootstrap": "^2.7.2", "react-dom": "^18.2.0" }, "devDependencies": { "@next/eslint-plugin-next": "^13.4.7", + "@swc/core": "^1.3.36", "@types/node": "^18.14.2", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", diff --git a/packages/passport/sdk-sample-app/src/components/zkevm/EthSendTransactionExamples/NFTTransfer.tsx b/packages/passport/sdk-sample-app/src/components/zkevm/EthSendTransactionExamples/NFTTransfer.tsx index ba1aa87d5f..6f7c3a0cfa 100644 --- a/packages/passport/sdk-sample-app/src/components/zkevm/EthSendTransactionExamples/NFTTransfer.tsx +++ b/packages/passport/sdk-sample-app/src/components/zkevm/EthSendTransactionExamples/NFTTransfer.tsx @@ -11,7 +11,7 @@ import { import { EnvironmentNames, RequestExampleProps } from '@/types'; import { useImmutableProvider } from '@/context/ImmutableProvider'; import { usePassportProvider } from '@/context/PassportProvider'; -import { NFT } from '@imtbl/generated-clients/dist/blockchain-data'; +import { BlockchainData } from '@imtbl/generated-clients'; import WorkflowButton from '@/components/WorkflowButton'; import { utils } from 'ethers'; @@ -20,7 +20,7 @@ type GroupedAsset = { assets: NFTCandidate[]; }; -type NFTCandidate = NFT & { selected: boolean, to_address?: string, to_address_required?: string }; +type NFTCandidate = BlockchainData.NFT & { selected: boolean, to_address?: string, to_address_required?: string }; const chainNameMapping = (environment: EnvironmentNames) => { switch (environment) { @@ -36,7 +36,7 @@ const chainNameMapping = (environment: EnvironmentNames) => { }; function NFTTransfer({ disabled, handleExampleSubmitted }: RequestExampleProps) { - const [assets, setAssets] = useState([]); + const [assets, setAssets] = useState([]); const [transfers, setTransfers] = useState[]>([]); const [fromAddress, setFromAddress] = useState(''); const { zkEvmProvider } = usePassportProvider(); @@ -111,14 +111,14 @@ function NFTTransfer({ disabled, handleExampleSubmitted }: RequestExampleProps) }; const assetsRes = await blockchainData.listNFTsByAccountAddress(payload); - setAssets(assetsRes.result as NFT[]); + setAssets(assetsRes.result as BlockchainData.NFT[]); }; getAssets().catch(console.log); }, [blockchainData, chainName, fromAddress]); const groupedAssets = useMemo( () => assets - .reduce((group: GroupedAsset[], rawAsset: NFT) => { + .reduce((group: GroupedAsset[], rawAsset: BlockchainData.NFT) => { const sameContractAddressAssets = group.find( (g) => g.contract_address.toLowerCase() === rawAsset.contract_address.toLowerCase(), ); diff --git a/packages/passport/sdk-sample-app/tsconfig.build.json b/packages/passport/sdk-sample-app/tsconfig.build.json new file mode 100644 index 0000000000..20c737854b --- /dev/null +++ b/packages/passport/sdk-sample-app/tsconfig.build.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "customConditions": ["default"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "paths": { + "@/*": ["./src/*"], + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/packages/passport/sdk-sample-app/tsconfig.json b/packages/passport/sdk-sample-app/tsconfig.json index 0e45b9034f..5565e520a4 100644 --- a/packages/passport/sdk-sample-app/tsconfig.json +++ b/packages/passport/sdk-sample-app/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], + "customConditions": ["development"], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -9,7 +10,7 @@ "noEmit": true, "esModuleInterop": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", diff --git a/packages/passport/sdk/.eslintrc b/packages/passport/sdk/.eslintrc.cjs similarity index 90% rename from packages/passport/sdk/.eslintrc rename to packages/passport/sdk/.eslintrc.cjs index 4fd603ea90..a617575b17 100644 --- a/packages/passport/sdk/.eslintrc +++ b/packages/passport/sdk/.eslintrc.cjs @@ -1,9 +1,9 @@ -{ +module.exports = { "extends": ["../../../.eslintrc"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname }, "rules": { "@typescript-eslint/naming-convention": [ diff --git a/packages/passport/sdk/jest.config.ts b/packages/passport/sdk/jest.config.ts index a457a0abf9..e166d4ce55 100644 --- a/packages/passport/sdk/jest.config.ts +++ b/packages/passport/sdk/jest.config.ts @@ -4,14 +4,7 @@ const config: Config = { clearMocks: true, coverageProvider: 'v8', moduleDirectories: ['node_modules', 'src'], - moduleNameMapper: { - '@imtbl/config': '../../config/src', - '@imtbl/metrics': '../../internal/metrics/src', - '@imtbl/generated-clients': '../../internal/generated-clients/src', - '@imtbl/guardian': '../../internal/guardian/src', - '@imtbl/x-client': '../../x-client/src', - '@imtbl/toolkit': '../../internal/toolkit/src', - }, + moduleNameMapper: { '^@imtbl/(.*)$': '/../../../node_modules/@imtbl/$1/src' }, testEnvironment: 'jsdom', transform: { '^.+\\.(t|j)sx?$': '@swc/jest', diff --git a/packages/passport/sdk/package.json b/packages/passport/sdk/package.json index 464636ec92..18fa3e0ef3 100644 --- a/packages/passport/sdk/package.json +++ b/packages/passport/sdk/package.json @@ -15,7 +15,7 @@ "@imtbl/toolkit": "0.0.0", "@imtbl/x-client": "0.0.0", "@imtbl/x-provider": "0.0.0", - "@magic-ext/oidc": "4.2.0", + "@magic-ext/oidc": "4.3.1", "@metamask/detect-provider": "^2.0.0", "axios": "^1.6.5", "ethers": "^5.7.2", @@ -47,11 +47,24 @@ "prettier": "^2.8.7", "rollup": "^4.19.1", "ts-node": "^10.9.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" }, "engines": { "node": ">=20.11.0" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } + }, "files": [ "dist" ], @@ -62,12 +75,11 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest", "test:watch": "jest --watch", "typecheck": "tsc --noEmit --jsx preserve" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/passport/sdk/rollup.config.js b/packages/passport/sdk/rollup.config.js index 16a7d58d7e..0269dcdc15 100644 --- a/packages/passport/sdk/rollup.config.js +++ b/packages/passport/sdk/rollup.config.js @@ -1,4 +1,7 @@ import typescript from '@rollup/plugin-typescript'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: 'src/index.ts', @@ -6,5 +9,5 @@ export default { dir: 'dist', format: 'es', }, - plugins: [typescript()], + plugins: [isProduction ? typescript({customConditions: ["default"]}) : swc.rollup()], }; diff --git a/packages/passport/sdk/src/Passport.int.test.ts b/packages/passport/sdk/src/Passport.int.test.ts index 0cb5a821fd..b7e915bf91 100644 --- a/packages/passport/sdk/src/Passport.int.test.ts +++ b/packages/passport/sdk/src/Passport.int.test.ts @@ -93,7 +93,6 @@ describe('Passport', () => { (Magic as jest.Mock).mockImplementation(() => ({ openid: { loginWithOIDC: mockLoginWithOidc }, rpcProvider: { request: mockMagicRequest }, - preload: jest.fn(), })); }); diff --git a/packages/passport/sdk/src/index.ts b/packages/passport/sdk/src/index.ts index 7a782c1ed8..f9411a0dc6 100644 --- a/packages/passport/sdk/src/index.ts +++ b/packages/passport/sdk/src/index.ts @@ -1,13 +1,14 @@ export { PassportError } from './errors/passportError'; export { Passport } from './Passport'; export { + ProviderEvent, +} from './zkEvm/types'; +export type { RequestArguments, JsonRpcRequestPayload, JsonRpcResponsePayload, JsonRpcRequestCallback, - Provider, - ProviderEvent, - AccountsChangedEvent, + Provider, AccountsChangedEvent, TypedDataPayload, } from './zkEvm/types'; export { @@ -15,7 +16,7 @@ export { ProviderErrorCode, RpcErrorCode, } from './zkEvm/JsonRpcError'; -export { +export type { LinkWalletParams, LinkedWallet, UserProfile, diff --git a/packages/passport/sdk/src/magicAdapter.test.ts b/packages/passport/sdk/src/magicAdapter.test.ts index 762f99621d..acad6c4252 100644 --- a/packages/passport/sdk/src/magicAdapter.test.ts +++ b/packages/passport/sdk/src/magicAdapter.test.ts @@ -4,7 +4,7 @@ import MagicAdapter from './magicAdapter'; import { PassportConfiguration } from './config'; import { PassportError, PassportErrorType } from './errors/passportError'; -const loginWithOIDCMock:jest.MockedFunction<(args: LoginWithOpenIdParams) => Promise> = jest.fn(); +const loginWithOIDCMock: jest.MockedFunction<(args: LoginWithOpenIdParams) => Promise> = jest.fn(); const rpcProvider = {}; @@ -23,7 +23,6 @@ describe('MagicWallet', () => { magicProviderId: providerId, } as PassportConfiguration; const idToken = 'e30=.e30=.e30='; - const preload = jest.fn(); beforeEach(() => { jest.resetAllMocks(); @@ -35,7 +34,6 @@ describe('MagicWallet', () => { logout: logoutMock, }, rpcProvider, - preload, })); }); @@ -56,10 +54,9 @@ describe('MagicWallet', () => { }); it('starts initialising the magicClient', () => { jest.spyOn(window.document, 'readyState', 'get').mockReturnValue('complete'); - preload.mockResolvedValue(Promise.resolve()); const magicAdapter = new MagicAdapter(config); - // @ts-ignore - expect(magicAdapter.lazyMagicClient).toBeDefined(); + // @ts-expect-error: client is private + expect(magicAdapter.client).toBeDefined(); }); }); @@ -75,15 +72,31 @@ describe('MagicWallet', () => { it('does nothing', () => { const magicAdapter = new MagicAdapter(config); - // @ts-ignore - expect(magicAdapter.magicClientPromise).toBeUndefined(); + // @ts-expect-error: client is private + expect(magicAdapter.client).toBeUndefined(); + }); + + it('should throw a browser error for loginWithOIDC', async () => { + const magicAdapter = new MagicAdapter(config); + + let type = ''; + let message = ''; + + try { + await magicAdapter.login(idToken); + } catch (e: any) { + type = e.type; + message = e.message; + } + + expect(type).toEqual(PassportErrorType.WALLET_CONNECTION_ERROR); + expect(message).toEqual('Cannot perform this action outside of the browser'); }); }); }); describe('login', () => { it('should call loginWithOIDC and initialise the provider with the correct arguments', async () => { - preload.mockResolvedValue(Promise.resolve()); const magicAdapter = new MagicAdapter(config); const magicProvider = await magicAdapter.login(idToken); @@ -101,7 +114,6 @@ describe('MagicWallet', () => { }); it('should throw a PassportError when an error is thrown', async () => { - preload.mockResolvedValue(Promise.resolve()); const magicAdapter = new MagicAdapter(config); loginWithOIDCMock.mockImplementation(() => { @@ -121,7 +133,6 @@ describe('MagicWallet', () => { describe('logout', () => { it('calls the logout function', async () => { - preload.mockResolvedValue(Promise.resolve()); const magicAdapter = new MagicAdapter(config); await magicAdapter.login(idToken); await magicAdapter.logout(); diff --git a/packages/passport/sdk/src/magicAdapter.ts b/packages/passport/sdk/src/magicAdapter.ts index 7e0d512e0d..c30cb1690d 100644 --- a/packages/passport/sdk/src/magicAdapter.ts +++ b/packages/passport/sdk/src/magicAdapter.ts @@ -5,7 +5,6 @@ import { ethers } from 'ethers'; import { trackDuration } from '@imtbl/metrics'; import { PassportErrorType, withPassportError } from './errors/passportError'; import { PassportConfiguration } from './config'; -import { lazyDocumentReady } from './utils/lazyLoad'; type MagicClient = InstanceWithExtensions; @@ -14,28 +13,24 @@ const MAINNET = 'mainnet'; export default class MagicAdapter { private readonly config: PassportConfiguration; - private readonly lazyMagicClient?: Promise; + private readonly client?: MagicClient; constructor(config: PassportConfiguration) { this.config = config; if (typeof window !== 'undefined') { - this.lazyMagicClient = lazyDocumentReady(() => { - const client = new Magic(this.config.magicPublishableApiKey, { - extensions: [new OpenIdExtension()], - network: MAINNET, // We always connect to mainnet to ensure addresses are the same across envs - }); - client.preload(); - return client; + this.client = new Magic(this.config.magicPublishableApiKey, { + extensions: [new OpenIdExtension()], + network: MAINNET, // We always connect to mainnet to ensure addresses are the same across envs }); } } - private get magicClient(): Promise { - if (!this.lazyMagicClient) { + private get magicClient(): MagicClient { + if (!this.client) { throw new Error('Cannot perform this action outside of the browser'); } - return this.lazyMagicClient; + return this.client; } async login( @@ -44,8 +39,7 @@ export default class MagicAdapter { return withPassportError(async () => { const startTime = performance.now(); - const magicClient = await this.magicClient; - await magicClient.openid.loginWithOIDC({ + await this.magicClient.openid.loginWithOIDC({ jwt: idToken, providerId: this.config.magicProviderId, }); @@ -56,14 +50,13 @@ export default class MagicAdapter { Math.round(performance.now() - startTime), ); - return magicClient.rpcProvider as unknown as ethers.providers.ExternalProvider; + return this.magicClient.rpcProvider as unknown as ethers.providers.ExternalProvider; }, PassportErrorType.WALLET_CONNECTION_ERROR); } async logout() { - const magicClient = await this.magicClient; - if (magicClient.user) { - await magicClient.user.logout(); + if (this.magicClient.user) { + await this.magicClient.user.logout(); } } } diff --git a/packages/passport/sdk/src/utils/lazyLoad.test.ts b/packages/passport/sdk/src/utils/lazyLoad.test.ts deleted file mode 100644 index de6a5d577d..0000000000 --- a/packages/passport/sdk/src/utils/lazyLoad.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { lazyDocumentReady, lazyLoad } from './lazyLoad'; - -describe('lazyLoad', () => { - it('should return call initFunction and returns the value', async () => { - const initFunction = jest.fn().mockReturnValue('test'); - const promiseToAwait = jest.fn().mockResolvedValue(undefined); - const result = await lazyLoad(promiseToAwait, initFunction); - expect(result).toEqual('test'); - expect(initFunction).toHaveBeenCalled(); - }); -}); - -describe('lazyDocumentReady', () => { - let mockInitialiseFunction: jest.Mock; - let originalDocument: Document | undefined; - - beforeEach(() => { - mockInitialiseFunction = jest.fn(); - originalDocument = window.document; - const mockDocument = { - ...window.document, - readyState: 'complete', - }; - (window as any).document = mockDocument; - }); - - afterEach(() => { - // Restore the original document.readyState value after each test - (window as any).document = originalDocument; - }); - - it('should call the initialiseFunction when the document is already ready', async () => { - jest.spyOn(window.document, 'readyState', 'get').mockReturnValue('complete'); - - await lazyDocumentReady(mockInitialiseFunction); - - expect(mockInitialiseFunction).toHaveBeenCalledTimes(1); - }); - - it('should call the initialiseFunction when the document becomes ready', async () => { - jest.spyOn(window.document, 'readyState', 'get').mockReturnValue('loading'); - const mockAddEventListener = jest.spyOn(window.document, 'addEventListener'); - - const lazyDocumentPromise = lazyDocumentReady(mockInitialiseFunction); - expect(mockInitialiseFunction).toHaveBeenCalledTimes(0); - - jest.spyOn(window.document, 'readyState', 'get').mockReturnValue('complete'); - const mockEvent = new Event('readystatechange'); - window.document.dispatchEvent(mockEvent); - - await lazyDocumentPromise; - - expect(mockAddEventListener).toHaveBeenCalledWith('readystatechange', expect.any(Function)); - expect(mockInitialiseFunction).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/passport/sdk/src/utils/lazyLoad.ts b/packages/passport/sdk/src/utils/lazyLoad.ts deleted file mode 100644 index 2c22dfeec6..0000000000 --- a/packages/passport/sdk/src/utils/lazyLoad.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const lazyLoad = ( - promiseToAwait: () => Promise, - initialiseFunction: (arg: Y) => Promise | T, -): Promise => promiseToAwait().then(initialiseFunction); - -export const lazyDocumentReady = (initialiseFunction: () => Promise | T): Promise => { - const documentReadyPromise = () => new Promise((resolve) => { - if (window.document.readyState === 'complete') { - resolve(); - } else { - const onReadyStateChange = () => { - if (window.document.readyState === 'complete') { - resolve(); - window.document.removeEventListener('readystatechange', onReadyStateChange); - } - }; - window.document.addEventListener('readystatechange', onReadyStateChange); - } - }); - - return lazyLoad(documentReadyPromise, initialiseFunction); -}; diff --git a/packages/passport/sdk/tsconfig.json b/packages/passport/sdk/tsconfig.json index 34907d4105..14daed2fa4 100644 --- a/packages/passport/sdk/tsconfig.json +++ b/packages/passport/sdk/tsconfig.json @@ -1,26 +1,13 @@ { + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": true, - "resolveJsonModule": true, - "skipLibCheck": true + "customConditions": ["development"] }, "include": ["src", "src/types.ts"], "exclude": [ "node_modules", "dist", - "src/modules/provider/sample-app", - "src/modules/checkout" ] } diff --git a/packages/webhook/sdk/.eslintrc b/packages/webhook/sdk/.eslintrc.cjs similarity index 79% rename from packages/webhook/sdk/.eslintrc rename to packages/webhook/sdk/.eslintrc.cjs index f77729693e..3193df85c8 100644 --- a/packages/webhook/sdk/.eslintrc +++ b/packages/webhook/sdk/.eslintrc.cjs @@ -1,9 +1,9 @@ -{ +module.exports = { "extends": ["../../../.eslintrc"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname }, "rules": { "@typescript-eslint/comma-dangle": "off" diff --git a/packages/webhook/sdk/jest.config.ts b/packages/webhook/sdk/jest.config.ts index c64fb9b6e6..05ef77a17a 100644 --- a/packages/webhook/sdk/jest.config.ts +++ b/packages/webhook/sdk/jest.config.ts @@ -4,10 +4,7 @@ const config: Config = { clearMocks: true, coverageProvider: 'v8', moduleDirectories: ['node_modules', 'src'], - moduleNameMapper: { - '@imtbl/config': '../../config/src', - '@imtbl/generated-clients': '../../internal/generated-clients/src' - }, + moduleNameMapper: { '^@imtbl/(.*)$': '/../../../node_modules/@imtbl/$1/src' }, testEnvironment: 'node', transform: { '^.+\\.(t|j)sx?$': '@swc/jest', diff --git a/packages/webhook/sdk/package.json b/packages/webhook/sdk/package.json index 17a0b5ac42..63fd7fda9e 100644 --- a/packages/webhook/sdk/package.json +++ b/packages/webhook/sdk/package.json @@ -23,7 +23,20 @@ "rollup": "^4.19.1", "ts-mockito": "^2.6.1", "typechain": "^8.1.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" + }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } }, "files": [ "dist" @@ -35,13 +48,12 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "generate-types": "typechain --target=ethers-v5 --out-dir=src/typechain/types 'abi/*.json'", "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" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/webhook/sdk/rollup.config.js b/packages/webhook/sdk/rollup.config.js index 16a7d58d7e..0269dcdc15 100644 --- a/packages/webhook/sdk/rollup.config.js +++ b/packages/webhook/sdk/rollup.config.js @@ -1,4 +1,7 @@ import typescript from '@rollup/plugin-typescript'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: 'src/index.ts', @@ -6,5 +9,5 @@ export default { dir: 'dist', format: 'es', }, - plugins: [typescript()], + plugins: [isProduction ? typescript({customConditions: ["default"]}) : swc.rollup()], }; diff --git a/packages/webhook/sdk/src/index.ts b/packages/webhook/sdk/src/index.ts index d1149bdc6b..7943f8d197 100644 --- a/packages/webhook/sdk/src/index.ts +++ b/packages/webhook/sdk/src/index.ts @@ -19,6 +19,6 @@ export type { } from './event-types'; export { - handle, - WebhookHandlers + handle }; +export type { WebhookHandlers }; diff --git a/packages/webhook/sdk/tsconfig.json b/packages/webhook/sdk/tsconfig.json index 56c27e347a..5184ceffeb 100644 --- a/packages/webhook/sdk/tsconfig.json +++ b/packages/webhook/sdk/tsconfig.json @@ -1,20 +1,9 @@ { + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": true, - "resolveJsonModule": true, - "skipLibCheck": true + "customConditions": ["development"] }, "include": ["src"], "exclude": ["node_modules", "dist"] diff --git a/packages/checkout/.eslintrc b/packages/x-client/.eslintrc.cjs similarity index 72% rename from packages/checkout/.eslintrc rename to packages/x-client/.eslintrc.cjs index f90c594b06..8b8a821f7e 100644 --- a/packages/checkout/.eslintrc +++ b/packages/x-client/.eslintrc.cjs @@ -1,8 +1,8 @@ -{ +module.exports = { "extends": ["../../.eslintrc"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname } } diff --git a/packages/x-client/jest.config.cjs b/packages/x-client/jest.config.cjs index 18296f9e01..96f065a123 100644 --- a/packages/x-client/jest.config.cjs +++ b/packages/x-client/jest.config.cjs @@ -2,6 +2,7 @@ module.exports = { testEnvironment: 'node', moduleDirectories: ['node_modules', '/src'], modulePathIgnorePatterns: ['/dist/', '/backup/'], + moduleNameMapper: { '^@imtbl/(.*)$': '/../../node_modules/@imtbl/$1/src' }, testRegex: '^.+\\.test\\.(js|ts|jsx|tsx)$', testPathIgnorePatterns: [ '/node_modules/' diff --git a/packages/x-client/package.json b/packages/x-client/package.json index e78b0e45aa..b300e2efdd 100644 --- a/packages/x-client/package.json +++ b/packages/x-client/package.json @@ -15,24 +15,38 @@ "@imtbl/generated-clients": "0.0.0", "axios": "^1.6.5", "bn.js": "^5.2.1", - "elliptic": "^6.5.4", + "elliptic": "^6.5.7", "enc-utils": "^3.0.0", "ethereumjs-wallet": "^1.0.2", "ethers": "^5.7.2" }, "devDependencies": { "@rollup/plugin-typescript": "^11.1.6", + "@swc/core": "^1.3.36", "@swc/jest": "^0.2.24", "@types/jest": "^29.4.3", "eslint": "^8.40.0", "jest": "^29.4.3", "jest-environment-jsdom": "^29.4.3", "rollup": "^4.19.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" }, "engines": { "node": ">=20.11.0" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } + }, "files": [ "dist" ], @@ -43,11 +57,10 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", "test": "jest", "typecheck": "tsc --noEmit --jsx preserve" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/x-client/rollup.config.js b/packages/x-client/rollup.config.js index 16a7d58d7e..0269dcdc15 100644 --- a/packages/x-client/rollup.config.js +++ b/packages/x-client/rollup.config.js @@ -1,4 +1,7 @@ import typescript from '@rollup/plugin-typescript'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: 'src/index.ts', @@ -6,5 +9,5 @@ export default { dir: 'dist', format: 'es', }, - plugins: [typescript()], + plugins: [isProduction ? typescript({customConditions: ["default"]}) : swc.rollup()], }; diff --git a/packages/x-client/src/IMXClient.ts b/packages/x-client/src/IMXClient.ts index 85971ccd09..cdef8f66f3 100644 --- a/packages/x-client/src/IMXClient.ts +++ b/packages/x-client/src/IMXClient.ts @@ -4,65 +4,108 @@ import { ImxModuleConfiguration, } from './config'; import { formatError } from './utils/formatError'; -import { +import type { AddMetadataSchemaToCollectionRequest, + Asset, AssetsApi, AssetsApiGetAssetRequest, AssetsApiListAssetsRequest, + Balance, BalancesApi, BalancesApiGetBalanceRequest, BalancesApiListBalancesRequest, + Collection, + CollectionFilter, CollectionsApi, CollectionsApiGetCollectionRequest, CollectionsApiListCollectionFiltersRequest, CollectionsApiListCollectionsRequest, CreateCollectionRequest, CreateMetadataRefreshRequest, + CreateMetadataRefreshResponse, + CreateTransferResponseV1, + CurrencyWithLimits, + Deposit, DepositsApi, DepositsApiGetDepositRequest, DepositsApiListDepositsRequest, EncodingApi, EthSigner, + Exchange, + ExchangeCreateExchangeAndURLResponse, ExchangesApi, ExchangesApiCreateExchangeRequest, ExchangesApiGetExchangeRequest, ExchangesApiGetExchangesRequest, - MintsApi, - MintsApiGetMintRequest, - MintsApiListMintsRequest, + GetMetadataRefreshErrorsResponse, + GetMetadataRefreshes, + GetMetadataRefreshResponse, + GetTransactionsResponse, + GetUsersApiResponse, + ListAssetsResponse, + ListBalancesResponse, + ListCollectionsResponse, + ListDepositsResponse, + ListMintsResponse, + ListOrdersResponseV3, + ListTokensResponse, + ListTradesResponse, + ListTransfersResponse, + ListWithdrawalsResponse, MetadataApi, MetadataApiGetMetadataSchemaRequest, MetadataRefreshesApi, + MetadataSchemaProperty, MetadataSchemaRequest, + Mint, + MintsApi, + MintsApiGetMintRequest, + MintsApiListMintsRequest, + MintTokensResponse, NftCheckoutPrimaryApi, NftCheckoutPrimaryApiCreateNftPrimaryRequest, NftCheckoutPrimaryApiGetCurrenciesNFTCheckoutPrimaryRequest, NftCheckoutPrimaryApiGetNftPrimaryTransactionRequest, NftCheckoutPrimaryApiGetNftPrimaryTransactionsRequest, + NftprimarytransactionCreateResponse, + NftprimarytransactionGetResponse, + NftprimarytransactionListTransactionsResponse, OrdersApi, OrdersApiGetOrderV3Request, OrdersApiListOrdersV3Request, + OrderV3, PrimarySalesApi, PrimarySalesApiSignableCreatePrimarySaleRequest, + Project, ProjectsApi, + SuccessResponse, + TokenDetails, TokensApi, TokensApiGetTokenRequest, TokensApiListTokensRequest, + Trade, TradesApi, TradesApiGetTradeV3Request, TradesApiListTradesV3Request, + Transfer, TransfersApi, TransfersApiGetTransferRequest, TransfersApiListTransfersRequest, - UpdateCollectionRequest, - UnsignedMintRequest, - WalletConnection, UnsignedExchangeTransferRequest, + UnsignedMintRequest, + UpdateCollectionRequest, UsersApi, + WalletConnection, + Withdrawal, WithdrawalsApi, WithdrawalsApiGetWithdrawalRequest, WithdrawalsApiListWithdrawalsRequest, } from './types'; +import type { + AcceptPrimarySaleResponse, + CreatePrimarySaleResponse, + RejectPrimarySaleResponse, +} from './workflows'; import { Workflows } from './workflows'; export class IMXClient { @@ -147,7 +190,7 @@ export class IMXClient { * @returns a promise that resolves with the requested Deposit * @throws {@link IMXError} */ - public getDeposit(request: DepositsApiGetDepositRequest) { + public getDeposit(request: DepositsApiGetDepositRequest): Promise { return this.depositsApi .getDeposit(request) .then((res) => res.data) @@ -162,7 +205,7 @@ export class IMXClient { * @returns a promise that resolves with the requested list of Deposits * @throws {@link IMXError} */ - public listDeposits(request?: DepositsApiListDepositsRequest) { + public listDeposits(request?: DepositsApiListDepositsRequest): Promise { return this.depositsApi .listDeposits(request) .then((res) => res.data) @@ -177,7 +220,7 @@ export class IMXClient { * @returns a promise that resolves with the requested User * @throws {@link IMXError} */ - public getUser(ethAddress: string) { + public getUser(ethAddress: string): Promise { return this.usersApi .getUsers({ user: ethAddress }) .then((res) => res.data) @@ -192,7 +235,7 @@ export class IMXClient { * @returns a promise that resolves with the requested Asset * @throws {@link IMXError} */ - public getAsset(request: AssetsApiGetAssetRequest) { + public getAsset(request: AssetsApiGetAssetRequest): Promise { return this.assetApi .getAsset(request) .then((res) => res.data) @@ -207,7 +250,7 @@ export class IMXClient { * @returns a promise that resolves with the requested list of Assets * @throws {@link IMXError} */ - public listAssets(request?: AssetsApiListAssetsRequest) { + public listAssets(request?: AssetsApiListAssetsRequest): Promise { return this.assetApi .listAssets(request) .then((res) => res.data) @@ -226,7 +269,7 @@ export class IMXClient { public createCollection( ethSigner: EthSigner, request: CreateCollectionRequest, - ) { + ): Promise { return this.workflows .createCollection(ethSigner, request) .then((res) => res.data) @@ -241,7 +284,7 @@ export class IMXClient { * @returns a promise that resolves with the requested Collection * @throws {@link IMXError} */ - public getCollection(request: CollectionsApiGetCollectionRequest) { + public getCollection(request: CollectionsApiGetCollectionRequest): Promise { return this.collectionApi .getCollection(request) .then((res) => res.data) @@ -258,7 +301,7 @@ export class IMXClient { */ public listCollectionFilters( request: CollectionsApiListCollectionFiltersRequest, - ) { + ): Promise { return this.collectionApi .listCollectionFilters(request) .then((res) => res.data) @@ -273,7 +316,9 @@ export class IMXClient { * @returns a promise that resolves with the requested list of Collections * @throws {@link IMXError} */ - public listCollections(request?: CollectionsApiListCollectionsRequest) { + public listCollections( + request?: CollectionsApiListCollectionsRequest, + ): Promise { return this.collectionApi .listCollections(request) .then((res) => res.data) @@ -294,7 +339,7 @@ export class IMXClient { ethSigner: EthSigner, collectionAddress: string, request: UpdateCollectionRequest, - ) { + ): Promise { return this.workflows .updateCollection(ethSigner, collectionAddress, request) .then((res) => res.data) @@ -315,7 +360,7 @@ export class IMXClient { ethSigner: EthSigner, collectionAddress: string, request: AddMetadataSchemaToCollectionRequest, - ) { + ): Promise { return this.workflows .addMetadataSchemaToCollection(ethSigner, collectionAddress, request) .then((res) => res.data) @@ -330,7 +375,9 @@ export class IMXClient { * @returns a promise that resolves with the requested Metadata schema * @throws {@link IMXError} */ - public getMetadataSchema(request: MetadataApiGetMetadataSchemaRequest) { + public getMetadataSchema( + request: MetadataApiGetMetadataSchemaRequest, + ): Promise { return this.metadataApi .getMetadataSchema(request) .then((res) => res.data) @@ -345,7 +392,7 @@ export class IMXClient { * @param collectionAddress - the Collection contract address * @param name - the Metadata schema name * @param request - the request object containing the parameters to be provided in the API request - * @returns a promise that resolves with the SuccessResponse if successful + * @returns a promise that resolves with the {@link SuccessResponse} * @throws {@link IMXError} */ public updateMetadataSchemaByName( @@ -353,7 +400,7 @@ export class IMXClient { collectionAddress: string, name: string, request: MetadataSchemaRequest, - ) { + ): Promise { return this.workflows .updateMetadataSchemaByName(ethSigner, collectionAddress, name, request) .then((res) => res.data) @@ -376,7 +423,7 @@ export class IMXClient { collectionAddress?: string, pageSize?: number, cursor?: string, - ) { + ): Promise { return this.workflows .listMetadataRefreshes(ethSigner, collectionAddress, pageSize, cursor) .then((res) => res.data) @@ -399,7 +446,7 @@ export class IMXClient { refreshId: string, pageSize?: number, cursor?: string, - ) { + ): Promise { return this.workflows .getMetadataRefreshErrors(ethSigner, refreshId, pageSize, cursor) .then((res) => res.data) @@ -415,7 +462,10 @@ export class IMXClient { * @returns a promise that resolves with the requested metadata refresh results * @throws {@link IMXError} */ - public getMetadataRefreshResults(ethSigner: EthSigner, refreshId: string) { + public getMetadataRefreshResults( + ethSigner: EthSigner, + refreshId: string, + ): Promise { return this.workflows .getMetadataRefreshResults(ethSigner, refreshId) .then((res) => res.data) @@ -434,7 +484,7 @@ export class IMXClient { public createMetadataRefresh( ethSigner: EthSigner, request: CreateMetadataRefreshRequest, - ) { + ): Promise { return this.workflows .createMetadataRefresh(ethSigner, request) .then((res) => res.data) @@ -450,7 +500,7 @@ export class IMXClient { * @returns a promise that resolves with the requested Project * @throws {@link IMXError} */ - public async getProject(ethSigner: EthSigner, id: string) { + public async getProject(ethSigner: EthSigner, id: string): Promise { return this.workflows .getProject(ethSigner, id) .then((res) => res.data) @@ -465,7 +515,7 @@ export class IMXClient { * @returns a promise that resolves with the requested Balance * @throws {@link IMXError} */ - public getBalance(request: BalancesApiGetBalanceRequest) { + public getBalance(request: BalancesApiGetBalanceRequest): Promise { return this.balanceApi .getBalance(request) .then((res) => res.data) @@ -480,7 +530,9 @@ export class IMXClient { * @returns a promise that resolves with the requested list of Balances * @throws {@link IMXError} */ - public listBalances(request: BalancesApiListBalancesRequest) { + public listBalances( + request: BalancesApiListBalancesRequest, + ): Promise { return this.balanceApi .listBalances(request) .then((res) => res.data) @@ -495,7 +547,7 @@ export class IMXClient { * @returns a promise that resolves with the requested Mint * @throws {@link IMXError} */ - public getMint(request: MintsApiGetMintRequest) { + public getMint(request: MintsApiGetMintRequest): Promise { return this.mintsApi .getMint(request) .then((res) => res.data) @@ -510,7 +562,7 @@ export class IMXClient { * @returns a promise that resolves with the requested list of Mints * @throws {@link IMXError} */ - public listMints(request?: MintsApiListMintsRequest) { + public listMints(request?: MintsApiListMintsRequest): Promise { return this.mintsApi .listMints(request) .then((res) => res.data) @@ -526,7 +578,10 @@ export class IMXClient { * @returns a promise that resolves with the minted tokens * @throws {@link IMXError} */ - public mint(ethSigner: EthSigner, request: UnsignedMintRequest) { + public mint( + ethSigner: EthSigner, + request: UnsignedMintRequest, + ): Promise { return this.workflows.mint(ethSigner, request); } @@ -536,7 +591,9 @@ export class IMXClient { * @returns a promise that resolves with the requested list of Withdrawals * @throws {@link IMXError} */ - public listWithdrawals(request?: WithdrawalsApiListWithdrawalsRequest) { + public listWithdrawals( + request?: WithdrawalsApiListWithdrawalsRequest, + ): Promise { return this.withdrawalsApi .listWithdrawals(request) .then((res) => res.data) @@ -551,7 +608,7 @@ export class IMXClient { * @returns a promise that resolves with the requested Withdrawal * @throws {@link IMXError} */ - public getWithdrawal(request: WithdrawalsApiGetWithdrawalRequest) { + public getWithdrawal(request: WithdrawalsApiGetWithdrawalRequest): Promise { return this.withdrawalsApi .getWithdrawal(request) .then((res) => res.data) @@ -566,7 +623,7 @@ export class IMXClient { * @returns a promise that resolves with the requested Order * @throws {@link IMXError} */ - public getOrder(request: OrdersApiGetOrderV3Request) { + public getOrder(request: OrdersApiGetOrderV3Request): Promise { return this.ordersApi .getOrderV3(request) .then((res) => res.data) @@ -581,7 +638,7 @@ export class IMXClient { * @returns a promise that resolves with the requested list of Orders * @throws {@link IMXError} */ - public listOrders(request?: OrdersApiListOrdersV3Request) { + public listOrders(request?: OrdersApiListOrdersV3Request): Promise { return this.ordersApi .listOrdersV3(request) .then((res) => res.data) @@ -596,7 +653,7 @@ export class IMXClient { * @returns a promise that resolves with the requested Trade * @throws {@link IMXError} */ - public getTrade(request: TradesApiGetTradeV3Request) { + public getTrade(request: TradesApiGetTradeV3Request): Promise { return this.tradesApi .getTradeV3(request) .then((res) => res.data) @@ -611,7 +668,7 @@ export class IMXClient { * @returns a promise that resolves with the requested list of Trades * @throws {@link IMXError} */ - public listTrades(request?: TradesApiListTradesV3Request) { + public listTrades(request?: TradesApiListTradesV3Request): Promise { return this.tradesApi .listTradesV3(request) .then((res) => res.data) @@ -626,7 +683,7 @@ export class IMXClient { * @returns a promise that resolves with the requested Token * @throws {@link IMXError} */ - public getToken(request: TokensApiGetTokenRequest) { + public getToken(request: TokensApiGetTokenRequest): Promise { return this.tokensApi .getToken(request) .then((res) => res.data) @@ -641,7 +698,7 @@ export class IMXClient { * @returns a promise that resolves with the requested list of Tokens * @throws {@link IMXError} */ - public listTokens(request?: TokensApiListTokensRequest) { + public listTokens(request?: TokensApiListTokensRequest): Promise { return this.tokensApi .listTokens(request) .then((res) => res.data) @@ -656,7 +713,7 @@ export class IMXClient { * @returns a promise that resolves with the requested Transfer * @throws {@link IMXError} */ - public getTransfer(request: TransfersApiGetTransferRequest) { + public getTransfer(request: TransfersApiGetTransferRequest): Promise { return this.transfersApi .getTransfer(request) .then((res) => res.data) @@ -671,7 +728,9 @@ export class IMXClient { * @returns a promise that resolves with the requested list of Transfers * @throws {@link IMXError} */ - public listTransfers(request?: TransfersApiListTransfersRequest) { + public listTransfers( + request?: TransfersApiListTransfersRequest, + ): Promise { return this.transfersApi .listTransfers(request) .then((res) => res.data) @@ -686,7 +745,9 @@ export class IMXClient { * @returns a promise that resolves with the created Exchange Transaction * @throws {@link IMXError} */ - public createExchange(request: ExchangesApiCreateExchangeRequest) { + public createExchange( + request: ExchangesApiCreateExchangeRequest, + ): Promise { return this.exchangeApi.createExchange(request) .then((res) => res.data) .catch((err) => { @@ -700,7 +761,7 @@ export class IMXClient { * @returns a promise that resolves with the Exchange Transaction * @throws {@link IMXError} */ - public getExchange(request: ExchangesApiGetExchangeRequest) { + public getExchange(request: ExchangesApiGetExchangeRequest): Promise { return this.exchangeApi.getExchange(request) .then((res) => res.data) .catch((err) => { @@ -714,7 +775,7 @@ export class IMXClient { * @returns a promise that resolves with Exchange Transactions * @throws {@link IMXError} */ - public getExchanges(request: ExchangesApiGetExchangesRequest) { + public getExchanges(request: ExchangesApiGetExchangesRequest): Promise { return this.exchangeApi.getExchanges(request) .then((res) => res.data) .catch((err) => { @@ -732,7 +793,7 @@ export class IMXClient { public exchangeTransfer( walletConnection: WalletConnection, request: UnsignedExchangeTransferRequest, - ) { + ): Promise { return this.workflows.exchangeTransfer(walletConnection, request); } @@ -744,7 +805,7 @@ export class IMXClient { */ public createNftPrimary( request: NftCheckoutPrimaryApiCreateNftPrimaryRequest, - ) { + ): Promise { return this.nftCheckoutPrimaryApi.createNftPrimary(request) .then((res) => res.data) .catch((err) => { @@ -760,7 +821,7 @@ export class IMXClient { */ public getCurrenciesNFTCheckoutPrimary( request: NftCheckoutPrimaryApiGetCurrenciesNFTCheckoutPrimaryRequest, - ) { + ): Promise { return this.nftCheckoutPrimaryApi .getCurrenciesNFTCheckoutPrimary(request) .then((res) => res.data) @@ -777,7 +838,7 @@ export class IMXClient { */ public getNftPrimaryTransaction( request: NftCheckoutPrimaryApiGetNftPrimaryTransactionRequest, - ) { + ): Promise { return this.nftCheckoutPrimaryApi .getNftPrimaryTransaction(request) .then((res) => res.data) @@ -794,7 +855,7 @@ export class IMXClient { */ public getNftPrimaryTransactions( request: NftCheckoutPrimaryApiGetNftPrimaryTransactionsRequest, - ) { + ): Promise { return this.nftCheckoutPrimaryApi .getNftPrimaryTransactions(request) .then((res) => res.data) @@ -813,7 +874,7 @@ export class IMXClient { public createPrimarySale( walletConnection: WalletConnection, request: PrimarySalesApiSignableCreatePrimarySaleRequest, - ) { + ): Promise { return this.workflows .createPrimarySale(walletConnection, request) .catch((err) => { @@ -828,7 +889,10 @@ export class IMXClient { * @returns a promise that resolves with the created Trade * @throws {@link IMXError} */ - public acceptPrimarySale(ethSigner: EthSigner, primarySaleId: number) { + public acceptPrimarySale( + ethSigner: EthSigner, + primarySaleId: number, + ): Promise { return this.workflows .acceptPrimarySale(ethSigner, primarySaleId) .catch((err) => { @@ -843,7 +907,10 @@ export class IMXClient { * @returns a promise that resolves with the rejected PrimarySale * @throws {@link IMXError} */ - public rejectPrimarySale(ethSigner: EthSigner, primarySaleId: number) { + public rejectPrimarySale( + ethSigner: EthSigner, + primarySaleId: number, + ): Promise { return this.workflows .rejectPrimarySale(ethSigner, primarySaleId) .catch((err) => { diff --git a/packages/x-client/src/index.ts b/packages/x-client/src/index.ts index 54dffc49bd..f484a40791 100644 --- a/packages/x-client/src/index.ts +++ b/packages/x-client/src/index.ts @@ -6,9 +6,9 @@ export * from './types'; /** * aliased exports to maintain backwards compatibility */ -export { ImxModuleConfiguration as ImxClientModuleConfiguration } from './config'; +export type { ImxModuleConfiguration as ImxClientModuleConfiguration } from './config'; export { generateLegacyStarkPrivateKey as imxClientGenerateLegacyStarkPrivateKey, createStarkSigner as imxClientCreateStarkSigner, } from './exportUtils'; -export { WalletConnection as ImxClientWalletConnection } from './types'; +export type { WalletConnection as ImxClientWalletConnection } from './types'; diff --git a/packages/x-client/src/types/api.ts b/packages/x-client/src/types/api.ts index 9509146795..2c7a7eecad 100644 --- a/packages/x-client/src/types/api.ts +++ b/packages/x-client/src/types/api.ts @@ -2,7 +2,7 @@ /* eslint-disable max-len */ import { imx } from '@imtbl/generated-clients'; -export { TransactionResponse } from '@ethersproject/providers'; +export type { TransactionResponse } from '@ethersproject/providers'; /** * Need to specifically export the classes and interfaces from the generated @@ -28,24 +28,28 @@ export class TransfersApi extends imx.TransfersApi {} export class UsersApi extends imx.UsersApi {} export class WithdrawalsApi extends imx.WithdrawalsApi {} -export interface APIError extends imx.APIError {} export interface AcceptPrimarySaleBadRequestBody extends imx.AcceptPrimarySaleBadRequestBody {} export interface AcceptPrimarySaleForbiddenBody extends imx.AcceptPrimarySaleForbiddenBody {} export interface AcceptPrimarySaleNotFoundBody extends imx.AcceptPrimarySaleNotFoundBody {} export interface AcceptPrimarySaleOKBody extends imx.AcceptPrimarySaleOKBody {} export interface AcceptPrimarySaleUnauthorizedBody extends imx.AcceptPrimarySaleUnauthorizedBody {} export interface AddMetadataSchemaToCollectionRequest extends imx.AddMetadataSchemaToCollectionRequest {} +export interface APIError extends imx.APIError {} +export interface Asset extends imx.Asset {} export interface AssetsApiGetAssetRequest extends imx.AssetsApiGetAssetRequest {} export interface AssetsApiListAssetsRequest extends imx.AssetsApiListAssetsRequest {} export interface Balance extends imx.Balance {} export interface BalancesApiGetBalanceRequest extends imx.BalancesApiGetBalanceRequest {} export interface BalancesApiListBalancesRequest extends imx.BalancesApiListBalancesRequest {} export interface CancelOrderResponse extends imx.CancelOrderResponse {} +export interface Collection extends imx.Collection {} +export interface CollectionFilter extends imx.CollectionFilter {} export interface CollectionsApiGetCollectionRequest extends imx.CollectionsApiGetCollectionRequest {} export interface CollectionsApiListCollectionFiltersRequest extends imx.CollectionsApiListCollectionFiltersRequest {} export interface CollectionsApiListCollectionsRequest extends imx.CollectionsApiListCollectionsRequest {} export interface CreateCollectionRequest extends imx.CreateCollectionRequest {} export interface CreateMetadataRefreshRequest extends imx.CreateMetadataRefreshRequest {} +export interface CreateMetadataRefreshResponse extends imx.CreateMetadataRefreshResponse {} export interface CreateOrderResponse extends imx.CreateOrderResponse {} export interface CreatePrimarySaleBadRequestBody extends imx.CreatePrimarySaleBadRequestBody {} export interface CreatePrimarySaleCreatedBody extends imx.CreatePrimarySaleCreatedBody {} @@ -55,48 +59,86 @@ export interface CreatePrimarySaleUnauthorizedBody extends imx.CreatePrimarySale export interface CreateTradeResponse extends imx.CreateTradeResponse {} export interface CreateTransferResponseV1 extends imx.CreateTransferResponseV1 {} export interface CreateWithdrawalResponse extends imx.CreateWithdrawalResponse {} +export interface CurrencyWithLimits extends imx.CurrencyWithLimits {} +export interface Deposit extends imx.Deposit {} export interface DepositsApiGetDepositRequest extends imx.DepositsApiGetDepositRequest {} export interface DepositsApiListDepositsRequest extends imx.DepositsApiListDepositsRequest {} +export interface Exchange extends imx.Exchange {} +export interface ExchangeCreateExchangeAndURLResponse extends imx.ExchangeCreateExchangeAndURLResponse {} export interface ExchangesApiCreateExchangeRequest extends imx.ExchangesApiCreateExchangeRequest {} export interface ExchangesApiGetExchangeRequest extends imx.ExchangesApiGetExchangeRequest {} export interface ExchangesApiGetExchangesRequest extends imx.ExchangesApiGetExchangesRequest {} +export interface GetMetadataRefreshes extends imx.GetMetadataRefreshes {} +export interface GetMetadataRefreshErrorsResponse extends imx.GetMetadataRefreshErrorsResponse {} +export interface GetMetadataRefreshResponse extends imx.GetMetadataRefreshResponse {} export interface GetSignableCancelOrderRequest extends imx.GetSignableCancelOrderRequest {} export interface GetSignableOrderRequest extends imx.GetSignableOrderRequest {} export interface GetSignableTradeRequest extends imx.GetSignableTradeRequest {} +export interface GetTransactionsResponse extends imx.GetTransactionsResponse {} +export interface GetUsersApiResponse extends imx.GetUsersApiResponse {} +export interface ListAssetsResponse extends imx.ListAssetsResponse {} +export interface ListBalancesResponse extends imx.ListBalancesResponse {} +export interface ListCollectionsResponse extends imx.ListCollectionsResponse {} +export interface ListDepositsResponse extends imx.ListDepositsResponse {} +export interface ListMintsResponse extends imx.ListMintsResponse {} +export interface ListOrdersResponseV3 extends imx.ListOrdersResponseV3 {} +export interface ListTokensResponse extends imx.ListTokensResponse {} +export interface ListTradesResponse extends imx.ListTradesResponse {} +export interface ListTransfersResponse extends imx.ListTransfersResponse {} +export interface ListWithdrawalsResponse extends imx.ListWithdrawalsResponse {} export interface MetadataApiGetMetadataSchemaRequest extends imx.MetadataApiGetMetadataSchemaRequest {} +export interface MetadataSchemaProperty extends imx.MetadataSchemaProperty {} export interface MetadataSchemaRequest extends imx.MetadataSchemaRequest {} +export interface Mint extends imx.Mint {} export interface MintFee extends imx.MintFee {} -export interface MintResultDetails extends imx.MintResultDetails {} export interface MintRequest extends imx.MintRequest {} -export interface MintTokenDataV2 extends imx.MintTokenDataV2 {} -export interface MintTokensResponse extends imx.MintTokensResponse {} -export interface MintUser extends imx.MintUser {} +export interface MintResultDetails extends imx.MintResultDetails {} export interface MintsApiGetMintRequest extends imx.MintsApiGetMintRequest {} export interface MintsApiListMintsRequest extends imx.MintsApiListMintsRequest {} export interface MintsApiMintTokensRequest extends imx.MintsApiMintTokensRequest {} +export interface MintTokenDataV2 extends imx.MintTokenDataV2 {} +export interface MintTokensResponse extends imx.MintTokensResponse {} +export interface MintUser extends imx.MintUser {} export interface NftCheckoutPrimaryApiCreateNftPrimaryRequest extends imx.NftCheckoutPrimaryApiCreateNftPrimaryRequest {} export interface NftCheckoutPrimaryApiGetCurrenciesNFTCheckoutPrimaryRequest extends imx.NftCheckoutPrimaryApiGetCurrenciesNFTCheckoutPrimaryRequest {} export interface NftCheckoutPrimaryApiGetNftPrimaryTransactionRequest extends imx.NftCheckoutPrimaryApiGetNftPrimaryTransactionRequest {} export interface NftCheckoutPrimaryApiGetNftPrimaryTransactionsRequest extends imx.NftCheckoutPrimaryApiGetNftPrimaryTransactionsRequest {} +export interface NftprimarytransactionCreateResponse extends imx.NftprimarytransactionCreateResponse {} +export interface NftprimarytransactionGetResponse extends imx.NftprimarytransactionGetResponse {} +export interface NftprimarytransactionListTransactionsResponse extends imx.NftprimarytransactionListTransactionsResponse {} export interface OrdersApiCreateOrderV3Request extends imx.OrdersApiCreateOrderV3Request {} export interface OrdersApiGetOrderV3Request extends imx.OrdersApiGetOrderV3Request {} export interface OrdersApiListOrdersV3Request extends imx.OrdersApiListOrdersV3Request {} +export interface OrderV3 extends imx.OrderV3 {} export interface PrimarySalesApiCreatePrimarySaleRequest extends imx.PrimarySalesApiCreatePrimarySaleRequest {} export interface PrimarySalesApiSignableCreatePrimarySaleRequest extends imx.PrimarySalesApiSignableCreatePrimarySaleRequest {} +export interface Project extends imx.Project {} export interface RejectPrimarySaleBadRequestBody extends imx.RejectPrimarySaleBadRequestBody {} export interface RejectPrimarySaleForbiddenBody extends imx.RejectPrimarySaleForbiddenBody {} export interface RejectPrimarySaleNotFoundBody extends imx.RejectPrimarySaleNotFoundBody {} export interface RejectPrimarySaleOKBody extends imx.RejectPrimarySaleOKBody {} export interface RejectPrimarySaleUnauthorizedBody extends imx.RejectPrimarySaleUnauthorizedBody {} export interface SignableToken extends imx.SignableToken {} +export interface SuccessResponse extends imx.SuccessResponse {} +export interface TokenDetails extends imx.TokenDetails {} export interface TokensApiGetTokenRequest extends imx.TokensApiGetTokenRequest {} export interface TokensApiListTokensRequest extends imx.TokensApiListTokensRequest {} +export interface Trade extends imx.Trade {} export interface TradesApiGetTradeV3Request extends imx.TradesApiGetTradeV3Request {} export interface TradesApiListTradesV3Request extends imx.TradesApiListTradesV3Request {} +export interface Transfer extends imx.Transfer {} export interface TransfersApiGetTransferRequest extends imx.TransfersApiGetTransferRequest {} export interface TransfersApiListTransfersRequest extends imx.TransfersApiListTransfersRequest {} export interface UpdateCollectionRequest extends imx.UpdateCollectionRequest {} +export interface Withdrawal extends imx.Withdrawal {} export interface WithdrawalsApiGetWithdrawalRequest extends imx.WithdrawalsApiGetWithdrawalRequest {} export interface WithdrawalsApiListWithdrawalsRequest extends imx.WithdrawalsApiListWithdrawalsRequest {} -export const { MetadataSchemaRequestTypeEnum } = imx; +// eslint-disable-next-line prefer-destructuring +export const MetadataSchemaRequestTypeEnum: { + readonly Enum: 'enum'; + readonly Text: 'text'; + readonly Boolean: 'boolean'; + readonly Continuous: 'continuous'; + readonly Discrete: 'discrete'; +} = imx.MetadataSchemaRequestTypeEnum; diff --git a/packages/x-client/src/workflows/primarySales.ts b/packages/x-client/src/workflows/primarySales.ts index 406dfcf52c..c9c826ac2a 100644 --- a/packages/x-client/src/workflows/primarySales.ts +++ b/packages/x-client/src/workflows/primarySales.ts @@ -39,21 +39,21 @@ type RejectPrimarySaleWorkflowParams = { primarySalesApi: PrimarySalesApi; }; -type CreatePrimarySaleResponse = +export type CreatePrimarySaleResponse = | CreatePrimarySaleBadRequestBody | CreatePrimarySaleCreatedBody | CreatePrimarySaleForbiddenBody | CreatePrimarySaleUnauthorizedBody | CreatePrimarySaleNotFoundBody; -type AcceptPrimarySaleResponse = +export type AcceptPrimarySaleResponse = | AcceptPrimarySaleOKBody | AcceptPrimarySaleBadRequestBody | AcceptPrimarySaleForbiddenBody | AcceptPrimarySaleNotFoundBody | AcceptPrimarySaleUnauthorizedBody; -type RejectPrimarySaleResponse = +export type RejectPrimarySaleResponse = | RejectPrimarySaleOKBody | RejectPrimarySaleBadRequestBody | RejectPrimarySaleForbiddenBody diff --git a/packages/x-client/src/workflows/workflows.ts b/packages/x-client/src/workflows/workflows.ts index f7ea821e70..800f8be819 100644 --- a/packages/x-client/src/workflows/workflows.ts +++ b/packages/x-client/src/workflows/workflows.ts @@ -14,6 +14,13 @@ import { CreateMetadataRefreshRequest, MetadataSchemaRequest, PrimarySalesApiSignableCreatePrimarySaleRequest, + Project, + Collection, + SuccessResponse, + GetMetadataRefreshes, + GetMetadataRefreshErrorsResponse, + GetMetadataRefreshResponse, + CreateMetadataRefreshResponse, } from '../types/api'; import { UnsignedMintRequest, @@ -32,6 +39,12 @@ import { RejectPrimarySalesWorkflow, } from './primarySales'; +export type { + AcceptPrimarySaleResponse, + CreatePrimarySaleResponse, + RejectPrimarySaleResponse, +} from './primarySales'; + export class Workflows { private readonly mintsApi: MintsApi; @@ -107,7 +120,7 @@ export class Workflows { }); } - public async getProject(ethSigner: EthSigner, id: string) { + public async getProject(ethSigner: EthSigner, id: string): Promise> { const imxAuthHeaders = await generateIMXAuthorisationHeaders(ethSigner); return this.projectsApi.getProject({ @@ -120,7 +133,7 @@ export class Workflows { public async createCollection( ethSigner: EthSigner, createCollectionRequest: CreateCollectionRequest, - ) { + ): Promise> { const imxAuthHeaders = await generateIMXAuthorisationHeaders(ethSigner); return this.collectionsApi.createCollection({ @@ -134,7 +147,7 @@ export class Workflows { ethSigner: EthSigner, address: string, updateCollectionRequest: UpdateCollectionRequest, - ) { + ): Promise> { const imxAuthHeaders = await generateIMXAuthorisationHeaders(ethSigner); return this.collectionsApi.updateCollection({ @@ -149,7 +162,7 @@ export class Workflows { ethSigner: EthSigner, address: string, addMetadataSchemaToCollectionRequest: AddMetadataSchemaToCollectionRequest, - ) { + ): Promise> { const imxAuthHeaders = await generateIMXAuthorisationHeaders(ethSigner); return this.metadataApi.addMetadataSchemaToCollection({ @@ -165,7 +178,7 @@ export class Workflows { address: string, name: string, metadataSchemaRequest: MetadataSchemaRequest, - ) { + ): Promise> { const imxAuthHeaders = await generateIMXAuthorisationHeaders(ethSigner); return this.metadataApi.updateMetadataSchemaByName({ @@ -182,7 +195,7 @@ export class Workflows { collectionAddress?: string, pageSize?: number, cursor?: string, - ) { + ): Promise> { const imxAuthHeaders = await generateIMXAuthorisationHeaders(ethSigner); const ethAddress = await ethSigner.getAddress(); @@ -201,7 +214,7 @@ export class Workflows { refreshId: string, pageSize?: number, cursor?: string, - ) { + ): Promise> { const imxAuthHeaders = await generateIMXAuthorisationHeaders(ethSigner); const ethAddress = await ethSigner.getAddress(); @@ -218,7 +231,7 @@ export class Workflows { public async getMetadataRefreshResults( ethSigner: EthSigner, refreshId: string, - ) { + ): Promise> { const imxAuthHeaders = await generateIMXAuthorisationHeaders(ethSigner); const ethAddress = await ethSigner.getAddress(); @@ -233,7 +246,7 @@ export class Workflows { public async createMetadataRefresh( ethSigner: EthSigner, request: CreateMetadataRefreshRequest, - ) { + ): Promise> { const imxAuthHeaders = await generateIMXAuthorisationHeaders(ethSigner); const ethAddress = await ethSigner.getAddress(); diff --git a/packages/x-client/tsconfig.json b/packages/x-client/tsconfig.json index 6a6643584c..212fde17b0 100644 --- a/packages/x-client/tsconfig.json +++ b/packages/x-client/tsconfig.json @@ -1,20 +1,9 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": true, - "resolveJsonModule": true, - "skipLibCheck": true + "customConditions": ["development"] }, "include": ["src"], "exclude": ["node_modules", "dist", "node_modules/elliptic"] diff --git a/packages/x-provider/.eslintrc b/packages/x-provider/.eslintrc.cjs similarity index 86% rename from packages/x-provider/.eslintrc rename to packages/x-provider/.eslintrc.cjs index 214bef5de8..a43b4737b9 100644 --- a/packages/x-provider/.eslintrc +++ b/packages/x-provider/.eslintrc.cjs @@ -1,9 +1,9 @@ -{ +module.exports = { "extends": ["../../.eslintrc"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": "." + "tsconfigRootDir": __dirname }, "rules": { "@typescript-eslint/naming-convention": [ diff --git a/packages/x-provider/jest.config.ts b/packages/x-provider/jest.config.ts index ca6e43f3ac..5a141d28a7 100644 --- a/packages/x-provider/jest.config.ts +++ b/packages/x-provider/jest.config.ts @@ -4,10 +4,7 @@ const config: Config = { clearMocks: true, coverageProvider: 'v8', moduleDirectories: ['node_modules', 'src'], - moduleNameMapper: { - '@imtbl/config': '../config/src', - '@imtbl/toolkit': '../internal/toolkit/src', - }, + moduleNameMapper: { '^@imtbl/(.*)$': '/../../node_modules/@imtbl/$1/src' }, testEnvironment: 'node', transform: { '^.+\\.(t|j)sx?$': '@swc/jest', diff --git a/packages/x-provider/package.json b/packages/x-provider/package.json index cbd9c2ed38..fb5bff6e3a 100644 --- a/packages/x-provider/package.json +++ b/packages/x-provider/package.json @@ -9,7 +9,7 @@ "@imtbl/generated-clients": "0.0.0", "@imtbl/toolkit": "0.0.0", "@imtbl/x-client": "0.0.0", - "@magic-ext/oidc": "4.2.0", + "@magic-ext/oidc": "4.3.1", "@metamask/detect-provider": "^2.0.0", "axios": "^1.6.5", "ethers": "^5.7.2", @@ -33,11 +33,24 @@ "prettier": "^2.8.7", "rollup": "^4.19.1", "ts-node": "^10.9.1", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "unplugin-swc": "^1.5.1" }, "engines": { "node": ">=20.11.0" }, + "exports": { + "development": { + "types": "./src/index.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "import": "./dist/index.js" + } + }, "files": [ "dist" ], @@ -48,13 +61,11 @@ "repository": "immutable/ts-immutable-sdk.git", "scripts": { "build": "NODE_ENV=production rollup --config rollup.config.js", - "d": "swc src -d dist --strip-leading-paths --ignore '**/*.test.*'", + "d": "rollup --config rollup.config.js", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", - "prepare": "wsrun -r build", "test": "jest", "test:watch": "jest --watch", "typecheck": "tsc --noEmit --jsx preserve" }, - "type": "module", - "types": "dist/index.d.ts" + "type": "module" } diff --git a/packages/x-provider/rollup.config.js b/packages/x-provider/rollup.config.js index 16a7d58d7e..0269dcdc15 100644 --- a/packages/x-provider/rollup.config.js +++ b/packages/x-provider/rollup.config.js @@ -1,4 +1,7 @@ import typescript from '@rollup/plugin-typescript'; +import swc from 'unplugin-swc' + +const isProduction = process.env.NODE_ENV === 'production'; export default { input: 'src/index.ts', @@ -6,5 +9,5 @@ export default { dir: 'dist', format: 'es', }, - plugins: [typescript()], + plugins: [isProduction ? typescript({customConditions: ["default"]}) : swc.rollup()], }; diff --git a/packages/x-provider/src/index.ts b/packages/x-provider/src/index.ts index de85e1a08b..99bc94293d 100644 --- a/packages/x-provider/src/index.ts +++ b/packages/x-provider/src/index.ts @@ -1,4 +1,4 @@ -export { IMXProvider } from './imxProvider'; +export type { IMXProvider } from './imxProvider'; export { GenericIMXProvider } from './genericImxProvider'; export { MetaMaskIMXProvider } from './l1-providers/metaMaskWrapper'; export { ProviderConfiguration } from './config'; diff --git a/packages/x-provider/src/sample-app/.env b/packages/x-provider/src/sample-app/.env new file mode 100644 index 0000000000..02269f00d9 --- /dev/null +++ b/packages/x-provider/src/sample-app/.env @@ -0,0 +1 @@ +DISABLE_ESLINT_PLUGIN=true diff --git a/packages/x-provider/src/sample-app/.gitignore b/packages/x-provider/src/sample-app/.gitignore index 0d956b3213..d170858507 100644 --- a/packages/x-provider/src/sample-app/.gitignore +++ b/packages/x-provider/src/sample-app/.gitignore @@ -17,6 +17,7 @@ node_modules .env.development.local .env.test.local .env.production.local +!.env npm-debug.log* yarn-debug.log* diff --git a/packages/x-provider/tsconfig.json b/packages/x-provider/tsconfig.json index 7dbb9f1fb9..a61b882cee 100644 --- a/packages/x-provider/tsconfig.json +++ b/packages/x-provider/tsconfig.json @@ -1,20 +1,9 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": true, - "resolveJsonModule": true, - "skipLibCheck": true + "customConditions": ["development"] }, "include": ["src"], "exclude": ["node_modules", "dist", "src/sample-app"] diff --git a/sdk/.eslintrc b/sdk/.eslintrc.cjs similarity index 75% rename from sdk/.eslintrc rename to sdk/.eslintrc.cjs index 9cf76ec00c..515261c86d 100644 --- a/sdk/.eslintrc +++ b/sdk/.eslintrc.cjs @@ -1,9 +1,10 @@ -{ +module.exports = { "ignorePatterns": ["jest.config.*"], + "extends": ["../.eslintrc"], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json", - "tsconfigRootDir": ".", + "tsconfigRootDir": __dirname, "sourceType": "module" }, "rules": { diff --git a/sdk/.gitignore b/sdk/.gitignore deleted file mode 100644 index 44803ff59f..0000000000 --- a/sdk/.gitignore +++ /dev/null @@ -1 +0,0 @@ -workspace-packages.json \ No newline at end of file diff --git a/sdk/package.json b/sdk/package.json index ae744e072b..46f6f6080a 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -18,7 +18,7 @@ "@ethersproject/wallet": "^5.7.0", "@imtbl/react-analytics": "0.2.1-alpha", "@jest/globals": "^29.5.0", - "@magic-ext/oidc": "4.2.0", + "@magic-ext/oidc": "4.3.1", "@metamask/detect-provider": "^2.0.0", "@opensea/seaport-js": "4.0.3", "@rive-app/react-canvas-lite": "^4.9.0", @@ -33,7 +33,7 @@ "bn.js": "^5.2.1", "buffer": "^6.0.3", "crypto-browserify": "^3.12.0", - "elliptic": "^6.5.4", + "elliptic": "^6.5.7", "enc-utils": "^3.0.0", "ethereumjs-wallet": "^1.0.2", "ethers": "^5.7.2", @@ -83,10 +83,9 @@ "@rollup/plugin-replace": "^5.0.7", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", - "@yarnpkg/cli": "3.5.0", - "@yarnpkg/core": "3.5.0", "eslint": "^8.40.0", "glob": "^10.2.3", + "rimraf": "^6.0.1", "rollup": "^4.19.1", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-polyfill-node": "^0.13.0", @@ -156,15 +155,14 @@ "@openzeppelin/contracts": "3.4.2-solc-0.7" }, "scripts": { - "build": "yarn updateDependencies && yarn regenModules && rm -rf dist && NODE_ENV=production node --max-old-space-size=14366 ../node_modules/rollup/dist/bin/rollup --config rollup.config.js && rm -rf dist/types && yarn copyBrowserBundles", - "build:only": "rm -rf dist && NODE_ENV=production node --max-old-space-size=8192 ../node_modules/rollup/dist/bin/rollup --config rollup.config.js && rm -rf dist/types", + "build": "yarn updateDependencies && yarn regenModules && rimraf dist && NODE_ENV=production node --max-old-space-size=14366 ../node_modules/rollup/dist/bin/rollup --config rollup.config.js && rimraf dist/types && yarn copyBrowserBundles", + "build:only": "rimraf dist && NODE_ENV=production node --max-old-space-size=8192 ../node_modules/rollup/dist/bin/rollup --config rollup.config.js && rimraf dist/types", "copyBrowserBundles": "node scripts/copyBrowserBundles.js", "generateIndex": "node scripts/generateIndex.js", "lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0", - "packageList": "./scripts/generate-package-list.sh", "regenModules": "yarn generateIndex && yarn updateExports", "typecheck": "tsc --noEmit --jsx preserve", - "updateDependencies": "yarn packageList && node ./scripts/updateDependencies.js && yarn syncpack:format", + "updateDependencies": "node ./scripts/updateDependencies.js && yarn run --top-level syncpack:format", "updateExports": "node scripts/updateExports.js" }, "type": "module", diff --git a/sdk/rollup.config.js b/sdk/rollup.config.js index 198add415c..17bb09849b 100644 --- a/sdk/rollup.config.js +++ b/sdk/rollup.config.js @@ -1,6 +1,6 @@ import typescript from '@rollup/plugin-typescript'; import { nodeResolve } from '@rollup/plugin-node-resolve'; -import { readFileSync } from 'fs'; +import { execSync } from 'child_process'; import commonJs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; import dts from 'rollup-plugin-dts'; @@ -11,23 +11,15 @@ import terser from '@rollup/plugin-terser'; import nodePolyfills from 'rollup-plugin-polyfill-node'; import babel from '@rollup/plugin-babel'; -import { fileURLToPath } from 'url'; -import { dirname, join } from 'path'; - -// Convert the import.meta.url to a file path -const __filename = fileURLToPath(import.meta.url); -// Get the directory name of the current module -const __dirname = dirname(__filename); -const projectRoot = __dirname; - // RELEASE_TYPE environment variable is set by the CI/CD pipeline const releaseType = process.env.RELEASE_TYPE || 'alpha'; -const packages = JSON.parse( - readFileSync(join(projectRoot, 'workspace-packages.json'), { encoding: 'utf8' } -)); - -const getPackages = () => packages.map((pkg) => pkg.name); +const packages = execSync('yarn workspaces list --json') + .toString() + .trim() + .split('\n') + .map((line) => JSON.parse(line)) + .map((pkg) => pkg.name); // Get relevant files to bundle const getFilesToBuild = () => { @@ -61,7 +53,8 @@ const buildJS = () => { }, plugins: [ nodeResolve({ - resolveOnly: getPackages(), + resolveOnly: packages, + exportConditions: ["default"], }), json(), commonJs(), @@ -114,7 +107,8 @@ export default [ }, plugins: [ nodeResolve({ - resolveOnly: getPackages(), + resolveOnly: packages, + exportConditions: ["default"] }), json(), commonJs(), @@ -141,6 +135,7 @@ export default [ main: true, browser: true, preferBuiltins: false, + exportConditions: ["default"], }), nodePolyfills(), json(), @@ -161,7 +156,7 @@ export default [ 'process.env.NODE_ENV': '"production"', 'process': 'undefined' }), - terser(), + terser({ keep_fnames: /./ }), ], }, diff --git a/sdk/scripts/copyBrowserBundles.js b/sdk/scripts/copyBrowserBundles.js index 7e5591b999..95f3b01af1 100755 --- a/sdk/scripts/copyBrowserBundles.js +++ b/sdk/scripts/copyBrowserBundles.js @@ -2,8 +2,9 @@ import fs from 'fs'; import * as glob from 'glob'; import path from 'path'; import pkg from '../package.json' assert { type: 'json' }; +import { fileURLToPath } from 'url'; -const dirname = path.dirname(new URL(import.meta.url).pathname); +const dirname = path.dirname(fileURLToPath(import.meta.url)); const SDK_VERSION = '__SDK_VERSION__'; diff --git a/sdk/scripts/generate-package-list.sh b/sdk/scripts/generate-package-list.sh deleted file mode 100755 index 71c90ba0d6..0000000000 --- a/sdk/scripts/generate-package-list.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -set -e - -yarn workspaces list --json | sed -e ' $ ! s/}/},/' | sed '$ s/$/]/' | sed '1 s/^/[/' > workspace-packages.json \ No newline at end of file diff --git a/sdk/scripts/generateIndex.js b/sdk/scripts/generateIndex.js index 277d8194c2..9aa88c1d21 100644 --- a/sdk/scripts/generateIndex.js +++ b/sdk/scripts/generateIndex.js @@ -1,7 +1,8 @@ import fs from 'fs'; import path from 'path'; +import { fileURLToPath } from 'url'; -const dirname = path.dirname(new URL(import.meta.url).pathname); +const dirname = path.dirname(fileURLToPath(import.meta.url)); // Read the JSON file const fileData = fs.readFileSync( diff --git a/sdk/scripts/updateDependencies.js b/sdk/scripts/updateDependencies.js index 1544fed536..822f5e9326 100644 --- a/sdk/scripts/updateDependencies.js +++ b/sdk/scripts/updateDependencies.js @@ -1,28 +1,27 @@ //@ts-check -import { getPluginConfiguration } from '@yarnpkg/cli'; -import { Configuration, Project } from '@yarnpkg/core'; import semver from 'semver'; import fs from 'fs'; import path from 'path'; +import { execSync } from 'child_process'; const __dirname = path.resolve(); const SDK_PACKAGE = '@imtbl/sdk'; -const getWorkspacePackages = () => { - const workspacePackages = JSON.parse( - fs.readFileSync(path.resolve(__dirname, 'workspace-packages.json'), { - encoding: 'utf8', - }) - ).map((pkg) => pkg.name); - return workspacePackages; -}; -const workspacePackages = getWorkspacePackages(); +const rootDir = path.resolve(__dirname, '..') + +const workspacePackages = execSync('yarn workspaces list --json') + .toString() + .trim() + .split('\n') + .map((line) => JSON.parse(line)) + +const workspaceNames = workspacePackages.map((pkg) => pkg.name); // Update the map with the dependency if it doesn't exist, or if the // version is greater than the existing version const updateVersion = (map, dependency, version) => { // Don't add any workspace packages as a dependency - if (workspacePackages.includes(dependency)) return; + if (workspaceNames.includes(dependency)) return; const existingVersion = map.get(dependency); @@ -43,50 +42,56 @@ const collectDependenciesRecusively = async (sdkWorkspace) => { // Recursively go through a workspace and update the dependencies const processWorkspace = (workspace) => { - const manifest = workspace.manifest; - const { dependencies, peerDependencies, devDependencies } = manifest; + const workspacePackageJSON = path.resolve( + rootDir, workspace, 'package.json' + ); + + const manifest = JSON.parse(fs.readFileSync(workspacePackageJSON, {encoding: 'utf8'})) + const { dependencies, peerDependencies, devDependencies, optionalDependencies } = manifest; // Dev dependencies, only check if they're workspace packages // And then process them - devDependencies.forEach((dep) => { - const depWorkspace = workspace.project.tryWorkspaceByIdent(dep); + Object.keys(devDependencies).forEach((dep) => { + const depWorkspace = workspacePackages.find((pkg) => pkg.name === dep); if (depWorkspace) { - processWorkspace(depWorkspace); + processWorkspace(depWorkspace.location); } }); // If sdkpackage, exit early - if (manifest.raw.name === SDK_PACKAGE) return; + if (manifest.name === SDK_PACKAGE) return; // UpdateVersion for dependencies - dependencies.forEach((dep) => { - // check for optional dependencies metadata - if (manifest.dependenciesMeta?.get(dep.name)?.get(null)?.optional) { - updateVersion( - optionalDependenciesMap, - packageName(dep.scope, dep.name), - dep.range - ); - } else { + if (dependencies) Object.keys(dependencies).forEach((dep) => { updateVersion( dependenciesMap, - packageName(dep.scope, dep.name), - dep.range + dep, + dependencies[dep] ); - } - const depWorkspace = workspace.project.tryWorkspaceByIdent(dep); - if (depWorkspace) { - processWorkspace(depWorkspace); - } + const depWorkspace = workspacePackages.find((pkg) => pkg.name === dep); + if (depWorkspace) { + processWorkspace(depWorkspace.location); + } }); + + + // refactor the above optionalDependencies part + if (optionalDependencies) Object.keys(optionalDependencies).forEach((dep) => + updateVersion( + optionalDependenciesMap, + dep, + optionalDependencies[dep] + ) + ); + // Same for peerDependencies, but don't recurse - peerDependencies.forEach((dep) => + if (peerDependencies) Object.keys(peerDependencies).forEach((dep) => updateVersion( peerDependenciesMap, - packageName(dep.scope, dep.name), - dep.range + dep, + peerDependencies[dep] ) ); }; @@ -101,11 +106,6 @@ const collectDependenciesRecusively = async (sdkWorkspace) => { }; }; -// Takes a scope and a package name and returns a scoped package name -const packageName = (scope, name) => { - return scope ? `@${scope}/${name}` : name; -}; - // Remove ranges to parse just version const parseVersion = (version) => { return version.replace(/^[^\d]*/, ''); @@ -113,14 +113,9 @@ const parseVersion = (version) => { // Update package.json with the dependencies and peerDependencies const main = async () => { - const cwd = process.cwd(); - const pluginConfiguration = getPluginConfiguration(); - const configuration = await Configuration.find(cwd, pluginConfiguration); - const { project } = await Project.find(configuration, cwd); - - const targetWorkspace = project.workspaces.find( - (workspace) => workspace.manifest.raw.name === SDK_PACKAGE - ); + const targetWorkspace = workspacePackages.find( + (pkg) => pkg.name === SDK_PACKAGE + ).location; if (!targetWorkspace) { throw Error(`${SDK_PACKAGE} package not found`); diff --git a/sdk/scripts/updateExports.js b/sdk/scripts/updateExports.js index 7147f5ae12..c610f71e2e 100644 --- a/sdk/scripts/updateExports.js +++ b/sdk/scripts/updateExports.js @@ -1,7 +1,8 @@ import fs from 'fs'; import path from 'path'; +import { fileURLToPath } from 'url'; -const __dirname = path.dirname(new URL(import.meta.url).pathname); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Read the module-release.json file const moduleReleaseData = fs.readFileSync( diff --git a/sdk/tsconfig.json b/sdk/tsconfig.json index 2057e61172..d8730a79e3 100644 --- a/sdk/tsconfig.json +++ b/sdk/tsconfig.json @@ -1,26 +1,13 @@ { + "extends": "../tsconfig.base.json", "compilerOptions": { + "customConditions": ["default"], "outDir": "./dist", "rootDirs": ["src"], - "target": "es2022", - "module": "esnext", - "moduleResolution": "node", - "noEmit": true, - "allowJs": false, - "removeComments": false, - "strict": true, - "forceConsistentCasingInFileNames": false, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "declaration": false, - "resolveJsonModule": true, - "skipLibCheck": false }, "include": ["src"], "exclude": [ "node_modules", - "dist", - "src/modules/provider/sample-app", - "src/modules/checkout" + "dist" ] } diff --git a/tests/func-tests/imx/package.json b/tests/func-tests/imx/package.json index 1a0cacd786..c7f68c0687 100644 --- a/tests/func-tests/imx/package.json +++ b/tests/func-tests/imx/package.json @@ -10,7 +10,6 @@ "devDependencies": { "@swc/jest": "^0.2.29", "@types/node": "^20.10.1", - "husky": "^8.0.0", "jest": "^29.7.0", "jest-cucumber": "^3.0.1", "pinst": "^3.0.0", @@ -18,14 +17,12 @@ "ts-node": "^10.9.1", "typescript": "^5.5.4" }, - "packageManager": "yarn@3.6.1", "resolutions": { "@openzeppelin/contracts": "3.4.2-solc-0.7" }, "scripts": { "func-test": "jest", "func-test:ci": "TAGS=\"not @skip and not @slow\" jest", - "postinstall": "cd ../../.. && husky install", "postpack": "pinst --enable", "prepack": "pinst --disable" } diff --git a/tests/func-tests/zkevm/features/order.feature b/tests/func-tests/zkevm/features/order.feature index 7ce1f65fc9..b0c42034c1 100644 --- a/tests/func-tests/zkevm/features/order.feature +++ b/tests/func-tests/zkevm/features/order.feature @@ -12,6 +12,7 @@ Feature: orderbook Then the listing should be of status filled And 1 ERC721 token should be transferred to the fulfiller And 1 trade should be available + And any remaining funds are returned to the banker Scenario: bulk creating and fulfilling ERC721 listings Given I have a funded offerer account @@ -23,6 +24,7 @@ Feature: orderbook Then the listing should be of status filled And 1 ERC721 token should be transferred to the fulfiller And 1 trade should be available + And any remaining funds are returned to the banker Scenario: create and completely fill a ERC1155 listing Given I have a funded offerer account @@ -34,6 +36,7 @@ Feature: orderbook Then the listing should be of status filled And 100 ERC1155 tokens should be transferred to the fulfiller And 1 trade should be available + And any remaining funds are returned to the banker Scenario: create and partially fill a ERC1155 listing Given I have a funded offerer account @@ -50,6 +53,7 @@ Feature: orderbook # Checks for the total amount of tokens transferred - 100 = 90 from first fulfilment + 10 from second fulfilment And 100 ERC1155 tokens should be transferred to the fulfiller And 2 trades should be available + And any remaining funds are returned to the banker Scenario: create and bulk fill multiple listings Given I have a funded offerer account @@ -67,6 +71,7 @@ Feature: orderbook Then the listing should be of status active # Assert only the ERC1155 trade in this scenario And 1 trade should be available + And any remaining funds are returned to the banker Scenario: create and fully fill a ERC1155 listing without an explicit fulfill amount Given I have a funded offerer account @@ -78,6 +83,7 @@ Feature: orderbook Then the listing should be of status filled And 100 ERC1155 tokens should be transferred to the fulfiller And 1 trade should be available + And any remaining funds are returned to the banker Scenario: create and partially fill a ERC1155 listing, second fill without explicit amount Given I have a funded offerer account @@ -93,4 +99,5 @@ Feature: orderbook Then the listing should be of status filled # Checks for the total amount of tokens transferred - 100 = 90 from first fulfilment + 10 from second fulfilment And 100 ERC1155 tokens should be transferred to the fulfiller - And 2 trades should be available \ No newline at end of file + And 2 trades should be available + And any remaining funds are returned to the banker \ No newline at end of file diff --git a/tests/func-tests/zkevm/package.json b/tests/func-tests/zkevm/package.json index 74271bf5aa..ac114a9c2d 100644 --- a/tests/func-tests/zkevm/package.json +++ b/tests/func-tests/zkevm/package.json @@ -22,7 +22,6 @@ "devDependencies": { "@swc/jest": "^0.2.29", "@types/node": "^20.10.1", - "husky": "^8.0.0", "jest": "^29.7.0", "jest-cucumber": "^3.0.1", "pinst": "^3.0.0", @@ -30,12 +29,10 @@ "ts-node": "^10.9.2", "typescript": "^5.5.4" }, - "packageManager": "yarn@3.6.1", "scripts": { "compile": "npx hardhat compile", "func-test": "yarn compile && jest", "func-test:ci": "yarn compile && TAGS=\"not @skip and not @slow\" jest", - "postinstall": "cd ../../.. && husky install", "postpack": "pinst --enable", "prepack": "pinst --disable" } diff --git a/tests/func-tests/zkevm/step-definitions/order.steps.ts b/tests/func-tests/zkevm/step-definitions/order.steps.ts index 16a1b46727..90214d47c5 100644 --- a/tests/func-tests/zkevm/step-definitions/order.steps.ts +++ b/tests/func-tests/zkevm/step-definitions/order.steps.ts @@ -1,7 +1,7 @@ import { Wallet } from 'ethers'; import { defineFeature, loadFeature } from 'jest-cucumber'; import { orderbook } from '@imtbl/sdk'; -import { Environment } from '@imtbl/config'; +import { Environment } from '@imtbl/sdk/config' import { getConfigFromEnv, getRandomTokenId, @@ -14,7 +14,7 @@ import { whenICreateAListing, whenIFulfillTheListingToBuy, andERC1155TokensShouldBeTransferredToTheFulfiller, thenTheListingsShouldBeOfStatus, whenIFulfillBulkListings, whenIFulfillTheListingToBuyWithoutExplicitFulfillmentAmt, - whenICreateABulkListing, + whenICreateABulkListing, andAnyRemainingFundsAreReturnedToBanker, } from './shared'; const feature = loadFeature('features/order.feature', { tagFilter: process.env.TAGS }); @@ -78,6 +78,8 @@ defineFeature(feature, (test) => { andERC721TokenShouldBeTransferredToTheFulfiller(and, bankerWallet, erc721ContractAddress, testTokenId, fulfiller); andTradeShouldBeAvailable(and, sdk, fulfiller, getListingId); + + andAnyRemainingFundsAreReturnedToBanker(and, bankerWallet, offerer, fulfiller); }, 120_000); test('bulk creating and fulfilling ERC721 listings', async ({ @@ -117,6 +119,8 @@ defineFeature(feature, (test) => { andERC721TokenShouldBeTransferredToTheFulfiller(and, bankerWallet, erc721ContractAddress, testTokenId2, fulfiller); andTradeShouldBeAvailable(and, sdk, fulfiller, getListingId); + + andAnyRemainingFundsAreReturnedToBanker(and, bankerWallet, offerer, fulfiller); }, 120_000); test('create and completely fill a ERC1155 listing', ({ @@ -156,6 +160,8 @@ defineFeature(feature, (test) => { andERC1155TokensShouldBeTransferredToTheFulfiller(and, bankerWallet, erc1155ContractAddress, testTokenId, fulfiller); andTradeShouldBeAvailable(and, sdk, fulfiller, getListingId); + + andAnyRemainingFundsAreReturnedToBanker(and, bankerWallet, offerer, fulfiller); }, 120_000); test('create and partially fill a ERC1155 listing', ({ @@ -203,6 +209,8 @@ defineFeature(feature, (test) => { andERC1155TokensShouldBeTransferredToTheFulfiller(and, bankerWallet, erc1155ContractAddress, testTokenId, fulfiller); andTradeShouldBeAvailable(and, sdk, fulfiller, getListingId); + + andAnyRemainingFundsAreReturnedToBanker(and, bankerWallet, offerer, fulfiller); }, 120_000); test('create and bulk fill multiple listings', ({ @@ -256,6 +264,8 @@ defineFeature(feature, (test) => { thenTheListingShouldBeOfStatus(then, sdk, getERC1155ListingId); andTradeShouldBeAvailable(and, sdk, fulfiller, getERC1155ListingId); + + andAnyRemainingFundsAreReturnedToBanker(and, bankerWallet, offerer, fulfiller); }, 120_000); test('create and fully fill a ERC1155 listing without an explicit fulfill amount', ({ @@ -294,6 +304,8 @@ defineFeature(feature, (test) => { andERC1155TokensShouldBeTransferredToTheFulfiller(and, bankerWallet, erc1155ContractAddress, testTokenId, fulfiller); andTradeShouldBeAvailable(and, sdk, fulfiller, getListingId); + + andAnyRemainingFundsAreReturnedToBanker(and, bankerWallet, offerer, fulfiller); }, 120_000); test('create and partially fill a ERC1155 listing, second fill without explicit amount', ({ @@ -341,5 +353,7 @@ defineFeature(feature, (test) => { andERC1155TokensShouldBeTransferredToTheFulfiller(and, bankerWallet, erc1155ContractAddress, testTokenId, fulfiller); andTradeShouldBeAvailable(and, sdk, fulfiller, getListingId); + + andAnyRemainingFundsAreReturnedToBanker(and, bankerWallet, offerer, fulfiller); }, 120_000); }); diff --git a/tests/func-tests/zkevm/step-definitions/shared.ts b/tests/func-tests/zkevm/step-definitions/shared.ts index 6a02582541..af12739852 100644 --- a/tests/func-tests/zkevm/step-definitions/shared.ts +++ b/tests/func-tests/zkevm/step-definitions/shared.ts @@ -1,6 +1,6 @@ import { orderbook } from '@imtbl/sdk'; import { DefineStepFunction } from 'jest-cucumber'; -import { Wallet } from 'ethers'; +import { BigNumber, Wallet } from 'ethers'; import { bulkFulfillListings, connectToTestERC1155Token, @@ -14,6 +14,7 @@ import { actionAll } from '../utils/orderbook/actions'; const imxForApproval = 0.03 * 1e18; const imxForFulfillment = 0.08 * 1e18; const listingPrice = 0.0001 * 1e18; +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 @@ -198,7 +199,7 @@ export const whenICreateABulkListing = ( }); const signatures = await actionAll(actions, offerer); - const { result } = await completeListings(signatures[0]); + const { result } = await completeListings(signatures); for (const res of result) { if (!res.success) { @@ -332,3 +333,39 @@ export const andTradeShouldBeAvailable = ( expect(targetTrades?.length === count); }); }; + +export const andAnyRemainingFundsAreReturnedToBanker = ( + and: DefineStepFunction, + banker: Wallet, + offerer: Wallet, + fulfiller: Wallet, +) => { + and(/^any remaining funds are returned to the banker$/, async () => { + await withBankerRetry(async () => { + const fulfillerBalance = await fulfiller.getBalance(); + const offererBalance = await offerer.getBalance(); + + if (fulfillerBalance.gt(BigNumber.from(transferTxnFee))) { + // fulfiller returns funds + const fulfillerReturnTxn = await fulfiller.sendTransaction({ + to: banker.address, + value: `${fulfillerBalance.sub(BigNumber.from(transferTxnFee)).toString()}`, + ...GAS_OVERRIDES, + }); + + await fulfillerReturnTxn.wait(1); + } + + if (offererBalance.gt(BigNumber.from(transferTxnFee))) { + // offerer returns funds + const offererReturnTxn = await offerer.sendTransaction({ + to: banker.address, + value: `${offererBalance.sub(BigNumber.from(transferTxnFee)).toString()}`, + ...GAS_OVERRIDES, + }); + + await offererReturnTxn.wait(1); + } + }); + }); +}; diff --git a/tests/func-tests/zkevm/tsconfig.json b/tests/func-tests/zkevm/tsconfig.json new file mode 100644 index 0000000000..e05ca5f05c --- /dev/null +++ b/tests/func-tests/zkevm/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDirs": ["src"], + }, + "include": ["src"], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/tsconfig.json b/tsconfig.base.json similarity index 61% rename from tsconfig.json rename to tsconfig.base.json index 333e556c0f..3ebd0139bf 100644 --- a/tsconfig.json +++ b/tsconfig.base.json @@ -1,9 +1,8 @@ { "compilerOptions": { - "jsx": "react", "target": "es2022", "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "noEmit": true, "allowJs": false, "removeComments": false, @@ -11,13 +10,17 @@ "forceConsistentCasingInFileNames": false, "allowSyntheticDefaultImports": true, "esModuleInterop": true, - "declaration": false, + "declaration": true, "resolveJsonModule": true, - "skipLibCheck": true + "skipLibCheck": true, + "isolatedModules": true, + "noEmitOnError": true }, "ts-node": { "compilerOptions": { - "module": "commonjs" + "module": "commonjs", + "moduleResolution": "node", + "customConditions": null } } } diff --git a/yarn.lock b/yarn.lock index dad3d6bc7e..8fdfe43f9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -100,15 +100,6 @@ __metadata: languageName: node linkType: hard -"@arcanis/slice-ansi@npm:^1.1.1": - version: 1.1.1 - resolution: "@arcanis/slice-ansi@npm:1.1.1" - dependencies: - grapheme-splitter: ^1.0.4 - checksum: 14ed60cb45750d386c64229ac7bab20e10eedc193503fa4decff764162d329d6d3363ed2cd3debec833186ee54affe4f824f6e8eff531295117fd1ebda200270 - languageName: node - linkType: hard - "@axelar-network/axelar-gmp-sdk-solidity@npm:^5.8.0": version: 5.10.0 resolution: "@axelar-network/axelar-gmp-sdk-solidity@npm:5.10.0" @@ -2265,7 +2256,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.22.6 resolution: "@babel/runtime@npm:7.22.6" dependencies: @@ -2537,20 +2528,6 @@ __metadata: languageName: node linkType: hard -"@chevrotain/types@npm:^9.1.0": - version: 9.1.0 - resolution: "@chevrotain/types@npm:9.1.0" - checksum: 5f26ff26aa345bc823b39ebe48f038db0998c80d13fa4b937961d68523a259ac86ec693bc1ad3f3cfa9ef83e5ffb6d94337960c3a1ee7cb82e3014adb4f5bf30 - languageName: node - linkType: hard - -"@chevrotain/utils@npm:^9.1.0": - version: 9.1.0 - resolution: "@chevrotain/utils@npm:9.1.0" - checksum: ca78c97c7c3e444431d0fafa348f0c955998cd86bc0d4bbdeaae3ff5abba8d416d69d5a4163e86cac962a392f1c325cb4a97b8b05722527da62e9b7635025e02 - languageName: node - linkType: hard - "@cnakazawa/watch@npm:^1.0.3": version: 1.0.4 resolution: "@cnakazawa/watch@npm:1.0.4" @@ -4198,6 +4175,7 @@ __metadata: ts-mockito: ^2.6.1 typechain: ^8.1.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4226,6 +4204,7 @@ __metadata: ts-node: ^10.9.1 typechain: ^8.1.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4237,6 +4216,8 @@ __metadata: "@imtbl/checkout-sdk": 0.0.0 "@imtbl/checkout-widgets": 0.0.0 "@imtbl/config": 0.0.0 + "@imtbl/orderbook": 0.0.0 + "@imtbl/passport": 0.0.0 "@svgr/webpack": ^8.0.1 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^13.4.0 @@ -4276,6 +4257,7 @@ __metadata: "@imtbl/bridge-sdk": 0.0.0 "@imtbl/config": 0.0.0 "@imtbl/dex-sdk": 0.0.0 + "@imtbl/generated-clients": 0.0.0 "@imtbl/metrics": 0.0.0 "@imtbl/orderbook": 0.0.0 "@imtbl/passport": 0.0.0 @@ -4288,6 +4270,7 @@ __metadata: "@rollup/plugin-replace": ^5.0.7 "@rollup/plugin-terser": ^0.4.4 "@rollup/plugin-typescript": ^11.1.6 + "@swc/core": ^1.3.36 "@types/uuid": ^8.3.4 axios: ^1.6.5 babel-jest: ^29.5.0 @@ -4302,6 +4285,7 @@ __metadata: typedoc: ^0.26.5 typedoc-plugin-markdown: ^4.2.3 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 uuid: ^8.3.2 languageName: unknown linkType: soft @@ -4316,6 +4300,7 @@ __metadata: "@imtbl/checkout-widgets": 0.0.0 "@imtbl/config": 0.0.0 "@imtbl/passport": 0.0.0 + "@swc/core": ^1.3.36 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^13.4.0 "@testing-library/user-event": ^13.5.0 @@ -4331,6 +4316,7 @@ __metadata: react-dom: ^18.2.0 react-router-dom: ^6.11.0 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 vite: ^5.0.12 vite-plugin-node-polyfills: ^0.16.0 web-vitals: ^2.1.4 @@ -4359,6 +4345,7 @@ __metadata: "@rollup/plugin-terser": ^0.4.4 "@rollup/plugin-typescript": ^11.1.6 "@svgr/webpack": ^8.0.1 + "@swc/core": ^1.3.36 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^13.4.0 "@testing-library/user-event": ^13.5.0 @@ -4381,16 +4368,20 @@ __metadata: local-cypress: ^1.2.6 os-browserify: ^0.3.0 pako: ^2.1.0 + pino-pretty: ^11.2.2 react-app-rewired: ^2.2.1 react-i18next: ^13.5.0 react-scripts: 5.0.1 + rimraf: ^6.0.1 rollup: ^4.19.1 + rollup-plugin-polyfill-node: ^0.13.0 rollup-plugin-svg: ^2.0.0 rollup-plugin-visualizer: ^5.12.0 stream-browserify: ^3.0.0 stream-http: ^3.2.0 ts-jest: ^29.1.0 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 url: ^0.11.0 web-vitals: ^2.1.4 languageName: unknown @@ -4415,6 +4406,7 @@ __metadata: rollup: ^4.19.1 ts-node: ^10.9.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4459,6 +4451,7 @@ __metadata: rollup: ^4.19.1 ts-node: ^10.9.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4468,6 +4461,7 @@ __metadata: dependencies: "@imtbl/config": 0.0.0 "@rollup/plugin-json": ^6.1.0 + "@rollup/plugin-typescript": ^11.1.6 "@swc/core": ^1.3.36 "@swc/jest": ^0.2.24 "@typechain/ethers-v5": ^10.2.0 @@ -4484,6 +4478,7 @@ __metadata: ts-node: ^10.9.1 typechain: ^8.1.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4509,6 +4504,7 @@ __metadata: ts-node: ^10.9.1 typechain: ^8.1.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4532,9 +4528,13 @@ __metadata: resolution: "@imtbl/generated-clients@workspace:packages/internal/generated-clients" dependencies: "@openapitools/openapi-generator-cli": ^2.13.4 + "@rollup/plugin-typescript": ^11.1.6 + "@swc/core": ^1.3.36 jest: ^29.4.3 + rimraf: ^6.0.1 rollup: ^4.19.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4543,6 +4543,8 @@ __metadata: resolution: "@imtbl/guardian@workspace:packages/internal/guardian" dependencies: "@rollup/plugin-json": ^6.1.0 + "@rollup/plugin-node-resolve": ^15.2.3 + "@rollup/plugin-typescript": ^11.1.6 "@swc/core": ^1.3.36 "@swc/jest": ^0.2.24 "@typechain/ethers-v5": ^10.2.0 @@ -4556,6 +4558,7 @@ __metadata: ts-node: ^10.9.1 typechain: ^8.1.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4564,6 +4567,7 @@ __metadata: resolution: "@imtbl/metrics@workspace:packages/internal/metrics" dependencies: "@rollup/plugin-typescript": ^11.1.6 + "@swc/core": ^1.3.36 "@swc/jest": ^0.2.24 "@types/jest": ^29.4.3 axios: ^1.6.5 @@ -4575,6 +4579,7 @@ __metadata: rollup: ^4.19.1 ts-jest: ^29.1.0 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4584,6 +4589,7 @@ __metadata: dependencies: "@imtbl/blockchain-data": 0.0.0 "@imtbl/config": 0.0.0 + "@imtbl/generated-clients": 0.0.0 "@imtbl/metrics": 0.0.0 "@imtbl/webhook": 0.0.0 "@rollup/plugin-typescript": ^11.1.6 @@ -4602,6 +4608,7 @@ __metadata: testcontainers: ^10.9.0 ts-mockito: ^2.6.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 dependenciesMeta: pg: optional: true @@ -4615,8 +4622,10 @@ __metadata: resolution: "@imtbl/orderbook@workspace:packages/orderbook" dependencies: "@imtbl/config": 0.0.0 + "@imtbl/metrics": 0.0.0 "@opensea/seaport-js": 4.0.3 "@rollup/plugin-typescript": ^11.1.6 + "@swc/core": ^1.3.36 "@swc/jest": ^0.2.24 "@typechain/ethers-v5": ^10.2.0 "@types/jest": ^29.4.3 @@ -4633,6 +4642,7 @@ __metadata: ts-mockito: ^2.6.1 typechain: ^8.1.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4644,12 +4654,14 @@ __metadata: "@biom3/react": ^0.25.0 "@imtbl/blockchain-data": 0.0.0 "@imtbl/config": 0.0.0 + "@imtbl/generated-clients": 0.0.0 "@imtbl/orderbook": 0.0.0 "@imtbl/passport": 0.0.0 "@imtbl/x-client": 0.0.0 "@imtbl/x-provider": 0.0.0 "@metamask/detect-provider": ^2.0.0 "@next/eslint-plugin-next": ^13.4.7 + "@swc/core": ^1.3.36 "@types/node": ^18.14.2 "@types/react": ^18.0.28 "@types/react-dom": ^18.0.11 @@ -4663,7 +4675,7 @@ __metadata: eslint-config-next: 13.3.1 ethers: ^5.7.2 framer-motion: ^11.0.6 - next: 13.3.1 + next: 13.4.11 react: ^18.2.0 react-bootstrap: ^2.7.2 react-dom: ^18.2.0 @@ -4685,7 +4697,7 @@ __metadata: "@imtbl/toolkit": 0.0.0 "@imtbl/x-client": 0.0.0 "@imtbl/x-provider": 0.0.0 - "@magic-ext/oidc": 4.2.0 + "@magic-ext/oidc": 4.3.1 "@metamask/detect-provider": ^2.0.0 "@rollup/plugin-typescript": ^11.1.6 "@swc/core": ^1.3.36 @@ -4715,6 +4727,7 @@ __metadata: rollup: ^4.19.1 ts-node: ^10.9.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 uuid: ^8.3.2 languageName: unknown linkType: soft @@ -4761,7 +4774,7 @@ __metadata: "@imtbl/x-client": 0.0.0 "@imtbl/x-provider": 0.0.0 "@jest/globals": ^29.5.0 - "@magic-ext/oidc": 4.2.0 + "@magic-ext/oidc": 4.3.1 "@metamask/detect-provider": ^2.0.0 "@opensea/seaport-js": 4.0.3 "@rive-app/react-canvas-lite": ^4.9.0 @@ -4778,14 +4791,12 @@ __metadata: "@uniswap/v3-sdk": ^3.9.0 "@walletconnect/ethereum-provider": ^2.11.1 "@walletconnect/modal": ^2.6.2 - "@yarnpkg/cli": 3.5.0 - "@yarnpkg/core": 3.5.0 assert: ^2.0.0 axios: ^1.6.5 bn.js: ^5.2.1 buffer: ^6.0.3 crypto-browserify: ^3.12.0 - elliptic: ^6.5.4 + elliptic: ^6.5.7 enc-utils: ^3.0.0 eslint: ^8.40.0 ethereumjs-wallet: ^1.0.2 @@ -4809,6 +4820,7 @@ __metadata: pino-pretty: ^11.2.2 prisma: ^5.13.0 react-i18next: ^13.5.0 + rimraf: ^6.0.1 rollup: ^4.19.1 rollup-plugin-dts: ^6.1.1 rollup-plugin-polyfill-node: ^0.13.0 @@ -4901,7 +4913,7 @@ __metadata: "@ethersproject/providers": ^5.7.2 "@ethersproject/wallet": ^5.7.0 "@imtbl/x-client": 0.0.0 - "@magic-ext/oidc": 4.2.0 + "@magic-ext/oidc": 4.3.1 "@metamask/detect-provider": ^2.0.0 "@rollup/plugin-typescript": ^11.1.6 "@swc/core": ^1.3.36 @@ -4926,6 +4938,7 @@ __metadata: rollup: ^4.19.1 ts-node: ^10.9.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4950,6 +4963,7 @@ __metadata: ts-mockito: ^2.6.1 typechain: ^8.1.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4966,11 +4980,12 @@ __metadata: "@imtbl/config": 0.0.0 "@imtbl/generated-clients": 0.0.0 "@rollup/plugin-typescript": ^11.1.6 + "@swc/core": ^1.3.36 "@swc/jest": ^0.2.24 "@types/jest": ^29.4.3 axios: ^1.6.5 bn.js: ^5.2.1 - elliptic: ^6.5.4 + elliptic: ^6.5.7 enc-utils: ^3.0.0 eslint: ^8.40.0 ethereumjs-wallet: ^1.0.2 @@ -4979,6 +4994,7 @@ __metadata: jest-environment-jsdom: ^29.4.3 rollup: ^4.19.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -4990,7 +5006,7 @@ __metadata: "@imtbl/generated-clients": 0.0.0 "@imtbl/toolkit": 0.0.0 "@imtbl/x-client": 0.0.0 - "@magic-ext/oidc": 4.2.0 + "@magic-ext/oidc": 4.3.1 "@metamask/detect-provider": ^2.0.0 "@rollup/plugin-typescript": ^11.1.6 "@swc/core": ^1.3.36 @@ -5013,6 +5029,7 @@ __metadata: rollup: ^4.19.1 ts-node: ^10.9.1 typescript: ^5.5.4 + unplugin-swc: ^1.5.1 languageName: unknown linkType: soft @@ -6231,6 +6248,13 @@ __metadata: languageName: node linkType: hard +"@magic-ext/oidc@npm:4.3.1": + version: 4.3.1 + resolution: "@magic-ext/oidc@npm:4.3.1" + checksum: 1eb1e2869b4acdb035b6e17244c835cd9a1c6d464ef666c827007e431da65c6fc7cd3e8c657b690f74ac01e4659336687aa336df57a315d5f34832721390b002 + languageName: node + linkType: hard + "@magic-sdk/commons@npm:^17.2.0": version: 17.2.0 resolution: "@magic-sdk/commons@npm:17.2.0" @@ -6826,10 +6850,10 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:13.3.1": - version: 13.3.1 - resolution: "@next/env@npm:13.3.1" - checksum: 3bf01e62b7de1de9de0cf7a5904364a2e807ad4f0a6cabfbb288225696a0750163bee23669bd45bfbec17dcf1fe66512212bdfa5be0a341ad187270a3cc27943 +"@next/env@npm:13.4.11": + version: 13.4.11 + resolution: "@next/env@npm:13.4.11" + checksum: f0181ae16ba3f7bbf6e7ce9b431218740b800ab3c2a61a50a75828794eecb7e6fe324f0547bfc6d80221ab3f4b812184a040c7c72b0f37c2f0c3742047f352c6 languageName: node linkType: hard @@ -6883,9 +6907,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:13.3.1": - version: 13.3.1 - resolution: "@next/swc-darwin-arm64@npm:13.3.1" +"@next/swc-darwin-arm64@npm:13.4.11": + version: 13.4.11 + resolution: "@next/swc-darwin-arm64@npm:13.4.11" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -6904,9 +6928,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-darwin-x64@npm:13.3.1": - version: 13.3.1 - resolution: "@next/swc-darwin-x64@npm:13.3.1" +"@next/swc-darwin-x64@npm:13.4.11": + version: 13.4.11 + resolution: "@next/swc-darwin-x64@npm:13.4.11" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -6925,9 +6949,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:13.3.1": - version: 13.3.1 - resolution: "@next/swc-linux-arm64-gnu@npm:13.3.1" +"@next/swc-linux-arm64-gnu@npm:13.4.11": + version: 13.4.11 + resolution: "@next/swc-linux-arm64-gnu@npm:13.4.11" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard @@ -6946,9 +6970,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:13.3.1": - version: 13.3.1 - resolution: "@next/swc-linux-arm64-musl@npm:13.3.1" +"@next/swc-linux-arm64-musl@npm:13.4.11": + version: 13.4.11 + resolution: "@next/swc-linux-arm64-musl@npm:13.4.11" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard @@ -6967,9 +6991,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:13.3.1": - version: 13.3.1 - resolution: "@next/swc-linux-x64-gnu@npm:13.3.1" +"@next/swc-linux-x64-gnu@npm:13.4.11": + version: 13.4.11 + resolution: "@next/swc-linux-x64-gnu@npm:13.4.11" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard @@ -6988,9 +7012,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:13.3.1": - version: 13.3.1 - resolution: "@next/swc-linux-x64-musl@npm:13.3.1" +"@next/swc-linux-x64-musl@npm:13.4.11": + version: 13.4.11 + resolution: "@next/swc-linux-x64-musl@npm:13.4.11" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard @@ -7009,9 +7033,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:13.3.1": - version: 13.3.1 - resolution: "@next/swc-win32-arm64-msvc@npm:13.3.1" +"@next/swc-win32-arm64-msvc@npm:13.4.11": + version: 13.4.11 + resolution: "@next/swc-win32-arm64-msvc@npm:13.4.11" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -7030,9 +7054,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-win32-ia32-msvc@npm:13.3.1": - version: 13.3.1 - resolution: "@next/swc-win32-ia32-msvc@npm:13.3.1" +"@next/swc-win32-ia32-msvc@npm:13.4.11": + version: 13.4.11 + resolution: "@next/swc-win32-ia32-msvc@npm:13.4.11" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -7051,9 +7075,9 @@ __metadata: languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:13.3.1": - version: 13.3.1 - resolution: "@next/swc-win32-x64-msvc@npm:13.3.1" +"@next/swc-win32-x64-msvc@npm:13.4.11": + version: 13.4.11 + resolution: "@next/swc-win32-x64-msvc@npm:13.4.11" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -11772,12 +11796,12 @@ __metadata: languageName: node linkType: hard -"@swc/helpers@npm:0.5.0": - version: 0.5.0 - resolution: "@swc/helpers@npm:0.5.0" +"@swc/helpers@npm:0.5.1, @swc/helpers@npm:^0.5.0": + version: 0.5.1 + resolution: "@swc/helpers@npm:0.5.1" dependencies: tslib: ^2.4.0 - checksum: 61c9c7dddb707deb58b85328cfe9d211887145f1311ae6a6e6c0fa9f781fb29916a8669a7d479e46e26a32a89d6ef4f293a22dee4925e009c84051e9dd10e8b7 + checksum: 71e0e27234590435e4c62b97ef5e796f88e786841a38c7116a5e27a3eafa7b9ead7cdec5249b32165902076de78446945311c973e59bddf77c1e24f33a8f272a languageName: node linkType: hard @@ -11791,15 +11815,6 @@ __metadata: languageName: node linkType: hard -"@swc/helpers@npm:^0.5.0": - version: 0.5.1 - resolution: "@swc/helpers@npm:0.5.1" - dependencies: - tslib: ^2.4.0 - checksum: 71e0e27234590435e4c62b97ef5e796f88e786841a38c7116a5e27a3eafa7b9ead7cdec5249b32165902076de78446945311c973e59bddf77c1e24f33a8f272a - languageName: node - linkType: hard - "@swc/jest@npm:^0.2.24": version: 0.2.27 resolution: "@swc/jest@npm:0.2.27" @@ -11939,7 +11954,6 @@ __metadata: "@swc/jest": ^0.2.29 "@types/node": ^20.10.1 dotenv: ^16.3.1 - husky: ^8.0.0 jest: ^29.7.0 jest-cucumber: ^3.0.1 pinst: ^3.0.0 @@ -11971,7 +11985,6 @@ __metadata: ethers: ^5.7.0 hardhat: ^2.19.4 hardhat-gas-reporter: ^1.0.8 - husky: ^8.0.0 jest: ^29.7.0 jest-cucumber: ^3.0.1 pinst: ^3.0.0 @@ -12296,13 +12309,6 @@ __metadata: languageName: node linkType: hard -"@types/emscripten@npm:^1.39.6": - version: 1.39.6 - resolution: "@types/emscripten@npm:1.39.6" - checksum: 437f2f9cdfd9057255662508fa9a415fe704ba484c6198f3549c5b05feebcdcd612b1ec7b10026d2566935d05d3c36f9366087cb42bc90bd25772a88fcfc9343 - languageName: node - linkType: hard - "@types/eslint-scope@npm:^3.7.3": version: 3.7.4 resolution: "@types/eslint-scope@npm:3.7.4" @@ -12545,13 +12551,6 @@ __metadata: languageName: node linkType: hard -"@types/lodash@npm:^4.14.175": - version: 4.14.195 - resolution: "@types/lodash@npm:4.14.195" - checksum: 39b75ca635b3fa943d17d3d3aabc750babe4c8212485a4df166fe0516e39288e14b0c60afc6e21913cc0e5a84734633c71e617e2bd14eaa1cf51b8d7799c432e - languageName: node - linkType: hard - "@types/long@npm:^4.0.1": version: 4.0.2 resolution: "@types/long@npm:4.0.2" @@ -12645,13 +12644,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^13.7.0": - version: 13.13.52 - resolution: "@types/node@npm:13.13.52" - checksum: 8f1afff497ebeba209e2dc340d823284e087a47632afe99a7daa30eaff80893e520f222ad400cd1f2d3b8288e93cf3eaded52a8e64eaefb8aacfe6c35de98f42 - languageName: node - linkType: hard - "@types/node@npm:^14.14.31": version: 14.18.53 resolution: "@types/node@npm:14.18.53" @@ -12853,7 +12845,7 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7.1.0, @types/semver@npm:^7.3.12": +"@types/semver@npm:^7.3.12": version: 7.5.0 resolution: "@types/semver@npm:7.5.0" checksum: 0a64b9b9c7424d9a467658b18dd70d1d781c2d6f033096a6e05762d20ebbad23c1b69b0083b0484722aabf35640b78ccc3de26368bcae1129c87e9df028a22e2 @@ -12980,13 +12972,6 @@ __metadata: languageName: node linkType: hard -"@types/treeify@npm:^1.0.0": - version: 1.0.0 - resolution: "@types/treeify@npm:1.0.0" - checksum: 1b2397030d13beee7f82b878ca80feeddb0d550a6b00d8be30082a370c0ac5985ecf7b9378cf93ea278ff00c3e900b416ae8d9379f2c7e8caecdece1dfc77380 - languageName: node - linkType: hard - "@types/trusted-types@npm:^2.0.2": version: 2.0.3 resolution: "@types/trusted-types@npm:2.0.3" @@ -14414,163 +14399,6 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/cli@npm:3.5.0": - version: 3.5.0 - resolution: "@yarnpkg/cli@npm:3.5.0" - dependencies: - "@yarnpkg/core": ^3.5.0 - "@yarnpkg/fslib": ^2.10.2 - "@yarnpkg/libzip": ^2.3.0 - "@yarnpkg/parsers": ^2.5.1 - "@yarnpkg/plugin-compat": ^3.1.10 - "@yarnpkg/plugin-dlx": ^3.1.4 - "@yarnpkg/plugin-essentials": ^3.3.0 - "@yarnpkg/plugin-file": ^2.3.1 - "@yarnpkg/plugin-git": ^2.6.5 - "@yarnpkg/plugin-github": ^2.3.1 - "@yarnpkg/plugin-http": ^2.2.1 - "@yarnpkg/plugin-init": ^3.2.1 - "@yarnpkg/plugin-link": ^2.2.1 - "@yarnpkg/plugin-nm": ^3.1.5 - "@yarnpkg/plugin-npm": ^2.7.3 - "@yarnpkg/plugin-npm-cli": ^3.3.0 - "@yarnpkg/plugin-pack": ^3.2.0 - "@yarnpkg/plugin-patch": ^3.2.4 - "@yarnpkg/plugin-pnp": ^3.2.8 - "@yarnpkg/plugin-pnpm": ^1.1.3 - "@yarnpkg/shell": ^3.2.5 - chalk: ^3.0.0 - ci-info: ^3.2.0 - clipanion: 3.2.0-rc.4 - semver: ^7.1.2 - tslib: ^1.13.0 - typanion: ^3.3.0 - yup: ^0.32.9 - peerDependencies: - "@yarnpkg/core": ^3.5.0 - checksum: 840fe59a36392cd3b09459aa8e0297e68d74b584fcbbdcd6b8770833aa45420314a14771c861825bffb354c2a2804808f0afb138c6358796129903a5645ffaa6 - languageName: node - linkType: hard - -"@yarnpkg/core@npm:3.5.0": - version: 3.5.0 - resolution: "@yarnpkg/core@npm:3.5.0" - dependencies: - "@arcanis/slice-ansi": ^1.1.1 - "@types/semver": ^7.1.0 - "@types/treeify": ^1.0.0 - "@yarnpkg/fslib": ^2.10.2 - "@yarnpkg/json-proxy": ^2.1.1 - "@yarnpkg/libzip": ^2.3.0 - "@yarnpkg/parsers": ^2.5.1 - "@yarnpkg/pnp": ^3.3.1 - "@yarnpkg/shell": ^3.2.5 - camelcase: ^5.3.1 - chalk: ^3.0.0 - ci-info: ^3.2.0 - clipanion: 3.2.0-rc.4 - cross-spawn: 7.0.3 - diff: ^5.1.0 - globby: ^11.0.1 - got: ^11.7.0 - json-file-plus: ^3.3.1 - lodash: ^4.17.15 - micromatch: ^4.0.2 - mkdirp: ^0.5.1 - p-limit: ^2.2.0 - pluralize: ^7.0.0 - pretty-bytes: ^5.1.0 - semver: ^7.1.2 - stream-to-promise: ^2.2.0 - strip-ansi: ^6.0.0 - tar: ^6.0.5 - tinylogic: ^1.0.3 - treeify: ^1.1.0 - tslib: ^1.13.0 - tunnel: ^0.0.6 - checksum: 0e7ca0fe3b3bcccddb7b2077731f48bb329f98b479ce8b1a010bb2200f5ee314e349bc42a05e0d692ddbd2e24fd8faae70a17b8c31fed9cbafd6025cc9618310 - languageName: node - linkType: hard - -"@yarnpkg/core@npm:^3.3.0, @yarnpkg/core@npm:^3.5.0": - version: 3.5.2 - resolution: "@yarnpkg/core@npm:3.5.2" - dependencies: - "@arcanis/slice-ansi": ^1.1.1 - "@types/semver": ^7.1.0 - "@types/treeify": ^1.0.0 - "@yarnpkg/fslib": ^2.10.3 - "@yarnpkg/json-proxy": ^2.1.1 - "@yarnpkg/libzip": ^2.3.0 - "@yarnpkg/parsers": ^2.5.1 - "@yarnpkg/pnp": ^3.3.3 - "@yarnpkg/shell": ^3.2.5 - camelcase: ^5.3.1 - chalk: ^3.0.0 - ci-info: ^3.2.0 - clipanion: 3.2.0-rc.4 - cross-spawn: 7.0.3 - diff: ^5.1.0 - globby: ^11.0.1 - got: ^11.7.0 - json-file-plus: ^3.3.1 - lodash: ^4.17.15 - micromatch: ^4.0.2 - mkdirp: ^0.5.1 - p-limit: ^2.2.0 - pluralize: ^7.0.0 - pretty-bytes: ^5.1.0 - semver: ^7.1.2 - stream-to-promise: ^2.2.0 - strip-ansi: ^6.0.0 - tar: ^6.0.5 - tinylogic: ^1.0.3 - treeify: ^1.1.0 - tslib: ^1.13.0 - tunnel: ^0.0.6 - checksum: 7635ea96c79195afc2146a1b8c5adfcb765a83bce2721bc0c88799a01e0e0b73243631f47b57df14667d7349aa0cf0a6b28b6ecfc9814ef329c7dd1f4c7f3826 - languageName: node - linkType: hard - -"@yarnpkg/extensions@npm:^1.1.2": - version: 1.1.2 - resolution: "@yarnpkg/extensions@npm:1.1.2" - peerDependencies: - "@yarnpkg/core": ^3.3.1 - checksum: 4293f286639296c13f4fcc012b4f9f3d7dafa6534ce54c849162965c0653ac22caf9ddedb0ac906e50a0c9ac52ad780232e2b75a00a80dc993579694da3d46cb - languageName: node - linkType: hard - -"@yarnpkg/fslib@npm:^2.10.0, @yarnpkg/fslib@npm:^2.10.1, @yarnpkg/fslib@npm:^2.10.2, @yarnpkg/fslib@npm:^2.10.3, @yarnpkg/fslib@npm:^2.5.0, @yarnpkg/fslib@npm:^2.6.2, @yarnpkg/fslib@npm:^2.7.1, @yarnpkg/fslib@npm:^2.9.0": - version: 2.10.3 - resolution: "@yarnpkg/fslib@npm:2.10.3" - dependencies: - "@yarnpkg/libzip": ^2.3.0 - tslib: ^1.13.0 - checksum: 0ca693f61d47bcf165411a121ed9123f512b1b5bfa5e1c6c8f280b4ffdbea9bf2a6db418f99ecfc9624587fdc695b2b64eb0fe7b4028e44095914b25ca99655e - languageName: node - linkType: hard - -"@yarnpkg/json-proxy@npm:^2.1.1": - version: 2.1.1 - resolution: "@yarnpkg/json-proxy@npm:2.1.1" - dependencies: - "@yarnpkg/fslib": ^2.5.0 - tslib: ^1.13.0 - checksum: 2c306b6ee158d48b15f4b09e7fb431b1096d4687c77cc49a9b37dbda04c05f13ef19175c795feefe1068668d0519a1caff7b3b7f6441a1ac6a5702ef0d60c250 - languageName: node - linkType: hard - -"@yarnpkg/libzip@npm:^2.2.4, @yarnpkg/libzip@npm:^2.3.0": - version: 2.3.0 - resolution: "@yarnpkg/libzip@npm:2.3.0" - dependencies: - "@types/emscripten": ^1.39.6 - tslib: ^1.13.0 - checksum: 533a4883f69bb013f955d80dc19719881697e6849ea5f0cbe6d87ef1d582b05cbae8a453802f92ad0c852f976296cac3ff7834be79a7e415b65cdf213e448110 - languageName: node - linkType: hard - "@yarnpkg/lockfile@npm:^1.1.0": version: 1.1.0 resolution: "@yarnpkg/lockfile@npm:1.1.0" @@ -14578,17 +14406,6 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/nm@npm:^3.1.0": - version: 3.1.0 - resolution: "@yarnpkg/nm@npm:3.1.0" - dependencies: - "@yarnpkg/core": ^3.3.0 - "@yarnpkg/fslib": ^2.9.0 - "@yarnpkg/pnp": ^3.2.5 - checksum: 771be0f0e6c785bc1f3b8750d3abed64a051ab4aa4a1602e4076ae5a34d8dd0f5cf6067a237bd2e0c9b29b37539bb2cb7e72a50dfa71f582990204347b4db55b - languageName: node - linkType: hard - "@yarnpkg/parsers@npm:3.0.0-rc.46": version: 3.0.0-rc.46 resolution: "@yarnpkg/parsers@npm:3.0.0-rc.46" @@ -14599,325 +14416,6 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/parsers@npm:^2.5.1": - version: 2.5.1 - resolution: "@yarnpkg/parsers@npm:2.5.1" - dependencies: - js-yaml: ^3.10.0 - tslib: ^1.13.0 - checksum: 42f98b8bd635add304ce392c6f600b46e40c2c4429d7b6c38b70f50b5fd6a854dd2369e0987b70546a3c8f690d280f040a885b35acfde891f5e173fc3f974277 - languageName: node - linkType: hard - -"@yarnpkg/plugin-compat@npm:^3.1.10": - version: 3.1.13 - resolution: "@yarnpkg/plugin-compat@npm:3.1.13" - dependencies: - "@yarnpkg/extensions": ^1.1.2 - peerDependencies: - "@yarnpkg/core": ^3.5.2 - "@yarnpkg/plugin-patch": ^3.2.4 - checksum: b73fdc2670cc2c1586d766cde321841efcb1d76dd36cfcce44133d581ff1b0e3a3a29331d46a2d3bc43bd94c840d23c1d3658bdef75943b418c4800f4e944917 - languageName: node - linkType: hard - -"@yarnpkg/plugin-dlx@npm:^3.1.4": - version: 3.1.4 - resolution: "@yarnpkg/plugin-dlx@npm:3.1.4" - dependencies: - "@yarnpkg/fslib": ^2.7.1 - "@yarnpkg/json-proxy": ^2.1.1 - clipanion: 3.2.0-rc.4 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/cli": ^3.2.3 - "@yarnpkg/core": ^3.2.4 - checksum: c98b94e0c884b8158ce82a782ba63d99af6f23dd655d6fff37fe3716005f74b2438fc882a2e2e2812d7a5f7d1c529943e6ed0f1fc4999548816af80063e4d779 - languageName: node - linkType: hard - -"@yarnpkg/plugin-essentials@npm:^3.3.0": - version: 3.3.0 - resolution: "@yarnpkg/plugin-essentials@npm:3.3.0" - dependencies: - "@yarnpkg/fslib": ^2.9.0 - "@yarnpkg/json-proxy": ^2.1.1 - "@yarnpkg/parsers": ^2.5.1 - ci-info: ^3.2.0 - clipanion: 3.2.0-rc.4 - enquirer: ^2.3.6 - lodash: ^4.17.15 - micromatch: ^4.0.2 - semver: ^7.1.2 - tslib: ^1.13.0 - typanion: ^3.3.0 - peerDependencies: - "@yarnpkg/cli": ^3.3.0 - "@yarnpkg/core": ^3.3.0 - "@yarnpkg/plugin-git": ^2.6.3 - checksum: babe8afdbafa8d28e2bf2339eb58b2cbed463795ed4319018ea1dfea9b8c6eeb89481484f13dfcbcf5d9fa4930c1ec3edcd37bb692c0b4071486a22c836f5cfe - languageName: node - linkType: hard - -"@yarnpkg/plugin-file@npm:^2.3.1": - version: 2.3.1 - resolution: "@yarnpkg/plugin-file@npm:2.3.1" - dependencies: - "@yarnpkg/fslib": ^2.6.2 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/core": ^3.2.1 - checksum: 0cd3a9ac59a7e24bc21e1a9ea28adf383a9a89a1585ea7810e43b7b81f3d59e2dcc134118ca95b0116e0f2fd99912e3ee6eba6ba5899642bb9bb298c2289b84d - languageName: node - linkType: hard - -"@yarnpkg/plugin-git@npm:^2.6.5": - version: 2.6.6 - resolution: "@yarnpkg/plugin-git@npm:2.6.6" - dependencies: - "@types/semver": ^7.1.0 - "@yarnpkg/fslib": ^2.10.3 - clipanion: 3.2.0-rc.4 - git-url-parse: ^13.1.0 - lodash: ^4.17.15 - semver: ^7.1.2 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/core": ^3.5.2 - checksum: 1bb90f56a934bc6006a7d2063d60a868c88e7ff59697691fd2ca02b3ab4961dcd5b49999c7e55335c2602e7601dea5c38572a53e3ea5595d50546d1245d1c4ef - languageName: node - linkType: hard - -"@yarnpkg/plugin-github@npm:^2.3.1": - version: 2.3.1 - resolution: "@yarnpkg/plugin-github@npm:2.3.1" - dependencies: - "@yarnpkg/fslib": ^2.6.2 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/core": ^3.2.1 - "@yarnpkg/plugin-git": ^2.6.1 - checksum: 45ea2817ec3ac9a600c6262d0a41bc392e6af033b4823443d4d49de939a24dfdc6e7ecd5433a925934f557ba3c5c9c2b35507294f1819b9bf23ca56cc5de88d3 - languageName: node - linkType: hard - -"@yarnpkg/plugin-http@npm:^2.2.1": - version: 2.2.1 - resolution: "@yarnpkg/plugin-http@npm:2.2.1" - dependencies: - "@yarnpkg/fslib": ^2.6.2 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/core": ^3.2.1 - checksum: 4f12902926caf9640bc8be197c6498b88471bb5f05cf331c8d64cfcaf00a965ab1ebf9989d6dde3f9dcfaea615f0246cccced270bbf9ea5b2afb577bd140fbe4 - languageName: node - linkType: hard - -"@yarnpkg/plugin-init@npm:^3.2.1": - version: 3.2.1 - resolution: "@yarnpkg/plugin-init@npm:3.2.1" - dependencies: - "@yarnpkg/fslib": ^2.10.1 - clipanion: 3.2.0-rc.4 - lodash: ^4.17.15 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/cli": ^3.4.1 - "@yarnpkg/core": ^3.4.0 - checksum: 260384a103960a81e61a980beed1aa954f6cb43c6c9f5b58dfe18ba3191aff0d42b1b785f5d689a3e0cdc83af57cfe404ba11693c55d18d863ae16ccaaaf59a9 - languageName: node - linkType: hard - -"@yarnpkg/plugin-link@npm:^2.2.1": - version: 2.2.1 - resolution: "@yarnpkg/plugin-link@npm:2.2.1" - dependencies: - "@yarnpkg/fslib": ^2.6.2 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/core": ^3.2.1 - checksum: 7fe10b656c05a390596ae41f11d89ec5b8841fe977b66f3f21b9afe7298e5c5370f9fc7fb91a6d4d728d0502027c82c02133b4476cb70d0d7bf3af62fbd2e397 - languageName: node - linkType: hard - -"@yarnpkg/plugin-nm@npm:^3.1.5": - version: 3.1.5 - resolution: "@yarnpkg/plugin-nm@npm:3.1.5" - dependencies: - "@yarnpkg/fslib": ^2.9.0 - "@yarnpkg/libzip": ^2.2.4 - "@yarnpkg/nm": ^3.1.0 - "@yarnpkg/parsers": ^2.5.1 - "@yarnpkg/plugin-pnp": ^3.2.5 - "@yarnpkg/pnp": ^3.2.5 - "@zkochan/cmd-shim": ^5.1.0 - clipanion: 3.2.0-rc.4 - micromatch: ^4.0.2 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/cli": ^3.3.0 - "@yarnpkg/core": ^3.3.0 - checksum: 65e90cca541f5feae152e483f2b0da8bfa57514bd2d25b9b40e608dd4cf5b6e325e586b2ace398581ef4a6d4a3a05c64ad4273f0c6b47354e3544404e63f6d95 - languageName: node - linkType: hard - -"@yarnpkg/plugin-npm-cli@npm:^3.3.0": - version: 3.4.0 - resolution: "@yarnpkg/plugin-npm-cli@npm:3.4.0" - dependencies: - "@yarnpkg/fslib": ^2.10.3 - clipanion: 3.2.0-rc.4 - enquirer: ^2.3.6 - micromatch: ^4.0.2 - semver: ^7.1.2 - tslib: ^1.13.0 - typanion: ^3.3.0 - peerDependencies: - "@yarnpkg/cli": ^3.6.0 - "@yarnpkg/core": ^3.5.2 - "@yarnpkg/plugin-npm": ^2.7.4 - "@yarnpkg/plugin-pack": ^3.2.0 - checksum: 3b234c5661757d0e4b74778043e2a170af2e22e1fbb8d62608b61e1db2a11924318b273d8fc1e4dbf8c4a23d6e831862206349c73fc7fbe70fe37b65070b24b2 - languageName: node - linkType: hard - -"@yarnpkg/plugin-npm@npm:^2.7.3": - version: 2.7.4 - resolution: "@yarnpkg/plugin-npm@npm:2.7.4" - dependencies: - "@yarnpkg/fslib": ^2.10.3 - enquirer: ^2.3.6 - semver: ^7.1.2 - ssri: ^6.0.1 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/core": ^3.5.2 - "@yarnpkg/plugin-pack": ^3.2.0 - checksum: 296969efab88ba3b9af182b6522179ae7bcfa7167e5f84ffe4aef65b5e6b98fef4389983b24dbc22837548e9d631b09049243a488fc7485ca0ea20ffb16b4913 - languageName: node - linkType: hard - -"@yarnpkg/plugin-pack@npm:^3.2.0": - version: 3.2.0 - resolution: "@yarnpkg/plugin-pack@npm:3.2.0" - dependencies: - "@yarnpkg/fslib": ^2.10.2 - clipanion: 3.2.0-rc.4 - micromatch: ^4.0.2 - tar-stream: ^2.0.1 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/cli": ^3.5.0 - "@yarnpkg/core": ^3.5.0 - checksum: 441edd3c84ea0eb0a2ea6192ee536e075c85c98d9f67f67a484e2d551f4c8d420a9803a1b3c59b3e9f44e5c84a833392f1ccf120087d7d1c594ccf54f4ff007a - languageName: node - linkType: hard - -"@yarnpkg/plugin-patch@npm:^3.2.4": - version: 3.2.4 - resolution: "@yarnpkg/plugin-patch@npm:3.2.4" - dependencies: - "@yarnpkg/fslib": ^2.9.0 - "@yarnpkg/libzip": ^2.2.4 - clipanion: 3.2.0-rc.4 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/cli": ^3.3.0 - "@yarnpkg/core": ^3.3.0 - checksum: 77cf0644e1215ab553b50c2ad9cebe852716692a53cf226cf19cdde2caa05220faee5a56d1bf30006117eef8f0954dac771016acc94cc2f95af8cd83e5a18acc - languageName: node - linkType: hard - -"@yarnpkg/plugin-pnp@npm:^3.2.5, @yarnpkg/plugin-pnp@npm:^3.2.6, @yarnpkg/plugin-pnp@npm:^3.2.8": - version: 3.2.11 - resolution: "@yarnpkg/plugin-pnp@npm:3.2.11" - dependencies: - "@types/semver": ^7.1.0 - "@yarnpkg/fslib": ^2.10.3 - "@yarnpkg/plugin-stage": ^3.1.3 - "@yarnpkg/pnp": ^3.3.4 - clipanion: 3.2.0-rc.4 - micromatch: ^4.0.2 - semver: ^7.1.2 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/cli": ^3.6.1 - "@yarnpkg/core": ^3.5.2 - checksum: 5fcd1e9fdc696a5dc0949edb95cedb16244ae4728de6d34c670f827720b973b3e923b16c957f2f1d607b733897772605035614ab1008ad8e220716172545f74c - languageName: node - linkType: hard - -"@yarnpkg/plugin-pnpm@npm:^1.1.3": - version: 1.1.3 - resolution: "@yarnpkg/plugin-pnpm@npm:1.1.3" - dependencies: - "@yarnpkg/fslib": ^2.10.0 - "@yarnpkg/plugin-pnp": ^3.2.6 - "@yarnpkg/plugin-stage": ^3.1.3 - clipanion: 3.2.0-rc.4 - p-limit: ^2.2.0 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/cli": ^3.3.1 - "@yarnpkg/core": ^3.3.1 - checksum: 3d01d53fd312cc16f9dd61338c3c4bc213e3c7c321581506753786e881eb58137a2e645663b74354f8849740fd4cd2a179f520a2e0af73235ffbdf4f775ba143 - languageName: node - linkType: hard - -"@yarnpkg/plugin-stage@npm:^3.1.3": - version: 3.1.3 - resolution: "@yarnpkg/plugin-stage@npm:3.1.3" - dependencies: - "@yarnpkg/fslib": ^2.7.1 - clipanion: 3.2.0-rc.4 - tslib: ^1.13.0 - peerDependencies: - "@yarnpkg/cli": ^3.2.3 - "@yarnpkg/core": ^3.2.4 - checksum: 7d4602f4e5692daf0faa70a8eadecd51308f1ab5de00dda546cdf28a18ad69d66d0fb15a61193ceb9c69aac00b826cb75e612ae5b0205151cb6c876ed41d5fd8 - languageName: node - linkType: hard - -"@yarnpkg/pnp@npm:^3.2.5, @yarnpkg/pnp@npm:^3.3.1, @yarnpkg/pnp@npm:^3.3.3, @yarnpkg/pnp@npm:^3.3.4": - version: 3.3.4 - resolution: "@yarnpkg/pnp@npm:3.3.4" - dependencies: - "@types/node": ^13.7.0 - "@yarnpkg/fslib": ^2.10.3 - checksum: ab6a5425997d18dc0d3897f29218d484eb417cf964640a37380e16ac7862390b63bb24227164568bec59da7d70dc1028ee0a1cc7356eb8c605c94e7cbffe67eb - languageName: node - linkType: hard - -"@yarnpkg/shell@npm:^3.2.5": - version: 3.2.5 - resolution: "@yarnpkg/shell@npm:3.2.5" - dependencies: - "@yarnpkg/fslib": ^2.9.0 - "@yarnpkg/parsers": ^2.5.1 - chalk: ^3.0.0 - clipanion: 3.2.0-rc.4 - cross-spawn: 7.0.3 - fast-glob: ^3.2.2 - micromatch: ^4.0.2 - stream-buffers: ^3.0.2 - tslib: ^1.13.0 - bin: - shell: ./lib/cli.js - checksum: 89fe80fec6ccd5a1a713ea11285bce17fe1f3cc42507b4e63565818c4afb41e588d368cf7c198fe2b3eeb900cae87233c2d52c27da288a57f82f85a07cf9b221 - languageName: node - linkType: hard - -"@zkochan/cmd-shim@npm:^5.1.0": - version: 5.4.1 - resolution: "@zkochan/cmd-shim@npm:5.4.1" - dependencies: - cmd-extension: ^1.0.2 - graceful-fs: ^4.2.10 - is-windows: ^1.0.2 - checksum: 418b4b3a5fb36960f9a96adb6ba3946be0494ab06be1959c8b2805fe37f7ebca9d185108138e30c7ff193eac74eebd10a361eca949a608c9e16afbffd6a48482 - languageName: node - linkType: hard - "@zkochan/js-yaml@npm:0.0.7": version: 0.0.7 resolution: "@zkochan/js-yaml@npm:0.0.7" @@ -15077,6 +14575,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.12.1": + version: 8.12.1 + resolution: "acorn@npm:8.12.1" + bin: + acorn: bin/acorn + checksum: 677880034aee5bdf7434cc2d25b641d7bedb0b5ef47868a78dadabedccf58e1c5457526d9d8249cd253f2df087e081c3fe7d903b448d8e19e5131a3065b83c07 + languageName: node + linkType: hard + "address@npm:^1.0.1, address@npm:^1.1.2": version: 1.2.2 resolution: "address@npm:1.2.2" @@ -15341,7 +14848,7 @@ __metadata: languageName: node linkType: hard -"any-promise@npm:^1.0.0, any-promise@npm:^1.1.0, any-promise@npm:~1.3.0": +"any-promise@npm:^1.0.0": version: 1.3.0 resolution: "any-promise@npm:1.3.0" checksum: 0ee8a9bdbe882c90464d75d1f55cf027f5458650c4bd1f0467e65aec38ccccda07ca5844969ee77ed46d04e7dded3eaceb027e8d32f385688523fe305fa7e1de @@ -15781,7 +15288,7 @@ __metadata: languageName: node linkType: hard -"asap@npm:~2.0.3, asap@npm:~2.0.6": +"asap@npm:~2.0.6": version: 2.0.6 resolution: "asap@npm:2.0.6" checksum: b296c92c4b969e973260e47523207cd5769abd27c245a68c26dc7a0fe8053c55bb04360237cb51cab1df52be939da77150ace99ad331fb7fb13b3423ed73ff3d @@ -17611,18 +17118,7 @@ __metadata: languageName: node linkType: hard -"chevrotain@npm:^9.1.0": - version: 9.1.0 - resolution: "chevrotain@npm:9.1.0" - dependencies: - "@chevrotain/types": ^9.1.0 - "@chevrotain/utils": ^9.1.0 - regexp-to-ast: 0.5.0 - checksum: 632d0d7c69081e3cc3a08c071cb738c46499a05f1a513b7f9101f7a9b5570d6ee62cac5ba506659a85bf9e71e1029c462dbb7bd9fe1bfe019b6c1879ca29c525 - languageName: node - linkType: hard - -"chokidar@npm:^3.4.0": +"chokidar@npm:^3.4.0, chokidar@npm:^3.6.0": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -17884,17 +17380,6 @@ __metadata: languageName: node linkType: hard -"clipanion@npm:3.2.0-rc.4": - version: 3.2.0-rc.4 - resolution: "clipanion@npm:3.2.0-rc.4" - dependencies: - typanion: ^3.3.1 - peerDependencies: - typanion: "*" - checksum: c9d8ba9e16dca3016c32f42107a7602c52c9176626e0c815113c32b614ca125a9707221ec9df9c0a06e9741a23e0664153db1522c4f80b29f4b4d427fba002be - languageName: node - linkType: hard - "clipboardy@npm:^4.0.0": version: 4.0.0 resolution: "clipboardy@npm:4.0.0" @@ -17994,13 +17479,6 @@ __metadata: languageName: node linkType: hard -"cmd-extension@npm:^1.0.2": - version: 1.0.2 - resolution: "cmd-extension@npm:1.0.2" - checksum: 4cbcdd53196a3c1db3484f67aa49ed83c0e6069713f60193a94d747cb84050e8e64d688673aa5159cf0184e054cb806ceb6119e45744f721cbd3a09a3e7038cb - languageName: node - linkType: hard - "co@npm:^4.6.0": version: 4.6.0 resolution: "co@npm:4.6.0" @@ -18703,17 +18181,6 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:7.0.3, cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": - version: 7.0.3 - resolution: "cross-spawn@npm:7.0.3" - dependencies: - path-key: ^3.1.0 - shebang-command: ^2.0.0 - which: ^2.0.1 - checksum: 671cc7c7288c3a8406f3c69a3ae2fc85555c04169e9d611def9a675635472614f1c0ed0ef80955d5b6d4e724f6ced67f0ad1bb006c2ea643488fcfef994d7f52 - languageName: node - linkType: hard - "cross-spawn@npm:^5.0.1": version: 5.1.0 resolution: "cross-spawn@npm:5.1.0" @@ -18738,6 +18205,17 @@ __metadata: languageName: node linkType: hard +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": + version: 7.0.3 + resolution: "cross-spawn@npm:7.0.3" + dependencies: + path-key: ^3.1.0 + shebang-command: ^2.0.0 + which: ^2.0.1 + checksum: 671cc7c7288c3a8406f3c69a3ae2fc85555c04169e9d611def9a675635472614f1c0ed0ef80955d5b6d4e724f6ced67f0ad1bb006c2ea643488fcfef994d7f52 + languageName: node + linkType: hard + "crossws@npm:^0.1.0": version: 0.1.1 resolution: "crossws@npm:0.1.1" @@ -19813,7 +19291,7 @@ __metadata: concurrently: ^8.2.2 eslint: ^8.40.0 ethers: ^5.7.2 - next: 13.3.1 + next: 13.4.11 postcss: 8.4.31 react: ^18.2.0 react-dom: ^18.2.0 @@ -19864,13 +19342,6 @@ __metadata: languageName: node linkType: hard -"diff@npm:^5.1.0": - version: 5.1.0 - resolution: "diff@npm:5.1.0" - checksum: c7bf0df7c9bfbe1cf8a678fd1b2137c4fb11be117a67bc18a0e03ae75105e8533dbfb1cda6b46beb3586ef5aed22143ef9d70713977d5fb1f9114e21455fba90 - languageName: node - linkType: hard - "diff@npm:^5.2.0": version: 5.2.0 resolution: "diff@npm:5.2.0" @@ -20364,6 +19835,21 @@ __metadata: languageName: node linkType: hard +"elliptic@npm:^6.5.7": + version: 6.5.7 + resolution: "elliptic@npm:6.5.7" + dependencies: + bn.js: ^4.11.9 + brorand: ^1.1.0 + hash.js: ^1.0.0 + hmac-drbg: ^1.0.1 + inherits: ^2.0.4 + minimalistic-assert: ^1.0.1 + minimalistic-crypto-utils: ^1.0.1 + checksum: af0ffddffdbc2fea4eeec74388cd73e62ed5a0eac6711568fb28071566319785df529c968b0bf1250ba4bc628e074b2d64c54a633e034aa6f0c6b152ceb49ab8 + languageName: node + linkType: hard + "embla-carousel-react@npm:^8.1.5": version: 8.1.5 resolution: "embla-carousel-react@npm:8.1.5" @@ -20497,15 +19983,6 @@ __metadata: languageName: node linkType: hard -"end-of-stream@npm:~1.1.0": - version: 1.1.0 - resolution: "end-of-stream@npm:1.1.0" - dependencies: - once: ~1.3.0 - checksum: 9fa637e259e50e5e3634e8e14064a183bd0d407733594631362f9df596409739bef5f7064840e6725212a9edc8b4a70a5a3088ac423e8564f9dc183dd098c719 - languageName: node - linkType: hard - "engine.io-client@npm:~6.5.2": version: 6.5.4 resolution: "engine.io-client@npm:6.5.4" @@ -21705,7 +21182,7 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8": +"eslint@npm:^8, eslint@npm:^8.40.0": version: 8.57.0 resolution: "eslint@npm:8.57.0" dependencies: @@ -21753,7 +21230,7 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.3.0, eslint@npm:^8.40.0": +"eslint@npm:^8.3.0": version: 8.45.0 resolution: "eslint@npm:8.45.0" dependencies: @@ -22663,7 +22140,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0": +"fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0": version: 3.3.0 resolution: "fast-glob@npm:3.3.0" dependencies: @@ -22757,13 +22234,6 @@ __metadata: languageName: node linkType: hard -"figgy-pudding@npm:^3.5.1": - version: 3.5.2 - resolution: "figgy-pudding@npm:3.5.2" - checksum: 4090bd66193693dcda605e44d6b8715d8fb5c92a67acd57826e55cf816a342f550d57e5638f822b39366e1b2fdb244e99b3068a37213aa1d6c1bf602b8fde5ae - languageName: node - linkType: hard - "figures@npm:3.2.0, figures@npm:^3.0.0, figures@npm:^3.2.0": version: 3.2.0 resolution: "figures@npm:3.2.0" @@ -23718,7 +23188,7 @@ __metadata: languageName: node linkType: hard -"git-url-parse@npm:13.1.0, git-url-parse@npm:^13.1.0": +"git-url-parse@npm:13.1.0": version: 13.1.0 resolution: "git-url-parse@npm:13.1.0" dependencies: @@ -23867,6 +23337,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^11.0.0": + version: 11.0.0 + resolution: "glob@npm:11.0.0" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^4.0.1 + minimatch: ^10.0.0 + minipass: ^7.1.2 + package-json-from-dist: ^1.0.0 + path-scurry: ^2.0.0 + bin: + glob: dist/esm/bin.mjs + checksum: 8a2dd914d5776987be5244624d9491bbcaf19f2387e06783737003ff696ebfd2264190c47014f8709c1c02d8bc892f17660cf986c587b107e194c0a3151ab333 + languageName: node + linkType: hard + "glob@npm:^5.0.15": version: 5.0.15 resolution: "glob@npm:5.0.15" @@ -23989,7 +23475,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.0.1, globby@npm:^11.0.4, globby@npm:^11.1.0": +"globby@npm:^11.0.4, globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -24031,7 +23517,7 @@ __metadata: languageName: node linkType: hard -"got@npm:^11.7.0, got@npm:^11.8.5": +"got@npm:^11.8.5": version: 11.8.6 resolution: "got@npm:11.8.6" dependencies: @@ -24076,20 +23562,13 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 languageName: node linkType: hard -"grapheme-splitter@npm:^1.0.4": - version: 1.0.4 - resolution: "grapheme-splitter@npm:1.0.4" - checksum: 0c22ec54dee1b05cd480f78cf14f732cb5b108edc073572c4ec205df4cd63f30f8db8025afc5debc8835a8ddeacf648a1c7992fe3dcd6ad38f9a476d84906620 - languageName: node - linkType: hard - "graphemer@npm:^1.4.0": version: 1.4.0 resolution: "graphemer@npm:1.4.0" @@ -24934,7 +24413,7 @@ __metadata: languageName: node linkType: hard -"husky@npm:^8.0.0, husky@npm:^8.0.3": +"husky@npm:^8.0.3": version: 8.0.3 resolution: "husky@npm:8.0.3" bin: @@ -25420,7 +24899,7 @@ __metadata: languageName: node linkType: hard -"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.1.5, is-callable@npm:^1.2.7": +"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": version: 1.2.7 resolution: "is-callable@npm:1.2.7" checksum: 61fd57d03b0d984e2ed3720fb1c7a897827ea174bd44402878e059542ea8c4aeedee0ea0985998aa5cc2736b2fa6e271c08587addb5b3959ac52cf665173d1ac @@ -26027,13 +25506,6 @@ __metadata: languageName: node linkType: hard -"is@npm:^3.2.1": - version: 3.3.0 - resolution: "is@npm:3.3.0" - checksum: 81fad3b40c606984c2d0699207c4c48d2a0d29cc834b274d0b74c172f3eeebdb981301fe0d690ce090a96bf021a8a1f8b1325262ad9870c525e557ac4a559c56 - languageName: node - linkType: hard - "isarray@npm:1.0.0, isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" @@ -26280,6 +25752,19 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^4.0.1": + version: 4.0.1 + resolution: "jackspeak@npm:4.0.1" + dependencies: + "@isaacs/cliui": ^8.0.2 + "@pkgjs/parseargs": ^0.11.0 + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 7989d19eddeff0631ef653df413e26290db77dc3791438bd12b56bed1c0b24d5d535fdfec13cf35775cd5b47f8ee57d36fd0bceaf2df672b1f523533fd4184cc + languageName: node + linkType: hard + "jake@npm:^10.8.5": version: 10.8.7 resolution: "jake@npm:10.8.7" @@ -28397,19 +27882,6 @@ __metadata: languageName: node linkType: hard -"json-file-plus@npm:^3.3.1": - version: 3.3.1 - resolution: "json-file-plus@npm:3.3.1" - dependencies: - is: ^3.2.1 - node.extend: ^2.0.0 - object.assign: ^4.1.0 - promiseback: ^2.0.2 - safer-buffer: ^2.0.2 - checksum: 162c7a0c8f3e5a7eeea945aac4f2578c11567c87d3e2eafdd9db6972d1fc8657cc1e04b8a23ff3731759da794057a932f251a4db06a267abf5d181977753cf38 - languageName: node - linkType: hard - "json-parse-even-better-errors@npm:^2.3.0, json-parse-even-better-errors@npm:^2.3.1": version: 2.3.1 resolution: "json-parse-even-better-errors@npm:2.3.1" @@ -29089,6 +28561,13 @@ __metadata: languageName: node linkType: hard +"load-tsconfig@npm:^0.2.5": + version: 0.2.5 + resolution: "load-tsconfig@npm:0.2.5" + checksum: 631740833c4a7157bb7b6eeae6e1afb6a6fac7416b7ba91bd0944d5c5198270af2d68bf8347af3cc2ba821adc4d83ef98f66278bd263bc284c863a09ec441503 + languageName: node + linkType: hard + "loader-runner@npm:^4.2.0": version: 4.3.0 resolution: "loader-runner@npm:4.3.0" @@ -29170,13 +28649,6 @@ __metadata: languageName: node linkType: hard -"lodash-es@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash-es@npm:4.17.21" - checksum: 05cbffad6e2adbb331a4e16fbd826e7faee403a1a04873b82b42c0f22090f280839f85b95393f487c1303c8a3d2a010048bf06151a6cbe03eee4d388fb0a12d2 - languageName: node - linkType: hard - "lodash.camelcase@npm:^4.3.0": version: 4.3.0 resolution: "lodash.camelcase@npm:4.3.0" @@ -29443,6 +28915,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^11.0.0": + version: 11.0.0 + resolution: "lru-cache@npm:11.0.0" + checksum: c29385f9369b1a566e1db9eda9a4b12f6507de906e5720ca12844dd775b7139c42b8e5837e7d5162bcc292ce4d3eecfa74ec2856c6afcc0caa2e3c9ea3a17f27 + languageName: node + linkType: hard + "lru-cache@npm:^4.0.1": version: 4.1.5 resolution: "lru-cache@npm:4.1.5" @@ -29966,6 +29445,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.0.0": + version: 10.0.1 + resolution: "minimatch@npm:10.0.1" + dependencies: + brace-expansion: ^2.0.1 + checksum: f5b63c2f30606091a057c5f679b067f84a2cd0ffbd2dbc9143bda850afd353c7be81949ff11ae0c86988f07390eeca64efd7143ee05a0dab37f6c6b38a2ebb6c + languageName: node + linkType: hard + "minimatch@npm:^5.0.1, minimatch@npm:^5.1.0, minimatch@npm:^5.1.6": version: 5.1.6 resolution: "minimatch@npm:5.1.6" @@ -30111,7 +29599,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:0.5.x, mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.6, mkdirp@npm:~0.5.1": +"mkdirp@npm:0.5.x, mkdirp@npm:^0.5.6, mkdirp@npm:~0.5.1": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -30374,13 +29862,6 @@ __metadata: languageName: node linkType: hard -"nanoclone@npm:^0.2.1": - version: 0.2.1 - resolution: "nanoclone@npm:0.2.1" - checksum: 96b2954e22f70561f41e20d69856266c65583c2a441dae108f1dc71b716785d2c8038dac5f1d5e92b117aed3825f526b53139e2e5d6e6db8a77cfa35b3b8bf40 - languageName: node - linkType: hard - "nanoid@npm:^3.3.4, nanoid@npm:^3.3.6": version: 3.3.6 resolution: "nanoid@npm:3.3.6" @@ -30485,29 +29966,30 @@ __metadata: languageName: node linkType: hard -"next@npm:13.3.1": - version: 13.3.1 - resolution: "next@npm:13.3.1" - dependencies: - "@next/env": 13.3.1 - "@next/swc-darwin-arm64": 13.3.1 - "@next/swc-darwin-x64": 13.3.1 - "@next/swc-linux-arm64-gnu": 13.3.1 - "@next/swc-linux-arm64-musl": 13.3.1 - "@next/swc-linux-x64-gnu": 13.3.1 - "@next/swc-linux-x64-musl": 13.3.1 - "@next/swc-win32-arm64-msvc": 13.3.1 - "@next/swc-win32-ia32-msvc": 13.3.1 - "@next/swc-win32-x64-msvc": 13.3.1 - "@swc/helpers": 0.5.0 +"next@npm:13.4.11": + version: 13.4.11 + resolution: "next@npm:13.4.11" + dependencies: + "@next/env": 13.4.11 + "@next/swc-darwin-arm64": 13.4.11 + "@next/swc-darwin-x64": 13.4.11 + "@next/swc-linux-arm64-gnu": 13.4.11 + "@next/swc-linux-arm64-musl": 13.4.11 + "@next/swc-linux-x64-gnu": 13.4.11 + "@next/swc-linux-x64-musl": 13.4.11 + "@next/swc-win32-arm64-msvc": 13.4.11 + "@next/swc-win32-ia32-msvc": 13.4.11 + "@next/swc-win32-x64-msvc": 13.4.11 + "@swc/helpers": 0.5.1 busboy: 1.6.0 caniuse-lite: ^1.0.30001406 postcss: 8.4.14 styled-jsx: 5.1.1 + watchpack: 2.4.0 + zod: 3.21.4 peerDependencies: "@opentelemetry/api": ^1.1.0 fibers: ">= 3.1.0" - node-sass: ^6.0.0 || ^7.0.0 react: ^18.2.0 react-dom: ^18.2.0 sass: ^1.3.0 @@ -30535,13 +30017,11 @@ __metadata: optional: true fibers: optional: true - node-sass: - optional: true sass: optional: true bin: next: dist/bin/next - checksum: a685abbcfe028940f8e3c86f6712fadcba1ca92c68b4dddfd1192d93a4ebac2d0947cd62d8cecf530afbd8163b2ef6dfe338b1fe7b699d78bc7123624a7622db + checksum: 3d6a7443bc1f22bc98db1cb87096df1e97c511a7cb8b0ee24469ea20bcfe50ba0b2baeb7e7ad9dba4c92cc126fc6809ba7f1690510ee2f54226bfd3173b921bd languageName: node linkType: hard @@ -30945,16 +30425,6 @@ __metadata: languageName: node linkType: hard -"node.extend@npm:^2.0.0": - version: 2.0.2 - resolution: "node.extend@npm:2.0.2" - dependencies: - has: ^1.0.3 - is: ^3.2.1 - checksum: 1fe3a1ca7fc35392f169c8a46d889d07deb201bba3a20d17df23efab509698c9639737b0c235c9be772a34035e749bae5d477f74c9e26a1b67c78bd7d6dce8e4 - languageName: node - linkType: hard - "nofilter@npm:^3.1.0": version: 3.1.0 resolution: "nofilter@npm:3.1.0" @@ -31281,7 +30751,7 @@ __metadata: languageName: node linkType: hard -"object.assign@npm:^4.1.0, object.assign@npm:^4.1.2, object.assign@npm:^4.1.4": +"object.assign@npm:^4.1.2, object.assign@npm:^4.1.4": version: 4.1.4 resolution: "object.assign@npm:4.1.4" dependencies: @@ -31505,15 +30975,6 @@ __metadata: languageName: node linkType: hard -"once@npm:~1.3.0": - version: 1.3.3 - resolution: "once@npm:1.3.3" - dependencies: - wrappy: 1 - checksum: 8e832de08b1d73b470e01690c211cb4fcefccab1fd1bd19e706d572d74d3e9b7e38a8bfcdabdd364f9f868757d9e8e5812a59817dc473eaf698ff3bfae2219f2 - languageName: node - linkType: hard - "onetime@npm:^5.1.0, onetime@npm:^5.1.2": version: 5.1.2 resolution: "onetime@npm:5.1.2" @@ -32133,6 +31594,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^2.0.0": + version: 2.0.0 + resolution: "path-scurry@npm:2.0.0" + dependencies: + lru-cache: ^11.0.0 + minipass: ^7.1.2 + checksum: 9953ce3857f7e0796b187a7066eede63864b7e1dfc14bf0484249801a5ab9afb90d9a58fc533ebb1b552d23767df8aa6a2c6c62caf3f8a65f6ce336a97bbb484 + languageName: node + linkType: hard + "path-to-regexp@npm:0.1.7": version: 0.1.7 resolution: "path-to-regexp@npm:0.1.7" @@ -32546,13 +32017,6 @@ __metadata: languageName: node linkType: hard -"pluralize@npm:^7.0.0": - version: 7.0.0 - resolution: "pluralize@npm:7.0.0" - checksum: e3f694924b7c8c03dc9fa40b2312e17787998ac6e20fccace11efa1146046eb9931541bfd247b3ec5535e730d902a5aee7c32681d5bf9a00fc74a72039a3e609 - languageName: node - linkType: hard - "pngjs@npm:^5.0.0": version: 5.0.0 resolution: "pngjs@npm:5.0.0" @@ -33623,7 +33087,7 @@ __metadata: languageName: node linkType: hard -"pretty-bytes@npm:^5.1.0, pretty-bytes@npm:^5.3.0, pretty-bytes@npm:^5.4.1, pretty-bytes@npm:^5.6.0": +"pretty-bytes@npm:^5.3.0, pretty-bytes@npm:^5.4.1, pretty-bytes@npm:^5.6.0": version: 5.6.0 resolution: "pretty-bytes@npm:5.6.0" checksum: 9c082500d1e93434b5b291bd651662936b8bd6204ec9fa17d563116a192d6d86b98f6d328526b4e8d783c07d5499e2614a807520249692da9ec81564b2f439cd @@ -33729,15 +33193,6 @@ __metadata: languageName: node linkType: hard -"promise-deferred@npm:^2.0.3": - version: 2.0.3 - resolution: "promise-deferred@npm:2.0.3" - dependencies: - promise: ^7.3.1 - checksum: 2e640ddd1e21da2543d66e589d6fa970eca8fa3a1e88629db3cd095cb77427536cdc426646bd092f6db05ff5e28e29f0ad87fb4e44d7529af9914e8e4b9e9899 - languageName: node - linkType: hard - "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -33762,15 +33217,6 @@ __metadata: languageName: node linkType: hard -"promise@npm:^7.3.1": - version: 7.3.1 - resolution: "promise@npm:7.3.1" - dependencies: - asap: ~2.0.3 - checksum: 475bb069130179fbd27ed2ab45f26d8862376a137a57314cf53310bdd85cc986a826fd585829be97ebc0aaf10e9d8e68be1bfe5a4a0364144b1f9eedfa940cf1 - languageName: node - linkType: hard - "promise@npm:^8.0.0, promise@npm:^8.1.0": version: 8.3.0 resolution: "promise@npm:8.3.0" @@ -33780,16 +33226,6 @@ __metadata: languageName: node linkType: hard -"promiseback@npm:^2.0.2": - version: 2.0.3 - resolution: "promiseback@npm:2.0.3" - dependencies: - is-callable: ^1.1.5 - promise-deferred: ^2.0.3 - checksum: c4d75176df643be766cd11fca2df38fac83e62a1c5a9e3d5c89acb4d32080ce7f14c74b6794e8ea1d15687edb88df60404882105a47e27aecfa7e45800f68464 - languageName: node - linkType: hard - "prompts@npm:^2.0.1, prompts@npm:^2.4.2": version: 2.4.2 resolution: "prompts@npm:2.4.2" @@ -33843,13 +33279,6 @@ __metadata: languageName: node linkType: hard -"property-expr@npm:^2.0.4": - version: 2.0.5 - resolution: "property-expr@npm:2.0.5" - checksum: 4ebe82ce45aaf1527e96e2ab84d75d25217167ec3ff6378cf83a84fb4abc746e7c65768a79d275881602ae82f168f9a6dfaa7f5e331d0fcc83d692770bcce5f1 - languageName: node - linkType: hard - "proto-list@npm:~1.2.1": version: 1.2.4 resolution: "proto-list@npm:1.2.4" @@ -34902,13 +34331,6 @@ __metadata: languageName: node linkType: hard -"regexp-to-ast@npm:0.5.0": - version: 0.5.0 - resolution: "regexp-to-ast@npm:0.5.0" - checksum: 72e32f2a1217bb22398ac30867ddd43e16943b6b569dd4eb472de47494c7a39e34f47ee3e92ad4cbf92308f98997da366fe094a0e58eb6b93eab0ee956fff86d - languageName: node - linkType: hard - "regexp.prototype.flags@npm:^1.4.3, regexp.prototype.flags@npm:^1.5.0": version: 1.5.0 resolution: "regexp.prototype.flags@npm:1.5.0" @@ -35433,6 +34855,18 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:^6.0.1": + version: 6.0.1 + resolution: "rimraf@npm:6.0.1" + dependencies: + glob: ^11.0.0 + package-json-from-dist: ^1.0.0 + bin: + rimraf: dist/esm/bin.mjs + checksum: 8ba5b84131c1344e9417cb7e8c05d8368bb73cbe5dd4c1d5eb49fc0b558209781658d18c450460e30607d0b7865bb067482839a2f343b186b07ae87715837e66 + languageName: node + linkType: hard + "ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1": version: 2.0.2 resolution: "ripemd160@npm:2.0.2" @@ -35982,7 +35416,7 @@ __metadata: resolution: "seaport-core@https://github.com/immutable/seaport-core.git#commit=0633350ec34f21fcede657ff812f11cf7d19144e" dependencies: seaport-types: ^0.0.1 - checksum: 392bce86bbfc4f7c00b65575b238825f4c696bddf5af08be7aa496862e63879375387fd4400f6e900ffee08d65c1f75cf3adad9c6c41ddcf7a3b0389cd73c3c7 + checksum: d8adba0d54106c6fe9370f0775fadef2198e5eab440b36919d1f917705ce2f0a7028e4da021b6df049aa3ca35d7e673a28b78a731130f0ff9fdf7a8bd32e3b94 languageName: node linkType: hard @@ -36026,7 +35460,7 @@ __metadata: seaport-sol: ^1.5.0 seaport-types: ^0.0.1 solady: ^0.0.84 - checksum: f31a7443a50fa1c35ec03ea031743d1d10896653ae443fa15ab8e6f5b4a2ca43f6743d523ae4e1f14df867451e5b2b2130b0bfa58a1085b0bcae3fceb8dfdc9b + checksum: a77e141e4ab5d2c4bb190a38fbb6cda3011fdf5f350b250fbeb4d82ae81cf917a966a2dcb8d9e4fd1bed29e5510ede9b15941b0ac77aeb4272dab94c9f51e7ff languageName: node linkType: hard @@ -36129,7 +35563,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.5.4, semver@npm:^7.1.2, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.4.0, semver@npm:^7.5.2, semver@npm:^7.5.3": +"semver@npm:7.5.4, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.4.0, semver@npm:^7.5.2, semver@npm:^7.5.3": version: 7.5.4 resolution: "semver@npm:7.5.4" dependencies: @@ -37087,15 +36521,6 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^6.0.1": - version: 6.0.2 - resolution: "ssri@npm:6.0.2" - dependencies: - figgy-pudding: ^3.5.1 - checksum: 7c2e5d442f6252559c8987b7114bcf389fe5614bf65de09ba3e6f9a57b9b65b2967de348fcc3acccff9c069adb168140dd2c5fc2f6f4a779e604a27ef1f7d551 - languageName: node - linkType: hard - "stable@npm:^0.1.8": version: 0.1.8 resolution: "stable@npm:0.1.8" @@ -37194,13 +36619,6 @@ __metadata: languageName: node linkType: hard -"stream-buffers@npm:^3.0.2": - version: 3.0.2 - resolution: "stream-buffers@npm:3.0.2" - checksum: b09fdeea606e3113ebd0e07010ed0cf038608fa396130add9e45deaff5cc3ba845dc25c31ad24f8341f85907846344cb7c85f75ea52c6572e2ac646e9b6072d0 - languageName: node - linkType: hard - "stream-http@npm:^3.2.0": version: 3.2.0 resolution: "stream-http@npm:3.2.0" @@ -37220,26 +36638,6 @@ __metadata: languageName: node linkType: hard -"stream-to-array@npm:~2.3.0": - version: 2.3.0 - resolution: "stream-to-array@npm:2.3.0" - dependencies: - any-promise: ^1.1.0 - checksum: 7feaf63b38399b850615e6ffcaa951e96e4c8f46745dbce4b553a94c5dc43966933813747014935a3ff97793e7f30a65270bde19f82b2932871a1879229a77cf - languageName: node - linkType: hard - -"stream-to-promise@npm:^2.2.0": - version: 2.2.0 - resolution: "stream-to-promise@npm:2.2.0" - dependencies: - any-promise: ~1.3.0 - end-of-stream: ~1.1.0 - stream-to-array: ~2.3.0 - checksum: 2c9ddb69c34d10ad27eb06197abc93fd1b1cd5f9597ead28ade4d6c57f4110d948a2ef14530f2f7b3b967f74f3554b57c38a4501b72a13b27fc8745bd7190d1d - languageName: node - linkType: hard - "streamsearch@npm:^1.1.0": version: 1.1.0 resolution: "streamsearch@npm:1.1.0" @@ -38167,7 +37565,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^2.0.0, tar-stream@npm:^2.0.1, tar-stream@npm:^2.2.0, tar-stream@npm:~2.2.0": +"tar-stream@npm:^2.0.0, tar-stream@npm:^2.2.0, tar-stream@npm:~2.2.0": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -38191,7 +37589,7 @@ __metadata: languageName: node linkType: hard -"tar@npm:^6.0.5, tar@npm:^6.1.11, tar@npm:^6.1.2": +"tar@npm:^6.1.11, tar@npm:^6.1.2": version: 6.1.15 resolution: "tar@npm:6.1.15" dependencies: @@ -38480,15 +37878,6 @@ __metadata: languageName: node linkType: hard -"tinylogic@npm:^1.0.3": - version: 1.0.3 - resolution: "tinylogic@npm:1.0.3" - dependencies: - chevrotain: ^9.1.0 - checksum: fdf7fcc170050889b210fd035b1eb2ac81a68d1324010a427eeee53ac49613ecaa3fbd33b41adb1264dfb02b4d500b3f442da1db3ffc53834c654345c1658afa - languageName: node - linkType: hard - "titleize@npm:^3.0.0": version: 3.0.0 resolution: "titleize@npm:3.0.0" @@ -38599,13 +37988,6 @@ __metadata: languageName: node linkType: hard -"toposort@npm:^2.0.2": - version: 2.0.2 - resolution: "toposort@npm:2.0.2" - checksum: d64c74b570391c9432873f48e231b439ee56bc49f7cb9780b505cfdf5cb832f808d0bae072515d93834dd6bceca5bb34448b5b4b408335e4d4716eaf68195dcb - languageName: node - linkType: hard - "tough-cookie@npm:^4.0.0, tough-cookie@npm:^4.1.2, tough-cookie@npm:^4.1.3": version: 4.1.3 resolution: "tough-cookie@npm:4.1.3" @@ -38957,7 +38339,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:1.14.1, tslib@npm:^1.13.0, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": +"tslib@npm:1.14.1, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd @@ -39054,13 +38436,6 @@ __metadata: languageName: node linkType: hard -"typanion@npm:^3.3.0, typanion@npm:^3.3.1": - version: 3.13.0 - resolution: "typanion@npm:3.13.0" - checksum: 7d1506ab3a635ca5aaf84696829092f4cf6949c7995e950e0a744f55b4d4a824e2cf22278d37c323396188240d0003bd10de14a64da8e1ded3ddd71dcec2d146 - languageName: node - linkType: hard - "type-check@npm:^0.4.0, type-check@npm:~0.4.0": version: 0.4.0 resolution: "type-check@npm:0.4.0" @@ -39642,6 +39017,31 @@ __metadata: languageName: node linkType: hard +"unplugin-swc@npm:^1.5.1": + version: 1.5.1 + resolution: "unplugin-swc@npm:1.5.1" + dependencies: + "@rollup/pluginutils": ^5.1.0 + load-tsconfig: ^0.2.5 + unplugin: ^1.11.0 + peerDependencies: + "@swc/core": ^1.2.108 + checksum: f278ccb085510f4cec3943a0f896465c67caee012bcea31beb630a8455adf190682745ee44cd791a922999d43c9f36fe5cbbe2e6e8f81dfbbae4d0ee07fc0642 + languageName: node + linkType: hard + +"unplugin@npm:^1.11.0": + version: 1.12.2 + resolution: "unplugin@npm:1.12.2" + dependencies: + acorn: ^8.12.1 + chokidar: ^3.6.0 + webpack-sources: ^3.2.3 + webpack-virtual-modules: ^0.6.2 + checksum: 69daad6c52fe8ce871070d56880cadad7770fa24419ff169eb38b0b481f5578abb27b9963f3ed3b6794a217f0761b5371c2dd3708cdd40417f96ef36f7e259d5 + languageName: node + linkType: hard + "unquote@npm:~1.1.1": version: 1.1.1 resolution: "unquote@npm:1.1.1" @@ -40320,7 +39720,7 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.4.0": +"watchpack@npm:2.4.0, watchpack@npm:^2.4.0": version: 2.4.0 resolution: "watchpack@npm:2.4.0" dependencies: @@ -40701,6 +40101,13 @@ __metadata: languageName: node linkType: hard +"webpack-virtual-modules@npm:^0.6.2": + version: 0.6.2 + resolution: "webpack-virtual-modules@npm:0.6.2" + checksum: 7e8e1d63f35864c815420cc2f27da8561a1e028255040698a352717de0ba46d3b3faf16f06c1a1965217054c4c2894eb9af53a85451870e919b5707ce9c5822d + languageName: node + linkType: hard + "webpack@npm:^5.64.4": version: 5.88.2 resolution: "webpack@npm:5.88.2" @@ -41733,21 +41140,6 @@ __metadata: languageName: node linkType: hard -"yup@npm:^0.32.9": - version: 0.32.11 - resolution: "yup@npm:0.32.11" - dependencies: - "@babel/runtime": ^7.15.4 - "@types/lodash": ^4.14.175 - lodash: ^4.17.21 - lodash-es: ^4.17.21 - nanoclone: ^0.2.1 - property-expr: ^2.0.4 - toposort: ^2.0.2 - checksum: 43a16786b47cc910fed4891cebdd89df6d6e31702e9462e8f969c73eac88551ce750732608012201ea6b93802c8847cb0aa27b5d57370640f4ecf30f9f97d4b0 - languageName: node - linkType: hard - "zip-stream@npm:^4.1.0": version: 4.1.1 resolution: "zip-stream@npm:4.1.1"