Compare commits
372
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9919026f38 | ||
|
|
c349f44d58 | ||
|
|
fc7c6b6211 | ||
|
|
55b00eff26 | ||
|
|
76d219e6be | ||
|
|
f13408a33d | ||
|
|
74b19012c2 | ||
|
|
8f85868324 | ||
|
|
448b357ed4 | ||
|
|
912738bd0a | ||
|
|
f1aecc3049 | ||
|
|
0b38dac852 | ||
|
|
440a4ade95 | ||
|
|
29d0c62610 | ||
|
|
f295ec67b4 | ||
|
|
1686fb4b2c | ||
|
|
85dd18e8f0 | ||
|
|
ec1f2324c7 | ||
|
|
b59a31c365 | ||
|
|
d29f7e78f0 | ||
|
|
3e2b392b6b | ||
|
|
cf96333fa2 | ||
|
|
4213cfee77 | ||
|
|
25ef4be50f | ||
|
|
ab1f8bb6f6 | ||
|
|
5f8ec45af7 | ||
|
|
b45d761f8f | ||
|
|
04de5b2783 | ||
|
|
dee4596549 | ||
|
|
3547901ddd | ||
|
|
fe7ab1c5d3 | ||
|
|
bdd9ed2298 | ||
|
|
0977454d45 | ||
|
|
19b8e18b6b | ||
|
|
7c0060b7fd | ||
|
|
0a07d7a248 | ||
|
|
8c81ca5c30 | ||
|
|
c911d183e6 | ||
|
|
f15733f34c | ||
|
|
a6c074c3f3 | ||
|
|
fdb9baedee | ||
|
|
9e591b32b7 | ||
|
|
12ec3be4a3 | ||
|
|
48893c730e | ||
|
|
f37e86b7d2 | ||
|
|
62b7388bb2 | ||
|
|
065c4db41b | ||
|
|
431ab23ee2 | ||
|
|
836484ba98 | ||
|
|
5047686f2a | ||
|
|
7cab8d1679 | ||
|
|
0f0125acf2 | ||
|
|
894cbefc63 | ||
|
|
73dc026ff9 | ||
|
|
af32007b9f | ||
|
|
5fba324ac1 | ||
|
|
bff433e121 | ||
|
|
68c8e3c4d7 | ||
|
|
4e8ff58208 | ||
|
|
d0d5a3c23d | ||
|
|
9eaa21d09a | ||
|
|
557429b5cf | ||
|
|
96d2dfd25d | ||
|
|
5862c558fb | ||
|
|
e913e0daa9 | ||
|
|
f7a74a3b7c | ||
|
|
73883f56d0 | ||
|
|
e47085af92 | ||
|
|
f525e2cc23 | ||
|
|
af85389dc0 | ||
|
|
ccb4ce8d00 | ||
|
|
4cdd0bd634 | ||
|
|
ea95c5f609 | ||
|
|
699a2b16fb | ||
|
|
a8267f912d | ||
|
|
c673e39f23 | ||
|
|
08d04fe4dc | ||
|
|
cbf5508bd5 | ||
|
|
3731459d33 | ||
|
|
64f24f1bb1 | ||
|
|
cac6c30bca | ||
|
|
e7740a59db | ||
|
|
7a37e564fd | ||
|
|
e173636f4b | ||
|
|
8c6cdaa288 | ||
|
|
ad9b3febff | ||
|
|
94f49b1294 | ||
|
|
517e2a75e3 | ||
|
|
72692689e9 | ||
|
|
efc56fa580 | ||
|
|
564b17e760 | ||
|
|
b4507db963 | ||
|
|
f451da6395 | ||
|
|
ee47840767 | ||
|
|
bef97f72f4 | ||
|
|
3e87dd3ccb | ||
|
|
def42511ff | ||
|
|
c42545a647 | ||
|
|
6e20d1f21a | ||
|
|
e684d7e1ca | ||
|
|
f545a67e9f | ||
|
|
034a257442 | ||
|
|
1bfb91dbf3 | ||
|
|
5d95cbcdb2 | ||
|
|
639e535ed5 | ||
|
|
af3acc6b10 | ||
|
|
eca47763cd | ||
|
|
37b5bec897 | ||
|
|
48d91e4431 | ||
|
|
a20be881e3 | ||
|
|
47f8945325 | ||
|
|
1b0bf99da7 | ||
|
|
f03be398e0 | ||
|
|
27996cb4bc | ||
|
|
2eaa67acbe | ||
|
|
aafd7968df | ||
|
|
48797c7cf0 | ||
|
|
4edcb37100 | ||
|
|
83dcaf7058 | ||
|
|
b4e005be7f | ||
|
|
ccb7db4873 | ||
|
|
9151aa7dc8 | ||
|
|
5a01c537e3 | ||
|
|
f09c153ff7 | ||
|
|
15431621fd | ||
|
|
887af24450 | ||
|
|
15514f190a | ||
|
|
72589ec234 | ||
|
|
f8bceec10c | ||
|
|
7c0744a006 | ||
|
|
846022e2cf | ||
|
|
96bd92e724 | ||
|
|
c2b6d977ed | ||
|
|
7cb8e59be4 | ||
|
|
04a47e8971 | ||
|
|
8a6a97a3e9 | ||
|
|
612bcdd16f | ||
|
|
0ed77d2c9e | ||
|
|
7129aef6e1 | ||
|
|
f50b74819d | ||
|
|
3eba69afd7 | ||
|
|
98c2253e8f | ||
|
|
32d91ef709 | ||
|
|
b79dc18f7a | ||
|
|
4af785c37c | ||
|
|
6f818f393e | ||
|
|
97ed4779a9 | ||
|
|
2d3fca5237 | ||
|
|
7016019f2a | ||
|
|
4e7b293c54 | ||
|
|
c9c0e4ff39 | ||
|
|
06c24c153a | ||
|
|
aa6d9ed75d | ||
|
|
42a606588d | ||
|
|
2820761003 | ||
|
|
5200d5d7e7 | ||
|
|
e731c1a7c3 | ||
|
|
2e8d3fd6e7 | ||
|
|
cc4613b5fd | ||
|
|
78566babaa | ||
|
|
65fc7ba6ff | ||
|
|
1c24c3c3c4 | ||
|
|
408c5a81fa | ||
|
|
352291791e | ||
|
|
aabbb149e8 | ||
|
|
d59450f814 | ||
|
|
7d9a4c6abc | ||
|
|
99806e595d | ||
|
|
21c21c1adf | ||
|
|
4b10719047 | ||
|
|
989cf7347c | ||
|
|
72227b2e16 | ||
|
|
ed6c54f20d | ||
|
|
26116a6f63 | ||
|
|
f11a2719ff | ||
|
|
ef27aad0aa | ||
|
|
b03a902145 | ||
|
|
340605b507 | ||
|
|
15f8006e1f | ||
|
|
811c5a1166 | ||
|
|
c41a80d86d | ||
|
|
ad851f7b4d | ||
|
|
6218c88584 | ||
|
|
c9b564feaf | ||
|
|
87fd6ab14e | ||
|
|
df74340fa5 | ||
|
|
a57b4f01f3 | ||
|
|
126752d7d0 | ||
|
|
89ff921a7d | ||
|
|
9124b3180c | ||
|
|
f41a443c57 | ||
|
|
b7cd744cd0 | ||
|
|
bb1b4e6ac3 | ||
|
|
2f8ec89c4c | ||
|
|
6ae52664e5 | ||
|
|
4f867e15ed | ||
|
|
cc8f983615 | ||
|
|
f45ef62128 | ||
|
|
9727502eb3 | ||
|
|
b6eec6146c | ||
|
|
88daa3c5cb | ||
|
|
53c6507669 | ||
|
|
782b58d3f6 | ||
|
|
e17e41f4d1 | ||
|
|
712b11a43e | ||
|
|
306debb029 | ||
|
|
86f0ceff46 | ||
|
|
7f0d1ec947 | ||
|
|
b1218cbd7d | ||
|
|
97ef35ce0f | ||
|
|
1f46f270cd | ||
|
|
e8c25ed0f2 | ||
|
|
78d6d3c159 | ||
|
|
c72d92956a | ||
|
|
cf88795376 | ||
|
|
47287fb189 | ||
|
|
3763326c25 | ||
|
|
894eb27dfa | ||
|
|
8b3b97c8f7 | ||
|
|
89d87a5eb3 | ||
|
|
492a818079 | ||
|
|
86f608390a | ||
|
|
8096c78b46 | ||
|
|
521a49c6ec | ||
|
|
25a4773bba | ||
|
|
18381fac73 | ||
|
|
89b5c6fe07 | ||
|
|
4dccccc6fa | ||
|
|
de15e4212f | ||
|
|
1f41adbd2c | ||
|
|
1918495f2e | ||
|
|
24514a6e9f | ||
|
|
ad06c5b289 | ||
|
|
9de3f8c8e9 | ||
|
|
b1fa17158d | ||
|
|
2bdf1984cb | ||
|
|
df048af605 | ||
|
|
41507d32a7 | ||
|
|
b2cadf164c | ||
|
|
57b9c671aa | ||
|
|
3b7d10ed93 | ||
|
|
d6bb61b7fa | ||
|
|
8e591383fc | ||
|
|
6f95a520e6 | ||
|
|
f669d3ef4a | ||
|
|
83575058db | ||
|
|
028cb78125 | ||
|
|
c8261b74b5 | ||
|
|
a36c788415 | ||
|
|
ac232b5aff | ||
|
|
ba6f45027a | ||
|
|
d677e9d634 | ||
|
|
9b92697fb7 | ||
|
|
c19e471f65 | ||
|
|
b6fc298d0c | ||
|
|
ea41231b3e | ||
|
|
2ea3f00c41 | ||
|
|
20745f8cd6 | ||
|
|
caf979537b | ||
|
|
e4b10bc5bd | ||
|
|
ef0bac7f44 | ||
|
|
23de9eac4f | ||
|
|
1972f3ec0d | ||
|
|
ffd0fc42ae | ||
|
|
93531a5d47 | ||
|
|
b16aaebc4c | ||
|
|
1136d959ba | ||
|
|
db23f0588d | ||
|
|
e662408e19 | ||
|
|
afe4ba6471 | ||
|
|
6647f707bd | ||
|
|
cac373b010 | ||
|
|
9412d41683 | ||
|
|
2506eeb25d | ||
|
|
ba7dcbbad8 | ||
|
|
cd1abe6ba7 | ||
|
|
e1bcc67ceb | ||
|
|
d26b76872a | ||
|
|
9711b51067 | ||
|
|
e2037310a6 | ||
|
|
683d542334 | ||
|
|
9c8a3fb6d4 | ||
|
|
41accc2e21 | ||
|
|
f2a62110c4 | ||
|
|
db9f811bf8 | ||
|
|
fc5efe9521 | ||
|
|
82397636f4 | ||
|
|
27a30e3ffb | ||
|
|
2f1155239d | ||
|
|
c295a47144 | ||
|
|
0dade96d0d | ||
|
|
666ff5f694 | ||
|
|
8bc41cdc60 | ||
|
|
37a707f98c | ||
|
|
3c8f6205e9 | ||
|
|
1eaedc262f | ||
|
|
e187d21e12 | ||
|
|
b030a86608 | ||
|
|
acbabc8999 | ||
|
|
196195e2f9 | ||
|
|
7c86babb86 | ||
|
|
36f2b70d57 | ||
|
|
849146bf5c | ||
|
|
4cac79c45d | ||
|
|
1bda83b114 | ||
|
|
ce05bfd861 | ||
|
|
d982a98b0c | ||
|
|
b983087101 | ||
|
|
b25789fa46 | ||
|
|
e39b66f309 | ||
|
|
5f5ab015e1 | ||
|
|
f4f05bd9a4 | ||
|
|
2f5b66bef9 | ||
|
|
63258bc9c3 | ||
|
|
a80fc97b6a | ||
|
|
24f66e38c2 | ||
|
|
b5bfaefa85 | ||
|
|
493ebf032e | ||
|
|
d608cea197 | ||
|
|
a9ec190ff9 | ||
|
|
3edec391be | ||
|
|
bdec5a59af | ||
|
|
9d03619aab | ||
|
|
0ccbca78dc | ||
|
|
75c752cc14 | ||
|
|
3580182fa9 | ||
|
|
b54f95dc90 | ||
|
|
f4074ddadc | ||
|
|
208b0389a5 | ||
|
|
3b877ba221 | ||
|
|
45a400fd92 | ||
|
|
3332fa5697 | ||
|
|
fdda564b1e | ||
|
|
89db3db7ff | ||
|
|
bd9c705571 | ||
|
|
0ad0f21bcd | ||
|
|
4169b9428f | ||
|
|
2f9e8aaab4 | ||
|
|
421461ece0 | ||
|
|
485ebc8400 | ||
|
|
772f8431e3 | ||
|
|
5345479922 | ||
|
|
cfe8bbafa4 | ||
|
|
18dc8d33ed | ||
|
|
f8ab4779bb | ||
|
|
6fe16c5bf2 | ||
|
|
ba0e169654 | ||
|
|
27b869ed93 | ||
|
|
091ce2eb45 | ||
|
|
e24a371508 | ||
|
|
b86afb4b09 | ||
|
|
964add6f20 | ||
|
|
ec414bbc42 | ||
|
|
51dde28224 | ||
|
|
57432d21c4 | ||
|
|
19d87c2885 | ||
|
|
68812e9d27 | ||
|
|
90c53475f0 | ||
|
|
5a52585a7a | ||
|
|
6b48b9288b | ||
|
|
9d74a43aac | ||
|
|
9c7e5518ff | ||
|
|
b234c58d44 | ||
|
|
b1ae844e8b | ||
|
|
78bae497ba | ||
|
|
3e425bf8e6 | ||
|
|
4dca5b21a6 | ||
|
|
42c79e93dc | ||
|
|
2951b5533c | ||
|
|
fe8751eeae | ||
|
|
a0cd47ec35 | ||
|
|
3902e7aba4 |
@@ -2,14 +2,17 @@
|
||||
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
|
||||
"changelog": ["@changesets/changelog-github", { "repo": "thirdweb-dev/js" }],
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"fixed": [
|
||||
["@thirdweb-dev/sdk", "@thirdweb-dev/react-core", "@thirdweb-dev/react"]
|
||||
],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": [],
|
||||
"ignore": ["e2e-cra-5", "e2e-next-12", "e2e-vite-3"],
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"updateInternalDependents": "always"
|
||||
"updateInternalDependents": "always",
|
||||
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
||||
},
|
||||
"snapshot": {
|
||||
"useCalculatedVersion": true,
|
||||
|
||||
+60
-56
@@ -1,5 +1,9 @@
|
||||
# Contributing to thirdweb
|
||||
|
||||
You can find a full video tutorial on how to contribute to thirdweb below:
|
||||
|
||||
https://www.youtube.com/watch?v=TXsQ3qok3B0
|
||||
|
||||
## Getting Started
|
||||
|
||||
To get started, read the [How this repo works](#how-this-repo-works) section below to learn about the structure of this repo.
|
||||
@@ -16,7 +20,7 @@ If you have any questions about the issue, feel free to ask on our [Discord serv
|
||||
|
||||
We use [Turborepo](https://turborepo.org/docs/getting-started) to manage the monorepo, and help speed up the [CI/CD](https://www.atlassian.com/continuous-delivery/principles/continuous-integration-vs-delivery-vs-deployment) pipeline to ship to production faster 🚢.
|
||||
|
||||
You can see a quick outline of each of the projects within this repo below, which each live within the [/packages](/packages) directory:
|
||||
You can see a quick outline of each of the projects within this repo below, each living within the [/packages](/packages) directory:
|
||||
|
||||
| Package | Description | Latest Version |
|
||||
| ------------------------------ | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -58,75 +62,75 @@ To begin:
|
||||
|
||||
Now you have got the repo on your local machine, and you're ready to start making your changes!
|
||||
|
||||
### Test Your Changes
|
||||
|
||||
We use [yalc](https://github.com/wclr/yalc) to test changes locally.
|
||||
|
||||
Install the yalc CLI globally:
|
||||
|
||||
```bash
|
||||
yarn global add yalc
|
||||
```
|
||||
|
||||
First, create a test project where you can experiment with your changes:
|
||||
|
||||
You can create a basic starter project with the `sdk` and `react` packages installed using the CLI:
|
||||
|
||||
```bash
|
||||
npx thirdweb create --app
|
||||
```
|
||||
|
||||
Use `yalc` to link your local changes in the monorepo to the test project, by running the following command from your test repo:
|
||||
|
||||
```bash
|
||||
yalc add @thirdweb-dev/react # Link the react package
|
||||
yalc add @thirdweb-dev/sdk # Link the sdk package
|
||||
# etc...
|
||||
```
|
||||
|
||||
From the monorepo, run the following command to publish your local changes to the test project:
|
||||
|
||||
```bash
|
||||
yarn push
|
||||
```
|
||||
|
||||
Now, each time you make a change to the monorepo, you can run `yarn push` to publish your changes to the test project.
|
||||
|
||||
In your test project, you need to:
|
||||
|
||||
1. Delete the dependencies cache. If you're using Next.js, that is the `.next` directory, and if you're using CRA, that is the `node_modules/.cache` directory.
|
||||
2. Restart the development server.
|
||||
|
||||
<br/>
|
||||
|
||||
### Publish Your Changes
|
||||
|
||||
Once you're satisfied with your changes, you are ready to submit them for review!
|
||||
|
||||
1. Use [changeset](https://github.com/changesets/changesets) to generate a changeset file:
|
||||
|
||||
```
|
||||
yarn changeset
|
||||
```
|
||||
```
|
||||
yarn changeset
|
||||
```
|
||||
|
||||
We follow [semantic versioning](https://semver.org/) for generating versioned releases of our packages (i.e. version = `MAJOR.MINOR.PATCH`)
|
||||
We follow [semantic versioning](https://semver.org/) for generating versioned releases of our packages (i.e. version = `MAJOR.MINOR.PATCH`)
|
||||
|
||||
- Update `major` for breaking changes
|
||||
- Update `minor` for new features,
|
||||
- Update `patch` for non-breaking bug fixes, etc)
|
||||
- Update `major` for breaking changes
|
||||
- Update `minor` for new features,
|
||||
- Update `patch` for non-breaking bug fixes, etc)
|
||||
|
||||
2. Commit the changeset along with your changes:
|
||||
|
||||
```
|
||||
git commit -am "My commit message"
|
||||
```
|
||||
```
|
||||
git commit -am "My commit message"
|
||||
```
|
||||
|
||||
3. Push your changes to the SDK:
|
||||
|
||||
```
|
||||
git push origin MY_BRANCH_NAME
|
||||
```
|
||||
```
|
||||
git push origin MY_BRANCH_NAME
|
||||
```
|
||||
|
||||
4. Create a [pull request](https://www.atlassian.com/git/tutorials/making-a-pull-request) to the `main` branch of the official (not your fork) SDK repo.
|
||||
|
||||
It's helpful to tag PRs with `[SDK]`, `[REACT]`, `[AUTH]`, (the name of the package you're modifying) to indicate the package that you are engaging with.
|
||||
|
||||
<br/>
|
||||
|
||||
### Testing Your Changes
|
||||
|
||||
When you make a pull request, your changes will be tested automatically by our CI/CD pipeline using [GitHub Actions](https://github.com/features/actions).
|
||||
|
||||
On your pull request, you can release your changes to a `dev` version by adding a **comment** with the following text:
|
||||
|
||||
```
|
||||
/release-pr
|
||||
```
|
||||
|
||||
This comment triggers the `release-pr` GitHub Action that publishes your changes to a `dev` version of the package(s) you are modifying.
|
||||
|
||||
Once this is done, you can test your changes by installing the `dev` version of the package(s).
|
||||
|
||||
If you don't already have a test project, you can use the [CLI](https://github.com/thirdweb-dev/thirdweb-cli) to create one:
|
||||
|
||||
```bash
|
||||
npx thirdweb create
|
||||
```
|
||||
|
||||
Inside your test project, you can install the `dev` version of the package(s) you are modifying.
|
||||
|
||||
For example, if you are modifying the `@thirdweb-dev/react` package, you can install the `dev` version of the package by running:
|
||||
|
||||
```
|
||||
yarn add @thirdweb-dev/sdk@dev
|
||||
```
|
||||
|
||||
This will install the `dev` version of the `@thirdweb-dev/react` package in your project, as you can see in the `package.json` file:
|
||||
|
||||
```diff
|
||||
"dependencies": {
|
||||
- "@thirdweb-dev/react": "^2",
|
||||
+ "@thirdweb-dev/react": "^2.9.7-dev-b073878",
|
||||
}
|
||||
```
|
||||
|
||||
You can now test your changes in your test project!
|
||||
It's helpful to tag PRs with `[SDK]`, `[REACT]`, `[AUTH]`, (the name of the package you're modifying) to indicate the package that you are engaging with.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
@@ -13,8 +13,8 @@ concurrency:
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Test
|
||||
# timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-32
|
||||
# To use Remote Caching, uncomment the next lines and follow the steps below.
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
@@ -24,13 +24,11 @@ jobs:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
|
||||
- name: Test
|
||||
# this already builds anything that changed...
|
||||
- name: Build & Test
|
||||
run: yarn test
|
||||
|
||||
@@ -12,8 +12,8 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
integration-tests:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-32
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -27,9 +27,3 @@ jobs:
|
||||
run: yarn e2e
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: packages/react/playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
# To use Remote Caching, uncomment the next lines and follow the steps below.
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install
|
||||
uses: ./.github/composite-actions/install
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
deploy-check:
|
||||
# this just checks if ur allowed to do the action so does not need to run on a fast machine
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: acknowledge deployment request to commenter
|
||||
@@ -39,7 +40,8 @@ jobs:
|
||||
comment_body: ${{ github.event.comment.body }}
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-latest-32
|
||||
timeout-minutes: 30
|
||||
needs: deploy-check
|
||||
if: needs.deploy-check.outputs.triggered == 'true'
|
||||
steps:
|
||||
|
||||
@@ -14,7 +14,8 @@ concurrency:
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-32
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v3
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Compressed Size
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest-32
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: preactjs/compressed-size-action@v2
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
strip-hash: "\\-(\\w{8})(?:\\.esm|\\.cjs)"
|
||||
minimum-change-threshold: 100
|
||||
# exclude source maps, node modules and any test related files
|
||||
exclude: "{**/*.map,**/node_modules/**,**/test/**}"
|
||||
+6
-1
@@ -12,4 +12,9 @@ server/dist
|
||||
public/dist
|
||||
.turbo
|
||||
./packages/contracts-js/abis
|
||||
.idea/
|
||||
.idea/
|
||||
.yalc/
|
||||
yalc.lock
|
||||
build/
|
||||
playwright-report/
|
||||
.vscode/
|
||||
@@ -1,4 +1,3 @@
|
||||
lib
|
||||
node_modules
|
||||
dist
|
||||
contracts-js
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
| [/auth](./packages/auth) | Best in class wallet authentication for Node backends | <a href="https://www.npmjs.com/package/@thirdweb-dev/auth"><img src="https://img.shields.io/npm/v/@thirdweb-dev/auth?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/storage](./packages/storage) | Best in class decentralized storage SDK for Browser and Node | <a href="https://www.npmjs.com/package/@thirdweb-dev/storage"><img src="https://img.shields.io/npm/v/@thirdweb-dev/storage?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/cli](./packages/cli) | Publish and deploy smart contracts without dealing with private keys | <a href="https://www.npmjs.com/package/thirdweb"><img src="https://img.shields.io/npm/v/thirdweb?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/solana](./packages/solana) | Solana SDK for Browser, Node and React Native | <a href="https://www.npmjs.com/package/@thirdweb-dev/solana"><img src="https://img.shields.io/npm/v/@thirdweb-dev/solana?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
+11
-12
@@ -8,14 +8,15 @@
|
||||
"private": true,
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*"
|
||||
"packages/*",
|
||||
"test/**/*"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"test": "turbo run test",
|
||||
"test": "turbo run test test:evm test:solana",
|
||||
"e2e": "turbo run e2e",
|
||||
"build": "turbo run build",
|
||||
"build:release": "turbo run build --force",
|
||||
"build": "turbo run build --filter=./packages/*",
|
||||
"build:release": "turbo run build --force --filter=./packages/*",
|
||||
"dev": "turbo run dev --no-cache --parallel --continue",
|
||||
"fix:repo": "manypkg fix",
|
||||
"lint": "turbo run lint",
|
||||
@@ -26,12 +27,8 @@
|
||||
"version-packages": "changeset version",
|
||||
"release": "node ./scripts/pre-release.mjs && changeset publish",
|
||||
"version-packages:nightly": "changeset version --snapshot nightly",
|
||||
"release:nightly": "node ./scripts/pre-release.mjs && changeset publish --tag nightly"
|
||||
},
|
||||
"preconstruct": {
|
||||
"packages": [
|
||||
"packages/*"
|
||||
]
|
||||
"release:nightly": "node ./scripts/pre-release.mjs && changeset publish --tag nightly",
|
||||
"push": "turbo run push --filter=./packages/*"
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"dependencies": {
|
||||
@@ -39,11 +36,13 @@
|
||||
"@changesets/cli": "^2.24.3",
|
||||
"@manypkg/cli": "^0.19.1",
|
||||
"@manypkg/get-packages": "^1.1.3",
|
||||
"@trivago/prettier-plugin-sort-imports": "^3.3.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.0.0",
|
||||
"eslint": "^8.21.0",
|
||||
"mocha": "^10.2.0",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.7.1",
|
||||
"turbo": "latest"
|
||||
"turbo": "^1.4.6",
|
||||
"yalc": "^1.0.0-pre.53"
|
||||
},
|
||||
"manypkg": {
|
||||
"defaultBranch": "main"
|
||||
|
||||
@@ -1,5 +1,181 @@
|
||||
# @thirdweb-dev/auth
|
||||
|
||||
## 3.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#533](https://github.com/thirdweb-dev/js/pull/533) [`dee4596`](https://github.com/thirdweb-dev/js/commit/dee45965496d5d0298944031dd13a4345f9e1683) Thanks [@adam-maj](https://github.com/adam-maj)! - Remove next major version pin on auth
|
||||
|
||||
## 3.0.2
|
||||
|
||||
## 3.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#520](https://github.com/thirdweb-dev/js/pull/520) [`8c81ca5`](https://github.com/thirdweb-dev/js/commit/8c81ca5c3033b04b1f64e3a1134a72e7e3ec03b1) Thanks [@adam-maj](https://github.com/adam-maj)! - Update auth and react-core dependencies
|
||||
|
||||
## 3.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- [#460](https://github.com/thirdweb-dev/js/pull/460) [`a6c074c`](https://github.com/thirdweb-dev/js/commit/a6c074c3f33148cd17f5a66a58df9272a4381bab) Thanks [@adam-maj](https://github.com/adam-maj)! - Complete Auth redesign and update to add a number of major and quality of life improvements, including the following:
|
||||
|
||||
- Ability to use Auth APIs with both cookies and JWTs, allowing non browser clients to interact with Auth (mobile, gaming, scripts, etc.)
|
||||
- Ability to store session data and other data on the Auth user
|
||||
- Callbacks to run side-effects on login, logout, and requesting user data
|
||||
- Ability to configure cookies for custom domains and backend setups
|
||||
- Support for validation of the entire EIP4361/CAIP122 specification
|
||||
- No more need for redirects or payload encoding on Auth requests
|
||||
- and more...
|
||||
|
||||
See the new documentation to view the new changes and usage: [Auth Documentation](https://portal.thirdweb.com/auth).
|
||||
|
||||
## How to upgrade
|
||||
|
||||
The `ThirdwebAuth` constructor now takes the `domain` in the constructor, and takes a more generic `wallet` interface as input. The `wallet` can be imported from the `@thirdweb-dev/wallets` package, or for more simpler use cases, from the `@thirdweb-dev/auth/evm` and `@thirdweb-dev/auth/solana` entrypoints.
|
||||
|
||||
```js
|
||||
import { PrivateKeyWallet } from "@thirdweb-dev/auth/evm";
|
||||
|
||||
// Pass in domain and wallet to the constructor
|
||||
const wallet = new PrivateKeyWallet("0x...");
|
||||
const auth = new ThirdwebAuth(wallet, "example.com");
|
||||
|
||||
// Auth functions no longer require domain to be passed in
|
||||
const payload = await auth.login();
|
||||
```
|
||||
|
||||
## 2.0.41
|
||||
|
||||
## 2.0.40
|
||||
|
||||
## 2.0.39
|
||||
|
||||
## 2.0.38
|
||||
|
||||
## 2.0.37
|
||||
|
||||
## 2.0.36
|
||||
|
||||
## 2.0.35
|
||||
|
||||
## 2.0.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#425](https://github.com/thirdweb-dev/js/pull/425) [`f545a67`](https://github.com/thirdweb-dev/js/commit/f545a67e9fb597d27bb39ca515d24d306fbb121a) Thanks [@adam-maj](https://github.com/adam-maj)! - Add sameSite none and include credentials
|
||||
|
||||
## 2.0.33
|
||||
|
||||
## 2.0.32
|
||||
|
||||
## 2.0.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#397](https://github.com/thirdweb-dev/js/pull/397) [`72589ec`](https://github.com/thirdweb-dev/js/commit/72589ec234d86939b67b0ca8b3b789f2e9c54cc2) Thanks [@adam-maj](https://github.com/adam-maj)! - Call user callback in getUser
|
||||
|
||||
## 2.0.30
|
||||
|
||||
## 2.0.29
|
||||
|
||||
## 2.0.28
|
||||
|
||||
## 2.0.27
|
||||
|
||||
## 2.0.26
|
||||
|
||||
## 2.0.25
|
||||
|
||||
## 2.0.24
|
||||
|
||||
## 2.0.23
|
||||
|
||||
## 2.0.22
|
||||
|
||||
## 2.0.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#327](https://github.com/thirdweb-dev/js/pull/327) [`ef27aad`](https://github.com/thirdweb-dev/js/commit/ef27aad0aafc4577e85f44dc77dfbe880bd239b5) Thanks [@jnsdls](https://github.com/jnsdls)! - enable e2e testing
|
||||
|
||||
## 2.0.20
|
||||
|
||||
## 2.0.19
|
||||
|
||||
## 2.0.18
|
||||
|
||||
## 2.0.17
|
||||
|
||||
## 2.0.16
|
||||
|
||||
## 2.0.15
|
||||
|
||||
## 2.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#274](https://github.com/thirdweb-dev/js/pull/274) [`ad06c5b`](https://github.com/thirdweb-dev/js/commit/ad06c5b28902422f9b416d65255c64c937a5e046) Thanks [@adam-maj](https://github.com/adam-maj)! - Add solana auth support and plugins for react and auth package
|
||||
|
||||
## 2.0.13
|
||||
|
||||
## 2.0.12
|
||||
|
||||
## 2.0.11
|
||||
|
||||
## 2.0.10
|
||||
|
||||
## 2.0.9
|
||||
|
||||
## 2.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ce05bfd`](https://github.com/thirdweb-dev/js/commit/ce05bfd8615a9c79664856bce53de8b43bed5c87)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`5f5ab01`](https://github.com/thirdweb-dev/js/commit/5f5ab015e1dd3c471d6affe995ef36ec88932b3c)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 2.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`a80fc97`](https://github.com/thirdweb-dev/js/commit/a80fc97b6a1e72ed46a400b4b602e180947fb870)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 2.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4169b94`](https://github.com/thirdweb-dev/js/commit/4169b9428f9001b7cad259a4e56fe610316cd191), [`3b877ba`](https://github.com/thirdweb-dev/js/commit/3b877ba221acfd85f80b99e1bc382055217f0a39), [`b54f95d`](https://github.com/thirdweb-dev/js/commit/b54f95dc906928ff2f9251748f254a16fe1f2cee), [`f8ab477`](https://github.com/thirdweb-dev/js/commit/f8ab4779bb2d6d66200e1e8fd558e0ac069a2f54), [`772f843`](https://github.com/thirdweb-dev/js/commit/772f8431e3a62d0ded62dae90a43e9a7edd5b1a2), [`a9ec190`](https://github.com/thirdweb-dev/js/commit/a9ec190ff99d2714cef2500d20ea0f3f73f07be3), [`208b038`](https://github.com/thirdweb-dev/js/commit/208b0389a50ea48bbb9600fec60fec2f1671d4b9), [`5345479`](https://github.com/thirdweb-dev/js/commit/534547992243bdd3a77e34ec2b2487b5adab366a)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 2.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 2.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`51dde28`](https://github.com/thirdweb-dev/js/commit/51dde28224209f1b8b26f436c204a5e702281564), [`9d74a43`](https://github.com/thirdweb-dev/js/commit/9d74a43aac21448beba63ba4e2637945965a3634), [`b234c58`](https://github.com/thirdweb-dev/js/commit/b234c58d44d8322e44b2d2ba87ad4ec7d699e961)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 2.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`42c79e9`](https://github.com/thirdweb-dev/js/commit/42c79e93dc958ca46a55d705aeea44ffdbbcc5f6), [`fe8751e`](https://github.com/thirdweb-dev/js/commit/fe8751eeae7ad013b890a8092ddbd091ecbd6708)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 2.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -16,9 +16,13 @@
|
||||
Install the latest version of the SDK with either `npm` or `yarn`:
|
||||
|
||||
```shell
|
||||
npm install @thirdweb-dev/auth @thirdweb-dev/sdk ethers
|
||||
npm install @thirdweb-dev/auth
|
||||
```
|
||||
|
||||
```shell
|
||||
yarn add @thirdweb-dev/auth @thirdweb-dev/sdk ethers
|
||||
yarn add @thirdweb-dev/auth
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Auth Docs](https://portal.thirdweb.com/auth)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-auth-evm.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth-evm.esm.js"
|
||||
}
|
||||
+78
-27
@@ -1,78 +1,128 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "2.0.1",
|
||||
"version": "3.0.3",
|
||||
"main": "dist/thirdweb-dev-auth.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth.esm.js",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"module": "./dist/thirdweb-dev-auth.esm.js",
|
||||
"default": "./dist/thirdweb-dev-auth.cjs.js"
|
||||
},
|
||||
"./evm": {
|
||||
"module": "./evm/dist/thirdweb-dev-auth-evm.esm.js",
|
||||
"default": "./evm/dist/thirdweb-dev-auth-evm.cjs.js"
|
||||
},
|
||||
"./next": {
|
||||
"module": "./next/dist/thirdweb-dev-auth-next.esm.js",
|
||||
"default": "./next/dist/thirdweb-dev-auth-next.cjs.js"
|
||||
},
|
||||
"./solana": {
|
||||
"module": "./solana/dist/thirdweb-dev-auth-solana.esm.js",
|
||||
"default": "./solana/dist/thirdweb-dev-auth-solana.cjs.js"
|
||||
},
|
||||
"./express": {
|
||||
"module": "./express/dist/thirdweb-dev-auth-express.esm.js",
|
||||
"default": "./express/dist/thirdweb-dev-auth-express.cjs.js"
|
||||
},
|
||||
"./next": {
|
||||
"module": "./next/dist/thirdweb-dev-auth-next.esm.js",
|
||||
"default": "./next/dist/thirdweb-dev-auth-express.cjs.js"
|
||||
},
|
||||
"./next-auth": {
|
||||
"module": "./next-auth/dist/thirdweb-dev-auth-next-auth.esm.js",
|
||||
"default": "./next-auth/dist/thirdweb-dev-auth-next-auth.cjs.js"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/auth",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"next/**/*",
|
||||
"express/**/*",
|
||||
"next-auth/**/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "preconstruct build",
|
||||
"format": "prettier --write 'src/**/*'",
|
||||
"lint": "eslint src/",
|
||||
"fix": "eslint src/ --fix",
|
||||
"clean": "rm -rf dist/ && rm -rf node_modules/"
|
||||
"clean": "rm -rf dist/ && rm -rf node_modules/",
|
||||
"test": "mocha --config './test/.mocharc.json' --timeout 30000 --parallel './test/**/*.test.ts'"
|
||||
},
|
||||
"preconstruct": {
|
||||
"entrypoints": [
|
||||
"next/index.ts",
|
||||
"index.ts",
|
||||
"express/index.ts",
|
||||
"next-auth/index.ts"
|
||||
]
|
||||
"next/index.ts",
|
||||
"next-auth/index.ts",
|
||||
"evm/index.ts",
|
||||
"solana/index.ts"
|
||||
],
|
||||
"___experimentalFlags_WILL_CHANGE_IN_PATCH": {
|
||||
"exports": true
|
||||
},
|
||||
"exports": true
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19.3",
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@microsoft/api-documenter": "^7.19.4",
|
||||
"@microsoft/api-extractor": "^7.29.2",
|
||||
"@microsoft/tsdoc": "^0.14.1",
|
||||
"@preconstruct/cli": "^2.2.1",
|
||||
"@thirdweb-dev/sdk": "*",
|
||||
"@solana/web3.js": "^1.73.0",
|
||||
"@swc-node/register": "^1.5.4",
|
||||
"@swc/core": "^1.3.23",
|
||||
"@thirdweb-dev/wallets": "*",
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/chai": "^4.3.4",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"@wagmi/core": "^0.8.18",
|
||||
"chai": "^4.3.7",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-inclusive-language": "^2.2.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-tsdoc": "^0.2.16",
|
||||
"ethers": "^5.7.0",
|
||||
"ethers": "^5.7.2",
|
||||
"express": "^4.18.1",
|
||||
"next": "12.2.0",
|
||||
"mocha": "^10.2.0",
|
||||
"next": "^12.2.0",
|
||||
"next-auth": "^4.10.3",
|
||||
"prettier": "^2.7.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@thirdweb-dev/sdk": "*",
|
||||
"ethers": ">=5.5.1",
|
||||
"express": "^4.18.1",
|
||||
"next": "12.2.0",
|
||||
"next-auth": "^4.10.3"
|
||||
"@noble/ed25519": "^1.7.1",
|
||||
"@solana/web3.js": "^1.73.0",
|
||||
"bs58": "^5.0.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"ethers": "^5",
|
||||
"express": "^4",
|
||||
"next": "^12 || ^13",
|
||||
"next-auth": "^4",
|
||||
"tweetnacl": "^1.0.3"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@noble/ed25519": {
|
||||
"optional": true
|
||||
},
|
||||
"@solana/web3.js": {
|
||||
"optional": true
|
||||
},
|
||||
"bs58": {
|
||||
"optional": true
|
||||
},
|
||||
"cookie-parser": {
|
||||
"optional": true
|
||||
},
|
||||
"tweetnacl": {
|
||||
"optional": true
|
||||
},
|
||||
"ethers": {
|
||||
"optional": true
|
||||
},
|
||||
"express": {
|
||||
"optional": true
|
||||
},
|
||||
@@ -85,6 +135,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": "^0.5.0",
|
||||
"cookie-parser": "^1.4.6"
|
||||
"uuid": "^9.0.0",
|
||||
"zod": "^3.20.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-auth-solana.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth-solana.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
import {
|
||||
LoginOptions,
|
||||
LoginPayload,
|
||||
GenerateOptions,
|
||||
LoginPayloadData,
|
||||
LoginPayloadDataSchema,
|
||||
AuthenticationPayloadDataSchema,
|
||||
AuthenticationPayloadData,
|
||||
LoginOptionsSchema,
|
||||
VerifyOptionsSchema,
|
||||
VerifyOptions,
|
||||
GenerateOptionsSchema,
|
||||
AuthenticateOptionsSchema,
|
||||
AuthenticateOptions,
|
||||
User,
|
||||
Json,
|
||||
} from "./schema";
|
||||
import { isBrowser } from "./utils";
|
||||
import type { GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
|
||||
export class ThirdwebAuth {
|
||||
private domain: string;
|
||||
private wallet: GenericAuthWallet;
|
||||
|
||||
constructor(wallet: GenericAuthWallet, domain: string) {
|
||||
this.wallet = wallet;
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
public updateWallet(wallet: GenericAuthWallet) {
|
||||
this.wallet = wallet;
|
||||
}
|
||||
|
||||
public async login(options?: LoginOptions): Promise<LoginPayload> {
|
||||
const parsedOptions = LoginOptionsSchema.parse(options);
|
||||
|
||||
let chainId: string | undefined = parsedOptions?.chainId;
|
||||
if (!chainId && this.wallet.getChainId) {
|
||||
try {
|
||||
chainId = (await this.wallet.getChainId()).toString();
|
||||
} catch {
|
||||
// ignore error
|
||||
}
|
||||
}
|
||||
|
||||
const payloadData = LoginPayloadDataSchema.parse({
|
||||
type: this.wallet.type,
|
||||
domain: parsedOptions?.domain || this.domain,
|
||||
address: await this.wallet.getAddress(),
|
||||
statement: parsedOptions?.statement,
|
||||
version: parsedOptions?.version,
|
||||
uri:
|
||||
parsedOptions?.uri || (isBrowser() ? window.location.href : undefined),
|
||||
chain_id: chainId,
|
||||
nonce: parsedOptions?.nonce,
|
||||
expiration_time:
|
||||
parsedOptions?.expirationTime || new Date(Date.now() + 1000 * 60 * 5),
|
||||
invalid_before: parsedOptions?.invalidBefore,
|
||||
resources: parsedOptions?.resources,
|
||||
});
|
||||
|
||||
const message = this.generateMessage(payloadData);
|
||||
const signature = await this.wallet.signMessage(message);
|
||||
|
||||
return {
|
||||
payload: payloadData,
|
||||
signature,
|
||||
};
|
||||
}
|
||||
|
||||
public async verify(
|
||||
payload: LoginPayload,
|
||||
options?: VerifyOptions,
|
||||
): Promise<string> {
|
||||
const parsedOptions = VerifyOptionsSchema.parse(options);
|
||||
|
||||
if (payload.payload.type !== this.wallet.type) {
|
||||
throw new Error(
|
||||
`Expected chain type '${this.wallet.type}' does not match chain type on payload '${payload.payload.type}'`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check that the intended domain matches the domain of the payload
|
||||
const domain = parsedOptions?.domain || this.domain;
|
||||
if (payload.payload.domain !== domain) {
|
||||
throw new Error(
|
||||
`Expected domain '${domain}' does not match domain on payload '${payload.payload.domain}'`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check that the payload statement matches the expected statement
|
||||
if (parsedOptions?.statement) {
|
||||
if (payload.payload.statement !== parsedOptions.statement) {
|
||||
throw new Error(
|
||||
`Expected statement '${parsedOptions.statement}' does not match statement on payload '${payload.payload.statement}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the intended URI matches the URI of the payload
|
||||
if (parsedOptions?.uri) {
|
||||
if (payload.payload.uri !== parsedOptions.uri) {
|
||||
throw new Error(
|
||||
`Expected URI '${parsedOptions.uri}' does not match URI on payload '${payload.payload.uri}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the intended version matches the version of the payload
|
||||
if (parsedOptions?.version) {
|
||||
if (payload.payload.version !== parsedOptions.version) {
|
||||
throw new Error(
|
||||
`Expected version '${parsedOptions.version}' does not match version on payload '${payload.payload.version}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the intended chain ID matches the chain ID of the payload
|
||||
if (parsedOptions?.chainId) {
|
||||
if (payload.payload.chain_id !== parsedOptions.chainId) {
|
||||
throw new Error(
|
||||
`Expected chain ID '${parsedOptions.chainId}' does not match chain ID on payload '${payload.payload.chain_id}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the payload nonce is valid
|
||||
if (parsedOptions?.validateNonce !== undefined) {
|
||||
try {
|
||||
await parsedOptions.validateNonce(payload.payload.nonce);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
throw new Error(`Login request nonce is invalid`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that it isn't before the invalid before time
|
||||
const currentTime = new Date();
|
||||
if (currentTime < new Date(payload.payload.invalid_before)) {
|
||||
throw new Error(`Login request is not yet valid`);
|
||||
}
|
||||
|
||||
// Check that the payload hasn't expired
|
||||
if (currentTime > new Date(payload.payload.expiration_time)) {
|
||||
throw new Error(`Login request has expired`);
|
||||
}
|
||||
|
||||
// Check that the specified resources are present on the payload
|
||||
if (parsedOptions?.resources) {
|
||||
const missingResources = parsedOptions.resources.filter(
|
||||
(resource) => !payload.payload.resources?.includes(resource),
|
||||
);
|
||||
if (missingResources.length > 0) {
|
||||
throw new Error(
|
||||
`Login request is missing required resources: ${missingResources.join(
|
||||
", ",
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the signing address is the claimed wallet address
|
||||
const message = this.generateMessage(payload.payload);
|
||||
const chainId =
|
||||
this.wallet.type === "evm" && payload.payload.chain_id
|
||||
? parseInt(payload.payload.chain_id)
|
||||
: undefined;
|
||||
const verified = await this.verifySignature(
|
||||
message,
|
||||
payload.signature,
|
||||
payload.payload.address,
|
||||
chainId,
|
||||
);
|
||||
if (!verified) {
|
||||
throw new Error(
|
||||
`Signer address does not match payload address '${payload.payload.address.toLowerCase()}'`,
|
||||
);
|
||||
}
|
||||
|
||||
return payload.payload.address;
|
||||
}
|
||||
|
||||
public async generate(
|
||||
payload: LoginPayload,
|
||||
options?: GenerateOptions,
|
||||
): Promise<string> {
|
||||
if (isBrowser()) {
|
||||
throw new Error(
|
||||
"Authentication tokens should not be generated in the browser, as they must be signed by a server-side admin wallet.",
|
||||
);
|
||||
}
|
||||
|
||||
const parsedOptions = GenerateOptionsSchema.parse(options);
|
||||
|
||||
const domain = parsedOptions?.domain || this.domain;
|
||||
const userAddress = await this.verify(payload, {
|
||||
domain,
|
||||
...parsedOptions?.verifyOptions,
|
||||
});
|
||||
|
||||
let session: Json | undefined = undefined;
|
||||
if (typeof parsedOptions?.session === "function") {
|
||||
const sessionTrigger = (await parsedOptions.session(userAddress)) as Json;
|
||||
if (sessionTrigger) {
|
||||
session = sessionTrigger;
|
||||
}
|
||||
} else {
|
||||
session = parsedOptions?.session;
|
||||
}
|
||||
|
||||
const adminAddress = await this.wallet.getAddress();
|
||||
const payloadData = AuthenticationPayloadDataSchema.parse({
|
||||
iss: adminAddress,
|
||||
sub: userAddress,
|
||||
aud: domain,
|
||||
nbf: parsedOptions?.invalidBefore || new Date(),
|
||||
exp:
|
||||
parsedOptions?.expirationTime ||
|
||||
new Date(Date.now() + 1000 * 60 * 60 * 5),
|
||||
iat: new Date(),
|
||||
jti: parsedOptions?.tokenId,
|
||||
ctx: session,
|
||||
});
|
||||
|
||||
const message = JSON.stringify(payloadData);
|
||||
const signature = await this.wallet.signMessage(message);
|
||||
|
||||
// Header used for JWT token specifying hash algorithm
|
||||
const header = {
|
||||
// Specify ECDSA with SHA-256 for hashing algorithm
|
||||
alg: "ES256",
|
||||
typ: "JWT",
|
||||
};
|
||||
|
||||
const encodedHeader = Buffer.from(JSON.stringify(header)).toString(
|
||||
"base64",
|
||||
);
|
||||
const encodedData = Buffer.from(JSON.stringify(payloadData))
|
||||
.toString("base64")
|
||||
.replace(/=/g, "");
|
||||
const encodedSignature = Buffer.from(signature).toString("base64");
|
||||
|
||||
// Generate a JWT token with base64 encoded header, payload, and signature
|
||||
const token = `${encodedHeader}.${encodedData}.${encodedSignature}`;
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate With Token
|
||||
* @remarks Server-side function that authenticates the provided JWT token. This function verifies that
|
||||
* the provided authentication token is valid and returns the address of the authenticated wallet.
|
||||
*
|
||||
* @param domain - The domain of the server-side application doing authentication
|
||||
* @param token - The authentication token being used
|
||||
* @returns The address of the authenticated wallet
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const domain = "example.com";
|
||||
* const loginPayload = await sdk.auth.login(domain);
|
||||
* const token = await sdk.auth.generateAuthToken(domain, loginPayload);
|
||||
*
|
||||
* // Authenticate the token and get the address of authenticating users wallet
|
||||
* const address = sdk.auth.authenticate(domain, token);
|
||||
* ```
|
||||
*/
|
||||
public async authenticate<TSession extends Json = Json>(
|
||||
token: string,
|
||||
options?: AuthenticateOptions,
|
||||
): Promise<User<TSession>> {
|
||||
if (isBrowser()) {
|
||||
throw new Error(
|
||||
"Should not authenticate tokens in the browser, as they must be verified by the server-side admin wallet.",
|
||||
);
|
||||
}
|
||||
|
||||
const parsedOptions = AuthenticateOptionsSchema.parse(options);
|
||||
const domain = parsedOptions?.domain || this.domain;
|
||||
|
||||
const encodedPayload = token.split(".")[1];
|
||||
const encodedSignature = token.split(".")[2];
|
||||
const payload: AuthenticationPayloadData = JSON.parse(
|
||||
Buffer.from(encodedPayload, "base64").toString(),
|
||||
);
|
||||
const signature = Buffer.from(encodedSignature, "base64").toString();
|
||||
|
||||
// Check that the payload unique ID is valid
|
||||
if (parsedOptions?.validateTokenId !== undefined) {
|
||||
try {
|
||||
await parsedOptions.validateTokenId(payload.jti);
|
||||
} catch (err) {
|
||||
throw new Error(`Token ID is invalid`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the token audience matches the domain
|
||||
if (payload.aud !== domain) {
|
||||
throw new Error(
|
||||
`Expected token to be for the domain '${domain}', but found token with domain '${payload.aud}'`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check that the token is past the invalid before time
|
||||
const currentTime = Math.floor(new Date().getTime() / 1000);
|
||||
if (currentTime < payload.nbf) {
|
||||
throw new Error(
|
||||
`This token is invalid before epoch time '${payload.nbf}', current epoch time is '${currentTime}'`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check that the token hasn't expired
|
||||
if (currentTime > payload.exp) {
|
||||
throw new Error(
|
||||
`This token expired at epoch time '${payload.exp}', current epoch time is '${currentTime}'`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check that the connected wallet matches the token issuer
|
||||
const connectedAddress = await this.wallet.getAddress();
|
||||
if (connectedAddress.toLowerCase() !== payload.iss.toLowerCase()) {
|
||||
throw new Error(
|
||||
`Expected the connected wallet address '${connectedAddress}' to match the token issuer address '${payload.iss}'`,
|
||||
);
|
||||
}
|
||||
|
||||
let chainId: number | undefined = undefined;
|
||||
if (this.wallet.getChainId) {
|
||||
try {
|
||||
chainId = await this.wallet.getChainId();
|
||||
} catch {
|
||||
// ignore error
|
||||
}
|
||||
}
|
||||
|
||||
const verified = await this.verifySignature(
|
||||
JSON.stringify(payload),
|
||||
signature,
|
||||
connectedAddress,
|
||||
chainId,
|
||||
);
|
||||
if (!verified) {
|
||||
throw new Error(
|
||||
`The connected wallet address '${connectedAddress}' did not sign the token`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
address: payload.sub,
|
||||
session: payload.ctx as TSession | undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private async verifySignature(
|
||||
message: string,
|
||||
signature: string,
|
||||
address: string,
|
||||
chainId?: number,
|
||||
) {
|
||||
return this.wallet.verifySignature(message, signature, address, chainId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a EIP-4361 & CAIP-122 compliant message to sign based on the login payload
|
||||
*/
|
||||
private generateMessage(payload: LoginPayloadData): string {
|
||||
const typeField = payload.type === "evm" ? "Ethereum" : "Solana";
|
||||
const header = `${payload.domain} wants you to sign in with your ${typeField} account:`;
|
||||
let prefix = [header, payload.address].join("\n");
|
||||
prefix = [prefix, payload.statement].join("\n\n");
|
||||
if (payload.statement) {
|
||||
prefix += "\n";
|
||||
}
|
||||
|
||||
const suffixArray = [];
|
||||
if (payload.uri) {
|
||||
const uriField = `URI: ${payload.uri}`;
|
||||
suffixArray.push(uriField);
|
||||
}
|
||||
|
||||
const versionField = `Version: ${payload.version}`;
|
||||
suffixArray.push(versionField);
|
||||
|
||||
if (payload.chain_id) {
|
||||
const chainField = `Chain ID: ` + payload.chain_id || "1";
|
||||
suffixArray.push(chainField);
|
||||
}
|
||||
|
||||
const nonceField = `Nonce: ${payload.nonce}`;
|
||||
suffixArray.push(nonceField);
|
||||
|
||||
const issuedAtField = `Issued At: ${payload.issued_at}`;
|
||||
suffixArray.push(issuedAtField);
|
||||
|
||||
const expiryField = `Expiration Time: ${payload.expiration_time}`;
|
||||
suffixArray.push(expiryField);
|
||||
|
||||
if (payload.invalid_before) {
|
||||
const invalidBeforeField = `Not Before: ${payload.invalid_before}`;
|
||||
suffixArray.push(invalidBeforeField);
|
||||
}
|
||||
|
||||
if (payload.resources) {
|
||||
suffixArray.push(
|
||||
[`Resources:`, ...payload.resources.map((x) => `- ${x}`)].join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
const suffix = suffixArray.join("\n");
|
||||
return [prefix, suffix].join("\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { ThirdwebAuth } from "./auth";
|
||||
export * from "./schema";
|
||||
@@ -0,0 +1,208 @@
|
||||
import { utils, BigNumber } from "ethers";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { z } from "zod";
|
||||
|
||||
export const AddressSchema = z.string().refine(
|
||||
(arg) => utils.isAddress(arg),
|
||||
(out) => {
|
||||
return {
|
||||
message: `${out} is not a valid address`,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export const RawDateSchema = z.date().transform((i) => {
|
||||
return BigNumber.from(Math.floor(i.getTime() / 1000));
|
||||
});
|
||||
|
||||
export const AccountTypeSchema = z.union([
|
||||
z.literal("evm"),
|
||||
z.literal("solana"),
|
||||
]);
|
||||
|
||||
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
||||
type Literal = z.infer<typeof literalSchema>;
|
||||
export type Json = Literal | { [key: string]: Json } | Json[];
|
||||
const JsonSchema: z.ZodType<Json> = z.lazy(
|
||||
() => z.union([literalSchema, z.array(JsonSchema), z.record(JsonSchema)]),
|
||||
{ invalid_type_error: "Provided value was not valid JSON" },
|
||||
);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const LoginOptionsSchema = z
|
||||
.object({
|
||||
domain: z.string().optional(),
|
||||
statement: z.string().optional(),
|
||||
uri: z.string().optional(),
|
||||
version: z.string().optional(),
|
||||
chainId: z.string().optional(),
|
||||
nonce: z.string().optional(),
|
||||
expirationTime: z.date().optional(),
|
||||
invalidBefore: z.date().optional(),
|
||||
resources: z.array(z.string()).optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const LoginPayloadDataSchema = z.object({
|
||||
type: AccountTypeSchema,
|
||||
domain: z.string(),
|
||||
address: z.string(),
|
||||
statement: z
|
||||
.string()
|
||||
.default(
|
||||
"Please ensure that the domain above matches the URL of the current website.",
|
||||
),
|
||||
uri: z.string().optional(),
|
||||
version: z.string().default("1"),
|
||||
chain_id: z.string().optional(),
|
||||
nonce: z.string().default(uuidv4()),
|
||||
issued_at: z
|
||||
.date()
|
||||
.default(new Date())
|
||||
.transform((d) => d.toISOString()),
|
||||
expiration_time: z.date().transform((d) => d.toISOString()),
|
||||
invalid_before: z
|
||||
.date()
|
||||
.default(new Date())
|
||||
.transform((d) => d.toISOString()),
|
||||
resources: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const LoginPayloadSchema = z.object({
|
||||
payload: LoginPayloadDataSchema,
|
||||
signature: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
const VerifyOptionsSchemaRequired = z.object({
|
||||
domain: z.string().optional(),
|
||||
statement: z.string().optional(),
|
||||
uri: z.string().optional(),
|
||||
version: z.string().optional(),
|
||||
chainId: z.string().optional(),
|
||||
validateNonce: z.function().args(z.string()).optional(),
|
||||
resources: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const VerifyOptionsSchema = VerifyOptionsSchemaRequired.optional();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const GenerateOptionsSchema = z
|
||||
.object({
|
||||
domain: z.string().optional(),
|
||||
tokenId: z.string().optional(),
|
||||
expirationTime: z.date().optional(),
|
||||
invalidBefore: z.date().optional(),
|
||||
session: z.union([JsonSchema, z.function().args(z.string())]).optional(),
|
||||
verifyOptions: VerifyOptionsSchemaRequired.omit({
|
||||
domain: true,
|
||||
}).optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const AuthenticationPayloadDataSchema = z.object({
|
||||
iss: z.string(),
|
||||
sub: z.string(),
|
||||
aud: z.string(),
|
||||
exp: RawDateSchema.transform((b) => b.toNumber()),
|
||||
nbf: RawDateSchema.transform((b) => b.toNumber()),
|
||||
iat: RawDateSchema.transform((b) => b.toNumber()),
|
||||
jti: z.string().default(uuidv4()),
|
||||
ctx: JsonSchema.optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const AuthenticationPayloadSchema = z.object({
|
||||
payload: AuthenticationPayloadDataSchema,
|
||||
signature: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const AuthenticateOptionsSchema = z
|
||||
.object({
|
||||
domain: z.string().optional(),
|
||||
validateTokenId: z.function().args(z.string()).optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type LoginOptions = z.input<typeof LoginOptionsSchema>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type LoginPayloadData = z.output<typeof LoginPayloadDataSchema>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type LoginPayload = z.output<typeof LoginPayloadSchema>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type VerifyOptions = z.input<typeof VerifyOptionsSchema>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type GenerateOptions = z.input<typeof GenerateOptionsSchema>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type AuthenticationPayloadData = z.output<
|
||||
typeof AuthenticationPayloadDataSchema
|
||||
>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type AuthenticationPayload = z.output<
|
||||
typeof AuthenticationPayloadSchema
|
||||
>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type AuthenticateOptions = z.output<typeof AuthenticateOptionsSchema>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type User<TContext extends Json = Json> = {
|
||||
address: string;
|
||||
session?: TContext;
|
||||
};
|
||||
|
||||
export const LoginPayloadOutputSchema = LoginPayloadSchema.extend({
|
||||
payload: LoginPayloadDataSchema.extend({
|
||||
issued_at: z.string(),
|
||||
expiration_time: z.string(),
|
||||
invalid_before: z.string(),
|
||||
}),
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
// @ts-expext-error window is not defined
|
||||
export const isBrowser = () => typeof window !== "undefined";
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,613 @@
|
||||
import type { Chain } from "@wagmi/core";
|
||||
|
||||
const arbitrum: Chain = {
|
||||
id: 42_161,
|
||||
name: "Arbitrum One",
|
||||
network: "arbitrum",
|
||||
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
||||
rpcUrls: {
|
||||
alchemy: {
|
||||
http: ["https://arb-mainnet.g.alchemy.com/v2"],
|
||||
webSocket: ["wss://arb-mainnet.g.alchemy.com/v2"],
|
||||
},
|
||||
infura: {
|
||||
http: ["https://arbitrum-mainnet.infura.io/v3"],
|
||||
webSocket: ["wss://arbitrum-mainnet.infura.io/ws/v3"],
|
||||
},
|
||||
default: {
|
||||
http: ["https://arb1.arbitrum.io/rpc"],
|
||||
},
|
||||
public: {
|
||||
http: ["https://arb1.arbitrum.io/rpc"],
|
||||
},
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: { name: "Arbiscan", url: "https://arbiscan.io" },
|
||||
default: { name: "Arbiscan", url: "https://arbiscan.io" },
|
||||
},
|
||||
contracts: {
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 7654707,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const arbitrumGoerli: Chain = {
|
||||
id: 421_613,
|
||||
name: "Arbitrum Goerli",
|
||||
network: "arbitrum-goerli",
|
||||
nativeCurrency: {
|
||||
name: "Arbitrum Goerli Ether",
|
||||
symbol: "ETH",
|
||||
decimals: 18,
|
||||
},
|
||||
rpcUrls: {
|
||||
alchemy: {
|
||||
http: ["https://arb-goerli.g.alchemy.com/v2"],
|
||||
webSocket: ["wss://arb-goerli.g.alchemy.com/v2"],
|
||||
},
|
||||
infura: {
|
||||
http: ["https://arbitrum-goerli.infura.io/v3"],
|
||||
webSocket: ["wss://arbitrum-goerli.infura.io/ws/v3"],
|
||||
},
|
||||
default: {
|
||||
http: ["https://goerli-rollup.arbitrum.io/rpc"],
|
||||
},
|
||||
public: {
|
||||
http: ["https://goerli-rollup.arbitrum.io/rpc"],
|
||||
},
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: { name: "Arbiscan", url: "https://goerli.arbiscan.io/" },
|
||||
default: { name: "Arbiscan", url: "https://goerli.arbiscan.io/" },
|
||||
},
|
||||
contracts: {
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 88114,
|
||||
},
|
||||
},
|
||||
testnet: true,
|
||||
};
|
||||
|
||||
const avalanche: Chain = {
|
||||
id: 43_114,
|
||||
name: "Avalanche",
|
||||
network: "avalanche",
|
||||
nativeCurrency: {
|
||||
decimals: 18,
|
||||
name: "Avalanche",
|
||||
symbol: "AVAX",
|
||||
},
|
||||
rpcUrls: {
|
||||
default: { http: ["https://api.avax.network/ext/bc/C/rpc"] },
|
||||
public: { http: ["https://api.avax.network/ext/bc/C/rpc"] },
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: { name: "SnowTrace", url: "https://snowtrace.io" },
|
||||
default: { name: "SnowTrace", url: "https://snowtrace.io" },
|
||||
},
|
||||
contracts: {
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 11907934,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const avalancheFuji: Chain = {
|
||||
id: 43_113,
|
||||
name: "Avalanche Fuji",
|
||||
network: "avalanche-fuji",
|
||||
nativeCurrency: {
|
||||
decimals: 18,
|
||||
name: "Avalanche Fuji",
|
||||
symbol: "AVAX",
|
||||
},
|
||||
rpcUrls: {
|
||||
default: { http: ["https://api.avax-test.network/ext/bc/C/rpc"] },
|
||||
public: { http: ["https://api.avax-test.network/ext/bc/C/rpc"] },
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: { name: "SnowTrace", url: "https://testnet.snowtrace.io" },
|
||||
default: { name: "SnowTrace", url: "https://testnet.snowtrace.io" },
|
||||
},
|
||||
contracts: {
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 7096959,
|
||||
},
|
||||
},
|
||||
testnet: true,
|
||||
};
|
||||
|
||||
const bsc: Chain = {
|
||||
id: 56,
|
||||
name: "BNB Smart Chain",
|
||||
network: "bsc",
|
||||
nativeCurrency: {
|
||||
decimals: 18,
|
||||
name: "BNB",
|
||||
symbol: "BNB",
|
||||
},
|
||||
rpcUrls: {
|
||||
default: { http: ["https://rpc.ankr.com/bsc"] },
|
||||
public: { http: ["https://rpc.ankr.com/bsc"] },
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: { name: "BscScan", url: "https://bscscan.com" },
|
||||
default: { name: "BscScan", url: "https://bscscan.com" },
|
||||
},
|
||||
contracts: {
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 15921452,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const bscTestnet: Chain = {
|
||||
id: 97,
|
||||
name: "Binance Smart Chain Testnet",
|
||||
network: "bsc-testnet",
|
||||
nativeCurrency: {
|
||||
decimals: 18,
|
||||
name: "BNB",
|
||||
symbol: "tBNB",
|
||||
},
|
||||
rpcUrls: {
|
||||
default: { http: ["https://bsc-testnet.public.blastapi.io"] },
|
||||
public: { http: ["https://bsc-testnet.public.blastapi.io"] },
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: { name: "BscScan", url: "https://testnet.bscscan.com" },
|
||||
default: { name: "BscScan", url: "https://testnet.bscscan.com" },
|
||||
},
|
||||
contracts: {
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 17422483,
|
||||
},
|
||||
},
|
||||
testnet: true,
|
||||
};
|
||||
|
||||
const fantom: Chain = {
|
||||
id: 250,
|
||||
name: "Fantom",
|
||||
network: "fantom",
|
||||
nativeCurrency: {
|
||||
decimals: 18,
|
||||
name: "Fantom",
|
||||
symbol: "FTM",
|
||||
},
|
||||
rpcUrls: {
|
||||
default: { http: ["https://rpc.ankr.com/fantom"] },
|
||||
public: { http: ["https://rpc.ankr.com/fantom"] },
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: { name: "FTMScan", url: "https://ftmscan.com" },
|
||||
default: { name: "FTMScan", url: "https://ftmscan.com" },
|
||||
},
|
||||
contracts: {
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 33001987,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const fantomTestnet: Chain = {
|
||||
id: 4002,
|
||||
name: "Fantom Testnet",
|
||||
network: "fantom-testnet",
|
||||
nativeCurrency: {
|
||||
decimals: 18,
|
||||
name: "Fantom",
|
||||
symbol: "FTM",
|
||||
},
|
||||
rpcUrls: {
|
||||
default: { http: ["https://rpc.testnet.fantom.network"] },
|
||||
public: { http: ["https://rpc.testnet.fantom.network"] },
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: { name: "FTMScan", url: "https://testnet.ftmscan.com" },
|
||||
default: { name: "FTMScan", url: "https://testnet.ftmscan.com" },
|
||||
},
|
||||
contracts: {
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 8328688,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const goerli: Chain = {
|
||||
id: 5,
|
||||
network: "goerli",
|
||||
name: "Goerli",
|
||||
nativeCurrency: { name: "Goerli Ether", symbol: "ETH", decimals: 18 },
|
||||
rpcUrls: {
|
||||
alchemy: {
|
||||
http: ["https://eth-goerli.g.alchemy.com/v2"],
|
||||
webSocket: ["wss://eth-goerli.g.alchemy.com/v2"],
|
||||
},
|
||||
infura: {
|
||||
http: ["https://goerli.infura.io/v3"],
|
||||
webSocket: ["wss://goerli.infura.io/ws/v3"],
|
||||
},
|
||||
default: {
|
||||
http: ["https://rpc.ankr.com/eth_goerli"],
|
||||
},
|
||||
public: {
|
||||
http: ["https://rpc.ankr.com/eth_goerli"],
|
||||
},
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: {
|
||||
name: "Etherscan",
|
||||
url: "https://goerli.etherscan.io",
|
||||
},
|
||||
default: {
|
||||
name: "Etherscan",
|
||||
url: "https://goerli.etherscan.io",
|
||||
},
|
||||
},
|
||||
contracts: {
|
||||
ensRegistry: {
|
||||
address: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
|
||||
},
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 6507670,
|
||||
},
|
||||
},
|
||||
testnet: true,
|
||||
};
|
||||
|
||||
const mainnet: Chain = {
|
||||
id: 1,
|
||||
network: "homestead",
|
||||
name: "Ethereum",
|
||||
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
||||
rpcUrls: {
|
||||
alchemy: {
|
||||
http: ["https://eth-mainnet.g.alchemy.com/v2"],
|
||||
webSocket: ["wss://eth-mainnet.g.alchemy.com/v2"],
|
||||
},
|
||||
infura: {
|
||||
http: ["https://mainnet.infura.io/v3"],
|
||||
webSocket: ["wss://mainnet.infura.io/ws/v3"],
|
||||
},
|
||||
default: {
|
||||
http: ["https://cloudflare-eth.com"],
|
||||
},
|
||||
public: {
|
||||
http: ["https://cloudflare-eth.com"],
|
||||
},
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: {
|
||||
name: "Etherscan",
|
||||
url: "https://etherscan.io",
|
||||
},
|
||||
default: {
|
||||
name: "Etherscan",
|
||||
url: "https://etherscan.io",
|
||||
},
|
||||
},
|
||||
contracts: {
|
||||
ensRegistry: {
|
||||
address: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
|
||||
},
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 14353601,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const optimism: Chain = {
|
||||
id: 10,
|
||||
name: "Optimism",
|
||||
network: "optimism",
|
||||
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
||||
rpcUrls: {
|
||||
alchemy: {
|
||||
http: ["https://opt-mainnet.g.alchemy.com/v2"],
|
||||
webSocket: ["wss://opt-mainnet.g.alchemy.com/v2"],
|
||||
},
|
||||
infura: {
|
||||
http: ["https://optimism-mainnet.infura.io/v3"],
|
||||
webSocket: ["wss://optimism-mainnet.infura.io/ws/v3"],
|
||||
},
|
||||
default: {
|
||||
http: ["https://mainnet.optimism.io"],
|
||||
},
|
||||
public: {
|
||||
http: ["https://mainnet.optimism.io"],
|
||||
},
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: {
|
||||
name: "Etherscan",
|
||||
url: "https://optimistic.etherscan.io",
|
||||
},
|
||||
default: {
|
||||
name: "Etherscan",
|
||||
url: "https://optimistic.etherscan.io",
|
||||
},
|
||||
},
|
||||
contracts: {
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 4286263,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const optimismGoerli: Chain = {
|
||||
id: 420,
|
||||
name: "Optimism Goerli",
|
||||
network: "optimism-goerli",
|
||||
nativeCurrency: { name: "Goerli Ether", symbol: "ETH", decimals: 18 },
|
||||
rpcUrls: {
|
||||
alchemy: {
|
||||
http: ["https://opt-goerli.g.alchemy.com/v2"],
|
||||
webSocket: ["wss://opt-goerli.g.alchemy.com/v2"],
|
||||
},
|
||||
infura: {
|
||||
http: ["https://optimism-goerli.infura.io/v3"],
|
||||
webSocket: ["wss://optimism-goerli.infura.io/ws/v3"],
|
||||
},
|
||||
default: {
|
||||
http: ["https://goerli.optimism.io"],
|
||||
},
|
||||
public: {
|
||||
http: ["https://goerli.optimism.io"],
|
||||
},
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: {
|
||||
name: "Etherscan",
|
||||
url: "https://goerli-optimism.etherscan.io",
|
||||
},
|
||||
default: {
|
||||
name: "Etherscan",
|
||||
url: "https://goerli-optimism.etherscan.io",
|
||||
},
|
||||
},
|
||||
contracts: {
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 49461,
|
||||
},
|
||||
},
|
||||
testnet: true,
|
||||
};
|
||||
|
||||
const polygon: Chain = {
|
||||
id: 137,
|
||||
name: "Polygon",
|
||||
network: "matic",
|
||||
nativeCurrency: { name: "MATIC", symbol: "MATIC", decimals: 18 },
|
||||
rpcUrls: {
|
||||
alchemy: {
|
||||
http: ["https://polygon-mainnet.g.alchemy.com/v2"],
|
||||
webSocket: ["wss://polygon-mainnet.g.alchemy.com/v2"],
|
||||
},
|
||||
infura: {
|
||||
http: ["https://polygon-mainnet.infura.io/v3"],
|
||||
webSocket: ["wss://polygon-mainnet.infura.io/ws/v3"],
|
||||
},
|
||||
default: {
|
||||
http: ["https://polygon-rpc.com"],
|
||||
},
|
||||
public: {
|
||||
http: ["https://polygon-rpc.com"],
|
||||
},
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: {
|
||||
name: "PolygonScan",
|
||||
url: "https://polygonscan.com",
|
||||
},
|
||||
default: {
|
||||
name: "PolygonScan",
|
||||
url: "https://polygonscan.com",
|
||||
},
|
||||
},
|
||||
contracts: {
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 25770160,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const polygonMumbai: Chain = {
|
||||
id: 80_001,
|
||||
name: "Polygon Mumbai",
|
||||
network: "maticmum",
|
||||
nativeCurrency: { name: "MATIC", symbol: "MATIC", decimals: 18 },
|
||||
rpcUrls: {
|
||||
alchemy: {
|
||||
http: ["https://polygon-mumbai.g.alchemy.com/v2"],
|
||||
webSocket: ["wss://polygon-mumbai.g.alchemy.com/v2"],
|
||||
},
|
||||
infura: {
|
||||
http: ["https://polygon-mumbai.infura.io/v3"],
|
||||
webSocket: ["wss://polygon-mumbai.infura.io/ws/v3"],
|
||||
},
|
||||
default: {
|
||||
http: ["https://matic-mumbai.chainstacklabs.com"],
|
||||
},
|
||||
public: {
|
||||
http: ["https://matic-mumbai.chainstacklabs.com"],
|
||||
},
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: {
|
||||
name: "PolygonScan",
|
||||
url: "https://mumbai.polygonscan.com",
|
||||
},
|
||||
default: {
|
||||
name: "PolygonScan",
|
||||
url: "https://mumbai.polygonscan.com",
|
||||
},
|
||||
},
|
||||
contracts: {
|
||||
multicall3: {
|
||||
address: "0xca11bde05977b3631167028862be2a173976ca11",
|
||||
blockCreated: 25770160,
|
||||
},
|
||||
},
|
||||
testnet: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export enum ChainId {
|
||||
Mainnet = 1,
|
||||
Goerli = 5,
|
||||
Polygon = 137,
|
||||
Mumbai = 80001,
|
||||
Fantom = 250,
|
||||
FantomTestnet = 4002,
|
||||
Avalanche = 43114,
|
||||
AvalancheFujiTestnet = 43113,
|
||||
Optimism = 10,
|
||||
OptimismGoerli = 420,
|
||||
Arbitrum = 42161,
|
||||
ArbitrumGoerli = 421613,
|
||||
BinanceSmartChainMainnet = 56,
|
||||
BinanceSmartChainTestnet = 97,
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type SUPPORTED_CHAIN_ID =
|
||||
| ChainId.Mainnet
|
||||
| ChainId.Goerli
|
||||
| ChainId.Mumbai
|
||||
| ChainId.Polygon
|
||||
| ChainId.Fantom
|
||||
| ChainId.FantomTestnet
|
||||
| ChainId.Avalanche
|
||||
| ChainId.AvalancheFujiTestnet
|
||||
| ChainId.Optimism
|
||||
| ChainId.OptimismGoerli
|
||||
| ChainId.Arbitrum
|
||||
| ChainId.ArbitrumGoerli
|
||||
| ChainId.BinanceSmartChainMainnet
|
||||
| ChainId.BinanceSmartChainTestnet;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const SUPPORTED_CHAIN_IDS: SUPPORTED_CHAIN_ID[] = [
|
||||
ChainId.Mainnet,
|
||||
ChainId.Goerli,
|
||||
ChainId.Polygon,
|
||||
ChainId.Mumbai,
|
||||
ChainId.Fantom,
|
||||
ChainId.FantomTestnet,
|
||||
ChainId.Avalanche,
|
||||
ChainId.AvalancheFujiTestnet,
|
||||
ChainId.Optimism,
|
||||
ChainId.OptimismGoerli,
|
||||
ChainId.Arbitrum,
|
||||
ChainId.ArbitrumGoerli,
|
||||
ChainId.BinanceSmartChainMainnet,
|
||||
ChainId.BinanceSmartChainTestnet,
|
||||
];
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const NATIVE_TOKEN_ADDRESS =
|
||||
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
||||
|
||||
export const DEFAULT_API_KEY =
|
||||
"c6634ad2d97b74baf15ff556016830c251050e6c36b9da508ce3ec80095d3dc1";
|
||||
|
||||
function getRpcNameFromChainId(chainId: SUPPORTED_CHAIN_ID): string {
|
||||
switch (chainId) {
|
||||
case ChainId.Mainnet:
|
||||
return "mainnet";
|
||||
case ChainId.Goerli:
|
||||
return "goerli";
|
||||
case ChainId.Polygon:
|
||||
return "polygon";
|
||||
case ChainId.Mumbai:
|
||||
return "mumbai";
|
||||
case ChainId.Avalanche:
|
||||
return "avalanche";
|
||||
case ChainId.AvalancheFujiTestnet:
|
||||
return "avalanche-fuji";
|
||||
case ChainId.Fantom:
|
||||
return "fantom";
|
||||
case ChainId.FantomTestnet:
|
||||
return "fantom-testnet";
|
||||
case ChainId.Arbitrum:
|
||||
return "arbitrum";
|
||||
case ChainId.ArbitrumGoerli:
|
||||
return "arbitrum-goerli";
|
||||
case ChainId.Optimism:
|
||||
return "optimism";
|
||||
case ChainId.OptimismGoerli:
|
||||
return "optimism-goerli";
|
||||
case ChainId.BinanceSmartChainMainnet:
|
||||
return "bsc";
|
||||
case ChainId.BinanceSmartChainTestnet:
|
||||
return "bsc-testnet";
|
||||
default:
|
||||
throw new Error("Unsupported chain id");
|
||||
}
|
||||
}
|
||||
|
||||
export function getRpcUrl(chainId: SUPPORTED_CHAIN_ID) {
|
||||
return `https://${getRpcNameFromChainId(
|
||||
chainId,
|
||||
)}.rpc.thirdweb.com/${DEFAULT_API_KEY}`;
|
||||
}
|
||||
|
||||
function enhanceChain<TChain extends Chain>(chain: TChain) {
|
||||
const twRPC = getRpcUrl(chain.id);
|
||||
return {
|
||||
...chain,
|
||||
rpcUrls: {
|
||||
...chain.rpcUrls,
|
||||
default: {
|
||||
...chain.rpcUrls.default,
|
||||
http: [twRPC, ...chain.rpcUrls.default.http],
|
||||
},
|
||||
public: {
|
||||
...chain.rpcUrls.public,
|
||||
http: [twRPC, ...(chain.rpcUrls.public?.http || [])],
|
||||
},
|
||||
},
|
||||
} as TChain;
|
||||
}
|
||||
|
||||
export const supportedChains: Record<SUPPORTED_CHAIN_ID, Chain> = {
|
||||
[ChainId.Mainnet]: enhanceChain(mainnet),
|
||||
[ChainId.Goerli]: enhanceChain(goerli),
|
||||
[ChainId.Polygon]: enhanceChain(polygon),
|
||||
[ChainId.Mumbai]: enhanceChain(polygonMumbai),
|
||||
[ChainId.Avalanche]: enhanceChain(avalanche),
|
||||
[ChainId.AvalancheFujiTestnet]: enhanceChain(avalancheFuji),
|
||||
[ChainId.Fantom]: enhanceChain(fantom),
|
||||
[ChainId.FantomTestnet]: enhanceChain(fantomTestnet),
|
||||
[ChainId.Arbitrum]: enhanceChain(arbitrum),
|
||||
[ChainId.ArbitrumGoerli]: enhanceChain(arbitrumGoerli),
|
||||
[ChainId.Optimism]: enhanceChain(optimism),
|
||||
[ChainId.OptimismGoerli]: enhanceChain(optimismGoerli),
|
||||
[ChainId.BinanceSmartChainMainnet]: enhanceChain(bsc),
|
||||
[ChainId.BinanceSmartChainTestnet]: enhanceChain(bscTestnet),
|
||||
};
|
||||
|
||||
export const thirdwebChains: Chain[] = Object.values(supportedChains);
|
||||
@@ -0,0 +1,93 @@
|
||||
import { SUPPORTED_CHAIN_ID, supportedChains } from "./evm";
|
||||
import type { Ecosystem, GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
const EIP1271_ABI = [
|
||||
"function isValidSignature(bytes32 _message, bytes _signature) public view returns (bytes4)",
|
||||
];
|
||||
const EIP1271_MAGICVALUE = "0x1626ba7e";
|
||||
|
||||
export const checkContractWalletSignature = async (
|
||||
message: string,
|
||||
signature: string,
|
||||
address: string,
|
||||
chainId: number,
|
||||
): Promise<boolean> => {
|
||||
const rpcUrl =
|
||||
supportedChains[chainId as SUPPORTED_CHAIN_ID]?.rpcUrls.default.http[0];
|
||||
if (!rpcUrl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
|
||||
const walletContract = new ethers.Contract(address, EIP1271_ABI, provider);
|
||||
const hashMessage = ethers.utils.hashMessage(message);
|
||||
try {
|
||||
const res = await walletContract.isValidSignature(hashMessage, signature);
|
||||
return res === EIP1271_MAGICVALUE;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export class SignerWallet implements GenericAuthWallet {
|
||||
type: Ecosystem = "evm";
|
||||
#signer: ethers.Signer;
|
||||
|
||||
constructor(signer: ethers.Signer) {
|
||||
this.#signer = signer;
|
||||
}
|
||||
|
||||
public async getAddress(): Promise<string> {
|
||||
return this.#signer.getAddress();
|
||||
}
|
||||
|
||||
public async getChainId(): Promise<number> {
|
||||
return this.#signer.getChainId();
|
||||
}
|
||||
|
||||
public async signMessage(message: string): Promise<string> {
|
||||
return await this.#signer.signMessage(message);
|
||||
}
|
||||
|
||||
public async verifySignature(
|
||||
message: string,
|
||||
signature: string,
|
||||
address: string,
|
||||
chainId?: number,
|
||||
): Promise<boolean> {
|
||||
const messageHash = ethers.utils.hashMessage(message);
|
||||
const messageHashBytes = ethers.utils.arrayify(messageHash);
|
||||
const recoveredAddress = ethers.utils.recoverAddress(
|
||||
messageHashBytes,
|
||||
signature,
|
||||
);
|
||||
|
||||
if (recoveredAddress === address) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the address is a smart contract wallet
|
||||
if (chainId !== undefined) {
|
||||
try {
|
||||
const isValid = await checkContractWalletSignature(
|
||||
message,
|
||||
signature,
|
||||
address,
|
||||
chainId || 1,
|
||||
);
|
||||
return isValid;
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class PrivateKeyWallet extends SignerWallet {
|
||||
constructor(privateKey: string) {
|
||||
super(new ethers.Wallet(privateKey));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Json } from "../../core/schema";
|
||||
import { ThirdwebAuthContext, ThirdwebAuthUser } from "../types";
|
||||
import { Request } from "express";
|
||||
|
||||
function getToken(req: Request): string | undefined {
|
||||
if (req.headers["authorization"]) {
|
||||
const authorizationHeader = req.headers["authorization"].split(" ");
|
||||
if (authorizationHeader?.length === 2) {
|
||||
return authorizationHeader[1];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cookie: string | undefined = !req.cookies
|
||||
? undefined
|
||||
: typeof req.cookies.get === "function"
|
||||
? (req.cookies as any).get("thirdweb_auth_token")
|
||||
: (req.cookies as any).thirdweb_auth_token;
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
export async function getUser<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
>(
|
||||
req: Request,
|
||||
ctx: ThirdwebAuthContext<TData, TSession>,
|
||||
): Promise<ThirdwebAuthUser<TData, TSession> | null> {
|
||||
const token = getToken(req);
|
||||
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let authenticatedUser: ThirdwebAuthUser<TData, TSession>;
|
||||
try {
|
||||
authenticatedUser = await ctx.auth.authenticate<TSession>(token, {
|
||||
validateTokenId: async (tokenId: string) => {
|
||||
if (ctx.authOptions?.validateTokenId) {
|
||||
await ctx.authOptions?.validateTokenId(tokenId);
|
||||
}
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ctx.callbacks?.onUser) {
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
const data = await ctx.callbacks.onUser(authenticatedUser);
|
||||
if (!data) {
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
return { ...authenticatedUser, data: data };
|
||||
}
|
||||
@@ -1,68 +1,63 @@
|
||||
import { Json, ThirdwebAuth as ThirdwebAuthSDK } from "../core";
|
||||
import { getUser } from "./helpers/user";
|
||||
import loginHandler from "./routes/login";
|
||||
import logoutHandler from "./routes/logout";
|
||||
import userHandler from "./routes/user";
|
||||
import {
|
||||
ThirdwebAuthConfig,
|
||||
ThirdwebAuthRoute,
|
||||
ThirdwebAuthUser,
|
||||
} from "./types";
|
||||
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
import { ThirdwebAuthConfig, ThirdwebAuthContext } from "./types";
|
||||
import cookieParser from "cookie-parser";
|
||||
import { Express, NextFunction, Request, Response } from "express";
|
||||
import express, { Request, Response } from "express";
|
||||
|
||||
export * from "./types";
|
||||
|
||||
export function getUser(req: Request): ThirdwebAuthUser | null {
|
||||
return req.user;
|
||||
}
|
||||
|
||||
export function ThirdwebAuth(app: Express, cfg: ThirdwebAuthConfig) {
|
||||
const ctx = {
|
||||
...cfg,
|
||||
sdk: ThirdwebSDK.fromPrivateKey(cfg.privateKey, "mainnet"),
|
||||
const asyncHandler =
|
||||
(fn: CallableFunction) =>
|
||||
(...args: any[]) => {
|
||||
const fnReturn = fn(...args);
|
||||
const next = args[args.length - 1];
|
||||
return Promise.resolve(fnReturn).catch(next);
|
||||
};
|
||||
|
||||
const authUrl = cfg.authUrl?.replace(/\/$/, "") || "/auth";
|
||||
export function ThirdwebAuth<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
>(cfg: ThirdwebAuthConfig<TData, TSession>) {
|
||||
const ctx = {
|
||||
...cfg,
|
||||
auth: new ThirdwebAuthSDK(cfg.wallet, cfg.domain),
|
||||
};
|
||||
|
||||
app.use(cookieParser());
|
||||
const router = express.Router();
|
||||
const cookieMiddleware = cookieParser();
|
||||
|
||||
app.use(async (req: Request, _: Response, next: NextFunction) => {
|
||||
const { sdk, domain } = ctx;
|
||||
let user = null;
|
||||
const token = req.cookies.thirdweb_auth_token;
|
||||
router.use(express.json());
|
||||
router.use(cookieMiddleware);
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
const address = await sdk.auth.authenticate(domain, token);
|
||||
router.post(
|
||||
"/login",
|
||||
asyncHandler((req: Request, res: Response) =>
|
||||
loginHandler(req, res, ctx as ThirdwebAuthContext),
|
||||
),
|
||||
);
|
||||
|
||||
if (ctx.callbacks?.user) {
|
||||
user = await ctx.callbacks.user(address);
|
||||
}
|
||||
router.get(
|
||||
"/user",
|
||||
asyncHandler((req: Request, res: Response) =>
|
||||
userHandler(req, res, ctx as ThirdwebAuthContext),
|
||||
),
|
||||
);
|
||||
|
||||
user = { ...user, address };
|
||||
} catch {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
router.post(
|
||||
"/logout",
|
||||
asyncHandler((req: Request, res: Response) =>
|
||||
logoutHandler(req, res, ctx as ThirdwebAuthContext),
|
||||
),
|
||||
);
|
||||
|
||||
req.user = user as ThirdwebAuthUser | null;
|
||||
next();
|
||||
});
|
||||
|
||||
app.get(`${authUrl}/:route`, (req: Request, res: Response) => {
|
||||
const action = req.params.route as ThirdwebAuthRoute;
|
||||
|
||||
switch (action) {
|
||||
case "login":
|
||||
return loginHandler(req, res, ctx);
|
||||
case "user":
|
||||
return userHandler(req, res);
|
||||
case "logout":
|
||||
return logoutHandler(req, res);
|
||||
default:
|
||||
return res.status(400).json({
|
||||
message: "Invalid route for authentication.",
|
||||
});
|
||||
}
|
||||
});
|
||||
return {
|
||||
authRouter: router,
|
||||
authMiddleware: cookieMiddleware,
|
||||
getUser: (req: Request) => {
|
||||
return getUser<TData, TSession>(req, ctx);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,38 +1,67 @@
|
||||
import { ThirdwebAuthContext } from "../types";
|
||||
import { LoginPayload } from "@thirdweb-dev/sdk";
|
||||
import { GenerateOptions } from "../../core";
|
||||
import { LoginPayloadBodySchema, ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
import { Request, Response } from "express";
|
||||
|
||||
function redirectWithError(req: Request, res: Response, error: string) {
|
||||
const encodedError = encodeURIComponent(error);
|
||||
const url = new URL(req.headers.referer as string);
|
||||
url.searchParams.set("error", encodedError);
|
||||
return res.redirect(url.toString());
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: Request,
|
||||
res: Response,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "GET") {
|
||||
return redirectWithError(req, res, "INVALID_METHOD");
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
const { sdk, domain } = ctx;
|
||||
const parsedPayload = LoginPayloadBodySchema.safeParse(req.body);
|
||||
|
||||
// Get signed login payload from the frontend
|
||||
const payload = JSON.parse(atob(req.query.payload as string)) as LoginPayload;
|
||||
if (!payload) {
|
||||
redirectWithError(req, res, "MISSING_LOGIN_PAYLOAD");
|
||||
if (!parsedPayload.success) {
|
||||
return res.status(400).json({ error: "Invalid login payload" });
|
||||
}
|
||||
|
||||
let token;
|
||||
const payload = parsedPayload.data.payload;
|
||||
|
||||
const validateNonce = async (nonce: string) => {
|
||||
if (ctx.authOptions?.validateNonce) {
|
||||
await ctx.authOptions?.validateNonce(nonce);
|
||||
}
|
||||
};
|
||||
|
||||
const getSession = async (address: string) => {
|
||||
if (ctx.callbacks?.onLogin) {
|
||||
return ctx.callbacks.onLogin(address, req);
|
||||
}
|
||||
};
|
||||
|
||||
const expirationTime = ctx.authOptions?.tokenDurationInSeconds
|
||||
? new Date(Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds)
|
||||
: undefined;
|
||||
|
||||
const generateOptions: GenerateOptions = {
|
||||
verifyOptions: {
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: ctx.authOptions?.chainId,
|
||||
validateNonce,
|
||||
resources: ctx.authOptions?.resources,
|
||||
},
|
||||
expirationTime,
|
||||
session: getSession,
|
||||
};
|
||||
|
||||
let token: string;
|
||||
try {
|
||||
// Generate an access token with the SDK using the signed payload
|
||||
token = await sdk.auth.generateAuthToken(domain, payload);
|
||||
} catch {
|
||||
return redirectWithError(req, res, "INVALID_LOGIN_PAYLOAD");
|
||||
token = await ctx.auth.generate(payload, generateOptions);
|
||||
} catch (err: any) {
|
||||
if (err.message) {
|
||||
return res.status(400).json({ error: err.message });
|
||||
} else if (typeof err === "string") {
|
||||
return res.status(400).json({ error: err });
|
||||
} else {
|
||||
return res.status(400).json({ error: "Invalid login payload" });
|
||||
}
|
||||
}
|
||||
|
||||
// Securely set httpOnly cookie on request to prevent XSS on frontend
|
||||
@@ -40,17 +69,14 @@ export default async function handler(
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
serialize("thirdweb_auth_token", token, {
|
||||
path: "/",
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
}),
|
||||
);
|
||||
|
||||
if (ctx.callbacks?.login) {
|
||||
const address = sdk.auth.verify(domain, payload);
|
||||
await ctx.callbacks.login(address);
|
||||
}
|
||||
|
||||
return res.status(301).redirect(req.query.redirect as string);
|
||||
// Send token in body and as cookie for frontend and backend use cases
|
||||
return res.status(200).json({ token });
|
||||
}
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
import { getUser } from "../helpers/user";
|
||||
import { ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
import { Request, Response } from "express";
|
||||
|
||||
export default async function handler(req: Request, res: Response) {
|
||||
if (req.method !== "GET") {
|
||||
return res.status(400).json({
|
||||
error: "Invalid method. Only GET supported.",
|
||||
export default async function handler(
|
||||
req: Request,
|
||||
res: Response,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
error: "Invalid method. Only POST supported.",
|
||||
});
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onLogout) {
|
||||
const user = await getUser(req, ctx);
|
||||
if (user) {
|
||||
await ctx.callbacks.onLogout(user, req);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the access token to 'none' and expire in 5 seconds
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
serialize("thirdweb_auth_token", "", {
|
||||
path: "/",
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
expires: new Date(Date.now() + 5 * 1000),
|
||||
}),
|
||||
);
|
||||
|
||||
return res.status(301).redirect(req.headers.referer as string);
|
||||
return res.status(200).json({ message: "Succesfully logged out" });
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { getUser } from "../helpers/user";
|
||||
import { ThirdwebAuthContext } from "../types";
|
||||
import { Request, Response } from "express";
|
||||
|
||||
export default async function handler(req: Request, res: Response) {
|
||||
export default async function handler(
|
||||
req: Request,
|
||||
res: Response,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "GET") {
|
||||
return res.status(400).json({
|
||||
error: "Invalid method. Only GET supported.",
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).json(req.user);
|
||||
const user = await getUser(req, ctx);
|
||||
return res.status(200).json(user);
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { ThirdwebAuthUser } from "..";
|
||||
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Express {
|
||||
export interface Request {
|
||||
user: ThirdwebAuthUser | null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,63 @@
|
||||
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
import { ThirdwebAuth } from "../../core";
|
||||
import { Json, LoginPayloadOutputSchema, User } from "../../core/schema";
|
||||
import { GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
import { Request } from "express";
|
||||
import { z } from "zod";
|
||||
|
||||
export const LoginPayloadBodySchema = z.object({
|
||||
payload: LoginPayloadOutputSchema,
|
||||
});
|
||||
|
||||
export type ThirdwebAuthRoute = "login" | "user" | "logout";
|
||||
|
||||
export type ThirdwebAuthConfig = {
|
||||
privateKey: string;
|
||||
export type ThirdwebAuthUser<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = User<TSession> & {
|
||||
data?: TData;
|
||||
};
|
||||
|
||||
export type ThirdwebAuthConfig<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = {
|
||||
domain: string;
|
||||
authUrl?: string;
|
||||
wallet: GenericAuthWallet;
|
||||
authOptions?: {
|
||||
statement?: string;
|
||||
uri?: string;
|
||||
version?: string;
|
||||
chainId?: string;
|
||||
resources?: string[];
|
||||
validateNonce?:
|
||||
| ((nonce: string) => void)
|
||||
| ((nonce: string) => Promise<void>);
|
||||
validateTokenId?:
|
||||
| ((tokenId: string) => void)
|
||||
| ((tokenId: string) => Promise<void>);
|
||||
tokenDurationInSeconds?: number;
|
||||
};
|
||||
cookieOptions?: {
|
||||
domain?: string;
|
||||
path?: string;
|
||||
sameSite?: "lax" | "strict" | "none";
|
||||
};
|
||||
callbacks?: {
|
||||
login?: (address: string) => Promise<void> | void;
|
||||
user?: (
|
||||
address: string,
|
||||
) =>
|
||||
| Promise<Omit<ThirdwebAuthUser, "address">>
|
||||
| Omit<ThirdwebAuthUser, "address">;
|
||||
onLogin?:
|
||||
| ((address: string, req?: Request) => void | TSession)
|
||||
| ((address: string, req?: Request) => Promise<void | TSession>);
|
||||
onUser?:
|
||||
| ((user: User<TSession>, req?: Request) => void | TData)
|
||||
| ((user: User<TSession>, req?: Request) => Promise<void | TData>);
|
||||
onLogout?:
|
||||
| ((user: User, req?: Request) => void)
|
||||
| ((user: User, req?: Request) => Promise<void>);
|
||||
};
|
||||
};
|
||||
|
||||
export type ThirdwebAuthContext = {
|
||||
sdk: ThirdwebSDK;
|
||||
domain: string;
|
||||
callbacks?: {
|
||||
login?: (address: string) => Promise<void> | void;
|
||||
user?: (
|
||||
address: string,
|
||||
) =>
|
||||
| Promise<Omit<ThirdwebAuthUser, "address">>
|
||||
| Omit<ThirdwebAuthUser, "address">;
|
||||
};
|
||||
};
|
||||
|
||||
export type ThirdwebAuthUser = {
|
||||
address: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type RequestWithUser = Request & {
|
||||
user: ThirdwebAuthUser | null;
|
||||
export type ThirdwebAuthContext<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = Omit<Omit<ThirdwebAuthConfig<TData, TSession>, "wallet">, "domain"> & {
|
||||
auth: ThirdwebAuth;
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./core";
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ThirdwebAuth } from "../core";
|
||||
import { ThirdwebNextAuthConfig } from "./types";
|
||||
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
import { serialize } from "cookie";
|
||||
import {
|
||||
GetServerSidePropsContext,
|
||||
@@ -14,7 +14,7 @@ import NextAuth, {
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
|
||||
export function ThirdwebNextAuth(cfg: ThirdwebNextAuthConfig) {
|
||||
const sdk = ThirdwebSDK.fromPrivateKey(cfg.privateKey, "mainnet");
|
||||
const auth = new ThirdwebAuth(cfg.wallet, cfg.domain);
|
||||
|
||||
function ThirdwebProvider(res: GetServerSidePropsContext["res"]) {
|
||||
return CredentialsProvider({
|
||||
@@ -29,8 +29,8 @@ export function ThirdwebNextAuth(cfg: ThirdwebNextAuthConfig) {
|
||||
async authorize({ payload }: any) {
|
||||
try {
|
||||
const parsed = JSON.parse(payload);
|
||||
const token = await sdk.auth.generateAuthToken(cfg.domain, parsed);
|
||||
const address = await sdk.auth.authenticate(cfg.domain, token);
|
||||
const token = await auth.generate(parsed);
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
// Securely set httpOnly cookie on request to prevent XSS on frontend
|
||||
// And set path to / to enable thirdweb_auth_token usage on all endpoints
|
||||
@@ -40,11 +40,11 @@ export function ThirdwebNextAuth(cfg: ThirdwebNextAuthConfig) {
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
sameSite: "none",
|
||||
}),
|
||||
);
|
||||
|
||||
return { address };
|
||||
return { id: user.address, address: user.address };
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
@@ -63,7 +63,7 @@ export function ThirdwebNextAuth(cfg: ThirdwebNextAuthConfig) {
|
||||
}): Promise<Session> {
|
||||
const token = req.cookies.thirdweb_auth_token || "";
|
||||
try {
|
||||
const address = await sdk.auth.authenticate(cfg.domain, token);
|
||||
const address = await auth.authenticate(token);
|
||||
_session.user = { ..._session.user, address } as Session["user"];
|
||||
return _session;
|
||||
} catch {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
import { NextAuthOptions } from "next-auth";
|
||||
|
||||
export type ThirdwebNextAuthConfig = {
|
||||
privateKey: string;
|
||||
domain: string;
|
||||
wallet: GenericAuthWallet;
|
||||
nextOptions: NextAuthOptions;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Json } from "../../core/schema";
|
||||
import { ThirdwebAuthContext, ThirdwebAuthUser } from "../types";
|
||||
import { GetServerSidePropsContext, NextApiRequest } from "next";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
function getToken(
|
||||
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest,
|
||||
): string | undefined {
|
||||
if (!!(req as NextApiRequest).headers["authorization"]) {
|
||||
const authorizationHeader = (req as NextApiRequest).headers[
|
||||
"authorization"
|
||||
]?.split(" ");
|
||||
if (authorizationHeader?.length === 2) {
|
||||
return authorizationHeader[1];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cookie: string | undefined = !req.cookies
|
||||
? undefined
|
||||
: typeof req.cookies.get === "function"
|
||||
? (req.cookies as any).get("thirdweb_auth_token")
|
||||
: (req.cookies as any).thirdweb_auth_token;
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
export async function getUser<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
>(
|
||||
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest,
|
||||
ctx: ThirdwebAuthContext<TData, TSession>,
|
||||
): Promise<ThirdwebAuthUser<TData, TSession> | null> {
|
||||
const token = getToken(req);
|
||||
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let authenticatedUser: ThirdwebAuthUser<TData, TSession>;
|
||||
try {
|
||||
authenticatedUser = await ctx.auth.authenticate<TSession>(token, {
|
||||
validateTokenId: async (tokenId: string) => {
|
||||
if (ctx.authOptions?.validateTokenId) {
|
||||
await ctx.authOptions?.validateTokenId(tokenId);
|
||||
}
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ctx.callbacks?.onUser) {
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
const data = await ctx.callbacks.onUser(authenticatedUser);
|
||||
if (!data) {
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
return { ...authenticatedUser, data: data };
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Json, ThirdwebAuth as ThirdwebAuthSDK } from "../core";
|
||||
import { getUser } from "./helpers/user";
|
||||
import loginHandler from "./routes/login";
|
||||
import logoutHandler from "./routes/logout";
|
||||
import userHandler from "./routes/user";
|
||||
@@ -5,9 +7,7 @@ import {
|
||||
ThirdwebAuthConfig,
|
||||
ThirdwebAuthContext,
|
||||
ThirdwebAuthRoute,
|
||||
ThirdwebAuthUser,
|
||||
} from "./types";
|
||||
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
import { NextRequest } from "next/server";
|
||||
import {
|
||||
GetServerSidePropsContext,
|
||||
@@ -32,7 +32,7 @@ async function ThirdwebAuthRouter(
|
||||
case "user":
|
||||
return await userHandler(req, res, ctx);
|
||||
case "logout":
|
||||
return await logoutHandler(req, res);
|
||||
return await logoutHandler(req, res, ctx);
|
||||
default:
|
||||
return res.status(400).json({
|
||||
message: "Invalid route for authentication.",
|
||||
@@ -40,10 +40,13 @@ async function ThirdwebAuthRouter(
|
||||
}
|
||||
}
|
||||
|
||||
export function ThirdwebAuth(cfg: ThirdwebAuthConfig) {
|
||||
export function ThirdwebAuth<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
>(cfg: ThirdwebAuthConfig<TData, TSession>) {
|
||||
const ctx = {
|
||||
...cfg,
|
||||
sdk: ThirdwebSDK.fromPrivateKey(cfg.privateKey, "mainnet"),
|
||||
auth: new ThirdwebAuthSDK(cfg.wallet, cfg.domain),
|
||||
};
|
||||
|
||||
function ThirdwebAuthHandler(
|
||||
@@ -51,33 +54,18 @@ export function ThirdwebAuth(cfg: ThirdwebAuthConfig) {
|
||||
) {
|
||||
if (args.length === 0) {
|
||||
return async (req: NextApiRequest, res: NextApiResponse) =>
|
||||
await ThirdwebAuthRouter(req, res, ctx);
|
||||
await ThirdwebAuthRouter(req, res, ctx as ThirdwebAuthContext);
|
||||
}
|
||||
|
||||
return ThirdwebAuthRouter(args[0], args[1], ctx);
|
||||
return ThirdwebAuthRouter(args[0], args[1], ctx as ThirdwebAuthContext);
|
||||
}
|
||||
|
||||
async function getUser(
|
||||
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest,
|
||||
) {
|
||||
const { sdk, domain } = ctx;
|
||||
let user: ThirdwebAuthUser | null = null;
|
||||
const token =
|
||||
typeof req.cookies.get === "function"
|
||||
? (req.cookies as any).get("thirdweb_auth_token")
|
||||
: (req.cookies as any).thirdweb_auth_token;
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
const address = await sdk.auth.authenticate(domain, token);
|
||||
user = { address };
|
||||
} catch {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
return { ThirdwebAuthHandler, getUser };
|
||||
return {
|
||||
ThirdwebAuthHandler,
|
||||
getUser: (
|
||||
req: GetServerSidePropsContext["req"] | NextRequest | NextApiRequest,
|
||||
) => {
|
||||
return getUser<TData, TSession>(req, ctx);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,42 +1,67 @@
|
||||
import { ThirdwebAuthContext } from "../types";
|
||||
import { LoginPayload } from "@thirdweb-dev/sdk";
|
||||
import { GenerateOptions } from "../../core";
|
||||
import { LoginPayloadBodySchema, ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
function redirectWithError(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
error: string,
|
||||
) {
|
||||
const encodedError = encodeURIComponent(error);
|
||||
const url = new URL(req.headers.referer as string);
|
||||
url.searchParams.set("error", encodedError);
|
||||
return res.redirect(url.toString());
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "GET") {
|
||||
return redirectWithError(req, res, "INVALID_METHOD");
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ error: "Method not allowed" });
|
||||
}
|
||||
|
||||
const { sdk, domain } = ctx;
|
||||
const parsedPayload = LoginPayloadBodySchema.safeParse(req.body);
|
||||
|
||||
// Get signed login payload from the frontend
|
||||
const payload = JSON.parse(atob(req.query.payload as string)) as LoginPayload;
|
||||
if (!payload) {
|
||||
redirectWithError(req, res, "MISSING_LOGIN_PAYLOAD");
|
||||
if (!parsedPayload.success) {
|
||||
return res.status(400).json({ error: "Invalid login payload" });
|
||||
}
|
||||
|
||||
let token;
|
||||
const payload = parsedPayload.data.payload;
|
||||
|
||||
const validateNonce = async (nonce: string) => {
|
||||
if (ctx.authOptions?.validateNonce) {
|
||||
await ctx.authOptions?.validateNonce(nonce);
|
||||
}
|
||||
};
|
||||
|
||||
const getSession = async (address: string) => {
|
||||
if (ctx.callbacks?.onLogin) {
|
||||
return ctx.callbacks.onLogin(address, req);
|
||||
}
|
||||
};
|
||||
|
||||
const expirationTime = ctx.authOptions?.tokenDurationInSeconds
|
||||
? new Date(Date.now() + 1000 * ctx.authOptions.tokenDurationInSeconds)
|
||||
: undefined;
|
||||
|
||||
const generateOptions: GenerateOptions = {
|
||||
verifyOptions: {
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: ctx.authOptions?.chainId,
|
||||
validateNonce,
|
||||
resources: ctx.authOptions?.resources,
|
||||
},
|
||||
expirationTime,
|
||||
session: getSession,
|
||||
};
|
||||
|
||||
let token: string;
|
||||
try {
|
||||
// Generate an access token with the SDK using the signed payload
|
||||
token = await sdk.auth.generateAuthToken(domain, payload);
|
||||
} catch {
|
||||
return redirectWithError(req, res, "INVALID_LOGIN_PAYLOAD");
|
||||
token = await ctx.auth.generate(payload, generateOptions);
|
||||
} catch (err: any) {
|
||||
if (err.message) {
|
||||
return res.status(400).json({ error: err.message });
|
||||
} else if (typeof err === "string") {
|
||||
return res.status(400).json({ error: err });
|
||||
} else {
|
||||
return res.status(400).json({ error: "Invalid login payload" });
|
||||
}
|
||||
}
|
||||
|
||||
// Securely set httpOnly cookie on request to prevent XSS on frontend
|
||||
@@ -44,17 +69,14 @@ export default async function handler(
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
serialize("thirdweb_auth_token", token, {
|
||||
path: "/",
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
sameSite: ctx.cookieOptions?.sameSite || "none",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
}),
|
||||
);
|
||||
|
||||
if (ctx.callbacks?.login) {
|
||||
const address = sdk.auth.verify(domain, payload);
|
||||
await ctx.callbacks.login(address);
|
||||
}
|
||||
|
||||
return res.status(301).redirect(req.query.redirect as string);
|
||||
// Send token in body and as cookie for frontend and backend use cases
|
||||
return res.status(200).json({ token });
|
||||
}
|
||||
|
||||
@@ -1,24 +1,35 @@
|
||||
import { getUser } from "../helpers/user";
|
||||
import { ThirdwebAuthContext } from "../types";
|
||||
import { serialize } from "cookie";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "GET") {
|
||||
return res.status(400).json({
|
||||
error: "Invalid method. Only GET supported.",
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
error: "Invalid method. Only POST supported.",
|
||||
});
|
||||
}
|
||||
|
||||
if (ctx.callbacks?.onLogout) {
|
||||
const user = await getUser(req, ctx);
|
||||
if (user) {
|
||||
await ctx.callbacks.onLogout(user, req);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the access token to 'none' and expire in 5 seconds
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
serialize("thirdweb_auth_token", "", {
|
||||
path: "/",
|
||||
domain: ctx.cookieOptions?.domain,
|
||||
path: ctx.cookieOptions?.path || "/",
|
||||
expires: new Date(Date.now() + 5 * 1000),
|
||||
}),
|
||||
);
|
||||
|
||||
return res.status(301).redirect(req.headers.referer as string);
|
||||
return res.status(200).json({ message: "Succesfully logged out" });
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ThirdwebAuthContext, ThirdwebAuthUser } from "../types";
|
||||
import { getUser } from "../helpers/user";
|
||||
import { ThirdwebAuthContext } from "../types";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
@@ -12,23 +13,6 @@ export default async function handler(
|
||||
});
|
||||
}
|
||||
|
||||
const { sdk, domain } = ctx;
|
||||
let user = null;
|
||||
const token = req.cookies.thirdweb_auth_token;
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
const address = await sdk.auth.authenticate(domain, token);
|
||||
|
||||
if (ctx.callbacks?.user) {
|
||||
user = await ctx.callbacks.user(address);
|
||||
}
|
||||
|
||||
user = { ...user, address };
|
||||
} catch {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).json(user as ThirdwebAuthUser | null);
|
||||
const user = await getUser(req, ctx);
|
||||
return res.status(200).json(user);
|
||||
}
|
||||
|
||||
@@ -1,34 +1,75 @@
|
||||
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
import { ThirdwebAuth } from "../../core";
|
||||
import { Json, LoginPayloadOutputSchema, User } from "../../core/schema";
|
||||
import { GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
import { GetServerSidePropsContext, NextApiRequest } from "next";
|
||||
import { NextRequest } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
export const LoginPayloadBodySchema = z.object({
|
||||
payload: LoginPayloadOutputSchema,
|
||||
});
|
||||
|
||||
type RequestType =
|
||||
| GetServerSidePropsContext["req"]
|
||||
| NextRequest
|
||||
| NextApiRequest;
|
||||
|
||||
export type ThirdwebAuthRoute = "login" | "logout" | "user";
|
||||
|
||||
export type ThirdwebAuthConfig = {
|
||||
privateKey: string;
|
||||
export type ThirdwebAuthUser<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = User<TSession> & {
|
||||
data?: TData;
|
||||
};
|
||||
|
||||
export type ThirdwebAuthConfig<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = {
|
||||
domain: string;
|
||||
wallet: GenericAuthWallet;
|
||||
authOptions?: {
|
||||
statement?: string;
|
||||
uri?: string;
|
||||
version?: string;
|
||||
chainId?: string;
|
||||
resources?: string[];
|
||||
validateNonce?:
|
||||
| ((nonce: string) => void)
|
||||
| ((nonce: string) => Promise<void>);
|
||||
validateTokenId?:
|
||||
| ((tokenId: string) => void)
|
||||
| ((tokenId: string) => Promise<void>);
|
||||
tokenDurationInSeconds?: number;
|
||||
};
|
||||
cookieOptions?: {
|
||||
domain?: string;
|
||||
path?: string;
|
||||
sameSite?: "lax" | "strict" | "none";
|
||||
};
|
||||
callbacks?: {
|
||||
login?: (address: string) => Promise<void> | void;
|
||||
user?: (
|
||||
address: string,
|
||||
) =>
|
||||
| Promise<Omit<ThirdwebAuthUser, "address">>
|
||||
| Omit<ThirdwebAuthUser, "address">;
|
||||
onLogin?:
|
||||
| ((address: string, req?: NextApiRequest) => void | TSession)
|
||||
| ((address: string, req?: NextApiRequest) => Promise<void | TSession>);
|
||||
onUser?:
|
||||
| (<TRequestType extends RequestType = RequestType>(
|
||||
user: User<TSession>,
|
||||
req?: TRequestType,
|
||||
) => void | TData)
|
||||
| (<TRequestType extends RequestType = RequestType>(
|
||||
user: User<TSession>,
|
||||
req?: TRequestType,
|
||||
) => Promise<void | TData>);
|
||||
onLogout?:
|
||||
| ((user: User, req?: NextApiRequest) => void)
|
||||
| ((user: User, req?: NextApiRequest) => Promise<void>);
|
||||
};
|
||||
};
|
||||
|
||||
export type ThirdwebAuthContext = {
|
||||
sdk: ThirdwebSDK;
|
||||
domain: string;
|
||||
callbacks?: {
|
||||
login?: (address: string) => Promise<void> | void;
|
||||
user?: (
|
||||
address: string,
|
||||
) =>
|
||||
| Promise<Omit<ThirdwebAuthUser, "address">>
|
||||
| Omit<ThirdwebAuthUser, "address">;
|
||||
};
|
||||
};
|
||||
|
||||
export type ThirdwebAuthUser = {
|
||||
address: string;
|
||||
[key: string]: any;
|
||||
export type ThirdwebAuthContext<
|
||||
TData extends Json = Json,
|
||||
TSession extends Json = Json,
|
||||
> = Omit<Omit<ThirdwebAuthConfig<TData, TSession>, "wallet">, "domain"> & {
|
||||
auth: ThirdwebAuth;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import * as ed25519 from "@noble/ed25519";
|
||||
import { Signer, Keypair, PublicKey } from "@solana/web3.js";
|
||||
import type { Ecosystem, GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
import bs58 from "bs58";
|
||||
import nacl from "tweetnacl";
|
||||
|
||||
export interface SolanaSigner {
|
||||
publicKey: PublicKey;
|
||||
signMessage(message: Uint8Array): Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
export class SignerWallet implements GenericAuthWallet {
|
||||
type: Ecosystem = "solana";
|
||||
private signer: SolanaSigner;
|
||||
|
||||
constructor(signer: SolanaSigner) {
|
||||
this.signer = signer;
|
||||
}
|
||||
|
||||
public async getAddress(): Promise<string> {
|
||||
return this.signer.publicKey.toBase58();
|
||||
}
|
||||
|
||||
public async signMessage(message: string): Promise<string> {
|
||||
const encodedMessage = new TextEncoder().encode(message);
|
||||
const signedMessage = await this.signer.signMessage(encodedMessage);
|
||||
const signature = bs58.encode(signedMessage);
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
public async verifySignature(
|
||||
message: string,
|
||||
signature: string,
|
||||
address: string,
|
||||
): Promise<boolean> {
|
||||
return nacl.sign.detached.verify(
|
||||
new TextEncoder().encode(message),
|
||||
bs58.decode(signature),
|
||||
bs58.decode(address),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class KeypairSigner implements SolanaSigner {
|
||||
private keypair: Signer;
|
||||
public publicKey: PublicKey;
|
||||
|
||||
constructor(keypair: Signer) {
|
||||
this.keypair = keypair;
|
||||
this.publicKey = keypair.publicKey;
|
||||
}
|
||||
|
||||
public async signMessage(message: Uint8Array): Promise<Uint8Array> {
|
||||
return ed25519.sync.sign(message, this.keypair.secretKey.slice(0, 32));
|
||||
}
|
||||
}
|
||||
|
||||
export class KeypairWallet extends SignerWallet {
|
||||
constructor(keypair: Keypair) {
|
||||
super(new KeypairSigner(keypair));
|
||||
}
|
||||
}
|
||||
|
||||
export class PrivateKeyWallet extends KeypairWallet {
|
||||
constructor(privateKey: string) {
|
||||
super(Keypair.fromSecretKey(bs58.decode(privateKey)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extension": [
|
||||
"ts"
|
||||
],
|
||||
"require": [
|
||||
"@swc-node/register"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
import { ThirdwebAuth } from "../src/core";
|
||||
import { SignerWallet } from "../src/evm";
|
||||
import { expect } from "chai";
|
||||
import { Wallet } from "ethers";
|
||||
|
||||
describe("Wallet Authentication", async () => {
|
||||
let adminWallet: any, signerWallet: any, attackerWallet: any;
|
||||
let auth: ThirdwebAuth;
|
||||
|
||||
before(async () => {
|
||||
const [adminSigner, signerSigner, attackerSigner] = [
|
||||
Wallet.createRandom(),
|
||||
Wallet.createRandom(),
|
||||
Wallet.createRandom(),
|
||||
];
|
||||
|
||||
adminWallet = new SignerWallet(adminSigner);
|
||||
signerWallet = new SignerWallet(signerSigner);
|
||||
attackerWallet = new SignerWallet(attackerSigner);
|
||||
|
||||
auth = new ThirdwebAuth(signerWallet, "thirdweb.com");
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
auth.updateWallet(signerWallet);
|
||||
});
|
||||
|
||||
it("Should verify logged in wallet", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload);
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should verify logged in wallet with chain ID and expiration", async () => {
|
||||
const payload = await auth.login({
|
||||
expirationTime: new Date(Date.now() + 1000 * 60 * 5),
|
||||
chainId: "137",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
chainId: "137",
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should verify payload with resources", async () => {
|
||||
const payload = await auth.login({
|
||||
resources: ["https://example.com", "https://test.com"],
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
resources: ["https://example.com", "https://test.com"],
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject payload without necessary resources", async () => {
|
||||
const payload = await auth.login({
|
||||
resources: ["https://example.com"],
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
resources: ["https://example.com", "https://test.com"],
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal(
|
||||
"Login request is missing required resources: https://test.com",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should verify payload with customized statement", async () => {
|
||||
const payload = await auth.login({
|
||||
statement: "Please sign!",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
statement: "Please sign!",
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect statement", async () => {
|
||||
const payload = await auth.login({
|
||||
statement: "Please sign!",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
statement: "Please sign again!",
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.include(
|
||||
"Expected statement 'Please sign again!' does not match statement on payload",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject invalid nonce", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
validateNonce: (nonce: string) => {
|
||||
if (nonce === payload.payload.nonce) {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal("Login request nonce is invalid");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should accept valid nonce", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
validateNonce: (nonce: string) => {
|
||||
if (nonce !== payload.payload.nonce) {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect domain", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, { domain: "test.thirdweb.com" });
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal(
|
||||
"Expected domain 'test.thirdweb.com' does not match domain on payload 'thirdweb.com'",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject expired login payload", async () => {
|
||||
const payload = await auth.login({
|
||||
expirationTime: new Date(Date.now() - 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal("Login request has expired");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect chain ID", async () => {
|
||||
const payload = await auth.login({
|
||||
chainId: "1",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
chainId: "137",
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal(
|
||||
"Expected chain ID '137' does not match chain ID on payload '1'",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect signer", async () => {
|
||||
const payload = await auth.login();
|
||||
payload.payload.address = await attackerWallet.getAddress();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("does not match payload address");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should generate valid authentication token", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject token with incorrect domain", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
|
||||
try {
|
||||
await auth.authenticate(token, { domain: "test.thirdweb.com" });
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain(
|
||||
"Expected token to be for the domain 'test.thirdweb.com', but found token with domain 'thirdweb.com'",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject token before invalid before", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
invalidBefore: new Date(Date.now() + 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("This token is invalid before");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject expired authentication token", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
expirationTime: new Date(Date.now() - 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("This token expired");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject if admin address is not connected wallet address", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
|
||||
auth.updateWallet(signerWallet);
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain(
|
||||
`Expected the connected wallet address '${await signerWallet.getAddress()}' to match the token issuer address '${await adminWallet.getAddress()}'`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should accept token with valid token ID", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
tokenId: "test",
|
||||
});
|
||||
|
||||
const user = await auth.authenticate(token, {
|
||||
validateTokenId: (tokenId: string) => {
|
||||
if (tokenId !== "test") {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject token with invalid token ID", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
tokenId: "test",
|
||||
});
|
||||
|
||||
try {
|
||||
await auth.authenticate(token, {
|
||||
validateTokenId: (tokenId: string) => {
|
||||
if (tokenId !== "invalid") {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("Token ID is invalid");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should propagate session on token", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
session: { role: "admin" },
|
||||
});
|
||||
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
expect(user.session).to.deep.equal({ role: "admin" });
|
||||
});
|
||||
|
||||
it("Should call session callback function", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
session: (address: string) => {
|
||||
return { address, role: "admin" };
|
||||
},
|
||||
});
|
||||
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
expect(user.session).to.deep.equal({
|
||||
address: await signerWallet.getAddress(),
|
||||
role: "admin",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,353 @@
|
||||
import { ThirdwebAuth } from "../src/core";
|
||||
import { KeypairWallet } from "../src/solana";
|
||||
import { Keypair } from "@solana/web3.js";
|
||||
import { expect } from "chai";
|
||||
|
||||
describe("Wallet Authentication", async () => {
|
||||
let adminWallet: any, signerWallet: any, attackerWallet: any;
|
||||
let auth: ThirdwebAuth;
|
||||
|
||||
before(async () => {
|
||||
const [adminSigner, signerSigner, attackerSigner] = [
|
||||
Keypair.generate(),
|
||||
Keypair.generate(),
|
||||
Keypair.generate(),
|
||||
];
|
||||
|
||||
adminWallet = new KeypairWallet(adminSigner);
|
||||
signerWallet = new KeypairWallet(signerSigner);
|
||||
attackerWallet = new KeypairWallet(attackerSigner);
|
||||
|
||||
auth = new ThirdwebAuth(signerWallet, "thirdweb.com");
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
auth.updateWallet(signerWallet);
|
||||
});
|
||||
|
||||
it("Should verify logged in wallet", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload);
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should verify logged in wallet with chain ID and expiration", async () => {
|
||||
const payload = await auth.login({
|
||||
expirationTime: new Date(Date.now() + 1000 * 60 * 5),
|
||||
chainId: "137",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
chainId: "137",
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should verify payload with resources", async () => {
|
||||
const payload = await auth.login({
|
||||
resources: ["https://example.com", "https://test.com"],
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
resources: ["https://example.com", "https://test.com"],
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject payload without necessary resources", async () => {
|
||||
const payload = await auth.login({
|
||||
resources: ["https://example.com"],
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
resources: ["https://example.com", "https://test.com"],
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal(
|
||||
"Login request is missing required resources: https://test.com",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should verify payload with customized statement", async () => {
|
||||
const payload = await auth.login({
|
||||
statement: "Please sign!",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
statement: "Please sign!",
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect statement", async () => {
|
||||
const payload = await auth.login({
|
||||
statement: "Please sign!",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
statement: "Please sign again!",
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.include(
|
||||
"Expected statement 'Please sign again!' does not match statement on payload",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject invalid nonce", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
validateNonce: (nonce: string) => {
|
||||
if (nonce === payload.payload.nonce) {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal("Login request nonce is invalid");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should accept valid nonce", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const address = await auth.verify(payload, {
|
||||
validateNonce: (nonce: string) => {
|
||||
if (nonce !== payload.payload.nonce) {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect domain", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, { domain: "test.thirdweb.com" });
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal(
|
||||
"Expected domain 'test.thirdweb.com' does not match domain on payload 'thirdweb.com'",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject expired login payload", async () => {
|
||||
const payload = await auth.login({
|
||||
expirationTime: new Date(Date.now() - 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal("Login request has expired");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect chain ID", async () => {
|
||||
const payload = await auth.login({
|
||||
chainId: "1",
|
||||
});
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload, {
|
||||
chainId: "137",
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.equal(
|
||||
"Expected chain ID '137' does not match chain ID on payload '1'",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject payload with incorrect signer", async () => {
|
||||
const payload = await auth.login();
|
||||
payload.payload.address = await attackerWallet.getAddress();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
try {
|
||||
await auth.verify(payload);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("does not match payload address");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should generate valid authentication token", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject token with incorrect domain", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
|
||||
try {
|
||||
await auth.authenticate(token, { domain: "test.thirdweb.com" });
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain(
|
||||
"Expected token to be for the domain 'test.thirdweb.com', but found token with domain 'thirdweb.com'",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject token before invalid before", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
invalidBefore: new Date(Date.now() + 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("This token is invalid before");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject expired authentication token", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
expirationTime: new Date(Date.now() - 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("This token expired");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should reject if admin address is not connected wallet address", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload);
|
||||
|
||||
auth.updateWallet(signerWallet);
|
||||
try {
|
||||
await auth.authenticate(token);
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain(
|
||||
`Expected the connected wallet address '${await signerWallet.getAddress()}' to match the token issuer address '${await adminWallet.getAddress()}'`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should accept token with valid token ID", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
tokenId: "test",
|
||||
});
|
||||
|
||||
const user = await auth.authenticate(token, {
|
||||
validateTokenId: (tokenId: string) => {
|
||||
if (tokenId !== "test") {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
});
|
||||
|
||||
it("Should reject token with invalid token ID", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
tokenId: "test",
|
||||
});
|
||||
|
||||
try {
|
||||
await auth.authenticate(token, {
|
||||
validateTokenId: (tokenId: string) => {
|
||||
if (tokenId !== "invalid") {
|
||||
throw new Error();
|
||||
}
|
||||
},
|
||||
});
|
||||
expect.fail();
|
||||
} catch (err: any) {
|
||||
expect(err.message).to.contain("Token ID is invalid");
|
||||
}
|
||||
});
|
||||
|
||||
it("Should propagate session on token", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
session: { role: "admin" },
|
||||
});
|
||||
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
expect(user.session).to.deep.equal({ role: "admin" });
|
||||
});
|
||||
|
||||
it("Should call session callback function", async () => {
|
||||
const payload = await auth.login();
|
||||
|
||||
auth.updateWallet(adminWallet);
|
||||
const token = await auth.generate(payload, {
|
||||
session: (address: string) => {
|
||||
return { address, role: "admin" };
|
||||
},
|
||||
});
|
||||
|
||||
const user = await auth.authenticate(token);
|
||||
|
||||
expect(user.address).to.equal(await signerWallet.getAddress());
|
||||
expect(user.session).to.deep.equal({
|
||||
address: await signerWallet.getAddress(),
|
||||
role: "admin",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@thirdweb-dev/tsconfig/base.json",
|
||||
"extends": "@thirdweb-dev/tsconfig/sdk.json",
|
||||
"include": ["."],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,427 @@
|
||||
# thirdweb
|
||||
|
||||
## 0.9.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`55b00ef`](https://github.com/thirdweb-dev/js/commit/55b00eff2668eb751b9f907e035b3a0c122d2183)]:
|
||||
- @thirdweb-dev/sdk@3.8.2
|
||||
|
||||
## 0.9.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`448b357`](https://github.com/thirdweb-dev/js/commit/448b357ed4b864e05e531837ca3bc97fbcab3291)]:
|
||||
- @thirdweb-dev/sdk@3.8.1
|
||||
|
||||
## 0.9.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#549](https://github.com/thirdweb-dev/js/pull/549) [`912738b`](https://github.com/thirdweb-dev/js/commit/912738bd0afdf81e118c720811911ba3d1979ac0) Thanks [@kumaryash90](https://github.com/kumaryash90)! - add staking bases to CLI
|
||||
|
||||
- Updated dependencies [[`0b38dac`](https://github.com/thirdweb-dev/js/commit/0b38dac852e54d0bccd263631f4342aee39d7c29), [`29d0c62`](https://github.com/thirdweb-dev/js/commit/29d0c62610cdb88f75d19574bddcb028b5c40393), [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c), [`f295ec6`](https://github.com/thirdweb-dev/js/commit/f295ec67b4cc422c51beda94cbc4d54a31ce9566), [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c), [`f1aecc3`](https://github.com/thirdweb-dev/js/commit/f1aecc30499e39fb23a205bade939bd939f0d0e4)]:
|
||||
- @thirdweb-dev/sdk@3.8.0
|
||||
|
||||
## 0.9.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ec1f232`](https://github.com/thirdweb-dev/js/commit/ec1f2324c7a0e58c2d2a209cfe0d194807ba5b70), [`d29f7e7`](https://github.com/thirdweb-dev/js/commit/d29f7e78f09063630e812dd41164d8a41d4f97dd)]:
|
||||
- @thirdweb-dev/sdk@3.7.4
|
||||
|
||||
## 0.9.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#537](https://github.com/thirdweb-dev/js/pull/537) [`cf96333`](https://github.com/thirdweb-dev/js/commit/cf96333fa2f46a4a73a822a0b1248891cc890267) Thanks [@adam-maj](https://github.com/adam-maj)! - Fix cache error on windows OS
|
||||
|
||||
## 0.9.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#528](https://github.com/thirdweb-dev/js/pull/528) [`ab1f8bb`](https://github.com/thirdweb-dev/js/commit/ab1f8bb6f658dadbca2cb53decbc893050217407) Thanks [@adam-maj](https://github.com/adam-maj)! - Added --skip-update-check flag to CLI to prevent checking for new versions and automatically updating before running commands. Also, only check for check for updates every 24 hours.
|
||||
|
||||
- Updated dependencies [[`b45d761`](https://github.com/thirdweb-dev/js/commit/b45d761f8f2695b48312ec6eff15e0549e6a8a41), [`5f8ec45`](https://github.com/thirdweb-dev/js/commit/5f8ec45af7c81d075a84846098f38cad90d03fb9), [`04de5b2`](https://github.com/thirdweb-dev/js/commit/04de5b278362573991140c3a496de7451e5c770d)]:
|
||||
- @thirdweb-dev/sdk@3.7.3
|
||||
|
||||
## 0.9.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`19b8e18`](https://github.com/thirdweb-dev/js/commit/19b8e18b6bf68e374c817fefbf2fd29ac5573052)]:
|
||||
- @thirdweb-dev/sdk@3.7.2
|
||||
|
||||
## 0.9.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@3.7.1
|
||||
|
||||
## 0.9.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#517](https://github.com/thirdweb-dev/js/pull/517) [`9e591b3`](https://github.com/thirdweb-dev/js/commit/9e591b32b78f1300b2014d44d591d297249a1f84) Thanks [@avneesh0612](https://github.com/avneesh0612)! - add express, node.js, and react native templates
|
||||
|
||||
- Updated dependencies [[`f37e86b`](https://github.com/thirdweb-dev/js/commit/f37e86b7d21f7da0df6ab549cb903dc99db10a79), [`f15733f`](https://github.com/thirdweb-dev/js/commit/f15733f34c5985eec5593b3d9196e5528ba96443)]:
|
||||
- @thirdweb-dev/sdk@3.7.0
|
||||
|
||||
## 0.9.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`7cab8d1`](https://github.com/thirdweb-dev/js/commit/7cab8d1679f8d007091aa03adb83add3822a504a)]:
|
||||
- @thirdweb-dev/sdk@3.6.11
|
||||
|
||||
## 0.9.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#483](https://github.com/thirdweb-dev/js/pull/483) [`4e8ff58`](https://github.com/thirdweb-dev/js/commit/4e8ff582084c3d201314e6c5dff5b79f15eb6292) Thanks [@jnsdls](https://github.com/jnsdls)! - `thirdweb create` with usage of `forge` should now function correctly in windows CMD shell
|
||||
|
||||
- Updated dependencies [[`5fba324`](https://github.com/thirdweb-dev/js/commit/5fba324ac17ab02bc8f13d82a232bc5c6970c8e5), [`894cbef`](https://github.com/thirdweb-dev/js/commit/894cbefc6361f23fc528a6c6819f5c71793d46e4), [`557429b`](https://github.com/thirdweb-dev/js/commit/557429b5cfb3af2983ee01cf7d12d41ee0557593), [`73dc026`](https://github.com/thirdweb-dev/js/commit/73dc026ff9d0ac9099bd5a9a9cab8fdbfc0ae723), [`9eaa21d`](https://github.com/thirdweb-dev/js/commit/9eaa21d09ab9c700aea61a2a25f8ca9859d20857), [`68c8e3c`](https://github.com/thirdweb-dev/js/commit/68c8e3c4d79f5d56dc4414241bcca0d88285fcca), [`bff433e`](https://github.com/thirdweb-dev/js/commit/bff433e12150dc029e33a578219c8437a510da99)]:
|
||||
- @thirdweb-dev/sdk@3.6.10
|
||||
|
||||
## 0.9.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e913e0d`](https://github.com/thirdweb-dev/js/commit/e913e0daa9ece9a6274f7ffa4e66bdcbf32e6ada), [`5862c55`](https://github.com/thirdweb-dev/js/commit/5862c558fb48604b5aca4defd1ccc06fc3536358), [`73883f5`](https://github.com/thirdweb-dev/js/commit/73883f56d0ed0a35ace8b98b96caa782443be22e), [`f7a74a3`](https://github.com/thirdweb-dev/js/commit/f7a74a3b7c489ada5b1ec435632a326334a33c9b)]:
|
||||
- @thirdweb-dev/sdk@3.6.9
|
||||
|
||||
## 0.9.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#454](https://github.com/thirdweb-dev/js/pull/454) [`c673e39`](https://github.com/thirdweb-dev/js/commit/c673e39f23ef082097d73d62910580e8fad400a0) Thanks [@jnsdls](https://github.com/jnsdls)! - upgraded dependencies
|
||||
|
||||
- [#461](https://github.com/thirdweb-dev/js/pull/461) [`af85389`](https://github.com/thirdweb-dev/js/commit/af85389dc07fb163b8f9d70093eb1b691e47561a) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Fix deprecated links
|
||||
|
||||
- Updated dependencies [[`c673e39`](https://github.com/thirdweb-dev/js/commit/c673e39f23ef082097d73d62910580e8fad400a0), [`ea95c5f`](https://github.com/thirdweb-dev/js/commit/ea95c5f609e306e333ee0f73f7920503358ca848), [`699a2b1`](https://github.com/thirdweb-dev/js/commit/699a2b16fb991c474ec57db8f178e2601d631f39), [`4cdd0bd`](https://github.com/thirdweb-dev/js/commit/4cdd0bd6348494a256d7c6a2bdf8f7b5c20f6877), [`a8267f9`](https://github.com/thirdweb-dev/js/commit/a8267f912df84c58d3fe3f47b90bd474f73c84ca)]:
|
||||
- @thirdweb-dev/sdk@3.6.8
|
||||
|
||||
## 0.9.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#441](https://github.com/thirdweb-dev/js/pull/441) [`64f24f1`](https://github.com/thirdweb-dev/js/commit/64f24f1bb167c3d0cb3beca51fd300eaec21cc27) Thanks [@adam-maj](https://github.com/adam-maj)! - CLI autoupdates on run
|
||||
|
||||
- [#447](https://github.com/thirdweb-dev/js/pull/447) [`e7740a5`](https://github.com/thirdweb-dev/js/commit/e7740a59db022a3396194ac66cd195d8ae754d57) Thanks [@adam-maj](https://github.com/adam-maj)! - Detect whether forge is installed
|
||||
|
||||
- Updated dependencies [[`3731459`](https://github.com/thirdweb-dev/js/commit/3731459d33f1ded7ebb69124809449b901b3ad3d), [`cac6c30`](https://github.com/thirdweb-dev/js/commit/cac6c30bca5e17df81d746ef81316af47d5e252e), [`7a37e56`](https://github.com/thirdweb-dev/js/commit/7a37e564fd5d5a9df84c8da44ecaf6c42f67a0e2)]:
|
||||
- @thirdweb-dev/sdk@3.6.7
|
||||
|
||||
## 0.9.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`8c6cdaa`](https://github.com/thirdweb-dev/js/commit/8c6cdaa2887fb2cc40d3ee6991233d195d103805)]:
|
||||
- @thirdweb-dev/sdk@3.6.6
|
||||
|
||||
## 0.9.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#444](https://github.com/thirdweb-dev/js/pull/444) [`94f49b1`](https://github.com/thirdweb-dev/js/commit/94f49b1294bc0208171b16ed413cbb44644af017) Thanks [@adam-maj](https://github.com/adam-maj)! - Add changeset
|
||||
|
||||
## 0.9.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`efc56fa`](https://github.com/thirdweb-dev/js/commit/efc56fa5802490ac8ef50037658d046afd89e9a1)]:
|
||||
- @thirdweb-dev/sdk@3.6.5
|
||||
|
||||
## 0.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#430](https://github.com/thirdweb-dev/js/pull/430) [`e684d7e`](https://github.com/thirdweb-dev/js/commit/e684d7e1ca16f633bce8698529d16bf60c264aa4) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix uploading contracts with special characters in the comments
|
||||
|
||||
- [#435](https://github.com/thirdweb-dev/js/pull/435) [`bef97f7`](https://github.com/thirdweb-dev/js/commit/bef97f72f4860596b7b70b05e67fcadf2c9a012e) Thanks [@adam-maj](https://github.com/adam-maj)! - Make solc default backup compilation
|
||||
|
||||
- [#432](https://github.com/thirdweb-dev/js/pull/432) [`3e87dd3`](https://github.com/thirdweb-dev/js/commit/3e87dd3ccb04a2b73437357c939a251e83b783b2) Thanks [@adam-maj](https://github.com/adam-maj)! - Display file name on duplicate contract names
|
||||
|
||||
- [#431](https://github.com/thirdweb-dev/js/pull/431) [`c42545a`](https://github.com/thirdweb-dev/js/commit/c42545a64725492136025fa1dcdb4d46ba674a30) Thanks [@adam-maj](https://github.com/adam-maj)! - Add changeset
|
||||
|
||||
- [#428](https://github.com/thirdweb-dev/js/pull/428) [`6e20d1f`](https://github.com/thirdweb-dev/js/commit/6e20d1f21a622f102e94b956f700d0871c0dc779) Thanks [@adam-maj](https://github.com/adam-maj)! - Update CLI to enable create contract inside contracts project
|
||||
|
||||
- Updated dependencies [[`f451da6`](https://github.com/thirdweb-dev/js/commit/f451da6395689a5f89800ee63f34b6175b61f703), [`af3acc6`](https://github.com/thirdweb-dev/js/commit/af3acc6b10751b840e56aef6400da5eea6040df2), [`1bfb91d`](https://github.com/thirdweb-dev/js/commit/1bfb91dbf3a39160c987a425813ac5dbb84703ad), [`ee47840`](https://github.com/thirdweb-dev/js/commit/ee478407673b0416e0c1cfe2be11bd6963395348), [`def4251`](https://github.com/thirdweb-dev/js/commit/def42511ff5a20d83f9094164dafb87e412571b5), [`034a257`](https://github.com/thirdweb-dev/js/commit/034a257442314c67729f7bafcbe3740cff33fa32), [`eca4776`](https://github.com/thirdweb-dev/js/commit/eca47763cd89cc3b7aa57b542971837987540b55), [`639e535`](https://github.com/thirdweb-dev/js/commit/639e535ed55280ad9d081001aab3f5af72bb3e45)]:
|
||||
- @thirdweb-dev/sdk@3.6.4
|
||||
|
||||
## 0.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#415](https://github.com/thirdweb-dev/js/pull/415) [`48d91e4`](https://github.com/thirdweb-dev/js/commit/48d91e4431a3740d0f98f730ed5abf5b287f92c1) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Fix mappings for prebuilts redirect on deploy
|
||||
|
||||
## 0.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#403](https://github.com/thirdweb-dev/js/pull/403) [`27996cb`](https://github.com/thirdweb-dev/js/commit/27996cb4bc6f054f45b0a66b0a3aca732e0ef610) Thanks [@avneesh0612](https://github.com/avneesh0612)! - Add CLI Support for solana with --solana flag on create command
|
||||
|
||||
- [#283](https://github.com/thirdweb-dev/js/pull/283) [`2eaa67a`](https://github.com/thirdweb-dev/js/commit/2eaa67acbe21464e7715d0b696afdc024460a44d) Thanks [@adam-maj](https://github.com/adam-maj)! - add capability to deploy apps
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#413](https://github.com/thirdweb-dev/js/pull/413) [`1b0bf99`](https://github.com/thirdweb-dev/js/commit/1b0bf99da796df88ff307dd004cdaf239fb2b1ba) Thanks [@furqanrydhan](https://github.com/furqanrydhan)! - small interface tweaks
|
||||
|
||||
- Updated dependencies [[`48797c7`](https://github.com/thirdweb-dev/js/commit/48797c7cf0695965e33eb6bf602be4652f7085a8), [`83dcaf7`](https://github.com/thirdweb-dev/js/commit/83dcaf70584af8c5cdf0427f9fc1ba82d89f0887), [`f03be39`](https://github.com/thirdweb-dev/js/commit/f03be398e0d7b75c9c5b4fb643f35903e89a17e7), [`2eaa67a`](https://github.com/thirdweb-dev/js/commit/2eaa67acbe21464e7715d0b696afdc024460a44d), [`47f8945`](https://github.com/thirdweb-dev/js/commit/47f8945325447874b1ace0359919c1d54fd55436)]:
|
||||
- @thirdweb-dev/sdk@3.6.3
|
||||
|
||||
## 0.8.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ccb7db4`](https://github.com/thirdweb-dev/js/commit/ccb7db48739b8dddcb2c032b3b6e3e5200485715), [`9151aa7`](https://github.com/thirdweb-dev/js/commit/9151aa7dc80754a7aa4fd3f6ce0207bccbb6451f), [`5a01c53`](https://github.com/thirdweb-dev/js/commit/5a01c537e30bd7ef562aad1012413990de6faedf)]:
|
||||
- @thirdweb-dev/sdk@3.6.2
|
||||
|
||||
## 0.8.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`15514f1`](https://github.com/thirdweb-dev/js/commit/15514f190a844d809ae5b1edb580aab46a682485), [`7c0744a`](https://github.com/thirdweb-dev/js/commit/7c0744a006e78987ad8b271b2c64f4bac7759510), [`f8bceec`](https://github.com/thirdweb-dev/js/commit/f8bceec10c60b30dc3d6f45926e2a9e018202578), [`887af24`](https://github.com/thirdweb-dev/js/commit/887af24450e316c2ea390cd49c0563cebe793c0f), [`96bd92e`](https://github.com/thirdweb-dev/js/commit/96bd92e724ec7d880d23348071b26ab8c21dd868)]:
|
||||
- @thirdweb-dev/sdk@3.6.1
|
||||
|
||||
## 0.8.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`7cb8e59`](https://github.com/thirdweb-dev/js/commit/7cb8e59be48dc923316eb36ab43a1bef7364f6b1), [`0ed77d2`](https://github.com/thirdweb-dev/js/commit/0ed77d2c9e7526444ef660aab82a950865234aa6), [`04a47e8`](https://github.com/thirdweb-dev/js/commit/04a47e8971d28da2532ebd9db824eeb459c17bd4)]:
|
||||
- @thirdweb-dev/sdk@3.6.0
|
||||
|
||||
## 0.8.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3eba69a`](https://github.com/thirdweb-dev/js/commit/3eba69afd78b7aa472a9c00c9d4220bb69ad1d3e), [`4af785c`](https://github.com/thirdweb-dev/js/commit/4af785c37c831ecd7087c1c9eb9095922cac0855), [`6f818f3`](https://github.com/thirdweb-dev/js/commit/6f818f393eb9023b53a3358d62d60ec23a9246bd), [`b79dc18`](https://github.com/thirdweb-dev/js/commit/b79dc18f7ab5155bbf4af02dc1a953546160bad0)]:
|
||||
- @thirdweb-dev/sdk@3.5.2
|
||||
|
||||
## 0.8.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4e7b293`](https://github.com/thirdweb-dev/js/commit/4e7b293c543244de79c150ca8c37d941221fc9a3), [`c9c0e4f`](https://github.com/thirdweb-dev/js/commit/c9c0e4ff39b054af746f3aec75fcbd0a3ef7239d)]:
|
||||
- @thirdweb-dev/sdk@3.5.1
|
||||
|
||||
## 0.8.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`5200d5d`](https://github.com/thirdweb-dev/js/commit/5200d5d7e730da58bd4d3e7c222e3c661265f913), [`42a6065`](https://github.com/thirdweb-dev/js/commit/42a606588dfed03c67c57357dd0ee1ad19fad6ea)]:
|
||||
- @thirdweb-dev/sdk@3.5.0
|
||||
|
||||
## 0.8.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e731c1a`](https://github.com/thirdweb-dev/js/commit/e731c1a7c341e6f2b9cc42c88d5c1b036dcf4a7c)]:
|
||||
- @thirdweb-dev/sdk@3.4.5
|
||||
|
||||
## 0.8.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@3.4.4
|
||||
|
||||
## 0.8.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`1c24c3c`](https://github.com/thirdweb-dev/js/commit/1c24c3c3c48476a824f817e09e7bf0fbe67c1db5), [`65fc7ba`](https://github.com/thirdweb-dev/js/commit/65fc7ba6fff093a971b53cb0979c39768e090ba1)]:
|
||||
- @thirdweb-dev/sdk@3.4.3
|
||||
|
||||
## 0.8.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`21c21c1`](https://github.com/thirdweb-dev/js/commit/21c21c1adfd09b60ad563bfd4c308597140de33c), [`3522917`](https://github.com/thirdweb-dev/js/commit/352291791ee900e1500f84f095290497934d2f60)]:
|
||||
- @thirdweb-dev/sdk@3.4.2
|
||||
|
||||
## 0.8.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`72227b2`](https://github.com/thirdweb-dev/js/commit/72227b2e166a3a68bbb41cf2b389322f5b7547a2)]:
|
||||
- @thirdweb-dev/sdk@3.4.1
|
||||
|
||||
## 0.8.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#327](https://github.com/thirdweb-dev/js/pull/327) [`ef27aad`](https://github.com/thirdweb-dev/js/commit/ef27aad0aafc4577e85f44dc77dfbe880bd239b5) Thanks [@jnsdls](https://github.com/jnsdls)! - enable e2e testing
|
||||
|
||||
- Updated dependencies [[`340605b`](https://github.com/thirdweb-dev/js/commit/340605b507f384fbd2999b9c16542af3c53e84a9), [`26116a6`](https://github.com/thirdweb-dev/js/commit/26116a6f637ee845f7bd17f20ffe17caf184658e), [`15f8006`](https://github.com/thirdweb-dev/js/commit/15f8006e1fb22333b7ee239b45e7b1b12d6dccc8), [`ef27aad`](https://github.com/thirdweb-dev/js/commit/ef27aad0aafc4577e85f44dc77dfbe880bd239b5)]:
|
||||
- @thirdweb-dev/sdk@3.4.0
|
||||
|
||||
## 0.8.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ad851f7`](https://github.com/thirdweb-dev/js/commit/ad851f7b4d6abb5ab9364ef3dec229f913e18ce5), [`6218c88`](https://github.com/thirdweb-dev/js/commit/6218c885842e6b4e44ec38ec92a9211e56c49bf4)]:
|
||||
- @thirdweb-dev/sdk@3.3.1
|
||||
|
||||
## 0.8.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#326](https://github.com/thirdweb-dev/js/pull/326) [`126752d`](https://github.com/thirdweb-dev/js/commit/126752d7d02c9a808a63fb9e67a9df6658b5682b) Thanks [@jnsdls](https://github.com/jnsdls)! - remove tons of unused dependencies, use SDK function for extracting ipfs has from bytecode
|
||||
|
||||
- Updated dependencies [[`126752d`](https://github.com/thirdweb-dev/js/commit/126752d7d02c9a808a63fb9e67a9df6658b5682b), [`df74340`](https://github.com/thirdweb-dev/js/commit/df74340fa51323304c32419761d6f18628b060fa), [`89ff921`](https://github.com/thirdweb-dev/js/commit/89ff921a7d9f0e42b4e6707c8b56d0aab95c1aa0), [`a57b4f0`](https://github.com/thirdweb-dev/js/commit/a57b4f01f3a49590b897101f2730582e4124a554)]:
|
||||
- @thirdweb-dev/sdk@3.3.0
|
||||
|
||||
## 0.8.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4f867e1`](https://github.com/thirdweb-dev/js/commit/4f867e15edbe5757afbf54cab207458711a8986c), [`b6eec61`](https://github.com/thirdweb-dev/js/commit/b6eec6146c23b66a3681bbb48b173b9019254214), [`9727502`](https://github.com/thirdweb-dev/js/commit/9727502eb30ac139382b7c7c8e8fc0967cbfbcf1), [`782b58d`](https://github.com/thirdweb-dev/js/commit/782b58d3f6a3b49ff9b3d837551db5161f96171a), [`f41a443`](https://github.com/thirdweb-dev/js/commit/f41a443c5785240197607b5bf93ff18a1c8e8307), [`f45ef62`](https://github.com/thirdweb-dev/js/commit/f45ef62128cfefe06275a38440c90d45e9f3cef7), [`2f8ec89`](https://github.com/thirdweb-dev/js/commit/2f8ec89c4c83ae577092c840c198e6fcfb114e69), [`b7cd744`](https://github.com/thirdweb-dev/js/commit/b7cd744cd0482a6c3fe5ae0c858f20cd380d4114), [`53c6507`](https://github.com/thirdweb-dev/js/commit/53c65076696a43bd9a7e58a7493d4e65c172900f)]:
|
||||
- @thirdweb-dev/sdk@3.2.6
|
||||
|
||||
## 0.8.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`712b11a`](https://github.com/thirdweb-dev/js/commit/712b11a43e389ae10b7f09648779a69c05b2c913)]:
|
||||
- @thirdweb-dev/sdk@3.2.5
|
||||
|
||||
## 0.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@3.2.4
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@3.2.3
|
||||
|
||||
## 0.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#276](https://github.com/thirdweb-dev/js/pull/276) [`24514a6`](https://github.com/thirdweb-dev/js/commit/24514a6e9fc1316622c1494e5be225e53f88d26e) Thanks [@furqanrydhan](https://github.com/furqanrydhan)! - build is now default compile command, reorganized the help list
|
||||
|
||||
- [#279](https://github.com/thirdweb-dev/js/pull/279) [`1918495`](https://github.com/thirdweb-dev/js/commit/1918495f2ee7f50d94083d9577dab0bf1c828c94) Thanks [@furqanrydhan](https://github.com/furqanrydhan)! - Updating command list order, helper text and changed detect -> build
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#277](https://github.com/thirdweb-dev/js/pull/277) [`9de3f8c`](https://github.com/thirdweb-dev/js/commit/9de3f8c8e954733bfc3eb6e8e0e786ff5b296f0c) Thanks [@adam-maj](https://github.com/adam-maj)! - Add recursive folder upload support
|
||||
|
||||
- [#268](https://github.com/thirdweb-dev/js/pull/268) [`57b9c67`](https://github.com/thirdweb-dev/js/commit/57b9c671aaf7fce0076dd332c171ba6944752e7a) Thanks [@adam-maj](https://github.com/adam-maj)! - Update storage upload cLI
|
||||
|
||||
- Updated dependencies [[`521a49c`](https://github.com/thirdweb-dev/js/commit/521a49c6ec6a73068adcfbc1d94d2f3f17afae86), [`8b3b97c`](https://github.com/thirdweb-dev/js/commit/8b3b97c8f7b3710ce62ed86cfeb4f6de3c23d559), [`25a4773`](https://github.com/thirdweb-dev/js/commit/25a4773bba406f4e270ac86e68c5266d24c08330), [`b2cadf1`](https://github.com/thirdweb-dev/js/commit/b2cadf164cfe9fb27081df7530356baf70ec2b3a), [`d6bb61b`](https://github.com/thirdweb-dev/js/commit/d6bb61b7fac759fff7d6293edd46f693f5a7889c), [`78d6d3c`](https://github.com/thirdweb-dev/js/commit/78d6d3c15968a449924b3f24354e151a90e4b20d), [`89d87a5`](https://github.com/thirdweb-dev/js/commit/89d87a5eb31e78e68e097718e9ae6e4c4d101868), [`41507d3`](https://github.com/thirdweb-dev/js/commit/41507d32a78c556f50c88de81c4d6109b1783c78), [`8096c78`](https://github.com/thirdweb-dev/js/commit/8096c78b466a7f5f9c2aac6e2c56a06f9d3a25d6), [`b1fa171`](https://github.com/thirdweb-dev/js/commit/b1fa17158dcac00265bc8f4d64f1f9708482cdb5), [`18381fa`](https://github.com/thirdweb-dev/js/commit/18381fac73741826934212ac6441842ed42a64a0), [`df048af`](https://github.com/thirdweb-dev/js/commit/df048af605f449279b01d7352b604e7856ac223c), [`d6bb61b`](https://github.com/thirdweb-dev/js/commit/d6bb61b7fac759fff7d6293edd46f693f5a7889c), [`cf88795`](https://github.com/thirdweb-dev/js/commit/cf88795376d3110f9d3aa839928d22276904b15a), [`89b5c6f`](https://github.com/thirdweb-dev/js/commit/89b5c6fe079e4df2cf12cb557aa9784f88c5d159)]:
|
||||
- @thirdweb-dev/sdk@3.2.2
|
||||
|
||||
## 0.7.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#259](https://github.com/thirdweb-dev/js/pull/259) [`6f95a52`](https://github.com/thirdweb-dev/js/commit/6f95a520e6cc69e08f734705e8adc71431b091cc) Thanks [@adam-maj](https://github.com/adam-maj)! - Add npx thirdweb upload command
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@3.2.1
|
||||
|
||||
## 0.7.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#246](https://github.com/thirdweb-dev/js/pull/246) [`ef0bac7`](https://github.com/thirdweb-dev/js/commit/ef0bac7f44b93939bfc9e2706b39edaa22992eec) Thanks [@simonlim94](https://github.com/simonlim94)! - Renamed initial commit message during thirdweb cli createApp
|
||||
|
||||
- Updated dependencies [[`ba6f450`](https://github.com/thirdweb-dev/js/commit/ba6f45027a6ec11aebb4a1918ab2ba4f88894ede), [`c8261b7`](https://github.com/thirdweb-dev/js/commit/c8261b74b5828ac66ea3a6d7636aa57e40ea1a14), [`20745f8`](https://github.com/thirdweb-dev/js/commit/20745f8cd603235eb5039e61a39c0959c5860bca), [`2ea3f00`](https://github.com/thirdweb-dev/js/commit/2ea3f00c41b5e70d9bb634cd86790890520fc4d6), [`9412d41`](https://github.com/thirdweb-dev/js/commit/9412d416832c87ab2c858cb120c6c5de03459c50), [`d677e9d`](https://github.com/thirdweb-dev/js/commit/d677e9d6341252013762882d25408ea8f93791e5), [`e662408`](https://github.com/thirdweb-dev/js/commit/e662408e1936983485936609552f80a56cbed082), [`e4b10bc`](https://github.com/thirdweb-dev/js/commit/e4b10bc5bd12178cba8856b60255732123cb7ae5), [`caf9795`](https://github.com/thirdweb-dev/js/commit/caf979537b5acb1610f182c94afa5d95c208ec3f), [`028cb78`](https://github.com/thirdweb-dev/js/commit/028cb781252b0050943b14cfdf38132c18753a4b), [`1972f3e`](https://github.com/thirdweb-dev/js/commit/1972f3ec0d511fbc17642b2a30852a177092a09e), [`6647f70`](https://github.com/thirdweb-dev/js/commit/6647f707bdd050725264a8a74220c11912f68e57), [`cac373b`](https://github.com/thirdweb-dev/js/commit/cac373b010ce3be3615a36671b66815a27785061)]:
|
||||
- @thirdweb-dev/sdk@3.2.0
|
||||
|
||||
## 0.7.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#103](https://github.com/thirdweb-dev/js/pull/103) [`e1bcc67`](https://github.com/thirdweb-dev/js/commit/e1bcc67cebfc95fce615439ff03871b4fef0eed9) Thanks [@b4s36t4](https://github.com/b4s36t4)! - automatically re-name the created example repo to the user provided app name
|
||||
|
||||
- Updated dependencies [[`8239763`](https://github.com/thirdweb-dev/js/commit/82397636f41faa2b44cfe70e75212f0f42291092), [`27a30e3`](https://github.com/thirdweb-dev/js/commit/27a30e3ffb56dd7fa8412a066bc3ac0977aca8e2), [`f2a6211`](https://github.com/thirdweb-dev/js/commit/f2a62110c43e7b8f35c86a197730e732f8fcc786), [`d26b768`](https://github.com/thirdweb-dev/js/commit/d26b76872a6c85651ee06aa0732ee2967f70db27), [`ba7dcbb`](https://github.com/thirdweb-dev/js/commit/ba7dcbbad8db2b73baf1435f4175d19933cb7d4f), [`9c8a3fb`](https://github.com/thirdweb-dev/js/commit/9c8a3fb6d4520dd6cdf2d1c17f33b764e871599e)]:
|
||||
- @thirdweb-dev/sdk@3.1.2
|
||||
|
||||
## 0.7.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`666ff5f`](https://github.com/thirdweb-dev/js/commit/666ff5f694f1e146150ddb9fa3233f00533b6d60), [`0dade96`](https://github.com/thirdweb-dev/js/commit/0dade96d0d2553d25b386d822e534eb7ed0aefa9), [`c295a47`](https://github.com/thirdweb-dev/js/commit/c295a47144cd722c6f8861c1ec567b53a05ea0bf)]:
|
||||
- @thirdweb-dev/sdk@3.1.1
|
||||
|
||||
## 0.7.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3c8f620`](https://github.com/thirdweb-dev/js/commit/3c8f6205e9b49ae9fea1f9629678210c6c9c36e6), [`e187d21`](https://github.com/thirdweb-dev/js/commit/e187d21e123a506fac0459da18f2d4fc94abae29), [`acbabc8`](https://github.com/thirdweb-dev/js/commit/acbabc8999b2b7b6e0eb89cae97e2f1ef7143501), [`b030a86`](https://github.com/thirdweb-dev/js/commit/b030a866080953bbf4e504ddc41d7050b250d2d7)]:
|
||||
- @thirdweb-dev/sdk@3.1.0
|
||||
|
||||
## 0.7.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ce05bfd`](https://github.com/thirdweb-dev/js/commit/ce05bfd8615a9c79664856bce53de8b43bed5c87)]:
|
||||
- @thirdweb-dev/sdk@3.0.8
|
||||
|
||||
## 0.7.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`5f5ab01`](https://github.com/thirdweb-dev/js/commit/5f5ab015e1dd3c471d6affe995ef36ec88932b3c)]:
|
||||
- @thirdweb-dev/sdk@3.0.7
|
||||
|
||||
## 0.7.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`a80fc97`](https://github.com/thirdweb-dev/js/commit/a80fc97b6a1e72ed46a400b4b602e180947fb870)]:
|
||||
- @thirdweb-dev/sdk@3.0.6
|
||||
|
||||
## 0.7.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#172](https://github.com/thirdweb-dev/js/pull/172) [`89db3db`](https://github.com/thirdweb-dev/js/commit/89db3db7ff3d5af7e7a7b5f25e299b9faad44eb0) Thanks [@adam-maj](https://github.com/adam-maj)! - Add thirdweb deploy to end of thirdweb detect and yarn build to thirdweb create
|
||||
|
||||
- [#175](https://github.com/thirdweb-dev/js/pull/175) [`3332fa5`](https://github.com/thirdweb-dev/js/commit/3332fa5697b9ff2a4ded1f2c508304f59f434be2) Thanks [@adam-maj](https://github.com/adam-maj)! - Split create contract flow into two steps
|
||||
|
||||
- [#177](https://github.com/thirdweb-dev/js/pull/177) [`3edec39`](https://github.com/thirdweb-dev/js/commit/3edec391be169453aaee412846005a820939fab0) Thanks [@adam-maj](https://github.com/adam-maj)! - thirdweb deploy redirects to contract deploy page if no contracts detected
|
||||
|
||||
- [#184](https://github.com/thirdweb-dev/js/pull/184) [`bdec5a5`](https://github.com/thirdweb-dev/js/commit/bdec5a59af680802e206e3025b2c463fc7dcb740) Thanks [@aeither](https://github.com/aeither)! - Fix "Invalid Project name" when a project with a capital letter is entered
|
||||
|
||||
- Updated dependencies [[`4169b94`](https://github.com/thirdweb-dev/js/commit/4169b9428f9001b7cad259a4e56fe610316cd191), [`3b877ba`](https://github.com/thirdweb-dev/js/commit/3b877ba221acfd85f80b99e1bc382055217f0a39), [`b54f95d`](https://github.com/thirdweb-dev/js/commit/b54f95dc906928ff2f9251748f254a16fe1f2cee), [`f8ab477`](https://github.com/thirdweb-dev/js/commit/f8ab4779bb2d6d66200e1e8fd558e0ac069a2f54), [`772f843`](https://github.com/thirdweb-dev/js/commit/772f8431e3a62d0ded62dae90a43e9a7edd5b1a2), [`a9ec190`](https://github.com/thirdweb-dev/js/commit/a9ec190ff99d2714cef2500d20ea0f3f73f07be3), [`208b038`](https://github.com/thirdweb-dev/js/commit/208b0389a50ea48bbb9600fec60fec2f1671d4b9), [`5345479`](https://github.com/thirdweb-dev/js/commit/534547992243bdd3a77e34ec2b2487b5adab366a)]:
|
||||
- @thirdweb-dev/sdk@3.0.5
|
||||
|
||||
## 0.7.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#148](https://github.com/thirdweb-dev/js/pull/148) [`964add6`](https://github.com/thirdweb-dev/js/commit/964add6f205577298b8f4b9ce7298e5bf09e88e7) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix deploy/release CLI uploads
|
||||
|
||||
- Updated dependencies []:
|
||||
- @thirdweb-dev/sdk@3.0.4
|
||||
|
||||
## 0.7.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#134](https://github.com/thirdweb-dev/js/pull/134) [`b1ae844`](https://github.com/thirdweb-dev/js/commit/b1ae844e8bbed59c0225b32f4115ae384fa23ef5) Thanks [@adam-maj](https://github.com/adam-maj)! - Update ordering of commands in npx thirdweb
|
||||
|
||||
- Updated dependencies [[`51dde28`](https://github.com/thirdweb-dev/js/commit/51dde28224209f1b8b26f436c204a5e702281564), [`9d74a43`](https://github.com/thirdweb-dev/js/commit/9d74a43aac21448beba63ba4e2637945965a3634), [`b234c58`](https://github.com/thirdweb-dev/js/commit/b234c58d44d8322e44b2d2ba87ad4ec7d699e961)]:
|
||||
- @thirdweb-dev/sdk@3.0.3
|
||||
|
||||
## 0.7.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#128](https://github.com/thirdweb-dev/js/pull/128) [`4dca5b2`](https://github.com/thirdweb-dev/js/commit/4dca5b21a66fe8ff4c4b24663e1b9ad6c446b7e2) Thanks [@jarrodwatts](https://github.com/jarrodwatts)! - Add new documentation for detectable features
|
||||
|
||||
- Updated dependencies [[`42c79e9`](https://github.com/thirdweb-dev/js/commit/42c79e93dc958ca46a55d705aeea44ffdbbcc5f6), [`fe8751e`](https://github.com/thirdweb-dev/js/commit/fe8751eeae7ad013b890a8092ddbd091ecbd6708)]:
|
||||
- @thirdweb-dev/sdk@3.0.2
|
||||
|
||||
## 0.7.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+12
-3
@@ -62,7 +62,7 @@ Deploying released contracts give deployers access to automatic SDKs to integrat
|
||||
npx thirdweb@latest detect
|
||||
```
|
||||
|
||||
As you're developing your contracts, you may want to implement [Extensions](https://portal.thirdweb.com/extensions) to unlock functionality on the SDKs (ie. nft minting with automatic upload to IPFS) and the dashboard (ie. generated UI to manage permissions). This command will show what extensions were detected on your contract, unlocking the corresponding functionality on the SDKs and dashboard.
|
||||
As you're developing your contracts, you may want to implement [Extensions](https://portal.thirdweb.com/contractkit) to unlock functionality on the SDKs (ie. nft minting with automatic upload to IPFS) and the dashboard (ie. generated UI to manage permissions). This command will show what extensions were detected on your contract, unlocking the corresponding functionality on the SDKs and dashboard.
|
||||
|
||||
---
|
||||
|
||||
@@ -101,8 +101,17 @@ $ npx thirdweb@latest release
|
||||
|
||||
The simplest way to work on the CLI locally is to:
|
||||
|
||||
1. Install the package locally
|
||||
2. Run the `build:watch` command to compile any changes in realtime
|
||||
1. Install the package locally by running
|
||||
|
||||
```bash
|
||||
git clone https://github.com/thirdweb-dev/js.git
|
||||
```
|
||||
2. From the project root run
|
||||
|
||||
```bash
|
||||
yarn && yarn build
|
||||
```
|
||||
3. From inside `js/packages/cli` Run the `build:watch` command to compile any changes in real time
|
||||
|
||||
```bash
|
||||
$ npm install -g ./
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
export const CREATE_MESSAGES = {
|
||||
typeOfProject: "What type of project do you want to create?",
|
||||
projectName: "What is your project named?",
|
||||
contractName: "What will be the name of your new smart contract?",
|
||||
chain: "Which blockchain do you want to use?",
|
||||
framework: "What framework do you want to use?",
|
||||
language: "What language do you want to use?",
|
||||
contract: "What contract do you want to start from?",
|
||||
contract: "What type of contract do you want to start from?",
|
||||
extensions: "What extensions do you want to add to your contract?",
|
||||
} as const;
|
||||
|
||||
export const ERROR_MESSAGES = {
|
||||
noConfiguration:
|
||||
"Failed to find a supported project configuration file in current directory",
|
||||
"No contract project configuration file found in current directory",
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CREATE_MESSAGES } from "../constants/constants";
|
||||
import { prepareEnvironment } from "@gmrchk/cli-testing-library";
|
||||
|
||||
// this creates an app, can take some time that's fine
|
||||
jest.setTimeout(120_000);
|
||||
jest.setTimeout(150_000);
|
||||
|
||||
describe("npx thirdweb create", () => {
|
||||
it("should create app (CRA) successfully", async () => {
|
||||
@@ -16,6 +16,8 @@ describe("npx thirdweb create", () => {
|
||||
await pressKey("enter");
|
||||
await waitForText(CREATE_MESSAGES.projectName);
|
||||
await pressKey("enter");
|
||||
await waitForText(CREATE_MESSAGES.chain);
|
||||
await pressKey("enter");
|
||||
await waitForText(CREATE_MESSAGES.framework);
|
||||
// select CRA
|
||||
await pressKey("arrowDown");
|
||||
|
||||
@@ -15,8 +15,8 @@ describe("npx thirdweb deploy", () => {
|
||||
`${path}/BasicContract.sol`,
|
||||
);
|
||||
|
||||
const { waitForText, waitForFinish, getExitCode, writeText, getStdout } =
|
||||
await spawn("node", "./dist/cli/index.js deploy");
|
||||
const { waitForText, waitForFinish, getExitCode, writeText, getStderr } =
|
||||
await spawn("node", "./dist/cli/index.js deploy --contract");
|
||||
|
||||
expect(await exists("BasicContract.sol")).toEqual(true);
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("npx thirdweb deploy", () => {
|
||||
// wait for program to finish
|
||||
await waitForFinish();
|
||||
|
||||
expect(getStdout().at(-1)).toContain(
|
||||
expect(getStderr().at(-1)).toContain(
|
||||
"https://thirdweb.com/contracts/deploy/",
|
||||
);
|
||||
|
||||
|
||||
@@ -19,10 +19,17 @@ describe("npx thirdweb detect", () => {
|
||||
await create.waitForText(CREATE_MESSAGES.framework);
|
||||
// select hardhat
|
||||
await create.pressKey("enter");
|
||||
await create.waitForText(CREATE_MESSAGES.contractName);
|
||||
// set contract name
|
||||
await create.writeText("Contract");
|
||||
await create.pressKey("enter");
|
||||
await create.waitForText(CREATE_MESSAGES.contract);
|
||||
// select ERC721Base
|
||||
// select ERC721
|
||||
await create.pressKey("arrowDown");
|
||||
await create.pressKey("enter");
|
||||
await create.waitForText(CREATE_MESSAGES.extensions);
|
||||
// select no extra extensions
|
||||
await create.pressKey("enter");
|
||||
|
||||
// wait for program to finish
|
||||
await create.waitForFinish();
|
||||
@@ -54,6 +61,9 @@ describe("npx thirdweb detect", () => {
|
||||
|
||||
const lines = detect.getStdout();
|
||||
|
||||
// check that detect excited successfully
|
||||
expect(detect.getExitCode()).toEqual(0);
|
||||
|
||||
// Detected extensions
|
||||
expect(lines.findIndex((line) => line.includes("ERC721"))).toBeGreaterThan(
|
||||
-1,
|
||||
|
||||
@@ -15,7 +15,7 @@ describe("npx thirdweb release", () => {
|
||||
`${path}/BasicContract.sol`,
|
||||
);
|
||||
|
||||
const { waitForText, waitForFinish, getExitCode, writeText, getStdout } =
|
||||
const { waitForText, waitForFinish, getExitCode, writeText, getStderr } =
|
||||
await spawn("node", "./dist/cli/index.js release");
|
||||
|
||||
expect(await exists("BasicContract.sol")).toEqual(true);
|
||||
@@ -26,7 +26,7 @@ describe("npx thirdweb release", () => {
|
||||
// wait for program to finish
|
||||
await waitForFinish();
|
||||
|
||||
expect(getStdout().at(-1)).toContain(
|
||||
expect(getStderr().at(-1)).toContain(
|
||||
"https://thirdweb.com/contracts/release/",
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"main": "dist/cli/index.js",
|
||||
"version": "0.7.12",
|
||||
"version": "0.9.19",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gmrchk/cli-testing-library": "^0.1.2",
|
||||
"@types/async-retry": "1.3.0",
|
||||
"@types/async-retry": "^1.3.0",
|
||||
"@types/cross-spawn": "^6.0.2",
|
||||
"@types/inquirer": "^8.2.1",
|
||||
"@types/jest": "^29.0.0",
|
||||
@@ -18,38 +18,32 @@
|
||||
"@types/prompts": "^2.0.14",
|
||||
"@types/rimraf": "^3.0.0",
|
||||
"@types/tar": "^6.1.2",
|
||||
"@types/update-notifier": "^5.1.0",
|
||||
"@types/update-notifier": "^6.0.1",
|
||||
"@types/validate-npm-package-name": "^4.0.0",
|
||||
"hardhat": "^2.9.3",
|
||||
"jest": "^28",
|
||||
"jest": "^29",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-jest": "^28.0.8",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-node": "^10.7.0",
|
||||
"tsc": "^2.0.4",
|
||||
"tsup": "^6.2.3",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@thirdweb-dev/sdk": "*",
|
||||
"async-retry": "^1.3.3",
|
||||
"cbor": "^8.1.0",
|
||||
"commander": "^9.1.0",
|
||||
"cpy": "^9.0.1",
|
||||
"detect-package-manager": "^2.0.1",
|
||||
"enquirer": "^2.3.6",
|
||||
"ethers": "^5.7.0",
|
||||
"form-data": "^4.0.0",
|
||||
"ethers": "^5.7.2",
|
||||
"got": "11.8.5",
|
||||
"inquirer": "^8.2.3",
|
||||
"ipfs-unixfs": "^6.0.7",
|
||||
"ipld-dag-pb": "^0.22.3",
|
||||
"multihashes": "^4.0.3",
|
||||
"open": "^8.4.0",
|
||||
"prompts": "^2.4.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"solc": "0.8.13",
|
||||
"solc": "0.8.17",
|
||||
"sync-disk-cache": "^2.1.0",
|
||||
"tar": "^6.1.11",
|
||||
"tslog": "^3.3.3",
|
||||
"update-check": "^1.5.4",
|
||||
"update-notifier": "^6.0.2",
|
||||
"validate-npm-package-name": "^4.0.0",
|
||||
"yaml": "^2.1.1"
|
||||
|
||||
+312
-97
@@ -1,48 +1,251 @@
|
||||
#!/usr/bin/env node
|
||||
import { installGithubAction } from "../common/ci-installer";
|
||||
import { detectExtensions } from "../common/feature-detector";
|
||||
import { processProject } from "../common/processor";
|
||||
import { cliVersion, pkg } from "../constants/urls";
|
||||
import { info, logger } from "../core/helpers/logger";
|
||||
import { info, logger, spinner } from "../core/helpers/logger";
|
||||
import { CacheEntry } from "../core/types/cache";
|
||||
import { twCreate } from "../create/command";
|
||||
import generateDashboardUrl from "../helpers/generate-dashboard-url";
|
||||
import { deploy } from "../deploy";
|
||||
import { findPackageInstallation } from "../helpers/detect-local-packages";
|
||||
import { upload } from "../storage/command";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import chalk from "chalk";
|
||||
import { exec, spawn } from "child_process";
|
||||
import { Command } from "commander";
|
||||
import open from "open";
|
||||
import prompts from "prompts";
|
||||
import Cache from "sync-disk-cache";
|
||||
|
||||
const main = async () => {
|
||||
const program = new Command();
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
const skipIntro = process.env.THIRDWEB_CLI_SKIP_INTRO === "true";
|
||||
|
||||
//yes this has to look like this, eliminates whitespace
|
||||
console.info(`
|
||||
$$\\ $$\\ $$\\ $$\\ $$\\
|
||||
$$ | $$ | \\__| $$ | $$ |
|
||||
$$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$$$$\\
|
||||
\\_$$ _| $$ __$$\\ $$ |$$ __$$\\ $$ __$$ |$$ | $$ | $$ |$$ __$$\\ $$ __$$\\
|
||||
$$ | $$ | $$ |$$ |$$ | \\__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
|
||||
$$ |$$\\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
|
||||
\\$$$$ |$$ | $$ |$$ |$$ | \\$$$$$$$ |\\$$$$$\\$$$$ |\\$$$$$$$\\ $$$$$$$ |
|
||||
\\____/ \\__| \\__|\\__|\\__| \\_______| \\_____\\____/ \\_______|\\_______/ `);
|
||||
console.info(`\n 💎 thirdweb-cli v${cliVersion} 💎\n`);
|
||||
import("update-notifier").then(({ default: updateNotifier }) => {
|
||||
updateNotifier({
|
||||
pkg,
|
||||
shouldNotifyInNpmScript: true,
|
||||
// check every time while we're still building the CLI
|
||||
updateCheckInterval: 0,
|
||||
}).notify();
|
||||
});
|
||||
const program = new Command();
|
||||
const cache = new Cache("thirdweb:cli");
|
||||
|
||||
// yes this has to look like this, eliminates whitespace
|
||||
if (!skipIntro) {
|
||||
console.info(`
|
||||
$$\\ $$\\ $$\\ $$\\ $$\\
|
||||
$$ | $$ | \\__| $$ | $$ |
|
||||
$$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$$$$\\
|
||||
\\_$$ _| $$ __$$\\ $$ |$$ __$$\\ $$ __$$ |$$ | $$ | $$ |$$ __$$\\ $$ __$$\\
|
||||
$$ | $$ | $$ |$$ |$$ | \\__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
|
||||
$$ |$$\\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
|
||||
\\$$$$ |$$ | $$ |$$ |$$ | \\$$$$$$$ |\\$$$$$\\$$$$ |\\$$$$$$$\\ $$$$$$$ |
|
||||
\\____/ \\__| \\__|\\__|\\__| \\_______| \\_____\\____/ \\_______|\\_______/ `);
|
||||
console.info(`\n 💎 thirdweb-cli v${cliVersion} 💎\n`);
|
||||
}
|
||||
|
||||
program
|
||||
.name("thirdweb-cli")
|
||||
.description("Official thirdweb command line interface")
|
||||
.version(cliVersion, "-v, --version");
|
||||
.version(cliVersion, "-v, --version")
|
||||
.option("--skip-update-check", "Skip check for auto updates")
|
||||
.hook("preAction", async () => {
|
||||
if (skipIntro || program.opts().skipUpdateCheck) {
|
||||
return;
|
||||
}
|
||||
|
||||
let shouldCheckVersion = true;
|
||||
try {
|
||||
const lastCheckCache: CacheEntry = cache.get("last-version-check");
|
||||
|
||||
if (lastCheckCache.isCached) {
|
||||
const lastVersionCheck = new Date(lastCheckCache.value);
|
||||
// Don't check for updates if already checked within past 24 hours
|
||||
if (
|
||||
new Date().getTime() - lastVersionCheck.getTime() <
|
||||
1000 * 60 * 60 * 24
|
||||
) {
|
||||
shouldCheckVersion = false;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
||||
if (!shouldCheckVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
const versionSpinner = spinner("Checking for updates...");
|
||||
await import("update-notifier").then(
|
||||
async ({ default: updateNotifier }) => {
|
||||
const notifier = updateNotifier({
|
||||
pkg,
|
||||
shouldNotifyInNpmScript: true,
|
||||
// check every time while we're still building the CLI
|
||||
updateCheckInterval: 0,
|
||||
});
|
||||
|
||||
const versionInfo = await notifier.fetchInfo();
|
||||
versionSpinner.stop();
|
||||
|
||||
try {
|
||||
// Set cache to prevent checking for updates again for 24 hours
|
||||
cache.set("last-version-check", new Date().toISOString());
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
||||
if (versionInfo.type !== "latest") {
|
||||
const res = await prompts({
|
||||
type: "toggle",
|
||||
name: "upgrade",
|
||||
message: `A new version of the CLI is available. Would you like to upgrade?`,
|
||||
initial: true,
|
||||
active: "yes",
|
||||
inactive: "no",
|
||||
});
|
||||
|
||||
if (res.upgrade) {
|
||||
const updateSpinner = spinner(
|
||||
`Upgrading CLI to version ${versionInfo.latest}...`,
|
||||
);
|
||||
|
||||
const clonedEnvironment = { ...process.env };
|
||||
clonedEnvironment.THIRDWEB_CLI_SKIP_INTRO = "true";
|
||||
|
||||
const installation = await findPackageInstallation();
|
||||
|
||||
// If the package isn't installed anywhere, just defer to npx thirdweb@latest
|
||||
if (!installation) {
|
||||
updateSpinner.succeed(
|
||||
`Now using CLI version ${versionInfo.latest}. Continuing execution...`,
|
||||
);
|
||||
|
||||
await new Promise((done, failed) => {
|
||||
const shell = spawn(
|
||||
`npx --yes thirdweb@latest ${process.argv
|
||||
.slice(2)
|
||||
.join(" ")}`,
|
||||
[],
|
||||
{ stdio: "inherit", shell: true, env: clonedEnvironment },
|
||||
);
|
||||
shell.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
done("");
|
||||
} else {
|
||||
failed();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return process.exit(0);
|
||||
}
|
||||
|
||||
// Otherwise, get the correct command based on package manager and local vs. global
|
||||
let command = "";
|
||||
switch (installation.packageManager) {
|
||||
case "npm":
|
||||
command = installation.isGlobal
|
||||
? `npm install -g thirdweb`
|
||||
: `npm install thirdweb`;
|
||||
break;
|
||||
case "yarn":
|
||||
command = installation.isGlobal
|
||||
? `yarn global add thirdweb`
|
||||
: `yarn add thirdweb`;
|
||||
break;
|
||||
case "pnpm":
|
||||
command = installation.isGlobal
|
||||
? `pnpm add -g thirdweb@latest`
|
||||
: `pnpm add thirdweb@latest`;
|
||||
break;
|
||||
default:
|
||||
console.error(
|
||||
`Could not detect package manager in use, aborting automatic upgrade.\nIf you want to upgrade the CLI, please do it manually with your package manager.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await new Promise((done, failed) => {
|
||||
exec(command, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
failed(err);
|
||||
return;
|
||||
}
|
||||
|
||||
done({ stdout, stderr });
|
||||
});
|
||||
});
|
||||
|
||||
updateSpinner.succeed(
|
||||
`Successfully upgraded CLI to version ${versionInfo.latest}. Continuing execution...`,
|
||||
);
|
||||
|
||||
// If the package is installed globally with yarn or pnpm, then npx won't recognize it
|
||||
// So we need to make sure to run the command directly
|
||||
const executionCommand =
|
||||
!installation.isGlobal || installation.packageManager === "npm"
|
||||
? `npx thirdweb`
|
||||
: `thirdweb`;
|
||||
await new Promise((done, failed) => {
|
||||
const shell = spawn(
|
||||
`${executionCommand} ${process.argv.slice(2).join(" ")}`,
|
||||
[],
|
||||
{ stdio: "inherit", shell: true, env: clonedEnvironment },
|
||||
);
|
||||
shell.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
done("");
|
||||
} else {
|
||||
failed();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
program
|
||||
.command("detect")
|
||||
.command("create [projectType] [projectPath]")
|
||||
.description(
|
||||
"Compile contracts and detect implemented thirdweb contract extensions",
|
||||
"Create a web3 app from any of our official templates: https://github.com/thirdweb-example/",
|
||||
)
|
||||
.option("--app", "Create a web3 app.")
|
||||
.option("--contract", "Create a web3 contract project")
|
||||
.option("--ts, --typescript", "Initialize as a TypeScript project.")
|
||||
.option("--js, --javascript", "Initialize as a JavaScript project.")
|
||||
.option("--forge", "Initialize as a Forge project.")
|
||||
.option("--hardhat", "Initialize as a Hardhat project.")
|
||||
.option("--cra", "Initialize as a Create React App project.")
|
||||
.option("--next", "Initialize as a Next.js project.")
|
||||
.option("--vite", "Initialize as a Vite project.")
|
||||
.option("--reactNative", "Initialize as a React Native project.")
|
||||
.option("--express", "Initialize as a Express project.")
|
||||
.option("--node", "Initialize as a Node project.")
|
||||
.option(
|
||||
"--use-npm",
|
||||
"Explicitly tell the CLI to bootstrap the app using npm",
|
||||
)
|
||||
.option(
|
||||
"--use-pnpm",
|
||||
"Explicitly tell the CLI to bootstrap the app using pnpm",
|
||||
)
|
||||
.option("--framework [name]", "The preferred framework.")
|
||||
.option("--solana", "Initialize as a Solana project.")
|
||||
.option("--evm", "Initialize as an Ethereum project.")
|
||||
.option(
|
||||
"-t, --template [name]",
|
||||
"A template to start your project from. You can use an template repository name from the official thirdweb-example org.",
|
||||
)
|
||||
.option(
|
||||
"-c, --contract-name [name]",
|
||||
"Name of the new smart contract to create",
|
||||
)
|
||||
.action(async (type, path, options) => {
|
||||
await twCreate(type, path, options);
|
||||
});
|
||||
|
||||
program
|
||||
.command("build")
|
||||
.description("Compile contract and detect thirdweb contract extensions")
|
||||
.option("-p, --path <project-path>", "path to project", ".")
|
||||
.option("-d, --debug", "show debug logs")
|
||||
.option("-a, --all", "run detection on all contracts")
|
||||
@@ -50,104 +253,116 @@ $$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$
|
||||
await detectExtensions(options);
|
||||
});
|
||||
|
||||
program
|
||||
.command("release")
|
||||
.description(
|
||||
"Release contracts, making them available for others to deploy and unlocking SDKs, Dashboards and Analytics.",
|
||||
)
|
||||
.option("-p, --path <project-path>", "path to project", ".")
|
||||
.option("--dry-run", "dry run (skip actually publishing)")
|
||||
.option("-d, --debug", "show debug logs")
|
||||
.option("--ci", "Continuous Integration mode")
|
||||
.action(async (options) => {
|
||||
const url = await processProject(options, "release");
|
||||
info(`Open this link to release your contracts:`);
|
||||
logger.info(chalk.blueBright(url.toString()));
|
||||
open(url.toString());
|
||||
});
|
||||
|
||||
program
|
||||
.command("deploy")
|
||||
.description(
|
||||
"Deploy contracts through your thirdweb dashboard, without dealing with private keys.",
|
||||
)
|
||||
.description("Deploy your (or team) contracts securely to blockchains")
|
||||
.option("-p, --path <project-path>", "path to project", ".")
|
||||
.option("--dry-run", "dry run (skip actually publishing)")
|
||||
.option("-d, --debug", "show debug logs")
|
||||
.option("--ci", "Continuous Integration mode")
|
||||
.option(
|
||||
"--dist-path [distPath]",
|
||||
"Path to the dist folder where the HTML based App is",
|
||||
)
|
||||
.option(
|
||||
"-n, --name [name]",
|
||||
"Name of the pre-built or released contract (such as nft-drop)",
|
||||
)
|
||||
.option(
|
||||
"-f, --file [name]",
|
||||
"Filter for contract files that contain this file name",
|
||||
)
|
||||
.option(
|
||||
"-cn, --contract-name [name]",
|
||||
"Filter for contracts that contain this contract name",
|
||||
)
|
||||
.option(
|
||||
"-cv, --contract-version [version]",
|
||||
"Version of the released contract",
|
||||
)
|
||||
.option("--app", "Deploy a web app to decentralized storage")
|
||||
.option("--contract", "Deploy a smart contract to blockchains")
|
||||
.action(async (options) => {
|
||||
if (options.name) {
|
||||
const url = generateDashboardUrl(options.name, options.contractVersion);
|
||||
|
||||
if (!url) {
|
||||
logger.error(
|
||||
chalk.red(
|
||||
`Could not find a contract named ${options.name} ${
|
||||
options.contractVersion || ""
|
||||
}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
info(`Open this link to deploy your contract:`);
|
||||
logger.info(chalk.blueBright(url));
|
||||
open(url.toString());
|
||||
return;
|
||||
const url = await deploy(options);
|
||||
if (url) {
|
||||
open(url);
|
||||
}
|
||||
});
|
||||
|
||||
const url = await processProject(options, "deploy");
|
||||
info(`Open this link to deploy your contracts:`);
|
||||
logger.info(chalk.blueBright(url.toString()));
|
||||
program
|
||||
.command("release")
|
||||
.description(
|
||||
"Release your protocol so other devs can deploy them and unlock SDKs, Dashboards and Analytics",
|
||||
)
|
||||
.option("-p, --path <project-path>", "path to project", ".")
|
||||
.option(
|
||||
"-f, --file [name]",
|
||||
"Filter for contract files that contain the file name",
|
||||
)
|
||||
.option(
|
||||
"-cn, --contract-name [name]",
|
||||
"Filter for contracts that contain this contract name",
|
||||
)
|
||||
.option("--dry-run", "dry run (skip actually publishing)")
|
||||
.option("-d, --debug", "show debug logs")
|
||||
.option("--ci", "Continuous Integration mode")
|
||||
.action(async (options) => {
|
||||
const url = await processProject(options, "release");
|
||||
info(
|
||||
`Open this link to release your contracts: ${chalk.blueBright(
|
||||
url.toString(),
|
||||
)}`,
|
||||
);
|
||||
open(url.toString());
|
||||
});
|
||||
|
||||
program
|
||||
.command("create")
|
||||
.description(
|
||||
"Create a thirdweb app from any of our official templates. Checkout some examples you can use here: https://github.com/thirdweb-example/",
|
||||
)
|
||||
.option("--app", `Create a thirdweb app.`)
|
||||
.option("--contract", `Create a thirdweb contracts project.`)
|
||||
.option("--ts, --typescript", `Initialize as a TypeScript project.`)
|
||||
.option("--js, --javascript", `Initialize as a JavaScript project.`)
|
||||
.option("--forge", `Initialize as a Forge project.`)
|
||||
.option("--hardhat", `Initialize as a Hardhat project.`)
|
||||
.option("--cra", `Initialize as a Create React App project.`)
|
||||
.option("--next", `Initialize as a Next.js project.`)
|
||||
.option("--vite", `Initialize as a Vite project.`)
|
||||
.option(
|
||||
"--use-npm",
|
||||
`Explicitly tell the CLI to bootstrap the app using npm`,
|
||||
)
|
||||
.option(
|
||||
"--use-pnpm",
|
||||
`Explicitly tell the CLI to bootstrap the app using pnpm`,
|
||||
)
|
||||
.option("--framework [name]", `The preferred framework.`)
|
||||
.option(
|
||||
"-t, --template [name]",
|
||||
`A template to start your project from. You can use an template repository name from the official thirdweb-example org.`,
|
||||
)
|
||||
.action(async (options) => {
|
||||
await twCreate(options);
|
||||
.command("upload")
|
||||
.description("Upload any file or directory to decentralized storage (IPFS)")
|
||||
.argument("[upload]", "path to file or directory to upload")
|
||||
.action(async (path) => {
|
||||
const storage = new ThirdwebStorage();
|
||||
try {
|
||||
const uri = await upload(storage, path);
|
||||
info(
|
||||
`Files stored at the following IPFS URI: ${chalk.blueBright(
|
||||
uri.toString(),
|
||||
)}`,
|
||||
);
|
||||
|
||||
const url = storage.resolveScheme(uri);
|
||||
info(
|
||||
`Open this link to view your upload: ${chalk.blueBright(
|
||||
url.toString(),
|
||||
)}`,
|
||||
);
|
||||
} catch (err: any) {
|
||||
if (
|
||||
err.toString() === "No files detected in specified directory" ||
|
||||
err.toString() === "Invalid path provided" ||
|
||||
err.toString() === "No path provided"
|
||||
) {
|
||||
logger.error(
|
||||
chalk.redBright(
|
||||
"Please specify a valid file or directory to upload",
|
||||
),
|
||||
);
|
||||
} else {
|
||||
logger.error(chalk.redBright("Failed to upload files"), err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command("install-ci")
|
||||
.command("detect")
|
||||
.description(
|
||||
"(alpha) Set up continuous integration for your contracts. This adds a github action to deploy the project on pull requests and pushes to branches. Publishes on push the the main branch.",
|
||||
"(deprecated) Compile contracts and detect implemented thirdweb contract extensions",
|
||||
)
|
||||
.option("-p, --path <project-path>", "path to project", ".")
|
||||
.option("-d, --debug", "show debug logs")
|
||||
.option("-a, --all", "run detection on all contracts")
|
||||
.action(async (options) => {
|
||||
await installGithubAction(options);
|
||||
await detectExtensions(options);
|
||||
});
|
||||
|
||||
await program.parseAsync();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { spinner } from "../core/helpers/logger";
|
||||
import { IpfsStorage } from "@thirdweb-dev/storage";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
||||
import path, { join } from "path";
|
||||
|
||||
@@ -13,10 +13,10 @@ export async function installGithubAction(options: any) {
|
||||
: path.resolve(`${projectPath}/${options.path}`);
|
||||
projectPath = resolvedPath;
|
||||
}
|
||||
const storage = new IpfsStorage();
|
||||
const storage = new ThirdwebStorage();
|
||||
const log = spinner("Installing thirdweb Github Action...");
|
||||
try {
|
||||
const ghActionData = await storage.getRaw(ghActionHash);
|
||||
const ghActionData = await (await storage.download(ghActionHash)).text();
|
||||
const dir = join(projectPath, ".github/workflows");
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
|
||||
@@ -5,8 +5,10 @@ import { logger, spinner } from "../core/helpers/logger";
|
||||
import { createContractsPrompt } from "../core/helpers/selector";
|
||||
import { ContractFeatures, Feature } from "../core/interfaces/ContractFeatures";
|
||||
import { ContractPayload } from "../core/interfaces/ContractPayload";
|
||||
import { getPkgManager } from "../create/helpers/get-pkg-manager";
|
||||
import { detectFeatures, FeatureWithEnabled } from "@thirdweb-dev/sdk";
|
||||
import chalk from "chalk";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import ora from "ora";
|
||||
import path from "path";
|
||||
|
||||
@@ -70,13 +72,13 @@ export async function detectExtensions(options: any) {
|
||||
const enabledFeatures: Feature[] = features.enabledFeatures.map(
|
||||
(feature) => ({
|
||||
name: feature.name,
|
||||
reference: `https://portal.thirdweb.com/contracts/${feature.docLinks.contracts}`,
|
||||
reference: `https://portal.thirdweb.com/interfaces/${feature.name.toLowerCase()}`,
|
||||
}),
|
||||
);
|
||||
const suggestedFeatures: Feature[] = features.suggestedFeatures.map(
|
||||
(feature) => ({
|
||||
name: feature.name,
|
||||
reference: `https://portal.thirdweb.com/contracts/${feature.docLinks.contracts}`,
|
||||
reference: `https://portal.thirdweb.com/interfaces/${feature.name.toLowerCase()}`,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -113,6 +115,26 @@ export async function detectExtensions(options: any) {
|
||||
)} - ${chalk.dim(chalk.gray(feature.reference))}`,
|
||||
);
|
||||
});
|
||||
|
||||
let deployCmd = `npx thirdweb@latest deploy`;
|
||||
if (existsSync(projectPath + "/package.json")) {
|
||||
const packageManager = getPkgManager();
|
||||
const useYarn = packageManager === "yarn";
|
||||
const pkgJson = JSON.parse(
|
||||
readFileSync(projectPath + "/package.json", "utf-8"),
|
||||
);
|
||||
if (pkgJson?.scripts?.deploy === deployCmd) {
|
||||
deployCmd = `${packageManager}${useYarn ? "" : " run"} deploy`;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(``);
|
||||
ora(
|
||||
`Once you're done writing your contracts, you can run the following command to deploy them:`,
|
||||
).info();
|
||||
logger.info(``);
|
||||
logger.info(` ${chalk.cyan(deployCmd)}`);
|
||||
logger.info(``);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { THIRDWEB_URL, cliVersion } from "../constants/urls";
|
||||
import { cliVersion, THIRDWEB_URL } from "../constants/urls";
|
||||
import build from "../core/builder/build";
|
||||
import detect from "../core/detection/detect";
|
||||
import { execute } from "../core/helpers/exec";
|
||||
import { error, info, logger, spinner } from "../core/helpers/logger";
|
||||
import { createContractsPrompt } from "../core/helpers/selector";
|
||||
import { ContractPayload } from "../core/interfaces/ContractPayload";
|
||||
import { IpfsStorage } from "@thirdweb-dev/storage";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import chalk from "chalk";
|
||||
import { readFileSync } from "fs";
|
||||
import ora from "ora";
|
||||
import path from "path";
|
||||
|
||||
export async function processProject(
|
||||
@@ -15,7 +16,7 @@ export async function processProject(
|
||||
command: "deploy" | "release",
|
||||
) {
|
||||
// TODO: allow overriding the default storage
|
||||
const storage = new IpfsStorage();
|
||||
const storage = new ThirdwebStorage();
|
||||
|
||||
logger.setSettings({
|
||||
minLevel: options.debug ? "debug" : "info",
|
||||
@@ -35,6 +36,27 @@ export async function processProject(
|
||||
|
||||
const projectType = await detect(projectPath, options);
|
||||
|
||||
if (projectType === "none") {
|
||||
if (command === "deploy") {
|
||||
info(
|
||||
"No contracts detected in this directory. Redirecting to the thirdweb contract explore page.",
|
||||
);
|
||||
return "https://thirdweb.com/explore";
|
||||
}
|
||||
|
||||
error("No detected contracts in this directory.");
|
||||
logger.info(``);
|
||||
ora(
|
||||
`Detected contract files must end with the '.sol' extension and exist in this directory or the '/contracts' subdirectory`,
|
||||
).info();
|
||||
logger.info(``);
|
||||
ora("To create a new contracts project, run the following command:").info();
|
||||
logger.info(``);
|
||||
logger.info(` ${chalk.cyan(`npx thirdweb@latest create --contract`)}`);
|
||||
logger.info(``);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.ci) {
|
||||
logger.info("Installing dependencies...");
|
||||
try {
|
||||
@@ -54,7 +76,7 @@ export async function processProject(
|
||||
}
|
||||
}
|
||||
|
||||
let compiledResult;
|
||||
let compiledResult: { contracts: ContractPayload[] };
|
||||
const compileLoader = spinner("Compiling project...");
|
||||
try {
|
||||
compiledResult = await build(projectPath, projectType);
|
||||
@@ -84,10 +106,47 @@ export async function processProject(
|
||||
if (options.ci) {
|
||||
selectedContracts = compiledResult.contracts;
|
||||
} else {
|
||||
const choices = compiledResult.contracts.map((c) => ({
|
||||
name: c.name,
|
||||
value: c,
|
||||
}));
|
||||
const filtered = compiledResult.contracts
|
||||
.filter((c) => {
|
||||
if (typeof options.file === "string" && options.file.length > 0) {
|
||||
return c.fileName.includes(options.file);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.filter((c) => {
|
||||
if (
|
||||
typeof options.contractName === "string" &&
|
||||
options.contractName.length > 0
|
||||
) {
|
||||
return c.name.includes(options.contractName);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (filtered.length === 0) {
|
||||
logger.error(`No contracts found matching the specified filters.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const choices = filtered.map((c) => {
|
||||
if (
|
||||
compiledResult.contracts.filter(
|
||||
(other: ContractPayload) => other.name === c.name,
|
||||
).length > 1
|
||||
) {
|
||||
return {
|
||||
name: `${c.name} - ${chalk.gray(c.fileName)}`,
|
||||
value: c,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: c.name,
|
||||
value: c,
|
||||
};
|
||||
});
|
||||
const prompt = createContractsPrompt(
|
||||
choices,
|
||||
`Choose which contract(s) to ${command}`,
|
||||
@@ -129,7 +188,9 @@ export async function processProject(
|
||||
if (file.includes(soliditySDKPackage)) {
|
||||
usesSoliditySDK = true;
|
||||
}
|
||||
return await storage.uploadSingle(file);
|
||||
return await storage.upload(file, {
|
||||
uploadWithoutDirectory: true,
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -140,14 +201,15 @@ export async function processProject(
|
||||
const metadataURIs = await Promise.all(
|
||||
selectedContracts.map(async (c) => {
|
||||
logger.debug(`Uploading ${c.name}...`);
|
||||
const hash = await storage.uploadSingle(c.metadata);
|
||||
return `ipfs://${hash}`;
|
||||
return await storage.upload(JSON.parse(JSON.stringify(c.metadata)), {
|
||||
uploadWithoutDirectory: true,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// Upload batch all bytecodes
|
||||
const bytecodes = selectedContracts.map((c) => c.bytecode);
|
||||
const { uris: bytecodeURIs } = await storage.uploadBatch(bytecodes);
|
||||
const bytecodeURIs = await storage.uploadBatch(bytecodes);
|
||||
|
||||
const combinedContents = selectedContracts.map((c, i) => {
|
||||
// attach analytics blob to metadata
|
||||
@@ -169,14 +231,13 @@ export async function processProject(
|
||||
let combinedURIs: string[] = [];
|
||||
if (combinedContents.length === 1) {
|
||||
// use upload single if only one contract to get a clean IPFS hash
|
||||
const metadataUri = await storage.uploadSingle(
|
||||
JSON.stringify(combinedContents[0]),
|
||||
);
|
||||
const metadataUri = await storage.upload(combinedContents[0], {
|
||||
uploadWithoutDirectory: true,
|
||||
});
|
||||
combinedURIs.push(metadataUri);
|
||||
} else {
|
||||
// otherwise upload batch
|
||||
const { uris } = await storage.uploadMetadataBatch(combinedContents);
|
||||
combinedURIs = uris;
|
||||
combinedURIs = await storage.uploadBatch(combinedContents);
|
||||
}
|
||||
|
||||
loader.succeed("Upload successful");
|
||||
|
||||
@@ -21,7 +21,7 @@ contract Contract is ERC721Base {
|
||||
{}
|
||||
|
||||
}`,
|
||||
ERC721Signature: `// SPDX-License-Identifier: Apache-2.0
|
||||
ERC721SignatureMint: `// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@thirdweb-dev/contracts/base/ERC721SignatureMint.sol";
|
||||
@@ -113,7 +113,29 @@ contract Contract is ERC1155Base {
|
||||
{}
|
||||
|
||||
}`,
|
||||
ERC1155Signature: `// SPDX-License-Identifier: Apache-2.0
|
||||
Staking721Base: `// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@thirdweb-dev/contracts/base/Staking721Base.sol";
|
||||
|
||||
contract Contract is Staking721Base {
|
||||
constructor(
|
||||
uint256 _timeUnit,
|
||||
uint256 _rewardsPerUnitTime,
|
||||
address _nftCollection,
|
||||
address _rewardToken,
|
||||
address _nativeTokenWrapper
|
||||
)
|
||||
Staking721Base(
|
||||
_timeUnit,
|
||||
_rewardsPerUnitTime,
|
||||
_nftCollection,
|
||||
_rewardToken,
|
||||
_nativeTokenWrapper
|
||||
)
|
||||
{}
|
||||
}`,
|
||||
ERC1155SignatureMint: `// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@thirdweb-dev/contracts/base/ERC1155SignatureMint.sol";
|
||||
@@ -182,6 +204,28 @@ contract Contract is ERC1155Drop {
|
||||
_primarySaleRecipient
|
||||
)
|
||||
{}
|
||||
}`,
|
||||
Staking1155Base: `// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@thirdweb-dev/contracts/base/Staking1155Base.sol";
|
||||
|
||||
contract Contract is Staking1155Base {
|
||||
constructor(
|
||||
uint256 _defaultTimeUnit,
|
||||
uint256 _defaultRewardsPerUnitTime,
|
||||
address _stakingToken,
|
||||
address _rewardToken,
|
||||
address _nativeTokenWrapper
|
||||
)
|
||||
Staking1155Base(
|
||||
_defaultTimeUnit,
|
||||
_defaultRewardsPerUnitTime,
|
||||
_stakingToken,
|
||||
_rewardToken,
|
||||
_nativeTokenWrapper
|
||||
)
|
||||
{}
|
||||
}`,
|
||||
ERC20Base: `// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
@@ -288,6 +332,30 @@ contract Contract is ERC20DropVote {
|
||||
_primarySaleRecipient
|
||||
)
|
||||
{}
|
||||
}`,
|
||||
Staking20Base: `// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@thirdweb-dev/contracts/base/Staking20Base.sol";
|
||||
|
||||
contract Contract is Staking20Base {
|
||||
constructor(
|
||||
uint256 _timeUnit,
|
||||
uint256 _rewardRatioNumerator,
|
||||
uint256 _rewardRatioDenominator,
|
||||
address _stakingToken,
|
||||
address _rewardToken,
|
||||
address _nativeTokenWrapper
|
||||
)
|
||||
Staking20Base(
|
||||
_timeUnit,
|
||||
_rewardRatioNumerator,
|
||||
_rewardRatioDenominator,
|
||||
_stakingToken,
|
||||
_rewardToken,
|
||||
_nativeTokenWrapper
|
||||
)
|
||||
{}
|
||||
}`,
|
||||
};
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ export class BrownieBuilder extends BaseBuilder {
|
||||
metadata: {},
|
||||
bytecode,
|
||||
sources: [], // TODO
|
||||
fileName: "", // TODO
|
||||
name: contractName,
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { extractIPFSHashFromBytecode } from "../helpers/ipfs";
|
||||
import { logger } from "../helpers/logger";
|
||||
import { CompileOptions, IBuilder } from "../interfaces/Builder";
|
||||
import { ContractPayload } from "../interfaces/ContractPayload";
|
||||
import { extractIPFSHashFromBytecode } from "@thirdweb-dev/sdk/evm";
|
||||
|
||||
export abstract class BaseBuilder implements IBuilder {
|
||||
abstract compile(
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { findFiles } from "../../common/file-helper";
|
||||
import { hasForge } from "../../create/helpers/has-forge";
|
||||
import { execute } from "../helpers/exec";
|
||||
import { logger } from "../helpers/logger";
|
||||
import { CompileOptions } from "../interfaces/Builder";
|
||||
import { ContractPayload } from "../interfaces/ContractPayload";
|
||||
import { BaseBuilder } from "./builder-base";
|
||||
import chalk from "chalk";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { basename, join } from "path";
|
||||
|
||||
@@ -11,6 +13,18 @@ export class FoundryBuilder extends BaseBuilder {
|
||||
public async compile(options: CompileOptions): Promise<{
|
||||
contracts: ContractPayload[];
|
||||
}> {
|
||||
const isInstalled = await hasForge();
|
||||
if (!isInstalled) {
|
||||
console.error(
|
||||
`\n${chalk.redBright(
|
||||
`error`,
|
||||
)} You don't have forge installed on this machine!\n\nYou can install forge by following these instructions:\n${chalk.blueBright(
|
||||
`https://book.getfoundry.sh/getting-started/installation`,
|
||||
)}\n`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await execute("forge clean", options.projectPath);
|
||||
await execute("forge build --extra-output metadata", options.projectPath);
|
||||
|
||||
@@ -88,6 +102,11 @@ export class FoundryBuilder extends BaseBuilder {
|
||||
})
|
||||
.filter((path) => path !== undefined) as string[];
|
||||
|
||||
const fileNames = Object.keys(
|
||||
parsedMetadata?.settings?.compilationTarget || {},
|
||||
);
|
||||
const fileName = fileNames.length > 0 ? fileNames[0] : "";
|
||||
|
||||
if (
|
||||
this.shouldProcessContract(
|
||||
contractInfo.abi,
|
||||
@@ -99,6 +118,7 @@ export class FoundryBuilder extends BaseBuilder {
|
||||
name: contractName,
|
||||
metadata,
|
||||
bytecode,
|
||||
fileName,
|
||||
sources,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export class HardhatBuilder extends BaseBuilder {
|
||||
//the hardhat extractor **logs out** the runtime config of hardhat, we take that stdout and parse it
|
||||
const stringifiedConfig = (
|
||||
await execute(
|
||||
`npx hardhat run ${configExtractorScriptPath} --no-compile`,
|
||||
`npx hardhat run "${configExtractorScriptPath}" --no-compile`,
|
||||
options.projectPath,
|
||||
)
|
||||
).stdout;
|
||||
@@ -123,11 +123,17 @@ export class HardhatBuilder extends BaseBuilder {
|
||||
})
|
||||
.filter((path) => path !== undefined) as string[];
|
||||
|
||||
const fileNames = Object.keys(
|
||||
meta?.settings?.compilationTarget || {},
|
||||
);
|
||||
const fileName = fileNames.length > 0 ? fileNames[0] : "";
|
||||
|
||||
if (this.shouldProcessContract(abi, deployedBytecode, contractName)) {
|
||||
contracts.push({
|
||||
metadata,
|
||||
bytecode,
|
||||
name: contractName,
|
||||
fileName,
|
||||
sources,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -140,11 +140,17 @@ export class SolcBuilder extends BaseBuilder {
|
||||
})
|
||||
.filter((path) => path !== undefined) as string[];
|
||||
|
||||
const fileNames = Object.keys(
|
||||
parsedMetadata?.settings?.compilationTarget || {},
|
||||
);
|
||||
const fileName = fileNames.length > 0 ? fileNames[0] : "";
|
||||
|
||||
if (this.shouldProcessContract(abi, deployedBytecode, contractName)) {
|
||||
contracts.push({
|
||||
metadata,
|
||||
bytecode,
|
||||
name: contractName,
|
||||
fileName,
|
||||
sources: _sources,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,11 +72,18 @@ export class TruffleBuilder extends BaseBuilder {
|
||||
return undefined;
|
||||
})
|
||||
.filter((path) => path !== undefined) as string[];
|
||||
|
||||
const fileNames = Object.keys(
|
||||
parsedMetadata?.settings?.compilationTarget || {},
|
||||
);
|
||||
const fileName = fileNames.length > 0 ? fileNames[0] : "";
|
||||
|
||||
if (this.shouldProcessContract(abi, deployedBytecode, contractName)) {
|
||||
contracts.push({
|
||||
metadata,
|
||||
bytecode,
|
||||
name: contractName,
|
||||
fileName,
|
||||
sources,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import { Detector } from "./detector";
|
||||
import FoundryDetector from "./foundry";
|
||||
import HardhatDetector from "./hardhat";
|
||||
import TruffleDetector from "./truffle";
|
||||
import fs, { existsSync } from "fs";
|
||||
import inquirer from "inquirer";
|
||||
import { parse } from "path";
|
||||
|
||||
const { Confirm } = require("enquirer");
|
||||
|
||||
@@ -26,18 +28,25 @@ export default async function detect(
|
||||
|
||||
//if there is no project returned at all then just return unknown}
|
||||
if (!possibleProjectTypes.length) {
|
||||
warn(`${ERROR_MESSAGES.noConfiguration} ${path}`);
|
||||
const prompt = new Confirm({
|
||||
name: "continue",
|
||||
message:
|
||||
"Do you want to continue and compile this project with solc instead?",
|
||||
});
|
||||
const shouldCompile = await prompt.run();
|
||||
if (!shouldCompile) {
|
||||
logger.warn("Aborted contract compilation");
|
||||
process.exit(1);
|
||||
const canCompile = hasContracts(path);
|
||||
|
||||
if (canCompile) {
|
||||
warn(`${ERROR_MESSAGES.noConfiguration} ${path}`);
|
||||
const prompt = new Confirm({
|
||||
name: "continue",
|
||||
message:
|
||||
"Do you want to continue and compile this project with solc instead?",
|
||||
initial: true,
|
||||
});
|
||||
const shouldCompile = await prompt.run();
|
||||
if (!shouldCompile) {
|
||||
logger.warn("Aborted contract compilation");
|
||||
process.exit(1);
|
||||
}
|
||||
return "solc";
|
||||
} else {
|
||||
return "none";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
//if there is only one possible option just return it
|
||||
if (possibleProjectTypes.length === 1) {
|
||||
@@ -64,3 +73,24 @@ export default async function detect(
|
||||
return answer[question];
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a directory has any .sol files or a /contracts folder with .sol files
|
||||
function hasContracts(path: any) {
|
||||
return (
|
||||
["", "/contracts"].filter((p) => {
|
||||
const dirPath = path + p;
|
||||
|
||||
if (!existsSync(dirPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(dirPath);
|
||||
const contracts = files.filter((filePath) => {
|
||||
const { ext } = parse(filePath);
|
||||
return ext === ".sol";
|
||||
});
|
||||
|
||||
return contracts.length > 0;
|
||||
}).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ProjectType } from "../types/ProjectType";
|
||||
import { Detector } from "./detector";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
export default class NPMDetector implements Detector {
|
||||
public projectType: ProjectType = "npm";
|
||||
|
||||
public matches(path: string): boolean {
|
||||
return existsSync(path + "/package-lock.json");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ProjectType } from "../types/ProjectType";
|
||||
import { Detector } from "./detector";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
export default class ViteDetector implements Detector {
|
||||
public projectType: ProjectType = "vite";
|
||||
|
||||
public matches(path: string): boolean {
|
||||
return (
|
||||
existsSync(path + "/vite.config.js") ||
|
||||
existsSync(path + "/vite.config.ts")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ProjectType } from "../types/ProjectType";
|
||||
import { Detector } from "./detector";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
export default class YarnDetector implements Detector {
|
||||
public projectType: ProjectType = "yarn";
|
||||
|
||||
public matches(path: string): boolean {
|
||||
return existsSync(path + "/yarn.lock");
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import { decodeFirstSync } from "cbor";
|
||||
import { UnixFS } from "ipfs-unixfs";
|
||||
import { DAGNode } from "ipld-dag-pb";
|
||||
import { toB58String } from "multihashes";
|
||||
|
||||
/**
|
||||
* Derives IPFS hash of string
|
||||
* @param {String} str
|
||||
* @return {String} IPFS hash (ex: "Qm")
|
||||
*/
|
||||
export async function getIPFSHash(str: string) {
|
||||
const file = new UnixFS({
|
||||
type: "file",
|
||||
data: Buffer.from(str),
|
||||
});
|
||||
const node = new DAGNode(file.marshal());
|
||||
const metadataLink = await node.toDAGLink();
|
||||
return toB58String(metadataLink.Hash.multihash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the IPFS hash from the given bytecode
|
||||
* @param bytecode
|
||||
* @returns
|
||||
*/
|
||||
export function extractIPFSHashFromBytecode(
|
||||
bytecode: string,
|
||||
): string | undefined {
|
||||
const numericBytecode = hexToBytes(bytecode);
|
||||
const cborLength: number =
|
||||
numericBytecode[numericBytecode.length - 2] * 0x100 +
|
||||
numericBytecode[numericBytecode.length - 1];
|
||||
const bytecodeBuffer = Buffer.from(
|
||||
numericBytecode.slice(numericBytecode.length - 2 - cborLength, -2),
|
||||
);
|
||||
const cborData = decodeFirstSync(bytecodeBuffer);
|
||||
if (cborData["ipfs"]) {
|
||||
const uri = toB58String(cborData["ipfs"]);
|
||||
return uri;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function hexToBytes(hex: string | number) {
|
||||
hex = hex.toString(16);
|
||||
if (!hex.startsWith("0x")) {
|
||||
hex = `0x${hex}`;
|
||||
}
|
||||
if (!isHexStrict(hex)) {
|
||||
throw new Error('Given value "' + hex + '" is not a valid hex string.');
|
||||
}
|
||||
|
||||
hex = hex.replace(/^0x/i, "");
|
||||
|
||||
for (var bytes = [], c = 0; c < hex.length; c += 2) {
|
||||
bytes.push(parseInt(hex.slice(c, c + 2), 16));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function isHexStrict(hex: string | number) {
|
||||
return (
|
||||
(typeof hex === "string" || typeof hex === "number") &&
|
||||
/^(-)?0x[0-9a-f]*$/i.test(hex.toString())
|
||||
);
|
||||
}
|
||||
@@ -17,6 +17,11 @@ export interface ContractPayload {
|
||||
*/
|
||||
metadata: any;
|
||||
|
||||
/**
|
||||
* The file name of the original compiled file
|
||||
*/
|
||||
fileName: string;
|
||||
|
||||
/**
|
||||
* The source file paths
|
||||
*/
|
||||
|
||||
@@ -3,4 +3,11 @@ export type ProjectType =
|
||||
| "foundry"
|
||||
| "hardhat"
|
||||
| "truffle"
|
||||
| "unknown";
|
||||
| "solc"
|
||||
| "vite"
|
||||
| "next"
|
||||
| "cra"
|
||||
| "spa-webapp"
|
||||
| "yarn"
|
||||
| "npm"
|
||||
| "none";
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export type CacheEntry = {
|
||||
isCached: boolean;
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
declare module 'sync-disk-cache';
|
||||
declare module "solc";
|
||||
-1
@@ -1 +0,0 @@
|
||||
declare module "solc";
|
||||
+331
-152
@@ -2,60 +2,94 @@
|
||||
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { CREATE_MESSAGES } from "../../constants/constants";
|
||||
import detect from "../core/detection/detect";
|
||||
import { DownloadError, createApp } from "./helpers/create-app";
|
||||
import { createContract } from "./helpers/create-contract";
|
||||
import { createContractProject } from "./helpers/create-contract";
|
||||
import { getPkgManager } from "./helpers/get-pkg-manager";
|
||||
import { validateNpmName } from "./helpers/validate-pkg";
|
||||
import chalk from "chalk";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import prompts from "prompts";
|
||||
|
||||
let projectType: string = "";
|
||||
let contractName: string = "";
|
||||
let projectPath: string = "";
|
||||
let projectType: string = "";
|
||||
let framework: string = "";
|
||||
let language: string = "";
|
||||
let baseContract: string = "";
|
||||
let chain: string = "";
|
||||
/* let createType: string = "app"; */
|
||||
|
||||
export async function twCreate(options: any) {
|
||||
if (typeof projectPath === "string") {
|
||||
projectPath = projectPath.trim();
|
||||
export async function twCreate(
|
||||
pType: string,
|
||||
pPath: string = "",
|
||||
options: any,
|
||||
) {
|
||||
if (typeof pPath === "string") {
|
||||
projectPath = pPath;
|
||||
}
|
||||
|
||||
if (options.app) {
|
||||
if (typeof options.contractName === "string") {
|
||||
contractName = options.contractName;
|
||||
}
|
||||
|
||||
if (pType === "app" || options.app) {
|
||||
projectType = "app";
|
||||
}
|
||||
|
||||
if (options.contract) {
|
||||
} else if (pType === "contract" || options.contract) {
|
||||
projectType = "contract";
|
||||
}
|
||||
|
||||
if (options.typescript) {
|
||||
language = "typescript";
|
||||
if (projectType === "app") {
|
||||
if (options.typescript) {
|
||||
language = "typescript";
|
||||
}
|
||||
|
||||
if (options.javascript) {
|
||||
language = "javascript";
|
||||
}
|
||||
|
||||
if (options.next) {
|
||||
framework = "next";
|
||||
}
|
||||
|
||||
if (options.cra) {
|
||||
framework = "cra";
|
||||
}
|
||||
|
||||
if (options.vite) {
|
||||
framework = "vite";
|
||||
}
|
||||
|
||||
if (options.node) {
|
||||
framework = "node";
|
||||
}
|
||||
|
||||
if (options.express) {
|
||||
framework = "express";
|
||||
}
|
||||
|
||||
if (options.reactNative) {
|
||||
framework = "react-native";
|
||||
}
|
||||
|
||||
if (options.solana) {
|
||||
chain = "solana";
|
||||
}
|
||||
|
||||
if (options.evm) {
|
||||
chain = "evm";
|
||||
}
|
||||
}
|
||||
|
||||
if (options.javascript) {
|
||||
language = "javascript";
|
||||
}
|
||||
if (projectType === "contract") {
|
||||
if (options.forge) {
|
||||
framework = "forge";
|
||||
}
|
||||
|
||||
if (options.next) {
|
||||
framework = "next";
|
||||
}
|
||||
|
||||
if (options.cra) {
|
||||
framework = "cra";
|
||||
}
|
||||
|
||||
if (options.vite) {
|
||||
framework = "vite";
|
||||
}
|
||||
|
||||
if (options.forge) {
|
||||
framework = "forge";
|
||||
}
|
||||
|
||||
if (options.hardhat) {
|
||||
framework = "hardhat";
|
||||
if (options.hardhat) {
|
||||
framework = "hardhat";
|
||||
}
|
||||
}
|
||||
|
||||
if (options.framework) {
|
||||
@@ -76,151 +110,293 @@ export async function twCreate(options: any) {
|
||||
if (typeof res.projectType === "string") {
|
||||
projectType = res.projectType.trim();
|
||||
}
|
||||
} else if (!projectType && options.template) {
|
||||
} else if (!projectType || options.template) {
|
||||
// If no project type is specified, but a template is, we assume the user wants to create an app.
|
||||
// We do this so old users can still use the --template flag to create an app.
|
||||
projectType = "app";
|
||||
}
|
||||
|
||||
if (!projectPath) {
|
||||
const defaultName =
|
||||
projectType === "contract" ? "thirdweb-contracts" : "thirdweb-app";
|
||||
const res = await prompts({
|
||||
type: "text",
|
||||
name: "path",
|
||||
message: CREATE_MESSAGES.projectName,
|
||||
initial: options.template || defaultName,
|
||||
validate: (name) => {
|
||||
const validation = validateNpmName(path.basename(path.resolve(name)));
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
return "Invalid project name: " + validation.problems?.[0];
|
||||
},
|
||||
});
|
||||
|
||||
if (typeof res.path === "string") {
|
||||
projectPath = res.path.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (!projectPath) {
|
||||
console.log(
|
||||
"\nPlease specify the project directory:\n" +
|
||||
` ${chalk.cyan("npx thirdweb create")} ${chalk.green(
|
||||
"<project-directory>",
|
||||
)}\n` +
|
||||
"For example:\n" +
|
||||
` ${chalk.cyan("npx thirdweb create")} ${chalk.green(
|
||||
"my-thirdweb-app",
|
||||
)}\n\n` +
|
||||
`Run ${chalk.cyan("npx thirdweb --help")} to see all options.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!options.template) {
|
||||
if (projectType === "app" && !framework) {
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "framework",
|
||||
message: CREATE_MESSAGES.framework,
|
||||
choices: [
|
||||
{ title: "Next.js", value: "next" },
|
||||
{ title: "Create React App", value: "cra" },
|
||||
{ title: "Vite", value: "vite" },
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof res.framework === "string") {
|
||||
framework = res.framework.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (projectType === "app" && !language) {
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "language",
|
||||
message: CREATE_MESSAGES.language,
|
||||
choices: [
|
||||
{ title: "JavaScript", value: "javascript" },
|
||||
{ title: "TypeScript", value: "typescript" },
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof res.language === "string") {
|
||||
language = res.language.trim();
|
||||
}
|
||||
}
|
||||
// Whether to only create a new contract without the project
|
||||
let onlyContract = false;
|
||||
if (projectType === "contract") {
|
||||
const resolvedProjectPath = path.resolve(projectPath);
|
||||
const contractProjectType = await detect(resolvedProjectPath, {});
|
||||
|
||||
if (
|
||||
projectType === "contract" &&
|
||||
framework !== "forge" &&
|
||||
framework !== "hardhat"
|
||||
contractProjectType === "foundry" &&
|
||||
fs.existsSync(path.join(resolvedProjectPath, "src"))
|
||||
) {
|
||||
onlyContract = true;
|
||||
projectPath = path.join(projectPath, "src");
|
||||
} else if (contractProjectType === "hardhat") {
|
||||
if (fs.existsSync(path.join(resolvedProjectPath, "contracts"))) {
|
||||
onlyContract = true;
|
||||
projectPath = path.join(projectPath, "contracts");
|
||||
} else {
|
||||
// If there's no expected contracts folder, check for a directory that has .sol files
|
||||
const directories = fs
|
||||
.readdirSync(resolvedProjectPath)
|
||||
.filter((file) =>
|
||||
fs.lstatSync(path.join(resolvedProjectPath, file)).isDirectory(),
|
||||
);
|
||||
for (const directory of directories) {
|
||||
const files = fs.readdirSync(path.join(projectPath, directory));
|
||||
if (files.some((file) => file.endsWith(".sol"))) {
|
||||
onlyContract = true;
|
||||
projectPath = path.join(projectPath, directory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!onlyContract) {
|
||||
if (!projectPath) {
|
||||
const defaultName =
|
||||
projectType === "contract" ? "thirdweb-contracts" : "thirdweb-app";
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "framework",
|
||||
message: CREATE_MESSAGES.framework,
|
||||
choices: [
|
||||
{ title: "Hardhat", value: "hardhat" },
|
||||
{ title: "Forge", value: "forge" },
|
||||
],
|
||||
type: "text",
|
||||
name: "path",
|
||||
message: CREATE_MESSAGES.projectName,
|
||||
initial: options.template || defaultName,
|
||||
format: (name: string) => name.toLowerCase(),
|
||||
validate: (name: string) => {
|
||||
const validation = validateNpmName(
|
||||
path.basename(path.resolve(name.toLowerCase())),
|
||||
);
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
return "Invalid project name: " + validation.problems?.[0];
|
||||
},
|
||||
});
|
||||
|
||||
if (typeof res.framework === "string") {
|
||||
framework = res.framework.trim();
|
||||
if (typeof res.path === "string") {
|
||||
projectPath = path.join(projectPath, res.path.trim());
|
||||
}
|
||||
}
|
||||
|
||||
if (!projectPath) {
|
||||
console.log(
|
||||
"\nPlease specify the project directory:\n" +
|
||||
` ${chalk.cyan("npx thirdweb create")} ${chalk.green(
|
||||
"<project-directory>",
|
||||
)}\n` +
|
||||
"For example:\n" +
|
||||
` ${chalk.cyan("npx thirdweb create")} ${chalk.green(
|
||||
"my-thirdweb-app",
|
||||
)}\n\n` +
|
||||
`Run ${chalk.cyan("npx thirdweb --help")} to see all options.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!options.template) {
|
||||
if (projectType === "app" && !chain) {
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "chain",
|
||||
message: CREATE_MESSAGES.chain,
|
||||
choices: [
|
||||
{ title: "EVM", value: "evm" },
|
||||
{ title: "Solana", value: "solana" },
|
||||
],
|
||||
});
|
||||
|
||||
if (res.chain === "solana") {
|
||||
chain = "solana";
|
||||
} else {
|
||||
chain = "evm";
|
||||
}
|
||||
}
|
||||
|
||||
if (projectType === "app" && !framework) {
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "framework",
|
||||
message: CREATE_MESSAGES.framework,
|
||||
choices:
|
||||
chain === "solana"
|
||||
? [
|
||||
{ title: "Next.js", value: "next" },
|
||||
{ title: "Create React App", value: "cra" },
|
||||
{ title: "Node.js", value: "node" },
|
||||
{ title: "Express", value: "express" },
|
||||
// Solana doesn't support Vite just yet:
|
||||
// { title: "Vite", value: "vite" },
|
||||
]
|
||||
: [
|
||||
{ title: "Next.js", value: "next" },
|
||||
{ title: "Create React App", value: "cra" },
|
||||
{ title: "Vite", value: "vite" },
|
||||
{ title: "React Native", value: "react-native" },
|
||||
{ title: "Node.js", value: "node" },
|
||||
{ title: "Express", value: "express" },
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof res.framework === "string") {
|
||||
framework = res.framework.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (projectType === "app" && !language) {
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "language",
|
||||
message: CREATE_MESSAGES.language,
|
||||
choices: [
|
||||
{ title: "JavaScript", value: "javascript" },
|
||||
{ title: "TypeScript", value: "typescript" },
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof res.language === "string") {
|
||||
language = res.language.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
projectType === "contract" &&
|
||||
framework !== "forge" &&
|
||||
framework !== "hardhat"
|
||||
) {
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "framework",
|
||||
message: CREATE_MESSAGES.framework,
|
||||
choices: [
|
||||
{ title: "Hardhat", value: "hardhat" },
|
||||
{ title: "Forge", value: "forge" },
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof res.framework === "string") {
|
||||
framework = res.framework.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (!framework) {
|
||||
console.log("Please specify a framework");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!language) {
|
||||
// Default = JavaScript
|
||||
language = "javascript";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projectType === "contract") {
|
||||
// Select contract name
|
||||
if (!contractName) {
|
||||
const defaultName = "MyContract";
|
||||
const res = await prompts({
|
||||
type: "text",
|
||||
name: "path",
|
||||
message: CREATE_MESSAGES.contractName,
|
||||
initial: defaultName,
|
||||
validate: (name: string) => {
|
||||
const isValid = /(^[a-z0-9A-Z]+$)|(^[a-z0-9A-Z]+\.sol$)/.test(name);
|
||||
if (isValid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return `Invalid contract name 'contractName' (only alphanumeric characters allowed)`;
|
||||
},
|
||||
});
|
||||
|
||||
if (typeof res.path === "string") {
|
||||
contractName = res.path.trim();
|
||||
}
|
||||
|
||||
if (!contractName) {
|
||||
console.log(
|
||||
"\nPlease specify the contract name:\n" +
|
||||
` ${chalk.cyan(
|
||||
"npx thirdweb create --contract --name",
|
||||
)} ${chalk.green("<contract-name>")}\n` +
|
||||
"For example:\n" +
|
||||
` ${chalk.cyan(
|
||||
"npx thirdweb create --contract --name",
|
||||
)} ${chalk.green("MyContract")}\n\n` +
|
||||
`Run ${chalk.cyan("npx thirdweb --help")} to see all options.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Select base contract
|
||||
if (projectType === "contract" && !baseContract) {
|
||||
const res = await prompts({
|
||||
let standard = "none";
|
||||
const standardPrompt = await prompts({
|
||||
type: "select",
|
||||
name: "baseContract",
|
||||
name: "standard",
|
||||
message: CREATE_MESSAGES.contract,
|
||||
choices: [
|
||||
{ title: "Empty Contract", value: "" },
|
||||
{ title: "ERC721 Base", value: "ERC721Base" },
|
||||
{ title: "ERC721 + Signature Mint", value: "ERC721Signature" },
|
||||
{ title: "ERC721 + Lazy Mint", value: "ERC721LazyMint" },
|
||||
{ title: "ERC721 + Delayed Reveal", value: "ERC721DelayedReveal" },
|
||||
{ title: "ERC721 + Drop", value: "ERC721Drop" },
|
||||
{ title: "ERC1155 Base", value: "ERC1155Base" },
|
||||
{ title: "ERC1155 + Signature Mint", value: "ERC1155Signature" },
|
||||
{ title: "ERC1155 + Lazy Mint", value: "ERC1155LazyMint" },
|
||||
{ title: "ERC1155 + Delayed Reveal", value: "ERC1155DelayedReveal" },
|
||||
{ title: "ERC1155 + Drop", value: "ERC1155Drop" },
|
||||
{ title: "ERC20 Base", value: "ERC20Base" },
|
||||
{ title: "ERC20 + Vote", value: "ERC20Vote" },
|
||||
{ title: "ERC20 + Signature Mint", value: "ERC20SignatureMint" },
|
||||
{
|
||||
title: "ERC20 + Vote + Signature Mint",
|
||||
value: "ERC20SignatureMintVote",
|
||||
},
|
||||
{ title: "ERC20 + Drop", value: "ERC20Drop" },
|
||||
{ title: "ERC20 + Vote + Drop", value: "ERC20DropVote" },
|
||||
...(!onlyContract
|
||||
? [{ title: "Empty Contract", value: "none" }]
|
||||
: []),
|
||||
{ title: "ERC721", value: "erc721" },
|
||||
{ title: "ERC20", value: "erc20" },
|
||||
{ title: "ERC1155", value: "erc1155" },
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof res.baseContract === "string") {
|
||||
baseContract = res.baseContract.trim();
|
||||
if (typeof standardPrompt.standard === "string") {
|
||||
standard = standardPrompt.standard.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (!framework) {
|
||||
console.log("Please specify a framework");
|
||||
process.exit(1);
|
||||
}
|
||||
if (standard === "none") {
|
||||
baseContract = "";
|
||||
} else {
|
||||
let choices: prompts.Choice[] = [];
|
||||
if (standard === "erc721") {
|
||||
choices = [
|
||||
{ title: "None", value: "ERC721Base" },
|
||||
{ title: "Signature Mint", value: "ERC721SignatureMint" },
|
||||
{ title: "Lazy Mint", value: "ERC721LazyMint" },
|
||||
{ title: "Delayed Reveal", value: "ERC721DelayedReveal" },
|
||||
{ title: "Drop", value: "ERC721Drop" },
|
||||
];
|
||||
} else if (standard === "erc20") {
|
||||
choices = [
|
||||
{ title: "None", value: "ERC20Base" },
|
||||
{ title: "Vote", value: "ERC20Vote" },
|
||||
{ title: "Signature Mint", value: "ERC20SignatureMint" },
|
||||
{ title: "Vote + Signature Mint", value: "ERC20SignatureMintVote" },
|
||||
{ title: "Drop", value: "ERC20Drop" },
|
||||
{ title: "Vote + Drop", value: "ERC20DropVote" },
|
||||
];
|
||||
} else if (standard === "erc1155") {
|
||||
choices = [
|
||||
{ title: "None", value: "ERC1155Base" },
|
||||
{ title: "Signature Mint", value: "ERC1155SignatureMint" },
|
||||
{ title: "Lazy Mint", value: "ERC1155LazyMint" },
|
||||
{ title: "Delayed Reveal", value: "ERC1155DelayedReveal" },
|
||||
{ title: "Drop", value: "ERC1155Drop" },
|
||||
];
|
||||
}
|
||||
|
||||
if (!language) {
|
||||
// Default = JavaScript
|
||||
language = "javascript";
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "baseContract",
|
||||
message: CREATE_MESSAGES.extensions,
|
||||
choices: choices,
|
||||
});
|
||||
|
||||
if (typeof res.baseContract === "string") {
|
||||
baseContract = res.baseContract.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedProjectPath = path.resolve(projectPath);
|
||||
const projectName = path.basename(resolvedProjectPath);
|
||||
// Resolve project path
|
||||
projectPath = path.resolve(projectPath);
|
||||
const projectName = path.basename(projectPath);
|
||||
|
||||
const { valid, problems } = validateNpmName(projectName);
|
||||
if (!valid) {
|
||||
@@ -252,18 +428,21 @@ export async function twCreate(options: any) {
|
||||
try {
|
||||
if (projectType === "app") {
|
||||
await createApp({
|
||||
appPath: resolvedProjectPath,
|
||||
appPath: projectPath,
|
||||
packageManager,
|
||||
framework,
|
||||
language,
|
||||
template,
|
||||
chain,
|
||||
});
|
||||
} else {
|
||||
await createContract({
|
||||
contractPath: resolvedProjectPath,
|
||||
await createContractProject({
|
||||
contractPath: projectPath,
|
||||
packageManager,
|
||||
framework,
|
||||
contractName,
|
||||
baseContract,
|
||||
onlyContract,
|
||||
});
|
||||
}
|
||||
} catch (reason) {
|
||||
|
||||
@@ -10,6 +10,8 @@ import { makeDir } from "./make-dir";
|
||||
import { downloadAndExtractRepo, hasTemplate } from "./templates";
|
||||
import retry from "async-retry";
|
||||
import chalk from "chalk";
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
export class DownloadError extends Error {}
|
||||
@@ -20,12 +22,14 @@ export async function createApp({
|
||||
framework,
|
||||
language,
|
||||
template,
|
||||
chain,
|
||||
}: {
|
||||
appPath: string;
|
||||
packageManager: PackageManager;
|
||||
framework?: string;
|
||||
language?: string;
|
||||
template?: string;
|
||||
chain?: string;
|
||||
}): Promise<void> {
|
||||
let frameworkPath = "";
|
||||
|
||||
@@ -41,7 +45,9 @@ export async function createApp({
|
||||
process.exit(1);
|
||||
}
|
||||
} else if (framework) {
|
||||
frameworkPath = `${framework}-${language || "javascript"}-starter`;
|
||||
frameworkPath = `${framework}-${language || "javascript"}-${
|
||||
chain === "solana" ? "solana-" : ""
|
||||
}starter`;
|
||||
const found = await hasTemplate(frameworkPath);
|
||||
|
||||
if (!found) {
|
||||
@@ -113,6 +119,20 @@ export async function createApp({
|
||||
);
|
||||
}
|
||||
|
||||
const jsonFile = path.join(root, "package.json");
|
||||
const packageString = fs.readFileSync(jsonFile);
|
||||
try {
|
||||
const packageJson = JSON.parse(packageString.toString());
|
||||
if (packageJson) {
|
||||
packageJson.name = appName;
|
||||
}
|
||||
|
||||
fs.writeFileSync(jsonFile, JSON.stringify(packageJson, null, 2) + os.EOL);
|
||||
} catch (e) {
|
||||
console.log("Failed to re-name package.json, continuing...");
|
||||
console.log();
|
||||
}
|
||||
|
||||
console.log("Installing packages. This might take a couple of minutes.");
|
||||
console.log();
|
||||
|
||||
@@ -141,6 +161,21 @@ export async function createApp({
|
||||
);
|
||||
}
|
||||
|
||||
const jsonFile = path.join(root, "package.json");
|
||||
const packageString = fs.readFileSync(jsonFile);
|
||||
try {
|
||||
const packageJson = JSON.parse(packageString.toString());
|
||||
if (packageJson) {
|
||||
packageJson.name = appName;
|
||||
}
|
||||
|
||||
fs.writeFileSync(jsonFile, JSON.stringify(packageJson, null, 2) + os.EOL);
|
||||
} catch (e) {
|
||||
console.log("Failed to re-name package.json, continuing...");
|
||||
|
||||
console.log();
|
||||
}
|
||||
|
||||
console.log("Installing packages. This might take a couple of minutes.");
|
||||
console.log();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { hasBaseContract, readBaseContract } from "./base-contracts";
|
||||
import { DownloadError } from "./create-app";
|
||||
import { PackageManager } from "./get-pkg-manager";
|
||||
import { tryGitInit } from "./git";
|
||||
import { hasForge } from "./has-forge";
|
||||
import { install } from "./install";
|
||||
import { isFolderEmpty } from "./is-folder-empty";
|
||||
import { getOnline } from "./is-online";
|
||||
@@ -11,22 +12,28 @@ import { submodules } from "./submodules";
|
||||
import { downloadAndExtractRepo } from "./templates";
|
||||
import retry from "async-retry";
|
||||
import chalk from "chalk";
|
||||
import fs from "fs";
|
||||
import { writeFile } from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
interface ICreateContract {
|
||||
interface ICreateContractProject {
|
||||
contractPath: string;
|
||||
packageManager: PackageManager;
|
||||
contractName: string;
|
||||
framework?: string;
|
||||
baseContract?: string;
|
||||
onlyContract: boolean;
|
||||
}
|
||||
|
||||
export async function createContract({
|
||||
export async function createContractProject({
|
||||
contractPath,
|
||||
packageManager,
|
||||
framework,
|
||||
contractName,
|
||||
baseContract,
|
||||
}: ICreateContract) {
|
||||
onlyContract,
|
||||
}: ICreateContractProject) {
|
||||
// Check that the selected base contract is valid
|
||||
if (baseContract) {
|
||||
const found = hasBaseContract(baseContract);
|
||||
|
||||
@@ -40,8 +47,8 @@ export async function createContract({
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we have write permission for the specified directory
|
||||
const root = path.resolve(contractPath);
|
||||
|
||||
if (!(await isWriteable(path.dirname(root)))) {
|
||||
console.error(
|
||||
"The application path is not writable, please check folder permissions and try again.",
|
||||
@@ -52,111 +59,179 @@ export async function createContract({
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const projectName = path.basename(root);
|
||||
|
||||
await makeDir(root);
|
||||
if (!isFolderEmpty(root, projectName)) {
|
||||
// Check that the contract name is valid
|
||||
if (!/(^[a-z0-9A-Z]+$)|(^[a-z0-9A-Z]+\.sol$)/.test(contractName)) {
|
||||
console.error(
|
||||
`Contract name ${chalk.red(
|
||||
`"${contractName}"`,
|
||||
)} is not valid (only alphanumeric characters are allowed).`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Clean the contract name
|
||||
contractName = contractName.replace(/\.sol$/, "") + ".sol";
|
||||
|
||||
const useYarn = packageManager === "yarn";
|
||||
const isOnline = !useYarn || (await getOnline());
|
||||
const originalDirectory = process.cwd();
|
||||
|
||||
console.log(
|
||||
`Creating a new thirdweb contracts project in ${chalk.green(root)}.`,
|
||||
);
|
||||
console.log();
|
||||
|
||||
process.chdir(root);
|
||||
|
||||
function isErrorLike(err: unknown): err is { message: string } {
|
||||
return (
|
||||
typeof err === "object" &&
|
||||
err !== null &&
|
||||
typeof (err as { message?: unknown }).message === "string"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Downloading files. This might take a moment.`);
|
||||
|
||||
let starter = "";
|
||||
if (framework === "hardhat") {
|
||||
starter = "hardhat-javascript-starter";
|
||||
} else if (framework === "forge") {
|
||||
starter = "forge-starter";
|
||||
} else {
|
||||
console.error("Please provide a valid contracts framework.");
|
||||
if (onlyContract) {
|
||||
// If we are already in a contracts project, just create a new contract
|
||||
if (fs.existsSync(path.join(root, contractName))) {
|
||||
console.error(
|
||||
`A contract with the name ${chalk.red(
|
||||
`"${contractName}"`,
|
||||
)} already exists in specified directory.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await retry(
|
||||
() => downloadAndExtractRepo(root, { name: starter, filePath: "" }),
|
||||
{
|
||||
retries: 3,
|
||||
},
|
||||
);
|
||||
|
||||
// Modify the /contracts/MyContract.sol contents to match the base contract
|
||||
if (baseContract && baseContract.length > 0) {
|
||||
const baseContractText = readBaseContract(baseContract);
|
||||
|
||||
// Set the contents of the Contract.sol file to the base contract
|
||||
let contractFile = "";
|
||||
if (framework === "hardhat") {
|
||||
contractFile = path.join(root, "contracts", "Contract.sol");
|
||||
}
|
||||
if (framework === "forge") {
|
||||
contractFile = path.join(root, "src", "Contract.sol");
|
||||
}
|
||||
contractFile = path.join(root, contractName);
|
||||
|
||||
// Write the base contract to the MyContract.sol file
|
||||
await writeFile(contractFile, baseContractText);
|
||||
|
||||
console.log(
|
||||
`${chalk.green("Success!")} Created ${contractName} at ${contractPath}`,
|
||||
);
|
||||
}
|
||||
} catch (reason) {
|
||||
throw new DownloadError(
|
||||
isErrorLike(reason) ? reason.message : String(reason),
|
||||
);
|
||||
}
|
||||
|
||||
console.log("Installing packages. This might take a couple of minutes.");
|
||||
console.log();
|
||||
|
||||
await install(root, null, { packageManager, isOnline });
|
||||
console.log();
|
||||
|
||||
if (tryGitInit(root)) {
|
||||
console.log("Initialized a git repository.");
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (framework === "forge") {
|
||||
await submodules();
|
||||
}
|
||||
|
||||
let cdpath: string;
|
||||
if (path.join(originalDirectory, projectName) === contractPath) {
|
||||
cdpath = projectName;
|
||||
} else {
|
||||
cdpath = contractPath;
|
||||
}
|
||||
// Otherwise, create a new contracts project
|
||||
const projectName = path.basename(root);
|
||||
|
||||
console.log(
|
||||
`${chalk.green("Success!")} Created ${projectName} at ${contractPath}`,
|
||||
);
|
||||
console.log("Inside that directory, you can run several commands:");
|
||||
console.log();
|
||||
console.log(chalk.cyan(` ${packageManager} ${useYarn ? "" : "run"} deploy`));
|
||||
console.log(" Deploys your contracts with the thirdweb deploy flow.");
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.cyan(` ${packageManager} ${useYarn ? "" : "run"} release`),
|
||||
);
|
||||
console.log(" Releases your contracts with the thirdweb release flow..");
|
||||
console.log();
|
||||
console.log("We suggest that you begin by typing:");
|
||||
console.log();
|
||||
console.log(chalk.cyan(" cd"), cdpath);
|
||||
console.log();
|
||||
await makeDir(root);
|
||||
if (!isFolderEmpty(root, projectName)) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const isOnline = !useYarn || (await getOnline());
|
||||
const originalDirectory = process.cwd();
|
||||
|
||||
console.log(
|
||||
`Creating a new thirdweb contracts project in ${chalk.green(root)}.`,
|
||||
);
|
||||
console.log();
|
||||
|
||||
process.chdir(root);
|
||||
|
||||
function isErrorLike(err: unknown): err is { message: string } {
|
||||
return (
|
||||
typeof err === "object" &&
|
||||
err !== null &&
|
||||
typeof (err as { message?: unknown }).message === "string"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Downloading files. This might take a moment.`);
|
||||
|
||||
let starter = "";
|
||||
if (framework === "hardhat") {
|
||||
starter = "hardhat-javascript-starter";
|
||||
} else if (framework === "forge") {
|
||||
starter = "forge-starter";
|
||||
} else {
|
||||
console.error("Please provide a valid contracts framework.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await retry(
|
||||
() => downloadAndExtractRepo(root, { name: starter, filePath: "" }),
|
||||
{
|
||||
retries: 3,
|
||||
},
|
||||
);
|
||||
|
||||
// Add in a new contracts file with specific base contract
|
||||
if (baseContract && baseContract.length > 0) {
|
||||
const baseContractText = readBaseContract(baseContract);
|
||||
|
||||
// Set the filename of the new file and delete the dummy Contract.sol file
|
||||
let contractFile = "";
|
||||
if (framework === "hardhat") {
|
||||
fs.unlinkSync(path.join(root, "contracts", "Contract.sol"));
|
||||
contractFile = path.join(root, "contracts", contractName);
|
||||
}
|
||||
if (framework === "forge") {
|
||||
fs.unlinkSync(path.join(root, "src", "Contract.sol"));
|
||||
contractFile = path.join(root, "src", contractName);
|
||||
}
|
||||
|
||||
// Write the base contract to the new file
|
||||
await writeFile(contractFile, baseContractText);
|
||||
}
|
||||
} catch (reason) {
|
||||
throw new DownloadError(
|
||||
isErrorLike(reason) ? reason.message : String(reason),
|
||||
);
|
||||
}
|
||||
|
||||
console.log("Installing packages. This might take a couple of minutes.");
|
||||
console.log();
|
||||
|
||||
await install(root, null, { packageManager, isOnline });
|
||||
console.log();
|
||||
|
||||
if (tryGitInit(root)) {
|
||||
console.log("Initialized a git repository.");
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (framework === "forge") {
|
||||
await submodules();
|
||||
}
|
||||
|
||||
let cdpath: string;
|
||||
if (path.join(originalDirectory, projectName) === contractPath) {
|
||||
cdpath = projectName;
|
||||
} else {
|
||||
cdpath = contractPath;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${chalk.green("Success!")} Created ${projectName} at ${contractPath}`,
|
||||
);
|
||||
console.log();
|
||||
console.log("Inside that directory, you can run several commands:");
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.cyan(` ${packageManager}${useYarn ? "" : " run"} build`),
|
||||
);
|
||||
console.log(
|
||||
" Compiles your contracts and detects thirdweb extensions implemented on them.",
|
||||
);
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.cyan(` ${packageManager}${useYarn ? "" : " run"} deploy`),
|
||||
);
|
||||
console.log(" Deploys your contracts with the thirdweb deploy flow.");
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.cyan(` ${packageManager}${useYarn ? "" : " run"} release`),
|
||||
);
|
||||
console.log(" Releases your contracts with the thirdweb release flow.");
|
||||
console.log();
|
||||
console.log("We suggest that you begin by typing:");
|
||||
console.log();
|
||||
console.log(chalk.cyan(" cd"), cdpath);
|
||||
console.log();
|
||||
|
||||
if (framework === "forge") {
|
||||
const isInstalled = await hasForge();
|
||||
|
||||
if (!isInstalled) {
|
||||
console.log(
|
||||
`\n${chalk.yellowBright(
|
||||
`warning`,
|
||||
)} You don't have forge installed on this machine, and will need it to run this project! \n\nYou can install forge by following these instructions:\n${chalk.blueBright(
|
||||
`https://book.getfoundry.sh/getting-started/installation`,
|
||||
)}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export function tryGitInit(root: string): boolean {
|
||||
execSync("git checkout -b main", { stdio: "ignore" });
|
||||
|
||||
execSync("git add -A", { stdio: "ignore" });
|
||||
execSync('git commit -m "Initial commit from Create thirdweb app"', {
|
||||
execSync('git commit -m "Initial commit from npx thirdweb create"', {
|
||||
stdio: "ignore",
|
||||
});
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { exec } from "child_process";
|
||||
|
||||
export async function hasForge() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
return await new Promise((resolve, reject) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
exec("forge --version", (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
resolve(false);
|
||||
}
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,11 +1,26 @@
|
||||
import spawn from "cross-spawn";
|
||||
|
||||
export async function runCommand(command: string, args: string[]) {
|
||||
export async function runCommand(
|
||||
command: string,
|
||||
args: string[],
|
||||
logging: boolean = false,
|
||||
) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
/**
|
||||
* Spawn the installation process.
|
||||
*/
|
||||
const child = spawn(command, args);
|
||||
child.stdout?.on("data", (data) => {
|
||||
if (logging) {
|
||||
console.log(data.toString());
|
||||
}
|
||||
});
|
||||
|
||||
child.stderr?.on("error", (err) => {
|
||||
if (logging) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
child.on("close", (code) => {
|
||||
if (code !== 0) {
|
||||
reject({ command: `${command}` });
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { runCommand } from "./run-command";
|
||||
import { rm, writeFile } from "node:fs/promises";
|
||||
|
||||
/**
|
||||
* Spawn a package manager installation with either Yarn or NPM.
|
||||
@@ -8,12 +9,12 @@ import { runCommand } from "./run-command";
|
||||
*/
|
||||
export async function submodules(): Promise<void> {
|
||||
// Remove script folder because it clashes with contract templates (and not needed with deploy)
|
||||
await runCommand("rm", ["-rf", "script"]);
|
||||
await rm("script", { recursive: true, force: true });
|
||||
|
||||
// Reset and recursively add forge submodules
|
||||
await runCommand("rm", ["-rf", ".gitmodules"]);
|
||||
await runCommand("rm", ["-rf", "lib"]);
|
||||
await runCommand("touch", [".gitmodules"]);
|
||||
await rm(".gitmodules", { recursive: true, force: true });
|
||||
await rm("lib", { recursive: true, force: true });
|
||||
await writeFile(".gitmodules", "");
|
||||
await runCommand("git", [
|
||||
"submodule",
|
||||
"add",
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import NPMDetector from "../core/detection/npm";
|
||||
import ViteDetector from "../core/detection/vite";
|
||||
import YarnDetector from "../core/detection/yarn";
|
||||
import { runCommand } from "../create/helpers/run-command";
|
||||
import { upload } from "../storage/command";
|
||||
import { DEFAULT_IPFS_GATEWAY } from "@thirdweb-dev/sdk";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
|
||||
export async function deployApp(distPath = "dist", projectPath = ".") {
|
||||
const storage = new ThirdwebStorage();
|
||||
const supportedFrameworks = [new ViteDetector()];
|
||||
|
||||
const possibleProjects = supportedFrameworks
|
||||
.filter((detector) => detector.matches(projectPath))
|
||||
.map((detector) => detector.projectType);
|
||||
|
||||
const hasYarn = new YarnDetector().matches(".");
|
||||
const hasNPM = new NPMDetector().matches(".");
|
||||
|
||||
if (possibleProjects.length === 0) {
|
||||
throw new Error("No supported project detected");
|
||||
}
|
||||
|
||||
try {
|
||||
if (hasYarn) {
|
||||
await runCommand("yarn", ["build"]);
|
||||
} else if (hasNPM) {
|
||||
await runCommand("npm", ["build"]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Can't build project");
|
||||
return Promise.reject("Can't build project");
|
||||
}
|
||||
|
||||
try {
|
||||
let url = await upload(storage, distPath);
|
||||
url = url.replace("ipfs://", DEFAULT_IPFS_GATEWAY);
|
||||
return `${url}/index.html`;
|
||||
} catch (err) {
|
||||
console.error("Can't upload project");
|
||||
return Promise.reject("Can't upload project");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { processProject } from "../common/processor";
|
||||
import { info, logger } from "../core/helpers/logger";
|
||||
import generateDashboardUrl from "../helpers/generate-dashboard-url";
|
||||
import { deployApp } from "./app";
|
||||
import chalk from "chalk";
|
||||
|
||||
type DeployOptions = {
|
||||
name: string;
|
||||
contractVersion: string;
|
||||
app: boolean;
|
||||
path?: string;
|
||||
distPath?: string;
|
||||
};
|
||||
|
||||
export async function deploy(options: DeployOptions) {
|
||||
if (options.name) {
|
||||
const url = generateDashboardUrl(options.name, options.contractVersion);
|
||||
|
||||
if (!url) {
|
||||
logger.error(
|
||||
chalk.red(
|
||||
`Could not find a contract named ${options.name} ${
|
||||
options.contractVersion || ""
|
||||
}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
info(`Open this link to deploy your contract: ${chalk.blueBright(url)}`);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
if (options.app) {
|
||||
try {
|
||||
let url = await deployApp(options.distPath, options.path);
|
||||
info(`Here is the link to your app: ${chalk.blueBright(url.toString())}`);
|
||||
return url.toString();
|
||||
} catch (err) {
|
||||
logger.error("Failed to deploy app, No compatible project found", err);
|
||||
return Promise.reject(
|
||||
"failed to deploy app, no compatible project found",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const url = await processProject(options, "deploy");
|
||||
info(
|
||||
`Open this link to deploy your contracts: ${chalk.blueBright(
|
||||
url.toString(),
|
||||
)}`,
|
||||
);
|
||||
return url.toString();
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { exec } from "child_process";
|
||||
|
||||
type PackageManager = "npm" | "yarn" | "pnpm";
|
||||
|
||||
interface Installation {
|
||||
packageManager: PackageManager;
|
||||
isGlobal: boolean;
|
||||
}
|
||||
|
||||
export async function findPackageInstallation(): Promise<
|
||||
Installation | undefined
|
||||
> {
|
||||
const isLocal = await isInstalledLocally();
|
||||
if (isLocal) {
|
||||
const packageManager = await import("detect-package-manager").then(
|
||||
({ detect }) => {
|
||||
return detect();
|
||||
},
|
||||
);
|
||||
|
||||
return { packageManager, isGlobal: false };
|
||||
}
|
||||
|
||||
const isGlobalNpm = await isInstalledGloballyWithNpm();
|
||||
if (isGlobalNpm) {
|
||||
return { packageManager: "npm", isGlobal: true };
|
||||
}
|
||||
|
||||
const isGlobalYarn = await isInstalledGloballyWithYarn();
|
||||
if (isGlobalYarn) {
|
||||
return { packageManager: "yarn", isGlobal: true };
|
||||
}
|
||||
|
||||
const isGlobalPnpm = await isInstalledGloballyWithPnpm();
|
||||
if (isGlobalPnpm) {
|
||||
return { packageManager: "pnpm", isGlobal: true };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// For LOCAL packages, npm ls picks up things installed by yarn and pnpm too
|
||||
async function isInstalledLocally(): Promise<boolean> {
|
||||
const packages = await detectPackages("npm ls --depth=0");
|
||||
return containsThirdweb(packages || []);
|
||||
}
|
||||
|
||||
// But for GLOBAL packages, we need to use a separate command for each one
|
||||
async function isInstalledGloballyWithNpm(): Promise<boolean> {
|
||||
const packages = await detectPackages("npm ls -g --depth=0");
|
||||
return containsThirdweb(packages || []);
|
||||
}
|
||||
|
||||
async function isInstalledGloballyWithYarn(): Promise<boolean> {
|
||||
const packages = await detectPackages("yarn --cwd `yarn global dir` list");
|
||||
return containsThirdweb(packages || []);
|
||||
}
|
||||
|
||||
async function isInstalledGloballyWithPnpm(): Promise<boolean> {
|
||||
const packages: string[] = await new Promise((resolve) => {
|
||||
exec(`pnpm list -g --depth=0`, (err, stdout) => {
|
||||
var pkgs: string[] = [];
|
||||
pkgs = stdout.split("dependencies:")[1]?.trim().split("\n") || [];
|
||||
pkgs = pkgs.map((pkg) => pkg.replace(" ", "@"));
|
||||
resolve(pkgs);
|
||||
});
|
||||
});
|
||||
return containsThirdweb(packages || []);
|
||||
}
|
||||
|
||||
function containsThirdweb(packages: string[]) {
|
||||
return packages.some((pkg) => pkg.match(/^thirdweb/));
|
||||
}
|
||||
|
||||
async function detectPackages(cmd: string): Promise<string[]> {
|
||||
return new Promise((resolve) => {
|
||||
exec(cmd, (err, stdout) => {
|
||||
var packages: string[] = [];
|
||||
packages = stdout.split("\n");
|
||||
// We're one short on '-' to support both yarn and npm
|
||||
packages = packages.filter(function (item) {
|
||||
if (item.match(/^├─.+/g) !== null) {
|
||||
return true;
|
||||
}
|
||||
if (item.match(/^└─.+/g) !== null) {
|
||||
return true;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
packages = packages
|
||||
.map(function (item) {
|
||||
if (item.match(/^├─.+/g) !== null) {
|
||||
return item.replace(/(^├──\s|^├─\s)/g, "");
|
||||
}
|
||||
if (item.match(/^└─.+/g) !== null) {
|
||||
return item.replace(/(^└──\s|^├─\s)/g, "");
|
||||
}
|
||||
})
|
||||
.filter((item) => !!item) as string[];
|
||||
resolve(packages);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -13,18 +13,18 @@ export default function generateDashboardUrl(
|
||||
// Pre-built contract
|
||||
else {
|
||||
const mapping = {
|
||||
"signature-drop": "drop/signature-drop",
|
||||
"nft-drop": "drop/nft-drop",
|
||||
"edition-drop": "drop/edition-drop",
|
||||
"nft-collection": "token/nft-collection",
|
||||
edition: "token/edition",
|
||||
"token-drop": "drop/token-drop",
|
||||
token: "token/token",
|
||||
marketplace: "marketplace/marketplace",
|
||||
split: "governance/split",
|
||||
vote: "governance/vote",
|
||||
pack: "token/pack",
|
||||
multiwrap: "token/multiwrap",
|
||||
"signature-drop": "SignatureDrop",
|
||||
"nft-drop": "DropERC721",
|
||||
"edition-drop": "DropERC1155",
|
||||
"nft-collection": "TokenERC721",
|
||||
edition: "TokenERC1155",
|
||||
"token-drop": "DropERC20",
|
||||
token: "TokenERC20",
|
||||
marketplace: "Marketplace",
|
||||
split: "Split",
|
||||
vote: "VoteERC20",
|
||||
pack: "Pack",
|
||||
multiwrap: "Multiwrap",
|
||||
};
|
||||
|
||||
const foundMapping = mapping[name as keyof typeof mapping];
|
||||
@@ -33,6 +33,6 @@ export default function generateDashboardUrl(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return `https://thirdweb.com/contracts/new/pre-built/${foundMapping}`;
|
||||
return `https://thirdweb.com/thirdweb.eth/${foundMapping}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { spinner } from "../core/helpers/logger";
|
||||
import { BufferOrStringWithName, ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
function recurseFiles(
|
||||
uploadPath: string,
|
||||
basePath: string,
|
||||
): BufferOrStringWithName[] {
|
||||
const files = [];
|
||||
|
||||
const fileType = fs.lstatSync(uploadPath);
|
||||
if (fileType.isFile()) {
|
||||
const fileName = path.relative(basePath, uploadPath);
|
||||
const fileData = fs.readFileSync(uploadPath);
|
||||
files.push({ name: fileName, data: fileData });
|
||||
} else if (fileType.isDirectory()) {
|
||||
const paths = fs.readdirSync(uploadPath);
|
||||
paths.forEach((subPath) => {
|
||||
const fullSubPath = path.join(uploadPath, subPath);
|
||||
const subFiles = recurseFiles(fullSubPath, basePath);
|
||||
subFiles.forEach((file) => files.push(file));
|
||||
});
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
export async function upload(
|
||||
storage: ThirdwebStorage,
|
||||
uploadPath: string,
|
||||
): Promise<string> {
|
||||
if (!uploadPath) {
|
||||
return Promise.reject("No path provided");
|
||||
}
|
||||
|
||||
const pathExists = fs.existsSync(uploadPath);
|
||||
if (!pathExists) {
|
||||
return Promise.reject("Invalid path provided");
|
||||
}
|
||||
|
||||
let uri = "";
|
||||
const fileType = fs.lstatSync(uploadPath);
|
||||
if (fileType.isDirectory()) {
|
||||
const files = recurseFiles(uploadPath, uploadPath);
|
||||
|
||||
if (files.length === 0) {
|
||||
return Promise.reject("No files detected in specified directory");
|
||||
}
|
||||
|
||||
const spin = spinner(
|
||||
"Uploading directory to IPFS. This may take a while depending on file sizes.",
|
||||
);
|
||||
const uris = await storage.uploadBatch(files);
|
||||
spin.succeed("Successfully uploaded directory to IPFS");
|
||||
uri = uris[0].substring(0, uris[0].lastIndexOf("/"));
|
||||
} else if (fileType.isFile()) {
|
||||
const fileName = path.parse(uploadPath).base;
|
||||
const fileData = fs.readFileSync(uploadPath);
|
||||
const file = { name: fileName, data: fileData };
|
||||
|
||||
const spin = spinner(
|
||||
"Uploading file to IPFS. This may take a while depending on file sizes.",
|
||||
);
|
||||
uri = await storage.upload(file, { uploadWithoutDirectory: true });
|
||||
spin.succeed("Succesfully uploaded file to IPFS.");
|
||||
} else {
|
||||
return Promise.reject("Invalid path provided");
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
src/
|
||||
@@ -1,5 +1,59 @@
|
||||
# @thirdweb-dev/contracts-js
|
||||
|
||||
## 1.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#553](https://github.com/thirdweb-dev/js/pull/553) [`76d219e`](https://github.com/thirdweb-dev/js/commit/76d219e6bea1496b45623b3081152854ce1eaa6e) Thanks [@jnsdls](https://github.com/jnsdls)! - force release
|
||||
|
||||
## 1.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#474](https://github.com/thirdweb-dev/js/pull/474) [`1686fb4`](https://github.com/thirdweb-dev/js/commit/1686fb4b2c0d93004623bc02fcb0e32233fe582c) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Update contracts to 3.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#549](https://github.com/thirdweb-dev/js/pull/549) [`912738b`](https://github.com/thirdweb-dev/js/commit/912738bd0afdf81e118c720811911ba3d1979ac0) Thanks [@kumaryash90](https://github.com/kumaryash90)! - add staking bases to CLI
|
||||
|
||||
- [#544](https://github.com/thirdweb-dev/js/pull/544) [`f1aecc3`](https://github.com/thirdweb-dev/js/commit/f1aecc30499e39fb23a205bade939bd939f0d0e4) Thanks [@kumaryash90](https://github.com/kumaryash90)! - Contracts v3.4.0
|
||||
|
||||
## 1.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#463](https://github.com/thirdweb-dev/js/pull/463) [`557429b`](https://github.com/thirdweb-dev/js/commit/557429b5cfb3af2983ee01cf7d12d41ee0557593) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - PackVRF contract
|
||||
|
||||
- [#311](https://github.com/thirdweb-dev/js/pull/311) [`9eaa21d`](https://github.com/thirdweb-dev/js/commit/9eaa21d09ab9c700aea61a2a25f8ca9859d20857) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update to v3.3.0 of contracts package
|
||||
|
||||
- [#311](https://github.com/thirdweb-dev/js/pull/311) [`9eaa21d`](https://github.com/thirdweb-dev/js/commit/9eaa21d09ab9c700aea61a2a25f8ca9859d20857) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update to 3.1.10 of contracts package
|
||||
|
||||
## 1.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#438](https://github.com/thirdweb-dev/js/pull/438) [`f451da6`](https://github.com/thirdweb-dev/js/commit/f451da6395689a5f89800ee63f34b6175b61f703) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix SigMint detection for OSRF NFT Collection contracts
|
||||
|
||||
## 1.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#376](https://github.com/thirdweb-dev/js/pull/376) [`4af785c`](https://github.com/thirdweb-dev/js/commit/4af785c37c831ecd7087c1c9eb9095922cac0855) Thanks [@adam-maj](https://github.com/adam-maj)! - Update tiered-drop with tokens claimed
|
||||
|
||||
- [#373](https://github.com/thirdweb-dev/js/pull/373) [`6f818f3`](https://github.com/thirdweb-dev/js/commit/6f818f393eb9023b53a3358d62d60ec23a9246bd) Thanks [@adam-maj](https://github.com/adam-maj)! - Update tiered-drop contract version
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#333](https://github.com/thirdweb-dev/js/pull/333) [`aa6d9ed`](https://github.com/thirdweb-dev/js/commit/aa6d9ed75d126d7a0cca9fb8fc389ff94e9d1e14) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update to contracts package v3.2.0
|
||||
|
||||
## 1.1.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#226](https://github.com/thirdweb-dev/js/pull/226) [`f2a6211`](https://github.com/thirdweb-dev/js/commit/f2a62110c43e7b8f35c86a197730e732f8fcc786) Thanks [@jnsdls](https://github.com/jnsdls)! - expose some factories as separate exports
|
||||
|
||||
## 1.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-contracts-js-factories-TWFactory__factory.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js-factories-TWFactory__factory.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-contracts-js-factories-TWFee__factory.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js-factories-TWFee__factory.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-contracts-js-factories-TWMultichainRegistryLogic__factory.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js-factories-TWMultichainRegistryLogic__factory.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-contracts-js-factories-TWMultichainRegistryRouter__factory.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js-factories-TWMultichainRegistryRouter__factory.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-contracts-js-factories-TWMultichainRegistryStorage__factory.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js-factories-TWMultichainRegistryStorage__factory.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-contracts-js-factories-TWMultichainRegistry__factory.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js-factories-TWMultichainRegistry__factory.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-contracts-js-factories-TWProxy__factory.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js-factories-TWProxy__factory.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-contracts-js-factories-TWRegistryLegacy__factory.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js-factories-TWRegistryLegacy__factory.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-contracts-js-factories-TWRegistry__factory.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js-factories-TWRegistry__factory.esm.js"
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/contracts-js",
|
||||
"version": "1.1.8",
|
||||
"version": "1.3.1",
|
||||
"main": "dist/thirdweb-dev-contracts-js.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js.esm.js",
|
||||
"license": "Apache-2.0",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/contracts-js",
|
||||
"dependencies": {
|
||||
"@thirdweb-dev/contracts": "3.1.8-0"
|
||||
"@thirdweb-dev/contracts": "3.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@preconstruct/cli": "^2.2.1",
|
||||
"ethers": "^5.7.0",
|
||||
"@typechain/ethers-v5": "10.0.0",
|
||||
"ethers": "^5.7.2",
|
||||
"typechain": "^8.1.0",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
@@ -20,15 +21,20 @@
|
||||
"ethers": "^5.7.0"
|
||||
},
|
||||
"files": [
|
||||
"dist/"
|
||||
"dist/",
|
||||
"factories/"
|
||||
],
|
||||
"preconstruct": {
|
||||
"entrypoints": [
|
||||
"index.ts",
|
||||
"./factories/TW*.ts"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"format": "prettier --write 'src/**/*'",
|
||||
"lint": "eslint src/",
|
||||
"fix": "eslint src/ --fix",
|
||||
"clean": "rm -rf dist/",
|
||||
"typechain": "rm -rf ./src && typechain --target ethers-v5 --out-dir src '../../node_modules/@thirdweb-dev/contracts/abi/*.json'",
|
||||
"extract-abi": "node ./scripts/extract-abis.mjs",
|
||||
"build": "yarn typechain && tsc && yarn preconstruct build && yarn extract-abi"
|
||||
"build": "yarn typechain && tsc && yarn preconstruct build && yarn extract-abi",
|
||||
"push": "yalc push"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/* Autogenerated file. Do not edit manually. */
|
||||
|
||||
/* tslint:disable */
|
||||
|
||||
/* eslint-disable */
|
||||
import type {
|
||||
TypedEventFilter,
|
||||
TypedEvent,
|
||||
TypedListener,
|
||||
OnEvent,
|
||||
} from "./common";
|
||||
import type {
|
||||
FunctionFragment,
|
||||
Result,
|
||||
EventFragment,
|
||||
} from "@ethersproject/abi";
|
||||
import type { Listener, Provider } from "@ethersproject/providers";
|
||||
import type {
|
||||
BaseContract,
|
||||
BigNumber,
|
||||
BytesLike,
|
||||
CallOverrides,
|
||||
ContractTransaction,
|
||||
Overrides,
|
||||
PopulatedTransaction,
|
||||
Signer,
|
||||
utils,
|
||||
} from "ethers";
|
||||
|
||||
export interface AppURIInterface extends utils.Interface {
|
||||
functions: {
|
||||
"appURI()": FunctionFragment;
|
||||
"setAppURI(string)": FunctionFragment;
|
||||
};
|
||||
|
||||
getFunction(nameOrSignatureOrTopic: "appURI" | "setAppURI"): FunctionFragment;
|
||||
|
||||
encodeFunctionData(functionFragment: "appURI", values?: undefined): string;
|
||||
encodeFunctionData(functionFragment: "setAppURI", values: [string]): string;
|
||||
|
||||
decodeFunctionResult(functionFragment: "appURI", data: BytesLike): Result;
|
||||
decodeFunctionResult(functionFragment: "setAppURI", data: BytesLike): Result;
|
||||
|
||||
events: {
|
||||
"AppURIUpdated(string,string)": EventFragment;
|
||||
};
|
||||
|
||||
getEvent(nameOrSignatureOrTopic: "AppURIUpdated"): EventFragment;
|
||||
}
|
||||
|
||||
export interface AppURIUpdatedEventObject {
|
||||
prevURI: string;
|
||||
newURI: string;
|
||||
}
|
||||
export type AppURIUpdatedEvent = TypedEvent<
|
||||
[string, string],
|
||||
AppURIUpdatedEventObject
|
||||
>;
|
||||
|
||||
export type AppURIUpdatedEventFilter = TypedEventFilter<AppURIUpdatedEvent>;
|
||||
|
||||
export interface AppURI extends BaseContract {
|
||||
connect(signerOrProvider: Signer | Provider | string): this;
|
||||
attach(addressOrName: string): this;
|
||||
deployed(): Promise<this>;
|
||||
|
||||
interface: AppURIInterface;
|
||||
|
||||
queryFilter<TEvent extends TypedEvent>(
|
||||
event: TypedEventFilter<TEvent>,
|
||||
fromBlockOrBlockhash?: string | number | undefined,
|
||||
toBlock?: string | number | undefined
|
||||
): Promise<Array<TEvent>>;
|
||||
|
||||
listeners<TEvent extends TypedEvent>(
|
||||
eventFilter?: TypedEventFilter<TEvent>
|
||||
): Array<TypedListener<TEvent>>;
|
||||
listeners(eventName?: string): Array<Listener>;
|
||||
removeAllListeners<TEvent extends TypedEvent>(
|
||||
eventFilter: TypedEventFilter<TEvent>
|
||||
): this;
|
||||
removeAllListeners(eventName?: string): this;
|
||||
off: OnEvent<this>;
|
||||
on: OnEvent<this>;
|
||||
once: OnEvent<this>;
|
||||
removeListener: OnEvent<this>;
|
||||
|
||||
functions: {
|
||||
appURI(overrides?: CallOverrides): Promise<[string]>;
|
||||
|
||||
setAppURI(
|
||||
_uri: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
};
|
||||
|
||||
appURI(overrides?: CallOverrides): Promise<string>;
|
||||
|
||||
setAppURI(
|
||||
_uri: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
callStatic: {
|
||||
appURI(overrides?: CallOverrides): Promise<string>;
|
||||
|
||||
setAppURI(_uri: string, overrides?: CallOverrides): Promise<void>;
|
||||
};
|
||||
|
||||
filters: {
|
||||
"AppURIUpdated(string,string)"(
|
||||
prevURI?: null,
|
||||
newURI?: null
|
||||
): AppURIUpdatedEventFilter;
|
||||
AppURIUpdated(prevURI?: null, newURI?: null): AppURIUpdatedEventFilter;
|
||||
};
|
||||
|
||||
estimateGas: {
|
||||
appURI(overrides?: CallOverrides): Promise<BigNumber>;
|
||||
|
||||
setAppURI(
|
||||
_uri: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<BigNumber>;
|
||||
};
|
||||
|
||||
populateTransaction: {
|
||||
appURI(overrides?: CallOverrides): Promise<PopulatedTransaction>;
|
||||
|
||||
setAppURI(
|
||||
_uri: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<PopulatedTransaction>;
|
||||
};
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/* Autogenerated file. Do not edit manually. */
|
||||
|
||||
/* tslint:disable */
|
||||
|
||||
/* eslint-disable */
|
||||
import type {
|
||||
TypedEventFilter,
|
||||
TypedEvent,
|
||||
TypedListener,
|
||||
OnEvent,
|
||||
} from "./common";
|
||||
import type { FunctionFragment, Result } from "@ethersproject/abi";
|
||||
import type { Listener, Provider } from "@ethersproject/providers";
|
||||
import type {
|
||||
BaseContract,
|
||||
BigNumber,
|
||||
BigNumberish,
|
||||
BytesLike,
|
||||
CallOverrides,
|
||||
PopulatedTransaction,
|
||||
Signer,
|
||||
utils,
|
||||
} from "ethers";
|
||||
|
||||
export interface BatchMintMetadataInterface extends utils.Interface {
|
||||
functions: {
|
||||
"getBaseURICount()": FunctionFragment;
|
||||
"getBatchIdAtIndex(uint256)": FunctionFragment;
|
||||
};
|
||||
|
||||
getFunction(
|
||||
nameOrSignatureOrTopic: "getBaseURICount" | "getBatchIdAtIndex"
|
||||
): FunctionFragment;
|
||||
|
||||
encodeFunctionData(
|
||||
functionFragment: "getBaseURICount",
|
||||
values?: undefined
|
||||
): string;
|
||||
encodeFunctionData(
|
||||
functionFragment: "getBatchIdAtIndex",
|
||||
values: [BigNumberish]
|
||||
): string;
|
||||
|
||||
decodeFunctionResult(
|
||||
functionFragment: "getBaseURICount",
|
||||
data: BytesLike
|
||||
): Result;
|
||||
decodeFunctionResult(
|
||||
functionFragment: "getBatchIdAtIndex",
|
||||
data: BytesLike
|
||||
): Result;
|
||||
|
||||
events: {};
|
||||
}
|
||||
|
||||
export interface BatchMintMetadata extends BaseContract {
|
||||
connect(signerOrProvider: Signer | Provider | string): this;
|
||||
attach(addressOrName: string): this;
|
||||
deployed(): Promise<this>;
|
||||
|
||||
interface: BatchMintMetadataInterface;
|
||||
|
||||
queryFilter<TEvent extends TypedEvent>(
|
||||
event: TypedEventFilter<TEvent>,
|
||||
fromBlockOrBlockhash?: string | number | undefined,
|
||||
toBlock?: string | number | undefined
|
||||
): Promise<Array<TEvent>>;
|
||||
|
||||
listeners<TEvent extends TypedEvent>(
|
||||
eventFilter?: TypedEventFilter<TEvent>
|
||||
): Array<TypedListener<TEvent>>;
|
||||
listeners(eventName?: string): Array<Listener>;
|
||||
removeAllListeners<TEvent extends TypedEvent>(
|
||||
eventFilter: TypedEventFilter<TEvent>
|
||||
): this;
|
||||
removeAllListeners(eventName?: string): this;
|
||||
off: OnEvent<this>;
|
||||
on: OnEvent<this>;
|
||||
once: OnEvent<this>;
|
||||
removeListener: OnEvent<this>;
|
||||
|
||||
functions: {
|
||||
getBaseURICount(overrides?: CallOverrides): Promise<[BigNumber]>;
|
||||
|
||||
getBatchIdAtIndex(
|
||||
_index: BigNumberish,
|
||||
overrides?: CallOverrides
|
||||
): Promise<[BigNumber]>;
|
||||
};
|
||||
|
||||
getBaseURICount(overrides?: CallOverrides): Promise<BigNumber>;
|
||||
|
||||
getBatchIdAtIndex(
|
||||
_index: BigNumberish,
|
||||
overrides?: CallOverrides
|
||||
): Promise<BigNumber>;
|
||||
|
||||
callStatic: {
|
||||
getBaseURICount(overrides?: CallOverrides): Promise<BigNumber>;
|
||||
|
||||
getBatchIdAtIndex(
|
||||
_index: BigNumberish,
|
||||
overrides?: CallOverrides
|
||||
): Promise<BigNumber>;
|
||||
};
|
||||
|
||||
filters: {};
|
||||
|
||||
estimateGas: {
|
||||
getBaseURICount(overrides?: CallOverrides): Promise<BigNumber>;
|
||||
|
||||
getBatchIdAtIndex(
|
||||
_index: BigNumberish,
|
||||
overrides?: CallOverrides
|
||||
): Promise<BigNumber>;
|
||||
};
|
||||
|
||||
populateTransaction: {
|
||||
getBaseURICount(overrides?: CallOverrides): Promise<PopulatedTransaction>;
|
||||
|
||||
getBatchIdAtIndex(
|
||||
_index: BigNumberish,
|
||||
overrides?: CallOverrides
|
||||
): Promise<PopulatedTransaction>;
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user