Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a20be881e3 | ||
|
|
47f8945325 | ||
|
|
1b0bf99da7 | ||
|
|
f03be398e0 | ||
|
|
27996cb4bc | ||
|
|
2eaa67acbe | ||
|
|
aafd7968df | ||
|
|
48797c7cf0 | ||
|
|
4edcb37100 | ||
|
|
83dcaf7058 | ||
|
|
b4e005be7f | ||
|
|
ccb7db4873 | ||
|
|
9151aa7dc8 | ||
|
|
5a01c537e3 | ||
|
|
f09c153ff7 | ||
|
|
15431621fd | ||
|
|
887af24450 | ||
|
|
15514f190a | ||
|
|
72589ec234 | ||
|
|
f8bceec10c | ||
|
|
7c0744a006 | ||
|
|
846022e2cf | ||
|
|
96bd92e724 |
@@ -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,15 @@
|
||||
# @thirdweb-dev/auth
|
||||
|
||||
## 2.0.33
|
||||
|
||||
## 2.0.32
|
||||
|
||||
## 2.0.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#397](https://github.com/thirdweb-dev/js/pull/397) [`72589ec`](https://github.com/thirdweb-dev/js/commit/72589ec234d86939b67b0ca8b3b789f2e9c54cc2) Thanks [@adam-maj](https://github.com/adam-maj)! - Call user callback in getUser
|
||||
|
||||
## 2.0.30
|
||||
|
||||
## 2.0.29
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "2.0.30",
|
||||
"version": "2.0.33",
|
||||
"exports": {
|
||||
"./next": {
|
||||
"module": "./next/dist/thirdweb-dev-auth-next.esm.js",
|
||||
@@ -73,7 +73,7 @@
|
||||
"eslint-plugin-inclusive-language": "^2.2.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-tsdoc": "^0.2.16",
|
||||
"ethers": "^5.7.1",
|
||||
"ethers": "^5.7.2",
|
||||
"express": "^4.18.1",
|
||||
"next": "^12.2.0",
|
||||
"next-auth": "^4.10.3",
|
||||
|
||||
@@ -70,7 +70,13 @@ export function ThirdwebAuth(cfg: ThirdwebAuthConfig) {
|
||||
if (token) {
|
||||
try {
|
||||
const address = await sdk.auth.authenticate(domain, token);
|
||||
user = { address };
|
||||
|
||||
let data = {};
|
||||
if (ctx.callbacks?.user) {
|
||||
data = await ctx.callbacks.user(address);
|
||||
}
|
||||
|
||||
user = { ...data, address };
|
||||
} catch {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@@ -70,7 +70,13 @@ export function ThirdwebAuth(cfg: ThirdwebAuthConfig) {
|
||||
if (token) {
|
||||
try {
|
||||
const address = await sdk.auth.authenticate(domain, token);
|
||||
user = { address };
|
||||
|
||||
let data = {};
|
||||
if (ctx.callbacks?.user) {
|
||||
data = await ctx.callbacks.user(address);
|
||||
}
|
||||
|
||||
user = { ...data, address };
|
||||
} catch {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# thirdweb
|
||||
|
||||
## 0.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#403](https://github.com/thirdweb-dev/js/pull/403) [`27996cb`](https://github.com/thirdweb-dev/js/commit/27996cb4bc6f054f45b0a66b0a3aca732e0ef610) Thanks [@avneesh0612](https://github.com/avneesh0612)! - Add CLI Support for solana with --solana flag on create command
|
||||
|
||||
- [#283](https://github.com/thirdweb-dev/js/pull/283) [`2eaa67a`](https://github.com/thirdweb-dev/js/commit/2eaa67acbe21464e7715d0b696afdc024460a44d) Thanks [@adam-maj](https://github.com/adam-maj)! - add capability to deploy apps
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#413](https://github.com/thirdweb-dev/js/pull/413) [`1b0bf99`](https://github.com/thirdweb-dev/js/commit/1b0bf99da796df88ff307dd004cdaf239fb2b1ba) Thanks [@furqanrydhan](https://github.com/furqanrydhan)! - small interface tweaks
|
||||
|
||||
- Updated dependencies [[`48797c7`](https://github.com/thirdweb-dev/js/commit/48797c7cf0695965e33eb6bf602be4652f7085a8), [`83dcaf7`](https://github.com/thirdweb-dev/js/commit/83dcaf70584af8c5cdf0427f9fc1ba82d89f0887), [`f03be39`](https://github.com/thirdweb-dev/js/commit/f03be398e0d7b75c9c5b4fb643f35903e89a17e7), [`2eaa67a`](https://github.com/thirdweb-dev/js/commit/2eaa67acbe21464e7715d0b696afdc024460a44d), [`47f8945`](https://github.com/thirdweb-dev/js/commit/47f8945325447874b1ace0359919c1d54fd55436)]:
|
||||
- @thirdweb-dev/sdk@3.6.3
|
||||
|
||||
## 0.8.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ccb7db4`](https://github.com/thirdweb-dev/js/commit/ccb7db48739b8dddcb2c032b3b6e3e5200485715), [`9151aa7`](https://github.com/thirdweb-dev/js/commit/9151aa7dc80754a7aa4fd3f6ce0207bccbb6451f), [`5a01c53`](https://github.com/thirdweb-dev/js/commit/5a01c537e30bd7ef562aad1012413990de6faedf)]:
|
||||
- @thirdweb-dev/sdk@3.6.2
|
||||
|
||||
## 0.8.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`15514f1`](https://github.com/thirdweb-dev/js/commit/15514f190a844d809ae5b1edb580aab46a682485), [`7c0744a`](https://github.com/thirdweb-dev/js/commit/7c0744a006e78987ad8b271b2c64f4bac7759510), [`f8bceec`](https://github.com/thirdweb-dev/js/commit/f8bceec10c60b30dc3d6f45926e2a9e018202578), [`887af24`](https://github.com/thirdweb-dev/js/commit/887af24450e316c2ea390cd49c0563cebe793c0f), [`96bd92e`](https://github.com/thirdweb-dev/js/commit/96bd92e724ec7d880d23348071b26ab8c21dd868)]:
|
||||
- @thirdweb-dev/sdk@3.6.1
|
||||
|
||||
## 0.8.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export const CREATE_MESSAGES = {
|
||||
typeOfProject: "What type of project do you want to create?",
|
||||
projectName: "What is your project named?",
|
||||
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?",
|
||||
|
||||
@@ -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/",
|
||||
);
|
||||
|
||||
|
||||
@@ -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.16",
|
||||
"version": "0.9.0",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gmrchk/cli-testing-library": "^0.1.2",
|
||||
"@types/async-retry": "1.3.0",
|
||||
"@types/async-retry": "^1.3.0",
|
||||
"@types/cross-spawn": "^6.0.2",
|
||||
"@types/inquirer": "^8.2.1",
|
||||
"@types/jest": "^29.0.0",
|
||||
@@ -33,13 +33,13 @@
|
||||
"async-retry": "^1.3.3",
|
||||
"commander": "^9.1.0",
|
||||
"enquirer": "^2.3.6",
|
||||
"ethers": "^5.7.0",
|
||||
"ethers": "^5.7.2",
|
||||
"got": "11.8.5",
|
||||
"inquirer": "^8.2.3",
|
||||
"open": "^8.4.0",
|
||||
"prompts": "^2.4.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"solc": "0.8.13",
|
||||
"solc": "0.8.17",
|
||||
"tar": "^6.1.11",
|
||||
"tslog": "^3.3.3",
|
||||
"update-notifier": "^6.0.2",
|
||||
|
||||
@@ -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,36 @@ $$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$
|
||||
.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);
|
||||
.action(async (type, path, options) => {
|
||||
await twCreate(type, path, options);
|
||||
});
|
||||
|
||||
program
|
||||
@@ -87,6 +89,10 @@ $$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$
|
||||
.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)",
|
||||
@@ -95,31 +101,13 @@ $$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$
|
||||
"-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
|
||||
@@ -133,8 +121,11 @@ $$$$$$\\ $$$$$$$\\ $$\\ $$$$$$\\ $$$$$$$ |$$\\ $$\\ $$\\ $$$$$$\\ $$$$
|
||||
.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 +135,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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,10 @@ export type ProjectType =
|
||||
| "hardhat"
|
||||
| "truffle"
|
||||
| "solc"
|
||||
| "vite"
|
||||
| "next"
|
||||
| "cra"
|
||||
| "spa-webapp"
|
||||
| "yarn"
|
||||
| "npm"
|
||||
| "none";
|
||||
|
||||
@@ -10,52 +10,67 @@ import chalk from "chalk";
|
||||
import path from "path";
|
||||
import prompts from "prompts";
|
||||
|
||||
let projectType: 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) {
|
||||
if (pType === "app" || options.app) {
|
||||
projectType = "app";
|
||||
}
|
||||
|
||||
if (options.contract) {
|
||||
} else if (pType === "contract" || options.contract) {
|
||||
projectType = "contract";
|
||||
}
|
||||
|
||||
if (options.typescript) {
|
||||
language = "typescript";
|
||||
if (projectType === "app") {
|
||||
if (options.typescript) {
|
||||
language = "typescript";
|
||||
}
|
||||
|
||||
if (options.javascript) {
|
||||
language = "javascript";
|
||||
}
|
||||
|
||||
if (options.next) {
|
||||
framework = "next";
|
||||
}
|
||||
|
||||
if (options.cra) {
|
||||
framework = "cra";
|
||||
}
|
||||
|
||||
if (options.vite) {
|
||||
framework = "vite";
|
||||
}
|
||||
|
||||
if (options.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,7 +91,7 @@ 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";
|
||||
@@ -123,16 +138,42 @@ export async function twCreate(pPath: string = "", options: any) {
|
||||
}
|
||||
|
||||
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: [
|
||||
{ title: "Next.js", value: "next" },
|
||||
{ title: "Create React App", value: "cra" },
|
||||
{ title: "Vite", value: "vite" },
|
||||
],
|
||||
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") {
|
||||
@@ -288,6 +329,7 @@ export async function twCreate(pPath: string = "", options: any) {
|
||||
framework,
|
||||
language,
|
||||
template,
|
||||
chain,
|
||||
});
|
||||
} else {
|
||||
await createContract({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@preconstruct/cli": "^2.2.1",
|
||||
"@typechain/ethers-v5": "10.0.0",
|
||||
"ethers": "^5.7.0",
|
||||
"ethers": "^5.7.2",
|
||||
"typechain": "^8.1.0",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"eslint-config-next": "^12.0.8",
|
||||
"eslint-config-next": "12.3.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-turbo": "latest",
|
||||
"eslint-plugin-react": "^7.28.0"
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @thirdweb-dev/react
|
||||
|
||||
## 3.6.3
|
||||
|
||||
## 3.6.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#405](https://github.com/thirdweb-dev/js/pull/405) [`ccb7db4`](https://github.com/thirdweb-dev/js/commit/ccb7db48739b8dddcb2c032b3b6e3e5200485715) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Add updateAll param to creators and royalty update hooks for Solana
|
||||
|
||||
## 3.6.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#392](https://github.com/thirdweb-dev/js/pull/392) [`846022e`](https://github.com/thirdweb-dev/js/commit/846022e2cfb12b846aae029b85899fb72fd2c9ad) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Fix unlimited price for useActiveClaimConditionForWallet
|
||||
|
||||
- [#388](https://github.com/thirdweb-dev/js/pull/388) [`7c0744a`](https://github.com/thirdweb-dev/js/commit/7c0744a006e78987ad8b271b2c64f4bac7759510) Thanks [@adam-maj](https://github.com/adam-maj)! - Add support for total supply and pagination on get all
|
||||
|
||||
## 3.6.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react",
|
||||
"version": "3.6.0",
|
||||
"version": "3.6.3",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -62,7 +62,7 @@
|
||||
},
|
||||
"sideEffects:": false,
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.10",
|
||||
"@babel/core": "^7.19.3",
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
@@ -73,6 +73,7 @@
|
||||
"@preconstruct/cli": "^2.2.1",
|
||||
"@solana/wallet-adapter-base": "^0.9.17",
|
||||
"@solana/wallet-adapter-react": "^0.15.19",
|
||||
"@solana/wallet-adapter-wallets": "^0.19.0",
|
||||
"@solana/web3.js": "^1.62.0",
|
||||
"@thirdweb-dev/sdk": "*",
|
||||
"@types/color": "^3.0.3",
|
||||
@@ -87,7 +88,7 @@
|
||||
"eslint-plugin-inclusive-language": "^2.2.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-tsdoc": "^0.2.16",
|
||||
"ethers": "^5.7.1",
|
||||
"ethers": "^5.7.2",
|
||||
"prettier": "^2.7.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
convertToReadableQuantity,
|
||||
fetchCurrencyMetadata,
|
||||
} from "@thirdweb-dev/sdk";
|
||||
import { BigNumberish, utils } from "ethers";
|
||||
import { BigNumberish, constants, utils } from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
@@ -411,7 +411,9 @@ export function useActiveClaimConditionForWallet(
|
||||
);
|
||||
|
||||
const normalizedPrize = price
|
||||
? utils.parseUnits(price, currencyMetadata.decimals)
|
||||
? price === "unlimited"
|
||||
? constants.MaxUint256
|
||||
: utils.parseUnits(price, currencyMetadata.decimals)
|
||||
: null;
|
||||
|
||||
const priceWithOverride =
|
||||
|
||||
@@ -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 },
|
||||
);
|
||||
|
||||
@@ -4,17 +4,22 @@ import {
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { QueryAllParams } from "@thirdweb-dev/sdk";
|
||||
import type { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
|
||||
export function nftGetAllQuery(
|
||||
program: RequiredParam<NFTCollection | NFTDrop>,
|
||||
queryParams?: QueryAllParams,
|
||||
) {
|
||||
return {
|
||||
queryKey: createSOLProgramQueryKey(program, ["getAll"] as const),
|
||||
queryKey: createSOLProgramQueryKey(program, [
|
||||
"getAll",
|
||||
queryParams,
|
||||
] as const),
|
||||
|
||||
queryFn: async () => {
|
||||
requiredParamInvariant(program, "program is required");
|
||||
return await program.getAll();
|
||||
return program.getAll(queryParams);
|
||||
},
|
||||
enabled: !!program,
|
||||
};
|
||||
@@ -40,6 +45,9 @@ export function nftGetAllQuery(
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function useNFTs(program: RequiredParam<NFTCollection | NFTDrop>) {
|
||||
return useQuery(nftGetAllQuery(program));
|
||||
export function useNFTs(
|
||||
program: RequiredParam<NFTCollection | NFTDrop>,
|
||||
queryParams?: QueryAllParams,
|
||||
) {
|
||||
return useQuery(nftGetAllQuery(program, queryParams));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
|
||||
export function nftTotalSupplyQuery(
|
||||
program: RequiredParam<NFTCollection | NFTDrop>,
|
||||
) {
|
||||
return {
|
||||
queryKey: createSOLProgramQueryKey(program, ["supply"] as const),
|
||||
|
||||
queryFn: async () => {
|
||||
requiredParamInvariant(program, "program is required");
|
||||
return program.totalSupply();
|
||||
},
|
||||
enabled: !!program,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the totaly supply of NFTs on the program
|
||||
* @param program - The NFT program to get the total supply of
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* import { useProgram, useTotalSupply } from "@thirdweb-dev/react/solana";
|
||||
*
|
||||
* export default function Component() {
|
||||
* const { program } = useProgram("{{program_address}}");
|
||||
* const { data: supply, isLoading } = useTotalSupply(program);
|
||||
*
|
||||
* return (
|
||||
* <pre>{JSON.stringify(supply)}</pre>
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function useTotalSupply(
|
||||
program: RequiredParam<NFTCollection | NFTDrop>,
|
||||
) {
|
||||
return useQuery(nftTotalSupplyQuery(program));
|
||||
}
|
||||
@@ -4,7 +4,11 @@ import {
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { CreatorInput, NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
import type {
|
||||
NFTCollection,
|
||||
NFTDrop,
|
||||
UpdateCreatorInput,
|
||||
} from "@thirdweb-dev/sdk/solana";
|
||||
|
||||
/**
|
||||
* Update the creators for an NFT program
|
||||
@@ -35,9 +39,9 @@ export function useUpdateCreators(
|
||||
) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
async (creators: CreatorInput[]) => {
|
||||
async ({ creators, updateAll }: UpdateCreatorInput) => {
|
||||
requiredParamInvariant(program, "program is required");
|
||||
return await program.updateCreators(creators);
|
||||
return await program.updateCreators(creators, updateAll);
|
||||
},
|
||||
{
|
||||
onSettled: () =>
|
||||
|
||||
@@ -4,7 +4,11 @@ import {
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
import type {
|
||||
NFTCollection,
|
||||
NFTDrop,
|
||||
UpdateRoyaltySettingsInput,
|
||||
} from "@thirdweb-dev/sdk/solana";
|
||||
|
||||
/**
|
||||
* Update the royalty for an NFT program
|
||||
@@ -35,9 +39,9 @@ export function useUpdateRoyaltySettings(
|
||||
) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
async (sellerFeeBasisPoints: number) => {
|
||||
async ({ sellerFeeBasisPoints, updateAll }: UpdateRoyaltySettingsInput) => {
|
||||
requiredParamInvariant(program, "program is required");
|
||||
return await program.updateRoyalty(sellerFeeBasisPoints);
|
||||
return await program.updateRoyalty(sellerFeeBasisPoints, updateAll);
|
||||
},
|
||||
{
|
||||
onSettled: () =>
|
||||
|
||||
@@ -18,6 +18,7 @@ export * from "./hooks/nft/useRoyaltySettings";
|
||||
export * from "./hooks/nft/useUpdateRoyaltySettings";
|
||||
export * from "./hooks/nft/useCreators";
|
||||
export * from "./hooks/nft/useUpdateCreators";
|
||||
export * from "./hooks/nft/useTotalSupply";
|
||||
|
||||
// NFT Collection
|
||||
export * from "./hooks/nft/collection/useMintNFT";
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,43 @@
|
||||
# @thirdweb-dev/sdk
|
||||
|
||||
## 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
|
||||
|
||||
- [#405](https://github.com/thirdweb-dev/js/pull/405) [`ccb7db4`](https://github.com/thirdweb-dev/js/commit/ccb7db48739b8dddcb2c032b3b6e3e5200485715) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Add updateAll param to creators and royalty update hooks for Solana
|
||||
|
||||
- [#404](https://github.com/thirdweb-dev/js/pull/404) [`9151aa7`](https://github.com/thirdweb-dev/js/commit/9151aa7dc80754a7aa4fd3f6ce0207bccbb6451f) Thanks [@jnsdls](https://github.com/jnsdls)! - [EVM] - expose `getDefaultTrustedForwarders` helper function
|
||||
|
||||
- [#305](https://github.com/thirdweb-dev/js/pull/305) [`5a01c53`](https://github.com/thirdweb-dev/js/commit/5a01c537e30bd7ef562aad1012413990de6faedf) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update to Metaplex 0.17
|
||||
|
||||
## 3.6.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#398](https://github.com/thirdweb-dev/js/pull/398) [`15514f1`](https://github.com/thirdweb-dev/js/commit/15514f190a844d809ae5b1edb580aab46a682485) Thanks [@jnsdls](https://github.com/jnsdls)! - [EVM] - add `defaultValue` to constructorParams schema for releases
|
||||
|
||||
- [#388](https://github.com/thirdweb-dev/js/pull/388) [`7c0744a`](https://github.com/thirdweb-dev/js/commit/7c0744a006e78987ad8b271b2c64f4bac7759510) Thanks [@adam-maj](https://github.com/adam-maj)! - Add support for total supply and pagination on get all
|
||||
|
||||
- [#395](https://github.com/thirdweb-dev/js/pull/395) [`f8bceec`](https://github.com/thirdweb-dev/js/commit/f8bceec10c60b30dc3d6f45926e2a9e018202578) Thanks [@jnsdls](https://github.com/jnsdls)! - [Solana] - fix claim condition reset to native token from custom
|
||||
|
||||
- [#399](https://github.com/thirdweb-dev/js/pull/399) [`887af24`](https://github.com/thirdweb-dev/js/commit/887af24450e316c2ea390cd49c0563cebe793c0f) Thanks [@jnsdls](https://github.com/jnsdls)! - [EVM] - fix custom error handling for null-ish error values
|
||||
|
||||
- [#390](https://github.com/thirdweb-dev/js/pull/390) [`96bd92e`](https://github.com/thirdweb-dev/js/commit/96bd92e724ec7d880d23348071b26ab8c21dd868) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - [SDK] Expose `getAllDetectedFeatures(ABI)` helper function
|
||||
|
||||
## 3.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-sdk-evm-wallets.cjs.js",
|
||||
"module": "dist/thirdweb-dev-sdk-evm-wallets.esm.js"
|
||||
}
|
||||
+25
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/sdk",
|
||||
"version": "3.6.0",
|
||||
"version": "3.6.3",
|
||||
"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"
|
||||
],
|
||||
@@ -57,7 +63,7 @@
|
||||
"test:evm:all": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/evm/.mocharc.json' --timeout 30000 --parallel './test/evm/**/*.test.ts'",
|
||||
"test:evm": "make test-evm",
|
||||
"test:evm:single": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/evm/.mocharc.json' --timeout 30000",
|
||||
"node:solana:start": "DEBUG='amman:(info|error|debug)' amman start",
|
||||
"node:solana:start": "DEBUG='amman:(info|error|debug)' amman start --forceClone",
|
||||
"node:solana:stop": "amman stop",
|
||||
"test:solana:all": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/solana/.mocharc.json' --timeout 30000 --parallel './test/solana/**/*.test.ts'",
|
||||
"test:solana": "make test-sol",
|
||||
@@ -65,18 +71,19 @@
|
||||
"push": "yalc push"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@metaplex-foundation/amman": "0.10.0",
|
||||
"@metaplex-foundation/amman-client": "0.2.1",
|
||||
"@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",
|
||||
"@microsoft/api-extractor": "^7.29.2",
|
||||
"@microsoft/tsdoc": "^0.14.1",
|
||||
"@nomiclabs/hardhat-ethers": "2.0.5",
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.5",
|
||||
"@preconstruct/cli": "^2.2.1",
|
||||
"@swc-node/register": "^1.5.1",
|
||||
"@swc/core": "^1.2.177",
|
||||
"@types/chai": "^4.3.3",
|
||||
"@types/deep-equal-in-any-order": "^1.0.1",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/uuid": "^8.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
@@ -89,21 +96,27 @@
|
||||
"eslint-plugin-inclusive-language": "^2.2.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-tsdoc": "^0.2.16",
|
||||
"ethers": "^5.7.0",
|
||||
"ethers": "^5.7.2",
|
||||
"hardhat": "^2.9.3",
|
||||
"mocha": "^10.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.215.0",
|
||||
"ethers": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@aws-sdk/client-secrets-manager": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@metaplex-foundation/js": "0.15.0",
|
||||
"@metaplex-foundation/mpl-token-metadata": "2.2.2",
|
||||
"@project-serum/anchor": "0.25.0",
|
||||
"@solana/spl-token": "0.2.0",
|
||||
"@solana/web3.js": "1.62.0",
|
||||
"@metaplex-foundation/js": "^0.17.6",
|
||||
"@metaplex-foundation/mpl-token-metadata": "^2.3.3",
|
||||
"@project-serum/anchor": "^0.25.0",
|
||||
"@solana/spl-token": "^0.3.5",
|
||||
"@solana/web3.js": "^1.62.0",
|
||||
"@thirdweb-dev/contracts-js": "*",
|
||||
"@thirdweb-dev/storage": "*",
|
||||
"bn.js": "^5.2.1",
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const DEFAULT_QUERY_ALL_COUNT = 100;
|
||||
|
||||
export const QueryAllParamsSchema = z
|
||||
.object({
|
||||
start: z.number().default(0),
|
||||
count: z.number().default(DEFAULT_QUERY_ALL_COUNT),
|
||||
})
|
||||
.default({
|
||||
start: 0,
|
||||
count: DEFAULT_QUERY_ALL_COUNT,
|
||||
});
|
||||
|
||||
/**
|
||||
* Pagination Parameters
|
||||
* @public
|
||||
*/
|
||||
export interface QueryAllParams {
|
||||
/**
|
||||
* the index to start from (default: 0)
|
||||
*/
|
||||
start?: number;
|
||||
/**
|
||||
* how many items to return (default: 100)
|
||||
*/
|
||||
count?: number;
|
||||
}
|
||||
export type QueryAllParams = Exclude<
|
||||
z.input<typeof QueryAllParamsSchema>,
|
||||
undefined
|
||||
>;
|
||||
|
||||
@@ -394,20 +394,14 @@ 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);
|
||||
const reason = parseMessageParts(/.*?"message[^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);
|
||||
@@ -459,9 +453,12 @@ function parseMessageParts(regex: RegExp, raw: string): string {
|
||||
* @param message
|
||||
*/
|
||||
export function includesErrorMessage(err: any, message: string): boolean {
|
||||
if (!err) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
(err && err.toString().includes(message)) ||
|
||||
(err.message && err.message.toString().includes(message)) ||
|
||||
(err.error && err.error.toString().includes(message))
|
||||
(err && err.message && err.message.toString().includes(message)) ||
|
||||
(err && err.error && err.error.toString().includes(message))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -438,8 +438,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 +458,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",
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -525,7 +525,7 @@ export async function fetchExtendedReleaseMetadata(
|
||||
* @returns the nested struct of all features and whether they're detected in the abi
|
||||
*/
|
||||
export function detectFeatures(
|
||||
abi: z.input<typeof AbiSchema>,
|
||||
abi: Abi,
|
||||
features: Record<string, Feature> = SUPPORTED_FEATURES,
|
||||
): Record<string, FeatureWithEnabled> {
|
||||
const results: Record<string, FeatureWithEnabled> = {};
|
||||
@@ -542,6 +542,48 @@ export function detectFeatures(
|
||||
return results;
|
||||
}
|
||||
|
||||
function extractFeatures(
|
||||
input: Record<string, FeatureWithEnabled>,
|
||||
enabledExtensions: FeatureWithEnabled[],
|
||||
) {
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
for (const extensionKey in input) {
|
||||
const extension = input[extensionKey];
|
||||
// if extension is enabled, then add it to enabledFeatures
|
||||
if (extension.enabled) {
|
||||
enabledExtensions.push(extension);
|
||||
}
|
||||
// recurse
|
||||
extractFeatures(extension.features, enabledExtensions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the detected features in the abi
|
||||
* @param abi - parsed array of abi entries
|
||||
* @returns array of all detected extensions with full information on each feature
|
||||
* @public
|
||||
*/
|
||||
export function getAllDetectedFeatures(abi: Abi): FeatureWithEnabled[] {
|
||||
const features: FeatureWithEnabled[] = [];
|
||||
extractFeatures(detectFeatures(abi), features);
|
||||
return features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the detected features names in the abi
|
||||
* @param abi - parsed array of abi entries
|
||||
* @returns array of all detected features names
|
||||
* @public
|
||||
*/
|
||||
export function getAllDetectedFeatureNames(abi: Abi): string[] {
|
||||
const features: FeatureWithEnabled[] = [];
|
||||
extractFeatures(detectFeatures(abi), features);
|
||||
return features.map((f) => f.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given ABI supports a given feature
|
||||
* @internal
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PrebuiltContractType } from "../core/types";
|
||||
import { ChainId, SUPPORTED_CHAIN_ID } from "./chains";
|
||||
import { ChainId, SUPPORTED_CHAIN_ID, SUPPORTED_CHAIN_IDS } from "./chains";
|
||||
import { constants } from "ethers";
|
||||
|
||||
/**
|
||||
@@ -290,3 +290,24 @@ export function getContractPublisherAddress() {
|
||||
return ContractPublisher_address;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param chainId - chain id
|
||||
* @returns the array of trusted forwarders for the given chain id
|
||||
* @internal
|
||||
*/
|
||||
export function getDefaultTrustedForwarders(
|
||||
chainId: SUPPORTED_CHAIN_ID,
|
||||
): string[] {
|
||||
const chainEnum = SUPPORTED_CHAIN_IDS.find((c) => c === chainId);
|
||||
const biconomyForwarder = chainEnum
|
||||
? CONTRACT_ADDRESSES[chainEnum].biconomyForwarder
|
||||
: constants.AddressZero;
|
||||
const openzeppelinForwarder = chainEnum
|
||||
? CONTRACT_ADDRESSES[chainEnum].openzeppelinForwarder
|
||||
: constants.AddressZero;
|
||||
return biconomyForwarder !== constants.AddressZero
|
||||
? [openzeppelinForwarder, biconomyForwarder]
|
||||
: [openzeppelinForwarder];
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
CONTRACT_ADDRESSES,
|
||||
getApprovedImplementation,
|
||||
SUPPORTED_CHAIN_IDS,
|
||||
getDefaultTrustedForwarders,
|
||||
} from "../../constants";
|
||||
import {
|
||||
EditionDropInitializer,
|
||||
@@ -296,16 +295,7 @@ export class ContractFactory extends ContractWrapper<TWFactory> {
|
||||
|
||||
private async getDefaultTrustedForwarders(): Promise<string[]> {
|
||||
const chainId = await this.getChainID();
|
||||
const chainEnum = SUPPORTED_CHAIN_IDS.find((c) => c === chainId);
|
||||
const biconomyForwarder = chainEnum
|
||||
? CONTRACT_ADDRESSES[chainEnum].biconomyForwarder
|
||||
: constants.AddressZero;
|
||||
const openzeppelinForwarder = chainEnum
|
||||
? CONTRACT_ADDRESSES[chainEnum].openzeppelinForwarder
|
||||
: constants.AddressZero;
|
||||
return biconomyForwarder !== constants.AddressZero
|
||||
? [openzeppelinForwarder, biconomyForwarder]
|
||||
: [openzeppelinForwarder];
|
||||
return getDefaultTrustedForwarders(chainId);
|
||||
}
|
||||
|
||||
private async getImplementation(
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -162,6 +162,7 @@ export const ExtraPublishMetadataSchemaInput = z
|
||||
.object({
|
||||
displayName: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
defaultValue: z.string().optional(),
|
||||
})
|
||||
.catchall(z.any()),
|
||||
)
|
||||
|
||||
@@ -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,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 @@
|
||||
export * from "./aws";
|
||||
@@ -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";
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { toCurrencyValue } from "../utils/token";
|
||||
import {
|
||||
Amount,
|
||||
CandyMachineEndSettings,
|
||||
CandyMachineV2EndSettings,
|
||||
DateTime,
|
||||
Metaplex,
|
||||
sol,
|
||||
@@ -168,8 +168,7 @@ export class ClaimConditions {
|
||||
if (parsed.currencyAddress && parsed.price) {
|
||||
const fetchedToken = await this.metaplex
|
||||
.tokens()
|
||||
.findMintByAddress({ address: new PublicKey(parsed.currencyAddress) })
|
||||
.run();
|
||||
.findMintByAddress({ address: new PublicKey(parsed.currencyAddress) });
|
||||
price = token(Number(parsed.price), fetchedToken.decimals);
|
||||
tokenMint = fetchedToken.address;
|
||||
}
|
||||
@@ -180,7 +179,7 @@ export class ClaimConditions {
|
||||
: undefined;
|
||||
|
||||
// max claimable
|
||||
const endSettings: CandyMachineEndSettings | null | undefined =
|
||||
const endSettings: CandyMachineV2EndSettings | null | undefined =
|
||||
parsed.maxClaimable
|
||||
? parsed.maxClaimable === "unlimited"
|
||||
? null
|
||||
@@ -194,20 +193,22 @@ export class ClaimConditions {
|
||||
|
||||
const data = {
|
||||
...(wallet && { wallet }),
|
||||
...(tokenMint && { tokenMint }),
|
||||
...(tokenMint
|
||||
? { tokenMint }
|
||||
: // if passing currencyAddress explcitly as null we need to honor that!
|
||||
parsed.currencyAddress === null
|
||||
? { tokenMint: null }
|
||||
: {}),
|
||||
...(price && { price }),
|
||||
...(goLiveDate && { goLiveDate }),
|
||||
...(sellerFeeBasisPoints && { sellerFeeBasisPoints }),
|
||||
...(endSettings !== undefined && { endSettings }),
|
||||
};
|
||||
|
||||
const result = await this.metaplex
|
||||
.candyMachines()
|
||||
.update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
...data,
|
||||
})
|
||||
.run();
|
||||
const result = await this.metaplex.candyMachinesV2().update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
...data,
|
||||
});
|
||||
|
||||
return {
|
||||
signature: result.response.signature,
|
||||
@@ -216,8 +217,7 @@ export class ClaimConditions {
|
||||
|
||||
private async getCandyMachine() {
|
||||
return this.metaplex
|
||||
.candyMachines()
|
||||
.findByAddress({ address: this.dropMintAddress })
|
||||
.run();
|
||||
.candyMachinesV2()
|
||||
.findByAddress({ address: this.dropMintAddress });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ export class Deployer {
|
||||
// initialize candy machine with default config
|
||||
// final claim conditions can be updated later
|
||||
const candyMachineTx = await this.metaplex
|
||||
.candyMachines()
|
||||
.candyMachinesV2()
|
||||
.builders()
|
||||
.create({
|
||||
itemsAvailable: toBigNumber(candyMachineInfo.totalSupply),
|
||||
@@ -264,23 +264,20 @@ export class Deployer {
|
||||
const block = await this.metaplex.connection.getLatestBlockhash();
|
||||
const dropTx = collectionTx
|
||||
.add(candyMachineTx)
|
||||
.setFeePayer(this.metaplex.identity())
|
||||
.setTransactionOptions({
|
||||
blockhash: block.blockhash,
|
||||
feePayer: this.metaplex.identity().publicKey,
|
||||
lastValidBlockHeight: block.lastValidBlockHeight,
|
||||
});
|
||||
const regTx = TransactionBuilder.make()
|
||||
.setFeePayer(this.metaplex.identity());
|
||||
const regTransaction = TransactionBuilder.make()
|
||||
.add(...registryInstructions)
|
||||
.setFeePayer(this.metaplex.identity())
|
||||
.setTransactionOptions({
|
||||
.toTransaction({
|
||||
blockhash: block.blockhash,
|
||||
feePayer: this.metaplex.identity().publicKey,
|
||||
lastValidBlockHeight: block.lastValidBlockHeight,
|
||||
});
|
||||
const dropSigners = [this.metaplex.identity(), ...dropTx.getSigners()];
|
||||
const { keypairs } = getSignerHistogram(dropSigners);
|
||||
const dropTransaction = dropTx.toTransaction();
|
||||
const dropTransaction = dropTx.toTransaction({
|
||||
blockhash: block.blockhash,
|
||||
lastValidBlockHeight: block.lastValidBlockHeight,
|
||||
});
|
||||
// partially sign with the PDA keypairs
|
||||
if (keypairs.length > 0) {
|
||||
dropTransaction.partialSign(...keypairs);
|
||||
@@ -288,7 +285,7 @@ export class Deployer {
|
||||
// make the connected wallet sign both candyMachine + registry transactions
|
||||
const signedTx = await this.metaplex
|
||||
.identity()
|
||||
.signAllTransactions([dropTransaction, regTx.toTransaction()]);
|
||||
.signAllTransactions([dropTransaction, regTransaction]);
|
||||
|
||||
// send the signed transactions *sequentially* the drop creation needs to succeed first before adding to registry
|
||||
const signatures: string[] = [];
|
||||
@@ -301,7 +298,10 @@ export class Deployer {
|
||||
// wait for confirmations in parallel
|
||||
const confirmations = await Promise.all(
|
||||
signatures.map((sig) => {
|
||||
return this.metaplex.rpc().confirmTransaction(sig);
|
||||
return this.metaplex.rpc().confirmTransaction(sig, {
|
||||
blockhash: block.blockhash,
|
||||
lastValidBlockHeight: block.lastValidBlockHeight,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
QueryAllParams,
|
||||
DEFAULT_QUERY_ALL_COUNT,
|
||||
QueryAllParamsSchema,
|
||||
} from "../../../core/schema/QueryParams";
|
||||
import { NFT } from "../../../core/schema/nft";
|
||||
import {
|
||||
@@ -49,12 +49,9 @@ export class NFTHelper {
|
||||
}
|
||||
|
||||
async getRaw(nftAddress: string) {
|
||||
return await this.metaplex
|
||||
.nfts()
|
||||
.findByMint({
|
||||
mintAddress: new PublicKey(nftAddress),
|
||||
})
|
||||
.run();
|
||||
return await this.metaplex.nfts().findByMint({
|
||||
mintAddress: new PublicKey(nftAddress),
|
||||
});
|
||||
}
|
||||
|
||||
async get(nftAddress: string): Promise<NFT> {
|
||||
@@ -67,14 +64,11 @@ export class NFTHelper {
|
||||
nftAddress: string,
|
||||
quantity: number = 1,
|
||||
): Promise<TransactionResult> {
|
||||
const result = await this.metaplex
|
||||
.nfts()
|
||||
.send({
|
||||
mintAddress: new PublicKey(nftAddress),
|
||||
toOwner: new PublicKey(receiverAddress),
|
||||
amount: token(quantity, 0),
|
||||
})
|
||||
.run();
|
||||
const result = await this.metaplex.nfts().send({
|
||||
mintAddress: new PublicKey(nftAddress),
|
||||
toOwner: new PublicKey(receiverAddress),
|
||||
amount: token(quantity, 0),
|
||||
});
|
||||
|
||||
return {
|
||||
signature: result.response.signature,
|
||||
@@ -121,13 +115,73 @@ export class NFTHelper {
|
||||
}
|
||||
}
|
||||
|
||||
async supplyOf(nftAddress: string): Promise<number> {
|
||||
let originalEdition;
|
||||
|
||||
const originalEditionAccount = await this.metaplex
|
||||
.rpc()
|
||||
.getAccount(findMasterEditionV2Pda(new PublicKey(nftAddress)));
|
||||
|
||||
if (originalEditionAccount.exists) {
|
||||
originalEdition = toNftOriginalEdition(
|
||||
toOriginalEditionAccount(originalEditionAccount),
|
||||
);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Add one to supply to account for the master edition
|
||||
return originalEdition.supply.toNumber() + 1;
|
||||
}
|
||||
|
||||
async totalSupply(collectionAddress: string): Promise<number> {
|
||||
const metadataAddresses = await this.getAllMetadataAddresses(
|
||||
collectionAddress,
|
||||
);
|
||||
return metadataAddresses.length;
|
||||
}
|
||||
|
||||
async getAll(
|
||||
collectionAddress: string,
|
||||
queryParams?: QueryAllParams,
|
||||
): Promise<NFT[]> {
|
||||
const { start, count } = QueryAllParamsSchema.parse(queryParams);
|
||||
|
||||
const metadataAddresses = await this.getAllMetadataAddresses(
|
||||
collectionAddress,
|
||||
);
|
||||
|
||||
// Metaplex GmaBuilder has a weird thing where they always start at 1 not 0
|
||||
// so we workaround it by adding an extra address, and shifting the count to get the actual count we want
|
||||
const fixedMetadataAddresses = (
|
||||
start === 0 ? [PublicKey.default] : []
|
||||
).concat(metadataAddresses);
|
||||
const metadataInfos = await GmaBuilder.make(
|
||||
this.metaplex,
|
||||
fixedMetadataAddresses,
|
||||
).getBetween(start, start === 0 ? count + 1 : start + count);
|
||||
|
||||
// Parse each account into a metadata account
|
||||
const metadataParsed: Metadata<JsonMetadata<string>>[] = [];
|
||||
for (const metadataInfo of metadataInfos) {
|
||||
if (metadataInfo.exists) {
|
||||
try {
|
||||
metadataParsed.push(toMetadata(toMetadataAccount(metadataInfo)));
|
||||
} catch (error) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, fetch the metadata + mint for each in parallel
|
||||
const nfts = await Promise.all(
|
||||
metadataParsed.map((m) => this.toNFTMetadata(m)),
|
||||
);
|
||||
return nfts;
|
||||
}
|
||||
|
||||
private async getAllMetadataAddresses(collectionAddress: string) {
|
||||
const collectionKey = new PublicKey(collectionAddress);
|
||||
const start = queryParams?.start || 0;
|
||||
const count = queryParams?.count || DEFAULT_QUERY_ALL_COUNT;
|
||||
|
||||
// TODO cache signatures <> transactions mapping in memory so pagination doesn't re-request this everytime
|
||||
const allSignatures: ConfirmedSignatureInfo[] = [];
|
||||
@@ -205,52 +259,7 @@ export class NFTHelper {
|
||||
}
|
||||
}
|
||||
|
||||
// Metaplex GmaBuilder has a weird thing where they always start at 1 not 0
|
||||
// so we workaround it by adding an extra address, and shifting the count to get the actual count we want
|
||||
const fixedMetadataAddresses = (
|
||||
start === 0 ? [PublicKey.default] : []
|
||||
).concat(metadataAddresses);
|
||||
const metadataInfos = await GmaBuilder.make(
|
||||
this.metaplex,
|
||||
fixedMetadataAddresses,
|
||||
).getBetween(start, start === 0 ? count + 1 : start + count);
|
||||
|
||||
// parse each account into a metadata account
|
||||
const metadataParsed: Metadata<JsonMetadata<string>>[] = [];
|
||||
for (const metadataInfo of metadataInfos) {
|
||||
if (metadataInfo.exists) {
|
||||
try {
|
||||
metadataParsed.push(toMetadata(toMetadataAccount(metadataInfo)));
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finally fetch the metadta + mint for each in parallel
|
||||
const nfts = await Promise.all(
|
||||
metadataParsed.map((m) => this.toNFTMetadata(m)),
|
||||
);
|
||||
return nfts;
|
||||
}
|
||||
|
||||
async supplyOf(nftAddress: string): Promise<number> {
|
||||
let originalEdition;
|
||||
|
||||
const originalEditionAccount = await this.metaplex
|
||||
.rpc()
|
||||
.getAccount(findMasterEditionV2Pda(new PublicKey(nftAddress)));
|
||||
|
||||
if (originalEditionAccount.exists) {
|
||||
originalEdition = toNftOriginalEdition(
|
||||
toOriginalEditionAccount(originalEditionAccount),
|
||||
);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Add one to supply to account for the master edition
|
||||
return originalEdition.supply.toNumber() + 1;
|
||||
return metadataAddresses;
|
||||
}
|
||||
|
||||
async toNFTMetadata(
|
||||
|
||||
@@ -6,12 +6,12 @@ import { WalletAccount } from "../types/common";
|
||||
import { RegisteredProgram } from "../types/programs";
|
||||
import { UserWallet } from "./user-wallet";
|
||||
import {
|
||||
CandyMachine,
|
||||
CandyMachineV2,
|
||||
InstructionWithSigners,
|
||||
Metadata,
|
||||
Metaplex,
|
||||
Pda,
|
||||
TokenProgram,
|
||||
TokenGpaBuilder,
|
||||
} from "@metaplex-foundation/js";
|
||||
import { TokenStandard } from "@metaplex-foundation/mpl-token-metadata";
|
||||
import type { BN, Idl } from "@project-serum/anchor";
|
||||
@@ -38,9 +38,8 @@ export class Registry {
|
||||
public async getProgramType(address: string) {
|
||||
try {
|
||||
const candyMachine = await this.metaplex
|
||||
.candyMachines()
|
||||
.findByAddress({ address: new PublicKey(address) })
|
||||
.run();
|
||||
.candyMachinesV2()
|
||||
.findByAddress({ address: new PublicKey(address) });
|
||||
if (candyMachine) {
|
||||
return "nft-drop";
|
||||
}
|
||||
@@ -49,8 +48,7 @@ export class Registry {
|
||||
}
|
||||
const metadata = await this.metaplex
|
||||
.nfts()
|
||||
.findByMint({ mintAddress: new PublicKey(address) })
|
||||
.run();
|
||||
.findByMint({ mintAddress: new PublicKey(address) });
|
||||
|
||||
if (metadata) {
|
||||
if (metadata.collectionDetails) {
|
||||
@@ -198,7 +196,10 @@ export class Registry {
|
||||
return account.exists;
|
||||
}
|
||||
|
||||
private getDropForCollection(candyMachines: CandyMachine[], meta: Metadata) {
|
||||
private getDropForCollection(
|
||||
candyMachines: CandyMachineV2[],
|
||||
meta: Metadata,
|
||||
) {
|
||||
return candyMachines.find(
|
||||
(candyMachine) =>
|
||||
candyMachine.collectionMintAddress?.toBase58() ===
|
||||
@@ -211,18 +212,12 @@ export class Registry {
|
||||
walletAddress: string,
|
||||
): Promise<WalletAccount[]> {
|
||||
const mints = await this.getOwnedTokenAddreses(walletAddress);
|
||||
const metadatas = await this.metaplex
|
||||
.nfts()
|
||||
.findAllByMintList({ mints })
|
||||
.run();
|
||||
const metadatas = await this.metaplex.nfts().findAllByMintList({ mints });
|
||||
|
||||
const candyMachines = await this.metaplex
|
||||
.candyMachines()
|
||||
.findAllBy({
|
||||
type: "authority",
|
||||
publicKey: new PublicKey(walletAddress),
|
||||
})
|
||||
.run();
|
||||
const candyMachines = await this.metaplex.candyMachinesV2().findAllBy({
|
||||
type: "authority",
|
||||
publicKey: new PublicKey(walletAddress),
|
||||
});
|
||||
|
||||
return metadatas
|
||||
.map((mintMetadata) => {
|
||||
@@ -260,7 +255,7 @@ export class Registry {
|
||||
}
|
||||
|
||||
private async getOwnedTokenAddreses(walletAddress: string) {
|
||||
return await TokenProgram.tokenAccounts(this.metaplex)
|
||||
return await new TokenGpaBuilder(this.metaplex)
|
||||
.selectMint()
|
||||
.whereOwner(new PublicKey(walletAddress))
|
||||
.getDataAsPublicKeys();
|
||||
|
||||
@@ -140,6 +140,18 @@ export class NFTCollection {
|
||||
return this.nft.getAll(this.publicKey.toBase58(), queryParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of nfts minted on this program
|
||||
* @returns the total number of NFTs minted on this program
|
||||
*
|
||||
* ```jsx
|
||||
* const totalSupply = await program.totalSupply();
|
||||
* ```
|
||||
*/
|
||||
async totalSupply(): Promise<number> {
|
||||
return this.nft.totalSupply(this.publicKey.toBase58());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the NFT balance of the connected wallet
|
||||
* @returns the NFT balance
|
||||
@@ -297,20 +309,17 @@ export class NFTCollection {
|
||||
const name =
|
||||
typeof metadata === "string" ? "" : metadata.name?.toString() || "";
|
||||
const collection = await this.getCollection();
|
||||
const { nft } = await this.metaplex
|
||||
.nfts()
|
||||
.create({
|
||||
name,
|
||||
uri,
|
||||
creators: collection.creators,
|
||||
sellerFeeBasisPoints: collection.sellerFeeBasisPoints,
|
||||
collection: this.publicKey,
|
||||
collectionAuthority: this.metaplex.identity(),
|
||||
tokenOwner: new PublicKey(to),
|
||||
// Always sets max supply to unlimited so editions can be minted
|
||||
maxSupply: null,
|
||||
})
|
||||
.run();
|
||||
const { nft } = await this.metaplex.nfts().create({
|
||||
name,
|
||||
uri,
|
||||
creators: collection.creators,
|
||||
sellerFeeBasisPoints: collection.sellerFeeBasisPoints,
|
||||
collection: this.publicKey,
|
||||
collectionAuthority: this.metaplex.identity(),
|
||||
tokenOwner: new PublicKey(to),
|
||||
// Always sets max supply to unlimited so editions can be minted
|
||||
maxSupply: null,
|
||||
});
|
||||
|
||||
return nft.address.toBase58();
|
||||
}
|
||||
@@ -411,13 +420,11 @@ export class NFTCollection {
|
||||
* ```
|
||||
*/
|
||||
async burn(nftAddress: string): Promise<TransactionResult> {
|
||||
const tx = await this.metaplex
|
||||
.nfts()
|
||||
.delete({
|
||||
mintAddress: new PublicKey(nftAddress),
|
||||
collection: this.publicKey,
|
||||
})
|
||||
.run();
|
||||
const tx = await this.metaplex.nfts().delete({
|
||||
mintAddress: new PublicKey(nftAddress),
|
||||
collection: this.publicKey,
|
||||
});
|
||||
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
@@ -458,8 +465,7 @@ export class NFTCollection {
|
||||
allNfts.map(async (nft) => {
|
||||
const metaplexNft = await this.metaplex
|
||||
.nfts()
|
||||
.findByMint({ mintAddress: new PublicKey(nft.metadata.id) })
|
||||
.run();
|
||||
.findByMint({ mintAddress: new PublicKey(nft.metadata.id) });
|
||||
|
||||
txs.push(
|
||||
this.metaplex
|
||||
@@ -479,16 +485,10 @@ export class NFTCollection {
|
||||
|
||||
return await sendMultipartTransaction(txs, this.metaplex);
|
||||
} else {
|
||||
const tx = await this.metaplex
|
||||
.nfts()
|
||||
.update({
|
||||
nftOrSft: await this.getCollection(),
|
||||
creators: enforceCreator(
|
||||
creators,
|
||||
this.metaplex.identity().publicKey,
|
||||
),
|
||||
})
|
||||
.run();
|
||||
const tx = await this.metaplex.nfts().update({
|
||||
nftOrSft: await this.getCollection(),
|
||||
creators: enforceCreator(creators, this.metaplex.identity().publicKey),
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -526,8 +526,7 @@ export class NFTCollection {
|
||||
allNfts.map(async (nft) => {
|
||||
const metaplexNft = await this.metaplex
|
||||
.nfts()
|
||||
.findByMint({ mintAddress: new PublicKey(nft.metadata.id) })
|
||||
.run();
|
||||
.findByMint({ mintAddress: new PublicKey(nft.metadata.id) });
|
||||
|
||||
txs.push(
|
||||
this.metaplex.nfts().builders().update({
|
||||
@@ -541,13 +540,10 @@ export class NFTCollection {
|
||||
|
||||
return await sendMultipartTransaction(txs, this.metaplex);
|
||||
} else {
|
||||
const tx = await this.metaplex
|
||||
.nfts()
|
||||
.update({
|
||||
nftOrSft: await this.getCollection(),
|
||||
sellerFeeBasisPoints,
|
||||
})
|
||||
.run();
|
||||
const tx = await this.metaplex.nfts().update({
|
||||
nftOrSft: await this.getCollection(),
|
||||
sellerFeeBasisPoints,
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -560,7 +556,6 @@ export class NFTCollection {
|
||||
private async getCollection() {
|
||||
return await this.metaplex
|
||||
.nfts()
|
||||
.findByMint({ mintAddress: this.publicKey })
|
||||
.run();
|
||||
.findByMint({ mintAddress: this.publicKey });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
QueryAllParams,
|
||||
QueryAllParamsSchema,
|
||||
} from "../../core/schema/QueryParams";
|
||||
import {
|
||||
CommonNFTInput,
|
||||
NFT,
|
||||
@@ -16,7 +20,7 @@ import { CreatorInput, CreatorOutput } from "../types/programs";
|
||||
import { sendMultipartTransaction } from "../utils/transactions";
|
||||
import { getNework } from "../utils/urls";
|
||||
import {
|
||||
CandyMachineItem,
|
||||
CandyMachineV2Item,
|
||||
Metaplex,
|
||||
toBigNumber,
|
||||
} from "@metaplex-foundation/js";
|
||||
@@ -99,8 +103,7 @@ export class NFTDrop {
|
||||
invariant(info.collectionMintAddress, "Collection mint address not found");
|
||||
const metadata = await this.metaplex
|
||||
.nfts()
|
||||
.findByMint({ mintAddress: info.collectionMintAddress })
|
||||
.run();
|
||||
.findByMint({ mintAddress: info.collectionMintAddress });
|
||||
|
||||
return (await this.nft.toNFTMetadata(metadata)).metadata;
|
||||
}
|
||||
@@ -166,22 +169,37 @@ export class NFTDrop {
|
||||
* console.log(nfts[0].metadata.name);
|
||||
* ```
|
||||
*/
|
||||
async getAll(): Promise<NFT[]> {
|
||||
// TODO: Add pagination to get NFT functions
|
||||
async getAll(queryParams?: QueryAllParams): Promise<NFT[]> {
|
||||
const parsedQueryParams = QueryAllParamsSchema.parse(queryParams);
|
||||
const info = await this.getCandyMachine();
|
||||
const claimed = await this.getAllClaimed();
|
||||
return await Promise.all(
|
||||
info.items.map(async (item) => {
|
||||
// Check if the NFT has been claimed
|
||||
// TODO: This only checks name/uri which is potentially not unique
|
||||
const claimedNFT = claimed.find(
|
||||
|
||||
// First, get all the claimed NFTs within the query params range
|
||||
const claimedNfts = await this.getAllClaimed(parsedQueryParams);
|
||||
const totalClaimed = await this.totalClaimedSupply();
|
||||
|
||||
// Then filter out all claimed NFTs from items to leave only unclaimed remaining
|
||||
const unclaimedItems: CandyMachineV2Item[] = [];
|
||||
info.items.forEach((item) => {
|
||||
const isClaimed =
|
||||
claimedNfts.filter(
|
||||
(nft) =>
|
||||
nft.metadata.name === item.name && nft.metadata.uri === item.uri,
|
||||
);
|
||||
if (claimedNFT) {
|
||||
return claimedNFT;
|
||||
}
|
||||
// not claimed yet, return a unclaimed NFT with just the metadata
|
||||
).length > 0;
|
||||
|
||||
if (!isClaimed) {
|
||||
unclaimedItems.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
// Only fill the remaining count left over after claimed NFTs with unclaimed NFTs
|
||||
const startIndex = Math.max(0, parsedQueryParams.start - totalClaimed);
|
||||
const endIndex = Math.max(
|
||||
0,
|
||||
startIndex + parsedQueryParams.count - claimedNfts.length,
|
||||
);
|
||||
|
||||
const unclaimedNfts = await Promise.all(
|
||||
unclaimedItems.slice(startIndex, endIndex).map(async (item) => {
|
||||
const metadata: NFTMetadata = await this.storage.downloadJSON(item.uri);
|
||||
return {
|
||||
metadata: {
|
||||
@@ -195,6 +213,9 @@ export class NFTDrop {
|
||||
} as NFT;
|
||||
}),
|
||||
);
|
||||
|
||||
// Always return claimed NFTs first, and then fill remaining query count with unclaimed NFTs
|
||||
return [...claimedNfts, ...unclaimedNfts];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,11 +229,14 @@ export class NFTDrop {
|
||||
* console.log(nfts[0].name)
|
||||
* ```
|
||||
*/
|
||||
async getAllClaimed(): Promise<NFT[]> {
|
||||
async getAllClaimed(queryParams?: QueryAllParams): Promise<NFT[]> {
|
||||
// using getAll from collection here because candy machin findAllMinted doesn't return anything
|
||||
const candy = await this.getCandyMachine();
|
||||
invariant(candy.collectionMintAddress, "Collection mint address not found");
|
||||
return await this.nft.getAll(candy.collectionMintAddress.toBase58());
|
||||
return await this.nft.getAll(
|
||||
candy.collectionMintAddress.toBase58(),
|
||||
queryParams,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,6 +292,21 @@ export class NFTDrop {
|
||||
return this.nft.ownerOf(nftAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total minted supply of this drop
|
||||
* @returns the total supply
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* // Get the total number of NFTs that have been minted on this drop
|
||||
* const supply = await program.totalSupply();
|
||||
* ```
|
||||
*/
|
||||
async totalSupply(): Promise<number> {
|
||||
const info = await this.getCandyMachine();
|
||||
return info.itemsLoaded.toNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total unclaimed supply of this drop
|
||||
* @returns the total supply
|
||||
@@ -361,20 +400,20 @@ export class NFTDrop {
|
||||
CommonNFTInput.parse(metadata),
|
||||
);
|
||||
const uris = await this.storage.uploadBatch(parsedMetadatas, options);
|
||||
const items: CandyMachineItem[] = uris.map((uri, i) => ({
|
||||
const items: CandyMachineV2Item[] = uris.map((uri, i) => ({
|
||||
name: parsedMetadatas[i].name?.toString() || "",
|
||||
uri,
|
||||
}));
|
||||
|
||||
// turn items into batches of $LAZY_MINT_BATCH_SIZE
|
||||
const batches: CandyMachineItem[][] = [];
|
||||
const batches: CandyMachineV2Item[][] = [];
|
||||
while (items.length) {
|
||||
batches.push(items.splice(0, LAZY_MINT_BATCH_SIZE));
|
||||
}
|
||||
|
||||
const builders = batches.map((batch, i) =>
|
||||
this.metaplex
|
||||
.candyMachines()
|
||||
.candyMachinesV2()
|
||||
.builders()
|
||||
.insertItems({
|
||||
candyMachine,
|
||||
@@ -425,7 +464,7 @@ export class NFTDrop {
|
||||
const builders = await Promise.all(
|
||||
[...Array(amount).keys()].map(async () => {
|
||||
return await this.metaplex
|
||||
.candyMachines()
|
||||
.candyMachinesV2()
|
||||
.builders()
|
||||
.mint({
|
||||
candyMachine,
|
||||
@@ -458,13 +497,10 @@ export class NFTDrop {
|
||||
const collection = candyMachine.collectionMintAddress
|
||||
? candyMachine.collectionMintAddress
|
||||
: undefined;
|
||||
const tx = await this.metaplex
|
||||
.nfts()
|
||||
.delete({
|
||||
mintAddress: new PublicKey(nftAddress),
|
||||
collection,
|
||||
})
|
||||
.run();
|
||||
const tx = await this.metaplex.nfts().delete({
|
||||
mintAddress: new PublicKey(nftAddress),
|
||||
collection,
|
||||
});
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
@@ -475,13 +511,11 @@ export class NFTDrop {
|
||||
* @param creators - the creators to update
|
||||
*/
|
||||
async updateCreators(creators: CreatorInput[]) {
|
||||
const tx = await this.metaplex
|
||||
.candyMachines()
|
||||
.update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
creators: enforceCreator(creators, this.metaplex.identity().publicKey),
|
||||
})
|
||||
.run();
|
||||
const tx = await this.metaplex.candyMachinesV2().update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
creators: enforceCreator(creators, this.metaplex.identity().publicKey),
|
||||
});
|
||||
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
@@ -492,13 +526,11 @@ export class NFTDrop {
|
||||
* @param sellerFeeBasisPoints - the royalty basis points of the collection
|
||||
*/
|
||||
async updateRoyalty(sellerFeeBasisPoints: number) {
|
||||
const tx = await this.metaplex
|
||||
.candyMachines()
|
||||
.update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
sellerFeeBasisPoints,
|
||||
})
|
||||
.run();
|
||||
const tx = await this.metaplex.candyMachinesV2().update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
sellerFeeBasisPoints,
|
||||
});
|
||||
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
@@ -506,8 +538,7 @@ export class NFTDrop {
|
||||
|
||||
private async getCandyMachine() {
|
||||
return this.metaplex
|
||||
.candyMachines()
|
||||
.findByAddress({ address: this.publicKey })
|
||||
.run();
|
||||
.candyMachinesV2()
|
||||
.findByAddress({ address: this.publicKey });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,14 +131,11 @@ export class Token {
|
||||
): Promise<TransactionResult> {
|
||||
const amountParsed = AmountSchema.parse(amount);
|
||||
const info = await this.getMint();
|
||||
const result = await this.metaplex
|
||||
.tokens()
|
||||
.mint({
|
||||
amount: token(amountParsed, info.decimals),
|
||||
mintAddress: this.publicKey,
|
||||
toOwner: new PublicKey(receiverAddress),
|
||||
})
|
||||
.run();
|
||||
const result = await this.metaplex.tokens().mint({
|
||||
amount: token(amountParsed, info.decimals),
|
||||
mintAddress: this.publicKey,
|
||||
toOwner: new PublicKey(receiverAddress),
|
||||
});
|
||||
return {
|
||||
signature: result.response.signature,
|
||||
};
|
||||
@@ -165,14 +162,11 @@ export class Token {
|
||||
amount: Amount,
|
||||
): Promise<TransactionResult> {
|
||||
const info = await this.getMint();
|
||||
const result = await this.metaplex
|
||||
.tokens()
|
||||
.send({
|
||||
mintAddress: this.publicKey,
|
||||
amount: token(amount, info.decimals),
|
||||
toOwner: new PublicKey(receiverAddress),
|
||||
})
|
||||
.run();
|
||||
const result = await this.metaplex.tokens().send({
|
||||
mintAddress: this.publicKey,
|
||||
amount: token(amount, info.decimals),
|
||||
toOwner: new PublicKey(receiverAddress),
|
||||
});
|
||||
return {
|
||||
signature: result.response.signature,
|
||||
};
|
||||
@@ -228,7 +222,6 @@ export class Token {
|
||||
private async getMint() {
|
||||
return await this.metaplex
|
||||
.tokens()
|
||||
.findMintByAddress({ address: this.publicKey })
|
||||
.run(); // TODO abstract types away
|
||||
.findMintByAddress({ address: this.publicKey });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,22 @@ export type TokenMetadataInput = z.input<typeof TokenMetadataInputSchema>;
|
||||
*/
|
||||
export type CreatorInput = z.input<typeof CreatorInputSchema>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type UpdateCreatorInput = {
|
||||
creators: CreatorInput[];
|
||||
updateAll: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type UpdateRoyaltySettingsInput = {
|
||||
sellerFeeBasisPoints: number;
|
||||
updateAll: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
||||
@@ -11,17 +11,14 @@ export async function sendMultipartTransaction(
|
||||
): Promise<TransactionResult[]> {
|
||||
const block = await metaplex.connection.getLatestBlockhash();
|
||||
const txns = builders.map((builder) => {
|
||||
const builderTx = builder
|
||||
.setTransactionOptions({
|
||||
blockhash: block.blockhash,
|
||||
feePayer: metaplex.identity().publicKey,
|
||||
lastValidBlockHeight: block.lastValidBlockHeight,
|
||||
})
|
||||
.setFeePayer(metaplex.identity());
|
||||
const builderTx = builder.setFeePayer(metaplex.identity());
|
||||
|
||||
const dropSigners = [metaplex.identity(), ...builderTx.getSigners()];
|
||||
const { keypairs } = getSignerHistogram(dropSigners);
|
||||
const tx = builderTx.toTransaction();
|
||||
const tx = builderTx.toTransaction({
|
||||
blockhash: block.blockhash,
|
||||
lastValidBlockHeight: block.lastValidBlockHeight,
|
||||
});
|
||||
|
||||
if (keypairs.length > 0) {
|
||||
tx.partialSign(...keypairs);
|
||||
@@ -44,7 +41,10 @@ export async function sendMultipartTransaction(
|
||||
// wait for confirmations in parallel
|
||||
const confirmations = await Promise.all(
|
||||
signatures.map((sig) => {
|
||||
return metaplex.rpc().confirmTransaction(sig);
|
||||
return metaplex.rpc().confirmTransaction(sig, {
|
||||
blockhash: block.blockhash,
|
||||
lastValidBlockHeight: block.lastValidBlockHeight,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ export const mochaHooks = {
|
||||
if (contractType === "custom") {
|
||||
continue;
|
||||
}
|
||||
let factories = [];
|
||||
let factories: any[] = [];
|
||||
switch (contractType) {
|
||||
case TokenInitializer.contractType:
|
||||
factories.push(TokenERC20__factory);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
ChainId,
|
||||
getAllDetectedFeatureNames,
|
||||
isFeatureEnabled,
|
||||
resolveContractUriFromAddress,
|
||||
ThirdwebSDK,
|
||||
@@ -9,10 +10,11 @@ import { AddressZero } from "@ethersproject/constants";
|
||||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
|
||||
import {
|
||||
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";
|
||||
@@ -94,6 +96,17 @@ 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",
|
||||
);
|
||||
});
|
||||
|
||||
it("should update bio", async () => {
|
||||
const address = adminWallet.address;
|
||||
const publisher = sdk.getPublisher();
|
||||
@@ -454,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, [
|
||||
|
||||
@@ -152,4 +152,44 @@ describe("NFTDrop", async () => {
|
||||
royalty = await drop.getRoyalty();
|
||||
expect(royalty).to.equal(100);
|
||||
});
|
||||
|
||||
it("Should get all correctly", async () => {
|
||||
const address = await sdk.deployer.createNftDrop({
|
||||
name: "NFT Drop #1",
|
||||
totalSupply: 5,
|
||||
});
|
||||
const contract = await sdk.getNFTDrop(address);
|
||||
|
||||
await contract.lazyMint([
|
||||
{ name: "NFT #1", description: "This is the #1 NFT" },
|
||||
{ name: "NFT #2", description: "This is the #2 NFT" },
|
||||
{ name: "NFT #3", description: "This is the #3 NFT" },
|
||||
{ name: "NFT #4", description: "This is the #4 NFT" },
|
||||
{ name: "NFT #5", description: "This is the #5 NFT" },
|
||||
]);
|
||||
|
||||
let all = await contract.getAll();
|
||||
expect(all.length).to.equal(5);
|
||||
expect(all[0].metadata.name).to.equal("NFT #1");
|
||||
expect(all.filter((nft) => nft.supply > 0).length).to.equal(0);
|
||||
|
||||
await contract.claimConditions.set({
|
||||
price: 0,
|
||||
maxClaimable: 5,
|
||||
});
|
||||
|
||||
await contract.claim(2);
|
||||
all = await contract.getAll();
|
||||
expect(all.length).to.equal(5);
|
||||
expect(all[0].supply).to.equal(1);
|
||||
expect(all[1].supply).to.equal(1);
|
||||
expect(all[2].supply).to.equal(0);
|
||||
|
||||
all = await contract.getAll({ start: 2, count: 3 });
|
||||
expect(all.length).to.equal(3);
|
||||
expect(all.filter((nft) => nft.supply > 0).length).to.equal(0);
|
||||
|
||||
all = await contract.getAll({ start: 0, count: 3 });
|
||||
expect(all.filter((nft) => nft.supply > 0).length).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"@preconstruct/cli": "^2.2.1",
|
||||
"@swc-node/register": "^1.5.1",
|
||||
"@types/chai": "^4.3.3",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"chai": "^4.3.6",
|
||||
|
||||
@@ -29,5 +29,6 @@
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.27.1",
|
||||
"serve": "^14.0.1"
|
||||
}
|
||||
},
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/test/e2e/cra-5"
|
||||
}
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
"@thirdweb-dev/react": "*",
|
||||
"@thirdweb-dev/sdk": "*",
|
||||
"ethers": "^5.7.2",
|
||||
"next": "^12",
|
||||
"next": "^12.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.27.1",
|
||||
"eslint": "8.25.0",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint-config-next": "12.3.1"
|
||||
}
|
||||
},
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/test/e2e/next-12"
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.27.1",
|
||||
"@vitejs/plugin-react": "^2",
|
||||
"vite": "^3",
|
||||
"serve": "^14.0.1",
|
||||
"@playwright/test": "^1.27.1"
|
||||
}
|
||||
"vite": "^3"
|
||||
},
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/test/e2e/vite-3"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user