Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
564b17e760 | ||
|
|
b4507db963 | ||
|
|
f451da6395 | ||
|
|
ee47840767 | ||
|
|
bef97f72f4 | ||
|
|
3e87dd3ccb | ||
|
|
def42511ff | ||
|
|
c42545a647 | ||
|
|
6e20d1f21a | ||
|
|
e684d7e1ca | ||
|
|
f545a67e9f | ||
|
|
034a257442 | ||
|
|
1bfb91dbf3 | ||
|
|
5d95cbcdb2 | ||
|
|
639e535ed5 | ||
|
|
af3acc6b10 | ||
|
|
eca47763cd | ||
|
|
37b5bec897 | ||
|
|
48d91e4431 | ||
|
|
a20be881e3 | ||
|
|
47f8945325 | ||
|
|
1b0bf99da7 | ||
|
|
f03be398e0 | ||
|
|
27996cb4bc | ||
|
|
2eaa67acbe | ||
|
|
aafd7968df | ||
|
|
48797c7cf0 | ||
|
|
4edcb37100 | ||
|
|
83dcaf7058 |
@@ -13,6 +13,6 @@ runs:
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: yarn install
|
||||
run: yarn install --frozen-lockfile
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
| [/storage](./packages/storage) | Best in class decentralized storage SDK for Browser and Node | <a href="https://www.npmjs.com/package/@thirdweb-dev/storage"><img src="https://img.shields.io/npm/v/@thirdweb-dev/storage?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
| [/cli](./packages/cli) | Publish and deploy smart contracts without dealing with private keys | <a href="https://www.npmjs.com/package/thirdweb"><img src="https://img.shields.io/npm/v/thirdweb?color=red&label=npm&logo=npm" alt="npm version"/></a> |
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit [https://portal.thirdweb.com/](https://portal.thirdweb.com/) to view the full documentation.
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @thirdweb-dev/auth
|
||||
|
||||
## 2.0.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#425](https://github.com/thirdweb-dev/js/pull/425) [`f545a67`](https://github.com/thirdweb-dev/js/commit/f545a67e9fb597d27bb39ca515d24d306fbb121a) Thanks [@adam-maj](https://github.com/adam-maj)! - Add sameSite none and include credentials
|
||||
|
||||
## 2.0.33
|
||||
|
||||
## 2.0.32
|
||||
|
||||
## 2.0.31
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "2.0.32",
|
||||
"version": "2.0.34",
|
||||
"exports": {
|
||||
"./next": {
|
||||
"module": "./next/dist/thirdweb-dev-auth-next.esm.js",
|
||||
|
||||
@@ -43,7 +43,7 @@ export default async function handler(
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
sameSite: "none",
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -40,11 +40,11 @@ export function ThirdwebNextAuth(cfg: ThirdwebNextAuthConfig) {
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
sameSite: "none",
|
||||
}),
|
||||
);
|
||||
|
||||
return { address };
|
||||
return { id: address, address };
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export default async function handler(
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
sameSite: "none",
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export default async function handler(
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "strict",
|
||||
sameSite: "none",
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
# thirdweb
|
||||
|
||||
## 0.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#430](https://github.com/thirdweb-dev/js/pull/430) [`e684d7e`](https://github.com/thirdweb-dev/js/commit/e684d7e1ca16f633bce8698529d16bf60c264aa4) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix uploading contracts with special characters in the comments
|
||||
|
||||
- [#435](https://github.com/thirdweb-dev/js/pull/435) [`bef97f7`](https://github.com/thirdweb-dev/js/commit/bef97f72f4860596b7b70b05e67fcadf2c9a012e) Thanks [@adam-maj](https://github.com/adam-maj)! - Make solc default backup compilation
|
||||
|
||||
- [#432](https://github.com/thirdweb-dev/js/pull/432) [`3e87dd3`](https://github.com/thirdweb-dev/js/commit/3e87dd3ccb04a2b73437357c939a251e83b783b2) Thanks [@adam-maj](https://github.com/adam-maj)! - Display file name on duplicate contract names
|
||||
|
||||
- [#431](https://github.com/thirdweb-dev/js/pull/431) [`c42545a`](https://github.com/thirdweb-dev/js/commit/c42545a64725492136025fa1dcdb4d46ba674a30) Thanks [@adam-maj](https://github.com/adam-maj)! - Add changeset
|
||||
|
||||
- [#428](https://github.com/thirdweb-dev/js/pull/428) [`6e20d1f`](https://github.com/thirdweb-dev/js/commit/6e20d1f21a622f102e94b956f700d0871c0dc779) Thanks [@adam-maj](https://github.com/adam-maj)! - Update CLI to enable create contract inside contracts project
|
||||
|
||||
- Updated dependencies [[`f451da6`](https://github.com/thirdweb-dev/js/commit/f451da6395689a5f89800ee63f34b6175b61f703), [`af3acc6`](https://github.com/thirdweb-dev/js/commit/af3acc6b10751b840e56aef6400da5eea6040df2), [`1bfb91d`](https://github.com/thirdweb-dev/js/commit/1bfb91dbf3a39160c987a425813ac5dbb84703ad), [`ee47840`](https://github.com/thirdweb-dev/js/commit/ee478407673b0416e0c1cfe2be11bd6963395348), [`def4251`](https://github.com/thirdweb-dev/js/commit/def42511ff5a20d83f9094164dafb87e412571b5), [`034a257`](https://github.com/thirdweb-dev/js/commit/034a257442314c67729f7bafcbe3740cff33fa32), [`eca4776`](https://github.com/thirdweb-dev/js/commit/eca47763cd89cc3b7aa57b542971837987540b55), [`639e535`](https://github.com/thirdweb-dev/js/commit/639e535ed55280ad9d081001aab3f5af72bb3e45)]:
|
||||
- @thirdweb-dev/sdk@3.6.4
|
||||
|
||||
## 0.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#415](https://github.com/thirdweb-dev/js/pull/415) [`48d91e4`](https://github.com/thirdweb-dev/js/commit/48d91e4431a3740d0f98f730ed5abf5b287f92c1) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Fix mappings for prebuilts redirect on deploy
|
||||
|
||||
## 0.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#403](https://github.com/thirdweb-dev/js/pull/403) [`27996cb`](https://github.com/thirdweb-dev/js/commit/27996cb4bc6f054f45b0a66b0a3aca732e0ef610) Thanks [@avneesh0612](https://github.com/avneesh0612)! - Add CLI Support for solana with --solana flag on create command
|
||||
|
||||
- [#283](https://github.com/thirdweb-dev/js/pull/283) [`2eaa67a`](https://github.com/thirdweb-dev/js/commit/2eaa67acbe21464e7715d0b696afdc024460a44d) Thanks [@adam-maj](https://github.com/adam-maj)! - add capability to deploy apps
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#413](https://github.com/thirdweb-dev/js/pull/413) [`1b0bf99`](https://github.com/thirdweb-dev/js/commit/1b0bf99da796df88ff307dd004cdaf239fb2b1ba) Thanks [@furqanrydhan](https://github.com/furqanrydhan)! - small interface tweaks
|
||||
|
||||
- Updated dependencies [[`48797c7`](https://github.com/thirdweb-dev/js/commit/48797c7cf0695965e33eb6bf602be4652f7085a8), [`83dcaf7`](https://github.com/thirdweb-dev/js/commit/83dcaf70584af8c5cdf0427f9fc1ba82d89f0887), [`f03be39`](https://github.com/thirdweb-dev/js/commit/f03be398e0d7b75c9c5b4fb643f35903e89a17e7), [`2eaa67a`](https://github.com/thirdweb-dev/js/commit/2eaa67acbe21464e7715d0b696afdc024460a44d), [`47f8945`](https://github.com/thirdweb-dev/js/commit/47f8945325447874b1ace0359919c1d54fd55436)]:
|
||||
- @thirdweb-dev/sdk@3.6.3
|
||||
|
||||
## 0.8.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export const CREATE_MESSAGES = {
|
||||
typeOfProject: "What type of project do you want to create?",
|
||||
projectName: "What is your project named?",
|
||||
contractName: "What will be the name of your new smart contract?",
|
||||
chain: "Which blockchain do you want to use?",
|
||||
framework: "What framework do you want to use?",
|
||||
language: "What language do you want to use?",
|
||||
contract: "What type of contract do you want to start from?",
|
||||
@@ -9,5 +11,5 @@ export const CREATE_MESSAGES = {
|
||||
|
||||
export const ERROR_MESSAGES = {
|
||||
noConfiguration:
|
||||
"Failed to find a supported project configuration file in current directory",
|
||||
"No contract project configuration file found in current directory",
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CREATE_MESSAGES } from "../constants/constants";
|
||||
import { prepareEnvironment } from "@gmrchk/cli-testing-library";
|
||||
|
||||
// this creates an app, can take some time that's fine
|
||||
jest.setTimeout(120_000);
|
||||
jest.setTimeout(150_000);
|
||||
|
||||
describe("npx thirdweb create", () => {
|
||||
it("should create app (CRA) successfully", async () => {
|
||||
@@ -16,6 +16,8 @@ describe("npx thirdweb create", () => {
|
||||
await pressKey("enter");
|
||||
await waitForText(CREATE_MESSAGES.projectName);
|
||||
await pressKey("enter");
|
||||
await waitForText(CREATE_MESSAGES.chain);
|
||||
await pressKey("enter");
|
||||
await waitForText(CREATE_MESSAGES.framework);
|
||||
// select CRA
|
||||
await pressKey("arrowDown");
|
||||
|
||||
@@ -15,8 +15,8 @@ describe("npx thirdweb deploy", () => {
|
||||
`${path}/BasicContract.sol`,
|
||||
);
|
||||
|
||||
const { waitForText, waitForFinish, getExitCode, writeText, getStdout } =
|
||||
await spawn("node", "./dist/cli/index.js deploy");
|
||||
const { waitForText, waitForFinish, getExitCode, writeText, getStderr } =
|
||||
await spawn("node", "./dist/cli/index.js deploy --contract");
|
||||
|
||||
expect(await exists("BasicContract.sol")).toEqual(true);
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("npx thirdweb deploy", () => {
|
||||
// wait for program to finish
|
||||
await waitForFinish();
|
||||
|
||||
expect(getStdout().at(-1)).toContain(
|
||||
expect(getStderr().at(-1)).toContain(
|
||||
"https://thirdweb.com/contracts/deploy/",
|
||||
);
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ describe("npx thirdweb detect", () => {
|
||||
await create.waitForText(CREATE_MESSAGES.framework);
|
||||
// select hardhat
|
||||
await create.pressKey("enter");
|
||||
await create.waitForText(CREATE_MESSAGES.contractName);
|
||||
// set contract name
|
||||
await create.writeText("Contract");
|
||||
await create.pressKey("enter");
|
||||
await create.waitForText(CREATE_MESSAGES.contract);
|
||||
// select ERC721
|
||||
await create.pressKey("arrowDown");
|
||||
|
||||
@@ -15,7 +15,7 @@ describe("npx thirdweb release", () => {
|
||||
`${path}/BasicContract.sol`,
|
||||
);
|
||||
|
||||
const { waitForText, waitForFinish, getExitCode, writeText, getStdout } =
|
||||
const { waitForText, waitForFinish, getExitCode, writeText, getStderr } =
|
||||
await spawn("node", "./dist/cli/index.js release");
|
||||
|
||||
expect(await exists("BasicContract.sol")).toEqual(true);
|
||||
@@ -26,7 +26,7 @@ describe("npx thirdweb release", () => {
|
||||
// wait for program to finish
|
||||
await waitForFinish();
|
||||
|
||||
expect(getStdout().at(-1)).toContain(
|
||||
expect(getStderr().at(-1)).toContain(
|
||||
"https://thirdweb.com/contracts/release/",
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"main": "dist/cli/index.js",
|
||||
"version": "0.8.18",
|
||||
"version": "0.9.2",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { processProject } from "../common/processor";
|
||||
import { cliVersion, pkg } from "../constants/urls";
|
||||
import { info, logger } from "../core/helpers/logger";
|
||||
import { twCreate } from "../create/command";
|
||||
import generateDashboardUrl from "../helpers/generate-dashboard-url";
|
||||
import { deploy } from "../deploy";
|
||||
import { upload } from "../storage/command";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import chalk from "chalk";
|
||||
@@ -40,34 +40,40 @@ $$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$
|
||||
.version(cliVersion, "-v, --version");
|
||||
|
||||
program
|
||||
.command("create [projectPath]")
|
||||
.command("create [projectType] [projectPath]")
|
||||
.description(
|
||||
"Create a web3 app from any of our official templates: https://github.com/thirdweb-example/",
|
||||
)
|
||||
.option("--app", `Create a web3 app.`)
|
||||
.option("--contract", `Create a web3 contract project`)
|
||||
.option("--ts, --typescript", `Initialize as a TypeScript project.`)
|
||||
.option("--js, --javascript", `Initialize as a JavaScript project.`)
|
||||
.option("--forge", `Initialize as a Forge project.`)
|
||||
.option("--hardhat", `Initialize as a Hardhat project.`)
|
||||
.option("--cra", `Initialize as a Create React App project.`)
|
||||
.option("--next", `Initialize as a Next.js project.`)
|
||||
.option("--vite", `Initialize as a Vite project.`)
|
||||
.option("--app", "Create a web3 app.")
|
||||
.option("--contract", "Create a web3 contract project")
|
||||
.option("--ts, --typescript", "Initialize as a TypeScript project.")
|
||||
.option("--js, --javascript", "Initialize as a JavaScript project.")
|
||||
.option("--forge", "Initialize as a Forge project.")
|
||||
.option("--hardhat", "Initialize as a Hardhat project.")
|
||||
.option("--cra", "Initialize as a Create React App project.")
|
||||
.option("--next", "Initialize as a Next.js project.")
|
||||
.option("--vite", "Initialize as a Vite project.")
|
||||
.option(
|
||||
"--use-npm",
|
||||
`Explicitly tell the CLI to bootstrap the app using npm`,
|
||||
"Explicitly tell the CLI to bootstrap the app using npm",
|
||||
)
|
||||
.option(
|
||||
"--use-pnpm",
|
||||
`Explicitly tell the CLI to bootstrap the app using pnpm`,
|
||||
"Explicitly tell the CLI to bootstrap the app using pnpm",
|
||||
)
|
||||
.option("--framework [name]", `The preferred framework.`)
|
||||
.option("--framework [name]", "The preferred framework.")
|
||||
.option("--solana", "Initialize as a Solana project.")
|
||||
.option("--evm", "Initialize as an Ethereum project.")
|
||||
.option(
|
||||
"-t, --template [name]",
|
||||
`A template to start your project from. You can use an template repository name from the official thirdweb-example org.`,
|
||||
"A template to start your project from. You can use an template repository name from the official thirdweb-example org.",
|
||||
)
|
||||
.action(async (path, options) => {
|
||||
await twCreate(path, options);
|
||||
.option(
|
||||
"-c, --contract-name [name]",
|
||||
"Name of the new smart contract to create",
|
||||
)
|
||||
.action(async (type, path, options) => {
|
||||
await twCreate(type, path, options);
|
||||
});
|
||||
|
||||
program
|
||||
@@ -87,39 +93,33 @@ $$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$
|
||||
.option("--dry-run", "dry run (skip actually publishing)")
|
||||
.option("-d, --debug", "show debug logs")
|
||||
.option("--ci", "Continuous Integration mode")
|
||||
.option(
|
||||
"--dist-path [distPath]",
|
||||
"Path to the dist folder where the HTML based App is",
|
||||
)
|
||||
.option(
|
||||
"-n, --name [name]",
|
||||
"Name of the pre-built or released contract (such as nft-drop)",
|
||||
)
|
||||
.option(
|
||||
"-f, --file [name]",
|
||||
"Filter for contract files that contain this file name",
|
||||
)
|
||||
.option(
|
||||
"-cn, --contract-name [name]",
|
||||
"Filter for contracts that contain this contract name",
|
||||
)
|
||||
.option(
|
||||
"-cv, --contract-version [version]",
|
||||
"Version of the released contract",
|
||||
)
|
||||
.option("--app", "Deploy a web app to decentralized storage")
|
||||
.option("--contract", "Deploy a smart contract to blockchains")
|
||||
.action(async (options) => {
|
||||
if (options.name) {
|
||||
const url = generateDashboardUrl(options.name, options.contractVersion);
|
||||
|
||||
if (!url) {
|
||||
logger.error(
|
||||
chalk.red(
|
||||
`Could not find a contract named ${options.name} ${
|
||||
options.contractVersion || ""
|
||||
}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
info(`Open this link to deploy your contract:`);
|
||||
logger.info(chalk.blueBright(url));
|
||||
open(url.toString());
|
||||
return;
|
||||
const url = await deploy(options);
|
||||
if (url) {
|
||||
open(url);
|
||||
}
|
||||
|
||||
const url = await processProject(options, "deploy");
|
||||
info(`Open this link to deploy your contracts:`);
|
||||
logger.info(chalk.blueBright(url.toString()));
|
||||
open(url.toString());
|
||||
});
|
||||
|
||||
program
|
||||
@@ -128,13 +128,24 @@ $$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$
|
||||
"Release your protocol so other devs can deploy them and unlock SDKs, Dashboards and Analytics",
|
||||
)
|
||||
.option("-p, --path <project-path>", "path to project", ".")
|
||||
.option(
|
||||
"-f, --file [name]",
|
||||
"Filter for contract files that contain the file name",
|
||||
)
|
||||
.option(
|
||||
"-cn, --contract-name [name]",
|
||||
"Filter for contracts that contain this contract name",
|
||||
)
|
||||
.option("--dry-run", "dry run (skip actually publishing)")
|
||||
.option("-d, --debug", "show debug logs")
|
||||
.option("--ci", "Continuous Integration mode")
|
||||
.action(async (options) => {
|
||||
const url = await processProject(options, "release");
|
||||
info(`Open this link to release your contracts:`);
|
||||
logger.info(chalk.blueBright(url.toString()));
|
||||
info(
|
||||
`Open this link to release your contracts: ${chalk.blueBright(
|
||||
url.toString(),
|
||||
)}`,
|
||||
);
|
||||
open(url.toString());
|
||||
});
|
||||
|
||||
@@ -144,13 +155,35 @@ $$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$
|
||||
.argument("[upload]", "path to file or directory to upload")
|
||||
.action(async (path) => {
|
||||
const storage = new ThirdwebStorage();
|
||||
const uri = await upload(storage, path);
|
||||
info(`Files stored at the following IPFS URI:`);
|
||||
logger.info(chalk.blueBright(uri.toString()));
|
||||
try {
|
||||
const uri = await upload(storage, path);
|
||||
info(
|
||||
`Files stored at the following IPFS URI: ${chalk.blueBright(
|
||||
uri.toString(),
|
||||
)}`,
|
||||
);
|
||||
|
||||
const url = storage.resolveScheme(uri);
|
||||
info(`Open this link to view your upload:`);
|
||||
logger.info(chalk.blueBright(url.toString()));
|
||||
const url = storage.resolveScheme(uri);
|
||||
info(
|
||||
`Open this link to view your upload: ${chalk.blueBright(
|
||||
url.toString(),
|
||||
)}`,
|
||||
);
|
||||
} catch (err: any) {
|
||||
if (
|
||||
err.toString() === "No files detected in specified directory" ||
|
||||
err.toString() === "Invalid path provided" ||
|
||||
err.toString() === "No path provided"
|
||||
) {
|
||||
logger.error(
|
||||
chalk.redBright(
|
||||
"Please specify a valid file or directory to upload",
|
||||
),
|
||||
);
|
||||
} else {
|
||||
logger.error(chalk.redBright("Failed to upload files"), err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
|
||||
@@ -76,7 +76,7 @@ export async function processProject(
|
||||
}
|
||||
}
|
||||
|
||||
let compiledResult;
|
||||
let compiledResult: { contracts: ContractPayload[] };
|
||||
const compileLoader = spinner("Compiling project...");
|
||||
try {
|
||||
compiledResult = await build(projectPath, projectType);
|
||||
@@ -106,10 +106,47 @@ export async function processProject(
|
||||
if (options.ci) {
|
||||
selectedContracts = compiledResult.contracts;
|
||||
} else {
|
||||
const choices = compiledResult.contracts.map((c) => ({
|
||||
name: c.name,
|
||||
value: c,
|
||||
}));
|
||||
const filtered = compiledResult.contracts
|
||||
.filter((c) => {
|
||||
if (typeof options.file === "string" && options.file.length > 0) {
|
||||
return c.fileName.includes(options.file);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.filter((c) => {
|
||||
if (
|
||||
typeof options.contractName === "string" &&
|
||||
options.contractName.length > 0
|
||||
) {
|
||||
return c.name.includes(options.contractName);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (filtered.length === 0) {
|
||||
logger.error(`No contracts found matching the specified filters.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const choices = filtered.map((c) => {
|
||||
if (
|
||||
compiledResult.contracts.filter(
|
||||
(other: ContractPayload) => other.name === c.name,
|
||||
).length > 1
|
||||
) {
|
||||
return {
|
||||
name: `${c.name} - ${chalk.gray(c.fileName)}`,
|
||||
value: c,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: c.name,
|
||||
value: c,
|
||||
};
|
||||
});
|
||||
const prompt = createContractsPrompt(
|
||||
choices,
|
||||
`Choose which contract(s) to ${command}`,
|
||||
@@ -164,7 +201,7 @@ export async function processProject(
|
||||
const metadataURIs = await Promise.all(
|
||||
selectedContracts.map(async (c) => {
|
||||
logger.debug(`Uploading ${c.name}...`);
|
||||
return await storage.upload(JSON.parse(c.metadata), {
|
||||
return await storage.upload(JSON.parse(JSON.stringify(c.metadata)), {
|
||||
uploadWithoutDirectory: true,
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -55,6 +55,7 @@ export class BrownieBuilder extends BaseBuilder {
|
||||
metadata: {},
|
||||
bytecode,
|
||||
sources: [], // TODO
|
||||
fileName: "", // TODO
|
||||
name: contractName,
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -88,6 +88,11 @@ export class FoundryBuilder extends BaseBuilder {
|
||||
})
|
||||
.filter((path) => path !== undefined) as string[];
|
||||
|
||||
const fileNames = Object.keys(
|
||||
parsedMetadata?.settings?.compilationTarget || {},
|
||||
);
|
||||
const fileName = fileNames.length > 0 ? fileNames[0] : "";
|
||||
|
||||
if (
|
||||
this.shouldProcessContract(
|
||||
contractInfo.abi,
|
||||
@@ -99,6 +104,7 @@ export class FoundryBuilder extends BaseBuilder {
|
||||
name: contractName,
|
||||
metadata,
|
||||
bytecode,
|
||||
fileName,
|
||||
sources,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export class HardhatBuilder extends BaseBuilder {
|
||||
//the hardhat extractor **logs out** the runtime config of hardhat, we take that stdout and parse it
|
||||
const stringifiedConfig = (
|
||||
await execute(
|
||||
`npx hardhat run ${configExtractorScriptPath} --no-compile`,
|
||||
`npx hardhat run "${configExtractorScriptPath}" --no-compile`,
|
||||
options.projectPath,
|
||||
)
|
||||
).stdout;
|
||||
@@ -123,11 +123,17 @@ export class HardhatBuilder extends BaseBuilder {
|
||||
})
|
||||
.filter((path) => path !== undefined) as string[];
|
||||
|
||||
const fileNames = Object.keys(
|
||||
meta?.settings?.compilationTarget || {},
|
||||
);
|
||||
const fileName = fileNames.length > 0 ? fileNames[0] : "";
|
||||
|
||||
if (this.shouldProcessContract(abi, deployedBytecode, contractName)) {
|
||||
contracts.push({
|
||||
metadata,
|
||||
bytecode,
|
||||
name: contractName,
|
||||
fileName,
|
||||
sources,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -140,11 +140,17 @@ export class SolcBuilder extends BaseBuilder {
|
||||
})
|
||||
.filter((path) => path !== undefined) as string[];
|
||||
|
||||
const fileNames = Object.keys(
|
||||
parsedMetadata?.settings?.compilationTarget || {},
|
||||
);
|
||||
const fileName = fileNames.length > 0 ? fileNames[0] : "";
|
||||
|
||||
if (this.shouldProcessContract(abi, deployedBytecode, contractName)) {
|
||||
contracts.push({
|
||||
metadata,
|
||||
bytecode,
|
||||
name: contractName,
|
||||
fileName,
|
||||
sources: _sources,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,11 +72,18 @@ export class TruffleBuilder extends BaseBuilder {
|
||||
return undefined;
|
||||
})
|
||||
.filter((path) => path !== undefined) as string[];
|
||||
|
||||
const fileNames = Object.keys(
|
||||
parsedMetadata?.settings?.compilationTarget || {},
|
||||
);
|
||||
const fileName = fileNames.length > 0 ? fileNames[0] : "";
|
||||
|
||||
if (this.shouldProcessContract(abi, deployedBytecode, contractName)) {
|
||||
contracts.push({
|
||||
metadata,
|
||||
bytecode,
|
||||
name: contractName,
|
||||
fileName,
|
||||
sources,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ export default async function detect(
|
||||
name: "continue",
|
||||
message:
|
||||
"Do you want to continue and compile this project with solc instead?",
|
||||
initial: true,
|
||||
});
|
||||
const shouldCompile = await prompt.run();
|
||||
if (!shouldCompile) {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ProjectType } from "../types/ProjectType";
|
||||
import { Detector } from "./detector";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
export default class NPMDetector implements Detector {
|
||||
public projectType: ProjectType = "npm";
|
||||
|
||||
public matches(path: string): boolean {
|
||||
return existsSync(path + "/package-lock.json");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ProjectType } from "../types/ProjectType";
|
||||
import { Detector } from "./detector";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
export default class ViteDetector implements Detector {
|
||||
public projectType: ProjectType = "vite";
|
||||
|
||||
public matches(path: string): boolean {
|
||||
return (
|
||||
existsSync(path + "/vite.config.js") ||
|
||||
existsSync(path + "/vite.config.ts")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ProjectType } from "../types/ProjectType";
|
||||
import { Detector } from "./detector";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
export default class YarnDetector implements Detector {
|
||||
public projectType: ProjectType = "yarn";
|
||||
|
||||
public matches(path: string): boolean {
|
||||
return existsSync(path + "/yarn.lock");
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,11 @@ export interface ContractPayload {
|
||||
*/
|
||||
metadata: any;
|
||||
|
||||
/**
|
||||
* The file name of the original compiled file
|
||||
*/
|
||||
fileName: string;
|
||||
|
||||
/**
|
||||
* The source file paths
|
||||
*/
|
||||
|
||||
@@ -4,4 +4,10 @@ export type ProjectType =
|
||||
| "hardhat"
|
||||
| "truffle"
|
||||
| "solc"
|
||||
| "vite"
|
||||
| "next"
|
||||
| "cra"
|
||||
| "spa-webapp"
|
||||
| "yarn"
|
||||
| "npm"
|
||||
| "none";
|
||||
|
||||
+262
-131
@@ -2,60 +2,82 @@
|
||||
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { CREATE_MESSAGES } from "../../constants/constants";
|
||||
import detect from "../core/detection/detect";
|
||||
import { DownloadError, createApp } from "./helpers/create-app";
|
||||
import { createContract } from "./helpers/create-contract";
|
||||
import { createContractProject } from "./helpers/create-contract";
|
||||
import { getPkgManager } from "./helpers/get-pkg-manager";
|
||||
import { validateNpmName } from "./helpers/validate-pkg";
|
||||
import chalk from "chalk";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import prompts from "prompts";
|
||||
|
||||
let projectType: string = "";
|
||||
let contractName: string = "";
|
||||
let projectPath: string = "";
|
||||
let projectType: string = "";
|
||||
let framework: string = "";
|
||||
let language: string = "";
|
||||
let baseContract: string = "";
|
||||
let chain: string = "";
|
||||
/* let createType: string = "app"; */
|
||||
|
||||
export async function twCreate(pPath: string = "", options: any) {
|
||||
export async function twCreate(
|
||||
pType: string,
|
||||
pPath: string = "",
|
||||
options: any,
|
||||
) {
|
||||
if (typeof pPath === "string") {
|
||||
projectPath = pPath;
|
||||
}
|
||||
|
||||
if (options.app) {
|
||||
projectType = "app";
|
||||
if (typeof options.contractName === "string") {
|
||||
contractName = options.contractName;
|
||||
}
|
||||
|
||||
if (options.contract) {
|
||||
if (pType === "app" || options.app) {
|
||||
projectType = "app";
|
||||
} else if (pType === "contract" || options.contract) {
|
||||
projectType = "contract";
|
||||
}
|
||||
|
||||
if (options.typescript) {
|
||||
language = "typescript";
|
||||
if (projectType === "app") {
|
||||
if (options.typescript) {
|
||||
language = "typescript";
|
||||
}
|
||||
|
||||
if (options.javascript) {
|
||||
language = "javascript";
|
||||
}
|
||||
|
||||
if (options.next) {
|
||||
framework = "next";
|
||||
}
|
||||
|
||||
if (options.cra) {
|
||||
framework = "cra";
|
||||
}
|
||||
|
||||
if (options.vite) {
|
||||
framework = "vite";
|
||||
}
|
||||
|
||||
if (options.solana) {
|
||||
chain = "solana";
|
||||
}
|
||||
|
||||
if (options.evm) {
|
||||
chain = "evm";
|
||||
}
|
||||
}
|
||||
|
||||
if (options.javascript) {
|
||||
language = "javascript";
|
||||
}
|
||||
if (projectType === "contract") {
|
||||
if (options.forge) {
|
||||
framework = "forge";
|
||||
}
|
||||
|
||||
if (options.next) {
|
||||
framework = "next";
|
||||
}
|
||||
|
||||
if (options.cra) {
|
||||
framework = "cra";
|
||||
}
|
||||
|
||||
if (options.vite) {
|
||||
framework = "vite";
|
||||
}
|
||||
|
||||
if (options.forge) {
|
||||
framework = "forge";
|
||||
}
|
||||
|
||||
if (options.hardhat) {
|
||||
framework = "hardhat";
|
||||
if (options.hardhat) {
|
||||
framework = "hardhat";
|
||||
}
|
||||
}
|
||||
|
||||
if (options.framework) {
|
||||
@@ -76,103 +98,216 @@ export async function twCreate(pPath: string = "", options: any) {
|
||||
if (typeof res.projectType === "string") {
|
||||
projectType = res.projectType.trim();
|
||||
}
|
||||
} else if (!projectType && options.template) {
|
||||
} else if (!projectType || options.template) {
|
||||
// If no project type is specified, but a template is, we assume the user wants to create an app.
|
||||
// We do this so old users can still use the --template flag to create an app.
|
||||
projectType = "app";
|
||||
}
|
||||
|
||||
if (!projectPath) {
|
||||
const defaultName =
|
||||
projectType === "contract" ? "thirdweb-contracts" : "thirdweb-app";
|
||||
const res = await prompts({
|
||||
type: "text",
|
||||
name: "path",
|
||||
message: CREATE_MESSAGES.projectName,
|
||||
initial: options.template || defaultName,
|
||||
format: (name: string) => name.toLowerCase(),
|
||||
validate: (name: string) => {
|
||||
const validation = validateNpmName(
|
||||
path.basename(path.resolve(name.toLowerCase())),
|
||||
);
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
return "Invalid project name: " + validation.problems?.[0];
|
||||
},
|
||||
});
|
||||
|
||||
if (typeof res.path === "string") {
|
||||
projectPath = res.path.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (!projectPath) {
|
||||
console.log(
|
||||
"\nPlease specify the project directory:\n" +
|
||||
` ${chalk.cyan("npx thirdweb create")} ${chalk.green(
|
||||
"<project-directory>",
|
||||
)}\n` +
|
||||
"For example:\n" +
|
||||
` ${chalk.cyan("npx thirdweb create")} ${chalk.green(
|
||||
"my-thirdweb-app",
|
||||
)}\n\n` +
|
||||
`Run ${chalk.cyan("npx thirdweb --help")} to see all options.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!options.template) {
|
||||
if (projectType === "app" && !framework) {
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "framework",
|
||||
message: CREATE_MESSAGES.framework,
|
||||
choices: [
|
||||
{ title: "Next.js", value: "next" },
|
||||
{ title: "Create React App", value: "cra" },
|
||||
{ title: "Vite", value: "vite" },
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof res.framework === "string") {
|
||||
framework = res.framework.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (projectType === "app" && !language) {
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "language",
|
||||
message: CREATE_MESSAGES.language,
|
||||
choices: [
|
||||
{ title: "JavaScript", value: "javascript" },
|
||||
{ title: "TypeScript", value: "typescript" },
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof res.language === "string") {
|
||||
language = res.language.trim();
|
||||
}
|
||||
}
|
||||
// Whether to only create a new contract without the project
|
||||
let onlyContract = false;
|
||||
if (projectType === "contract") {
|
||||
const resolvedProjectPath = path.resolve(projectPath);
|
||||
const contractProjectType = await detect(resolvedProjectPath, {});
|
||||
|
||||
if (
|
||||
projectType === "contract" &&
|
||||
framework !== "forge" &&
|
||||
framework !== "hardhat"
|
||||
contractProjectType === "foundry" &&
|
||||
fs.existsSync(path.join(resolvedProjectPath, "src"))
|
||||
) {
|
||||
onlyContract = true;
|
||||
projectPath = path.join(projectPath, "src");
|
||||
} else if (contractProjectType === "hardhat") {
|
||||
if (fs.existsSync(path.join(resolvedProjectPath, "contracts"))) {
|
||||
onlyContract = true;
|
||||
projectPath = path.join(projectPath, "contracts");
|
||||
} else {
|
||||
// If there's no expected contracts folder, check for a directory that has .sol files
|
||||
const directories = fs
|
||||
.readdirSync(resolvedProjectPath)
|
||||
.filter((file) =>
|
||||
fs.lstatSync(path.join(resolvedProjectPath, file)).isDirectory(),
|
||||
);
|
||||
for (const directory of directories) {
|
||||
const files = fs.readdirSync(path.join(projectPath, directory));
|
||||
if (files.some((file) => file.endsWith(".sol"))) {
|
||||
onlyContract = true;
|
||||
projectPath = path.join(projectPath, directory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!onlyContract) {
|
||||
if (!projectPath) {
|
||||
const defaultName =
|
||||
projectType === "contract" ? "thirdweb-contracts" : "thirdweb-app";
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "framework",
|
||||
message: CREATE_MESSAGES.framework,
|
||||
choices: [
|
||||
{ title: "Hardhat", value: "hardhat" },
|
||||
{ title: "Forge", value: "forge" },
|
||||
],
|
||||
type: "text",
|
||||
name: "path",
|
||||
message: CREATE_MESSAGES.projectName,
|
||||
initial: options.template || defaultName,
|
||||
format: (name: string) => name.toLowerCase(),
|
||||
validate: (name: string) => {
|
||||
const validation = validateNpmName(
|
||||
path.basename(path.resolve(name.toLowerCase())),
|
||||
);
|
||||
if (validation.valid) {
|
||||
return true;
|
||||
}
|
||||
return "Invalid project name: " + validation.problems?.[0];
|
||||
},
|
||||
});
|
||||
|
||||
if (typeof res.framework === "string") {
|
||||
framework = res.framework.trim();
|
||||
if (typeof res.path === "string") {
|
||||
projectPath = path.join(projectPath, res.path.trim());
|
||||
}
|
||||
}
|
||||
|
||||
if (!projectPath) {
|
||||
console.log(
|
||||
"\nPlease specify the project directory:\n" +
|
||||
` ${chalk.cyan("npx thirdweb create")} ${chalk.green(
|
||||
"<project-directory>",
|
||||
)}\n` +
|
||||
"For example:\n" +
|
||||
` ${chalk.cyan("npx thirdweb create")} ${chalk.green(
|
||||
"my-thirdweb-app",
|
||||
)}\n\n` +
|
||||
`Run ${chalk.cyan("npx thirdweb --help")} to see all options.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!options.template) {
|
||||
if (projectType === "app" && !chain) {
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "chain",
|
||||
message: CREATE_MESSAGES.chain,
|
||||
choices: [
|
||||
{ title: "EVM", value: "evm" },
|
||||
{ title: "Solana", value: "solana" },
|
||||
],
|
||||
});
|
||||
|
||||
if (res.chain === "solana") {
|
||||
chain = "solana";
|
||||
} else {
|
||||
chain = "evm";
|
||||
}
|
||||
}
|
||||
|
||||
if (projectType === "app" && !framework) {
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "framework",
|
||||
message: CREATE_MESSAGES.framework,
|
||||
choices:
|
||||
// Solana doesn't support Vite just yet:
|
||||
chain === "solana"
|
||||
? [
|
||||
{ title: "Next.js", value: "next" },
|
||||
{ title: "Create React App", value: "cra" },
|
||||
// { title: "Vite", value: "vite" },
|
||||
]
|
||||
: [
|
||||
{ title: "Next.js", value: "next" },
|
||||
{ title: "Create React App", value: "cra" },
|
||||
{ title: "Vite", value: "vite" },
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof res.framework === "string") {
|
||||
framework = res.framework.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (projectType === "app" && !language) {
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "language",
|
||||
message: CREATE_MESSAGES.language,
|
||||
choices: [
|
||||
{ title: "JavaScript", value: "javascript" },
|
||||
{ title: "TypeScript", value: "typescript" },
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof res.language === "string") {
|
||||
language = res.language.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
projectType === "contract" &&
|
||||
framework !== "forge" &&
|
||||
framework !== "hardhat"
|
||||
) {
|
||||
const res = await prompts({
|
||||
type: "select",
|
||||
name: "framework",
|
||||
message: CREATE_MESSAGES.framework,
|
||||
choices: [
|
||||
{ title: "Hardhat", value: "hardhat" },
|
||||
{ title: "Forge", value: "forge" },
|
||||
],
|
||||
});
|
||||
|
||||
if (typeof res.framework === "string") {
|
||||
framework = res.framework.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!framework) {
|
||||
console.log("Please specify a framework");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!language) {
|
||||
// Default = JavaScript
|
||||
language = "javascript";
|
||||
}
|
||||
}
|
||||
|
||||
if (projectType === "contract") {
|
||||
// Select contract name
|
||||
if (!contractName) {
|
||||
const defaultName = "MyContract";
|
||||
const res = await prompts({
|
||||
type: "text",
|
||||
name: "path",
|
||||
message: CREATE_MESSAGES.contractName,
|
||||
initial: defaultName,
|
||||
validate: (name: string) => {
|
||||
const isValid = /(^[a-z0-9A-Z]+$)|(^[a-z0-9A-Z]+\.sol$)/.test(name);
|
||||
if (isValid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return `Invalid contract name 'contractName' (only alphanumeric characters allowed)`;
|
||||
},
|
||||
});
|
||||
|
||||
if (typeof res.path === "string") {
|
||||
contractName = res.path.trim();
|
||||
}
|
||||
|
||||
if (!contractName) {
|
||||
console.log(
|
||||
"\nPlease specify the contract name:\n" +
|
||||
` ${chalk.cyan(
|
||||
"npx thirdweb create --contract --name",
|
||||
)} ${chalk.green("<contract-name>")}\n` +
|
||||
"For example:\n" +
|
||||
` ${chalk.cyan(
|
||||
"npx thirdweb create --contract --name",
|
||||
)} ${chalk.green("MyContract")}\n\n` +
|
||||
`Run ${chalk.cyan("npx thirdweb --help")} to see all options.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +319,9 @@ export async function twCreate(pPath: string = "", options: any) {
|
||||
name: "standard",
|
||||
message: CREATE_MESSAGES.contract,
|
||||
choices: [
|
||||
{ title: "Empty Contract", value: "none" },
|
||||
...(!onlyContract
|
||||
? [{ title: "Empty Contract", value: "none" }]
|
||||
: []),
|
||||
{ title: "ERC721", value: "erc721" },
|
||||
{ title: "ERC20", value: "erc20" },
|
||||
{ title: "ERC1155", value: "erc1155" },
|
||||
@@ -238,20 +375,11 @@ export async function twCreate(pPath: string = "", options: any) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!framework) {
|
||||
console.log("Please specify a framework");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!language) {
|
||||
// Default = JavaScript
|
||||
language = "javascript";
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedProjectPath = path.resolve(projectPath);
|
||||
const projectName = path.basename(resolvedProjectPath);
|
||||
// Resolve project path
|
||||
projectPath = path.resolve(projectPath);
|
||||
const projectName = path.basename(projectPath);
|
||||
|
||||
const { valid, problems } = validateNpmName(projectName);
|
||||
if (!valid) {
|
||||
@@ -283,18 +411,21 @@ export async function twCreate(pPath: string = "", options: any) {
|
||||
try {
|
||||
if (projectType === "app") {
|
||||
await createApp({
|
||||
appPath: resolvedProjectPath,
|
||||
appPath: projectPath,
|
||||
packageManager,
|
||||
framework,
|
||||
language,
|
||||
template,
|
||||
chain,
|
||||
});
|
||||
} else {
|
||||
await createContract({
|
||||
contractPath: resolvedProjectPath,
|
||||
await createContractProject({
|
||||
contractPath: projectPath,
|
||||
packageManager,
|
||||
framework,
|
||||
contractName,
|
||||
baseContract,
|
||||
onlyContract,
|
||||
});
|
||||
}
|
||||
} catch (reason) {
|
||||
|
||||
@@ -22,12 +22,14 @@ export async function createApp({
|
||||
framework,
|
||||
language,
|
||||
template,
|
||||
chain,
|
||||
}: {
|
||||
appPath: string;
|
||||
packageManager: PackageManager;
|
||||
framework?: string;
|
||||
language?: string;
|
||||
template?: string;
|
||||
chain?: string;
|
||||
}): Promise<void> {
|
||||
let frameworkPath = "";
|
||||
|
||||
@@ -43,7 +45,9 @@ export async function createApp({
|
||||
process.exit(1);
|
||||
}
|
||||
} else if (framework) {
|
||||
frameworkPath = `${framework}-${language || "javascript"}-starter`;
|
||||
frameworkPath = `${framework}-${language || "javascript"}-${
|
||||
chain === "solana" ? "solana-" : ""
|
||||
}starter`;
|
||||
const found = await hasTemplate(frameworkPath);
|
||||
|
||||
if (!found) {
|
||||
|
||||
@@ -11,22 +11,28 @@ import { submodules } from "./submodules";
|
||||
import { downloadAndExtractRepo } from "./templates";
|
||||
import retry from "async-retry";
|
||||
import chalk from "chalk";
|
||||
import fs from "fs";
|
||||
import { writeFile } from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
interface ICreateContract {
|
||||
interface ICreateContractProject {
|
||||
contractPath: string;
|
||||
packageManager: PackageManager;
|
||||
contractName: string;
|
||||
framework?: string;
|
||||
baseContract?: string;
|
||||
onlyContract: boolean;
|
||||
}
|
||||
|
||||
export async function createContract({
|
||||
export async function createContractProject({
|
||||
contractPath,
|
||||
packageManager,
|
||||
framework,
|
||||
contractName,
|
||||
baseContract,
|
||||
}: ICreateContract) {
|
||||
onlyContract,
|
||||
}: ICreateContractProject) {
|
||||
// Check that the selected base contract is valid
|
||||
if (baseContract) {
|
||||
const found = hasBaseContract(baseContract);
|
||||
|
||||
@@ -40,8 +46,8 @@ export async function createContract({
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we have write permission for the specified directory
|
||||
const root = path.resolve(contractPath);
|
||||
|
||||
if (!(await isWriteable(path.dirname(root)))) {
|
||||
console.error(
|
||||
"The application path is not writable, please check folder permissions and try again.",
|
||||
@@ -52,117 +58,165 @@ export async function createContract({
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const projectName = path.basename(root);
|
||||
|
||||
await makeDir(root);
|
||||
if (!isFolderEmpty(root, projectName)) {
|
||||
// Check that the contract name is valid
|
||||
if (!/(^[a-z0-9A-Z]+$)|(^[a-z0-9A-Z]+\.sol$)/.test(contractName)) {
|
||||
console.error(
|
||||
`Contract name ${chalk.red(
|
||||
`"${contractName}"`,
|
||||
)} is not valid (only alphanumeric characters are allowed).`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Clean the contract name
|
||||
contractName = contractName.replace(/\.sol$/, "") + ".sol";
|
||||
|
||||
const useYarn = packageManager === "yarn";
|
||||
const isOnline = !useYarn || (await getOnline());
|
||||
const originalDirectory = process.cwd();
|
||||
|
||||
console.log(
|
||||
`Creating a new thirdweb contracts project in ${chalk.green(root)}.`,
|
||||
);
|
||||
console.log();
|
||||
|
||||
process.chdir(root);
|
||||
|
||||
function isErrorLike(err: unknown): err is { message: string } {
|
||||
return (
|
||||
typeof err === "object" &&
|
||||
err !== null &&
|
||||
typeof (err as { message?: unknown }).message === "string"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Downloading files. This might take a moment.`);
|
||||
|
||||
let starter = "";
|
||||
if (framework === "hardhat") {
|
||||
starter = "hardhat-javascript-starter";
|
||||
} else if (framework === "forge") {
|
||||
starter = "forge-starter";
|
||||
} else {
|
||||
console.error("Please provide a valid contracts framework.");
|
||||
if (onlyContract) {
|
||||
// If we are already in a contracts project, just create a new contract
|
||||
if (fs.existsSync(path.join(root, contractName))) {
|
||||
console.error(
|
||||
`A contract with the name ${chalk.red(
|
||||
`"${contractName}"`,
|
||||
)} already exists in specified directory.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await retry(
|
||||
() => downloadAndExtractRepo(root, { name: starter, filePath: "" }),
|
||||
{
|
||||
retries: 3,
|
||||
},
|
||||
);
|
||||
|
||||
// Modify the /contracts/MyContract.sol contents to match the base contract
|
||||
if (baseContract && baseContract.length > 0) {
|
||||
const baseContractText = readBaseContract(baseContract);
|
||||
|
||||
// Set the contents of the Contract.sol file to the base contract
|
||||
let contractFile = "";
|
||||
if (framework === "hardhat") {
|
||||
contractFile = path.join(root, "contracts", "Contract.sol");
|
||||
}
|
||||
if (framework === "forge") {
|
||||
contractFile = path.join(root, "src", "Contract.sol");
|
||||
}
|
||||
contractFile = path.join(root, contractName);
|
||||
|
||||
// Write the base contract to the MyContract.sol file
|
||||
await writeFile(contractFile, baseContractText);
|
||||
|
||||
console.log(
|
||||
`${chalk.green("Success!")} Created ${contractName} at ${contractPath}`,
|
||||
);
|
||||
}
|
||||
} catch (reason) {
|
||||
throw new DownloadError(
|
||||
isErrorLike(reason) ? reason.message : String(reason),
|
||||
} else {
|
||||
// Otherwise, create a new contracts project
|
||||
const projectName = path.basename(root);
|
||||
|
||||
await makeDir(root);
|
||||
if (!isFolderEmpty(root, projectName)) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const isOnline = !useYarn || (await getOnline());
|
||||
const originalDirectory = process.cwd();
|
||||
|
||||
console.log(
|
||||
`Creating a new thirdweb contracts project in ${chalk.green(root)}.`,
|
||||
);
|
||||
}
|
||||
console.log();
|
||||
|
||||
console.log("Installing packages. This might take a couple of minutes.");
|
||||
console.log();
|
||||
process.chdir(root);
|
||||
|
||||
await install(root, null, { packageManager, isOnline });
|
||||
console.log();
|
||||
function isErrorLike(err: unknown): err is { message: string } {
|
||||
return (
|
||||
typeof err === "object" &&
|
||||
err !== null &&
|
||||
typeof (err as { message?: unknown }).message === "string"
|
||||
);
|
||||
}
|
||||
|
||||
if (tryGitInit(root)) {
|
||||
console.log("Initialized a git repository.");
|
||||
try {
|
||||
console.log(`Downloading files. This might take a moment.`);
|
||||
|
||||
let starter = "";
|
||||
if (framework === "hardhat") {
|
||||
starter = "hardhat-javascript-starter";
|
||||
} else if (framework === "forge") {
|
||||
starter = "forge-starter";
|
||||
} else {
|
||||
console.error("Please provide a valid contracts framework.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await retry(
|
||||
() => downloadAndExtractRepo(root, { name: starter, filePath: "" }),
|
||||
{
|
||||
retries: 3,
|
||||
},
|
||||
);
|
||||
|
||||
// Add in a new contracts file with specific base contract
|
||||
if (baseContract && baseContract.length > 0) {
|
||||
const baseContractText = readBaseContract(baseContract);
|
||||
|
||||
// Set the filename of the new file and delete the dummy Contract.sol file
|
||||
let contractFile = "";
|
||||
if (framework === "hardhat") {
|
||||
fs.unlinkSync(path.join(root, "contracts", "Contract.sol"));
|
||||
contractFile = path.join(root, "contracts", contractName);
|
||||
}
|
||||
if (framework === "forge") {
|
||||
fs.unlinkSync(path.join(root, "src", "Contract.sol"));
|
||||
contractFile = path.join(root, "src", contractName);
|
||||
}
|
||||
|
||||
// Write the base contract to the new file
|
||||
await writeFile(contractFile, baseContractText);
|
||||
}
|
||||
} catch (reason) {
|
||||
throw new DownloadError(
|
||||
isErrorLike(reason) ? reason.message : String(reason),
|
||||
);
|
||||
}
|
||||
|
||||
console.log("Installing packages. This might take a couple of minutes.");
|
||||
console.log();
|
||||
|
||||
await install(root, null, { packageManager, isOnline });
|
||||
console.log();
|
||||
|
||||
if (tryGitInit(root)) {
|
||||
console.log("Initialized a git repository.");
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (framework === "forge") {
|
||||
await submodules();
|
||||
}
|
||||
|
||||
let cdpath: string;
|
||||
if (path.join(originalDirectory, projectName) === contractPath) {
|
||||
cdpath = projectName;
|
||||
} else {
|
||||
cdpath = contractPath;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${chalk.green("Success!")} Created ${projectName} at ${contractPath}`,
|
||||
);
|
||||
console.log();
|
||||
console.log("Inside that directory, you can run several commands:");
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.cyan(` ${packageManager}${useYarn ? "" : " run"} build`),
|
||||
);
|
||||
console.log(
|
||||
" Compiles your contracts and detects thirdweb extensions implemented on them.",
|
||||
);
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.cyan(` ${packageManager}${useYarn ? "" : " run"} deploy`),
|
||||
);
|
||||
console.log(" Deploys your contracts with the thirdweb deploy flow.");
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.cyan(` ${packageManager}${useYarn ? "" : " run"} release`),
|
||||
);
|
||||
console.log(" Releases your contracts with the thirdweb release flow.");
|
||||
console.log();
|
||||
console.log("We suggest that you begin by typing:");
|
||||
console.log();
|
||||
console.log(chalk.cyan(" cd"), cdpath);
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (framework === "forge") {
|
||||
await submodules();
|
||||
}
|
||||
|
||||
let cdpath: string;
|
||||
if (path.join(originalDirectory, projectName) === contractPath) {
|
||||
cdpath = projectName;
|
||||
} else {
|
||||
cdpath = contractPath;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${chalk.green("Success!")} Created ${projectName} at ${contractPath}`,
|
||||
);
|
||||
console.log();
|
||||
console.log("Inside that directory, you can run several commands:");
|
||||
console.log();
|
||||
console.log(chalk.cyan(` ${packageManager}${useYarn ? "" : " run"} build`));
|
||||
console.log(
|
||||
" Compiles your contracts and detects thirdweb extensions implemented on them.",
|
||||
);
|
||||
console.log();
|
||||
console.log(chalk.cyan(` ${packageManager}${useYarn ? "" : " run"} deploy`));
|
||||
console.log(" Deploys your contracts with the thirdweb deploy flow.");
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.cyan(` ${packageManager}${useYarn ? "" : " run"} release`),
|
||||
);
|
||||
console.log(" Releases your contracts with the thirdweb release flow.");
|
||||
console.log();
|
||||
console.log("We suggest that you begin by typing:");
|
||||
console.log();
|
||||
console.log(chalk.cyan(" cd"), cdpath);
|
||||
console.log();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
import spawn from "cross-spawn";
|
||||
|
||||
export async function runCommand(command: string, args: string[]) {
|
||||
export async function runCommand(
|
||||
command: string,
|
||||
args: string[],
|
||||
logging: boolean = false,
|
||||
) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
/**
|
||||
* Spawn the installation process.
|
||||
*/
|
||||
const child = spawn(command, args);
|
||||
child.stdout?.on("data", (data) => {
|
||||
if (logging) {
|
||||
console.log(data.toString());
|
||||
}
|
||||
});
|
||||
|
||||
child.stderr?.on("error", (err) => {
|
||||
if (logging) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
child.on("close", (code) => {
|
||||
if (code !== 0) {
|
||||
reject({ command: `${command}` });
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import NPMDetector from "../core/detection/npm";
|
||||
import ViteDetector from "../core/detection/vite";
|
||||
import YarnDetector from "../core/detection/yarn";
|
||||
import { runCommand } from "../create/helpers/run-command";
|
||||
import { upload } from "../storage/command";
|
||||
import { DEFAULT_IPFS_GATEWAY } from "@thirdweb-dev/sdk";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
|
||||
export async function deployApp(distPath = "dist", projectPath = ".") {
|
||||
const storage = new ThirdwebStorage();
|
||||
const supportedFrameworks = [new ViteDetector()];
|
||||
|
||||
const possibleProjects = supportedFrameworks
|
||||
.filter((detector) => detector.matches(projectPath))
|
||||
.map((detector) => detector.projectType);
|
||||
|
||||
const hasYarn = new YarnDetector().matches(".");
|
||||
const hasNPM = new NPMDetector().matches(".");
|
||||
|
||||
if (possibleProjects.length === 0) {
|
||||
throw new Error("No supported project detected");
|
||||
}
|
||||
|
||||
try {
|
||||
if (hasYarn) {
|
||||
await runCommand("yarn", ["build"]);
|
||||
} else if (hasNPM) {
|
||||
await runCommand("npm", ["build"]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Can't build project");
|
||||
return Promise.reject("Can't build project");
|
||||
}
|
||||
|
||||
try {
|
||||
let url = await upload(storage, distPath);
|
||||
url = url.replace("ipfs://", DEFAULT_IPFS_GATEWAY);
|
||||
return `${url}/index.html`;
|
||||
} catch (err) {
|
||||
console.error("Can't upload project");
|
||||
return Promise.reject("Can't upload project");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { processProject } from "../common/processor";
|
||||
import { info, logger } from "../core/helpers/logger";
|
||||
import generateDashboardUrl from "../helpers/generate-dashboard-url";
|
||||
import { deployApp } from "./app";
|
||||
import chalk from "chalk";
|
||||
|
||||
type DeployOptions = {
|
||||
name: string;
|
||||
contractVersion: string;
|
||||
app: boolean;
|
||||
path?: string;
|
||||
distPath?: string;
|
||||
};
|
||||
|
||||
export async function deploy(options: DeployOptions) {
|
||||
if (options.name) {
|
||||
const url = generateDashboardUrl(options.name, options.contractVersion);
|
||||
|
||||
if (!url) {
|
||||
logger.error(
|
||||
chalk.red(
|
||||
`Could not find a contract named ${options.name} ${
|
||||
options.contractVersion || ""
|
||||
}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
info(`Open this link to deploy your contract: ${chalk.blueBright(url)}`);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
if (options.app) {
|
||||
try {
|
||||
let url = await deployApp(options.distPath, options.path);
|
||||
info(`Here is the link to your app: ${chalk.blueBright(url.toString())}`);
|
||||
return url.toString();
|
||||
} catch (err) {
|
||||
logger.error("Failed to deploy app, No compatible project found", err);
|
||||
return Promise.reject(
|
||||
"failed to deploy app, no compatible project found",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const url = await processProject(options, "deploy");
|
||||
info(
|
||||
`Open this link to deploy your contracts: ${chalk.blueBright(
|
||||
url.toString(),
|
||||
)}`,
|
||||
);
|
||||
return url.toString();
|
||||
}
|
||||
@@ -13,18 +13,18 @@ export default function generateDashboardUrl(
|
||||
// Pre-built contract
|
||||
else {
|
||||
const mapping = {
|
||||
"signature-drop": "drop/signature-drop",
|
||||
"nft-drop": "drop/nft-drop",
|
||||
"edition-drop": "drop/edition-drop",
|
||||
"nft-collection": "token/nft-collection",
|
||||
edition: "token/edition",
|
||||
"token-drop": "drop/token-drop",
|
||||
token: "token/token",
|
||||
marketplace: "marketplace/marketplace",
|
||||
split: "governance/split",
|
||||
vote: "governance/vote",
|
||||
pack: "token/pack",
|
||||
multiwrap: "token/multiwrap",
|
||||
"signature-drop": "SignatureDrop",
|
||||
"nft-drop": "DropERC721",
|
||||
"edition-drop": "DropERC1155",
|
||||
"nft-collection": "TokenERC721",
|
||||
edition: "TokenERC1155",
|
||||
"token-drop": "DropERC20",
|
||||
token: "TokenERC20",
|
||||
marketplace: "Marketplace",
|
||||
split: "Split",
|
||||
vote: "VoteERC20",
|
||||
pack: "Pack",
|
||||
multiwrap: "Multiwrap",
|
||||
};
|
||||
|
||||
const foundMapping = mapping[name as keyof typeof mapping];
|
||||
@@ -33,6 +33,6 @@ export default function generateDashboardUrl(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return `https://thirdweb.com/contracts/new/pre-built/${foundMapping}`;
|
||||
return `https://thirdweb.com/thirdweb.eth/${foundMapping}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { logger } from "../core/helpers/logger";
|
||||
import { spinner } from "../core/helpers/logger";
|
||||
import { BufferOrStringWithName, ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import chalk from "chalk";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
@@ -33,22 +31,12 @@ export async function upload(
|
||||
uploadPath: string,
|
||||
): Promise<string> {
|
||||
if (!uploadPath) {
|
||||
logger.error(
|
||||
`Please pass a path to a file or directory to upload with the following format:\n ${chalk.blueBright(
|
||||
`npx thirdweb@latest upload path/to/file.jpg`,
|
||||
)}\n`,
|
||||
);
|
||||
process.exit(1);
|
||||
return Promise.reject("No path provided");
|
||||
}
|
||||
|
||||
const pathExists = fs.existsSync(uploadPath);
|
||||
if (!pathExists) {
|
||||
logger.error(
|
||||
`Invalid path ${chalk.blueBright(
|
||||
uploadPath,
|
||||
)} provided. Please provide a valid path to a file or directory to upload."`,
|
||||
);
|
||||
process.exit(1);
|
||||
return Promise.reject("Invalid path provided");
|
||||
}
|
||||
|
||||
let uri = "";
|
||||
@@ -57,12 +45,7 @@ export async function upload(
|
||||
const files = recurseFiles(uploadPath, uploadPath);
|
||||
|
||||
if (files.length === 0) {
|
||||
logger.error(
|
||||
`No files detected in specified directory ${chalk.blueBright(
|
||||
uploadPath,
|
||||
)} to upload.`,
|
||||
);
|
||||
process.exit(1);
|
||||
return Promise.reject("No files detected in specified directory");
|
||||
}
|
||||
|
||||
const spin = spinner(
|
||||
@@ -82,12 +65,7 @@ export async function upload(
|
||||
uri = await storage.upload(file, { uploadWithoutDirectory: true });
|
||||
spin.succeed("Succesfully uploaded file to IPFS.");
|
||||
} else {
|
||||
logger.error(
|
||||
`Path ${chalk.blueBright(
|
||||
uploadPath,
|
||||
)} does not point to a valid file or directory. Please provide a valid path to a file or directory to upload`,
|
||||
);
|
||||
process.exit(1);
|
||||
return Promise.reject("Invalid path provided");
|
||||
}
|
||||
|
||||
return uri;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @thirdweb-dev/contracts-js
|
||||
|
||||
## 1.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#438](https://github.com/thirdweb-dev/js/pull/438) [`f451da6`](https://github.com/thirdweb-dev/js/commit/f451da6395689a5f89800ee63f34b6175b61f703) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix SigMint detection for OSRF NFT Collection contracts
|
||||
|
||||
## 1.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/contracts-js",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"main": "dist/thirdweb-dev-contracts-js.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js.esm.js",
|
||||
"license": "Apache-2.0",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/contracts-js",
|
||||
"dependencies": {
|
||||
"@thirdweb-dev/contracts": "^3.2.2"
|
||||
"@thirdweb-dev/contracts": "3.2.8-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @thirdweb-dev/react
|
||||
|
||||
## 3.6.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#425](https://github.com/thirdweb-dev/js/pull/425) [`f545a67`](https://github.com/thirdweb-dev/js/commit/f545a67e9fb597d27bb39ca515d24d306fbb121a) Thanks [@adam-maj](https://github.com/adam-maj)! - Add sameSite none and include credentials
|
||||
|
||||
- [#420](https://github.com/thirdweb-dev/js/pull/420) [`639e535`](https://github.com/thirdweb-dev/js/commit/639e535ed55280ad9d081001aab3f5af72bb3e45) Thanks [@jnsdls](https://github.com/jnsdls)! - update deps
|
||||
|
||||
## 3.6.3
|
||||
|
||||
## 3.6.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react",
|
||||
"version": "3.6.2",
|
||||
"version": "3.6.4",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -14,19 +14,19 @@ export const SupportedNetworkSelect: React.FC<SupportedNetworkSelectProps> = ({
|
||||
const deprecatedNetworks = useMemo(() => {
|
||||
return SUPPORTED_CHAIN_IDS.map((supportedChain) => {
|
||||
return getChainFromChainId(supportedChain);
|
||||
}).filter((n) => !!n.deprecated);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const testnets = useMemo(() => {
|
||||
return SUPPORTED_CHAIN_IDS.map((supportedChain) => {
|
||||
return getChainFromChainId(supportedChain);
|
||||
}).filter((n) => n.testnet && !n.deprecated);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const mainnets = useMemo(() => {
|
||||
return SUPPORTED_CHAIN_IDS.map((supportedChain) => {
|
||||
return getChainFromChainId(supportedChain);
|
||||
}).filter((n) => !n.testnet && !n.deprecated);
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<Select {...selectProps}>
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface ThirdwebNftMediaProps extends SharedMediaProps {
|
||||
/**
|
||||
* This component can be used to render NFTs from the thirdweb SDK.
|
||||
* Props: {@link ThirdwebNftMediaProps}
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* import { ThirdwebNftMedia, useContract, useNFT } from "@thirdweb-dev/react";
|
||||
|
||||
@@ -103,7 +103,10 @@ export class GnosisSafeConnector extends Connector {
|
||||
import("@gnosis.pm/safe-ethers-lib"),
|
||||
]);
|
||||
|
||||
const ethAdapter = new safeEthersLib.default({ ethers, signer });
|
||||
const ethAdapter = new safeEthersLib.default({
|
||||
ethers,
|
||||
signerOrProvider: signer,
|
||||
});
|
||||
|
||||
const safe = await safeCoreSdk.default.create({
|
||||
ethAdapter: ethAdapter as any,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NATIVE_TOKENS, SUPPORTED_CHAIN_ID } from "@thirdweb-dev/sdk";
|
||||
import { ChainId } from "@thirdweb-dev/sdk";
|
||||
import type { Chain as WagmiChain } from "wagmi";
|
||||
|
||||
export type Chain = WagmiChain & { deprecated?: boolean };
|
||||
export type Chain = WagmiChain;
|
||||
const chain: Record<string, Chain> = {
|
||||
mainnet: {
|
||||
id: ChainId.Mainnet,
|
||||
|
||||
@@ -106,7 +106,7 @@ export function useUnclaimedNFTSupply(contract: RequiredParam<DropContract>) {
|
||||
invariant(erc721, "No ERC721 Contract instance provided");
|
||||
return erc721.totalUnclaimedSupply();
|
||||
}
|
||||
invariant(false, "Contract is not an instance of ERC721")
|
||||
invariant(false, "Contract is not an instance of ERC721");
|
||||
},
|
||||
{ enabled: !!erc721 },
|
||||
);
|
||||
@@ -129,7 +129,7 @@ export function useClaimedNFTSupply(contract: RequiredParam<DropContract>) {
|
||||
if (erc721) {
|
||||
return erc721.totalClaimedSupply();
|
||||
}
|
||||
invariant(false, "Contract is not an instance of ERC721")
|
||||
invariant(false, "Contract is not an instance of ERC721");
|
||||
},
|
||||
{ enabled: !!erc721 },
|
||||
);
|
||||
|
||||
@@ -24,7 +24,9 @@ export function useUser() {
|
||||
authConfig,
|
||||
"Please specify an authConfig in the ThirdwebProvider",
|
||||
);
|
||||
const res = await fetch(`${authConfig.authUrl}/user`);
|
||||
const res = await fetch(`${authConfig.authUrl}/user`, {
|
||||
credentials: "include",
|
||||
});
|
||||
return (await res.json()) as ThirdwebAuthUser;
|
||||
},
|
||||
{
|
||||
|
||||
@@ -24,7 +24,9 @@ export function useUser() {
|
||||
authConfig,
|
||||
"Please specify an authConfig in the ThirdwebProvider",
|
||||
);
|
||||
const res = await fetch(`${authConfig.authUrl}/user`);
|
||||
const res = await fetch(`${authConfig.authUrl}/user`, {
|
||||
credentials: "include",
|
||||
});
|
||||
return (await res.json()) as ThirdwebAuthUser;
|
||||
},
|
||||
{
|
||||
|
||||
@@ -40,6 +40,8 @@ export function nftRoyaltyQuery(
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function useRoyaltySettings(program: RequiredParam<NFTCollection | NFTDrop>) {
|
||||
export function useRoyaltySettings(
|
||||
program: RequiredParam<NFTCollection | NFTDrop>,
|
||||
) {
|
||||
return useQuery(nftRoyaltyQuery(program));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ import {
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { NFTCollection, NFTDrop, UpdateCreatorInput } from "@thirdweb-dev/sdk/solana";
|
||||
import type {
|
||||
NFTCollection,
|
||||
NFTDrop,
|
||||
UpdateCreatorInput,
|
||||
} from "@thirdweb-dev/sdk/solana";
|
||||
|
||||
/**
|
||||
* Update the creators for an NFT program
|
||||
|
||||
@@ -4,7 +4,11 @@ import {
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { NFTCollection, NFTDrop, UpdateRoyaltySettingsInput } from "@thirdweb-dev/sdk/solana";
|
||||
import type {
|
||||
NFTCollection,
|
||||
NFTDrop,
|
||||
UpdateRoyaltySettingsInput,
|
||||
} from "@thirdweb-dev/sdk/solana";
|
||||
|
||||
/**
|
||||
* Update the royalty for an NFT program
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
# @thirdweb-dev/sdk
|
||||
|
||||
## 3.6.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#438](https://github.com/thirdweb-dev/js/pull/438) [`f451da6`](https://github.com/thirdweb-dev/js/commit/f451da6395689a5f89800ee63f34b6175b61f703) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix SigMint detection for OSRF NFT Collection contracts
|
||||
|
||||
- [#416](https://github.com/thirdweb-dev/js/pull/416) [`af3acc6`](https://github.com/thirdweb-dev/js/commit/af3acc6b10751b840e56aef6400da5eea6040df2) Thanks [@adam-maj](https://github.com/adam-maj)! - Add support for AWS KMS
|
||||
|
||||
- [#424](https://github.com/thirdweb-dev/js/pull/424) [`1bfb91d`](https://github.com/thirdweb-dev/js/commit/1bfb91dbf3a39160c987a425813ac5dbb84703ad) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Improve error parsing for other wallets
|
||||
|
||||
- [#436](https://github.com/thirdweb-dev/js/pull/436) [`ee47840`](https://github.com/thirdweb-dev/js/commit/ee478407673b0416e0c1cfe2be11bd6963395348) Thanks [@jnsdls](https://github.com/jnsdls)! - fix #429
|
||||
|
||||
- [#434](https://github.com/thirdweb-dev/js/pull/434) [`def4251`](https://github.com/thirdweb-dev/js/commit/def42511ff5a20d83f9094164dafb87e412571b5) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - [SOL] Remove workaround to fetch NFT owners
|
||||
|
||||
- [#429](https://github.com/thirdweb-dev/js/pull/429) [`034a257`](https://github.com/thirdweb-dev/js/commit/034a257442314c67729f7bafcbe3740cff33fa32) Thanks [@jakeloo](https://github.com/jakeloo)! - Extract more minimal proxy impl address
|
||||
|
||||
- [#418](https://github.com/thirdweb-dev/js/pull/418) [`eca4776`](https://github.com/thirdweb-dev/js/commit/eca47763cd89cc3b7aa57b542971837987540b55) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Increase gas limit for opening packs
|
||||
|
||||
- [#420](https://github.com/thirdweb-dev/js/pull/420) [`639e535`](https://github.com/thirdweb-dev/js/commit/639e535ed55280ad9d081001aab3f5af72bb3e45) Thanks [@jnsdls](https://github.com/jnsdls)! - update deps
|
||||
|
||||
- Updated dependencies [[`f451da6`](https://github.com/thirdweb-dev/js/commit/f451da6395689a5f89800ee63f34b6175b61f703)]:
|
||||
- @thirdweb-dev/contracts-js@1.2.2
|
||||
|
||||
## 3.6.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#408](https://github.com/thirdweb-dev/js/pull/408) [`48797c7`](https://github.com/thirdweb-dev/js/commit/48797c7cf0695965e33eb6bf602be4652f7085a8) Thanks [@jnsdls](https://github.com/jnsdls)! - [EVM] - add `Gasless` as a possible extension to feature-detect
|
||||
|
||||
- [#402](https://github.com/thirdweb-dev/js/pull/402) [`83dcaf7`](https://github.com/thirdweb-dev/js/commit/83dcaf70584af8c5cdf0427f9fc1ba82d89f0887) Thanks [@adam-maj](https://github.com/adam-maj)! - Expose sdk.fromWallet and wallet classes
|
||||
|
||||
- [#412](https://github.com/thirdweb-dev/js/pull/412) [`f03be39`](https://github.com/thirdweb-dev/js/commit/f03be398e0d7b75c9c5b4fb643f35903e89a17e7) Thanks [@easonchai](https://github.com/easonchai)! - Correctly handle contracts using the SignatureMintERC1155 ContractKit extension
|
||||
|
||||
- [#283](https://github.com/thirdweb-dev/js/pull/283) [`2eaa67a`](https://github.com/thirdweb-dev/js/commit/2eaa67acbe21464e7715d0b696afdc024460a44d) Thanks [@adam-maj](https://github.com/adam-maj)! - AppURI detection now enabled
|
||||
|
||||
- [#414](https://github.com/thirdweb-dev/js/pull/414) [`47f8945`](https://github.com/thirdweb-dev/js/commit/47f8945325447874b1ace0359919c1d54fd55436) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Improve error parsing
|
||||
|
||||
## 3.6.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-sdk-evm-wallets.cjs.js",
|
||||
"module": "dist/thirdweb-dev-sdk-evm-wallets.esm.js"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/sdk",
|
||||
"version": "3.6.2",
|
||||
"version": "3.6.4",
|
||||
"description": "The main thirdweb SDK.",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/sdk",
|
||||
"license": "Apache-2.0",
|
||||
@@ -19,6 +19,10 @@
|
||||
"module": "./solana/dist/thirdweb-dev-sdk-solana.esm.js",
|
||||
"default": "./solana/dist/thirdweb-dev-sdk-solana.cjs.js"
|
||||
},
|
||||
"./evm/wallets": {
|
||||
"module": "./evm/wallets/dist/thirdweb-dev-sdk-evm-wallets.esm.js",
|
||||
"default": "./evm/wallets/dist/thirdweb-dev-sdk-evm-wallets.cjs.js"
|
||||
},
|
||||
"./solana/server": {
|
||||
"module": "./solana/server/dist/thirdweb-dev-sdk-solana-server.esm.js",
|
||||
"default": "./solana/server/dist/thirdweb-dev-sdk-solana-server.cjs.js"
|
||||
@@ -28,6 +32,7 @@
|
||||
"files": [
|
||||
"dist/",
|
||||
"evm/",
|
||||
"wallets/",
|
||||
"solana/",
|
||||
"server/"
|
||||
],
|
||||
@@ -35,6 +40,7 @@
|
||||
"entrypoints": [
|
||||
"index.ts",
|
||||
"evm/index.ts",
|
||||
"evm/wallets/index.ts",
|
||||
"solana/index.ts",
|
||||
"solana/server/index.ts"
|
||||
],
|
||||
@@ -65,6 +71,7 @@
|
||||
"push": "yalc push"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.216.0",
|
||||
"@metaplex-foundation/amman": "^0.12.0",
|
||||
"@metaplex-foundation/amman-client": "^0.2.1",
|
||||
"@microsoft/api-documenter": "^7.19.4",
|
||||
@@ -90,13 +97,24 @@
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-tsdoc": "^0.2.16",
|
||||
"ethers": "^5.7.2",
|
||||
"ethers-aws-kms-signer": "^1.3.2",
|
||||
"hardhat": "^2.9.3",
|
||||
"mocha": "^10.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ethers": "^5"
|
||||
"@aws-sdk/client-secrets-manager": "^3.215.0",
|
||||
"ethers": "^5",
|
||||
"ethers-aws-kms-signer": "^1.3.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@aws-sdk/client-secrets-manager": {
|
||||
"optional": true
|
||||
},
|
||||
"ethers-aws-kms-signer": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@metaplex-foundation/js": "^0.17.6",
|
||||
@@ -109,7 +127,7 @@
|
||||
"bn.js": "^5.2.1",
|
||||
"bs58": "^5.0.0",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"merkletreejs": "^0.2.24",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
@@ -128,4 +146,4 @@
|
||||
"url": "https://github.com/thirdweb-dev/js/issues"
|
||||
},
|
||||
"author": "thirdweb eng <[email protected]>"
|
||||
}
|
||||
}
|
||||
@@ -394,20 +394,16 @@ export async function convertToTWError(
|
||||
contractInterface: ethers.utils.Interface,
|
||||
): Promise<TransactionError> {
|
||||
let raw: string;
|
||||
if (error.data) {
|
||||
if (typeof error === "object") {
|
||||
// metamask errors comes as objects, apply parsing on data object
|
||||
// TODO test errors from other wallets
|
||||
raw = JSON.stringify(error.data);
|
||||
} else if (error instanceof Error) {
|
||||
// regular ethers.js error
|
||||
raw = error.message;
|
||||
raw = JSON.stringify(error);
|
||||
} else {
|
||||
// not sure what this is, just throw it back
|
||||
raw = error.toString();
|
||||
}
|
||||
const reason =
|
||||
error.reason ||
|
||||
parseMessageParts(/.*?"message[^a-zA-Z0-9]*([^"\\]*).*?/, raw);
|
||||
parseMessageParts(/.*?"message[^a-zA-Z0-9]*([^"\\]*).*?/, raw) ||
|
||||
parseMessageParts(/.*?"reason[^a-zA-Z0-9]*([^"\\]*).*?/, raw);
|
||||
const data = parseMessageParts(/.*?"data[^a-zA-Z0-9]*([^"\\]*).*?/, raw);
|
||||
const rpcUrl = parseMessageParts(/.*?"url[^a-zA-Z0-9]*([^"\\]*).*?/, raw);
|
||||
let from = parseMessageParts(/.*?"from[^a-zA-Z0-9]*([^"\\]*).*?/, raw);
|
||||
|
||||
@@ -266,6 +266,42 @@ function toJSType(
|
||||
return jsType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param bytecode
|
||||
*/
|
||||
export function extractMinimalProxyImplementationAddress(
|
||||
bytecode: string,
|
||||
): string | undefined {
|
||||
// EIP-1167 clone minimal proxy - https://eips.ethereum.org/EIPS/eip-1167
|
||||
if (bytecode.startsWith("0x363d3d373d3d3d363d73")) {
|
||||
const implementationAddress = bytecode.slice(22, 62);
|
||||
return `0x${implementationAddress}`;
|
||||
}
|
||||
|
||||
// Minimal Proxy with receive() from 0xSplits - https://github.com/0xSplits/splits-contracts/blob/c7b741926ec9746182d0d1e2c4c2046102e5d337/contracts/libraries/Clones.sol
|
||||
if (bytecode.startsWith("0x36603057343d5230")) {
|
||||
// +40 = size of addr
|
||||
const implementationAddress = bytecode.slice(122, 122 + 40);
|
||||
return `0x${implementationAddress}`;
|
||||
}
|
||||
|
||||
// 0age's minimal proxy - https://medium.com/coinmonks/the-more-minimal-proxy-5756ae08ee48
|
||||
if (bytecode.startsWith("0x3d3d3d3d363d3d37363d73")) {
|
||||
// +40 = size of addr
|
||||
const implementationAddress = bytecode.slice(24, 24 + 40);
|
||||
return `0x${implementationAddress}`;
|
||||
}
|
||||
|
||||
// vyper's minimal proxy (uniswap v1) - https://etherscan.io/address/0x09cabec1ead1c0ba254b09efb3ee13841712be14#code
|
||||
if (bytecode.startsWith("0x366000600037611000600036600073")) {
|
||||
const implementationAddress = bytecode.slice(32, 32 + 40);
|
||||
return `0x${implementationAddress}`;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param address
|
||||
@@ -282,14 +318,20 @@ export async function resolveContractUriFromAddress(
|
||||
`Contract at ${address} does not exist on chain '${chain.name}' (chainId: ${chain.chainId})`,
|
||||
);
|
||||
}
|
||||
// EIP-1167 clone proxy - https://eips.ethereum.org/EIPS/eip-1167
|
||||
if (bytecode.startsWith("0x363d3d373d3d3d363d")) {
|
||||
const implementationAddress = bytecode.slice(22, 62);
|
||||
return await resolveContractUriFromAddress(
|
||||
`0x${implementationAddress}`,
|
||||
provider,
|
||||
);
|
||||
|
||||
try {
|
||||
const implementationAddress =
|
||||
extractMinimalProxyImplementationAddress(bytecode);
|
||||
if (implementationAddress) {
|
||||
return await resolveContractUriFromAddress(
|
||||
implementationAddress,
|
||||
provider,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// EIP-1967 proxy storage slots - https://eips.ethereum.org/EIPS/eip-1967
|
||||
try {
|
||||
const proxyStorage = await provider.getStorageAt(
|
||||
@@ -438,8 +480,8 @@ export async function fetchSourceFilesFromMetadata(
|
||||
async ([path, info]) => {
|
||||
const urls = (info as any).urls as string[];
|
||||
const ipfsLink = urls
|
||||
? urls.find((url) => url.includes("ipfs"))
|
||||
: undefined;
|
||||
? urls.find((url) => url.includes("ipfs"))
|
||||
: undefined;
|
||||
if (ipfsLink) {
|
||||
const ipfsHash = ipfsLink.split("ipfs/")[1];
|
||||
// 5 sec timeout for sources that haven't been uploaded to ipfs
|
||||
@@ -458,8 +500,8 @@ export async function fetchSourceFilesFromMetadata(
|
||||
return {
|
||||
filename: path,
|
||||
source:
|
||||
(info as any).content ||
|
||||
"Could not find source for this contract",
|
||||
(info as any).content ||
|
||||
"Could not find source for this contract",
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -543,8 +585,8 @@ export function detectFeatures(
|
||||
}
|
||||
|
||||
function extractFeatures(
|
||||
input: Record<string, FeatureWithEnabled>,
|
||||
enabledExtensions: FeatureWithEnabled[],
|
||||
input: Record<string, FeatureWithEnabled>,
|
||||
enabledExtensions: FeatureWithEnabled[],
|
||||
) {
|
||||
if (!input) {
|
||||
return;
|
||||
@@ -556,10 +598,7 @@ function extractFeatures(
|
||||
enabledExtensions.push(extension);
|
||||
}
|
||||
// recurse
|
||||
extractFeatures(
|
||||
extension.features,
|
||||
enabledExtensions,
|
||||
);
|
||||
extractFeatures(extension.features, enabledExtensions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
FEATURE_PRIMARY_SALE,
|
||||
FEATURE_ROYALTY,
|
||||
FEATURE_APPURI,
|
||||
FEATURE_GASLESS,
|
||||
} from "./thirdweb-features";
|
||||
|
||||
/**
|
||||
@@ -104,7 +105,8 @@ export type Feature =
|
||||
| typeof FEATURE_PERMISSIONS_ENUMERABLE
|
||||
| typeof FEATURE_METADATA
|
||||
| typeof FEATURE_APPURI
|
||||
| typeof FEATURE_OWNER;
|
||||
| typeof FEATURE_OWNER
|
||||
| typeof FEATURE_GASLESS;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -132,4 +134,5 @@ export const SUPPORTED_FEATURES: Record<string, Feature> = {
|
||||
[FEATURE_METADATA.name]: FEATURE_METADATA,
|
||||
[FEATURE_APPURI.name]: FEATURE_APPURI,
|
||||
[FEATURE_OWNER.name]: FEATURE_OWNER,
|
||||
[FEATURE_GASLESS.name]: FEATURE_GASLESS,
|
||||
};
|
||||
|
||||
@@ -12,8 +12,8 @@ import ILazyMintAbi from "@thirdweb-dev/contracts-js/dist/abis/ILazyMint.json";
|
||||
import IMintableERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/IMintableERC721.json";
|
||||
import MulticallAbi from "@thirdweb-dev/contracts-js/dist/abis/IMulticall.json";
|
||||
import SignatureMintERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/ISignatureMintERC721.json";
|
||||
import SignatureMintERC721_V1Abi from "@thirdweb-dev/contracts-js/dist/abis/ISignatureMintERC721_V1.json";
|
||||
import TieredDropAbi from "@thirdweb-dev/contracts-js/dist/abis/LazyMintWithTier.json";
|
||||
import TokenERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/TokenERC721.json";
|
||||
|
||||
export const FEATURE_NFT_BURNABLE = {
|
||||
name: "ERC721Burnable",
|
||||
@@ -163,7 +163,7 @@ export const FEATURE_NFT_SIGNATURE_MINTABLE_V1 = {
|
||||
sdk: "sdk.erc721signaturemint",
|
||||
contracts: "ISignatureMintERC721",
|
||||
},
|
||||
abis: [TokenERC721Abi],
|
||||
abis: [SignatureMintERC721_V1Abi],
|
||||
features: {},
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import IERC2771ContextAbi from "@thirdweb-dev/contracts-js/dist/abis/ERC2771Context.json";
|
||||
import IAppURI from "@thirdweb-dev/contracts-js/dist/abis/IAppURI.json";
|
||||
import IContractMetadataAbi from "@thirdweb-dev/contracts-js/dist/abis/IContractMetadata.json";
|
||||
import IPermissionsAbi from "@thirdweb-dev/contracts-js/dist/abis/IPermissions.json";
|
||||
@@ -96,3 +97,15 @@ export const FEATURE_OWNER = {
|
||||
abis: [IOwnableAbi],
|
||||
features: {},
|
||||
} as const;
|
||||
|
||||
export const FEATURE_GASLESS = {
|
||||
name: "Gasless",
|
||||
namespace: "gasless",
|
||||
docLinks: {
|
||||
sdk: "sdk.gaslesstransaction",
|
||||
// TODO add the correct name for this once it's added to portal
|
||||
contracts: "",
|
||||
},
|
||||
abis: [IERC2771ContextAbi],
|
||||
features: {},
|
||||
} as const;
|
||||
|
||||
@@ -536,10 +536,14 @@ export class Pack extends StandardErc1155<PackContract> {
|
||||
tokenId: BigNumberish,
|
||||
amount: BigNumberish = 1,
|
||||
): Promise<PackRewards> {
|
||||
const receipt = await this.contractWrapper.sendTransaction("openPack", [
|
||||
tokenId,
|
||||
amount,
|
||||
]);
|
||||
const receipt = await this.contractWrapper.sendTransaction(
|
||||
"openPack",
|
||||
[tokenId, amount],
|
||||
{
|
||||
// Higher gas limit for opening packs
|
||||
gasLimit: 500000,
|
||||
},
|
||||
);
|
||||
const event = this.contractWrapper.parseLogs<PackOpenedEvent>(
|
||||
"PackOpened",
|
||||
receipt?.logs,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { FEATURE_TOKEN } from "../constants/erc20-features";
|
||||
import { FEATURE_NFT } from "../constants/erc721-features";
|
||||
import { FEATURE_EDITION } from "../constants/erc1155-features";
|
||||
import {
|
||||
FEATURE_APPURI,
|
||||
FEATURE_OWNER,
|
||||
FEATURE_PERMISSIONS,
|
||||
FEATURE_PLATFORM_FEE,
|
||||
@@ -39,7 +38,6 @@ import type {
|
||||
IPrimarySale,
|
||||
IRoyalty,
|
||||
Ownable,
|
||||
AppURI,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BaseContract, CallOverrides, ContractInterface } from "ethers";
|
||||
@@ -84,6 +82,7 @@ export class SmartContract<TContract extends BaseContract = BaseContract>
|
||||
public publishedMetadata: ContractPublishedMetadata<TContract>;
|
||||
public abi: ContractInterface;
|
||||
public metadata: ContractMetadata<BaseContract, any>;
|
||||
public appURI: ContractAppURI<BaseContract>;
|
||||
|
||||
/**
|
||||
* Handle royalties
|
||||
@@ -120,13 +119,6 @@ export class SmartContract<TContract extends BaseContract = BaseContract>
|
||||
return assertEnabled(this.detectOwnable(), FEATURE_OWNER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set and get the appuri of the contract
|
||||
*/
|
||||
get appURI(): ContractAppURI<AppURI> {
|
||||
return assertEnabled(this.detectAppURI(), FEATURE_APPURI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-detects ERC20 standard functions.
|
||||
*/
|
||||
@@ -186,6 +178,8 @@ export class SmartContract<TContract extends BaseContract = BaseContract>
|
||||
CustomContractSchema,
|
||||
this.storage,
|
||||
);
|
||||
|
||||
this.appURI = new ContractAppURI(this.contractWrapper, this.metadata);
|
||||
}
|
||||
|
||||
onNetworkUpdated(network: NetworkOrSignerOrProvider): void {
|
||||
@@ -290,12 +284,6 @@ export class SmartContract<TContract extends BaseContract = BaseContract>
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private detectAppURI() {
|
||||
if (detectContractFeature<AppURI>(this.contractWrapper, "AppURI")) {
|
||||
return new ContractAppURI(this.contractWrapper);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
private detectOwnable() {
|
||||
if (detectContractFeature<Ownable>(this.contractWrapper, "Ownable")) {
|
||||
return new ContractOwner(this.contractWrapper);
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { detectContractFeature } from "../../common";
|
||||
import { FEATURE_APPURI } from "../../constants/thirdweb-features";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResult } from "../types";
|
||||
import { ContractMetadata } from "./contract-metadata";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import type { AppURI } from "@thirdweb-dev/contracts-js";
|
||||
import { BaseContract } from "ethers";
|
||||
|
||||
/**
|
||||
* Have an official Application URI for this contract.
|
||||
@@ -17,14 +20,19 @@ import type { AppURI } from "@thirdweb-dev/contracts-js";
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class ContractAppURI<TContract extends AppURI>
|
||||
export class ContractAppURI<TContract extends BaseContract>
|
||||
implements DetectableFeature
|
||||
{
|
||||
featureName = FEATURE_APPURI.name;
|
||||
private contractWrapper;
|
||||
metadata: ContractMetadata<BaseContract, any>;
|
||||
|
||||
constructor(contractWrapper: ContractWrapper<TContract>) {
|
||||
constructor(
|
||||
contractWrapper: ContractWrapper<TContract>,
|
||||
metadata: ContractMetadata<BaseContract, any>,
|
||||
) {
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,7 +40,11 @@ export class ContractAppURI<TContract extends AppURI>
|
||||
* @returns the appURI object
|
||||
*/
|
||||
public async get() {
|
||||
return await this.contractWrapper.readContract.appURI();
|
||||
if (detectContractFeature<AppURI>(this.contractWrapper, "AppURI")) {
|
||||
return await this.contractWrapper.readContract.appURI();
|
||||
}
|
||||
|
||||
return (await this.metadata.get()).appURI || "";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,10 +52,14 @@ export class ContractAppURI<TContract extends AppURI>
|
||||
* @param appURI - the uri to set (typically an IPFS hash)
|
||||
*/
|
||||
public async set(appURI: string): Promise<TransactionResult> {
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction("setAppURI", [
|
||||
appURI,
|
||||
]),
|
||||
};
|
||||
if (detectContractFeature<AppURI>(this.contractWrapper, "AppURI")) {
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction("setAppURI", [
|
||||
appURI,
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
return await this.metadata.update({ appURI });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ContractEvent, EventQueryOptions } from "../../types/index";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { EventFragment } from "@ethersproject/abi";
|
||||
import { BaseContract, Event, providers } from "ethers";
|
||||
import { ListenerFn } from "eventemitter3";
|
||||
import type { EventEmitter } from "eventemitter3";
|
||||
|
||||
/**
|
||||
* Listen to Contract events in real time
|
||||
@@ -28,7 +28,7 @@ export class ContractEvents<TContract extends BaseContract> {
|
||||
* @param listener - the callback function that will be called on every transaction
|
||||
* @public
|
||||
*/
|
||||
public addTransactionListener(listener: ListenerFn) {
|
||||
public addTransactionListener(listener: EventEmitter.ListenerFn) {
|
||||
this.contractWrapper.addListener(EventType.Transaction, listener);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export class ContractEvents<TContract extends BaseContract> {
|
||||
* @param listener - the callback function to remove
|
||||
* @public
|
||||
*/
|
||||
public removeTransactionListener(listener: ListenerFn) {
|
||||
public removeTransactionListener(listener: EventEmitter.ListenerFn) {
|
||||
this.contractWrapper.off(EventType.Transaction, listener);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export class ContractOwner<TContract extends Ownable>
|
||||
* Return the current owner of the contract
|
||||
* @returns the owner address
|
||||
*/
|
||||
public async get(): Promise<string> {
|
||||
public async get(): Promise<string> {
|
||||
return this.contractWrapper.readContract.owner();
|
||||
}
|
||||
|
||||
@@ -31,13 +31,13 @@ export class ContractOwner<TContract extends Ownable>
|
||||
* @remarks Can only be called by the current owner.
|
||||
*
|
||||
* @param address - the address of the new owner
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* await contract.owner.set("0x1234567890123456789012345678901234567890");
|
||||
* ```
|
||||
*/
|
||||
public async set(address: string): Promise<TransactionResult> {
|
||||
public async set(address: string): Promise<TransactionResult> {
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction("setOwner", [
|
||||
address,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { normalizePriceValue, setErc20Allowance } from "../../common/currency";
|
||||
import { getPrebuiltInfo } from "../../common/legacy";
|
||||
import { uploadOrExtractURIs } from "../../common/nft";
|
||||
import { FEATURE_EDITION_SIGNATURE_MINTABLE } from "../../constants/erc1155-features";
|
||||
import type { NFTCollectionInitializer } from "../../contracts";
|
||||
@@ -330,6 +331,12 @@ export class Erc1155SignatureMintable implements DetectableFeature {
|
||||
const signer = this.contractWrapper.getSigner();
|
||||
invariant(signer, "No signer available");
|
||||
|
||||
const contractInfo = await getPrebuiltInfo(
|
||||
this.contractWrapper.readContract.address,
|
||||
this.contractWrapper.getProvider(),
|
||||
);
|
||||
const isLegacyContract = contractInfo?.type === "TokenERC1155";
|
||||
|
||||
return await Promise.all(
|
||||
parsedRequests.map(async (m, i) => {
|
||||
const uri = uris[i];
|
||||
@@ -340,7 +347,7 @@ export class Erc1155SignatureMintable implements DetectableFeature {
|
||||
const signature = await this.contractWrapper.signTypedData(
|
||||
signer,
|
||||
{
|
||||
name: "TokenERC1155",
|
||||
name: isLegacyContract ? "TokenERC1155" : "SignatureMintERC1155",
|
||||
version: "1",
|
||||
chainId,
|
||||
verifyingContract: this.contractWrapper.readContract.address,
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { SmartContract } from "../contracts/smart-contract";
|
||||
import { SDKOptions } from "../schema/sdk-options";
|
||||
import { CurrencyValue } from "../types/index";
|
||||
import type { AbstractWallet } from "../wallets";
|
||||
import { WalletAuthenticator } from "./auth/wallet-authenticator";
|
||||
import type { ContractMetadata } from "./classes";
|
||||
import { ContractDeployer } from "./classes/contract-deployer";
|
||||
@@ -39,6 +40,43 @@ import invariant from "tiny-invariant";
|
||||
* @public
|
||||
*/
|
||||
export class ThirdwebSDK extends RPCConnectionHandler {
|
||||
/**
|
||||
* Get an instance of the thirdweb SDK based on an AbstractWallet
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk"
|
||||
*
|
||||
* const wallet = new AbstractWalletImplementation();
|
||||
* const sdk = await ThirdwebSDK.fromWallet(wallet, "mainnet");
|
||||
* ```
|
||||
*
|
||||
* @param wallet - the implementation of the AbstractWallet class to use for signing
|
||||
* @param network - the network (chain) to connect to (e.g. "mainnet", "rinkeby", "polygon", "mumbai"...) or a fully formed RPC url
|
||||
* @param options - the SDK options to use
|
||||
* @param storage - optional storage implementation to use
|
||||
* @returns an instance of the SDK
|
||||
*
|
||||
* @beta
|
||||
*/
|
||||
static async fromWallet(
|
||||
wallet: AbstractWallet,
|
||||
network: ChainOrRpc,
|
||||
options: SDKOptions = {},
|
||||
storage: ThirdwebStorage = new ThirdwebStorage(),
|
||||
) {
|
||||
const signerOrProvider = getProviderForNetwork(network);
|
||||
const provider = Signer.isSigner(signerOrProvider)
|
||||
? signerOrProvider.provider
|
||||
: typeof signerOrProvider === "string"
|
||||
? getReadOnlyProvider(signerOrProvider)
|
||||
: signerOrProvider;
|
||||
|
||||
const signer = await wallet.getSigner(provider);
|
||||
|
||||
return ThirdwebSDK.fromSigner(signer, network, options, storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the thirdweb SDK based on an existing ethers signer
|
||||
*
|
||||
@@ -54,8 +92,8 @@ export class ThirdwebSDK extends RPCConnectionHandler {
|
||||
* @param signer - a ethers Signer to be used for transactions
|
||||
* @param network - the network (chain) to connect to (e.g. "mainnet", "rinkeby", "polygon", "mumbai"...) or a fully formed RPC url
|
||||
* @param options - the SDK options to use
|
||||
* @returns an instance of the SDK
|
||||
* @param storage - optional storage implementation to use
|
||||
* @returns an instance of the SDK
|
||||
*
|
||||
* @beta
|
||||
*/
|
||||
|
||||
@@ -8,3 +8,4 @@ export * from "./token-erc20";
|
||||
export * from "./token-erc721";
|
||||
export * from "./token-erc1155";
|
||||
export * from "./vote";
|
||||
export * from "./custom";
|
||||
|
||||
@@ -80,4 +80,4 @@ export type SDKOptions = z.input<typeof SDKOptionsSchema>;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type SDKOptionsOutput = z.output<typeof SDKOptionsSchema>;
|
||||
export type SDKOptionsOutput = z.output<typeof SDKOptionsSchema>;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { AbstractWallet } from "../types/abstract";
|
||||
import {
|
||||
TypedDataDomain,
|
||||
TypedDataField,
|
||||
} from "@ethersproject/abstract-signer";
|
||||
import { ethers } from "ethers";
|
||||
import { AwsKmsSigner, AwsKmsSignerCredentials } from "ethers-aws-kms-signer";
|
||||
|
||||
/**
|
||||
* Create a wallet instance using a private key stored in AWS KMS.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk"
|
||||
* import { AwsKmsWallet } from "@thirdweb-dev/sdk/evm/wallets"
|
||||
*
|
||||
* const wallet = new AwsKmsallet({
|
||||
* region: "us-east-1",
|
||||
* accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
* secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
* sessionToken: process.env.AWS_SESSION_TOKEN,
|
||||
* keyId: process.env.AWS_KEY_ID,
|
||||
* });
|
||||
*
|
||||
* const sdk = await ThirdwebSDK.fromWallet(wallet, "mainnet");
|
||||
* ```
|
||||
*/
|
||||
export class AwsKmsWallet extends AbstractWallet {
|
||||
private cachedSigner: AwsKmsSigner;
|
||||
|
||||
constructor(options: AwsKmsSignerCredentials) {
|
||||
super();
|
||||
this.cachedSigner = this.updateSigner(new AwsKmsSigner(options));
|
||||
}
|
||||
|
||||
async getSigner(
|
||||
provider?: ethers.providers.Provider,
|
||||
): Promise<ethers.Signer> {
|
||||
if (provider) {
|
||||
this.cachedSigner = this.updateSigner(
|
||||
this.cachedSigner.connect(provider),
|
||||
);
|
||||
}
|
||||
|
||||
return this.cachedSigner;
|
||||
}
|
||||
|
||||
// Add _signTypedData method onto the signer for now so we don't need to reimpliment
|
||||
// The entire AWS KMS signer repository and maintain it ourselves.
|
||||
private updateSigner(signer: AwsKmsSigner): AwsKmsSigner {
|
||||
(signer as any)._signTypedData = async function (
|
||||
domain: TypedDataDomain,
|
||||
types: Record<string, Array<TypedDataField>>,
|
||||
value: Record<string, any>,
|
||||
) {
|
||||
const hash = ethers.utils._TypedDataEncoder.hash(domain, types, value);
|
||||
return signer._signDigest(hash);
|
||||
};
|
||||
|
||||
return signer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { AbstractWallet } from "../types/abstract";
|
||||
import {
|
||||
GetSecretValueCommand,
|
||||
SecretsManagerClient,
|
||||
SecretsManagerClientConfig,
|
||||
} from "@aws-sdk/client-secrets-manager";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
export type AwsSecretsManagerWalletOptions = {
|
||||
secretId: string;
|
||||
secretKeyName: string;
|
||||
awsConfig: SecretsManagerClientConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a wallet instance using a private key stored in AWS Secrets Manager.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk"
|
||||
* import { AwsSecretsManagerWallet } from "@thirdweb-dev/sdk/evm/wallets"
|
||||
*
|
||||
* const wallet = new AwsSecretsManagerWallet({
|
||||
* secretName: "my-secret",
|
||||
* secretKeyName: "private-key",
|
||||
* awsConfig: {
|
||||
* region: "us-east-1",
|
||||
* credentials: {
|
||||
* accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
* secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
* },
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* const sdk = await ThirdwebSDK.fromWallet(wallet, "mainnet");
|
||||
* ```
|
||||
*/
|
||||
export class AwsSecretsManagerWallet extends AbstractWallet {
|
||||
private secretId: string;
|
||||
private secretKeyName: string;
|
||||
private client: SecretsManagerClient;
|
||||
private cachedSigner: ethers.Signer | undefined;
|
||||
|
||||
constructor(options: AwsSecretsManagerWalletOptions) {
|
||||
super();
|
||||
this.secretId = options.secretId;
|
||||
this.secretKeyName = options.secretKeyName;
|
||||
this.client = new SecretsManagerClient(options.awsConfig);
|
||||
}
|
||||
|
||||
async getSigner(
|
||||
provider?: ethers.providers.Provider,
|
||||
): Promise<ethers.Signer> {
|
||||
if (this.cachedSigner) {
|
||||
return this.cachedSigner;
|
||||
}
|
||||
|
||||
const res = await this.client.send(
|
||||
new GetSecretValueCommand({
|
||||
SecretId: this.secretId,
|
||||
VersionStage: "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified
|
||||
}),
|
||||
);
|
||||
|
||||
if (!res || !res.SecretString) {
|
||||
throw new Error(`No secret found at ${this.secretId}`);
|
||||
}
|
||||
|
||||
const privateKey = JSON.parse(res.SecretString)[this.secretKeyName];
|
||||
if (!privateKey) {
|
||||
throw new Error(
|
||||
`Secret ${this.secretId} does not have key ${this.secretKeyName}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.cachedSigner = new ethers.Wallet(privateKey, provider);
|
||||
return this.cachedSigner;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./aws-secrets-manager";
|
||||
export * from "./aws-kms";
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./core";
|
||||
export * from "./types";
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ethers } from "ethers";
|
||||
|
||||
export abstract class AbstractWallet {
|
||||
abstract getSigner(
|
||||
provider?: ethers.providers.Provider,
|
||||
): Promise<ethers.Signer>;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./abstract";
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
} from "../../constants/addresses";
|
||||
import { TransactionResult } from "../../types/common";
|
||||
import { CreatorOutput } from "../../types/programs";
|
||||
import { getPublicRpc } from "../../utils/urls";
|
||||
import { parseCreators } from "./creators-helper";
|
||||
import {
|
||||
findMasterEditionV2Pda,
|
||||
@@ -96,15 +95,10 @@ export class NFTHelper {
|
||||
|
||||
async ownerOf(nftAddress: string): Promise<string | undefined> {
|
||||
try {
|
||||
// TODO switch back to normal connection when alchemy supports getTokenLargestAccounts
|
||||
const connection = new Connection(
|
||||
getPublicRpc(this.metaplex),
|
||||
"confirmed",
|
||||
);
|
||||
const largestAccounts = await connection.getTokenLargestAccounts(
|
||||
const largestAccounts = await this.connection.getTokenLargestAccounts(
|
||||
new PublicKey(nftAddress),
|
||||
);
|
||||
const largestAccountInfo = await connection.getParsedAccountInfo(
|
||||
const largestAccountInfo = await this.connection.getParsedAccountInfo(
|
||||
largestAccounts.value[0].address,
|
||||
);
|
||||
const parsedData = largestAccountInfo?.value?.data as ParsedAccountData;
|
||||
|
||||
@@ -56,7 +56,16 @@ export type NFTCollectionMetadataInput = z.input<
|
||||
*/
|
||||
export const TokenMetadataInputSchema = CommonContractSchema.extend({
|
||||
decimals: z.number().default(9),
|
||||
initialSupply: AmountSchema,
|
||||
initialSupply: AmountSchema.superRefine((val, context) => {
|
||||
// TODO remove this limitation when metaplex fixes https://github.com/metaplex-foundation/js/issues/421
|
||||
if (Number(val) > 9999999) {
|
||||
context.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Initial supply must less than 10M, additional supply can be minted after deployment.`,
|
||||
path: ["initialSupply"],
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -80,7 +89,7 @@ export type UpdateCreatorInput = {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type UpdateRoyaltySettingsInput = {
|
||||
export type UpdateRoyaltySettingsInput = {
|
||||
sellerFeeBasisPoints: number;
|
||||
updateAll: boolean;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
extractMinimalProxyImplementationAddress,
|
||||
ThirdwebSDK,
|
||||
} from "../../src/evm";
|
||||
import { signers, sdk } from "./before-setup";
|
||||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
|
||||
import { expect } from "chai";
|
||||
|
||||
global.fetch = require("cross-fetch");
|
||||
|
||||
describe("Custom Contracts", async () => {
|
||||
let adminWallet: SignerWithAddress;
|
||||
let realSDK: ThirdwebSDK;
|
||||
|
||||
before(async () => {
|
||||
[adminWallet] = signers;
|
||||
realSDK = new ThirdwebSDK(adminWallet);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
sdk.updateSignerOrProvider(adminWallet);
|
||||
realSDK.updateSignerOrProvider(adminWallet);
|
||||
});
|
||||
|
||||
it("should extract implementation address for eip-1167 minimal proxy", async () => {
|
||||
// https://eips.ethereum.org/EIPS/eip-1167#specification
|
||||
const code =
|
||||
"0x363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3";
|
||||
const implementationAddress =
|
||||
extractMinimalProxyImplementationAddress(code);
|
||||
expect(implementationAddress).to.equal(
|
||||
"0xbebebebebebebebebebebebebebebebebebebebe",
|
||||
);
|
||||
});
|
||||
|
||||
it("should extract implementation address for minimal proxy with receive", async () => {
|
||||
// https://github.com/0xSplits/splits-contracts/blob/c7b741926ec9746182d0d1e2c4c2046102e5d337/contracts/libraries/Clones.sol#L32
|
||||
const code =
|
||||
"0x36603057343d52307f830d2d700a97af574b186c80d40429385d24241565b08a7c559ba283a964d9b160203da23d3df35b3d3d3d3d363d3d37363d73bebebebebebebebebebebebebebebebebebebebe5af43d3d93803e605b57fd5bf3";
|
||||
const implementationAddress =
|
||||
extractMinimalProxyImplementationAddress(code);
|
||||
expect(implementationAddress).to.equal(
|
||||
"0xbebebebebebebebebebebebebebebebebebebebe",
|
||||
);
|
||||
});
|
||||
|
||||
it("should extract implementation address for more minimal proxy", async () => {
|
||||
// https://medium.com/coinmonks/the-more-minimal-proxy-5756ae08ee48
|
||||
const code =
|
||||
"0x3d3d3d3d363d3d37363d73bebebebebebebebebebebebebebebebebebebebe5af43d3d93803e602a57fd5bf3";
|
||||
const implementationAddress =
|
||||
extractMinimalProxyImplementationAddress(code);
|
||||
expect(implementationAddress).to.equal(
|
||||
"0xbebebebebebebebebebebebebebebebebebebebe",
|
||||
);
|
||||
});
|
||||
|
||||
it("should extract implementation address for older vyper (uniswap v1) proxy", async () => {
|
||||
const code =
|
||||
"0x366000600037611000600036600073bebebebebebebebebebebebebebebebebebebebe5af41558576110006000f3";
|
||||
const implementationAddress =
|
||||
extractMinimalProxyImplementationAddress(code);
|
||||
expect(implementationAddress).to.equal(
|
||||
"0xbebebebebebebebebebebebebebebebebebebebe",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,7 @@
|
||||
import {
|
||||
ChainId, getAllDetectedFeatureNames, isFeatureEnabled,
|
||||
ChainId,
|
||||
getAllDetectedFeatureNames,
|
||||
isFeatureEnabled,
|
||||
resolveContractUriFromAddress,
|
||||
ThirdwebSDK,
|
||||
} from "../../src/evm";
|
||||
@@ -7,11 +9,12 @@ import { implementations, signers } from "./before-setup";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
|
||||
import {
|
||||
DropERC721__factory, DropERC721_V3__factory,
|
||||
DropERC721__factory,
|
||||
DropERC721_V3__factory,
|
||||
TokenERC721__factory,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { expect } from "chai";
|
||||
import { assert, expect } from "chai";
|
||||
import { ethers } from "ethers";
|
||||
import { readFileSync } from "fs";
|
||||
import invariant from "tiny-invariant";
|
||||
@@ -95,15 +98,13 @@ describe("Publishing", async () => {
|
||||
|
||||
it("should extract all features", async () => {
|
||||
const tokenFeatures = getAllDetectedFeatureNames(TokenERC721__factory.abi);
|
||||
expect(
|
||||
tokenFeatures
|
||||
).to.contain("ERC721Enumerable");
|
||||
expect(
|
||||
getAllDetectedFeatureNames(DropERC721__factory.abi)
|
||||
).to.contain("ERC721ClaimPhasesV2");
|
||||
expect(
|
||||
getAllDetectedFeatureNames(DropERC721_V3__factory.abi)
|
||||
).to.contain("ERC721ClaimPhasesV1");
|
||||
expect(tokenFeatures).to.contain("ERC721Enumerable");
|
||||
expect(getAllDetectedFeatureNames(DropERC721__factory.abi)).to.contain(
|
||||
"ERC721ClaimPhasesV2",
|
||||
);
|
||||
expect(getAllDetectedFeatureNames(DropERC721_V3__factory.abi)).to.contain(
|
||||
"ERC721ClaimPhasesV1",
|
||||
);
|
||||
});
|
||||
|
||||
it("should update bio", async () => {
|
||||
@@ -466,6 +467,30 @@ describe("Publishing", async () => {
|
||||
expect(nftsAfter[1].supply).to.equal(0);
|
||||
});
|
||||
|
||||
it("ERC1155Signature mint feature detection", async () => {
|
||||
const ipfsUri = "ipfs://QmNuKYGZoiHyumKjT7gPk3vwy3WKt7gTf1hKGZ2eyGZGRd";
|
||||
const addr = await sdk.deployer.deployContractFromUri(ipfsUri, [
|
||||
"test",
|
||||
"test",
|
||||
]);
|
||||
const c = await sdk.getContract(addr);
|
||||
const payload = {
|
||||
metadata: {
|
||||
name: "SigMinted Edition",
|
||||
},
|
||||
to: samWallet.address, // Who will receive the NFT (or AddressZero for anyone)
|
||||
price: 0.5, // the price to pay for minting
|
||||
royaltyBps: 100, // custom royalty fees for this NFT (in bps)
|
||||
quantity: "1",
|
||||
};
|
||||
|
||||
const goodPayload = await c.erc1155.signature.generate(payload);
|
||||
const valid = await c.erc1155.signature.verify(goodPayload);
|
||||
expect(valid).to.eq(true);
|
||||
const tx = await c.erc1155.signature.mint(goodPayload);
|
||||
expect(tx.id.toNumber()).to.eq(0);
|
||||
});
|
||||
|
||||
it("Constructor params with tuples", async () => {
|
||||
const ipfsUri = "ipfs://QmZQa56Cj1gFnZgKSkvGE5uzhaQrQV3nU6upDWDusCaCwY/0";
|
||||
const addr = await sdk.deployer.deployContractFromUri(ipfsUri, [
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# folders
|
||||
dist/
|
||||
|
||||
# files
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["thirdweb"],
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
# @thirdweb-dev/unity-js-bridge
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#420](https://github.com/thirdweb-dev/js/pull/420) [`639e535`](https://github.com/thirdweb-dev/js/commit/639e535ed55280ad9d081001aab3f5af72bb3e45) Thanks [@jnsdls](https://github.com/jnsdls)! - Initial Release
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`639e535`](https://github.com/thirdweb-dev/js/commit/639e535ed55280ad9d081001aab3f5af72bb3e45), [`f451da6`](https://github.com/thirdweb-dev/js/commit/f451da6395689a5f89800ee63f34b6175b61f703), [`af3acc6`](https://github.com/thirdweb-dev/js/commit/af3acc6b10751b840e56aef6400da5eea6040df2), [`1bfb91d`](https://github.com/thirdweb-dev/js/commit/1bfb91dbf3a39160c987a425813ac5dbb84703ad), [`ee47840`](https://github.com/thirdweb-dev/js/commit/ee478407673b0416e0c1cfe2be11bd6963395348), [`def4251`](https://github.com/thirdweb-dev/js/commit/def42511ff5a20d83f9094164dafb87e412571b5), [`034a257`](https://github.com/thirdweb-dev/js/commit/034a257442314c67729f7bafcbe3740cff33fa32), [`eca4776`](https://github.com/thirdweb-dev/js/commit/eca47763cd89cc3b7aa57b542971837987540b55), [`639e535`](https://github.com/thirdweb-dev/js/commit/639e535ed55280ad9d081001aab3f5af72bb3e45)]:
|
||||
- @thirdweb-dev/wallets@0.1.0
|
||||
- @thirdweb-dev/sdk@3.6.4
|
||||
@@ -0,0 +1,16 @@
|
||||
<p align="center">
|
||||
<br />
|
||||
<a href="https://thirdweb.com"><img src="https://github.com/thirdweb-dev/js/blob/main/packages/sdk/logo.svg?raw=true" width="200" alt=""/></a>
|
||||
<br />
|
||||
</p>
|
||||
<h1 align="center">thirdweb Unity JS bridge</h1>
|
||||
<p align="center">
|
||||
<!-- <a href="https://www.npmjs.com/package/@thirdweb-dev/sdk"><img src="https://img.shields.io/npm/v/@thirdweb-dev/sdk?color=red&label=npm&logo=npm" alt="npm version"/></a> -->
|
||||
<!-- <a href="https://github.com/thirdweb-dev/js/actions/workflows/CI.yml"><img alt="Build Status" src="https://github.com/thirdweb-dev/js/actions/workflows/CI.yml/badge.svg"/></a> -->
|
||||
<!-- <a href="https://discord.gg/thirdweb"><img alt="Join our Discord!" src="https://img.shields.io/discord/834227967404146718.svg?color=7289da&label=discord&logo=discord&style=flat"/></a> -->
|
||||
|
||||
</p>
|
||||
<!-- <p align="center"><strong>Best in class Web3 SDK for Browser, Node and Mobile apps</strong></p> -->
|
||||
<br />
|
||||
|
||||
### todo
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/unity-js-bridge",
|
||||
"version": "0.1.0",
|
||||
"main": "dist/thirdweb-unity-bridge.js",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/unity-js-bridge",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/thirdweb-dev/js/issues"
|
||||
},
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@thirdweb-dev/sdk": "*",
|
||||
"@thirdweb-dev/storage": "*",
|
||||
"@thirdweb-dev/wallets": "*",
|
||||
"ethers": "^5.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.15.16",
|
||||
"node-stdlib-browser": "^1.2.0",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"scripts": {
|
||||
"format": "prettier --write 'src/**/*'",
|
||||
"lint": "eslint src/",
|
||||
"fix": "eslint src/ --fix",
|
||||
"clean": "rm -rf dist/",
|
||||
"build": "yarn clean && tsc && node scripts/build.js",
|
||||
"push": "yalc push"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
const esbuild = require("esbuild");
|
||||
const plugin = require("node-stdlib-browser/helpers/esbuild/plugin");
|
||||
const stdLibBrowser = require("node-stdlib-browser");
|
||||
|
||||
// const DIST_PATH = path.resolve(process.cwd(), "dist/");
|
||||
|
||||
// fs.rmSync(DIST_PATH, { recursive: true, force: true });
|
||||
|
||||
esbuild.build({
|
||||
jsx: "automatic",
|
||||
entryPoints: ["src/thirdweb-bridge.ts"],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
platform: "browser",
|
||||
target: "es2020",
|
||||
outfile: "dist/thirdweb-unity-bridge.js",
|
||||
splitting: false,
|
||||
write: true,
|
||||
sourcemap: false,
|
||||
inject: [require.resolve("node-stdlib-browser/helpers/esbuild/shim")],
|
||||
define: {
|
||||
process: JSON.stringify({
|
||||
env: "production",
|
||||
}),
|
||||
Buffer: "Buffer",
|
||||
},
|
||||
plugins: [plugin(stdLibBrowser)],
|
||||
});
|
||||
@@ -0,0 +1,219 @@
|
||||
/// --- Thirdweb Brige ---
|
||||
import { ChainOrRpc, ThirdwebSDK } from "@thirdweb-dev/sdk/evm";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import {
|
||||
CoinbaseWallet,
|
||||
MetaMask,
|
||||
WalletConnect,
|
||||
InjectedWallet,
|
||||
} from "@thirdweb-dev/wallets";
|
||||
import type { AbstractWallet } from "@thirdweb-dev/wallets/dist/declarations/src/wallets/base";
|
||||
import { BigNumber } from "ethers";
|
||||
import type { ContractInterface, Signer } from "ethers";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
bridge: TWBridge;
|
||||
}
|
||||
}
|
||||
|
||||
const SEPARATOR = "/";
|
||||
const SUB_SEPARATOR = "#";
|
||||
|
||||
// big number transform
|
||||
const bigNumberReplacer = (_key: string, value: any) => {
|
||||
// if we find a BigNumber then make it into a string (since that is safe)
|
||||
if (
|
||||
BigNumber.isBigNumber(value) ||
|
||||
(typeof value === "object" &&
|
||||
value !== null &&
|
||||
value.type === "BigNumber" &&
|
||||
"hex" in value)
|
||||
) {
|
||||
return BigNumber.from(value).toString();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const WALLETS = [
|
||||
MetaMask,
|
||||
InjectedWallet,
|
||||
WalletConnect,
|
||||
CoinbaseWallet,
|
||||
] as const;
|
||||
|
||||
type PossibleWallet = typeof WALLETS[number]["id"];
|
||||
|
||||
interface TWBridge {
|
||||
initialize: (chain: ChainOrRpc, options: string) => void;
|
||||
connect: (wallet: PossibleWallet, chainId?: number) => Promise<string>;
|
||||
disconnect: () => Promise<void>;
|
||||
switchNetwork: (chainId: number) => Promise<void>;
|
||||
invoke: (route: string, payload: string) => Promise<string | undefined>;
|
||||
}
|
||||
|
||||
const w = window;
|
||||
|
||||
class ThirdwebBridge implements TWBridge {
|
||||
private walletMap: Map<string, AbstractWallet> = new Map();
|
||||
private activeWallet: AbstractWallet | undefined;
|
||||
private initializedChain: ChainOrRpc | undefined;
|
||||
private activeSDK: ThirdwebSDK | undefined;
|
||||
|
||||
private updateSDKSigner(signer?: Signer) {
|
||||
if (this.activeSDK) {
|
||||
if (signer) {
|
||||
// set signer if we got one
|
||||
this.activeSDK.updateSignerOrProvider(signer);
|
||||
} else if (this.initializedChain) {
|
||||
// reset back to provider only in case signer gets reomved (disconnect case)
|
||||
this.activeSDK.updateSignerOrProvider(this.initializedChain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public initialize(chain: ChainOrRpc, options: string) {
|
||||
this.initializedChain = chain;
|
||||
console.debug("thirdwebSDK initialization:", chain, options);
|
||||
const sdkOptions = JSON.parse(options);
|
||||
const storage =
|
||||
sdkOptions && sdkOptions.ipfsGatewayUrl
|
||||
? new ThirdwebStorage({
|
||||
gatewayUrls: {
|
||||
"ipfs://": [sdkOptions.ipfsGatewayUrl],
|
||||
},
|
||||
})
|
||||
: new ThirdwebStorage();
|
||||
|
||||
this.activeSDK = new ThirdwebSDK(chain, sdkOptions, storage);
|
||||
for (let wallet of WALLETS) {
|
||||
const walletInstance = new wallet({
|
||||
appName: sdkOptions.appName || "thirdweb powered dApp",
|
||||
});
|
||||
walletInstance.on("connect", async () =>
|
||||
this.updateSDKSigner(await walletInstance.getSigner()),
|
||||
);
|
||||
walletInstance.on("change", async () =>
|
||||
this.updateSDKSigner(await walletInstance.getSigner()),
|
||||
);
|
||||
walletInstance.on("disconnect", () => this.updateSDKSigner());
|
||||
|
||||
this.walletMap.set(wallet.id, walletInstance);
|
||||
}
|
||||
}
|
||||
public async connect(
|
||||
wallet: PossibleWallet = "injected",
|
||||
chainId?: number | undefined,
|
||||
) {
|
||||
if (!this.activeSDK) {
|
||||
throw new Error("SDK not initialized");
|
||||
}
|
||||
if (chainId === 0) {
|
||||
chainId = undefined;
|
||||
}
|
||||
const walletInstance = this.walletMap.get(wallet);
|
||||
if (walletInstance) {
|
||||
await walletInstance.connect(chainId);
|
||||
this.activeWallet = walletInstance;
|
||||
this.updateSDKSigner(await walletInstance.getSigner());
|
||||
return await this.activeSDK.wallet.getAddress();
|
||||
} else {
|
||||
throw new Error("Invalid Wallet");
|
||||
}
|
||||
}
|
||||
public async disconnect() {
|
||||
if (this.activeWallet) {
|
||||
await this.activeWallet.disconnect();
|
||||
this.activeWallet = undefined;
|
||||
this.updateSDKSigner();
|
||||
}
|
||||
}
|
||||
public async switchNetwork(chainId: number) {
|
||||
if (chainId && this.activeWallet && "switchChain" in this.activeWallet) {
|
||||
await this.activeWallet.switchChain(chainId);
|
||||
this.updateSDKSigner(await this.activeWallet.getSigner());
|
||||
} else {
|
||||
throw new Error("Error Switching Network");
|
||||
}
|
||||
}
|
||||
|
||||
public async invoke(route: string, payload: string) {
|
||||
if (!this.activeSDK) {
|
||||
throw new Error("SDK not initialized");
|
||||
}
|
||||
const routeArgs = route.split(SEPARATOR);
|
||||
const firstArg = routeArgs[0].split(SUB_SEPARATOR);
|
||||
const addrOrSDK = firstArg[0];
|
||||
|
||||
const fnArgs = JSON.parse(payload).arguments;
|
||||
const parsedArgs = fnArgs.map((arg: unknown) => {
|
||||
try {
|
||||
return typeof arg === "string" &&
|
||||
(arg.startsWith("{") || arg.startsWith("["))
|
||||
? JSON.parse(arg)
|
||||
: arg;
|
||||
} catch (e) {
|
||||
return arg;
|
||||
}
|
||||
});
|
||||
console.debug("thirdwebSDK call:", route, parsedArgs);
|
||||
|
||||
// wallet call
|
||||
if (addrOrSDK.startsWith("sdk")) {
|
||||
let prop = undefined;
|
||||
if (firstArg.length > 1) {
|
||||
prop = firstArg[1];
|
||||
}
|
||||
if (prop && routeArgs.length === 2) {
|
||||
// @ts-expect-error need to type-guard this properly
|
||||
const result = await this.activeSDK[prop][routeArgs[1]](...parsedArgs);
|
||||
return JSON.stringify({ result: result }, bigNumberReplacer);
|
||||
} else if (routeArgs.length === 2) {
|
||||
// @ts-expect-error need to type-guard this properly
|
||||
const result = await this.activeSDK[routeArgs[1]](...parsedArgs);
|
||||
return JSON.stringify({ result: result }, bigNumberReplacer);
|
||||
} else {
|
||||
throw new Error("Invalid Route");
|
||||
}
|
||||
}
|
||||
|
||||
// contract call
|
||||
if (addrOrSDK.startsWith("0x")) {
|
||||
let typeOrAbi: string | ContractInterface | undefined;
|
||||
if (firstArg.length > 1) {
|
||||
try {
|
||||
typeOrAbi = JSON.parse(firstArg[1]); // try to parse ABI
|
||||
} catch (e) {
|
||||
typeOrAbi = firstArg[1];
|
||||
}
|
||||
}
|
||||
const contract = typeOrAbi
|
||||
? await this.activeSDK.getContract(addrOrSDK, typeOrAbi)
|
||||
: await this.activeSDK.getContract(addrOrSDK);
|
||||
if (routeArgs.length === 2) {
|
||||
// @ts-expect-error need to type-guard this properly
|
||||
const result = await contract[routeArgs[1]](...parsedArgs);
|
||||
return JSON.stringify({ result: result }, bigNumberReplacer);
|
||||
} else if (routeArgs.length === 3) {
|
||||
// @ts-expect-error need to type-guard this properly
|
||||
const result = await contract[routeArgs[1]][routeArgs[2]](
|
||||
...parsedArgs,
|
||||
);
|
||||
return JSON.stringify({ result: result }, bigNumberReplacer);
|
||||
} else if (routeArgs.length === 4) {
|
||||
// @ts-expect-error need to type-guard this properly
|
||||
const result = await contract[routeArgs[1]][routeArgs[2]][routeArgs[3]](
|
||||
...parsedArgs,
|
||||
);
|
||||
return JSON.stringify({ result: result }, bigNumberReplacer);
|
||||
} else {
|
||||
throw new Error("Invalid Route");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add the bridge to the window object type
|
||||
w.bridge = new ThirdwebBridge();
|
||||
|
||||
/// --- End Thirdweb Brige ---
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "@thirdweb-dev/tsconfig/base.json",
|
||||
"include": ["src", "types"],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-typescript",
|
||||
["@babel/preset-env", { "targets": "defaults, not ie 11" }]
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# folders
|
||||
dist/
|
||||
|
||||
# files
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["thirdweb"],
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
# @thirdweb-dev/wallets
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#420](https://github.com/thirdweb-dev/js/pull/420) [`639e535`](https://github.com/thirdweb-dev/js/commit/639e535ed55280ad9d081001aab3f5af72bb3e45) Thanks [@jnsdls](https://github.com/jnsdls)! - Init wallet package
|
||||
@@ -0,0 +1,16 @@
|
||||
<p align="center">
|
||||
<br />
|
||||
<a href="https://thirdweb.com"><img src="https://github.com/thirdweb-dev/js/blob/main/packages/sdk/logo.svg?raw=true" width="200" alt=""/></a>
|
||||
<br />
|
||||
</p>
|
||||
<h1 align="center">thirdweb wallets</h1>
|
||||
<p align="center">
|
||||
<!-- <a href="https://www.npmjs.com/package/@thirdweb-dev/sdk"><img src="https://img.shields.io/npm/v/@thirdweb-dev/sdk?color=red&label=npm&logo=npm" alt="npm version"/></a> -->
|
||||
<!-- <a href="https://github.com/thirdweb-dev/js/actions/workflows/CI.yml"><img alt="Build Status" src="https://github.com/thirdweb-dev/js/actions/workflows/CI.yml/badge.svg"/></a> -->
|
||||
<!-- <a href="https://discord.gg/thirdweb"><img alt="Join our Discord!" src="https://img.shields.io/discord/834227967404146718.svg?color=7289da&label=discord&logo=discord&style=flat"/></a> -->
|
||||
|
||||
</p>
|
||||
<!-- <p align="center"><strong>Best in class Web3 SDK for Browser, Node and Mobile apps</strong></p> -->
|
||||
<br />
|
||||
|
||||
### todo
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-wallets-connectors-coinbase-wallet.cjs.js",
|
||||
"module": "dist/thirdweb-dev-wallets-connectors-coinbase-wallet.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-wallets-connectors-injected.cjs.js",
|
||||
"module": "dist/thirdweb-dev-wallets-connectors-injected.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-wallets-connectors-metamask.cjs.js",
|
||||
"module": "dist/thirdweb-dev-wallets-connectors-metamask.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-wallets-connectors-wallet-connect.cjs.js",
|
||||
"module": "dist/thirdweb-dev-wallets-connectors-wallet-connect.esm.js"
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/wallets",
|
||||
"version": "0.1.0",
|
||||
"main": "dist/thirdweb-dev-wallets.cjs.js",
|
||||
"module": "dist/thirdweb-dev-wallets.esm.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"module": "./dist/thirdweb-dev-wallets.esm.js",
|
||||
"default": "./dist/thirdweb-dev-wallets.cjs.js"
|
||||
},
|
||||
"./wallets/base": {
|
||||
"module": "./wallets/base/dist/thirdweb-dev-wallets-wallets-base.esm.js",
|
||||
"default": "./wallets/base/dist/thirdweb-dev-wallets-wallets-base.cjs.js"
|
||||
},
|
||||
"./wallets/injected": {
|
||||
"module": "./wallets/injected/dist/thirdweb-dev-wallets-wallets-injected.esm.js",
|
||||
"default": "./wallets/injected/dist/thirdweb-dev-wallets-wallets-injected.cjs.js"
|
||||
},
|
||||
"./wallets/metamask": {
|
||||
"module": "./wallets/metamask/dist/thirdweb-dev-wallets-wallets-metamask.esm.js",
|
||||
"default": "./wallets/metamask/dist/thirdweb-dev-wallets-wallets-metamask.cjs.js"
|
||||
},
|
||||
"./wallets/wallet-connect": {
|
||||
"module": "./wallets/wallet-connect/dist/thirdweb-dev-wallets-wallets-wallet-connect.esm.js",
|
||||
"default": "./wallets/wallet-connect/dist/thirdweb-dev-wallets-wallets-wallet-connect.cjs.js"
|
||||
},
|
||||
"./wallets/coinbase-wallet": {
|
||||
"module": "./wallets/coinbase-wallet/dist/thirdweb-dev-wallets-wallets-coinbase-wallet.esm.js",
|
||||
"default": "./wallets/coinbase-wallet/dist/thirdweb-dev-wallets-wallets-coinbase-wallet.cjs.js"
|
||||
},
|
||||
"./connectors/injected": {
|
||||
"module": "./connectors/injected/dist/thirdweb-dev-wallets-connectors-injected.esm.js",
|
||||
"default": "./connectors/injected/dist/thirdweb-dev-wallets-connectors-injected.cjs.js"
|
||||
},
|
||||
"./connectors/metamask": {
|
||||
"module": "./connectors/metamask/dist/thirdweb-dev-wallets-connectors-metamask.esm.js",
|
||||
"default": "./connectors/metamask/dist/thirdweb-dev-wallets-connectors-metamask.cjs.js"
|
||||
},
|
||||
"./connectors/wallet-connect": {
|
||||
"module": "./connectors/wallet-connect/dist/thirdweb-dev-wallets-connectors-wallet-connect.esm.js",
|
||||
"default": "./connectors/wallet-connect/dist/thirdweb-dev-wallets-connectors-wallet-connect.cjs.js"
|
||||
},
|
||||
"./connectors/coinbase-wallet": {
|
||||
"module": "./connectors/coinbase-wallet/dist/thirdweb-dev-wallets-connectors-coinbase-wallet.esm.js",
|
||||
"default": "./connectors/coinbase-wallet/dist/thirdweb-dev-wallets-connectors-coinbase-wallet.cjs.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/wallets",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/thirdweb-dev/js/issues"
|
||||
},
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"files": [
|
||||
"dist/",
|
||||
"wallets/",
|
||||
"connectors/"
|
||||
],
|
||||
"preconstruct": {
|
||||
"entrypoints": [
|
||||
"index.ts",
|
||||
"connectors/**",
|
||||
"wallets/**"
|
||||
],
|
||||
"___experimentalFlags_WILL_CHANGE_IN_PATCH": {
|
||||
"exports": true
|
||||
},
|
||||
"exports": true
|
||||
},
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"@coinbase/wallet-sdk": "^3.6.0",
|
||||
"@wagmi/core": "^0.7.6",
|
||||
"@walletconnect/ethereum-provider": "^1.7.8",
|
||||
"abitype": "^0.2.5",
|
||||
"ethers": "^5.7.2",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"localforage": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preconstruct/cli": "^2.2.1",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"scripts": {
|
||||
"format": "prettier --write 'src/**/*'",
|
||||
"lint": "eslint src/",
|
||||
"fix": "eslint src/ --fix",
|
||||
"clean": "rm -rf dist/",
|
||||
"build": "tsc && preconstruct build"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
import type {
|
||||
CoinbaseWalletProvider,
|
||||
CoinbaseWalletSDK,
|
||||
} from "@coinbase/wallet-sdk";
|
||||
import type { CoinbaseWalletSDKOptions } from "@coinbase/wallet-sdk/dist/CoinbaseWalletSDK";
|
||||
import {
|
||||
AddChainError,
|
||||
Chain,
|
||||
ChainNotConfiguredError,
|
||||
Connector,
|
||||
normalizeChainId,
|
||||
ProviderRpcError,
|
||||
SwitchChainError,
|
||||
UserRejectedRequestError,
|
||||
} from "@wagmi/core";
|
||||
import type { Address } from "abitype";
|
||||
import { providers } from "ethers";
|
||||
import { getAddress, hexValue } from "ethers/lib/utils.js";
|
||||
|
||||
type Options = CoinbaseWalletSDKOptions & {
|
||||
/**
|
||||
* Fallback Ethereum JSON RPC URL
|
||||
* @default ""
|
||||
*/
|
||||
jsonRpcUrl?: string;
|
||||
/**
|
||||
* Fallback Ethereum Chain ID
|
||||
* @default 1
|
||||
*/
|
||||
chainId?: number;
|
||||
};
|
||||
|
||||
export class CoinbaseWalletConnector extends Connector<
|
||||
CoinbaseWalletProvider,
|
||||
Options,
|
||||
providers.JsonRpcSigner
|
||||
> {
|
||||
readonly id = "coinbaseWallet";
|
||||
readonly name = "Coinbase Wallet";
|
||||
readonly ready = true;
|
||||
|
||||
#client?: CoinbaseWalletSDK;
|
||||
#provider?: CoinbaseWalletProvider;
|
||||
|
||||
constructor({ chains, options }: { chains?: Chain[]; options: Options }) {
|
||||
super({
|
||||
chains,
|
||||
options: {
|
||||
reloadOnDisconnect: false,
|
||||
...options,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async connect({ chainId }: { chainId?: number } = {}) {
|
||||
try {
|
||||
const provider = await this.getProvider();
|
||||
provider.on("accountsChanged", this.onAccountsChanged);
|
||||
provider.on("chainChanged", this.onChainChanged);
|
||||
provider.on("disconnect", this.onDisconnect);
|
||||
|
||||
this.emit("message", { type: "connecting" });
|
||||
|
||||
const accounts = await provider.enable();
|
||||
const account = getAddress(accounts[0] as string);
|
||||
// Switch to chain if provided
|
||||
let id = await this.getChainId();
|
||||
let unsupported = this.isChainUnsupported(id);
|
||||
if (chainId && id !== chainId) {
|
||||
const chain = await this.switchChain(chainId);
|
||||
id = chain.id;
|
||||
unsupported = this.isChainUnsupported(id);
|
||||
}
|
||||
|
||||
return {
|
||||
account,
|
||||
chain: { id, unsupported },
|
||||
provider: new providers.Web3Provider(
|
||||
provider as unknown as providers.ExternalProvider,
|
||||
),
|
||||
};
|
||||
} catch (error) {
|
||||
if (
|
||||
/(user closed modal|accounts received is empty)/i.test(
|
||||
(error as ProviderRpcError).message,
|
||||
)
|
||||
) {
|
||||
throw new UserRejectedRequestError(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
if (!this.#provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = await this.getProvider();
|
||||
provider.removeListener("accountsChanged", this.onAccountsChanged);
|
||||
provider.removeListener("chainChanged", this.onChainChanged);
|
||||
provider.removeListener("disconnect", this.onDisconnect);
|
||||
provider.disconnect();
|
||||
provider.close();
|
||||
}
|
||||
|
||||
async getAccount() {
|
||||
const provider = await this.getProvider();
|
||||
const accounts = await provider.request<Address[]>({
|
||||
method: "eth_accounts",
|
||||
});
|
||||
// return checksum address
|
||||
return getAddress(accounts[0] as string);
|
||||
}
|
||||
|
||||
async getChainId() {
|
||||
const provider = await this.getProvider();
|
||||
const chainId = normalizeChainId(provider.chainId);
|
||||
return chainId;
|
||||
}
|
||||
|
||||
async getProvider() {
|
||||
if (!this.#provider) {
|
||||
let CoinbaseWalletSDK = (await import("@coinbase/wallet-sdk")).default;
|
||||
// Workaround for Vite dev import errors
|
||||
// https://github.com/vitejs/vite/issues/7112
|
||||
if (
|
||||
typeof CoinbaseWalletSDK !== "function" &&
|
||||
// @ts-expect-error This import error is not visible to TypeScript
|
||||
typeof CoinbaseWalletSDK.default === "function"
|
||||
) {
|
||||
CoinbaseWalletSDK = (
|
||||
CoinbaseWalletSDK as unknown as { default: typeof CoinbaseWalletSDK }
|
||||
).default;
|
||||
}
|
||||
this.#client = new CoinbaseWalletSDK(this.options);
|
||||
|
||||
/**
|
||||
* Mock implementations to retrieve private `walletExtension` method
|
||||
* from the Coinbase Wallet SDK.
|
||||
*/
|
||||
abstract class WalletProvider {
|
||||
// https://github.com/coinbase/coinbase-wallet-sdk/blob/b4cca90022ffeb46b7bbaaab9389a33133fe0844/packages/wallet-sdk/src/provider/CoinbaseWalletProvider.ts#L927-L936
|
||||
abstract getChainId(): number;
|
||||
}
|
||||
abstract class Client {
|
||||
// https://github.com/coinbase/coinbase-wallet-sdk/blob/b4cca90022ffeb46b7bbaaab9389a33133fe0844/packages/wallet-sdk/src/CoinbaseWalletSDK.ts#L233-L235
|
||||
abstract get walletExtension(): WalletProvider | undefined;
|
||||
}
|
||||
const walletExtensionChainId = (
|
||||
this.#client as unknown as Client
|
||||
).walletExtension?.getChainId();
|
||||
|
||||
const chain =
|
||||
this.chains.find((chain_) =>
|
||||
this.options.chainId
|
||||
? chain_.id === this.options.chainId
|
||||
: chain_.id === walletExtensionChainId,
|
||||
) || this.chains[0];
|
||||
const chainId = this.options.chainId || chain?.id;
|
||||
const jsonRpcUrl = this.options.jsonRpcUrl || chain?.rpcUrls.default;
|
||||
|
||||
this.#provider = this.#client.makeWeb3Provider(jsonRpcUrl, chainId);
|
||||
}
|
||||
return this.#provider;
|
||||
}
|
||||
|
||||
async getSigner({ chainId }: { chainId?: number } = {}) {
|
||||
const [provider, account] = await Promise.all([
|
||||
this.getProvider(),
|
||||
this.getAccount(),
|
||||
]);
|
||||
return new providers.Web3Provider(
|
||||
provider as unknown as providers.ExternalProvider,
|
||||
chainId,
|
||||
).getSigner(account);
|
||||
}
|
||||
|
||||
async isAuthorized() {
|
||||
try {
|
||||
const account = await this.getAccount();
|
||||
return !!account;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async switchChain(chainId: number) {
|
||||
const provider = await this.getProvider();
|
||||
const id = hexValue(chainId);
|
||||
|
||||
try {
|
||||
await provider.request({
|
||||
method: "wallet_switchEthereumChain",
|
||||
params: [{ chainId: id }],
|
||||
});
|
||||
return (
|
||||
this.chains.find((x) => x.id === chainId) ?? {
|
||||
id: chainId,
|
||||
name: `Chain ${id}`,
|
||||
network: `${id}`,
|
||||
rpcUrls: { default: "" },
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
const chain = this.chains.find((x) => x.id === chainId);
|
||||
if (!chain) {
|
||||
throw new ChainNotConfiguredError({ chainId, connectorId: this.id });
|
||||
}
|
||||
|
||||
// Indicates chain is not added to provider
|
||||
if ((error as ProviderRpcError).code === 4902) {
|
||||
try {
|
||||
await provider.request({
|
||||
method: "wallet_addEthereumChain",
|
||||
params: [
|
||||
{
|
||||
chainId: id,
|
||||
chainName: chain.name,
|
||||
nativeCurrency: chain.nativeCurrency,
|
||||
rpcUrls: [chain.rpcUrls.public ?? chain.rpcUrls.default],
|
||||
blockExplorerUrls: this.getBlockExplorerUrls(chain),
|
||||
},
|
||||
],
|
||||
});
|
||||
return chain;
|
||||
} catch (addError) {
|
||||
if (this.#isUserRejectedRequestError(addError)) {
|
||||
throw new UserRejectedRequestError(addError);
|
||||
}
|
||||
throw new AddChainError();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.#isUserRejectedRequestError(error)) {
|
||||
throw new UserRejectedRequestError(error);
|
||||
}
|
||||
throw new SwitchChainError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async watchAsset({
|
||||
address,
|
||||
decimals = 18,
|
||||
image,
|
||||
symbol,
|
||||
}: {
|
||||
address: string;
|
||||
decimals?: number;
|
||||
image?: string;
|
||||
symbol: string;
|
||||
}) {
|
||||
const provider = await this.getProvider();
|
||||
return provider.request<boolean>({
|
||||
method: "wallet_watchAsset",
|
||||
params: {
|
||||
type: "ERC20",
|
||||
options: {
|
||||
address,
|
||||
decimals,
|
||||
image,
|
||||
symbol,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected onAccountsChanged = (accounts: string[]) => {
|
||||
if (accounts.length === 0) {
|
||||
this.emit("disconnect");
|
||||
} else {
|
||||
this.emit("change", { account: getAddress(accounts[0] as string) });
|
||||
}
|
||||
};
|
||||
|
||||
protected onChainChanged = (chainId: number | string) => {
|
||||
const id = normalizeChainId(chainId);
|
||||
const unsupported = this.isChainUnsupported(id);
|
||||
this.emit("change", { chain: { id, unsupported } });
|
||||
};
|
||||
|
||||
protected onDisconnect = () => {
|
||||
this.emit("disconnect");
|
||||
};
|
||||
|
||||
#isUserRejectedRequestError(error: unknown) {
|
||||
return /(user rejected)/i.test((error as Error).message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
import { getInjectedName } from "../../utils/getInjectedName";
|
||||
import { getConnectorStorage } from "../../utils/storage";
|
||||
import {
|
||||
AddChainError,
|
||||
Chain,
|
||||
ChainNotConfiguredError,
|
||||
Connector,
|
||||
ConnectorNotFoundError,
|
||||
Ethereum,
|
||||
normalizeChainId,
|
||||
ProviderRpcError,
|
||||
ResourceUnavailableError,
|
||||
RpcError,
|
||||
SwitchChainError,
|
||||
UserRejectedRequestError,
|
||||
} from "@wagmi/core";
|
||||
import type { Address } from "abitype";
|
||||
import { providers } from "ethers";
|
||||
import { getAddress, hexValue } from "ethers/lib/utils.js";
|
||||
|
||||
declare interface Window {
|
||||
ethereum: Ethereum;
|
||||
}
|
||||
|
||||
export type InjectedConnectorOptions = {
|
||||
/** Name of connector */
|
||||
name?: string | ((detectedName: string | string[]) => string);
|
||||
/**
|
||||
* MetaMask 10.9.3 emits disconnect event when chain is changed.
|
||||
* This flag prevents the `"disconnect"` event from being emitted upon switching chains. See [GitHub issue](https://github.com/MetaMask/metamask-extension/issues/13375#issuecomment-1027663334) for more info.
|
||||
*/
|
||||
shimChainChangedDisconnect?: boolean;
|
||||
/**
|
||||
* MetaMask and other injected providers do not support programmatic disconnect.
|
||||
* This flag simulates the disconnect behavior by keeping track of connection status in storage. See [GitHub issue](https://github.com/MetaMask/metamask-extension/issues/10353) for more info.
|
||||
* @default true
|
||||
*/
|
||||
shimDisconnect?: boolean;
|
||||
};
|
||||
|
||||
export class InjectedConnector extends Connector<
|
||||
Window["ethereum"],
|
||||
InjectedConnectorOptions | undefined,
|
||||
providers.JsonRpcSigner
|
||||
> {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly ready = typeof window !== "undefined" && !!window.ethereum;
|
||||
|
||||
#provider?: Window["ethereum"];
|
||||
#switchingChains?: boolean;
|
||||
|
||||
protected shimDisconnectKey = "injected.shimDisconnect";
|
||||
|
||||
constructor({
|
||||
chains,
|
||||
options: options_,
|
||||
}: {
|
||||
chains?: Chain[];
|
||||
options?: InjectedConnectorOptions;
|
||||
} = {}) {
|
||||
const options = {
|
||||
shimDisconnect: true,
|
||||
shimChainChangedDisconnect: true,
|
||||
...options_,
|
||||
};
|
||||
super({ chains, options });
|
||||
|
||||
let name = "Injected";
|
||||
const overrideName = options.name;
|
||||
if (typeof overrideName === "string") {
|
||||
name = overrideName;
|
||||
} else if (typeof window !== "undefined") {
|
||||
const detectedName = getInjectedName(window.ethereum as Ethereum);
|
||||
if (overrideName) {
|
||||
name = overrideName(detectedName);
|
||||
} else {
|
||||
name =
|
||||
typeof detectedName === "string"
|
||||
? detectedName
|
||||
: (detectedName[0] as string);
|
||||
}
|
||||
}
|
||||
|
||||
this.id = "injected";
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
async connect({ chainId }: { chainId?: number } = {}) {
|
||||
try {
|
||||
const provider = await this.getProvider();
|
||||
if (!provider) {
|
||||
throw new ConnectorNotFoundError();
|
||||
}
|
||||
|
||||
if (provider.on) {
|
||||
provider.on("accountsChanged", this.onAccountsChanged);
|
||||
provider.on("chainChanged", this.onChainChanged);
|
||||
provider.on("disconnect", this.onDisconnect);
|
||||
}
|
||||
|
||||
this.emit("message", { type: "connecting" });
|
||||
|
||||
const accounts = await provider.request({
|
||||
method: "eth_requestAccounts",
|
||||
});
|
||||
const account = getAddress(accounts[0] as string);
|
||||
// Switch to chain if provided
|
||||
let id = await this.getChainId();
|
||||
let unsupported = this.isChainUnsupported(id);
|
||||
if (chainId && id !== chainId) {
|
||||
const chain = await this.switchChain(chainId);
|
||||
id = chain.id;
|
||||
unsupported = this.isChainUnsupported(id);
|
||||
}
|
||||
|
||||
// Add shim to storage signalling wallet is connected
|
||||
if (this.options?.shimDisconnect) {
|
||||
await getConnectorStorage().setItem(this.shimDisconnectKey, true);
|
||||
}
|
||||
|
||||
return { account, chain: { id, unsupported }, provider };
|
||||
} catch (error) {
|
||||
if (this.isUserRejectedRequestError(error)) {
|
||||
throw new UserRejectedRequestError(error);
|
||||
}
|
||||
if ((error as RpcError).code === -32002) {
|
||||
throw new ResourceUnavailableError(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
const provider = await this.getProvider();
|
||||
if (!provider?.removeListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
provider.removeListener("accountsChanged", this.onAccountsChanged);
|
||||
provider.removeListener("chainChanged", this.onChainChanged);
|
||||
provider.removeListener("disconnect", this.onDisconnect);
|
||||
|
||||
// Remove shim signalling wallet is disconnected
|
||||
if (this.options?.shimDisconnect) {
|
||||
await getConnectorStorage().removeItem(this.shimDisconnectKey);
|
||||
}
|
||||
}
|
||||
|
||||
async getAccount() {
|
||||
const provider = await this.getProvider();
|
||||
if (!provider) {
|
||||
throw new ConnectorNotFoundError();
|
||||
}
|
||||
const accounts = await provider.request({
|
||||
method: "eth_accounts",
|
||||
});
|
||||
// return checksum address
|
||||
return getAddress(accounts[0] as string);
|
||||
}
|
||||
|
||||
async getChainId() {
|
||||
const provider = await this.getProvider();
|
||||
if (!provider) {
|
||||
throw new ConnectorNotFoundError();
|
||||
}
|
||||
return provider.request({ method: "eth_chainId" }).then(normalizeChainId);
|
||||
}
|
||||
|
||||
async getProvider() {
|
||||
if (typeof window !== "undefined" && !!window.ethereum) {
|
||||
this.#provider = window.ethereum as Ethereum;
|
||||
}
|
||||
return this.#provider as Ethereum;
|
||||
}
|
||||
|
||||
async getSigner({ chainId }: { chainId?: number } = {}) {
|
||||
const [provider, account] = await Promise.all([
|
||||
this.getProvider(),
|
||||
this.getAccount(),
|
||||
]);
|
||||
return new providers.Web3Provider(
|
||||
provider as providers.ExternalProvider,
|
||||
chainId,
|
||||
).getSigner(account);
|
||||
}
|
||||
|
||||
async isAuthorized() {
|
||||
try {
|
||||
if (
|
||||
this.options?.shimDisconnect &&
|
||||
// If shim does not exist in storage, wallet is disconnected
|
||||
!(await getConnectorStorage().getItem(this.shimDisconnectKey))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const provider = await this.getProvider();
|
||||
if (!provider) {
|
||||
throw new ConnectorNotFoundError();
|
||||
}
|
||||
const account = await this.getAccount();
|
||||
return !!account;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async switchChain(chainId: number) {
|
||||
if (this.options?.shimChainChangedDisconnect) {
|
||||
this.#switchingChains = true;
|
||||
}
|
||||
|
||||
const provider = await this.getProvider();
|
||||
if (!provider) {
|
||||
throw new ConnectorNotFoundError();
|
||||
}
|
||||
const id = hexValue(chainId);
|
||||
|
||||
try {
|
||||
await provider.request({
|
||||
method: "wallet_switchEthereumChain",
|
||||
params: [{ chainId: id }],
|
||||
});
|
||||
return (
|
||||
this.chains.find((x) => x.id === chainId) ?? {
|
||||
id: chainId,
|
||||
name: `Chain ${id}`,
|
||||
network: `${id}`,
|
||||
rpcUrls: { default: "" },
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
const chain = this.chains.find((x) => x.id === chainId);
|
||||
if (!chain) {
|
||||
throw new ChainNotConfiguredError({ chainId, connectorId: this.id });
|
||||
}
|
||||
|
||||
// Indicates chain is not added to provider
|
||||
if (
|
||||
(error as ProviderRpcError).code === 4902 ||
|
||||
// Unwrapping for MetaMask Mobile
|
||||
// https://github.com/MetaMask/metamask-mobile/issues/2944#issuecomment-976988719
|
||||
(error as RpcError<{ originalError?: { code: number } }>)?.data
|
||||
?.originalError?.code === 4902
|
||||
) {
|
||||
try {
|
||||
await provider.request({
|
||||
method: "wallet_addEthereumChain",
|
||||
params: [
|
||||
{
|
||||
chainId: id,
|
||||
chainName: chain.name,
|
||||
nativeCurrency: chain.nativeCurrency,
|
||||
rpcUrls: [chain.rpcUrls.public ?? chain.rpcUrls.default],
|
||||
blockExplorerUrls: this.getBlockExplorerUrls(chain),
|
||||
},
|
||||
],
|
||||
});
|
||||
return chain;
|
||||
} catch (addError) {
|
||||
if (this.isUserRejectedRequestError(addError)) {
|
||||
throw new UserRejectedRequestError(error);
|
||||
}
|
||||
throw new AddChainError();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isUserRejectedRequestError(error)) {
|
||||
throw new UserRejectedRequestError(error);
|
||||
}
|
||||
throw new SwitchChainError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async watchAsset({
|
||||
address,
|
||||
decimals = 18,
|
||||
image,
|
||||
symbol,
|
||||
}: {
|
||||
address: Address;
|
||||
decimals?: number;
|
||||
image?: string;
|
||||
symbol: string;
|
||||
}) {
|
||||
const provider = await this.getProvider();
|
||||
if (!provider) {
|
||||
throw new ConnectorNotFoundError();
|
||||
}
|
||||
return provider.request({
|
||||
method: "wallet_watchAsset",
|
||||
params: {
|
||||
type: "ERC20",
|
||||
options: {
|
||||
address,
|
||||
decimals,
|
||||
image,
|
||||
symbol,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected onAccountsChanged = (accounts: string[]) => {
|
||||
if (accounts.length === 0) {
|
||||
this.emit("disconnect");
|
||||
} else {
|
||||
this.emit("change", {
|
||||
account: getAddress(accounts[0] as string),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
protected onChainChanged = (chainId: number | string) => {
|
||||
const id = normalizeChainId(chainId);
|
||||
const unsupported = this.isChainUnsupported(id);
|
||||
this.emit("change", { chain: { id, unsupported } });
|
||||
};
|
||||
|
||||
protected onDisconnect = async () => {
|
||||
// We need this as MetaMask can emit the "disconnect" event
|
||||
// upon switching chains. This workaround ensures that the
|
||||
// user currently isn't in the process of switching chains.
|
||||
if (this.options?.shimChainChangedDisconnect && this.#switchingChains) {
|
||||
this.#switchingChains = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit("disconnect");
|
||||
// Remove shim signalling wallet is disconnected
|
||||
if (this.options?.shimDisconnect) {
|
||||
await getConnectorStorage().removeItem(this.shimDisconnectKey);
|
||||
}
|
||||
};
|
||||
|
||||
protected isUserRejectedRequestError(error: unknown) {
|
||||
return (error as ProviderRpcError).code === 4001;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import { getConnectorStorage } from "../../utils/storage";
|
||||
import { InjectedConnector } from "../injected";
|
||||
import {
|
||||
Chain,
|
||||
ConnectorNotFoundError,
|
||||
Ethereum,
|
||||
InjectedConnectorOptions,
|
||||
ResourceUnavailableError,
|
||||
RpcError,
|
||||
UserRejectedRequestError,
|
||||
} from "@wagmi/core";
|
||||
import type { Address } from "abitype";
|
||||
import { getAddress } from "ethers/lib/utils.js";
|
||||
|
||||
declare interface Window {
|
||||
ethereum: Ethereum;
|
||||
}
|
||||
|
||||
export type MetaMaskConnectorOptions = Pick<
|
||||
InjectedConnectorOptions,
|
||||
"shimChainChangedDisconnect" | "shimDisconnect"
|
||||
> & {
|
||||
/**
|
||||
* While "disconnected" with `shimDisconnect`, allows user to select a different MetaMask account (than the currently connected account) when trying to connect.
|
||||
*/
|
||||
UNSTABLE_shimOnConnectSelectAccount?: boolean;
|
||||
};
|
||||
|
||||
export class MetaMaskConnector extends InjectedConnector {
|
||||
readonly id = "metaMask";
|
||||
readonly ready =
|
||||
typeof window !== "undefined" &&
|
||||
!!this.#findProvider(window.ethereum as Ethereum);
|
||||
|
||||
#provider?: Window["ethereum"];
|
||||
#UNSTABLE_shimOnConnectSelectAccount: MetaMaskConnectorOptions["UNSTABLE_shimOnConnectSelectAccount"];
|
||||
|
||||
constructor({
|
||||
chains,
|
||||
options: options_,
|
||||
}: {
|
||||
chains?: Chain[];
|
||||
options?: MetaMaskConnectorOptions;
|
||||
} = {}) {
|
||||
const options = {
|
||||
name: "MetaMask",
|
||||
shimDisconnect: true,
|
||||
shimChainChangedDisconnect: true,
|
||||
...options_,
|
||||
};
|
||||
super({ chains, options });
|
||||
|
||||
this.#UNSTABLE_shimOnConnectSelectAccount =
|
||||
options.UNSTABLE_shimOnConnectSelectAccount;
|
||||
}
|
||||
|
||||
async connect({ chainId }: { chainId?: number } = {}) {
|
||||
try {
|
||||
const provider = await this.getProvider();
|
||||
if (!provider) {
|
||||
throw new ConnectorNotFoundError();
|
||||
}
|
||||
|
||||
if (provider.on) {
|
||||
provider.on("accountsChanged", this.onAccountsChanged);
|
||||
provider.on("chainChanged", this.onChainChanged);
|
||||
provider.on("disconnect", this.onDisconnect);
|
||||
}
|
||||
|
||||
this.emit("message", { type: "connecting" });
|
||||
|
||||
// Attempt to show wallet select prompt with `wallet_requestPermissions` when
|
||||
// `shimDisconnect` is active and account is in disconnected state (flag in storage)
|
||||
let account: Address | null = null;
|
||||
if (
|
||||
this.#UNSTABLE_shimOnConnectSelectAccount &&
|
||||
this.options?.shimDisconnect &&
|
||||
(await getConnectorStorage().getItem(this.shimDisconnectKey))
|
||||
) {
|
||||
account = await this.getAccount().catch(() => null);
|
||||
const isConnected = !!account;
|
||||
if (isConnected) {
|
||||
// Attempt to show another prompt for selecting wallet if already connected
|
||||
await provider
|
||||
.request({
|
||||
method: "wallet_requestPermissions",
|
||||
params: [{ eth_accounts: {} }],
|
||||
})
|
||||
.catch(() => null);
|
||||
}
|
||||
}
|
||||
|
||||
if (!account) {
|
||||
const accounts = await provider.request({
|
||||
method: "eth_requestAccounts",
|
||||
});
|
||||
account = getAddress(accounts[0] as string);
|
||||
}
|
||||
|
||||
// Switch to chain if provided
|
||||
let id = await this.getChainId();
|
||||
let unsupported = this.isChainUnsupported(id);
|
||||
if (chainId && id !== chainId) {
|
||||
const chain = await this.switchChain(chainId);
|
||||
id = chain.id;
|
||||
unsupported = this.isChainUnsupported(id);
|
||||
}
|
||||
|
||||
if (this.options?.shimDisconnect) {
|
||||
await getConnectorStorage().setItem(this.shimDisconnectKey, true);
|
||||
}
|
||||
|
||||
return { account, chain: { id, unsupported }, provider };
|
||||
} catch (error) {
|
||||
if (this.isUserRejectedRequestError(error)) {
|
||||
throw new UserRejectedRequestError(error);
|
||||
}
|
||||
if ((error as RpcError).code === -32002) {
|
||||
throw new ResourceUnavailableError(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getProvider() {
|
||||
if (typeof window !== "undefined") {
|
||||
// TODO: Fallback to `ethereum#initialized` event for async injection
|
||||
// https://github.com/MetaMask/detect-provider#synchronous-and-asynchronous-injection=
|
||||
this.#provider = this.#findProvider(window.ethereum as Ethereum);
|
||||
}
|
||||
return this.#provider as Ethereum;
|
||||
}
|
||||
|
||||
#getReady(ethereum?: Ethereum) {
|
||||
const isMetaMask = !!ethereum?.isMetaMask;
|
||||
if (!isMetaMask) {
|
||||
return;
|
||||
}
|
||||
// Brave tries to make itself look like MetaMask
|
||||
// Could also try RPC `web3_clientVersion` if following is unreliable
|
||||
if (ethereum.isBraveWallet && !ethereum._events && !ethereum._state) {
|
||||
return;
|
||||
}
|
||||
if (ethereum.isAvalanche) {
|
||||
return;
|
||||
}
|
||||
if (ethereum.isKuCoinWallet) {
|
||||
return;
|
||||
}
|
||||
if (ethereum.isPortal) {
|
||||
return;
|
||||
}
|
||||
if (ethereum.isTokenPocket) {
|
||||
return;
|
||||
}
|
||||
if (ethereum.isTokenary) {
|
||||
return;
|
||||
}
|
||||
return ethereum;
|
||||
}
|
||||
|
||||
#findProvider(ethereum?: Ethereum) {
|
||||
if (ethereum?.providers) {
|
||||
return ethereum.providers.find(this.#getReady);
|
||||
}
|
||||
return this.#getReady(ethereum);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import {
|
||||
Chain,
|
||||
Connector,
|
||||
normalizeChainId,
|
||||
ProviderRpcError,
|
||||
SwitchChainError,
|
||||
UserRejectedRequestError,
|
||||
} from "@wagmi/core";
|
||||
import type WalletConnectProvider from "@walletconnect/ethereum-provider";
|
||||
import { providers } from "ethers";
|
||||
import { getAddress, hexValue } from "ethers/lib/utils.js";
|
||||
|
||||
/**
|
||||
* Wallets that support chain switching through WalletConnect
|
||||
* - imToken (token.im)
|
||||
* - MetaMask (metamask.io)
|
||||
* - Rainbow (rainbow.me)
|
||||
* - Trust Wallet (trustwallet.com)
|
||||
*/
|
||||
const switchChainAllowedRegex = /(imtoken|metamask|rainbow|trust wallet)/i;
|
||||
|
||||
type WalletConnectOptions = ConstructorParameters<
|
||||
typeof WalletConnectProvider
|
||||
>[0];
|
||||
|
||||
type WalletConnectSigner = providers.JsonRpcSigner;
|
||||
|
||||
export class WalletConnectConnector extends Connector<
|
||||
WalletConnectProvider,
|
||||
WalletConnectOptions,
|
||||
WalletConnectSigner
|
||||
> {
|
||||
readonly id = "walletConnect";
|
||||
readonly name = "WalletConnect";
|
||||
readonly ready = true;
|
||||
|
||||
#provider?: WalletConnectProvider;
|
||||
|
||||
constructor(config: { chains?: Chain[]; options: WalletConnectOptions }) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
async connect({ chainId }: { chainId?: number } = {}) {
|
||||
try {
|
||||
let targetChainId = chainId || 1;
|
||||
|
||||
const provider = await this.getProvider({
|
||||
chainId: targetChainId,
|
||||
create: true,
|
||||
});
|
||||
provider.on("accountsChanged", this.onAccountsChanged);
|
||||
provider.on("chainChanged", this.onChainChanged);
|
||||
provider.on("disconnect", this.onDisconnect);
|
||||
|
||||
// Defer message to the next tick to ensure wallet connect data (provided by `.enable()`) is available
|
||||
setTimeout(() => this.emit("message", { type: "connecting" }), 0);
|
||||
|
||||
const accounts = await provider.enable();
|
||||
const account = getAddress(accounts[0] as string);
|
||||
const id = await this.getChainId();
|
||||
const unsupported = this.isChainUnsupported(id);
|
||||
|
||||
// Not all WalletConnect options support programmatic chain switching
|
||||
// Only enable for wallet options that do
|
||||
const walletName = provider.connector?.peerMeta?.name ?? "";
|
||||
if (switchChainAllowedRegex.test(walletName)) {
|
||||
this.switchChain = this.#switchChain;
|
||||
}
|
||||
|
||||
return {
|
||||
account,
|
||||
chain: { id, unsupported },
|
||||
provider: new providers.Web3Provider(
|
||||
provider as providers.ExternalProvider,
|
||||
),
|
||||
};
|
||||
} catch (error) {
|
||||
if (/user closed modal/i.test((error as ProviderRpcError).message)) {
|
||||
throw new UserRejectedRequestError(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
const provider = await this.getProvider();
|
||||
await provider.disconnect();
|
||||
|
||||
provider.removeListener("accountsChanged", this.onAccountsChanged);
|
||||
provider.removeListener("chainChanged", this.onChainChanged);
|
||||
provider.removeListener("disconnect", this.onDisconnect);
|
||||
|
||||
if (typeof localStorage !== "undefined") {
|
||||
localStorage.removeItem("walletconnect");
|
||||
}
|
||||
}
|
||||
|
||||
async getAccount() {
|
||||
const provider = await this.getProvider();
|
||||
const accounts = provider.accounts;
|
||||
// return checksum address
|
||||
return getAddress(accounts[0] as string);
|
||||
}
|
||||
|
||||
async getChainId() {
|
||||
const provider = await this.getProvider();
|
||||
const chainId = normalizeChainId(provider.chainId);
|
||||
return chainId;
|
||||
}
|
||||
|
||||
async getProvider({
|
||||
chainId,
|
||||
create,
|
||||
}: { chainId?: number; create?: boolean } = {}) {
|
||||
// Force create new provider
|
||||
if (!this.#provider || chainId || create) {
|
||||
const rpc = !this.options?.infuraId
|
||||
? this.chains.reduce(
|
||||
(rpc_, chain) => ({ ...rpc_, [chain.id]: chain.rpcUrls.default }),
|
||||
{},
|
||||
)
|
||||
: {};
|
||||
|
||||
const WalletConnectProvider = (
|
||||
await import("@walletconnect/ethereum-provider")
|
||||
).default;
|
||||
this.#provider = new WalletConnectProvider({
|
||||
...this.options,
|
||||
chainId,
|
||||
rpc: { ...rpc, ...this.options?.rpc },
|
||||
});
|
||||
}
|
||||
|
||||
return this.#provider;
|
||||
}
|
||||
|
||||
async getSigner({ chainId }: { chainId?: number } = {}) {
|
||||
const [provider, account] = await Promise.all([
|
||||
this.getProvider({ chainId }),
|
||||
this.getAccount(),
|
||||
]);
|
||||
return new providers.Web3Provider(
|
||||
provider as providers.ExternalProvider,
|
||||
chainId,
|
||||
).getSigner(account);
|
||||
}
|
||||
|
||||
async isAuthorized() {
|
||||
try {
|
||||
const account = await this.getAccount();
|
||||
return !!account;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async #switchChain(chainId: number) {
|
||||
const provider = await this.getProvider();
|
||||
const id = hexValue(chainId);
|
||||
|
||||
try {
|
||||
// Set up a race between `wallet_switchEthereumChain` & the `chainChanged` event
|
||||
// to ensure the chain has been switched. This is because there could be a case
|
||||
// where a wallet may not resolve the `wallet_switchEthereumChain` method, or
|
||||
// resolves slower than `chainChanged`.
|
||||
await Promise.race([
|
||||
provider.request({
|
||||
method: "wallet_switchEthereumChain",
|
||||
params: [{ chainId: id }],
|
||||
}),
|
||||
new Promise((res) =>
|
||||
this.on("change", ({ chain }) => {
|
||||
if (chain?.id === chainId) {
|
||||
res(chainId);
|
||||
}
|
||||
}),
|
||||
),
|
||||
]);
|
||||
return (
|
||||
this.chains.find((x) => x.id === chainId) ?? {
|
||||
id: chainId,
|
||||
name: `Chain ${id}`,
|
||||
network: `${id}`,
|
||||
rpcUrls: { default: "" },
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
const message =
|
||||
typeof error === "string"
|
||||
? error
|
||||
: (error as ProviderRpcError)?.message;
|
||||
if (/user rejected request/i.test(message)) {
|
||||
throw new UserRejectedRequestError(error);
|
||||
}
|
||||
throw new SwitchChainError(error);
|
||||
}
|
||||
}
|
||||
|
||||
protected onAccountsChanged = (accounts: string[]) => {
|
||||
if (accounts.length === 0) {
|
||||
this.emit("disconnect");
|
||||
} else {
|
||||
this.emit("change", { account: getAddress(accounts[0] as string) });
|
||||
}
|
||||
};
|
||||
|
||||
protected onChainChanged = (chainId: number | string) => {
|
||||
const id = normalizeChainId(chainId);
|
||||
const unsupported = this.isChainUnsupported(id);
|
||||
this.emit("change", { chain: { id, unsupported } });
|
||||
};
|
||||
|
||||
protected onDisconnect = () => {
|
||||
this.emit("disconnect");
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
import {
|
||||
Chain,
|
||||
etherscanBlockExplorers,
|
||||
alchemyRpcUrls,
|
||||
infuraRpcUrls,
|
||||
} from "@wagmi/core";
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export enum ChainId {
|
||||
Mainnet = 1,
|
||||
Goerli = 5,
|
||||
Polygon = 137,
|
||||
Mumbai = 80001,
|
||||
Fantom = 250,
|
||||
FantomTestnet = 4002,
|
||||
Avalanche = 43114,
|
||||
AvalancheFujiTestnet = 43113,
|
||||
Optimism = 10,
|
||||
OptimismGoerli = 420,
|
||||
Arbitrum = 42161,
|
||||
ArbitrumGoerli = 421613,
|
||||
BinanceSmartChainMainnet = 56,
|
||||
BinanceSmartChainTestnet = 97,
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type SUPPORTED_CHAIN_ID =
|
||||
| ChainId.Mainnet
|
||||
| ChainId.Goerli
|
||||
| ChainId.Mumbai
|
||||
| ChainId.Polygon
|
||||
| ChainId.Fantom
|
||||
| ChainId.FantomTestnet
|
||||
| ChainId.Avalanche
|
||||
| ChainId.AvalancheFujiTestnet
|
||||
| ChainId.Optimism
|
||||
| ChainId.OptimismGoerli
|
||||
| ChainId.Arbitrum
|
||||
| ChainId.ArbitrumGoerli
|
||||
| ChainId.BinanceSmartChainMainnet
|
||||
| ChainId.BinanceSmartChainTestnet;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const SUPPORTED_CHAIN_IDS: SUPPORTED_CHAIN_ID[] = [
|
||||
ChainId.Mainnet,
|
||||
ChainId.Goerli,
|
||||
ChainId.Polygon,
|
||||
ChainId.Mumbai,
|
||||
ChainId.Fantom,
|
||||
ChainId.FantomTestnet,
|
||||
ChainId.Avalanche,
|
||||
ChainId.AvalancheFujiTestnet,
|
||||
ChainId.Optimism,
|
||||
ChainId.OptimismGoerli,
|
||||
ChainId.Arbitrum,
|
||||
ChainId.ArbitrumGoerli,
|
||||
ChainId.BinanceSmartChainMainnet,
|
||||
ChainId.BinanceSmartChainTestnet,
|
||||
];
|
||||
|
||||
type NativeCurrency = {
|
||||
name: string;
|
||||
/** 2-6 characters long */
|
||||
symbol: string;
|
||||
decimals: 18;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const NATIVE_TOKEN_ADDRESS =
|
||||
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const NATIVE_TOKENS: Record<SUPPORTED_CHAIN_ID, NativeCurrency> = {
|
||||
[ChainId.Mainnet]: {
|
||||
name: "Ether",
|
||||
symbol: "ETH",
|
||||
decimals: 18,
|
||||
},
|
||||
[ChainId.Goerli]: {
|
||||
name: "Görli Ether",
|
||||
symbol: "GOR",
|
||||
decimals: 18,
|
||||
},
|
||||
[ChainId.Polygon]: {
|
||||
name: "Matic",
|
||||
symbol: "MATIC",
|
||||
decimals: 18,
|
||||
},
|
||||
[ChainId.Mumbai]: {
|
||||
name: "Matic",
|
||||
symbol: "MATIC",
|
||||
decimals: 18,
|
||||
},
|
||||
[ChainId.Avalanche]: {
|
||||
name: "Avalanche",
|
||||
symbol: "AVAX",
|
||||
decimals: 18,
|
||||
},
|
||||
[ChainId.AvalancheFujiTestnet]: {
|
||||
name: "Avalanche",
|
||||
symbol: "AVAX",
|
||||
decimals: 18,
|
||||
},
|
||||
[ChainId.Fantom]: {
|
||||
name: "Fantom",
|
||||
symbol: "FTM",
|
||||
decimals: 18,
|
||||
},
|
||||
[ChainId.FantomTestnet]: {
|
||||
name: "Fantom",
|
||||
symbol: "FTM",
|
||||
decimals: 18,
|
||||
},
|
||||
[ChainId.Arbitrum]: {
|
||||
name: "Ether",
|
||||
symbol: "ETH",
|
||||
decimals: 18,
|
||||
},
|
||||
|
||||
[ChainId.ArbitrumGoerli]: {
|
||||
name: "Arbitrum Goerli Ether",
|
||||
symbol: "AGOR",
|
||||
decimals: 18,
|
||||
},
|
||||
[ChainId.Optimism]: {
|
||||
name: "Ether",
|
||||
symbol: "ETH",
|
||||
decimals: 18,
|
||||
},
|
||||
|
||||
[ChainId.OptimismGoerli]: {
|
||||
name: "Goerli Ether",
|
||||
symbol: "ETH",
|
||||
decimals: 18,
|
||||
},
|
||||
[ChainId.BinanceSmartChainMainnet]: {
|
||||
name: "Binance Chain Native Token",
|
||||
symbol: "BNB",
|
||||
decimals: 18,
|
||||
},
|
||||
[ChainId.BinanceSmartChainTestnet]: {
|
||||
name: "Binance Chain Native Token",
|
||||
symbol: "TBNB",
|
||||
decimals: 18,
|
||||
},
|
||||
};
|
||||
|
||||
export const DEFAULT_API_KEY =
|
||||
"c6634ad2d97b74baf15ff556016830c251050e6c36b9da508ce3ec80095d3dc1";
|
||||
|
||||
export function getRpcUrl(network: string) {
|
||||
return `https://${network}.rpc.thirdweb.com/${DEFAULT_API_KEY}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the native token for a given chain
|
||||
* @param chainId - the chain id
|
||||
* @public
|
||||
*/
|
||||
export function getNativeTokenByChainId(chainId: ChainId): NativeCurrency {
|
||||
return NATIVE_TOKENS[chainId as SUPPORTED_CHAIN_ID];
|
||||
}
|
||||
|
||||
const supportedChains: Record<SUPPORTED_CHAIN_ID, Chain> = {
|
||||
[ChainId.Mainnet]: {
|
||||
id: ChainId.Mainnet,
|
||||
name: "Ethereum",
|
||||
network: "homestead",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.Mainnet),
|
||||
rpcUrls: {
|
||||
alchemy: alchemyRpcUrls.mainnet,
|
||||
default: getRpcUrl("ethereum"),
|
||||
infura: infuraRpcUrls.mainnet,
|
||||
public: getRpcUrl("ethereum"),
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: etherscanBlockExplorers.mainnet,
|
||||
default: etherscanBlockExplorers.mainnet,
|
||||
},
|
||||
},
|
||||
[ChainId.Goerli]: {
|
||||
id: ChainId.Goerli,
|
||||
name: "Goerli",
|
||||
network: "goerli",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.Goerli),
|
||||
rpcUrls: {
|
||||
alchemy: alchemyRpcUrls.goerli,
|
||||
default: getRpcUrl("goerli"),
|
||||
infura: infuraRpcUrls.goerli,
|
||||
public: getRpcUrl("goerli"),
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: etherscanBlockExplorers.goerli,
|
||||
default: etherscanBlockExplorers.goerli,
|
||||
},
|
||||
testnet: true,
|
||||
},
|
||||
[ChainId.Polygon]: {
|
||||
id: ChainId.Polygon,
|
||||
name: "Polygon",
|
||||
network: "matic",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.Polygon),
|
||||
rpcUrls: {
|
||||
alchemy: alchemyRpcUrls.polygon,
|
||||
default: getRpcUrl("polygon"),
|
||||
infura: infuraRpcUrls.polygon,
|
||||
public: getRpcUrl("polygon"),
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: etherscanBlockExplorers.polygon,
|
||||
default: etherscanBlockExplorers.polygon,
|
||||
},
|
||||
},
|
||||
[ChainId.Mumbai]: {
|
||||
id: ChainId.Mumbai,
|
||||
name: "Mumbai",
|
||||
network: "maticmum",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.Mumbai),
|
||||
rpcUrls: {
|
||||
alchemy: alchemyRpcUrls.polygonMumbai,
|
||||
default: getRpcUrl("mumbai"),
|
||||
infura: infuraRpcUrls.polygonMumbai,
|
||||
public: getRpcUrl("mumbai"),
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: etherscanBlockExplorers.polygonMumbai,
|
||||
default: etherscanBlockExplorers.polygonMumbai,
|
||||
},
|
||||
testnet: true,
|
||||
},
|
||||
[ChainId.Avalanche]: {
|
||||
id: ChainId.Avalanche,
|
||||
name: "Avalanche",
|
||||
network: "avalanche",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.Avalanche),
|
||||
rpcUrls: {
|
||||
default: getRpcUrl("avalanche"),
|
||||
public: getRpcUrl("avalanche"),
|
||||
},
|
||||
blockExplorers: {
|
||||
default: {
|
||||
name: "SnowTrace",
|
||||
url: "https://snowtrace.io/",
|
||||
},
|
||||
},
|
||||
},
|
||||
[ChainId.AvalancheFujiTestnet]: {
|
||||
id: ChainId.AvalancheFujiTestnet,
|
||||
name: "Avalanche Fuji Testnet",
|
||||
network: "avalanche-fuji",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.AvalancheFujiTestnet),
|
||||
rpcUrls: {
|
||||
default: getRpcUrl("avalanche-fuji"),
|
||||
public: getRpcUrl("avalanche-fuji"),
|
||||
},
|
||||
blockExplorers: {
|
||||
default: {
|
||||
name: "SnowTrace",
|
||||
url: "https://testnet.snowtrace.io/",
|
||||
},
|
||||
},
|
||||
testnet: true,
|
||||
},
|
||||
[ChainId.Fantom]: {
|
||||
id: ChainId.Fantom,
|
||||
name: "Fantom Opera",
|
||||
network: "fantom",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.Fantom),
|
||||
rpcUrls: {
|
||||
default: getRpcUrl("fantom"),
|
||||
public: getRpcUrl("fantom"),
|
||||
},
|
||||
blockExplorers: {
|
||||
default: {
|
||||
name: "Fantom Explorer",
|
||||
url: "https://ftmscan.com/",
|
||||
},
|
||||
},
|
||||
},
|
||||
[ChainId.FantomTestnet]: {
|
||||
id: ChainId.FantomTestnet,
|
||||
name: "Fantom Opera Testnet",
|
||||
network: "fantom-testnet",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.FantomTestnet),
|
||||
rpcUrls: {
|
||||
default: getRpcUrl("fantom-testnet"),
|
||||
public: getRpcUrl("fantom-testnet"),
|
||||
},
|
||||
blockExplorers: {
|
||||
default: {
|
||||
name: "Fantom Explorer",
|
||||
url: "https://testnet.ftmscan.com/",
|
||||
},
|
||||
},
|
||||
testnet: true,
|
||||
},
|
||||
[ChainId.Optimism]: {
|
||||
id: ChainId.Optimism,
|
||||
name: "Optimism",
|
||||
network: "optimism",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.Optimism),
|
||||
rpcUrls: {
|
||||
alchemy: alchemyRpcUrls.optimism,
|
||||
default: getRpcUrl("optimism"),
|
||||
infura: infuraRpcUrls.optimism,
|
||||
public: getRpcUrl("optimism"),
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: etherscanBlockExplorers.optimism,
|
||||
default: etherscanBlockExplorers.optimism,
|
||||
},
|
||||
},
|
||||
[ChainId.OptimismGoerli]: {
|
||||
id: ChainId.OptimismGoerli,
|
||||
name: "Optimism Goerli",
|
||||
network: "optimism-goerli",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.OptimismGoerli),
|
||||
rpcUrls: {
|
||||
alchemy: alchemyRpcUrls.optimismGoerli,
|
||||
default: getRpcUrl("optimism-goerli"),
|
||||
infura: infuraRpcUrls.optimismGoerli,
|
||||
public: getRpcUrl("optimism-goerli"),
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: etherscanBlockExplorers.optimismGoerli,
|
||||
default: etherscanBlockExplorers.optimismGoerli,
|
||||
},
|
||||
testnet: true,
|
||||
},
|
||||
[ChainId.Arbitrum]: {
|
||||
id: ChainId.Arbitrum,
|
||||
name: "Arbitrum One",
|
||||
network: "arbitrum",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.Arbitrum),
|
||||
rpcUrls: {
|
||||
alchemy: alchemyRpcUrls.arbitrum,
|
||||
default: getRpcUrl("arbitrum"),
|
||||
infura: infuraRpcUrls.arbitrum,
|
||||
public: getRpcUrl("arbitrum"),
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: etherscanBlockExplorers.arbitrum,
|
||||
default: etherscanBlockExplorers.arbitrum,
|
||||
},
|
||||
},
|
||||
[ChainId.ArbitrumGoerli]: {
|
||||
id: ChainId.ArbitrumGoerli,
|
||||
name: "Arbitrum Goerli",
|
||||
network: "arbitrum-goerli",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.ArbitrumGoerli),
|
||||
rpcUrls: {
|
||||
alchemy: alchemyRpcUrls.arbitrumGoerli,
|
||||
default: getRpcUrl("arbitrum-goerli"),
|
||||
infura: infuraRpcUrls.arbitrumGoerli,
|
||||
public: getRpcUrl("arbitrum-goerli"),
|
||||
},
|
||||
blockExplorers: {
|
||||
etherscan: etherscanBlockExplorers.arbitrumGoerli,
|
||||
default: etherscanBlockExplorers.arbitrumGoerli,
|
||||
},
|
||||
testnet: true,
|
||||
},
|
||||
[ChainId.BinanceSmartChainMainnet]: {
|
||||
id: ChainId.BinanceSmartChainMainnet,
|
||||
name: "Binance Smart Chain",
|
||||
network: "bsc",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.BinanceSmartChainMainnet),
|
||||
rpcUrls: {
|
||||
default: getRpcUrl("binance"),
|
||||
public: getRpcUrl("binance"),
|
||||
},
|
||||
blockExplorers: {
|
||||
default: {
|
||||
name: "BscScan",
|
||||
url: "https://bscscan.com/",
|
||||
},
|
||||
},
|
||||
},
|
||||
[ChainId.BinanceSmartChainTestnet]: {
|
||||
id: ChainId.BinanceSmartChainTestnet,
|
||||
name: "Binance Smart Chain Testnet",
|
||||
network: "bsc-testnet",
|
||||
nativeCurrency: getNativeTokenByChainId(ChainId.BinanceSmartChainTestnet),
|
||||
rpcUrls: {
|
||||
default: getRpcUrl("binance-testnet"),
|
||||
public: getRpcUrl("binance-testnet"),
|
||||
},
|
||||
blockExplorers: {
|
||||
default: {
|
||||
name: "BscScan",
|
||||
url: "https://testnet.bscscan.com/",
|
||||
},
|
||||
},
|
||||
testnet: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const thirdwebChains: Chain[] = Object.values(supportedChains);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user