Compare commits

..
Author SHA1 Message Date
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
ed6c54f20d Version Packages (#336)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-21 13:26:24 -07:00
Joaquim VergesandGitHub 26116a6f63 [SDK] Update to new univeral RPC endpoint (#342) 2022-10-21 13:21:11 -07:00
Jonas DanielsandGitHub f11a2719ff [workflow] github hosted runners (#340) 2022-10-20 22:02:58 -07:00
Jonas DanielsandGitHub ef27aad0aa [Various, React] - add initial e2e testing setup (#327) 2022-10-21 04:19:04 +00:00
Nacho IacovinoandGitHub b03a902145 [React] Docs update (#339) 2022-10-20 20:17:01 -07:00
Adam MajmudarandGitHub 340605b507 Clean native token address in changeset (#337) 2022-10-20 23:57:55 +00:00
Adam MajmudarandGitHub 15f8006e1f Export smart contract (#335) 2022-10-20 23:46:02 +00:00
Adam MajmudarandGitHub 811c5a1166 Update code snippets for signature minting (#334) 2022-10-20 23:29:07 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
c41a80d86d Version Packages (#331)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-20 15:35:12 -07:00
Joaquim VergesandGitHub ad851f7b4d [SDK] Fix for finding addresses in legacy merkle tree format (#332) 2022-10-20 21:35:52 +00:00
Joaquim VergesandGitHub 6218c88584 [SDK] Fix for deploy byte parameters (#330) 2022-10-20 04:47:55 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
c9b564feaf Version Packages (#323)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-19 21:19:06 -07:00
Adam MajmudarandGitHub 87fd6ab14e Update storage download on fail (#324) 2022-10-19 12:44:36 -07:00
Joaquim VergesandGitHub df74340fa5 [SDK] Import cbor-x fully for es builds (#328) 2022-10-19 17:46:54 +00:00
Joaquim VergesandGitHub a57b4f01f3 [SDK] Implement sharded merkle trees for lightweight allowlist checks (#321) 2022-10-19 16:54:09 +00:00
Jonas DanielsandGitHub 126752d7d0 [Various] - clean up extraneous dependencies (#326) 2022-10-18 23:56:04 -07:00
89ff921a7d Fix vite (#315)
Co-authored-by: Jonas Daniels <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
2022-10-18 15:23:57 -07:00
113 changed files with 6920 additions and 3336 deletions
+2 -2
View File
@@ -9,10 +9,10 @@ runs:
with:
node-version: 16
registry-url: "https://registry.npmjs.org"
# cache: "yarn"
cache: "yarn"
- name: Install dependencies
shell: bash
run: yarn install --frozen-lockfile
run: yarn install
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
+1 -2
View File
@@ -14,8 +14,7 @@ jobs:
build:
name: Build and Test
# timeout-minutes: 15
# try out buildjet to speed up build and test
runs-on: buildjet-32vcpu-ubuntu-2204
runs-on: ubuntu-latest-32
# To use Remote Caching, uncomment the next lines and follow the steps below.
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
+1 -8
View File
@@ -13,8 +13,7 @@ concurrency:
jobs:
integration-tests:
timeout-minutes: 60
# try out buildjet to speed up build and test
runs-on: buildjet-32vcpu-ubuntu-2204
runs-on: ubuntu-latest-32
steps:
- uses: actions/checkout@v3
@@ -28,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
+1 -2
View File
@@ -40,8 +40,7 @@ jobs:
comment_body: ${{ github.event.comment.body }}
deploy:
# try out buildjet to speed up build and test
runs-on: buildjet-32vcpu-ubuntu-2204
runs-on: ubuntu-latest-32
needs: deploy-check
if: needs.deploy-check.outputs.triggered == 'true'
steps:
+1 -2
View File
@@ -14,8 +14,7 @@ concurrency:
jobs:
release:
name: Release
# try out buildjet to speed up build and test
runs-on: buildjet-32vcpu-ubuntu-2204
runs-on: ubuntu-latest-32
steps:
- name: Checkout branch
uses: actions/checkout@v3
+1 -2
View File
@@ -4,8 +4,7 @@ on: [pull_request]
jobs:
build:
# try out buildjet to speed up build and test
runs-on: buildjet-32vcpu-ubuntu-2204
runs-on: ubuntu-latest-32
steps:
- uses: actions/checkout@v3
+5 -1
View File
@@ -12,4 +12,8 @@ server/dist
public/dist
.turbo
./packages/contracts-js/abis
.idea/
.idea/
.yalc/
yalc.lock
build/
playwright-report/
-1
View File
@@ -1,4 +1,3 @@
lib
node_modules
dist
contracts-js
+2 -6
View File
@@ -8,7 +8,8 @@
"private": true,
"workspaces": {
"packages": [
"packages/*"
"packages/*",
"test/**/*"
]
},
"scripts": {
@@ -29,11 +30,6 @@
"release:nightly": "node ./scripts/pre-release.mjs && changeset publish --tag nightly",
"push": "turbo run push"
},
"preconstruct": {
"packages": [
"packages/*"
]
},
"packageManager": "[email protected]",
"dependencies": {
"@changesets/changelog-github": "^0.4.6",
+10
View File
@@ -1,5 +1,15 @@
# @thirdweb-dev/auth
## 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
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/auth",
"version": "2.0.18",
"version": "2.0.21",
"exports": {
"./next": {
"module": "./next/dist/thirdweb-dev-auth-next.esm.js",
+25
View File
@@ -1,5 +1,30 @@
# thirdweb
## 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
+1 -8
View File
@@ -1,7 +1,7 @@
{
"name": "thirdweb",
"main": "dist/cli/index.js",
"version": "0.8.4",
"version": "0.8.7",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -31,24 +31,17 @@
"dependencies": {
"@thirdweb-dev/sdk": "*",
"async-retry": "^1.3.3",
"cbor": "^8.1.0",
"commander": "^9.1.0",
"cpy": "^9.0.1",
"enquirer": "^2.3.6",
"ethers": "^5.7.0",
"form-data": "^4.0.0",
"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",
"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"
+3 -3
View File
@@ -40,7 +40,7 @@ $$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$
.version(cliVersion, "-v, --version");
program
.command("create")
.command("create [projectPath]")
.description(
"Create a web3 app from any of our official templates: https://github.com/thirdweb-example/",
)
@@ -66,8 +66,8 @@ $$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$
"-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);
.action(async (path, options) => {
await twCreate(path, options);
});
program
@@ -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(
-66
View File
@@ -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())
);
}
+3 -3
View File
@@ -17,9 +17,9 @@ let language: string = "";
let baseContract: string = "";
/* let createType: string = "app"; */
export async function twCreate(options: any) {
if (typeof projectPath === "string") {
projectPath = projectPath.trim();
export async function twCreate(pPath: string = "", options: any) {
if (typeof pPath === "string") {
projectPath = pPath;
}
if (options.app) {
+46
View File
@@ -1,5 +1,51 @@
# @thirdweb-dev/react
## 3.4.0
### Minor 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)! - Gnosis Safe and Magic Link connectors are no longer included in the default export. They are now available as named exports instead.
## Gnosis Safe
### Connector
```diff
- import { GnosisSafeConnector } from "@thirdweb-dev/react";
+ import { GnosisSafeConnector } from "@thirdweb-dev/react/evm/connectors/gnosis-safe";
```
### Hook
```diff
- import { useGnosis } from "@thirdweb-dev/react";
+ import { useGnosis } from "@thirdweb-dev/react/evm/connectors/gnosis-safe";
```
## Magic Link
### Connector
```diff
- import { MagicLink } from "@thirdweb-dev/react";
+ import { MagicConnector } from "@thirdweb-dev/react/evm/connectors/magic";
```
### Hook
```diff
- import { useMagic } from "@thirdweb-dev/react";
+ import { useMagic } from "@thirdweb-dev/react/evm/connectors/magic";
```
### Patch Changes
- [#339](https://github.com/thirdweb-dev/js/pull/339) [`b03a902`](https://github.com/thirdweb-dev/js/commit/b03a9021451b79f802f682f66e5ae8e9355d7e6f) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Fix docs for some functions
## 3.3.1
## 3.3.0
## 3.2.6
### Patch Changes
+2 -2
View File
@@ -26,11 +26,11 @@ The `useContractCall` hook to call a smart contract function has been renamed to
### Prebuilt Contract Hooks
Hooks to connect to prebuilt contracts such as `useNFTDrop` and `useMarketplace` are now deprecated in favour of using the generic `useContract` hook:
Hooks to connect to prebuilt contracts such as `useNFTDrop` and `useMarketplace` are now deprecated in favour of using the generic `useContract` hook followed by the contract type:
```diff
- const contract = useNFTDrop("0x...")
+ const { contract } = useContract("0x...")
+ const { contract } = useContract("0x...", "nft-drop")
```
You can currently still continue to use the prebuilt hooks, however they provide a warning in the console informing you of their deprecation. We will likely remove these hooks in a future version of the SDK.
+1 -1
View File
@@ -191,7 +191,7 @@ export default function Home() {
<div>
{nfts.map((nft) => (
<ThirdwebNftMedia
key={nft.tokenId}
key={nft.metadata.id}
metadata={nft.metadata}
height={200}
/>
@@ -0,0 +1,4 @@
{
"main": "dist/thirdweb-dev-react-evm-connectors-gnosis-safe.cjs.js",
"module": "dist/thirdweb-dev-react-evm-connectors-gnosis-safe.esm.js"
}
@@ -0,0 +1,4 @@
{
"main": "dist/thirdweb-dev-react-evm-connectors-magic.cjs.js",
"module": "dist/thirdweb-dev-react-evm-connectors-magic.esm.js"
}
+19 -18
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react",
"version": "3.2.6",
"version": "3.4.0",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -20,6 +20,14 @@
"module": "./solana/dist/thirdweb-dev-react-solana.esm.js",
"default": "./solana/dist/thirdweb-dev-react-solana.cjs.js"
},
"./evm/connectors/magic": {
"module": "./evm/connectors/magic/dist/thirdweb-dev-react-evm-connectors-magic.esm.js",
"default": "./evm/connectors/magic/dist/thirdweb-dev-react-evm-connectors-magic.cjs.js"
},
"./evm/connectors/gnosis-safe": {
"module": "./evm/connectors/gnosis-safe/dist/thirdweb-dev-react-evm-connectors-gnosis-safe.esm.js",
"default": "./evm/connectors/gnosis-safe/dist/thirdweb-dev-react-evm-connectors-gnosis-safe.cjs.js"
},
"./package.json": "./package.json"
},
"files": [
@@ -31,7 +39,8 @@
"entrypoints": [
"index.ts",
"solana/index.ts",
"evm/index.ts"
"evm/index.ts",
"evm/connectors/**.ts"
],
"___experimentalFlags_WILL_CHANGE_IN_PATCH": {
"exports": true
@@ -49,8 +58,6 @@
"generate-docs:solana": "api-extractor run --local --config ./config/api-extractor-solana.json && api-documenter markdown -i ./temp -o ./docs/solana && rm -rf ./temp-solana && mv ./temp ./temp-solana",
"generate-snippets": "node ./scripts/generate-snippets-evm.mjs && node ./scripts/generate-snippets-solana.mjs && node ./scripts/generate-feature-snippets-evm.mjs",
"build": "tsc && preconstruct build",
"e2e": "yarn test-ct",
"test-ct": "playwright test -c playwright-ct.config.ts",
"push": "yalc push"
},
"sideEffects:": false,
@@ -63,15 +70,11 @@
"@microsoft/api-documenter": "^7.19.4",
"@microsoft/api-extractor": "^7.29.2",
"@microsoft/tsdoc": "^0.14.1",
"@playwright/experimental-ct-react": "1.25.1",
"@playwright/test": "1.25.1",
"@preconstruct/cli": "^2.2.1",
"@solana/wallet-adapter-base": "^0.9.17",
"@solana/wallet-adapter-react": "^0.15.19",
"@solana/wallet-adapter-wallets": "^0.19.0",
"@solana/web3.js": "^1.63.0",
"@solana/web3.js": "^1.62.0",
"@thirdweb-dev/sdk": "*",
"@thirdweb-dev/solana": "*",
"@types/color": "^3.0.3",
"@types/mime": "^3.0.1",
"@types/react": "^18.0.5",
@@ -93,13 +96,14 @@
"dependencies": {
"@emotion/react": "^11.10.0",
"@emotion/styled": "^11.10.0",
"@gnosis.pm/safe-core-sdk": "2.1.0",
"@gnosis.pm/safe-ethers-adapters": "0.1.0-alpha.9",
"@gnosis.pm/safe-ethers-lib": "1.1.0",
"@gnosis.pm/safe-core-sdk": "^3.1.1",
"@gnosis.pm/safe-ethers-adapters": "0.1.0-alpha.11",
"@gnosis.pm/safe-ethers-lib": "^1.6.1",
"@react-icons/all-files": "^4.1.0",
"@solana/wallet-adapter-phantom": "^0.9.17",
"@tanstack/react-query": "^4.0.10",
"@zag-js/menu": "0.1.14",
"@zag-js/react": "0.1.16",
"@zag-js/menu": "^0.1.16",
"@zag-js/react": "^0.1.18",
"buffer": "^6.0.3",
"color": "^4.2.3",
"copy-to-clipboard": "^3.3.2",
@@ -114,7 +118,7 @@
"@solana/wallet-adapter-base": "^0.9.17",
"@solana/wallet-adapter-react": "^0.15.19",
"@solana/wallet-adapter-wallets": "^0.19.0",
"@solana/web3.js": "^1.63.0",
"@solana/web3.js": "^1.62.0",
"@thirdweb-dev/sdk": "*",
"ethers": ">=5.5.1",
"react": ">=18.0.0",
@@ -136,9 +140,6 @@
},
"@solana/web3.js": {
"optional": true
},
"@thirdweb-dev/solana": {
"optional": true
}
}
}
-57
View File
@@ -1,57 +0,0 @@
import type { PlaywrightTestConfig } from "@playwright/experimental-ct-react";
import { devices } from "@playwright/experimental-ct-react";
/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: "./",
/* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */
snapshotDir: "./__snapshots__",
/* Maximum time one test can run for. */
timeout: 10 * 1000,
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
// workers: process.env.CI ? 1 : undefined,
// we can use parallel tests because we have more cores now
workers: undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
/* Port to use for Playwright component endpoint. */
ctPort: 3100,
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
},
},
{
name: "firefox",
use: {
...devices["Desktop Firefox"],
},
},
{
name: "webkit",
use: {
...devices["Desktop Safari"],
},
},
],
};
export default config;
-4
View File
@@ -1,4 +0,0 @@
// Import styles, initialize component theme here.
// import '../src/common.css';
export {};
@@ -1,15 +0,0 @@
import { ConnectWallet } from ".";
import { ThirdwebProvider } from "../../providers/full";
import { test, expect } from "@playwright/experimental-ct-react";
test.use({ viewport: { width: 500, height: 500 } });
test("should render the connect wallet button", async ({ mount }) => {
const component = await mount(
<ThirdwebProvider desiredChainId={1}>
<ConnectWallet />
</ThirdwebProvider>,
);
await expect(component).toContainText("Connect Wallet");
});
@@ -144,7 +144,7 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
},
connect,
] = useConnect();
const disconnect = useDisconnect({ reconnectAfterGnosis: false });
const disconnect = useDisconnect({ reconnectPrevious: false });
const supportedConnectors = connectors.filter((c) =>
SUPPORTED_CONNECTORS.includes(c.id as typeof SUPPORTED_CONNECTORS[number]),
@@ -13,12 +13,14 @@ export interface ThirdwebNftMediaProps extends SharedMediaProps {
}
/**
*
* This component can be used to render NFTs from the thirdweb SDK.
* Props: {@link ThirdwebNftMediaProps}
*
* @example
* ```jsx
* import { ThirdwebNftMedia, useNFTCollection, useNFT } from "@thirdweb-dev/react";
* import { ThirdwebNftMedia, useContract, useNFT } from "@thirdweb-dev/react";
* export default function NFTCollectionRender() {
* const contract = useNFTCollection(<your-contract-address>);
* const { contract } = useContract(<your-contract-address>);
* const { data: nft, isLoading } = useNFT(contract, 0);
*
* return (
@@ -32,11 +34,6 @@ export interface ThirdwebNftMediaProps extends SharedMediaProps {
* );
* }
* ```
*
* Use this to get the primary sales recipient of your {@link SmartContract}
* @param contract - an instance of a {@link SmartContract}
* @returns the wallet address of the primary sales recipient
* @beta
*/
export const ThirdwebNftMedia = React.forwardRef<
HTMLMediaElement,
@@ -1,16 +0,0 @@
import { Web3Button } from ".";
import { ThirdwebProvider } from "../../providers/full";
import { test, expect } from "@playwright/experimental-ct-react";
test.use({ viewport: { width: 500, height: 500 } });
test("should render the connect wallet button", async ({ mount }) => {
const component = await mount(
<ThirdwebProvider desiredChainId={1}>
<Web3Button contractAddress="0x123" action={() => undefined}>
Do the thing
</Web3Button>
</ThirdwebProvider>,
);
await expect(component).toContainText("Connect Wallet");
});
@@ -1,13 +1,23 @@
import { ChainId } from "@thirdweb-dev/sdk";
import { useConnect } from "../hooks/wagmi-required/useConnect";
import { Signer, ethers, utils } from "ethers";
import invariant from "tiny-invariant";
import { Chain, Connector, ConnectorData, normalizeChainId } from "wagmi";
import {
Chain,
Connector,
ConnectorData,
normalizeChainId,
useContext as useWagmiContext,
} from "wagmi";
const CHAIN_ID_TO_GNOSIS_SERVER_URL = {
[ChainId.Mainnet]: "https://safe-transaction.mainnet.gnosis.io",
[ChainId.Avalanche]: "https://safe-transaction.avalanche.gnosis.io",
[ChainId.Polygon]: "https://safe-transaction.polygon.gnosis.io",
[ChainId.Goerli]: "https://safe-transaction.goerli.gnosis.io",
// mainnet
1: "https://safe-transaction.mainnet.gnosis.io",
// avalanche
43114: "https://safe-transaction.avalanche.gnosis.io",
// polygon
137: "https://safe-transaction.polygon.gnosis.io",
// goerli
5: "https://safe-transaction.goerli.gnosis.io",
};
export interface GnosisConnectorArguments {
@@ -163,3 +173,67 @@ export class GnosisSafeConnector extends Connector {
this.config = config;
}
}
/**
* Hook for connecting to a Gnosis Safe. This enables multisig wallets to connect to your application and sing transactions.
*
* ```javascript
* import { useGnosis } from "@thirdweb-dev/react"
* ```
*
*
* @example
* ```javascript
* import { useGnosis } from "@thirdweb-dev/react"
*
* const App = () => {
* const connectWithGnosis = useGnosis()
*
* return (
* <button onClick={() => connectWithGnosis({ safeAddress: "0x...", safeChainId: 1 })}>
* Connect Gnosis Safe
* </button>
* )
* }
* ```
*
* @public
*/
export function useGnosis() {
const wagmiContext = useWagmiContext();
invariant(
wagmiContext,
`useGnosis() can only be used inside <ThirdwebProvider />. If you are using <ThirdwebSDKProvider /> you will have to use your own wallet-connection logic.`,
);
const [connectors, connect] = useConnect();
if (connectors.loading) {
return () => Promise.reject("Gnosis connector not ready to be used, yet");
}
const connector = connectors.data.connectors.find((c) => c.id === "gnosis");
invariant(
connector,
"Gnosis connector not found, please make sure it is provided to your <ThirdwebProvider />",
);
return async (config: GnosisConnectorArguments) => {
const previousConnector = connectors.data.connector;
const previousConnectorChain = await previousConnector?.getChainId();
invariant(
!!previousConnector,
"Cannot connect to Gnosis Safe without first being connected to a personal wallet.",
);
invariant(
previousConnectorChain === config.safeChainId,
"Gnosis safe chain id must match personal wallet chain id.",
);
invariant(
utils.isAddress(config.safeAddress),
"Gnosis safe address must be a valid address.",
);
(connector as GnosisSafeConnector).setConfiguration(
previousConnector,
config,
);
return connect(connector);
};
}
+93 -1
View File
@@ -1,3 +1,4 @@
import { useConnect } from "../hooks/wagmi-required/useConnect";
import { providers, utils } from "ethers";
import type {
LoginWithMagicLinkConfiguration,
@@ -5,7 +6,12 @@ import type {
MagicSDKAdditionalConfiguration,
} from "magic-sdk";
import invariant from "tiny-invariant";
import { Chain, Connector, normalizeChainId } from "wagmi";
import {
Chain,
Connector,
normalizeChainId,
useContext as useWagmiContext,
} from "wagmi";
export interface MagicConnectorArguments
extends MagicSDKAdditionalConfiguration {
@@ -186,3 +192,89 @@ export class MagicConnector extends Connector {
}
}
}
/**
* Hook for connecting to an email wallet using magic link.
* This enables users without their own wallets to connect to your application and sign transactions securely using their email.
*
* ```javascript
* import { useMagic } from "@thirdweb-dev/react"
* ```
*
*
* @example
* Before using this hook, you first need to set up the magic configuration in your `ThirdwebProvider`, including your magic API key.
*
* ```javascript
* // Add the magic configuration object to your wallet connectors
* const connectors = [
* "metamask",
* "walletConnect",
* "walletLink",
* {
* name: "magic",
* options: {
* apiKey: "your-magic-api-key",
* }
* }
* ]
*
* // Add the above to the walletConnectors prop of your <ThirdwebProvider />
* const Provider = ({ children }) => (
* return (
* <ThirdwebProvider
* walletConnectors={connectors}
* // Specify remaining parameters
* ...
* >
* {children}
* </ThirdwebProvider>
* )
* }
* ```
*
* In order to use the hook to connect users with magic link, you just need to provide the users email to the connect function.
*
* You can setup the hook with the following configuration:
* ```javascript
* import { useMagic } from "@thirdweb-dev/react"
* import { useState } from "react"
*
* const LoginWithMagicLink = () => {
* const connectWithMagic = useMagic()
* const [email, setEmail] = useState()
*
* return (
* <div>
* <input value={email} onChange={(e) => setEmail(e.target.value)} />
* <button onClick={() => connectWithMagic({ email })}>Login</button>
* </div>
* )
* }
* ```
*
* @public
*/
export function useMagic() {
const wagmiContext = useWagmiContext();
invariant(
wagmiContext,
`useMagic() can only be used inside <ThirdwebProvider />. If you are using <ThirdwebSDKProvider /> you will have to use your own wallet-connection logic.`,
);
const [connectors, connect] = useConnect();
if (connectors.loading) {
return () => Promise.reject("Magic connector not ready to be used, yet");
}
const connector = connectors.data.connectors.find((c) => c.id === "magic");
invariant(
connector,
"Magic connector not found, please make sure it is provided to your <ThirdwebProvider />",
);
return (configuration: LoginWithMagicLinkConfiguration) => {
(connector as MagicConnector).setConfiguration(configuration);
return connect(connector);
};
}
@@ -10,7 +10,11 @@ import {
} from "../../utils/cache-keys";
import { useQueryWithNetwork } from "../query-utils/useQueryWithNetwork";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { ClaimCondition, ClaimConditionInput } from "@thirdweb-dev/sdk";
import {
ClaimCondition,
ClaimConditionFetchOptions,
ClaimConditionInput,
} from "@thirdweb-dev/sdk";
import { BigNumberish } from "ethers";
import invariant from "tiny-invariant";
@@ -65,25 +69,30 @@ export type SetClaimConditionsParams = {
export function useActiveClaimCondition(
contract: RequiredParam<DropContract>,
tokenId?: BigNumberish,
options?: ClaimConditionFetchOptions,
) {
const contractAddress = contract?.getAddress();
const { erc1155, erc721, erc20 } = getErcs(contract);
return useQueryWithNetwork(
cacheKeys.extensions.claimConditions.getActive(contractAddress, tokenId),
cacheKeys.extensions.claimConditions.getActive(
contractAddress,
tokenId,
options,
),
() => {
if (erc1155) {
requiredParamInvariant(
tokenId,
"tokenId is required for ERC1155 claim conditions",
);
return erc1155.claimConditions.getActive(tokenId);
return erc1155.claimConditions.getActive(tokenId, options);
}
if (erc721) {
return erc721.claimConditions.getActive();
return erc721.claimConditions.getActive(options);
}
if (erc20) {
return erc20.claimConditions.getActive();
return erc20.claimConditions.getActive(options);
}
throw new Error("Contract must be ERC721, ERC1155 or ERC20");
},
@@ -121,25 +130,30 @@ export function useActiveClaimCondition(
export function useClaimConditions(
contract: RequiredParam<DropContract>,
tokenId?: BigNumberish,
options?: ClaimConditionFetchOptions,
) {
const contractAddress = contract?.getAddress();
const { erc1155, erc721, erc20 } = getErcs(contract);
return useQueryWithNetwork(
cacheKeys.extensions.claimConditions.getAll(contractAddress, tokenId),
cacheKeys.extensions.claimConditions.getAll(
contractAddress,
tokenId,
options,
),
() => {
if (erc1155) {
requiredParamInvariant(
tokenId,
"tokenId is required for ERC1155 claim conditions",
);
return erc1155.claimConditions.getAll(tokenId);
return erc1155.claimConditions.getAll(tokenId, options);
}
if (erc721) {
return erc721.claimConditions.getAll();
return erc721.claimConditions.getAll(options);
}
if (erc20) {
return erc20.claimConditions.getAll();
return erc20.claimConditions.getAll(options);
}
throw new Error("Contract must be ERC721, ERC1155 or ERC20");
},
@@ -350,7 +364,7 @@ export function useResetClaimConditions(
return useMutation(
async () => {
const cleanConditions = (conditions: ClaimCondition[]) => {
const cleanConditions = async (conditions: ClaimCondition[]) => {
return conditions.map((c) => ({
...c,
price: c.currencyMetadata.displayValue,
@@ -364,24 +378,30 @@ export function useResetClaimConditions(
tokenId,
"tokenId is required for ERC1155 claim conditions",
);
const claimConditions = await erc1155.claimConditions.getAll(tokenId);
const claimConditions = await erc1155.claimConditions.getAll(tokenId, {
withAllowList: true,
});
return erc1155.claimConditions.set(
tokenId,
cleanConditions(claimConditions || []),
await cleanConditions(claimConditions || []),
true,
);
}
if (erc721) {
const claimConditions = await erc721.claimConditions.getAll();
const claimConditions = await erc721.claimConditions.getAll({
withAllowList: true,
});
return await erc721.claimConditions.set(
cleanConditions(claimConditions || []),
await cleanConditions(claimConditions || []),
true,
);
}
if (erc20) {
const claimConditions = await erc20.claimConditions.getAll();
const claimConditions = await erc20.claimConditions.getAll({
withAllowList: true,
});
return await erc20.claimConditions.set(
cleanConditions(claimConditions || []),
await cleanConditions(claimConditions || []),
true,
);
}
+1 -1
View File
@@ -542,7 +542,7 @@ export function useTransferNFT<TContract extends NFTContract>(
* @example
* ```jsx
* const Component = () => {
* const editionDrop = useEditionDrop(<ContractAddress>);
* const editionDrop = useContract(<ContractAddress>, "edition-drop");
* const {
* mutate: airdropNFT,
* isLoading,
@@ -1,72 +0,0 @@
import {
GnosisConnectorArguments,
GnosisSafeConnector,
} from "../../connectors/gnosis-safe";
import { useConnect } from "../wagmi-required/useConnect";
import { utils } from "ethers";
import invariant from "tiny-invariant";
import { useContext as useWagmiContext } from "wagmi";
/**
* Hook for connecting to a Gnosis Safe. This enables multisig wallets to connect to your application and sing transactions.
*
* ```javascript
* import { useGnosis } from "@thirdweb-dev/react"
* ```
*
*
* @example
* ```javascript
* import { useGnosis } from "@thirdweb-dev/react"
*
* const App = () => {
* const connectWithGnosis = useGnosis()
*
* return (
* <button onClick={() => connectWithGnosis({ safeAddress: "0x...", safeChainId: 1 })}>
* Connect Gnosis Safe
* </button>
* )
* }
* ```
*
* @public
*/
export function useGnosis() {
const wagmiContext = useWagmiContext();
invariant(
wagmiContext,
`useGnosis() can only be used inside <ThirdwebProvider />. If you are using <ThirdwebSDKProvider /> you will have to use your own wallet-connection logic.`,
);
const [connectors, connect] = useConnect();
if (connectors.loading) {
return () => Promise.reject("Gnosis connector not ready to be used, yet");
}
const connector = connectors.data.connectors.find((c) => c.id === "gnosis");
invariant(
connector,
"Gnosis connector not found, please make sure it is provided to your <ThirdwebProvider />",
);
return async (config: GnosisConnectorArguments) => {
const previousConnector = connectors.data.connector;
const previousConnectorChain = await previousConnector?.getChainId();
invariant(
!!previousConnector,
"Cannot connect to Gnosis Safe without first being connected to a personal wallet.",
);
invariant(
previousConnectorChain === config.safeChainId,
"Gnosis safe chain id must match personal wallet chain id.",
);
invariant(
utils.isAddress(config.safeAddress),
"Gnosis safe address must be a valid address.",
);
(connector as GnosisSafeConnector).setConfiguration(
previousConnector,
config,
);
return connect(connector);
};
}
@@ -1,91 +0,0 @@
import { MagicConnector } from "../../connectors/magic";
import { useConnect } from "../wagmi-required/useConnect";
import { LoginWithMagicLinkConfiguration } from "magic-sdk";
import invariant from "tiny-invariant";
import { useContext as useWagmiContext } from "wagmi";
/**
* Hook for connecting to an email wallet using magic link.
* This enables users without their own wallets to connect to your application and sign transactions securely using their email.
*
* ```javascript
* import { useMagic } from "@thirdweb-dev/react"
* ```
*
*
* @example
* Before using this hook, you first need to set up the magic configuration in your `ThirdwebProvider`, including your magic API key.
*
* ```javascript
* // Add the magic configuration object to your wallet connectors
* const connectors = [
* "metamask",
* "walletConnect",
* "walletLink",
* {
* name: "magic",
* options: {
* apiKey: "your-magic-api-key",
* }
* }
* ]
*
* // Add the above to the walletConnectors prop of your <ThirdwebProvider />
* const Provider = ({ children }) => (
* return (
* <ThirdwebProvider
* walletConnectors={connectors}
* // Specify remaining parameters
* ...
* >
* {children}
* </ThirdwebProvider>
* )
* }
* ```
*
* In order to use the hook to connect users with magic link, you just need to provide the users email to the connect function.
*
* You can setup the hook with the following configuration:
* ```javascript
* import { useMagic } from "@thirdweb-dev/react"
* import { useState } from "react"
*
* const LoginWithMagicLink = () => {
* const connectWithMagic = useMagic()
* const [email, setEmail] = useState()
*
* return (
* <div>
* <input value={email} onChange={(e) => setEmail(e.target.value)} />
* <button onClick={() => connectWithMagic({ email })}>Login</button>
* </div>
* )
* }
* ```
*
* @public
*/
export function useMagic() {
const wagmiContext = useWagmiContext();
invariant(
wagmiContext,
`useMagic() can only be used inside <ThirdwebProvider />. If you are using <ThirdwebSDKProvider /> you will have to use your own wallet-connection logic.`,
);
const [connectors, connect] = useConnect();
if (connectors.loading) {
return () => Promise.reject("Magic connector not ready to be used, yet");
}
const connector = connectors.data.connectors.find((c) => c.id === "magic");
invariant(
connector,
"Magic connector not found, please make sure it is provided to your <ThirdwebProvider />",
);
return (configuration: LoginWithMagicLinkConfiguration) => {
(connector as MagicConnector).setConfiguration(configuration);
return connect(connector);
};
}
@@ -1,10 +1,10 @@
import { useConnect } from "../wagmi-required/useConnect";
import { Buffer } from "buffer";
import invariant from "tiny-invariant";
import { useContext as useWagmiContext } from "wagmi";
if (!globalThis.Buffer) {
globalThis.Buffer = require("buffer/").Buffer;
}
globalThis.Buffer = Buffer;
/**
* Hook for connecting to a mobile wallet with Wallet Connect
*
@@ -1,10 +1,10 @@
import { useConnect } from "../wagmi-required/useConnect";
import { Buffer } from "buffer";
import invariant from "tiny-invariant";
import { useContext as useWagmiContext } from "wagmi";
if (!globalThis.Buffer) {
globalThis.Buffer = require("buffer/").Buffer;
}
globalThis.Buffer = Buffer;
/**
* Hook for connecting to a Coinbase wallet.
*
@@ -28,8 +28,8 @@ import { useContract } from "../async/contracts";
* @deprecated
* This hook is deprecated and will be removed in a future major version. You should use {@link useContract} instead.
* ```diff
* - const edition = await sdk.useEdition("0x1234...");
* + const edition = await sdk.useContract("0x1234...", "edition").contract;
* - const edition = useEdition("0x1234...");
* + const edition = useContract("0x1234...", "edition").contract;
* ```
*/
export function useEdition(contractAddress: RequiredParam<string>) {
@@ -27,8 +27,8 @@ import { useContract } from "../async/contracts";
* @deprecated
* This hook is deprecated and will be removed in a future major version. You should use {@link useContract} instead.
* ```diff
* - const editionDrop = await sdk.useEditionDrop("0x1234...");
* + const editionDrop = await sdk.useContract("0x1234...", "edition-drop").contract;
* - const editionDrop = useEditionDrop("0x1234...");
* + const editionDrop = useContract("0x1234...", "edition-drop").contract;
* ```
*/
export function useEditionDrop(contractAddress: RequiredParam<string>) {
@@ -28,8 +28,8 @@ import { useContract } from "../async/contracts";
* @deprecated
* This hook is deprecated and will be removed in a future major version. You should use {@link useContract} instead.
* ```diff
* - const marketplace = await sdk.useMarketplace("0x1234...");
* + const marketplace = await sdk.useContract("0x1234...", "marketplace").contract;
* - const marketplace = useMarketplace("0x1234...");
* + const marketplace = useContract("0x1234...", "marketplace").contract;
* ```
*/
export function useMarketplace(contractAddress: RequiredParam<string>) {
@@ -27,8 +27,8 @@ import { useContract } from "../async/contracts";
* @deprecated
* This hook is deprecated and will be removed in a future major version. You should use {@link useContract} instead.
* ```diff
* - const multiwrap = await sdk.useMultiwrap("0x1234...");
* + const multiwrap = await sdk.useContract("0x1234...", "multiwrap").contract;
* - const multiwrap = useMultiwrap("0x1234...");
* + const multiwrap = useContract("0x1234...", "multiwrap").contract;
* ```
*/
export function useMultiwrap(contractAddress: RequiredParam<string>) {
@@ -28,8 +28,8 @@ import { useContract } from "../async/contracts";
* @deprecated
* This hook is deprecated and will be removed in a future major version. You should use {@link useContract} instead.
* ```diff
* - const nftCollection = await sdk.useNFTCollection("0x1234...");
* + const nftCollection = await sdk.useContract("0x1234...", "nft-collection").contract;
* - const nftCollection = useNFTCollection("0x1234...");
* + const nftCollection = useContract("0x1234...", "nft-collection").contract;
* ```
*/
export function useNFTCollection(contractAddress: RequiredParam<string>) {
@@ -27,8 +27,8 @@ import { useContract } from "../async/contracts";
* @deprecated
* This hook is deprecated and will be removed in a future major version. You should use {@link useContract} instead.
* ```diff
* - const nftDrop = await sdk.useNFTDrop("0x1234...");
* + const nftDrop = await sdk.useContract("0x1234...", "nft-drop").contract;
* - const nftDrop = useNFTDrop("0x1234...");
* + const nftDrop = useContract("0x1234...", "nft-drop").contract;
* ```
*/
export function useNFTDrop(contractAddress: RequiredParam<string>) {
@@ -28,8 +28,8 @@ import { useContract } from "../async/contracts";
* @deprecated
* This hook is deprecated and will be removed in a future major version. You should use {@link useContract} instead.
* ```diff
* - const pack = await sdk.usePack("0x1234...");
* + const pack = await sdk.useContract("0x1234...", "pack").contract;
* - const pack = usePack("0x1234...");
* + const pack = useContract("0x1234...", "pack").contract;
* ```
*/
export function usePack(contractAddress: RequiredParam<string>) {
@@ -27,8 +27,8 @@ import { useContract } from "../async/contracts";
* @deprecated
* This hook is deprecated and will be removed in a future major version. You should use {@link useContract} instead.
* ```diff
* - const signatureDrop = await sdk.useSignatureDrop("0x1234...");
* + const signatureDrop = await sdk.useContract("0x1234...", "signature-drop").contract;
* - const signatureDrop = useSignatureDrop("0x1234...");
* + const signatureDrop = useContract("0x1234...", "signature-drop").contract;
* ```
*/
export function useSignatureDrop(contractAddress?: RequiredParam<string>) {
@@ -28,8 +28,8 @@ import { useContract } from "../async/contracts";
* @deprecated
* This hook is deprecated and will be removed in a future major version. You should use {@link useContract} instead.
* ```diff
* - const split = await sdk.useSplit("0x1234...");
* + const split = await sdk.useContract("0x1234...", "split").contract;
* - const split = useSplit("0x1234...");
* + const split = useContract("0x1234...", "split").contract;
* ```
*/
export function useSplit(contractAddress: RequiredParam<string>) {
@@ -28,8 +28,8 @@ import { useContract } from "../async/contracts";
* @deprecated
* This hook is deprecated and will be removed in a future major version. You should use {@link useContract} instead.
* ```diff
* - const token = await sdk.useToken("0x1234...");
* + const token = await sdk.useContract("0x1234...", "token").contract;
* - const token = useToken("0x1234...");
* + const token = useContract("0x1234...", "token").contract;
* ```
*/
export function useToken(contractAddress: RequiredParam<string>) {
@@ -28,8 +28,8 @@ import { useContract } from "../async/contracts";
* @deprecated
* This hook is deprecated and will be removed in a future major version. You should use {@link useContract} instead.
* ```diff
* - const token = await sdk.useTokenDrop("0x1234...");
* + const token = await sdk.useContract("0x1234...", "token-drop").contract;
* - const token = useTokenDrop("0x1234...");
* + const token = useContract("0x1234...", "token-drop").contract;
* ```
*/
export function useTokenDrop(contractAddress: RequiredParam<string>) {
@@ -28,8 +28,8 @@ import { useContract } from "../async/contracts";
* @deprecated
* This hook is deprecated and will be removed in a future major version. You should use {@link useContract} instead.
* ```diff
* - const vote = await sdk.useVote("0x1234...");
* + const vote = await sdk.useContract("0x1234...", "vote").contract;
* - const vote = useVote("0x1234...");
* + const vote = useContract("0x1234...", "vote").contract;
* ```
*/
export function useVote(contractAddress: RequiredParam<string>) {
@@ -1,7 +1,6 @@
import { GnosisSafeConnector } from "../../connectors/gnosis-safe";
import { useConnect } from "./useConnect";
import invariant from "tiny-invariant";
import { useAccount, useContext as useWagmiContext } from "wagmi";
import { Connector, useAccount, useContext as useWagmiContext } from "wagmi";
/**
* Hook for disconnecting the currently connected wallet
@@ -31,23 +30,22 @@ import { useAccount, useContext as useWagmiContext } from "wagmi";
*
* @public
*/
export function useDisconnect(options?: { reconnectAfterGnosis?: boolean }) {
export function useDisconnect(options?: { reconnectPrevious?: boolean }) {
const wagmiContext = useWagmiContext();
invariant(
wagmiContext,
`useDisconnect() can only be used inside <ThirdwebProvider />. If you are using <ThirdwebSDKProvider /> you will have to use your own connection logic.`,
);
const optsWithDefaults = { ...{ reconnectAfterGnosis: true }, ...options };
const optsWithDefaults = { ...{ reconnectPrevious: true }, ...options };
const [, connect] = useConnect();
const [data, disconnect] = useAccount();
return async () => {
const previousConnector =
(data.data?.connector instanceof GnosisSafeConnector &&
data.data.connector.previousConnector) ||
undefined;
// if there is a previous connector get it
const previousConnector: Connector | undefined =
(data?.data?.connector as any)?.previousConnector || undefined;
// if it's gnosis, just connect the previous connector
if (optsWithDefaults.reconnectAfterGnosis && previousConnector) {
if (optsWithDefaults.reconnectPrevious && previousConnector) {
try {
return await connect(previousConnector);
} catch (err) {
-2
View File
@@ -31,8 +31,6 @@ export * from "./hooks/wagmi-required/useConnect";
export * from "./hooks/connectors/useMetamask";
export * from "./hooks/connectors/useWalletConnect";
export * from "./hooks/connectors/useWalletLink";
export * from "./hooks/connectors/useGnosis";
export * from "./hooks/connectors/useMagic";
// re-exports
export { ChainId } from "@thirdweb-dev/sdk";
+76 -73
View File
@@ -1,90 +1,93 @@
import { useState, useCallback, useLayoutEffect, useEffect, useRef, PropsWithChildren } from "react";
import { createPortal } from "react-dom";
import {
useState,
useCallback,
useLayoutEffect,
useEffect,
useRef,
PropsWithChildren,
} from "react";
import { createPortal } from "react-dom";
function canUseDOM() {
function canUseDOM() {
return !!(
typeof window !== "undefined" &&
window.document &&
window.document.createElement
);
typeof window !== "undefined" &&
window.document &&
window.document.createElement
);
}
/**
* Forces a re-render, similar to `forceUpdate` in class components.
*/
function useForceUpdate() {
const [, dispatch] = useState<{}>(Object.create(null));
return useCallback(() => {
dispatch(Object.create(null));
}, []);
const [, dispatch] = useState<{}>(Object.create(null));
return useCallback(() => {
dispatch(Object.create(null));
}, []);
}
const useIsomorphicLayoutEffect = canUseDOM() ? useLayoutEffect : useEffect;
/**
* Portal from `@reach/portal`
*
* @see Docs https://reach.tech/portal#portal
*/
const PortalImpl: React.FC<PortalProps> = ({
children,
type = "reach-portal",
containerRef,
}) => {
const mountNode = useRef<HTMLDivElement | null>(null);
const portalNode = useRef<HTMLElement | null>(null);
const forceUpdate = useForceUpdate();
/**
* Portal from `@reach/portal`
*
* @see Docs https://reach.tech/portal#portal
*/
const PortalImpl: React.FC<PortalProps> = ({
children,
type = "reach-portal",
containerRef,
}) => {
const mountNode = useRef<HTMLDivElement | null>(null);
const portalNode = useRef<HTMLElement | null>(null);
const forceUpdate = useForceUpdate();
useIsomorphicLayoutEffect(() => {
// This ref may be null when a hot-loader replaces components on the page
if (!mountNode.current) {
return;
}
// It's possible that the content of the portal has, itself, been portaled.
// In that case, it's important to append to the correct document element.
const ownerDocument = mountNode.current.ownerDocument;
const body = containerRef?.current || ownerDocument.body;
portalNode.current = ownerDocument?.createElement(type);
body.appendChild(portalNode.current);
forceUpdate();
return () => {
if (portalNode.current && body) {
body.removeChild(portalNode.current);
}
};
}, [type, forceUpdate, containerRef]);
return portalNode.current ? (
createPortal(children, portalNode.current)
) : (
<span ref={mountNode} />
);
};
useIsomorphicLayoutEffect(() => {
// This ref may be null when a hot-loader replaces components on the page
if (!mountNode.current) {return;}
// It's possible that the content of the portal has, itself, been portaled.
// In that case, it's important to append to the correct document element.
const ownerDocument = mountNode.current.ownerDocument;
const body = containerRef?.current || ownerDocument.body;
portalNode.current = ownerDocument?.createElement(type);
body.appendChild(portalNode.current);
forceUpdate();
return () => {
if (portalNode.current && body) {
body.removeChild(portalNode.current);
}
};
}, [type, forceUpdate, containerRef]);
export const Portal: React.FC<PortalProps> = ({
unstable_skipInitialRender,
...props
}) => {
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
if (unstable_skipInitialRender) {
setHydrated(true);
}
}, [unstable_skipInitialRender]);
if (unstable_skipInitialRender && !hydrated) {
return null;
}
return <PortalImpl {...props} />;
};
return portalNode.current ? (
createPortal(children, portalNode.current)
) : (
<span ref={mountNode} />
);
};
export const Portal: React.FC<PortalProps> = ({
unstable_skipInitialRender,
...props
}) => {
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
if (unstable_skipInitialRender) {
setHydrated(true);
}
}, [unstable_skipInitialRender]);
if (unstable_skipInitialRender && !hydrated) {
return null;
}
return <PortalImpl {...props} />;
};
export type PortalProps = PropsWithChildren<{
type?: string;
containerRef?: React.RefObject<Node>;
unstable_skipInitialRender?: boolean;
}>;
Portal.displayName = "Portal";
export type PortalProps = PropsWithChildren<{
type?: string;
containerRef?: React.RefObject<Node>;
unstable_skipInitialRender?: boolean;
}>;
Portal.displayName = "Portal";
+9 -46
View File
@@ -1,8 +1,5 @@
import {
GnosisConnectorArguments,
GnosisSafeConnector,
} from "../connectors/gnosis-safe";
import { MagicConnector, MagicConnectorArguments } from "../connectors/magic";
import type { GnosisSafeConnector } from "../connectors/gnosis-safe";
import type { MagicConnector } from "../connectors/magic";
import {
Chain,
SupportedChain,
@@ -25,6 +22,7 @@ import {
ProviderProps as WagmiproviderProps,
useProvider,
useSigner,
Connector,
} from "wagmi";
import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet";
import { InjectedConnector } from "wagmi/connectors/injected";
@@ -56,26 +54,6 @@ export type WalletLinkConnectorType =
options: CoinbaseWalletConnector["options"];
};
/**
* @internal
*/
export type MagicConnectorType =
| "magic"
| {
name: "magic";
options: Omit<MagicConnectorArguments, "network">;
};
/**
* @internal
*/
export type GnosisConnectorType =
| "gnosis"
| {
name: "gnosis";
options: GnosisConnectorArguments;
};
/**
* @internal
*/
@@ -83,8 +61,8 @@ export type WalletConnector =
| InjectedConnectorType
| WalletConnectConnectorType
| WalletLinkConnectorType
| MagicConnectorType
| GnosisConnectorType;
| GnosisSafeConnector
| MagicConnector;
/**
* @internal
@@ -280,6 +258,9 @@ export const ThirdwebProvider = <
connectors: ({ chainId }: { chainId?: number }) => {
return walletConnectors
.map((connector) => {
if (connector instanceof Connector) {
return connector;
}
// injected connector
if (
(typeof connector === "string" &&
@@ -344,25 +325,7 @@ export const ThirdwebProvider = <
},
});
}
if (typeof connector === "object" && connector.name === "magic") {
const jsonRpcUrl = _rpcUrlMap[chainId || desiredChainId || 1];
return new MagicConnector({
chains: _supporrtedChains,
options: {
...connector.options,
network: { rpcUrl: jsonRpcUrl, chainId: desiredChainId || 1 },
rpcUrls: _rpcUrlMap,
},
});
}
if (
(typeof connector === "string" && connector === "gnosis") ||
(typeof connector === "object" && connector.name === "gnosis")
) {
return new GnosisSafeConnector({
chains: _supporrtedChains,
});
}
return null;
})
.filter((c) => c !== null);
+7 -4
View File
@@ -3,6 +3,7 @@ import { SupportedChainId } from "../constants/chain";
import { ContractAddress, WalletAddress } from "../types";
import { QueryClient, QueryKey } from "@tanstack/react-query";
import type {
ClaimConditionFetchOptions,
MarketplaceFilter,
QueryAllParams,
SUPPORTED_CHAIN_ID,
@@ -267,22 +268,24 @@ export const cacheKeys = {
getActive: (
contractAddress: RequiredParam<ContractAddress>,
tokenId?: BigNumberish,
options?: ClaimConditionFetchOptions,
) =>
createContractCacheKey(
contractAddress,
tokenId
? ["claimConditions", "getActive", { tokenId }]
: ["claimConditions", "getActive"],
? ["claimConditions", "getActive", { tokenId }, options]
: ["claimConditions", "getActive", options],
),
getAll: (
contractAddress: RequiredParam<ContractAddress>,
tokenId?: BigNumberish,
options?: ClaimConditionFetchOptions,
) =>
createContractCacheKey(
contractAddress,
tokenId
? ["claimConditions", "getAll", { tokenId }]
: ["claimConditions", "getAll"],
? ["claimConditions", "getAll", { tokenId }, options]
: ["claimConditions", "getAll", options],
),
getClaimIneligibilityReasons: (
contractAddress: RequiredParam<ContractAddress>,
+1 -1
View File
@@ -1,4 +1,4 @@
// at the moment we'll re-export everything from the ethereum package (only!)
// at the moment we'll re-export everything from the ethereum package
export * from "./evm";
// core exports
+1 -1
View File
@@ -1,12 +1,12 @@
import { ThirdwebAuthConfig } from "../contexts/thirdweb-auth";
import { ThirdwebSDKProvider } from "./base";
import type { WalletAdapter } from "@solana/wallet-adapter-base";
import { PhantomWalletAdapter } from "@solana/wallet-adapter-phantom";
import {
ConnectionProvider,
useWallet,
WalletProvider,
} from "@solana/wallet-adapter-react";
import { PhantomWalletAdapter } from "@solana/wallet-adapter-wallets";
import { getUrlForNetwork, Network } from "@thirdweb-dev/sdk/solana";
import { PropsWithChildren } from "react";
+61
View File
@@ -1,5 +1,66 @@
# @thirdweb-dev/sdk
## 3.4.0
### Patch Changes
- [#337](https://github.com/thirdweb-dev/js/pull/337) [`340605b`](https://github.com/thirdweb-dev/js/commit/340605b507f384fbd2999b9c16542af3c53e84a9) Thanks [@adam-maj](https://github.com/adam-maj)! - Clean native token address in listing
- [#342](https://github.com/thirdweb-dev/js/pull/342) [`26116a6`](https://github.com/thirdweb-dev/js/commit/26116a6f637ee845f7bd17f20ffe17caf184658e) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Add new univeral RPC urls
- [#335](https://github.com/thirdweb-dev/js/pull/335) [`15f8006`](https://github.com/thirdweb-dev/js/commit/15f8006e1fb22333b7ee239b45e7b1b12d6dccc8) Thanks [@adam-maj](https://github.com/adam-maj)! - Export smart contract
- [#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 [[`ef27aad`](https://github.com/thirdweb-dev/js/commit/ef27aad0aafc4577e85f44dc77dfbe880bd239b5)]:
- @thirdweb-dev/storage@1.0.6
## 3.3.1
### Patch Changes
- [#332](https://github.com/thirdweb-dev/js/pull/332) [`ad851f7`](https://github.com/thirdweb-dev/js/commit/ad851f7b4d6abb5ab9364ef3dec229f913e18ce5) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix for finding addresses in legacy merkle tree format
- [#330](https://github.com/thirdweb-dev/js/pull/330) [`6218c88`](https://github.com/thirdweb-dev/js/commit/6218c885842e6b4e44ec38ec92a9211e56c49bf4) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix deploying contracts with non-32 bytes parameters like bytes4
## 3.3.0
### Minor Changes
- [#315](https://github.com/thirdweb-dev/js/pull/315) [`89ff921`](https://github.com/thirdweb-dev/js/commit/89ff921a7d9f0e42b4e6707c8b56d0aab95c1aa0) Thanks [@furqanrydhan](https://github.com/furqanrydhan)! - fixing vite, multihash moved to non lazy load + cbor-x instead of cbor-web
- [#321](https://github.com/thirdweb-dev/js/pull/321) [`a57b4f0`](https://github.com/thirdweb-dev/js/commit/a57b4f01f3a49590b897101f2730582e4124a554) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - [SDK] Implement sharded merkle trees for lightweight allowlist checks
**Behavior change**
We've made allowlists much more performant using sharded merkle trees. This allows us to process large allowlists (1M+) efficiently.
To support those large allowlists, fetching claim conditions does not fetch the allowlist data by default anymore. Instead, you can pass an options object to additionally fetch the allowlist data along with the rest of the claim conditions data.
This affects `ClaimConditions.getActive()` and `ClaimConditions.getAll()`
Examples:
```ts
const activeClaimCondition =
await contract.erc721.claimConditions.getActive();
// `activeClaimCondition.snapshot` is undefined
const activeclaimConditionWithtAllowList =
await contract.erc721.claimConditions.getActive({
withAllowList: true,
});
// `activeClaimCondition.snapshot` returns the allowlist data
```
### 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)! - [EVM] - expose fn to get ipfs hash from bytecote publicly
- [#328](https://github.com/thirdweb-dev/js/pull/328) [`df74340`](https://github.com/thirdweb-dev/js/commit/df74340fa51323304c32419761d6f18628b060fa) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update cbor-x import to work with es2018 builds (embeds)
- Updated dependencies [[`87fd6ab`](https://github.com/thirdweb-dev/js/commit/87fd6ab14e1a67a1b12e72bd397fb21769537307)]:
- @thirdweb-dev/[email protected]
## 3.2.6
### Patch Changes
+9 -15
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/sdk",
"version": "3.2.6",
"version": "3.4.0",
"description": "The main thirdweb SDK.",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/sdk",
"license": "Apache-2.0",
@@ -65,9 +65,8 @@
"push": "yalc push"
},
"devDependencies": {
"@gmrchk/cli-testing-library": "^0.1.2",
"@metaplex-foundation/amman": "^0.10.0",
"@metaplex-foundation/amman-client": "^0.2.1",
"@metaplex-foundation/amman": "0.10.0",
"@metaplex-foundation/amman-client": "0.2.1",
"@microsoft/api-documenter": "^7.19.4",
"@microsoft/api-extractor": "^7.29.2",
"@microsoft/tsdoc": "^0.14.1",
@@ -82,7 +81,6 @@
"@types/uuid": "^8.3.3",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"cbor": "^8.1.0",
"chai": "^4.3.6",
"deep-equal-in-any-order": "^1.1.18",
"eslint": "^8.21.0",
@@ -91,11 +89,9 @@
"eslint-plugin-inclusive-language": "^2.2.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-tsdoc": "^0.2.16",
"esm": "^3.2.25",
"ethers": "^5.7.0",
"hardhat": "^2.9.3",
"mocha": "^10.0.0",
"node-stdlib-browser": "^1.2.0",
"prettier": "^2.7.1",
"typescript": "^4.7.4"
},
@@ -103,22 +99,20 @@
"ethers": "^5"
},
"dependencies": {
"@metaplex-foundation/js": "^0.15.0",
"@metaplex-foundation/mpl-token-metadata": "^2.2.2",
"@project-serum/anchor": "^0.25.0",
"@solana/spl-token": "^0.2.0",
"@solana/web3.js": "^1.50.1",
"@metaplex-foundation/js": "0.15.0",
"@metaplex-foundation/mpl-token-metadata": "2.2.2",
"@project-serum/anchor": "0.25.0",
"@solana/spl-token": "0.2.0",
"@solana/web3.js": "1.62.0",
"@thirdweb-dev/contracts-js": "*",
"@thirdweb-dev/storage": "*",
"bn.js": "^5.2.1",
"bs58": "^5.0.0",
"cbor-web": "^8.1.0",
"cbor-x": "^1.4.1",
"cross-fetch": "^3.1.5",
"eventemitter3": "^4.0.7",
"fast-deep-equal": "^3.1.3",
"form-data": "^4.0.0",
"merkletreejs": "^0.2.24",
"multihashes": "^4.0.3",
"tiny-invariant": "^1.2.0",
"tweetnacl": "^1.0.3",
"uuid": "^9.0.0",
+6
View File
@@ -0,0 +1,6 @@
export const DEFAULT_API_KEY =
"c6634ad2d97b74baf15ff556016830c251050e6c36b9da508ce3ec80095d3dc1";
export function getRpcUrl(network: string) {
return `https://${network}.rpc.thirdweb.com/${DEFAULT_API_KEY}`;
}
+69 -42
View File
@@ -6,6 +6,8 @@ import {
ClaimConditionOutputSchema,
} from "../schema/contracts/common/claim-conditions";
import {
SnapshotEntry,
SnapshotEntryWithProof,
SnapshotInputSchema,
SnapshotSchema,
} from "../schema/contracts/common/snapshots";
@@ -23,17 +25,18 @@ import {
isNativeToken,
normalizePriceValue,
} from "./currency";
import { ShardedMerkleTree } from "./sharded-merkle-tree";
import { createSnapshot } from "./snapshots";
import { IDropClaimCondition } from "@thirdweb-dev/contracts-js/dist/declarations/src/DropERC20";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import {
BigNumber,
BigNumberish,
ethers,
CallOverrides,
constants,
ethers,
providers,
utils,
CallOverrides,
} from "ethers";
/**
@@ -59,21 +62,17 @@ export async function prepareClaim(
.toString()
.startsWith(constants.AddressZero)
) {
const claims = await fetchSnapshot(
const claim = await fetchSnapshotEntryForAddress(
addressToClaim,
activeClaimCondition.merkleRootHash.toString(),
await merkleMetadataFetcher(),
storage,
);
const item =
claims &&
claims.find(
(c) => c.address.toLowerCase() === addressToClaim.toLowerCase(),
);
if (item === undefined) {
if (!claim) {
throw new Error("No claim found for this address");
}
proofs = item.proof;
maxClaimable = ethers.utils.parseUnits(item.maxClaimable, tokenDecimals);
proofs = claim.proof;
maxClaimable = ethers.utils.parseUnits(claim.maxClaimable, tokenDecimals);
}
} catch (e) {
// have to handle the valid error case that we *do* want to throw on
@@ -114,14 +113,6 @@ export async function prepareClaim(
};
}
type Snapshot =
| {
address: string;
proof: string[];
maxClaimable: string;
}[]
| undefined;
/**
* @internal
* @param merkleRoot
@@ -132,20 +123,59 @@ export async function fetchSnapshot(
merkleRoot: string,
merkleMetadata: Record<string, string> | undefined,
storage: ThirdwebStorage,
): Promise<Snapshot> {
): Promise<SnapshotEntry[] | null> {
if (!merkleMetadata) {
return undefined;
return null;
}
const snapshotUri = merkleMetadata[merkleRoot];
let snapshot: Snapshot = undefined;
if (snapshotUri) {
const raw = await storage.downloadJSON(snapshotUri);
const snapshotData = SnapshotSchema.parse(raw);
if (merkleRoot === snapshotData.merkleRoot) {
snapshot = snapshotData.claims;
if (raw.isShardedMerkleTree && raw.merkleRoot === merkleRoot) {
const smt = await ShardedMerkleTree.fromUri(snapshotUri, storage);
return smt?.getAllEntries() || null;
} else {
const snapshotData = SnapshotSchema.parse(raw);
if (merkleRoot === snapshotData.merkleRoot) {
return snapshotData.claims.map((claim) => ({
address: claim.address,
maxClaimable: claim.maxClaimable,
}));
}
}
}
return snapshot;
return null;
}
export async function fetchSnapshotEntryForAddress(
address: string,
merkleRoot: string,
merkleMetadata: Record<string, string> | undefined,
storage: ThirdwebStorage,
): Promise<SnapshotEntryWithProof | null> {
if (!merkleMetadata) {
return null;
}
const snapshotUri = merkleMetadata[merkleRoot];
if (snapshotUri) {
const raw = await storage.downloadJSON(snapshotUri);
if (raw.isShardedMerkleTree && raw.merkleRoot === merkleRoot) {
const merkleTree = await ShardedMerkleTree.fromShardedMerkleTreeInfo(
raw,
storage,
);
return await merkleTree.getProof(address);
}
// legacy non-sharded, just fetch it all and filter out
const snapshotData = SnapshotSchema.parse(raw);
if (merkleRoot === snapshotData.merkleRoot) {
return (
snapshotData.claims.find(
(c) => c.address.toLowerCase() === address.toLowerCase(),
) || null
);
}
}
return null;
}
/**
@@ -203,7 +233,11 @@ export async function updateExistingClaimConditions(
/**
* Fetches the proof for the current signer for a particular wallet.
*
* @param addressToClaim
* @param merkleRoot - The merkle root of the condition to check.
* @param tokenDecimals
* @param merkleMetadata
* @param storage
* @returns - The proof for the current signer for the specified condition.
*/
export async function getClaimerProofs(
@@ -213,30 +247,21 @@ export async function getClaimerProofs(
merkleMetadata: Record<string, string>,
storage: ThirdwebStorage,
): Promise<{ maxClaimable: BigNumber; proof: string[] }> {
const claims: Snapshot = await fetchSnapshot(
const claim = await fetchSnapshotEntryForAddress(
addressToClaim,
merkleRoot,
merkleMetadata,
storage,
);
if (claims === undefined) {
return {
proof: [],
maxClaimable: BigNumber.from(0),
};
}
const item = claims.find(
(c) => c.address.toLowerCase() === addressToClaim?.toLowerCase(),
);
if (item === undefined) {
if (!claim) {
return {
proof: [],
maxClaimable: BigNumber.from(0),
};
}
return {
proof: item.proof,
maxClaimable: ethers.utils.parseUnits(item.maxClaimable, tokenDecimals),
proof: claim.proof,
maxClaimable: ethers.utils.parseUnits(claim.maxClaimable, tokenDecimals),
};
}
@@ -356,10 +381,10 @@ export async function transformResultToClaimCondition(
provider: providers.Provider,
merkleMetadata: Record<string, string> | undefined,
storage: ThirdwebStorage,
shouldDownloadSnapshot: boolean,
): Promise<ClaimCondition> {
const cv = await fetchCurrencyValue(provider, pm.currency, pm.pricePerToken);
const claims = await fetchSnapshot(pm.merkleRoot, merkleMetadata, storage);
const maxClaimableSupply = convertToReadableQuantity(
pm.maxClaimableSupply,
tokenDecimals,
@@ -388,7 +413,9 @@ export async function transformResultToClaimCondition(
currencyAddress: pm.currency,
currencyMetadata: cv,
merkleRootHash: pm.merkleRoot,
snapshot: claims,
snapshot: shouldDownloadSnapshot
? await fetchSnapshot(pm.merkleRoot, merkleMetadata, storage)
: undefined,
});
}
+7
View File
@@ -26,6 +26,13 @@ export function isNativeToken(tokenAddress: string): boolean {
);
}
export function cleanCurrencyAddress(currencyAddress: string): string {
if (isNativeToken(currencyAddress)) {
return NATIVE_TOKEN_ADDRESS;
}
return currencyAddress;
}
export async function normalizePriceValue(
provider: providers.Provider,
inputPrice: Price,
@@ -22,6 +22,8 @@ import {
} from "../schema/contracts/custom";
import { ExtensionNotImplementedError } from "./error";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import bs58 from "bs58";
import { decode } from "cbor-x";
import { BaseContract, BigNumber, ethers } from "ethers";
import { z } from "zod";
@@ -282,9 +284,9 @@ export async function resolveContractUriFromAddress(
* @internal
* @param bytecode
*/
async function extractIPFSHashFromBytecode(
export function extractIPFSHashFromBytecode(
bytecode: string,
): Promise<string | undefined> {
): string | undefined {
const numericBytecode = hexToBytes(bytecode);
const cborLength: number =
numericBytecode[numericBytecode.length - 2] * 0x100 +
@@ -293,15 +295,13 @@ async function extractIPFSHashFromBytecode(
numericBytecode.slice(numericBytecode.length - 2 - cborLength, -2),
);
// load these lazily to avoid loading them when they are not needed
const [cbor, multiHashes] = await Promise.all([
import("cbor-web"),
import("multihashes"),
]);
const cborData = cbor.decodeFirstSync(bytecodeBuffer);
if (cborData["ipfs"]) {
return `ipfs://${multiHashes.toB58String(cborData["ipfs"])}`;
const cborData = decode(bytecodeBuffer);
if ("ipfs" in cborData && cborData["ipfs"]) {
try {
return `ipfs://${bs58.encode(cborData["ipfs"])}`;
} catch (e) {
console.warn("feature-detection ipfs cbor failed", e);
}
}
return undefined;
}
@@ -0,0 +1,216 @@
import {
ShardData,
ShardedMerkleTreeInfo,
ShardedSnapshot,
SnapshotEntry,
SnapshotEntryWithProof,
SnapshotInputSchema,
} from "../schema";
import { SnapshotInput } from "../types";
import { hashLeafNode } from "./snapshots";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import { utils } from "ethers";
import { MerkleTree } from "merkletreejs";
// shard using the first 2 hex character of the address
// this splits the merkle tree into 256 shards
// shard files will be 00.json, 01.json, 02.json, ..., ff.json
const SHARD_NYBBLES = 2;
export class ShardedMerkleTree {
private shardNybbles: number;
private shards: Record<string, ShardData>;
private trees: Record<string, MerkleTree>;
private storage: ThirdwebStorage;
private baseUri: string;
private originalEntriesUri: string;
private tokenDecimals: number;
constructor(
storage: ThirdwebStorage,
baseUri: string,
originalEntriesUri: string,
shardNybbles: number,
tokenDecimals: number,
) {
this.storage = storage;
this.shardNybbles = shardNybbles;
this.baseUri = baseUri;
this.originalEntriesUri = originalEntriesUri;
this.tokenDecimals = tokenDecimals;
this.shards = {};
this.trees = {};
}
static async fromUri(
uri: string,
storage: ThirdwebStorage,
): Promise<ShardedMerkleTree | undefined> {
try {
const shardedMerkleTreeInfo =
await storage.downloadJSON<ShardedMerkleTreeInfo>(uri);
if (shardedMerkleTreeInfo.isShardedMerkleTree) {
return ShardedMerkleTree.fromShardedMerkleTreeInfo(
shardedMerkleTreeInfo,
storage,
);
}
} catch (e) {
return undefined;
}
}
static async fromShardedMerkleTreeInfo(
info: ShardedMerkleTreeInfo,
storage: ThirdwebStorage,
): Promise<ShardedMerkleTree> {
return new ShardedMerkleTree(
storage,
info.baseUri,
info.originalEntriesUri,
info.shardNybbles,
info.tokenDecimals,
);
}
static hashEntry(entry: SnapshotEntry, tokenDecimals: number): string {
return hashLeafNode(
entry.address,
utils.parseUnits(entry.maxClaimable, tokenDecimals),
);
}
static async buildAndUpload(
snapshotInput: SnapshotInput,
tokenDecimals: number,
storage: ThirdwebStorage,
shardNybbles = SHARD_NYBBLES,
): Promise<ShardedSnapshot> {
const inputs = SnapshotInputSchema.parse(snapshotInput);
// TODO Could also derive shardNybbles from input size
const shards: Record<string, SnapshotEntry[]> = {};
for (const snapshotEntry of inputs) {
const shard = snapshotEntry.address
.slice(2, 2 + shardNybbles)
.toLowerCase();
if (shards[shard] === undefined) {
shards[shard] = [];
}
shards[shard].push(snapshotEntry);
}
// create shard => subtree root map
const roots = Object.fromEntries(
Object.entries(shards).map(([shard, entries]) => [
shard,
new MerkleTree(
entries.map((entry) =>
ShardedMerkleTree.hashEntry(entry, tokenDecimals),
),
utils.keccak256,
{
sort: true,
},
).getHexRoot(),
]),
);
// create master tree from shard => subtree root map
const tree = new MerkleTree(Object.values(roots), utils.keccak256, {
sort: true,
});
const shardsToUpload = [];
for (const [shardId, entries] of Object.entries(shards)) {
const data: ShardData = {
proofs: tree
.getProof(roots[shardId])
.map((value) => "0x" + value.data.toString("hex")),
entries,
};
shardsToUpload.push({
data: JSON.stringify(data),
name: `${shardId}.json`,
});
}
const uris = await storage.uploadBatch(shardsToUpload);
const baseUri = uris[0].slice(0, uris[0].lastIndexOf("/"));
const originalEntriesUri = await storage.upload(inputs);
const shardedMerkleInfo: ShardedMerkleTreeInfo = {
merkleRoot: tree.getHexRoot(),
baseUri,
originalEntriesUri,
shardNybbles,
tokenDecimals,
isShardedMerkleTree: true,
};
const masterUri = await storage.upload(shardedMerkleInfo);
return {
shardedMerkleInfo,
uri: masterUri,
};
}
public async getProof(
address: string,
): Promise<SnapshotEntryWithProof | null> {
const shardId = address.slice(2, 2 + this.shardNybbles).toLowerCase();
let shard = this.shards[shardId];
if (shard === undefined) {
try {
shard = this.shards[shardId] =
await this.storage.downloadJSON<ShardData>(
`${this.baseUri}/${shardId}.json`,
);
this.trees[shardId] = new MerkleTree(
shard.entries.map((entry) =>
ShardedMerkleTree.hashEntry(entry, this.tokenDecimals),
),
utils.keccak256,
{ sort: true },
);
} catch (e) {
console.warn("No merkle entry found for address", address);
return null;
}
}
const entry = shard.entries.find(
(i) => i.address.toLowerCase() === address.toLowerCase(),
);
if (!entry) {
return null;
}
const leaf = ShardedMerkleTree.hashEntry(entry, this.tokenDecimals);
const proof = this.trees[shardId]
.getProof(leaf)
.map((i) => "0x" + i.data.toString("hex"));
return {
address,
proof: proof.concat(shard.proofs),
maxClaimable: entry.maxClaimable,
};
}
public async getAllEntries(): Promise<SnapshotEntry[]> {
try {
const entries = await this.storage.downloadJSON<SnapshotEntry[]>(
this.originalEntriesUri,
);
return entries;
} catch (e) {
console.warn("Could not fetch original snapshot entries", e);
return [];
}
}
private computeAllShardIds(shardNybbles: number) {
const max = parseInt(`0x${"f".repeat(shardNybbles)}`, 16);
const allShards = [];
for (let i = 0; i <= max; i++) {
allShards.push(i.toString(16).padStart(shardNybbles, "0"));
}
return allShards;
}
}
+8 -29
View File
@@ -1,12 +1,10 @@
import {
SnapshotInputSchema,
SnapshotSchema,
} from "../schema/contracts/common/snapshots";
import { SnapshotInputSchema } from "../schema/contracts/common/snapshots";
import {
SnapshotInfo,
SnapshotInput,
} from "../types/claim-conditions/claim-conditions";
import { DuplicateLeafsError } from "./error";
import { ShardedMerkleTree } from "./sharded-merkle-tree";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import { BigNumber, BigNumberish, utils } from "ethers";
@@ -29,33 +27,14 @@ export async function createSnapshot(
if (hasDuplicates) {
throw new DuplicateLeafsError();
}
const hashedLeafs = input.map((i) =>
hashLeafNode(i.address, utils.parseUnits(i.maxClaimable, tokenDecimals)),
const tree = await ShardedMerkleTree.buildAndUpload(
input,
tokenDecimals,
storage,
);
// import dynamically to avoid bloating bundle size for non-merkle tree users
const MerkleTree = (await import("merkletreejs")).MerkleTree;
const tree = new MerkleTree(hashedLeafs, utils.keccak256, {
sort: true,
});
const snapshot = SnapshotSchema.parse({
merkleRoot: tree.getHexRoot(),
claims: input.map((i, index) => {
const proof = tree.getHexProof(hashedLeafs[index]);
return {
address: i.address,
maxClaimable: i.maxClaimable,
proof,
};
}),
});
const uri = await storage.upload(snapshot);
return {
merkleRoot: tree.getHexRoot(),
snapshotUri: uri,
snapshot,
merkleRoot: tree.shardedMerkleInfo.merkleRoot,
snapshotUri: tree.uri,
};
}
+24 -58
View File
@@ -1,3 +1,4 @@
import { getRpcUrl } from "../../core/constants/urls";
import { SignerOrProvider } from "../core/types";
import { StaticJsonRpcBatchProvider } from "../lib/static-batch-rpc";
import { ChainId, SUPPORTED_CHAIN_ID, SUPPORTED_CHAIN_IDS } from "./chains";
@@ -7,39 +8,16 @@ import { ethers, providers } from "ethers";
* @internal
*/
export const DEFAULT_IPFS_GATEWAY = "https://gateway.ipfscdn.io/ipfs/";
/**
* @internal
*/
export const PUBLIC_GATEWAYS = [
"https://gateway.ipfscdn.io/ipfs/",
"https://cloudflare-ipfs.com/ipfs/",
"https://ipfs.io/ipfs/",
];
/**
* @internal
*/
export const TW_IPFS_SERVER_URL = "https://upload.nftlabs.co";
/**
* @internal
*/
export const PINATA_IPFS_URL = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
/**
* @internal
* This is a community API key that is subject to rate limiting. Please use your own key.
*/
const DEFAULT_API_KEY = "_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC";
type ChainNames =
| "mumbai"
| "polygon"
// common alias for `polygon`
| "matic"
| "goerli"
| "mainnet"
// common alias for `mainnet`
| "ethereum"
| "goerli"
| "polygon"
// common alias for `polygon`
| "matic"
| "mumbai"
| "fantom"
| "fantom-testnet"
| "avalanche"
@@ -105,49 +83,37 @@ export function getProviderForNetwork(network: ChainOrRpc | SignerOrProvider) {
return network;
}
switch (network) {
case "mumbai":
return `https://polygon-mumbai.g.alchemy.com/v2/${DEFAULT_API_KEY}`;
case "rinkeby":
return `https://eth-rinkeby.g.alchemy.com/v2/${DEFAULT_API_KEY}`;
case "goerli":
return `https://eth-goerli.g.alchemy.com/v2/${DEFAULT_API_KEY}`;
case "polygon":
case "matic":
return `https://polygon-mainnet.g.alchemy.com/v2/${DEFAULT_API_KEY}`;
case "mainnet":
case "ethereum":
return `https://eth-mainnet.g.alchemy.com/v2/${DEFAULT_API_KEY}`;
return getRpcUrl("ethereum");
case "goerli":
return getRpcUrl("goerli");
case "polygon":
case "matic":
return getRpcUrl("polygon");
case "mumbai":
return getRpcUrl("mumbai");
case "optimism":
// TODO test this RPC
return `https://opt-mainnet.g.alchemy.com/v2/${DEFAULT_API_KEY}`;
case "optimism-kovan":
// alchemy optimism kovan rpc doesn't link to the testnet sequencer...
return "https://kovan.optimism.io";
return getRpcUrl("optimism");
case "optimism-goerli":
// TODO test this RPC
return `https://opt-goerli.g.alchemy.com/v2/${DEFAULT_API_KEY}`;
return getRpcUrl("optimism-goerli");
case "arbitrum":
// TODO test this RPC
return `https://arb-mainnet.g.alchemy.com/v2/${DEFAULT_API_KEY}`;
case "arbitrum-rinkeby":
// TODO test this RPC
return `https://arb-rinkeby.g.alchemy.com/v2/${DEFAULT_API_KEY}`;
return getRpcUrl("arbitrum");
case "arbitrum-goerli":
// TODO test this RPC
return `https://arb-goerli.g.alchemy.com/v2/${DEFAULT_API_KEY}`;
return getRpcUrl("arbitrum-goerli");
case "fantom":
return "https://rpc.ftm.tools";
return getRpcUrl("fantom");
case "fantom-testnet":
return "https://rpc.testnet.fantom.network/";
return getRpcUrl("fantom-testnet");
case "avalanche":
return "https://api.avax.network/ext/bc/C/rpc";
return getRpcUrl("avalanche");
case "avalanche-testnet":
case "avalanche-fuji":
return "https://api.avax-test.network/ext/bc/C/rpc";
return getRpcUrl("avalanche-fuji");
case "binance":
return "https://bsc-dataseed1.binance.org";
return getRpcUrl("binance");
case "binance-testnet":
return "https://data-seed-prebsc-1-s1.binance.org:8545";
return getRpcUrl("binance-testnet");
default:
if (network.startsWith("http") || network.startsWith("ws")) {
return network;
@@ -4,7 +4,6 @@ import {
} from "../../../core/schema/QueryParams";
import { NFT, NFTMetadata, NFTMetadataOrUri } from "../../../core/schema/nft";
import { getRoleHash } from "../../common";
import { prepareClaim } from "../../common/claim-conditions";
import { uploadOrExtractURIs } from "../../common/nft";
import { FEATURE_NFT_REVEALABLE } from "../../constants/erc721-features";
import { TransactionTask } from "../../core/classes/TransactionTask";
@@ -31,7 +30,6 @@ import {
import { PaperCheckout } from "../../integrations/paper-xyz";
import { DropErc721ContractSchema } from "../../schema/contracts/drop-erc721";
import { SDKOptions } from "../../schema/sdk-options";
import { ClaimVerification } from "../../types";
import { UploadProgressEvent } from "../../types/events";
import type { DropERC721 } from "@thirdweb-dev/contracts-js";
import type ABI from "@thirdweb-dev/contracts-js/dist/abis/DropERC721.json";
@@ -623,27 +621,6 @@ export class NFTDrop extends StandardErc721<DropERC721> {
/** ******************************
* PRIVATE FUNCTIONS
*******************************/
/**
* Returns proofs and the overrides required for the transaction.
*
* @returns - `overrides` and `proofs` as an object.
*/
private async prepareClaim(
quantity: BigNumberish,
checkERC20Allowance: boolean,
): Promise<ClaimVerification> {
return prepareClaim(
quantity,
await this.claimConditions.getActive(),
async () => (await this.metadata.get()).merkle,
0,
this.contractWrapper,
this.storage,
checkERC20Allowance,
);
}
/**
* @internal
*/
@@ -712,7 +712,7 @@ export class ContractDeployer extends RPCConnectionHandler {
ethers.utils.isHexString(constructorParamValues[index]),
`Could not parse bytes value. Expected valid hex string but got "${constructorParamValues[index]}".`,
);
return ethers.utils.toUtf8Bytes(constructorParamValues[index]);
return constructorParamValues[index];
}
if (p.startsWith("uint") || p.startsWith("int")) {
return BigNumber.from(constructorParamValues[index].toString());
@@ -18,6 +18,7 @@ import { AmountSchema } from "../../schema";
import {
Amount,
ClaimCondition,
ClaimConditionFetchOptions,
ClaimConditionInput,
ClaimVerification,
} from "../../types";
@@ -75,7 +76,9 @@ export class DropClaimConditions<
*
* @returns the claim condition metadata
*/
public async getActive(): Promise<ClaimCondition> {
public async getActive(
options?: ClaimConditionFetchOptions,
): Promise<ClaimCondition> {
const cc = await this.get();
const metadata = await this.metadata.get();
return await transformResultToClaimCondition(
@@ -84,6 +87,7 @@ export class DropClaimConditions<
this.contractWrapper.getProvider(),
metadata.merkle || {},
this.storage,
options?.withAllowList || false,
);
}
@@ -104,7 +108,9 @@ export class DropClaimConditions<
*
* @returns the claim conditions metadata
*/
public async getAll(): Promise<ClaimCondition[]> {
public async getAll(
options?: ClaimConditionFetchOptions,
): Promise<ClaimCondition[]> {
if (this.isMultiPhaseDropContract(this.contractWrapper)) {
const claimCondition =
(await this.contractWrapper.readContract.claimCondition()) as {
@@ -129,11 +135,12 @@ export class DropClaimConditions<
this.contractWrapper.getProvider(),
metadata.merkle,
this.storage,
options?.withAllowList || false,
),
),
);
} else {
return [await this.getActive()];
return [await this.getActive(options)];
}
}
@@ -13,6 +13,7 @@ import { NATIVE_TOKEN_ADDRESS } from "../../constants/index";
import { ClaimEligibility } from "../../enums";
import {
ClaimCondition,
ClaimConditionFetchOptions,
ClaimConditionInput,
ClaimConditionsForToken,
ClaimVerification,
@@ -63,7 +64,10 @@ export class DropErc1155ClaimConditions<
*
* @returns the claim condition metadata
*/
public async getActive(tokenId: BigNumberish): Promise<ClaimCondition> {
public async getActive(
tokenId: BigNumberish,
options?: ClaimConditionFetchOptions,
): Promise<ClaimCondition> {
const mc = await this.get(tokenId);
const metadata = await this.metadata.get();
return await transformResultToClaimCondition(
@@ -72,6 +76,7 @@ export class DropErc1155ClaimConditions<
this.contractWrapper.getProvider(),
metadata.merkle,
this.storage,
options?.withAllowList || false,
);
}
@@ -101,7 +106,10 @@ export class DropErc1155ClaimConditions<
*
* @returns the claim conditions metadata
*/
public async getAll(tokenId: BigNumberish): Promise<ClaimCondition[]> {
public async getAll(
tokenId: BigNumberish,
options?: ClaimConditionFetchOptions,
): Promise<ClaimCondition[]> {
if (this.isMultiPhaseDropContract(this.contractWrapper)) {
const claimCondition =
(await this.contractWrapper.readContract.claimCondition(tokenId)) as {
@@ -128,6 +136,7 @@ export class DropErc1155ClaimConditions<
this.contractWrapper.getProvider(),
metadata.merkle,
this.storage,
options?.withAllowList || false,
),
),
);
@@ -159,6 +159,34 @@ export class Erc1155SignatureMintable implements DetectableFeature {
* Verify that a payload is correctly signed
* @param signedPayload - the payload to verify
* @twfeature ERC1155SignatureMintable
*
* @example
* ```javascript
* const nftMetadata = {
* name: "Cool NFT #1",
* description: "This is a cool NFT",
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
* };
*
* const startTime = new Date();
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
* const payload = {
* metadata: nftMetadata, // The NFT to mint
* to: {{wallet_address}}, // Who will receive the NFT (or AddressZero for anyone)
* quantity: 2, // the quantity of NFTs to mint
* price: 0.5, // the price per NFT
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
* mintStartTime: startTime, // can mint anytime from now
* mintEndTime: endTime, // to 24h from now
* royaltyRecipient: "0x...", // custom royalty recipient for this NFT
* royaltyBps: 100, // custom royalty fees for this NFT (in bps)
* primarySaleRecipient: "0x...", // custom sale recipient for this NFT
* };
*
* const signedPayload = contract.erc1155.signature.generate(payload);
* // Now you can verify that the payload is valid
* const isValid = await contract.erc1155.signature.verify(signedPayload);
* ```
*/
public async verify(signedPayload: SignedPayload1155): Promise<boolean> {
const mintRequest = signedPayload.payload;
@@ -197,7 +225,7 @@ export class Erc1155SignatureMintable implements DetectableFeature {
* primarySaleRecipient: "0x...", // custom sale recipient for this NFT
* };
*
* const signedPayload = contract.erc1155.signature.generate(payload);
* const signedPayload = await contract.erc1155.signature.generate(payload);
* // now anyone can use these to mint the NFT using `contract.erc1155.signature.mint(signedPayload)`
* ```
* @param payloadToSign - the payload to sign
@@ -242,7 +270,7 @@ export class Erc1155SignatureMintable implements DetectableFeature {
* primarySaleRecipient: "0x...", // custom sale recipient for this NFT
* };
*
* const signedPayload = contract.erc1155.signature.generate(payload);
* const signedPayload = await contract.erc1155.signature.generateFromTokenId(payload);
* // now anyone can use these to mint the NFT using `contract.erc1155.signature.mint(signedPayload)`
* ```
* @param payloadToSign - the payload to sign
@@ -1,7 +1,15 @@
import { normalizePriceValue, setErc20Allowance } from "../../common/currency";
import { FEATURE_TOKEN_SIGNATURE_MINTABLE } from "../../constants/erc20-features";
import type { TokenInitializer } from "../../contracts";
import { FilledSignaturePayload20, MintRequest20, PayloadToSign20, PayloadWithUri20, Signature20PayloadInput, Signature20PayloadOutput, SignedPayload20 } from "../../schema/contracts/common/signature";
import {
FilledSignaturePayload20,
MintRequest20,
PayloadToSign20,
PayloadWithUri20,
Signature20PayloadInput,
Signature20PayloadOutput,
SignedPayload20,
} from "../../schema/contracts/common/signature";
import { DetectableFeature } from "../interfaces/DetectableFeature";
import { TransactionResult } from "../types";
import { ContractRoles } from "./contract-roles";
@@ -10,7 +18,6 @@ import type { ITokenERC20, TokenERC20 } from "@thirdweb-dev/contracts-js";
import { BigNumber, ethers } from "ethers";
import invariant from "tiny-invariant";
/**
* Enables generating ERC20 Tokens with rules and an associated signature, which can then be minted by anyone securely
* @public
@@ -110,6 +117,24 @@ export class Erc20SignatureMintable implements DetectableFeature {
* Verify that a payload is correctly signed
* @param signedPayload - the payload to verify
* @twfeature ERC20SignatureMintable
*
* ```javascript
* const startTime = new Date();
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
* const payload = {
* quantity: 4.2, // The quantity of tokens to be minted
* to: {{wallet_address}}, // Who will receive the tokens (or AddressZero for anyone)
* price: 0.5, // the price to pay for minting those tokens
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
* mintStartTime: startTime, // can mint anytime from now
* mintEndTime: endTime, // to 24h from now,
* primarySaleRecipient: "0x...", // custom sale recipient for this token mint
* };
*
* const signedPayload = await contract.erc20.signature.generate(payload);
* // Now you can verify if the signed payload is valid
* const isValid = await contract.erc20.signature.verify(signedPayload);
* ```
*/
public async verify(signedPayload: SignedPayload20): Promise<boolean> {
const mintRequest = signedPayload.payload;
@@ -139,7 +164,7 @@ export class Erc20SignatureMintable implements DetectableFeature {
* primarySaleRecipient: "0x...", // custom sale recipient for this token mint
* };
*
* const signedPayload = contract.erc20.signature.generate(payload);
* const signedPayload = await contract.erc20.signature.generate(payload);
* // now anyone can use these to mint the NFT using `contract.erc20.signature.mint(signedPayload)`
* ```
* @param mintRequest - the payload to sign
@@ -237,4 +262,4 @@ export class Erc20SignatureMintable implements DetectableFeature {
uid: mintRequest.uid,
} as ITokenERC20.MintRequestStructOutput;
}
}
}
@@ -177,6 +177,34 @@ export class Erc721WithQuantitySignatureMintable implements DetectableFeature {
* Verify that a payload is correctly signed
* @param signedPayload - the payload to verify
* @twfeature ERC721SignatureMint
*
* @example
* ```javascript
* const nftMetadata = {
* name: "Cool NFT #1",
* description: "This is a cool NFT",
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
* };
*
* const startTime = new Date();
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
* const payload = {
* metadata: nftMetadata, // The NFT to mint
* to: {{wallet_address}}, // Who will receive the NFT (or AddressZero for anyone)
* quantity: 2, // the quantity of NFTs to mint
* price: 0.5, // the price per NFT
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
* mintStartTime: startTime, // can mint anytime from now
* mintEndTime: endTime, // to 24h from now
* royaltyRecipient: "0x...", // custom royalty recipient for this NFT
* royaltyBps: 100, // custom royalty fees for this NFT (in bps)
* primarySaleRecipient: "0x...", // custom sale recipient for this NFT
* };
*
* const signedPayload = await contract.erc721.signature.generate(payload);
* // Now you can verify if the signed payload is valid
* const isValid = await contract.erc721.signature.verify(signedPayload);
* ```
*/
public async verify(
signedPayload: SignedPayload721WithQuantitySignature,
@@ -230,7 +258,7 @@ export class Erc721WithQuantitySignatureMintable implements DetectableFeature {
* primarySaleRecipient: "0x...", // custom sale recipient for this NFT
* };
*
* const signedPayload = contract.erc721.signature.generate(payload);
* const signedPayload = await contract.erc721.signature.generate(payload);
* // now anyone can use these to mint the NFT using `contract.erc721.signature.mint(signedPayload)`
* ```
* @param mintRequest - the payload to sign
@@ -5,6 +5,7 @@ import {
WrongListingTypeError,
} from "../../common";
import {
cleanCurrencyAddress,
fetchCurrencyMetadata,
fetchCurrencyValue,
normalizePriceValue,
@@ -233,7 +234,9 @@ export class MarketplaceAuction {
assetContract: listing.assetContractAddress,
tokenId: listing.tokenId,
buyoutPricePerToken: normalizedPricePerToken,
currencyToAccept: listing.currencyContractAddress,
currencyToAccept: cleanCurrencyAddress(
listing.currencyContractAddress,
),
listingType: ListingType.Auction,
quantityToList: listing.quantity,
reservePricePerToken: normalizedReservePrice,
@@ -1,5 +1,6 @@
import { ListingNotFoundError, WrongListingTypeError } from "../../common";
import {
cleanCurrencyAddress,
fetchCurrencyValue,
isNativeToken,
normalizePriceValue,
@@ -188,7 +189,9 @@ export class MarketplaceDirect {
assetContract: listing.assetContractAddress,
tokenId: listing.tokenId,
buyoutPricePerToken: normalizedPricePerToken,
currencyToAccept: listing.currencyContractAddress,
currencyToAccept: cleanCurrencyAddress(
listing.currencyContractAddress,
),
listingType: ListingType.Direct,
quantityToList: listing.quantity,
reservePricePerToken: normalizedPricePerToken,
+1
View File
@@ -44,3 +44,4 @@ export type { Split } from "./contracts/prebuilt-implementations/split";
export type { Token } from "./contracts/prebuilt-implementations/token";
export type { TokenDrop } from "./contracts/prebuilt-implementations/token-drop";
export type { Vote } from "./contracts/prebuilt-implementations/vote";
export type { SmartContract } from "./contracts/smart-contract";
@@ -23,7 +23,7 @@ export const ClaimConditionInputSchema = z.object({
quantityLimitPerTransaction: QuantitySchema,
waitInSeconds: BigNumberishSchema.default(0),
merkleRootHash: BytesLikeSchema.default(utils.hexZeroPad([0], 32)),
snapshot: z.optional(SnapshotInputSchema),
snapshot: z.optional(SnapshotInputSchema).nullable(),
});
/**
@@ -53,4 +53,5 @@ export const ClaimConditionOutputSchema = ClaimConditionInputSchema.extend({
price: BigNumberSchema,
waitInSeconds: BigNumberSchema,
startTime: BigNumberSchema.transform((n) => new Date(n.toNumber() * 1000)),
snapshot: SnapshotInputSchema.optional().nullable(),
});
@@ -11,25 +11,47 @@ export const MerkleSchema = z.object({
/**
* @internal
*/
export const SnapshotAddressInput = z.object({
export const SnapshotEntryInput = z.object({
address: AddressSchema,
maxClaimable: AmountSchema.default(0),
});
export type SnapshotEntry = z.output<typeof SnapshotEntryInput>;
export type ShardData = {
proofs: string[];
entries: SnapshotEntry[];
};
export type ShardedMerkleTreeInfo = {
merkleRoot: string;
baseUri: string;
originalEntriesUri: string;
shardNybbles: number;
tokenDecimals: number;
isShardedMerkleTree: true;
};
export type ShardedSnapshot = {
shardedMerkleInfo: ShardedMerkleTreeInfo;
uri: string;
};
/**
* @internal
*/
export const SnapshotInputSchema = z.union([
z.array(z.string()).transform((strings) =>
strings.map((address) =>
SnapshotAddressInput.parse({
SnapshotEntryInput.parse({
address,
}),
),
),
z.array(SnapshotAddressInput),
z.array(SnapshotEntryInput),
]);
const SnapshotEntryWithProofSchema = SnapshotEntryInput.extend({
proof: z.array(z.string()),
});
/**
* @internal
*/
@@ -38,12 +60,14 @@ export const SnapshotSchema = z.object({
* The merkle root
*/
merkleRoot: z.string(),
claims: z.array(
SnapshotAddressInput.extend({
proof: z.array(z.string()),
}),
),
claims: z.array(SnapshotEntryWithProofSchema),
});
/**
* @internal
*/
export type SnapshotEntryWithProof = z.output<
typeof SnapshotEntryWithProofSchema
>;
/**
* @internal
@@ -51,5 +75,4 @@ export const SnapshotSchema = z.object({
export const SnapshotInfoSchema = z.object({
merkleRoot: z.string(),
snapshotUri: z.string(),
snapshot: SnapshotSchema,
});
@@ -77,3 +77,7 @@ export type ClaimOptions = {
*/
checkERC20Allowance?: boolean;
};
export type ClaimConditionFetchOptions = {
withAllowList?: boolean;
};
-21
View File
@@ -1,21 +0,0 @@
/**
* @internal
*/
export const DEFAULT_IPFS_GATEWAY = "https://gateway.ipfscdn.io/ipfs/";
/**
* @internal
*/
export const PUBLIC_GATEWAYS = [
"https://gateway.ipfscdn.io/ipfs/",
"https://cloudflare-ipfs.com/ipfs/",
"https://ipfs.io/ipfs/",
];
/**
* @internal
*/
export const TW_IPFS_SERVER_URL = "https://upload.nftlabs.co";
/**
* @internal
*/
export const PINATA_IPFS_URL = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
+3 -8
View File
@@ -1,13 +1,8 @@
import { getRpcUrl } from "../../core/constants/urls";
import { Network } from "../types/index";
import { Metaplex } from "@metaplex-foundation/js";
import { Cluster, clusterApiUrl } from "@solana/web3.js";
/**
* @internal
* This is a community API key that is subject to rate limiting. Please use your own key.
*/
const DEFAULT_API_KEY = "_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC";
/**
* @internal
*/
@@ -18,9 +13,9 @@ export function getUrlForNetwork(network: Network) {
case "testnet":
return clusterApiUrl(network as Cluster);
case "mainnet-beta":
return `https://solana-mainnet.g.alchemy.com/v2/${DEFAULT_API_KEY}`;
return getRpcUrl("solana");
case "devnet":
return `https://solana-devnet.g.alchemy.com/v2/${DEFAULT_API_KEY}`;
return getRpcUrl("solana-devnet");
default:
return network;
}
+3 -1
View File
@@ -639,7 +639,9 @@ describe("Edition Drop Contract", async () => {
snapshot: [samWallet.address],
},
]);
const conditions = await bdContract.claimConditions.getAll(0);
const conditions = await bdContract.claimConditions.getAll(0, {
withAllowList: true,
});
assert.lengthOf(conditions, 1);
invariant(conditions[0].snapshot);
expect(conditions[0].snapshot[0].address).to.eq(samWallet.address);
+17
View File
@@ -203,6 +203,23 @@ describe("Marketplace Contract", async () => {
await marketplaceContract.auction.makeBid(id, 0.1);
});
it("Should list with native currency address", async () => {
const { id: listingId } = await marketplaceContract.direct.createListing({
assetContractAddress: dummyNftContract.getAddress(),
buyoutPricePerToken: 0.1,
currencyContractAddress: "0x0000000000000000000000000000000000000000",
startTimestamp: new Date(0), // start date can be in the past
listingDurationInSeconds: 60 * 60 * 24,
tokenId: 0,
quantity: 1,
});
const listing = await marketplaceContract.getListing(listingId);
expect(listing.currencyContractAddress.toLowerCase()).to.equal(
NATIVE_TOKEN_ADDRESS.toLowerCase(),
);
});
it("should list direct listings with 1155s", async () => {
const listingId = await createDirectListing(
dummyBundleContract.getAddress(),
+8 -3
View File
@@ -6,6 +6,7 @@ import {
NFTDrop,
TokenInitializer,
} from "../../src/evm";
import { ShardedMerkleTree } from "../../src/evm/common/sharded-merkle-tree";
import { expectError, sdk, signers, storage } from "./before-setup";
import { AddressZero } from "@ethersproject/constants";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
@@ -109,7 +110,9 @@ describe("NFT Drop Contract", async () => {
snapshot: [samWallet.address],
},
]);
const conditions = await dropContract.claimConditions.getAll();
const conditions = await dropContract.claimConditions.getAll({
withAllowList: true,
});
assert.lengthOf(conditions, 1);
invariant(conditions[0].snapshot);
expect(conditions[0].snapshot[0].address).to.eq(samWallet.address);
@@ -386,9 +389,11 @@ describe("NFT Drop Contract", async () => {
ethers.utils.solidityKeccak256(["address", "uint256"], [leaf, 0]),
);
const actualProof = snapshot.snapshot.claims.find(
(c) => c.address === leaf,
const smt = await ShardedMerkleTree.fromUri(
snapshot.snapshotUri,
storage,
);
const actualProof = await smt?.getProof(leaf);
assert.isDefined(actualProof);
expect(actualProof?.proof).to.include.ordered.members(expectedProof);
+10
View File
@@ -477,4 +477,14 @@ describe("Publishing", async () => {
});
expect(tx).to.not.eq(undefined);
});
it("bytes4 test", async () => {
const ipfsUri = "ipfs://QmTTdsRTtxQXHfUwCGK6epXhvuEwjBwLFcCjGUYDRUQc95";
const addr = await sdk.deployer.deployContractFromUri(ipfsUri, [
"test",
"tt",
"0x01551220", // bytes4 param
]);
expect(addr).to.not.eq(undefined);
});
});
+8 -4
View File
@@ -9,6 +9,7 @@ import {
Token,
TokenInitializer,
} from "../../src/evm";
import { ShardedMerkleTree } from "../../src/evm/common/sharded-merkle-tree";
import { expectError, sdk, signers, storage } from "./before-setup";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { assert, expect } from "chai";
@@ -481,8 +482,9 @@ describe("Signature drop tests", async () => {
snapshot: [samWallet.address],
},
]);
const conditions =
await signatureDropContract.claimConditions.getActive();
const conditions = await signatureDropContract.claimConditions.getActive({
withAllowList: true,
});
invariant(conditions.snapshot);
expect(conditions.snapshot[0].address).to.eq(samWallet.address);
});
@@ -749,9 +751,11 @@ describe("Signature drop tests", async () => {
ethers.utils.solidityKeccak256(["address", "uint256"], [leaf, 0]),
);
const actualProof = snapshot.snapshot.claims.find(
(c) => c.address === leaf,
const smt = await ShardedMerkleTree.fromUri(
snapshot.snapshotUri,
storage,
);
const actualProof = await smt?.getProof(leaf);
assert.isDefined(actualProof);
expect(actualProof?.proof).to.include.ordered.members(expectedProof);
+16 -34
View File
@@ -1,6 +1,8 @@
import { ShardedMerkleTree } from "../../src/evm/common/sharded-merkle-tree";
import { createSnapshot, Snapshot } from "../../src/evm/index";
import { MockStorage } from "./mock/MockStorage";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import { BigNumber } from "ethers";
const chai = require("chai");
const deepEqualInAnyOrder = require("deep-equal-in-any-order");
@@ -12,8 +14,6 @@ const { expect, assert } = chai;
global.fetch = require("cross-fetch");
describe("Snapshots", async () => {
let snapshot: Snapshot;
let uri: string;
let merkleRoot: string;
const leafs = [
@@ -23,9 +23,9 @@ describe("Snapshots", async () => {
"0x14fb3a9B317612ddc6d6Cc3c907CD9F2Aa091eE7",
];
const input = leafs.map((address) => ({
const input = leafs.map((address, i) => ({
address,
maxClaimable: 0,
maxClaimable: BigNumber.from(i + 1).toString(),
}));
let storage: ThirdwebStorage;
@@ -36,22 +36,30 @@ describe("Snapshots", async () => {
beforeEach(async () => {
const result = await createSnapshot(input, 0, storage);
snapshot = result.snapshot;
uri = result.snapshotUri;
merkleRoot = result.merkleRoot;
});
it("should shard merkle tree", async () => {
const result = await ShardedMerkleTree.buildAndUpload(input, 0, storage);
const sm = await ShardedMerkleTree.fromUri(result.uri, storage);
const proofs = await sm?.getProof(
"0xE79ee09bD47F4F5381dbbACaCff2040f2FbC5803",
);
expect(proofs?.maxClaimable).to.equal("1");
expect(proofs?.proof).length(2);
});
it("should generate a valid merkle root from a list of addresses", async () => {
assert.equal(
merkleRoot,
"0xe0c95ec2a9cc03bb25cdf2f3c9092a00698716373e4e34715498a68167fe4acd",
"0x8ef5aa32e8d987fe1e5b93155939ebfbb897746f476f0a97ad5354aa5317c751",
);
});
it("should warn about duplicate leafs", async () => {
const duplicateLeafs = input.concat({
address: "0xE79ee09bD47F4F5381dbbACaCff2040f2FbC5803",
maxClaimable: 0,
maxClaimable: "0",
});
try {
@@ -65,30 +73,4 @@ describe("Snapshots", async () => {
"should not reach this point, exception should have been thrown",
);
});
it("should contain the same number of claims as there are leafs", () => {
assert.lengthOf(snapshot.claims, leafs.length);
});
it("should contain a proof for every claim", () => {
assert.lengthOf(snapshot.claims, leafs.length);
snapshot.claims.forEach((claim) => {
assert.isNotEmpty(claim.proof);
});
});
it("should contain a claim for each leaf", () => {
leafs.forEach((leaf) => {
assert.notEqual(
snapshot.claims.find((c) => c.address === leaf),
undefined,
);
});
});
it("should upload the snapshot to storage", async () => {
const rawSnapshotJson = await storage.downloadJSON(uri);
expect(rawSnapshotJson).to.deep.equalInAnyOrder(snapshot);
});
});
+8 -3
View File
@@ -6,6 +6,7 @@ import {
TokenDropInitializer,
TokenInitializer,
} from "../../src/evm";
import { ShardedMerkleTree } from "../../src/evm/common/sharded-merkle-tree";
import { expectError, sdk, signers, storage } from "./before-setup";
import { AddressZero } from "@ethersproject/constants";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
@@ -81,7 +82,9 @@ describe("Token Drop Contract", async () => {
snapshot: [samWallet.address],
},
]);
const conditions = await dropContract.claimConditions.getAll();
const conditions = await dropContract.claimConditions.getAll({
withAllowList: true,
});
assert.lengthOf(conditions, 1);
invariant(conditions[0].snapshot);
expect(conditions[0].snapshot[0].address).to.eq(samWallet.address);
@@ -268,9 +271,11 @@ describe("Token Drop Contract", async () => {
ethers.utils.solidityKeccak256(["address", "uint256"], [leaf, 0]),
);
const actualProof = snapshot.snapshot.claims.find(
(c) => c.address === leaf,
const smt = await ShardedMerkleTree.fromUri(
snapshot.snapshotUri,
storage,
);
const actualProof = await smt?.getProof(leaf);
assert.isDefined(actualProof);
expect(actualProof?.proof).to.include.ordered.members(expectedProof);
-4
View File
@@ -1,4 +0,0 @@
declare module "cbor-web" {
import { decodeFirstSync } from "cbor";
export { decodeFirstSync };
}
+12
View File
@@ -1,5 +1,17 @@
# @thirdweb-dev/storage
## 1.0.6
### 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
## 1.0.5
### Patch Changes
- [#324](https://github.com/thirdweb-dev/js/pull/324) [`87fd6ab`](https://github.com/thirdweb-dev/js/commit/87fd6ab14e1a67a1b12e72bd397fb21769537307) Thanks [@adam-maj](https://github.com/adam-maj)! - Update storage download
## 1.0.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/storage",
"version": "1.0.4",
"version": "1.0.6",
"main": "dist/thirdweb-dev-storage.cjs.js",
"module": "dist/thirdweb-dev-storage.esm.js",
"exports": {
+5 -7
View File
@@ -110,18 +110,16 @@ export function replaceSchemeWithGatewayUrl(
uri: string,
gatewayUrls: GatewayUrls,
index = 0,
): string {
): string | undefined {
const scheme = Object.keys(gatewayUrls).find((s) => uri.startsWith(s));
const schemeGatewayUrls = scheme ? gatewayUrls[scheme] : [];
if (!scheme) {
return uri;
if ((!scheme && index > 0) || (scheme && index >= schemeGatewayUrls.length)) {
return undefined;
}
if (index > schemeGatewayUrls.length) {
throw new Error(
"[GATEWAY_URL_ERROR] Failed to resolve gateway URL - ran out of gateway URLs to try.",
);
if (!scheme) {
return uri;
}
return uri.replace(scheme, schemeGatewayUrls[index]);
@@ -20,25 +20,25 @@ export class StorageDownloader implements IStorageDownloader {
gatewayUrls: GatewayUrls,
attempts = 0,
): Promise<Response> {
// Replace recognized scheme with the highest priority gateway URL that hasn't already been attempted
let resolvedUri;
try {
resolvedUri = replaceSchemeWithGatewayUrl(uri, gatewayUrls, attempts);
} catch (err: any) {
// If every gateway URL we know about for the designated scheme has been tried (via recursion) and failed, throw an error
if (err.includes("[GATEWAY_URL_ERROR]")) {
throw new Error(
"[FAILED_TO_DOWNLOAD_ERROR] Unable to download from URI - all gateway URLs failed to respond.",
);
}
if (attempts > 3) {
throw new Error(
"[FAILED_TO_DOWNLOAD_ERROR] Failed to download from URI - too many attempts failed.",
);
}
throw err;
// Replace recognized scheme with the highest priority gateway URL that hasn't already been attempted
const resolvedUri = replaceSchemeWithGatewayUrl(uri, gatewayUrls, attempts);
// If every gateway URL we know about for the designated scheme has been tried (via recursion) and failed, throw an error
if (!resolvedUri) {
throw new Error(
"[FAILED_TO_DOWNLOAD_ERROR] Unable to download from URI - all gateway URLs failed to respond.",
);
}
const res = await fetch(resolvedUri);
// If request to the current gateway fails, recursively try the next one we know about
if (!res.ok) {
if (res.status >= 500 || res.status === 403 || res.status === 408) {
console.warn(
`Request to ${resolvedUri} failed with status ${res.status} - ${res.statusText}`,
);
+1 -1
View File
@@ -72,7 +72,7 @@ export class ThirdwebStorage<T extends UploadOptions = IpfsUploadBatchOptions> {
* ```
*/
resolveScheme(url: string): string {
return replaceSchemeWithGatewayUrl(url, this.gatewayUrls);
return replaceSchemeWithGatewayUrl(url, this.gatewayUrls) as string;
}
/**
+9
View File
@@ -0,0 +1,9 @@
# e2e-cra-5
## 0.1.1
### Patch Changes
- Updated dependencies [[`ef27aad`](https://github.com/thirdweb-dev/js/commit/ef27aad0aafc4577e85f44dc77dfbe880bd239b5), [`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), [`b03a902`](https://github.com/thirdweb-dev/js/commit/b03a9021451b79f802f682f66e5ae8e9355d7e6f)]:
- @thirdweb-dev/react@3.4.0
- @thirdweb-dev/sdk@3.4.0
@@ -0,0 +1,8 @@
import { test, expect } from "@playwright/test";
test("should work", async ({ page }, testInfo) => {
await page.goto("http://localhost:" + testInfo.config.webServer!.port);
const connectWalletEl = page.getByText("Connect Wallet");
await expect(connectWalletEl).toBeVisible();
});
+33
View File
@@ -0,0 +1,33 @@
{
"name": "e2e-cra-5",
"version": "0.1.1",
"private": true,
"dependencies": {
"@thirdweb-dev/react": "*",
"@thirdweb-dev/sdk": "*",
"ethers": "^5.7.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "^5"
},
"scripts": {
"build": "react-scripts build",
"e2e": "npx playwright test"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@playwright/test": "^1.27.1",
"serve": "^14.0.1"
}
}
+109
View File
@@ -0,0 +1,109 @@
import type { PlaywrightTestConfig } from "@playwright/test";
import { devices } from "@playwright/test";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: "./fixtures",
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
// timeout: 5000,
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
},
},
{
name: "firefox",
use: {
...devices["Desktop Firefox"],
},
},
{
name: "webkit",
use: {
...devices["Desktop Safari"],
},
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: {
// ...devices['Pixel 5'],
// },
// },
// {
// name: 'Mobile Safari',
// use: {
// ...devices['iPhone 12'],
// },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: {
// channel: 'msedge',
// },
// },
// {
// name: 'Google Chrome',
// use: {
// channel: 'chrome',
// },
// },
],
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
// outputDir: 'test-results/',
/* Run your local dev server before starting the tests */
webServer: {
command: "yarn serve -s build -p 3040",
port: 3040,
// its static so yeah go for it
reuseExistingServer: true,
},
};
export default config;
+37
View File
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
+26
View File
@@ -0,0 +1,26 @@
import { ConnectWallet, ThirdwebProvider } from "@thirdweb-dev/react";
import React from "react";
import ReactDOM from "react-dom/client";
const KitchenSink = () => {
return (
<ThirdwebProvider>
<WrappedKitchenSink />
</ThirdwebProvider>
);
};
const WrappedKitchenSink = () => {
return (
<div id="kitchen-sink">
<ConnectWallet />
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<KitchenSink />
</React.StrictMode>,
);
+9
View File
@@ -0,0 +1,9 @@
# e2e-next-12
## 0.1.1
### Patch Changes
- Updated dependencies [[`ef27aad`](https://github.com/thirdweb-dev/js/commit/ef27aad0aafc4577e85f44dc77dfbe880bd239b5), [`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), [`b03a902`](https://github.com/thirdweb-dev/js/commit/b03a9021451b79f802f682f66e5ae8e9355d7e6f)]:
- @thirdweb-dev/react@3.4.0
- @thirdweb-dev/sdk@3.4.0

Some files were not shown because too many files have changed in this diff Show More