Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96b43b8488 | ||
|
|
b4c9515937 | ||
|
|
9b20fc9ad2 | ||
|
|
51bbd3d1bc | ||
|
|
356f7aa643 | ||
|
|
2283e71aca | ||
|
|
90ab9ab031 | ||
|
|
6aee041343 | ||
|
|
21627c03d1 | ||
|
|
ce36322b38 | ||
|
|
564eaccf48 | ||
|
|
aa9e952cb5 | ||
|
|
85a6162137 | ||
|
|
e93aa70ef0 | ||
|
|
6e86123403 | ||
|
|
c60658ed8c |
@@ -1,5 +1,18 @@
|
||||
# @thirdweb-dev/auth
|
||||
|
||||
## 3.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1114](https://github.com/thirdweb-dev/js/pull/1114) [`2283e71a`](https://github.com/thirdweb-dev/js/commit/2283e71acaaf3d15eb2d6121682f1d2a81eec4f3) Thanks [@adam-maj](https://github.com/adam-maj)! - Switch to server-side payload generation
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1111](https://github.com/thirdweb-dev/js/pull/1111) [`21627c03`](https://github.com/thirdweb-dev/js/commit/21627c03d1bb1658fee19b12d580faa6c7f048d9) Thanks [@adam-maj](https://github.com/adam-maj)! - Update expiration dates for auth
|
||||
|
||||
- Updated dependencies [[`9b20fc9a`](https://github.com/thirdweb-dev/js/commit/9b20fc9ad2d303edc31f44fbd2ea3b4dcf35d11e)]:
|
||||
- @thirdweb-dev/wallets@0.2.25
|
||||
|
||||
## 3.0.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "3.0.23",
|
||||
"version": "3.1.0",
|
||||
"main": "dist/thirdweb-dev-auth.cjs.js",
|
||||
"module": "dist/thirdweb-dev-auth.esm.js",
|
||||
"browser": {
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
LoginPayloadDataSchema,
|
||||
AuthenticationPayloadDataSchema,
|
||||
AuthenticationPayloadData,
|
||||
LoginOptionsSchema,
|
||||
VerifyOptionsSchema,
|
||||
VerifyOptions,
|
||||
GenerateOptionsSchema,
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
AuthenticateOptions,
|
||||
User,
|
||||
Json,
|
||||
LoginOptionsSchema,
|
||||
} from "./schema";
|
||||
import { isBrowser } from "./utils";
|
||||
import type { GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
@@ -31,7 +31,7 @@ export class ThirdwebAuth {
|
||||
this.wallet = wallet;
|
||||
}
|
||||
|
||||
public async login(options?: LoginOptions): Promise<LoginPayload> {
|
||||
public async payload(options?: LoginOptions): Promise<LoginPayloadData> {
|
||||
const parsedOptions = LoginOptionsSchema.parse(options);
|
||||
|
||||
let chainId: string | undefined = parsedOptions?.chainId;
|
||||
@@ -43,31 +43,40 @@ export class ThirdwebAuth {
|
||||
}
|
||||
}
|
||||
|
||||
const payloadData = LoginPayloadDataSchema.parse({
|
||||
return LoginPayloadDataSchema.parse({
|
||||
type: this.wallet.type,
|
||||
domain: parsedOptions?.domain || this.domain,
|
||||
address: await this.wallet.getAddress(),
|
||||
address: parsedOptions?.address || (await this.wallet.getAddress()),
|
||||
statement: parsedOptions?.statement,
|
||||
version: parsedOptions?.version,
|
||||
uri: parsedOptions?.uri,
|
||||
chain_id: chainId,
|
||||
nonce: parsedOptions?.nonce,
|
||||
expiration_time:
|
||||
parsedOptions?.expirationTime || new Date(Date.now() + 1000 * 60 * 5),
|
||||
parsedOptions?.expirationTime || new Date(Date.now() + 1000 * 60 * 10),
|
||||
invalid_before:
|
||||
parsedOptions?.invalidBefore || new Date(Date.now() - 1000 * 60 * 5),
|
||||
parsedOptions?.invalidBefore || new Date(Date.now() - 1000 * 60 * 10),
|
||||
resources: parsedOptions?.resources,
|
||||
});
|
||||
}
|
||||
|
||||
const message = this.generateMessage(payloadData);
|
||||
public async loginWithPayload(
|
||||
payload: LoginPayloadData,
|
||||
): Promise<LoginPayload> {
|
||||
const message = this.generateMessage(payload);
|
||||
const signature = await this.wallet.signMessage(message);
|
||||
|
||||
return {
|
||||
payload: payloadData,
|
||||
payload,
|
||||
signature,
|
||||
};
|
||||
}
|
||||
|
||||
public async login(options?: LoginOptions): Promise<LoginPayload> {
|
||||
const payloadData = await this.payload(options);
|
||||
return await this.loginWithPayload(payloadData);
|
||||
}
|
||||
|
||||
public async verify(
|
||||
payload: LoginPayload,
|
||||
options?: VerifyOptions,
|
||||
|
||||
@@ -34,6 +34,7 @@ const JsonSchema: z.ZodType<Json> = z.lazy(
|
||||
export const LoginOptionsSchema = z
|
||||
.object({
|
||||
domain: z.string().optional(),
|
||||
address: z.string().optional(),
|
||||
statement: z.string().optional(),
|
||||
uri: z.string().optional(),
|
||||
version: z.string().optional(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Json, ThirdwebAuth as ThirdwebAuthSDK } from "../core";
|
||||
import { getUser } from "./helpers/user";
|
||||
import payloadHandler from "./routes/payload";
|
||||
import loginHandler from "./routes/login";
|
||||
import logoutHandler from "./routes/logout";
|
||||
import userHandler from "./routes/user";
|
||||
@@ -47,6 +48,13 @@ export function ThirdwebAuth<
|
||||
router.use(express.json());
|
||||
router.use(cookieMiddleware);
|
||||
|
||||
router.post(
|
||||
"/payload",
|
||||
asyncHandler((req: Request, res: Response) =>
|
||||
payloadHandler(req, res, ctx as ThirdwebAuthContext),
|
||||
),
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/login",
|
||||
asyncHandler((req: Request, res: Response) =>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Request, Response } from "express";
|
||||
import { PayloadBodySchema, ThirdwebAuthContext } from "../types";
|
||||
|
||||
export default async function handler(
|
||||
req: Request,
|
||||
res: Response,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
error: "Invalid method. Only POST supported.",
|
||||
});
|
||||
}
|
||||
|
||||
const parsedPayload = PayloadBodySchema.safeParse(req.body);
|
||||
if (!parsedPayload.success) {
|
||||
return res.status(400).json({ error: "Please provide an address" });
|
||||
}
|
||||
|
||||
// TODO: Add nonce generation + custom expiration + invalid before
|
||||
const payload = await ctx.auth.payload({
|
||||
address: parsedPayload.data.address,
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: ctx.authOptions?.chainId,
|
||||
resources: ctx.authOptions?.resources,
|
||||
});
|
||||
|
||||
return res.status(200).json({ payload });
|
||||
}
|
||||
@@ -4,6 +4,10 @@ import type { GenericAuthWallet } from "@thirdweb-dev/wallets";
|
||||
import { Request } from "express";
|
||||
import { z } from "zod";
|
||||
|
||||
export const PayloadBodySchema = z.object({
|
||||
address: z.string(),
|
||||
});
|
||||
|
||||
export const LoginPayloadBodySchema = z.object({
|
||||
payload: LoginPayloadOutputSchema,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Json, ThirdwebAuth as ThirdwebAuthSDK } from "../core";
|
||||
import { getUser } from "./helpers/user";
|
||||
import payloadHandler from "./routes/payload";
|
||||
import loginHandler from "./routes/login";
|
||||
import logoutHandler from "./routes/logout";
|
||||
import userHandler from "./routes/user";
|
||||
@@ -27,6 +28,8 @@ async function ThirdwebAuthRouter(
|
||||
const action = thirdweb?.[0] as ThirdwebAuthRoute;
|
||||
|
||||
switch (action) {
|
||||
case "payload":
|
||||
return await payloadHandler(req, res, ctx);
|
||||
case "login":
|
||||
return await loginHandler(req, res, ctx);
|
||||
case "user":
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { PayloadBodySchema, ThirdwebAuthContext } from "../types";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
ctx: ThirdwebAuthContext,
|
||||
) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
error: "Invalid method. Only POST supported.",
|
||||
});
|
||||
}
|
||||
|
||||
const parsedPayload = PayloadBodySchema.safeParse(req.body);
|
||||
if (!parsedPayload.success) {
|
||||
return res.status(400).json({ error: "Please provide an address" });
|
||||
}
|
||||
|
||||
// TODO: Add nonce generation + custom expiration + invalid before
|
||||
const payload = await ctx.auth.payload({
|
||||
address: parsedPayload.data.address,
|
||||
statement: ctx.authOptions?.statement,
|
||||
uri: ctx.authOptions?.uri,
|
||||
version: ctx.authOptions?.version,
|
||||
chainId: ctx.authOptions?.chainId,
|
||||
resources: ctx.authOptions?.resources,
|
||||
});
|
||||
|
||||
return res.status(200).json({ payload });
|
||||
}
|
||||
@@ -5,6 +5,10 @@ import { GetServerSidePropsContext, NextApiRequest } from "next";
|
||||
import { NextRequest } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
export const PayloadBodySchema = z.object({
|
||||
address: z.string(),
|
||||
});
|
||||
|
||||
export const LoginPayloadBodySchema = z.object({
|
||||
payload: LoginPayloadOutputSchema,
|
||||
});
|
||||
@@ -14,7 +18,7 @@ type RequestType =
|
||||
| NextRequest
|
||||
| NextApiRequest;
|
||||
|
||||
export type ThirdwebAuthRoute = "login" | "logout" | "user";
|
||||
export type ThirdwebAuthRoute = "payload" | "login" | "logout" | "user";
|
||||
|
||||
export type ThirdwebAuthUser<
|
||||
TData extends Json = Json,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "ChainX-EVM",
|
||||
"chain": "ChainX",
|
||||
"rpc": [
|
||||
"https://chainx-evm.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://mainnet.chainx.org/rpc",
|
||||
"https://mainnet2.chainx.org/rpc"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "BTC",
|
||||
"symbol": "BTC",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://chainx.org",
|
||||
"shortName": "chainx",
|
||||
"chainId": 1501,
|
||||
"networkId": 1501,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "chainx-evm scan",
|
||||
"url": "https://evm.chainx.org",
|
||||
"standard": "none"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "chainx-evm"
|
||||
} as const satisfies Chain;
|
||||
@@ -3,9 +3,9 @@ export default {
|
||||
"name": "Taiko (Alpha-2 Testnet)",
|
||||
"chain": "ETH",
|
||||
"icon": {
|
||||
"url": "ipfs://QmWMUDmNHzCtism9dYK1JooerZJ4da6kGt67HAQmLiEykc",
|
||||
"width": 1001,
|
||||
"height": 1001,
|
||||
"url": "ipfs://QmcHdmVr5VRUJq13jnM6tgah5Ge7hn3Dm14eY6vwivJ5ui",
|
||||
"width": 288,
|
||||
"height": 258,
|
||||
"format": "png"
|
||||
},
|
||||
"rpc": [
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "Taiko (Alpha-3 Testnet)",
|
||||
"chain": "ETH",
|
||||
"status": "active",
|
||||
"icon": {
|
||||
"url": "ipfs://QmcHdmVr5VRUJq13jnM6tgah5Ge7hn3Dm14eY6vwivJ5ui",
|
||||
"width": 288,
|
||||
"height": 258,
|
||||
"format": "png"
|
||||
},
|
||||
"rpc": [
|
||||
"https://taiko-alpha-3-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.test.taiko.xyz"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "Ether",
|
||||
"symbol": "ETH",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://taiko.xyz",
|
||||
"shortName": "taiko-a3",
|
||||
"chainId": 167005,
|
||||
"networkId": 167005,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "blockscout",
|
||||
"url": "https://explorer.test.taiko.xyz",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": true,
|
||||
"slug": "taiko-alpha-3-testnet"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "WhiteBIT Network Testnet",
|
||||
"chain": "WBT",
|
||||
"rpc": [
|
||||
"https://whitebit-network-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc-testnet.whitebit.network"
|
||||
],
|
||||
"faucets": [
|
||||
"https://explorer.whitebit.network/testnet/faucet"
|
||||
],
|
||||
"nativeCurrency": {
|
||||
"name": "WhiteBIT Coin",
|
||||
"symbol": "WBT",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://whitebit.com/wbt",
|
||||
"shortName": "twbt",
|
||||
"chainId": 2625,
|
||||
"networkId": 2625,
|
||||
"icon": {
|
||||
"url": "ipfs://QmQqAAn2F98TH5ouRyvReKxQdaGKjE7WJQPEPW6mFFVXUT",
|
||||
"width": 32,
|
||||
"height": 32,
|
||||
"format": "svg"
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "wb-explorer-testnet",
|
||||
"url": "https://explorer.whitebit.network/testnet",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": true,
|
||||
"slug": "whitebit-network-testnet"
|
||||
} as const satisfies Chain;
|
||||
@@ -1,37 +1,46 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "Kovan",
|
||||
"title": "Ethereum Testnet Kovan",
|
||||
"chain": "ETH",
|
||||
"name": "LUKSO Mainnet",
|
||||
"chain": "LUKSO",
|
||||
"icon": {
|
||||
"url": "ipfs://Qmeg9sFF5tAGi6MCx7YjtVHW6a23zqvHRK1xwzSdp9iE7z",
|
||||
"width": 512,
|
||||
"height": 512,
|
||||
"format": "png"
|
||||
},
|
||||
"rpc": [
|
||||
"https://kovan.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://kovan.poa.network",
|
||||
"http://kovan.poa.network:8545",
|
||||
"https://kovan.infura.io/v3/${INFURA_API_KEY}",
|
||||
"wss://kovan.infura.io/ws/v3/${INFURA_API_KEY}",
|
||||
"ws://kovan.poa.network:8546"
|
||||
],
|
||||
"faucets": [
|
||||
"http://fauceth.komputing.org?chain=42&address=${ADDRESS}",
|
||||
"https://faucet.kovan.network",
|
||||
"https://gitter.im/kovan-testnet/faucet"
|
||||
"https://lukso.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.mainnet.lukso.network",
|
||||
"wss://ws-rpc.mainnet.lukso.network"
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "Kovan Ether",
|
||||
"symbol": "ETH",
|
||||
"name": "LUKSO",
|
||||
"symbol": "LYX",
|
||||
"decimals": 18
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "etherscan",
|
||||
"url": "https://kovan.etherscan.io",
|
||||
"name": "Blockscout",
|
||||
"url": "https://explorer.execution.mainnet.lukso.network",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"infoURL": "https://kovan-testnet.github.io/website",
|
||||
"shortName": "kov",
|
||||
"infoURL": "https://lukso.network",
|
||||
"shortName": "lukso",
|
||||
"chainId": 42,
|
||||
"networkId": 42,
|
||||
"testnet": true,
|
||||
"slug": "kovan"
|
||||
"features": [
|
||||
{
|
||||
"name": "EIP155"
|
||||
},
|
||||
{
|
||||
"name": "EIP1559"
|
||||
}
|
||||
],
|
||||
"redFlags": [
|
||||
"reusedChainId"
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "lukso"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "LUKSO Testnet",
|
||||
"chain": "LUKSO Testnet",
|
||||
"icon": {
|
||||
"url": "ipfs://Qmeg9sFF5tAGi6MCx7YjtVHW6a23zqvHRK1xwzSdp9iE7z",
|
||||
"width": 512,
|
||||
"height": 512,
|
||||
"format": "png"
|
||||
},
|
||||
"rpc": [
|
||||
"https://lukso-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.testnet.lukso.network",
|
||||
"wss://ws-rpc.testnet.lukso.network"
|
||||
],
|
||||
"faucets": [
|
||||
"https://faucet.testnet.lukso.network"
|
||||
],
|
||||
"nativeCurrency": {
|
||||
"name": "TestLYX",
|
||||
"symbol": "LYXt",
|
||||
"decimals": 18
|
||||
},
|
||||
"explorers": [
|
||||
{
|
||||
"name": "Blockscout",
|
||||
"url": "https://explorer.execution.testnet.lukso.network",
|
||||
"standard": "none"
|
||||
}
|
||||
],
|
||||
"infoURL": "https://lukso.network",
|
||||
"shortName": "lukso-testnet",
|
||||
"chainId": 4201,
|
||||
"networkId": 4201,
|
||||
"features": [
|
||||
{
|
||||
"name": "EIP155"
|
||||
},
|
||||
{
|
||||
"name": "EIP1559"
|
||||
}
|
||||
],
|
||||
"testnet": true,
|
||||
"slug": "lukso-testnet"
|
||||
} as const satisfies Chain;
|
||||
@@ -8,8 +8,8 @@ export default {
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "BitDAO",
|
||||
"symbol": "BIT",
|
||||
"name": "Mantle",
|
||||
"symbol": "MNT",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://mantle.xyz",
|
||||
|
||||
@@ -10,8 +10,8 @@ export default {
|
||||
"https://faucet.testnet.mantle.xyz"
|
||||
],
|
||||
"nativeCurrency": {
|
||||
"name": "Testnet BitDAO",
|
||||
"symbol": "BIT",
|
||||
"name": "Testnet Mantle",
|
||||
"symbol": "MNT",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://mantle.xyz",
|
||||
|
||||
@@ -5,6 +5,8 @@ export default {
|
||||
"rpc": [
|
||||
"https://syscoin.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.syscoin.org",
|
||||
"https://rpc.ankr.com/syscoin/${ANKR_API_KEY}",
|
||||
"https://syscoin.public-rpc.com",
|
||||
"wss://rpc.syscoin.org/wss"
|
||||
],
|
||||
"faucets": [
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "Rollux Mainnet",
|
||||
"chain": "SYS",
|
||||
"rpc": [
|
||||
"https://rollux.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc.rollux.com",
|
||||
"wss://rpc.rollux.com/wss"
|
||||
],
|
||||
"faucets": [
|
||||
"https://rollux.id/faucetapp"
|
||||
],
|
||||
"nativeCurrency": {
|
||||
"name": "Syscoin",
|
||||
"symbol": "SYS",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://rollux.com",
|
||||
"shortName": "sys-rollux",
|
||||
"chainId": 570,
|
||||
"networkId": 570,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "Rollux Explorer",
|
||||
"url": "https://explorer.rollux.com",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "rollux"
|
||||
} as const satisfies Chain;
|
||||
@@ -1,29 +1,32 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "Syscoin Rollux Testnet",
|
||||
"name": "Rollux Testnet",
|
||||
"chain": "SYS",
|
||||
"rpc": [
|
||||
"https://syscoin-rollux-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rollux-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://rpc-tanenbaum.rollux.com",
|
||||
"https://rpc.ankr.com/rollux_testnet/${ANKR_API_KEY}",
|
||||
"wss://rpc-tanenbaum.rollux.com/wss"
|
||||
],
|
||||
"faucets": [],
|
||||
"faucets": [
|
||||
"https://rollux.id/faucetapp"
|
||||
],
|
||||
"nativeCurrency": {
|
||||
"name": "Rollux Testnet Syscoin",
|
||||
"symbol": "tSYS",
|
||||
"name": "Testnet Syscoin",
|
||||
"symbol": "TSYS",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://syscoin.org",
|
||||
"infoURL": "https://rollux.com",
|
||||
"shortName": "tsys-rollux",
|
||||
"chainId": 57000,
|
||||
"networkId": 57000,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "Syscoin Rollux Testnet Explorer",
|
||||
"name": "Rollux Testnet Explorer",
|
||||
"url": "https://rollux.tanenbaum.io",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": true,
|
||||
"slug": "syscoin-rollux-testnet"
|
||||
"slug": "rollux-testnet"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "UPTN Testnet",
|
||||
"chain": "UPTN",
|
||||
"icon": {
|
||||
"url": "ipfs://Qma6cGPCDcJPFxy5KQaMBrLtuVQiqeLncXVybcBoQuhai5",
|
||||
"width": 128,
|
||||
"height": 128,
|
||||
"format": "png"
|
||||
},
|
||||
"rpc": [
|
||||
"https://uptn-testnet.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://node-api.alp.uptn.io/v1/ext/rpc"
|
||||
],
|
||||
"features": [
|
||||
{
|
||||
"name": "EIP1559"
|
||||
}
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "UPTN",
|
||||
"symbol": "UPTN",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://uptn.io",
|
||||
"shortName": "UPTN-TEST",
|
||||
"chainId": 6118,
|
||||
"networkId": 6118,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "UPTN Testnet Explorer",
|
||||
"url": "https://testnet.explorer.uptn.io",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": true,
|
||||
"slug": "uptn-testnet"
|
||||
} as const satisfies Chain;
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { Chain } from "../src/types";
|
||||
export default {
|
||||
"name": "UPTN",
|
||||
"chain": "UPTN",
|
||||
"icon": {
|
||||
"url": "ipfs://Qma6cGPCDcJPFxy5KQaMBrLtuVQiqeLncXVybcBoQuhai5",
|
||||
"width": 128,
|
||||
"height": 128,
|
||||
"format": "png"
|
||||
},
|
||||
"rpc": [
|
||||
"https://uptn.rpc.thirdweb.com/${THIRDWEB_API_KEY}",
|
||||
"https://node-api.uptn.io/v1/ext/rpc"
|
||||
],
|
||||
"features": [
|
||||
{
|
||||
"name": "EIP1559"
|
||||
}
|
||||
],
|
||||
"faucets": [],
|
||||
"nativeCurrency": {
|
||||
"name": "UPTN",
|
||||
"symbol": "UPTN",
|
||||
"decimals": 18
|
||||
},
|
||||
"infoURL": "https://uptn.io",
|
||||
"shortName": "UPTN",
|
||||
"chainId": 6119,
|
||||
"networkId": 6119,
|
||||
"explorers": [
|
||||
{
|
||||
"name": "UPTN Explorer",
|
||||
"url": "https://explorer.uptn.io",
|
||||
"standard": "EIP3091"
|
||||
}
|
||||
],
|
||||
"testnet": false,
|
||||
"slug": "uptn"
|
||||
} as const satisfies Chain;
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,12 @@
|
||||
# thirdweb
|
||||
|
||||
## 0.10.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ce36322b`](https://github.com/thirdweb-dev/js/commit/ce36322b383af73905894b73f9409a146359ffb0), [`c60658ed`](https://github.com/thirdweb-dev/js/commit/c60658ed8c94867cca831b0d5535006da5b40aa6), [`aa9e952c`](https://github.com/thirdweb-dev/js/commit/aa9e952cb519a47ed112d2905b1f3787863035b6), [`564eaccf`](https://github.com/thirdweb-dev/js/commit/564eaccf480a81f36db43c782392595a5021e5ac), [`6aee0413`](https://github.com/thirdweb-dev/js/commit/6aee0413439b9ca408879bbb3c38c538c89d01af), [`e93aa70e`](https://github.com/thirdweb-dev/js/commit/e93aa70ef0093a2526404e11b7ddae8fb98c213b), [`51bbd3d1`](https://github.com/thirdweb-dev/js/commit/51bbd3d1bccbb92a1405ea50f6c178c091a90f20)]:
|
||||
- @thirdweb-dev/sdk@3.10.20
|
||||
|
||||
## 0.10.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"main": "dist/cli/index.js",
|
||||
"version": "0.10.20",
|
||||
"version": "0.10.21",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @thirdweb-dev/contracts-js
|
||||
|
||||
## 1.3.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1095](https://github.com/thirdweb-dev/js/pull/1095) [`564eaccf`](https://github.com/thirdweb-dev/js/commit/564eaccf480a81f36db43c782392595a5021e5ac) Thanks [@nkrishang](https://github.com/nkrishang)! - Add SmartWalletFactory extension detection and logic
|
||||
|
||||
## 1.3.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/contracts-js",
|
||||
"version": "1.3.4",
|
||||
"version": "1.3.5",
|
||||
"main": "dist/thirdweb-dev-contracts-js.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js.esm.js",
|
||||
"browser": {
|
||||
@@ -10,7 +10,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/contracts-js",
|
||||
"dependencies": {
|
||||
"@thirdweb-dev/contracts": "3.4.7-0"
|
||||
"@thirdweb-dev/contracts": "3.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @thirdweb-dev/react-core
|
||||
|
||||
## 3.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1114](https://github.com/thirdweb-dev/js/pull/1114) [`2283e71a`](https://github.com/thirdweb-dev/js/commit/2283e71acaaf3d15eb2d6121682f1d2a81eec4f3) Thanks [@adam-maj](https://github.com/adam-maj)! - Switch to server-side payload generation
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1111](https://github.com/thirdweb-dev/js/pull/1111) [`21627c03`](https://github.com/thirdweb-dev/js/commit/21627c03d1bb1658fee19b12d580faa6c7f048d9) Thanks [@adam-maj](https://github.com/adam-maj)! - Update expiration dates for auth
|
||||
|
||||
- Updated dependencies [[`ce36322b`](https://github.com/thirdweb-dev/js/commit/ce36322b383af73905894b73f9409a146359ffb0), [`c60658ed`](https://github.com/thirdweb-dev/js/commit/c60658ed8c94867cca831b0d5535006da5b40aa6), [`aa9e952c`](https://github.com/thirdweb-dev/js/commit/aa9e952cb519a47ed112d2905b1f3787863035b6), [`564eaccf`](https://github.com/thirdweb-dev/js/commit/564eaccf480a81f36db43c782392595a5021e5ac), [`6aee0413`](https://github.com/thirdweb-dev/js/commit/6aee0413439b9ca408879bbb3c38c538c89d01af), [`e93aa70e`](https://github.com/thirdweb-dev/js/commit/e93aa70ef0093a2526404e11b7ddae8fb98c213b), [`51bbd3d1`](https://github.com/thirdweb-dev/js/commit/51bbd3d1bccbb92a1405ea50f6c178c091a90f20), [`9b20fc9a`](https://github.com/thirdweb-dev/js/commit/9b20fc9ad2d303edc31f44fbd2ea3b4dcf35d11e)]:
|
||||
- @thirdweb-dev/sdk@3.10.20
|
||||
- @thirdweb-dev/wallets@0.2.25
|
||||
|
||||
## 3.12.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-core",
|
||||
"version": "3.12.4",
|
||||
"version": "3.13.0",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-core",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import {
|
||||
RequiredParam,
|
||||
requiredParamInvariant,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useSDKChainId } from "../../providers/thirdweb-sdk-provider";
|
||||
import {
|
||||
cacheKeys,
|
||||
invalidateContractAndBalances,
|
||||
} from "../../utils/cache-keys";
|
||||
import { useQueryWithNetwork } from "../query-utils/useQueryWithNetwork";
|
||||
import {
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
import type {
|
||||
AccountEvent, SmartContract,
|
||||
} from "@thirdweb-dev/sdk";
|
||||
import type { BytesLike } from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
|
||||
|
||||
/** **********************/
|
||||
/** READ HOOKS **/
|
||||
/** **********************/
|
||||
|
||||
/**
|
||||
* Get all wallets
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: smartWallets, isLoading, error } = useSmartWallets(contract);
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a smart wallet factory contract
|
||||
* @returns a response object that includes an array of all smart wallets with their associated admin
|
||||
* @twfeature SmartWalletFactory
|
||||
* @see {@link https://portal.thirdweb.com/react/react.usesmartwallets?utm_source=sdk | Documentation}
|
||||
* @beta
|
||||
*/
|
||||
export function useSmartWallets(
|
||||
contract: RequiredParam<SmartContract>
|
||||
): UseQueryResult<AccountEvent[]> {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.smartWalletFactory.getAll(contractAddress),
|
||||
() => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
invariant(
|
||||
contract.smartWalletFactory.getAllWallets,
|
||||
"Contract instance does not support contract.smartWalletFactory.getAllWallets",
|
||||
);
|
||||
return contract.smartWalletFactory.getAllWallets();
|
||||
},
|
||||
{ enabled: !!contract },
|
||||
);
|
||||
}
|
||||
|
||||
/** **********************/
|
||||
/** WRITE HOOKS **/
|
||||
/** **********************/
|
||||
/**
|
||||
* Create a smart wallet
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* const Component = () => {
|
||||
* const { contract } = useContract("{{contract_address}}");
|
||||
* const {
|
||||
* mutate: createSmartWallet,
|
||||
* isLoading,
|
||||
* error,
|
||||
* } = useCreateSmartWallet(contract);
|
||||
*
|
||||
* if (error) {
|
||||
* console.error("failed to create smart wallet", error);
|
||||
* }
|
||||
*
|
||||
* return (
|
||||
* <button
|
||||
* disabled={isLoading}
|
||||
* onClick={() => createSmartWallet("0x...")}
|
||||
* >
|
||||
* Create Smart Wallet
|
||||
* </button>
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a smart wallet factory contract
|
||||
* @returns a mutation object that can be used to create a smart wallet
|
||||
* @twfeature SmartWalletFactory
|
||||
* @see {@link https://portal.thirdweb.com/react/react.usecreatesmartwallet?utm_source=sdk | Documentation}
|
||||
* @beta
|
||||
*/
|
||||
export function useCreateSmartWallet(
|
||||
contract: RequiredParam<SmartContract>,
|
||||
) {
|
||||
const activeChainId = useSDKChainId();
|
||||
const contractAddress = contract?.getAddress();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
async (data: { admin: string; extraData: BytesLike; }) => {
|
||||
requiredParamInvariant(contract, "contract is undefined");
|
||||
|
||||
return contract.smartWalletFactory.createWallet(
|
||||
data.admin,
|
||||
data.extraData,
|
||||
);
|
||||
},
|
||||
{
|
||||
onSettled: () =>
|
||||
invalidateContractAndBalances(
|
||||
queryClient,
|
||||
contractAddress,
|
||||
activeChainId,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export function useLogin() {
|
||||
const wallet = useWallet();
|
||||
|
||||
const login = useMutation({
|
||||
mutationFn: async (options?: LoginOptions) => {
|
||||
mutationFn: async () => {
|
||||
invariant(
|
||||
authConfig,
|
||||
"Please specify an authConfig in the ThirdwebProvider",
|
||||
@@ -38,14 +38,29 @@ export function useLogin() {
|
||||
"Please specify an authUrl in the authConfig.",
|
||||
);
|
||||
|
||||
// we need to generate this auth payload
|
||||
|
||||
const payload = await doLogin(wallet, {
|
||||
...authConfig,
|
||||
...(options || {}),
|
||||
const address = await wallet.getAddress();
|
||||
let res = await fetch(`${authConfig.authUrl}/payload`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ address }),
|
||||
});
|
||||
|
||||
const res = await fetch(`${authConfig.authUrl}/login`, {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to get payload with status code ${res.status}`);
|
||||
}
|
||||
|
||||
let payloadData: LoginPayloadData;
|
||||
try {
|
||||
({ payload: payloadData } = await res.json());
|
||||
} catch {
|
||||
throw new Error(`Failed to get payload`);
|
||||
}
|
||||
|
||||
const payload = await doLoginWithPayload(wallet, payloadData);
|
||||
|
||||
res = await fetch(`${authConfig.authUrl}/login`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -73,11 +88,24 @@ export function useLogin() {
|
||||
});
|
||||
|
||||
return {
|
||||
login: (options?: LoginOptions) => login.mutateAsync(options),
|
||||
login: () => login.mutateAsync(),
|
||||
isLoading: login.isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
export async function doLoginWithPayload(
|
||||
wallet: GenericAuthWallet,
|
||||
payload: LoginPayloadData,
|
||||
): Promise<LoginPayload> {
|
||||
const message = generateMessage(payload);
|
||||
const signature = await wallet.signMessage(message);
|
||||
|
||||
return {
|
||||
payload,
|
||||
signature,
|
||||
};
|
||||
}
|
||||
|
||||
export async function doLogin(
|
||||
wallet: GenericAuthWallet,
|
||||
options?: LoginOptions,
|
||||
@@ -109,20 +137,15 @@ export async function doLogin(
|
||||
nonce: options?.nonce || nonce,
|
||||
issued_at: new Date().toISOString(),
|
||||
expiration_time: new Date(
|
||||
options?.expirationTime || Date.now() + 1000 * 60 * 5,
|
||||
options?.expirationTime || Date.now() + 1000 * 60 * 10,
|
||||
).toISOString(),
|
||||
invalid_before: new Date(
|
||||
options?.invalidBefore || Date.now(),
|
||||
options?.invalidBefore || Date.now() - 1000 * 60 * 10,
|
||||
).toISOString(),
|
||||
resources: options?.resources,
|
||||
};
|
||||
|
||||
const signature = await wallet.signMessage(generateMessage(payloadData));
|
||||
|
||||
return {
|
||||
payload: payloadData,
|
||||
signature,
|
||||
};
|
||||
return doLoginWithPayload(wallet, payloadData);
|
||||
}
|
||||
|
||||
// generate straight from auth
|
||||
|
||||
@@ -118,6 +118,11 @@ export const cacheKeys = {
|
||||
overrides,
|
||||
]),
|
||||
|
||||
smartWalletFactory: {
|
||||
getAll: (contractAddress: RequiredParam<ContractAddress>) =>
|
||||
createContractCacheKey(contractAddress, ["smartWalletFactory"]),
|
||||
},
|
||||
|
||||
app: {
|
||||
get: (contractAddress: RequiredParam<ContractAddress>) =>
|
||||
createContractCacheKey(contractAddress, ["appURI"]),
|
||||
|
||||
@@ -37,7 +37,7 @@ export function useLogin() {
|
||||
const signer = useSDK()?.wallet.getSigner();
|
||||
|
||||
const login = useMutation({
|
||||
mutationFn: async (options?: LoginOptions) => {
|
||||
mutationFn: async () => {
|
||||
invariant(
|
||||
authConfig,
|
||||
"Please specify an authConfig in the ThirdwebProvider",
|
||||
@@ -47,9 +47,29 @@ export function useLogin() {
|
||||
authConfig.authUrl,
|
||||
"Please specify an authUrl in the authConfig.",
|
||||
);
|
||||
const wallet = new SignerWallet(signer);
|
||||
const address = await wallet.getAddress();
|
||||
let res = await fetch(`${authConfig.authUrl}/payload`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ address }),
|
||||
});
|
||||
|
||||
const payload = doLogin(new SignerWallet(signer), options);
|
||||
const res = await fetch(`${authConfig.authUrl}/login`, {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to get payload with status code ${res.status}`);
|
||||
}
|
||||
|
||||
let payloadData: LoginPayloadData;
|
||||
try {
|
||||
({ payload: payloadData } = await res.json());
|
||||
} catch {
|
||||
throw new Error(`Failed to get payload`);
|
||||
}
|
||||
|
||||
const payload = await doLoginWithPayload(wallet, payloadData);
|
||||
res = await fetch(`${authConfig.authUrl}/login`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -72,7 +92,7 @@ export function useLogin() {
|
||||
});
|
||||
|
||||
return {
|
||||
login: (options?: LoginOptions) => login.mutateAsync(options),
|
||||
login: () => login.mutateAsync(),
|
||||
isLoading: login.isLoading,
|
||||
};
|
||||
}
|
||||
@@ -80,7 +100,20 @@ export function useLogin() {
|
||||
// login function extracted directly from auth
|
||||
const isBrowser = () => typeof window !== "undefined";
|
||||
|
||||
async function doLogin(
|
||||
export async function doLoginWithPayload(
|
||||
wallet: GenericAuthWallet,
|
||||
payload: LoginPayloadData,
|
||||
): Promise<LoginPayload> {
|
||||
const message = generateMessage(payload);
|
||||
const signature = await wallet.signMessage(message);
|
||||
|
||||
return {
|
||||
payload,
|
||||
signature,
|
||||
};
|
||||
}
|
||||
|
||||
export async function doLogin(
|
||||
wallet: GenericAuthWallet,
|
||||
options?: LoginOptions,
|
||||
): Promise<LoginPayload> {
|
||||
@@ -119,12 +152,7 @@ async function doLogin(
|
||||
resources: options?.resources,
|
||||
};
|
||||
|
||||
const signature = await wallet.signMessage(generateMessage(payloadData));
|
||||
|
||||
return {
|
||||
payload: payloadData,
|
||||
signature,
|
||||
};
|
||||
return doLoginWithPayload(wallet, payloadData);
|
||||
}
|
||||
|
||||
// generate straight from auth
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @thirdweb-dev/react-native-compat
|
||||
|
||||
## 0.2.17
|
||||
|
||||
## 0.2.16
|
||||
|
||||
## 0.2.15
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-native-compat",
|
||||
"description": "Shims for Thirdweb in React Native Projects",
|
||||
"version": "0.2.16",
|
||||
"version": "0.2.17",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native-compat",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @thirdweb-dev/react-native
|
||||
|
||||
## 0.2.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ce36322b`](https://github.com/thirdweb-dev/js/commit/ce36322b383af73905894b73f9409a146359ffb0), [`c60658ed`](https://github.com/thirdweb-dev/js/commit/c60658ed8c94867cca831b0d5535006da5b40aa6), [`aa9e952c`](https://github.com/thirdweb-dev/js/commit/aa9e952cb519a47ed112d2905b1f3787863035b6), [`564eaccf`](https://github.com/thirdweb-dev/js/commit/564eaccf480a81f36db43c782392595a5021e5ac), [`6aee0413`](https://github.com/thirdweb-dev/js/commit/6aee0413439b9ca408879bbb3c38c538c89d01af), [`21627c03`](https://github.com/thirdweb-dev/js/commit/21627c03d1bb1658fee19b12d580faa6c7f048d9), [`2283e71a`](https://github.com/thirdweb-dev/js/commit/2283e71acaaf3d15eb2d6121682f1d2a81eec4f3), [`e93aa70e`](https://github.com/thirdweb-dev/js/commit/e93aa70ef0093a2526404e11b7ddae8fb98c213b), [`51bbd3d1`](https://github.com/thirdweb-dev/js/commit/51bbd3d1bccbb92a1405ea50f6c178c091a90f20), [`9b20fc9a`](https://github.com/thirdweb-dev/js/commit/9b20fc9ad2d303edc31f44fbd2ea3b4dcf35d11e)]:
|
||||
- @thirdweb-dev/sdk@3.10.20
|
||||
- @thirdweb-dev/react-core@3.13.0
|
||||
- @thirdweb-dev/wallets@0.2.25
|
||||
|
||||
## 0.2.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react-native",
|
||||
"version": "0.2.16",
|
||||
"version": "0.2.17",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react-native",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ export const SessionRequestModal = () => {
|
||||
default:
|
||||
throw new Error(`Method not implemented: ${requestData.method}`);
|
||||
}
|
||||
}, [requestData]);
|
||||
}, [chain?.chainId, chain?.slug, requestData]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# @thirdweb-dev/react
|
||||
|
||||
## 3.13.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1115](https://github.com/thirdweb-dev/js/pull/1115) [`b4c95159`](https://github.com/thirdweb-dev/js/commit/b4c95159377dfaa27b4b9625bc9a828b799d0005) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Add hooks for smart wallet factory
|
||||
|
||||
- [#1116](https://github.com/thirdweb-dev/js/pull/1116) [`356f7aa6`](https://github.com/thirdweb-dev/js/commit/356f7aa6430003bbe8edf9a526bcee77ad5c21ee) Thanks [@MananTank](https://github.com/MananTank)! - - Fix Local wallet connection issue with Safe & Smart Wallet
|
||||
|
||||
- Fix Metamask QR connection issue with Safe & Smart Wallet
|
||||
|
||||
- [#1118](https://github.com/thirdweb-dev/js/pull/1118) [`9b20fc9a`](https://github.com/thirdweb-dev/js/commit/9b20fc9ad2d303edc31f44fbd2ea3b4dcf35d11e) Thanks [@MananTank](https://github.com/MananTank)! - - Remove chain restriction in paper wallet
|
||||
|
||||
- Fix Email validation in Paper and Magic wallet
|
||||
|
||||
- [#1114](https://github.com/thirdweb-dev/js/pull/1114) [`2283e71a`](https://github.com/thirdweb-dev/js/commit/2283e71acaaf3d15eb2d6121682f1d2a81eec4f3) Thanks [@adam-maj](https://github.com/adam-maj)! - Update ConnectWallet button with new auth
|
||||
|
||||
- Updated dependencies [[`ce36322b`](https://github.com/thirdweb-dev/js/commit/ce36322b383af73905894b73f9409a146359ffb0), [`c60658ed`](https://github.com/thirdweb-dev/js/commit/c60658ed8c94867cca831b0d5535006da5b40aa6), [`aa9e952c`](https://github.com/thirdweb-dev/js/commit/aa9e952cb519a47ed112d2905b1f3787863035b6), [`564eaccf`](https://github.com/thirdweb-dev/js/commit/564eaccf480a81f36db43c782392595a5021e5ac), [`6aee0413`](https://github.com/thirdweb-dev/js/commit/6aee0413439b9ca408879bbb3c38c538c89d01af), [`21627c03`](https://github.com/thirdweb-dev/js/commit/21627c03d1bb1658fee19b12d580faa6c7f048d9), [`2283e71a`](https://github.com/thirdweb-dev/js/commit/2283e71acaaf3d15eb2d6121682f1d2a81eec4f3), [`e93aa70e`](https://github.com/thirdweb-dev/js/commit/e93aa70ef0093a2526404e11b7ddae8fb98c213b), [`51bbd3d1`](https://github.com/thirdweb-dev/js/commit/51bbd3d1bccbb92a1405ea50f6c178c091a90f20), [`9b20fc9a`](https://github.com/thirdweb-dev/js/commit/9b20fc9ad2d303edc31f44fbd2ea3b4dcf35d11e)]:
|
||||
- @thirdweb-dev/sdk@3.10.20
|
||||
- @thirdweb-dev/react-core@3.13.0
|
||||
- @thirdweb-dev/wallets@0.2.25
|
||||
|
||||
## 3.12.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react",
|
||||
"version": "3.12.4",
|
||||
"version": "3.13.0",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -6,16 +6,11 @@ import {
|
||||
UseQueryResult,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import type { PaperSupportedChainId } from "@thirdweb-dev/wallets/evm/connectors/paper";
|
||||
|
||||
export function usePaperWallet() {
|
||||
const connect = useConnect();
|
||||
return useCallback(
|
||||
async (options: {
|
||||
chainId?: PaperSupportedChainId;
|
||||
clientId: string;
|
||||
email?: string;
|
||||
}) => {
|
||||
async (options: { chainId?: number; clientId: string; email?: string }) => {
|
||||
const { paperWallet } = await import(
|
||||
"../../../wallet/wallets/paperWallet"
|
||||
);
|
||||
|
||||
@@ -20,7 +20,6 @@ import { Button } from "../../components/buttons";
|
||||
import { Spinner } from "../../components/Spinner";
|
||||
import styled from "@emotion/styled";
|
||||
import { fadeInAnimation } from "../../components/FadeIn";
|
||||
import type { LoginOptions } from "@thirdweb-dev/auth";
|
||||
import { LockIcon } from "./icons/LockIcon";
|
||||
import { Flex } from "../../components/basic";
|
||||
import { shortenAddress } from "../../evm/utils/addresses";
|
||||
@@ -42,7 +41,6 @@ type ConnectWalletProps = {
|
||||
detailsBtn?: () => JSX.Element;
|
||||
dropdownPosition?: DropDownPosition;
|
||||
auth?: {
|
||||
loginOptions?: LoginOptions;
|
||||
loginOptional?: boolean;
|
||||
onLogin?: (token: string) => void;
|
||||
onLogout?: () => void;
|
||||
@@ -86,7 +84,7 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = (props) => {
|
||||
const signIn = async () => {
|
||||
try {
|
||||
setShowSignatureModal(true);
|
||||
const token = await login(props.auth?.loginOptions);
|
||||
const token = await login();
|
||||
props?.auth?.onLogin?.(token);
|
||||
} catch (err) {
|
||||
console.error("failed to log in", err);
|
||||
|
||||
@@ -31,7 +31,7 @@ export const CreateLocalWallet_Password: React.FC<{
|
||||
|
||||
const { localWallet, meta } = useLocalWalletInfo(props.localWalletConf);
|
||||
|
||||
const { setConnectedWallet } = useWalletContext();
|
||||
const { setConnectedWallet, setConnectionStatus } = useWalletContext();
|
||||
const [showImportScreen, setShowImportScreen] = useState(false);
|
||||
|
||||
const [generatedAddress, setGeneratedAddress] = useState<string | null>(null);
|
||||
@@ -65,7 +65,8 @@ export const CreateLocalWallet_Password: React.FC<{
|
||||
}
|
||||
|
||||
setIsConnecting(true);
|
||||
localWallet.connect();
|
||||
setConnectionStatus("connecting");
|
||||
await localWallet.connect();
|
||||
|
||||
await localWallet.save({
|
||||
strategy: "encryptedJson",
|
||||
@@ -193,7 +194,7 @@ export const CreateLocalWallet_Guest: React.FC<{
|
||||
localWallet: LocalWalletConfig;
|
||||
}> = (props) => {
|
||||
const { localWallet } = useLocalWalletInfo(props.localWallet);
|
||||
const { setConnectedWallet } = useWalletContext();
|
||||
const { setConnectedWallet, setConnectionStatus } = useWalletContext();
|
||||
const { onConnect } = props;
|
||||
|
||||
const handleConnect = useCallback(async () => {
|
||||
@@ -201,10 +202,11 @@ export const CreateLocalWallet_Guest: React.FC<{
|
||||
throw new Error("Invalid state");
|
||||
}
|
||||
await localWallet.generate();
|
||||
setConnectionStatus("connecting");
|
||||
await localWallet.connect();
|
||||
setConnectedWallet(localWallet);
|
||||
onConnect();
|
||||
}, [localWallet, setConnectedWallet, onConnect]);
|
||||
}, [localWallet, setConnectedWallet, onConnect, setConnectionStatus]);
|
||||
|
||||
const connecting = useRef(false);
|
||||
useEffect(() => {
|
||||
|
||||
@@ -31,7 +31,7 @@ export const ImportLocalWallet: React.FC<{
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [importedAddress, setImportedAddress] = useState<string | undefined>();
|
||||
|
||||
const { setConnectedWallet } = useWalletContext();
|
||||
const { setConnectedWallet, setConnectionStatus } = useWalletContext();
|
||||
|
||||
const handleImport = async () => {
|
||||
const localWallet = createWalletInstance(
|
||||
@@ -52,6 +52,9 @@ export const ImportLocalWallet: React.FC<{
|
||||
return;
|
||||
}
|
||||
|
||||
setConnectionStatus("connecting");
|
||||
await localWallet.connect();
|
||||
|
||||
await localWallet.save({
|
||||
strategy: "encryptedJson",
|
||||
password,
|
||||
|
||||
@@ -37,7 +37,7 @@ export const ReconnectLocalWallet: React.FC<ReconnectLocalWalletProps> = (
|
||||
const [password, setPassword] = useState("");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [isWrongPassword, setIsWrongPassword] = useState(false);
|
||||
const { setConnectedWallet } = useWalletContext();
|
||||
const { setConnectedWallet, setConnectionStatus } = useWalletContext();
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
const [showCreate, setShowCreate] = useState(false);
|
||||
const [showBackupConfirmation, setShowBackupConfirmation] = useState(false);
|
||||
@@ -110,6 +110,7 @@ export const ReconnectLocalWallet: React.FC<ReconnectLocalWalletProps> = (
|
||||
password,
|
||||
});
|
||||
|
||||
setConnectionStatus("connecting");
|
||||
await localWallet.connect();
|
||||
setConnectedWallet(localWallet);
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ export const localWallet = (
|
||||
},
|
||||
connectUI: LocalWalletConnectUI,
|
||||
isInstalled() {
|
||||
// TODO
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -65,7 +65,8 @@ const MagicSelectionUI: React.FC<
|
||||
const isPhone = Number.isInteger(Number(input[input.length - 1]));
|
||||
|
||||
if (isEmail && isEmailEnabled) {
|
||||
const isValidEmail = input.match(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/);
|
||||
const emailRegex = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,})$/g;
|
||||
const isValidEmail = emailRegex.test(input);
|
||||
if (!isValidEmail) {
|
||||
return "Invalid email address";
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ export const MetamaskScan: React.FC<{
|
||||
}> = ({ onBack, onConnected, onGetStarted, walletConfig }) => {
|
||||
const createInstance = useCreateWalletInstance();
|
||||
const [qrCodeUri, setQrCodeUri] = useState<string | undefined>();
|
||||
const { setConnectedWallet, chainToConnect } = useWalletContext();
|
||||
const { setConnectedWallet, chainToConnect, setConnectionStatus } =
|
||||
useWalletContext();
|
||||
|
||||
const scanStarted = useRef(false);
|
||||
useEffect(() => {
|
||||
@@ -26,6 +27,7 @@ export const MetamaskScan: React.FC<{
|
||||
|
||||
const metamask = createInstance(walletConfig);
|
||||
|
||||
setConnectionStatus("connecting");
|
||||
metamask.connectWithQrCode({
|
||||
chainId: chainToConnect?.chainId,
|
||||
onQrCodeUri(uri) {
|
||||
@@ -42,6 +44,7 @@ export const MetamaskScan: React.FC<{
|
||||
chainToConnect,
|
||||
onConnected,
|
||||
walletConfig,
|
||||
setConnectionStatus,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -38,7 +38,8 @@ const PaperSelectionUI: React.FC<SelectUIProps<PaperWallet, PaperConfig>> = (
|
||||
name="email"
|
||||
type="email"
|
||||
errorMessage={(input) => {
|
||||
const isValidEmail = input.match(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/);
|
||||
const emailRegex = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,})$/g;
|
||||
const isValidEmail = emailRegex.test(input);
|
||||
if (!isValidEmail) {
|
||||
return "Invalid email address";
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ export const SelectAccount: React.FC<{
|
||||
)}
|
||||
</NetworkSelect>
|
||||
{!disableNetworkSelection && (
|
||||
<ChevronDownIcon
|
||||
<StyledChevronDownIcon
|
||||
width={iconSize.sm}
|
||||
height={iconSize.sm}
|
||||
style={{
|
||||
@@ -394,3 +394,7 @@ const NetworkSelect = styled.select<{ theme?: Theme }>`
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledChevronDownIcon = styled(ChevronDownIcon)<{ theme?: Theme }>`
|
||||
color: ${(p) => p.theme.icon.secondary};
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# @thirdweb-dev/sdk
|
||||
|
||||
## 3.10.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1104](https://github.com/thirdweb-dev/js/pull/1104) [`ce36322b`](https://github.com/thirdweb-dev/js/commit/ce36322b383af73905894b73f9409a146359ffb0) Thanks [@farhanW3](https://github.com/farhanW3)! - Added getNonce() to User-Wallets, to help with getting the nonce of the wallet. Updated ERC1155 airdrop to use BigNumber to avoid overflow issue
|
||||
|
||||
- [#1105](https://github.com/thirdweb-dev/js/pull/1105) [`c60658ed`](https://github.com/thirdweb-dev/js/commit/c60658ed8c94867cca831b0d5535006da5b40aa6) Thanks [@MananTank](https://github.com/MananTank)! - Reduce Circular dependencies
|
||||
|
||||
- [#1110](https://github.com/thirdweb-dev/js/pull/1110) [`aa9e952c`](https://github.com/thirdweb-dev/js/commit/aa9e952cb519a47ed112d2905b1f3787863035b6) Thanks [@MananTank](https://github.com/MananTank)! - Reduce circular dependencies
|
||||
|
||||
- [#1095](https://github.com/thirdweb-dev/js/pull/1095) [`564eaccf`](https://github.com/thirdweb-dev/js/commit/564eaccf480a81f36db43c782392595a5021e5ac) Thanks [@nkrishang](https://github.com/nkrishang)! - Add SmartWalletFactory extension detection and logic
|
||||
|
||||
- [#1112](https://github.com/thirdweb-dev/js/pull/1112) [`6aee0413`](https://github.com/thirdweb-dev/js/commit/6aee0413439b9ca408879bbb3c38c538c89d01af) Thanks [@MananTank](https://github.com/MananTank)! - Reduce circular dependencies
|
||||
|
||||
- [#1108](https://github.com/thirdweb-dev/js/pull/1108) [`e93aa70e`](https://github.com/thirdweb-dev/js/commit/e93aa70ef0093a2526404e11b7ddae8fb98c213b) Thanks [@MananTank](https://github.com/MananTank)! - Reduce circular deps
|
||||
|
||||
- [#1117](https://github.com/thirdweb-dev/js/pull/1117) [`51bbd3d1`](https://github.com/thirdweb-dev/js/commit/51bbd3d1bccbb92a1405ea50f6c178c091a90f20) Thanks [@MananTank](https://github.com/MananTank)! - Reduce circular dependencies
|
||||
|
||||
- Updated dependencies [[`564eaccf`](https://github.com/thirdweb-dev/js/commit/564eaccf480a81f36db43c782392595a5021e5ac)]:
|
||||
- @thirdweb-dev/contracts-js@1.3.5
|
||||
|
||||
## 3.10.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/sdk",
|
||||
"version": "3.10.19",
|
||||
"version": "3.10.20",
|
||||
"description": "The main thirdweb SDK.",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/sdk",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import {
|
||||
AddressOrEns,
|
||||
AddressOrEnsSchema,
|
||||
} from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { Address } from "../../schema/shared/Address";
|
||||
|
||||
export async function resolveAddress(
|
||||
addressOrEns: AddressOrEns,
|
||||
): Promise<Address> {
|
||||
return AddressOrEnsSchema.parseAsync(addressOrEns);
|
||||
}
|
||||
@@ -1,9 +1,4 @@
|
||||
import { getSignerAndProvider } from "../functions/getSignerAndProvider";
|
||||
import {
|
||||
AddressOrEns,
|
||||
AddressOrEnsSchema,
|
||||
} from "../schema/shared/AddressOrEnsSchema";
|
||||
import { Address } from "../schema/shared/Address";
|
||||
import { getSignerAndProvider } from "../../constants/urls";
|
||||
import type { providers } from "ethers";
|
||||
|
||||
type CachedEns = {
|
||||
@@ -57,9 +52,3 @@ export async function resolveEns(
|
||||
}
|
||||
return resolvedPromise.address;
|
||||
}
|
||||
|
||||
export async function resolveAddress(
|
||||
addressOrEns: AddressOrEns,
|
||||
): Promise<Address> {
|
||||
return AddressOrEnsSchema.parseAsync(addressOrEns);
|
||||
}
|
||||
@@ -82,5 +82,7 @@ export { convertToReadableQuantity } from "./claim-conditions/convertToReadableQ
|
||||
export { fetchSnapshotEntryForAddress } from "./claim-conditions/fetchSnapshotEntryForAddress";
|
||||
|
||||
export { getCachedAbiForContract } from "./abi";
|
||||
export * from "./ens";
|
||||
export { prepareGaslessRequest } from "./transactions";
|
||||
|
||||
// ens
|
||||
export * from "./ens/resolveEns";
|
||||
export * from "./ens/resolveAddress";
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { SnapshotInputSchema } from "../schema/contracts/common/snapshots";
|
||||
import { SnapshotInput } from "../types/claim-conditions/claim-conditions";
|
||||
|
||||
export async function parseSnapshotInputs(inputs: SnapshotInput) {
|
||||
const chunkSize = 25000;
|
||||
const chunks = Array.from(
|
||||
{ length: Math.ceil(inputs.length / chunkSize) },
|
||||
(_, i) => inputs.slice(i * chunkSize, i * chunkSize + chunkSize),
|
||||
);
|
||||
|
||||
const results = [];
|
||||
for (const chunk of chunks) {
|
||||
results.push(...(await SnapshotInputSchema.parseAsync(chunk)));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import { fetchCurrencyMetadata } from "./currency/fetchCurrencyMetadata";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { ethers, utils } from "ethers";
|
||||
import { MerkleTree } from "merkletreejs";
|
||||
import { parseSnapshotInputs } from "./snapshots";
|
||||
import { parseSnapshotInputs } from "./parseSnapshotInputs";
|
||||
|
||||
// shard using the first 2 hex character of the address
|
||||
// this splits the merkle tree into 256 shards
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SnapshotInputSchema } from "../schema/contracts/common/snapshots";
|
||||
import {
|
||||
SnapshotInfo,
|
||||
SnapshotInput,
|
||||
@@ -10,21 +9,7 @@ import {
|
||||
} from "./sharded-merkle-tree";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
export async function parseSnapshotInputs(inputs: SnapshotInput) {
|
||||
const chunkSize = 25000;
|
||||
const chunks = Array.from(
|
||||
{ length: Math.ceil(inputs.length / chunkSize) },
|
||||
(_, i) => inputs.slice(i * chunkSize, i * chunkSize + chunkSize),
|
||||
);
|
||||
|
||||
const results = [];
|
||||
for (const chunk of chunks) {
|
||||
results.push(...(await SnapshotInputSchema.parseAsync(chunk)));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
import { parseSnapshotInputs } from "./parseSnapshotInputs";
|
||||
|
||||
/**
|
||||
* Create a snapshot (merkle tree) from a list of addresses and uploads it to IPFS
|
||||
|
||||
@@ -1,28 +1,8 @@
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { DeployTransaction, Transaction } from "../core/classes/transactions";
|
||||
import {
|
||||
ForwardRequestMessage,
|
||||
GaslessTransaction,
|
||||
PermitRequestMessage,
|
||||
TransactionResult,
|
||||
} from "../core/types";
|
||||
import { SDKOptionsOutput } from "../schema/sdk-options";
|
||||
import { computeEOAForwarderAddress } from "./any-evm-utils/computeEOAForwarderAddress";
|
||||
import { computeForwarderAddress } from "./any-evm-utils/computeForwarderAddress";
|
||||
import {
|
||||
BiconomyForwarderAbi,
|
||||
ChainAwareForwardRequest,
|
||||
ForwardRequest,
|
||||
getAndIncrementNonce,
|
||||
} from "./forwarder";
|
||||
import { signEIP2612Permit } from "./permit";
|
||||
import { signTypedDataInternal } from "./sign";
|
||||
import ForwarderABI from "@thirdweb-dev/contracts-js/dist/abis/Forwarder.json";
|
||||
import fetch from "cross-fetch";
|
||||
import { BigNumber, BytesLike, ethers } from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
import { CONTRACT_ADDRESSES } from "../constants/addresses/CONTRACT_ADDRESSES";
|
||||
import { getContractAddressByChainId } from "../constants/addresses/getContractAddressByChainId";
|
||||
import type {
|
||||
DeployTransaction,
|
||||
Transaction,
|
||||
} from "../core/classes/transactions";
|
||||
import type { TransactionResult } from "../core/types";
|
||||
|
||||
export function buildDeployTransactionFunction<TArgs extends any[]>(
|
||||
fn: (...args: TArgs) => Promise<DeployTransaction>,
|
||||
@@ -48,352 +28,3 @@ export function buildTransactionFunction<
|
||||
executeFn.prepare = fn;
|
||||
return executeFn;
|
||||
}
|
||||
|
||||
export async function defaultGaslessSendFunction(
|
||||
transaction: GaslessTransaction,
|
||||
signer: ethers.Signer,
|
||||
provider: ethers.providers.Provider,
|
||||
storage: ThirdwebStorage,
|
||||
gaslessOptions?: SDKOptionsOutput["gasless"],
|
||||
): Promise<string> {
|
||||
if (gaslessOptions && "biconomy" in gaslessOptions) {
|
||||
return biconomySendFunction(transaction, signer, provider, gaslessOptions);
|
||||
}
|
||||
return defenderSendFunction(
|
||||
transaction,
|
||||
signer,
|
||||
provider,
|
||||
storage,
|
||||
gaslessOptions,
|
||||
);
|
||||
}
|
||||
|
||||
export async function prepareGaslessRequest(tx: Transaction) {
|
||||
// @ts-expect-error
|
||||
const gaslessTx = await tx.prepareGasless();
|
||||
const gaslessOptions = tx.getGaslessOptions();
|
||||
|
||||
if (gaslessOptions && "biconomy" in gaslessOptions) {
|
||||
const request = await biconomyPrepareRequest(
|
||||
gaslessTx,
|
||||
// @ts-expect-error
|
||||
tx.signer,
|
||||
// @ts-expect-error
|
||||
tx.provider,
|
||||
gaslessOptions,
|
||||
);
|
||||
|
||||
return {
|
||||
url: "https://api.biconomy.io/api/v2/meta-tx/native",
|
||||
...request,
|
||||
};
|
||||
} else {
|
||||
invariant(
|
||||
gaslessOptions && "openzeppelin" in gaslessOptions,
|
||||
"calling openzeppelin gasless transaction without openzeppelin config in the SDK options",
|
||||
);
|
||||
|
||||
const request = await defenderPrepareRequest(
|
||||
gaslessTx,
|
||||
// @ts-expect-error
|
||||
tx.signer,
|
||||
// @ts-expect-error
|
||||
tx.provider,
|
||||
// @ts-expect-error
|
||||
tx.storage,
|
||||
gaslessOptions,
|
||||
);
|
||||
|
||||
return {
|
||||
url: gaslessOptions.openzeppelin.relayerUrl,
|
||||
...request,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function biconomyPrepareRequest(
|
||||
transaction: GaslessTransaction,
|
||||
signer: ethers.Signer,
|
||||
provider: ethers.providers.Provider,
|
||||
gaslessOptions?: SDKOptionsOutput["gasless"],
|
||||
) {
|
||||
invariant(
|
||||
gaslessOptions && "biconomy" in gaslessOptions,
|
||||
"calling biconomySendFunction without biconomy",
|
||||
);
|
||||
invariant(signer && provider, "signer and provider must be set");
|
||||
|
||||
const forwarder = new ethers.Contract(
|
||||
getContractAddressByChainId(
|
||||
transaction.chainId,
|
||||
"biconomyForwarder",
|
||||
) as string,
|
||||
BiconomyForwarderAbi,
|
||||
provider,
|
||||
);
|
||||
const batchId = 0;
|
||||
const batchNonce = await getAndIncrementNonce(forwarder, "getNonce", [
|
||||
transaction.from,
|
||||
batchId,
|
||||
]);
|
||||
|
||||
const request = {
|
||||
from: transaction.from,
|
||||
to: transaction.to,
|
||||
token: ethers.constants.AddressZero,
|
||||
txGas: transaction.gasLimit.toNumber(),
|
||||
tokenGasPrice: "0",
|
||||
batchId,
|
||||
batchNonce: batchNonce.toNumber(),
|
||||
deadline: Math.floor(
|
||||
Date.now() / 1000 +
|
||||
((gaslessOptions &&
|
||||
"biconomy" in gaslessOptions &&
|
||||
gaslessOptions.biconomy?.deadlineSeconds) ||
|
||||
3600),
|
||||
),
|
||||
data: transaction.data,
|
||||
};
|
||||
|
||||
const hashToSign = ethers.utils.arrayify(
|
||||
ethers.utils.solidityKeccak256(
|
||||
[
|
||||
"address",
|
||||
"address",
|
||||
"address",
|
||||
"uint256",
|
||||
"uint256",
|
||||
"uint256",
|
||||
"uint256",
|
||||
"uint256",
|
||||
"bytes32",
|
||||
],
|
||||
[
|
||||
request.from,
|
||||
request.to,
|
||||
request.token,
|
||||
request.txGas,
|
||||
request.tokenGasPrice,
|
||||
request.batchId,
|
||||
request.batchNonce,
|
||||
request.deadline,
|
||||
ethers.utils.keccak256(request.data),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
const signature = await signer.signMessage(hashToSign);
|
||||
|
||||
return {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
from: transaction.from,
|
||||
apiId: gaslessOptions.biconomy.apiId,
|
||||
params: [request, signature],
|
||||
to: transaction.to,
|
||||
gasLimit: transaction.gasLimit.toHexString(),
|
||||
}),
|
||||
headers: {
|
||||
"x-api-key": gaslessOptions.biconomy.apiKey,
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function biconomySendFunction(
|
||||
transaction: GaslessTransaction,
|
||||
signer: ethers.Signer,
|
||||
provider: ethers.providers.Provider,
|
||||
gaslessOptions?: SDKOptionsOutput["gasless"],
|
||||
): Promise<string> {
|
||||
const request = await biconomyPrepareRequest(
|
||||
transaction,
|
||||
signer,
|
||||
provider,
|
||||
gaslessOptions,
|
||||
);
|
||||
const response = await fetch(
|
||||
"https://api.biconomy.io/api/v2/meta-tx/native",
|
||||
request,
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
const resp = await response.json();
|
||||
if (!resp.txHash) {
|
||||
throw new Error(`relay transaction failed: ${resp.log}`);
|
||||
}
|
||||
return resp.txHash;
|
||||
}
|
||||
throw new Error(
|
||||
`relay transaction failed with status: ${response.status} (${response.statusText})`,
|
||||
);
|
||||
}
|
||||
|
||||
async function defenderPrepareRequest(
|
||||
transaction: GaslessTransaction,
|
||||
signer: ethers.Signer,
|
||||
provider: ethers.providers.Provider,
|
||||
storage: ThirdwebStorage,
|
||||
gaslessOptions?: SDKOptionsOutput["gasless"],
|
||||
) {
|
||||
invariant(
|
||||
gaslessOptions && "openzeppelin" in gaslessOptions,
|
||||
"calling openzeppelin gasless transaction without openzeppelin config in the SDK options",
|
||||
);
|
||||
invariant(signer, "provider is not set");
|
||||
invariant(provider, "provider is not set");
|
||||
const forwarderAddress =
|
||||
gaslessOptions.openzeppelin.relayerForwarderAddress ||
|
||||
(gaslessOptions.openzeppelin.useEOAForwarder
|
||||
? CONTRACT_ADDRESSES[
|
||||
transaction.chainId as keyof typeof CONTRACT_ADDRESSES
|
||||
].openzeppelinForwarderEOA ||
|
||||
(await computeEOAForwarderAddress(provider, storage))
|
||||
: CONTRACT_ADDRESSES[
|
||||
transaction.chainId as keyof typeof CONTRACT_ADDRESSES
|
||||
].openzeppelinForwarder ||
|
||||
(await computeForwarderAddress(provider, storage)));
|
||||
|
||||
const forwarder = new ethers.Contract(
|
||||
forwarderAddress,
|
||||
ForwarderABI,
|
||||
provider,
|
||||
);
|
||||
const nonce = await getAndIncrementNonce(forwarder, "getNonce", [
|
||||
transaction.from,
|
||||
]);
|
||||
let domain;
|
||||
let types;
|
||||
let message: ForwardRequestMessage | PermitRequestMessage;
|
||||
if (gaslessOptions.experimentalChainlessSupport) {
|
||||
domain = {
|
||||
name: "GSNv2 Forwarder",
|
||||
version: "0.0.1",
|
||||
verifyingContract: forwarderAddress,
|
||||
};
|
||||
types = {
|
||||
ForwardRequest: ChainAwareForwardRequest,
|
||||
};
|
||||
message = {
|
||||
from: transaction.from,
|
||||
to: transaction.to,
|
||||
value: BigNumber.from(0).toString(),
|
||||
gas: BigNumber.from(transaction.gasLimit).toString(),
|
||||
nonce: BigNumber.from(nonce).toString(),
|
||||
data: transaction.data,
|
||||
chainid: BigNumber.from(transaction.chainId).toString(),
|
||||
};
|
||||
} else {
|
||||
domain = {
|
||||
name: gaslessOptions.openzeppelin.domainName,
|
||||
version: gaslessOptions.openzeppelin.domainVersion,
|
||||
chainId: transaction.chainId,
|
||||
verifyingContract: forwarderAddress,
|
||||
};
|
||||
types = {
|
||||
ForwardRequest,
|
||||
};
|
||||
message = {
|
||||
from: transaction.from,
|
||||
to: transaction.to,
|
||||
value: BigNumber.from(0).toString(),
|
||||
gas: BigNumber.from(transaction.gasLimit).toString(),
|
||||
nonce: BigNumber.from(nonce).toString(),
|
||||
data: transaction.data,
|
||||
};
|
||||
}
|
||||
|
||||
let signature: BytesLike;
|
||||
|
||||
// if the executing function is "approve" and matches with erc20 approve signature
|
||||
// and if the token supports permit, then we use permit for gasless instead of approve.
|
||||
if (
|
||||
transaction.functionName === "approve" &&
|
||||
transaction.functionArgs.length === 2
|
||||
) {
|
||||
const spender = transaction.functionArgs[0];
|
||||
const amount = transaction.functionArgs[1];
|
||||
// TODO: support DAI permit by signDAIPermit
|
||||
const { message: permit, signature: sig } = await signEIP2612Permit(
|
||||
signer,
|
||||
transaction.to,
|
||||
transaction.from,
|
||||
spender,
|
||||
amount,
|
||||
);
|
||||
|
||||
const { r, s, v } = ethers.utils.splitSignature(sig);
|
||||
|
||||
message = {
|
||||
to: transaction.to,
|
||||
owner: permit.owner,
|
||||
spender: permit.spender,
|
||||
value: BigNumber.from(permit.value).toString(),
|
||||
nonce: BigNumber.from(permit.nonce).toString(),
|
||||
deadline: BigNumber.from(permit.deadline).toString(),
|
||||
r,
|
||||
s,
|
||||
v,
|
||||
};
|
||||
signature = sig;
|
||||
} else {
|
||||
const { signature: sig } = await signTypedDataInternal(
|
||||
signer,
|
||||
domain,
|
||||
types,
|
||||
message,
|
||||
);
|
||||
signature = sig;
|
||||
}
|
||||
|
||||
let messageType = "forward";
|
||||
|
||||
// if has owner property then it's permit :)
|
||||
if ((message as PermitRequestMessage)?.owner) {
|
||||
messageType = "permit";
|
||||
}
|
||||
|
||||
return {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
request: message,
|
||||
signature,
|
||||
forwarderAddress,
|
||||
type: messageType,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function defenderSendFunction(
|
||||
transaction: GaslessTransaction,
|
||||
signer: ethers.Signer,
|
||||
provider: ethers.providers.Provider,
|
||||
storage: ThirdwebStorage,
|
||||
gaslessOptions?: SDKOptionsOutput["gasless"],
|
||||
): Promise<string> {
|
||||
invariant(
|
||||
gaslessOptions && "openzeppelin" in gaslessOptions,
|
||||
"calling openzeppelin gasless transaction without openzeppelin config in the SDK options",
|
||||
);
|
||||
|
||||
const request = await defenderPrepareRequest(
|
||||
transaction,
|
||||
signer,
|
||||
provider,
|
||||
storage,
|
||||
gaslessOptions,
|
||||
);
|
||||
|
||||
const response = await fetch(gaslessOptions.openzeppelin.relayerUrl, request);
|
||||
if (response.ok) {
|
||||
const resp = await response.json();
|
||||
if (!resp.result) {
|
||||
throw new Error(`Relay transaction failed: ${resp.message}`);
|
||||
}
|
||||
const result = JSON.parse(resp.result);
|
||||
return result.txHash;
|
||||
}
|
||||
throw new Error(
|
||||
`relay transaction failed with status: ${response.status} (${response.statusText})`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -284,8 +284,8 @@ export const FEATURE_SMART_WALLET_FACTORY = {
|
||||
namespace: "smartwallet.factory",
|
||||
docLinks: {
|
||||
// TODO
|
||||
sdk: "",
|
||||
contracts: "",
|
||||
sdk: "sdk.smartWalletFactory",
|
||||
contracts: "smartWalletFactory",
|
||||
},
|
||||
abis: [IAccountFactory],
|
||||
features: {},
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { ChainInfo } from "../schema/shared/ChainInfo";
|
||||
import { getChainRPC } from "@thirdweb-dev/chains";
|
||||
import type { Chain } from "@thirdweb-dev/chains";
|
||||
import { providers } from "ethers";
|
||||
import type { Signer } from "ethers";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -190,3 +191,53 @@ export function getProviderFromRpcUrl(rpcUrl: string, chainId?: number) {
|
||||
// Always fallback to the default provider if no other option worked
|
||||
return providers.getDefaultProvider(rpcUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function getSignerAndProvider(
|
||||
network: NetworkInput,
|
||||
options?: SDKOptions,
|
||||
): [Signer | undefined, providers.Provider] {
|
||||
let signer: Signer | undefined;
|
||||
let provider: providers.Provider | undefined;
|
||||
|
||||
if (isSigner(network)) {
|
||||
// Here, we have an ethers.Signer
|
||||
signer = network;
|
||||
if (network.provider) {
|
||||
provider = network.provider;
|
||||
}
|
||||
} else if (isProvider(network)) {
|
||||
// Here, we have an ethers.providers.Provider
|
||||
provider = network;
|
||||
} else {
|
||||
// Here, we must have a ChainOrRpcUrl, which is a chain name, chain id, rpc url, or chain config
|
||||
// All of which, getChainProvider can handle for us
|
||||
provider = getChainProvider(network, options);
|
||||
}
|
||||
|
||||
if (options?.readonlySettings) {
|
||||
// If readonly settings are specified, then overwrite the provider
|
||||
provider = getProviderFromRpcUrl(
|
||||
options.readonlySettings.rpcUrl,
|
||||
options.readonlySettings.chainId,
|
||||
);
|
||||
}
|
||||
|
||||
// At this point, if we don't have a provider, don't default to a random chain
|
||||
// Instead, just throw an error
|
||||
if (!provider) {
|
||||
if (signer) {
|
||||
throw new Error(
|
||||
"No provider passed to the SDK! Please make sure that your signer is connected to a provider!",
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"No provider found! Make sure to specify which network to connect to, or pass a signer or provider to the SDK!",
|
||||
);
|
||||
}
|
||||
|
||||
return [signer, provider];
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getPrebuiltInfo } from "../common/legacy";
|
||||
import { fetchAbiFromAddress } from "../common/metadata-resolver";
|
||||
import { ALL_ROLES } from "../common/role";
|
||||
import type { NetworkInput } from "../core/types";
|
||||
import { getSignerAndProvider } from "../functions/getSignerAndProvider";
|
||||
import { getSignerAndProvider } from "../constants/urls";
|
||||
import { DropErc1155ContractSchema } from "../schema/contracts/drop-erc1155";
|
||||
import { DropErc721ContractSchema } from "../schema/contracts/drop-erc721";
|
||||
import { MarketplaceContractSchema } from "../schema/contracts/marketplace";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NFT, NFTMetadataOrUri } from "../../../core/schema/nft";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { isTokenApprovedForTransfer } from "../../common/marketplace";
|
||||
import { uploadOrExtractURI } from "../../common/nft";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { QueryAllParams } from "../../../core/schema/QueryParams";
|
||||
import { NFT } from "../../../core/schema/nft";
|
||||
import { assertEnabled } from "../../common/feature-detection/assertEnabled";
|
||||
import { detectContractFeature } from "../../common/feature-detection/detectContractFeature";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { isTokenApprovedForTransfer } from "../../common/marketplace";
|
||||
import { uploadOrExtractURI } from "../../common/nft";
|
||||
import { getRoleHash } from "../../common/role";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { ContractAppURI } from "../../core/classes/contract-appuri";
|
||||
import { ContractEncoder } from "../../core/classes/contract-encoder";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getRoleHash } from "../../common/role";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { ContractAppURI } from "../../core/classes/contract-appuri";
|
||||
import { ContractEncoder } from "../../core/classes/contract-encoder";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getRoleHash } from "../../common/role";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { ContractAppURI } from "../../core/classes/contract-appuri";
|
||||
import { ContractEncoder } from "../../core/classes/contract-encoder";
|
||||
@@ -11,7 +11,7 @@ import { ContractRoles } from "../../core/classes/contract-roles";
|
||||
import { ContractPrimarySale } from "../../core/classes/contract-sales";
|
||||
import { ContractWrapper } from "../../core/classes/contract-wrapper";
|
||||
import { TokenERC20History } from "../../core/classes/erc-20-history";
|
||||
import { Erc20SignatureMintable } from "../../core/classes/erc-20-signature-mintable";
|
||||
import { Erc20SignatureMintable } from "../../core/classes/erc-20";
|
||||
import { StandardErc20 } from "../../core/classes/erc-20-standard";
|
||||
import { GasCostEstimator } from "../../core/classes/gas-cost-estimator";
|
||||
import { Transaction } from "../../core/classes/transactions";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { ContractAppURI } from "../../core/classes/contract-appuri";
|
||||
import { ContractEncoder } from "../../core/classes/contract-encoder";
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
FEATURE_PLATFORM_FEE,
|
||||
FEATURE_PRIMARY_SALE,
|
||||
FEATURE_ROYALTY,
|
||||
FEATURE_SMART_WALLET_FACTORY,
|
||||
} from "../constants/thirdweb-features";
|
||||
import { Transaction } from "../core/classes/transactions";
|
||||
import { ContractAppURI } from "../core/classes/contract-appuri";
|
||||
@@ -51,6 +52,7 @@ import type {
|
||||
DirectListingsLogic,
|
||||
EnglishAuctionsLogic,
|
||||
OffersLogic,
|
||||
IAccountFactory,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BaseContract, CallOverrides } from "ethers";
|
||||
@@ -62,6 +64,7 @@ import { ContractOwner } from "../core/classes/contract-owner";
|
||||
import { MarketplaceV3DirectListings } from "../core/classes/marketplacev3-direct-listings";
|
||||
import { MarketplaceV3EnglishAuctions } from "../core/classes/marketplacev3-english-auction";
|
||||
import { MarketplaceV3Offers } from "../core/classes/marketplacev3-offers";
|
||||
import { SmartWalletFactory } from "../core/classes/smart-wallet-factory";
|
||||
|
||||
/**
|
||||
* Custom contract dynamic class with feature detection
|
||||
@@ -292,6 +295,41 @@ export class SmartContract<
|
||||
return assertEnabled(this.detectOffers(), FEATURE_OFFERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart Wallet Factory
|
||||
*
|
||||
* @remarks Create smart wallets and fetch data about them.
|
||||
* @example
|
||||
* ```javascript
|
||||
*
|
||||
* // Predict the address of the smart wallet that will be created for an admin.
|
||||
* const deterministicAddress = await contract.smartWalletFactory.predictWalletAddress(admin, extraData);
|
||||
*
|
||||
* // Create smart wallets
|
||||
* const tx = await contract.smartWalletFactory.createWallet(admin, extraData);
|
||||
* // the same as `deterministicAddress`
|
||||
* const smartWalletAddress = tx.address;
|
||||
*
|
||||
* // Get all smart wallets created by the factory
|
||||
* const allWallets = await contract.smartWalletFactory.getAllWallets();
|
||||
*
|
||||
* // Get all smart wallets on which a signer has been given authority.
|
||||
* const associatedWallets = await contract.smartWalletFactory.getAssociatedWallets(signer);
|
||||
*
|
||||
* // Get all signers who have been given authority on a smart wallet.
|
||||
* const associatedSigners = await contract.smartWalletFactory.getAssociatedSigners(smartWalletAddress);
|
||||
*
|
||||
* // Check whether a smart wallet has already been created for a given admin.
|
||||
* const isWalletDeployed = await contract.smartWalletFactory.isWalletDeployed(admin, extraData);
|
||||
* ```
|
||||
*/
|
||||
get smartWalletFactory(): SmartWalletFactory<IAccountFactory> {
|
||||
return assertEnabled(
|
||||
this.detectSmartWalletFactory(),
|
||||
FEATURE_SMART_WALLET_FACTORY,
|
||||
);
|
||||
}
|
||||
|
||||
private _chainId: number;
|
||||
get chainId() {
|
||||
return this._chainId;
|
||||
@@ -518,4 +556,15 @@ export class SmartContract<
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// ========== Smart account features ==========
|
||||
|
||||
private detectSmartWalletFactory() {
|
||||
if (
|
||||
detectContractFeature<IAccountFactory>(this.contractWrapper, FEATURE_SMART_WALLET_FACTORY.name)
|
||||
) {
|
||||
return new SmartWalletFactory(this.contractWrapper);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_OWNER } from "../../constants/thirdweb-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DEFAULT_API_KEY } from "../../../core/constants/urls";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { fetchPreDeployMetadata } from "../../common/feature-detection/fetchPreDeployMetadata";
|
||||
import { extractFunctions } from "../../common/feature-detection/extractFunctions";
|
||||
import { extractConstructorParams } from "../../common/feature-detection/extractConstructorParams";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { hasFunction } from "../../common/feature-detection/hasFunction";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { MissingRoleError } from "../../common/error";
|
||||
import { getRoleHash, Role } from "../../common/role";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AmountSchema } from "../../../core/schema/shared";
|
||||
import { includesErrorMessage } from "../../common/error";
|
||||
import { isNativeToken } from "../../common/currency/isNativeToken";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { detectContractFeature } from "../../common/feature-detection/detectContractFeature";
|
||||
import { hasFunction } from "../../common/feature-detection/hasFunction";
|
||||
import { SnapshotFormatVersion } from "../../common/sharded-merkle-tree";
|
||||
@@ -46,7 +46,7 @@ import type { IClaimCondition } from "@thirdweb-dev/contracts-js/src/IDrop";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish, constants, ethers, utils } from "ethers";
|
||||
import deepEqual from "fast-deep-equal";
|
||||
import { fetchSnapshotEntryForAddress } from "../../common";
|
||||
import { fetchSnapshotEntryForAddress } from "../../common/claim-conditions/fetchSnapshotEntryForAddress";
|
||||
import { abstractContractModelToLegacy } from "../../common/claim-conditions/abstractContractModelToLegacy";
|
||||
import { abstractContractModelToNew } from "../../common/claim-conditions/abstractContractModelToNew";
|
||||
import { convertQuantityToBigNumber } from "../../common/claim-conditions/convertQuantityToBigNumber";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { includesErrorMessage } from "../../common/error";
|
||||
import { detectContractFeature } from "../../common/feature-detection/detectContractFeature";
|
||||
import { hasFunction } from "../../common/feature-detection/hasFunction";
|
||||
import { isNativeToken } from "../../common/currency/isNativeToken";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { SnapshotFormatVersion } from "../../common/sharded-merkle-tree";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { isNode } from "../../common/utils";
|
||||
@@ -43,13 +43,13 @@ import { BigNumber, BigNumberish, constants, ethers, utils } from "ethers";
|
||||
import deepEqual from "fast-deep-equal";
|
||||
import { legacyContractModelToAbstract } from "../../common/claim-conditions/legacyContractModelToAbstract";
|
||||
import { newContractModelToAbstract } from "../../common/claim-conditions/newContractModelToAbstract";
|
||||
import { fetchSnapshotEntryForAddress } from "../../common";
|
||||
import { abstractContractModelToLegacy } from "../../common/claim-conditions/abstractContractModelToLegacy";
|
||||
import { abstractContractModelToNew } from "../../common/claim-conditions/abstractContractModelToNew";
|
||||
import { prepareClaim } from "../../common/claim-conditions/prepareClaim";
|
||||
import { processClaimConditionInputs } from "../../common/claim-conditions/processClaimConditionInputs";
|
||||
import { transformResultToClaimCondition } from "../../common/claim-conditions/transformResultToClaimCondition";
|
||||
import { updateExistingClaimConditions } from "../../common/claim-conditions/updateExistingClaimConditions";
|
||||
import { fetchSnapshotEntryForAddress } from "../../common/claim-conditions/fetchSnapshotEntryForAddress";
|
||||
|
||||
/**
|
||||
* Manages claim conditions for Edition Drop contracts
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import { NFT } from "../../../core/schema/nft";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { uploadOrExtractURIs } from "../../common/nft";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_EDITION_BATCH_MINTABLE } from "../../constants/erc1155-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { EditionMetadataOrUri } from "../../schema/tokens/edition";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResultWithId } from "../types";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc1155 } from "./erc-1155";
|
||||
import { Transaction } from "./transactions";
|
||||
import type { IMintableERC1155, IMulticall } from "@thirdweb-dev/contracts-js";
|
||||
import { TokensMintedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/TokenERC1155";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
/**
|
||||
* Mint Many ERC1155 NFTs at once
|
||||
* @remarks NFT batch minting functionality that handles IPFS storage for you.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.edition.mint.batch.to(walletAddress, [nftMetadataWithSupply1, nftMetadataWithSupply2, ...]);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc1155BatchMintable implements DetectableFeature {
|
||||
featureName = FEATURE_EDITION_BATCH_MINTABLE.name;
|
||||
private contractWrapper: ContractWrapper<IMintableERC1155 & IMulticall>;
|
||||
private erc1155: Erc1155;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
erc1155: Erc1155,
|
||||
contractWrapper: ContractWrapper<IMintableERC1155 & IMulticall>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc1155 = erc1155;
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint Many NFTs with limited supplies
|
||||
*
|
||||
* @remarks Mint many different NFTs with limited supplies to a specified wallet.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet you want to mint the NFT to
|
||||
* const toAddress = "{{wallet_address}}"
|
||||
*
|
||||
* // Custom metadata and supplies of your NFTs
|
||||
* const metadataWithSupply = [{
|
||||
* supply: 50, // The number of this NFT you want to mint
|
||||
* metadata: {
|
||||
* name: "Cool NFT #1",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
|
||||
* },
|
||||
* }, {
|
||||
* supply: 100,
|
||||
* metadata: {
|
||||
* name: "Cool NFT #2",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
|
||||
* },
|
||||
* }];
|
||||
*
|
||||
* const tx = await contract.edition.mint.batch.to(toAddress, metadataWithSupply);
|
||||
* const receipt = tx[0].receipt; // same transaction receipt for all minted NFTs
|
||||
* const firstTokenId = tx[0].id; // token id of the first minted NFT
|
||||
* const firstNFT = await tx[0].data(); // (optional) fetch details of the first minted NFT
|
||||
* ```
|
||||
*/
|
||||
to = buildTransactionFunction(
|
||||
async (
|
||||
to: AddressOrEns,
|
||||
metadataWithSupply: EditionMetadataOrUri[],
|
||||
): Promise<Transaction<TransactionResultWithId<NFT>[]>> => {
|
||||
const metadatas = metadataWithSupply.map((a) => a.metadata);
|
||||
const supplies = metadataWithSupply.map((a) => a.supply);
|
||||
const uris = await uploadOrExtractURIs(metadatas, this.storage);
|
||||
const resolvedAddress = await resolveAddress(to);
|
||||
const encoded = await Promise.all(
|
||||
uris.map(async (uri, index) =>
|
||||
this.contractWrapper.readContract.interface.encodeFunctionData(
|
||||
"mintTo",
|
||||
[
|
||||
resolvedAddress,
|
||||
ethers.constants.MaxUint256,
|
||||
uri,
|
||||
supplies[index],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "multicall",
|
||||
args: [encoded],
|
||||
parse: (receipt) => {
|
||||
const events = this.contractWrapper.parseLogs<TokensMintedEvent>(
|
||||
"TokensMinted",
|
||||
receipt.logs,
|
||||
);
|
||||
if (events.length === 0 || events.length < metadatas.length) {
|
||||
throw new Error("TokenMinted event not found, minting failed");
|
||||
}
|
||||
return events.map((e) => {
|
||||
const id = e.args.tokenIdMinted;
|
||||
return {
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc1155.get(id),
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_EDITION_BURNABLE } from "../../constants/erc1155-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Transaction } from "./transactions";
|
||||
import type { IBurnableERC1155 } from "@thirdweb-dev/contracts-js";
|
||||
import { BigNumberish } from "ethers";
|
||||
|
||||
export class Erc1155Burnable implements DetectableFeature {
|
||||
featureName = FEATURE_EDITION_BURNABLE.name;
|
||||
|
||||
private contractWrapper: ContractWrapper<IBurnableERC1155>;
|
||||
|
||||
constructor(contractWrapper: ContractWrapper<IBurnableERC1155>) {
|
||||
this.contractWrapper = contractWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Burn a specified amount of a NFTs
|
||||
*
|
||||
* @remarks Burn the specified NFTs from the connected wallet
|
||||
*
|
||||
* @param tokenId - the token Id to burn
|
||||
* @param amount - amount to burn
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The token ID to burn NFTs of
|
||||
* const tokenId = 0;
|
||||
* // The amount of the NFT you want to burn
|
||||
* const amount = 2;
|
||||
*
|
||||
* const result = await contract.edition.burn.tokens(tokenId, amount);
|
||||
* ```
|
||||
*/
|
||||
tokens = buildTransactionFunction(
|
||||
async (tokenId: BigNumberish, amount: BigNumberish) => {
|
||||
const account = await this.contractWrapper.getSignerAddress();
|
||||
return this.from.prepare(account, tokenId, amount);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Burn a specified amount of a NFTs
|
||||
*
|
||||
* @remarks Burn the specified NFTs from a specified wallet
|
||||
*
|
||||
* @param account - the address to burn NFTs from
|
||||
* @param tokenId - the tokenId to burn
|
||||
* @param amount - amount to burn
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The address of the wallet to burn NFTS from
|
||||
* const account = "0x...";
|
||||
* // The token ID to burn NFTs of
|
||||
* const tokenId = 0;
|
||||
* // The amount of this NFT you want to burn
|
||||
* const amount = 2;
|
||||
*
|
||||
* const result = await contract.edition.burn.from(account, tokenId, amount);
|
||||
* ```
|
||||
*/
|
||||
from = buildTransactionFunction(
|
||||
async (
|
||||
account: AddressOrEns,
|
||||
tokenId: BigNumberish,
|
||||
amount: BigNumberish,
|
||||
) => {
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "burn",
|
||||
args: [await resolveAddress(account), tokenId, amount],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Burn a batch of NFTs
|
||||
*
|
||||
* @remarks Burn the batch NFTs from the connected wallet
|
||||
*
|
||||
* @param tokenIds - the tokenIds to burn
|
||||
* @param amounts - amount of each token to burn
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The token IDs to burn NFTs of
|
||||
* const tokenIds = [0, 1];
|
||||
* // The amounts of each NFT you want to burn
|
||||
* const amounts = [2, 2];
|
||||
*
|
||||
* const result = await contract.edition.burn.batch(tokenIds, amounts);
|
||||
* ```
|
||||
*/
|
||||
batch = buildTransactionFunction(
|
||||
async (tokenIds: BigNumberish[], amounts: BigNumberish[]) => {
|
||||
const account = await this.contractWrapper.getSignerAddress();
|
||||
return this.batchFrom.prepare(account, tokenIds, amounts);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Burn a batch of NFTs
|
||||
*
|
||||
* @remarks Burn the batch NFTs from the specified wallet
|
||||
*
|
||||
* @param account - the address to burn NFTs from
|
||||
* @param tokenIds - the tokenIds to burn
|
||||
* @param amounts - amount of each token to burn
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The address of the wallet to burn NFTS from
|
||||
* const account = "0x...";
|
||||
* // The token IDs to burn NFTs of
|
||||
* const tokenIds = [0, 1];
|
||||
* // The amounts of each NFT you want to burn
|
||||
* const amounts = [2, 2];
|
||||
*
|
||||
* const result = await contract.edition.burn.batchFrom(account, tokenIds, amounts);
|
||||
* ```
|
||||
*/
|
||||
batchFrom = buildTransactionFunction(
|
||||
async (
|
||||
account: AddressOrEns,
|
||||
tokenIds: BigNumberish[],
|
||||
amounts: BigNumberish[],
|
||||
) => {
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "burnBatch",
|
||||
args: [await resolveAddress(account), tokenIds, amounts],
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { calculateClaimCost } from "../../common/claim-conditions/calculateClaimCost";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_EDITION_CLAIM_CUSTOM } from "../../constants/erc1155-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
import {
|
||||
DEFAULT_QUERY_ALL_COUNT,
|
||||
QueryAllParams,
|
||||
} from "../../../core/schema/QueryParams";
|
||||
import { NFT } from "../../../core/schema/nft";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { FEATURE_EDITION_ENUMERABLE } from "../../constants/erc1155-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { BaseERC1155 } from "../../types/eips";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc1155 } from "./erc-1155";
|
||||
import type { IERC1155Enumerable } from "@thirdweb-dev/contracts-js";
|
||||
import { BigNumber, BigNumberish } from "ethers";
|
||||
|
||||
/**
|
||||
* List ERC1155 NFTs
|
||||
* @remarks Easily list all the NFTs in a ERC1155 contract.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* const nfts = await contract.edition.query.all();
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc1155Enumerable implements DetectableFeature {
|
||||
featureName = FEATURE_EDITION_ENUMERABLE.name;
|
||||
private contractWrapper: ContractWrapper<BaseERC1155 & IERC1155Enumerable>;
|
||||
private erc1155: Erc1155;
|
||||
|
||||
constructor(
|
||||
erc1155: Erc1155,
|
||||
contractWrapper: ContractWrapper<BaseERC1155 & IERC1155Enumerable>,
|
||||
) {
|
||||
this.erc1155 = erc1155;
|
||||
this.contractWrapper = contractWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get All NFTs
|
||||
*
|
||||
* @remarks Get all the data associated with every NFT in this contract.
|
||||
*
|
||||
* By default, returns the first 100 NFTs, use queryParams to fetch more.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const nfts = await contract.edition.query.all();
|
||||
* ```
|
||||
* @param queryParams - optional filtering to only fetch a subset of results.
|
||||
* @returns The NFT metadata for all NFTs queried.
|
||||
*/
|
||||
public async all(queryParams?: QueryAllParams): Promise<NFT[]> {
|
||||
const start = BigNumber.from(queryParams?.start || 0).toNumber();
|
||||
const count = BigNumber.from(
|
||||
queryParams?.count || DEFAULT_QUERY_ALL_COUNT,
|
||||
).toNumber();
|
||||
const maxId = Math.min((await this.totalCount()).toNumber(), start + count);
|
||||
return await Promise.all(
|
||||
[...Array(maxId - start).keys()].map((i) =>
|
||||
this.erc1155.get((start + i).toString()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of NFTs minted
|
||||
* @remarks This returns the total number of NFTs minted in this contract, **not** the total supply of a given token.
|
||||
*
|
||||
* @returns the total number of NFTs minted in this contract
|
||||
* @public
|
||||
*/
|
||||
public async totalCount(): Promise<BigNumber> {
|
||||
return await this.contractWrapper.readContract.nextTokenIdToMint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the supply of token for a given tokenId.
|
||||
* @remarks This is **not** the sum of supply of all NFTs in the contract.
|
||||
*
|
||||
* @returns the total number of NFTs minted in this contract
|
||||
* @public
|
||||
*/
|
||||
public async totalCirculatingSupply(
|
||||
tokenId: BigNumberish,
|
||||
): Promise<BigNumber> {
|
||||
return await this.contractWrapper.readContract.totalSupply(tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all NFTs owned by a specific wallet
|
||||
*
|
||||
* @remarks Get all the data associated with the NFTs owned by a specific wallet.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet to get the NFTs of
|
||||
* const address = "{{wallet_address}}";
|
||||
* const nfts = await contract.edition.query.owned(address);
|
||||
* ```
|
||||
*
|
||||
* @returns The NFT metadata for all NFTs in the contract.
|
||||
*/
|
||||
public async owned(walletAddress?: AddressOrEns): Promise<NFT[]> {
|
||||
const address = await resolveAddress(
|
||||
walletAddress || (await this.contractWrapper.getSignerAddress()),
|
||||
);
|
||||
const maxId = await this.contractWrapper.readContract.nextTokenIdToMint();
|
||||
const balances = await this.contractWrapper.readContract.balanceOfBatch(
|
||||
Array(maxId.toNumber()).fill(address),
|
||||
Array.from(Array(maxId.toNumber()).keys()),
|
||||
);
|
||||
|
||||
const ownedBalances = balances
|
||||
.map((b, i) => {
|
||||
return {
|
||||
tokenId: i,
|
||||
balance: b,
|
||||
};
|
||||
})
|
||||
.filter((b) => b.balance.gt(0));
|
||||
return await Promise.all(
|
||||
ownedBalances.map(async (b) => {
|
||||
const editionMetadata = await this.erc1155.get(b.tokenId.toString());
|
||||
return {
|
||||
...editionMetadata,
|
||||
owner: address,
|
||||
quantityOwned: b.balance.toString(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
import { NFTMetadata, NFTMetadataOrUri } from "../../../core/schema/nft";
|
||||
import { detectContractFeature } from "../../common/feature-detection/detectContractFeature";
|
||||
import { getPrebuiltInfo } from "../../common/legacy";
|
||||
import { uploadOrExtractURIs } from "../../common/nft";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import {
|
||||
FEATURE_EDITION_LAZY_MINTABLE_V2,
|
||||
FEATURE_EDITION_REVEALABLE,
|
||||
} from "../../constants/erc1155-features";
|
||||
import { BaseDelayedRevealERC1155, BaseDropERC1155 } from "../../types/eips";
|
||||
import { UploadProgressEvent } from "../../types/events";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResultWithId } from "../types";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { DelayedReveal } from "./delayed-reveal";
|
||||
import { Erc1155 } from "./erc-1155";
|
||||
import { Transaction } from "./transactions";
|
||||
import type { DropERC1155_V2 } from "@thirdweb-dev/contracts-js";
|
||||
import { TokensLazyMintedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/LazyMint";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
export class Erc1155LazyMintable implements DetectableFeature {
|
||||
featureName = FEATURE_EDITION_LAZY_MINTABLE_V2.name;
|
||||
|
||||
/**
|
||||
* Delayed reveal
|
||||
* @remarks Create a batch of encrypted NFTs that can be revealed at a later time.
|
||||
* @example
|
||||
* ```javascript
|
||||
* // the real NFTs, these will be encrypted until you reveal them
|
||||
* const realNFTs = [{
|
||||
* name: "Common NFT #1",
|
||||
* description: "Common NFT, one of many.",
|
||||
* image: fs.readFileSync("path/to/image.png"),
|
||||
* }, {
|
||||
* name: "Super Rare NFT #2",
|
||||
* description: "You got a Super Rare NFT!",
|
||||
* image: fs.readFileSync("path/to/image.png"),
|
||||
* }];
|
||||
* // A placeholder NFT that people will get immediately in their wallet, and will be converted to the real NFT at reveal time
|
||||
* const placeholderNFT = {
|
||||
* name: "Hidden NFT",
|
||||
* description: "Will be revealed next week!"
|
||||
* };
|
||||
* // Create and encrypt the NFTs
|
||||
* await contract.edition.drop.revealer.createDelayedRevealBatch(
|
||||
* placeholderNFT,
|
||||
* realNFTs,
|
||||
* "my secret password",
|
||||
* );
|
||||
* // Whenever you're ready, reveal your NFTs at any time
|
||||
* const batchId = 0; // the batch to reveal
|
||||
* await contract.edition.drop.revealer.reveal(batchId, "my secret password");
|
||||
* ```
|
||||
*/
|
||||
public revealer: DelayedReveal<BaseDelayedRevealERC1155> | undefined;
|
||||
|
||||
private contractWrapper: ContractWrapper<BaseDropERC1155>;
|
||||
private erc1155: Erc1155;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
erc1155: Erc1155,
|
||||
contractWrapper: ContractWrapper<BaseDropERC1155>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc1155 = erc1155;
|
||||
this.contractWrapper = contractWrapper;
|
||||
|
||||
this.storage = storage;
|
||||
this.revealer = this.detectErc1155Revealable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a batch of NFTs to be claimed in the future
|
||||
*
|
||||
* @remarks Create batch allows you to create a batch of many NFTs in one transaction.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Custom metadata of the NFTs to create
|
||||
* const metadatas = [{
|
||||
* name: "Cool NFT",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
|
||||
* }, {
|
||||
* name: "Cool NFT",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"),
|
||||
* }];
|
||||
*
|
||||
* const results = await contract.erc1155.lazyMint(metadatas); // uploads and creates the NFTs on chain
|
||||
* const firstTokenId = results[0].id; // token id of the first created NFT
|
||||
* const firstNFT = await results[0].data(); // (optional) fetch details of the first created NFT
|
||||
* ```
|
||||
*
|
||||
* @param metadatas - The metadata to include in the batch.
|
||||
* @param options - optional upload progress callback
|
||||
*/
|
||||
lazyMint = buildTransactionFunction(
|
||||
async (
|
||||
metadatas: NFTMetadataOrUri[],
|
||||
options?: {
|
||||
onProgress: (event: UploadProgressEvent) => void;
|
||||
},
|
||||
): Promise<Transaction<TransactionResultWithId<NFTMetadata>[]>> => {
|
||||
const startFileNumber = await this.erc1155.nextTokenIdToMint();
|
||||
const batch = await uploadOrExtractURIs(
|
||||
metadatas,
|
||||
this.storage,
|
||||
startFileNumber.toNumber(),
|
||||
options,
|
||||
);
|
||||
// ensure baseUri is the same for the entire batch
|
||||
const baseUri = batch[0].substring(0, batch[0].lastIndexOf("/"));
|
||||
for (let i = 0; i < batch.length; i++) {
|
||||
const uri = batch[i].substring(0, batch[i].lastIndexOf("/"));
|
||||
if (baseUri !== uri) {
|
||||
throw new Error(
|
||||
`Can only create batches with the same base URI for every entry in the batch. Expected '${baseUri}' but got '${uri}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const parse = (receipt: ethers.providers.TransactionReceipt) => {
|
||||
const event = this.contractWrapper.parseLogs<TokensLazyMintedEvent>(
|
||||
"TokensLazyMinted",
|
||||
receipt?.logs,
|
||||
);
|
||||
const startingIndex = event[0].args.startTokenId;
|
||||
const endingIndex = event[0].args.endTokenId;
|
||||
const results: TransactionResultWithId<NFTMetadata>[] = [];
|
||||
for (let id = startingIndex; id.lte(endingIndex); id = id.add(1)) {
|
||||
results.push({
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc1155.getTokenMetadata(id),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
const prebuiltInfo = await getPrebuiltInfo(
|
||||
this.contractWrapper.readContract.address,
|
||||
this.contractWrapper.getProvider(),
|
||||
);
|
||||
if (
|
||||
this.isLegacyEditionDropContract(this.contractWrapper, prebuiltInfo)
|
||||
) {
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "lazyMint",
|
||||
args: [
|
||||
batch.length,
|
||||
`${baseUri.endsWith("/") ? baseUri : `${baseUri}/`}`,
|
||||
],
|
||||
parse,
|
||||
});
|
||||
} else {
|
||||
// new contracts/extensions have support for delayed reveal that adds an extra parameter to lazyMint
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "lazyMint",
|
||||
args: [
|
||||
batch.length,
|
||||
`${baseUri.endsWith("/") ? baseUri : `${baseUri}/`}`,
|
||||
ethers.utils.toUtf8Bytes(""),
|
||||
],
|
||||
parse,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/** ******************************
|
||||
* PRIVATE FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
private detectErc1155Revealable():
|
||||
| DelayedReveal<BaseDelayedRevealERC1155>
|
||||
| undefined {
|
||||
if (
|
||||
detectContractFeature<BaseDelayedRevealERC1155>(
|
||||
this.contractWrapper,
|
||||
"ERC1155Revealable",
|
||||
)
|
||||
) {
|
||||
return new DelayedReveal(
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
FEATURE_EDITION_REVEALABLE.name,
|
||||
() => this.erc1155.nextTokenIdToMint(),
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private isLegacyEditionDropContract(
|
||||
contractWrapper: ContractWrapper<any>,
|
||||
info: Awaited<ReturnType<typeof getPrebuiltInfo>>,
|
||||
): contractWrapper is ContractWrapper<DropERC1155_V2> {
|
||||
return (info && info.type === "DropERC1155" && info.version < 3) || false;
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
import { NFT } from "../../../core/schema/nft";
|
||||
import { detectContractFeature } from "../../common/feature-detection/detectContractFeature";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { uploadOrExtractURI } from "../../common/nft";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_EDITION_MINTABLE } from "../../constants/erc1155-features";
|
||||
import { EditionMetadataOrUri } from "../../schema/tokens/edition";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResultWithId } from "../types";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc1155 } from "./erc-1155";
|
||||
import { Erc1155BatchMintable } from "./erc-1155-batch-mintable";
|
||||
import { Transaction } from "./transactions";
|
||||
import type { IMintableERC1155, IMulticall } from "@thirdweb-dev/contracts-js";
|
||||
import { TransferSingleEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/ITokenERC1155";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish, ethers } from "ethers";
|
||||
|
||||
/**
|
||||
* Mint ERC1155 NFTs
|
||||
* @remarks NFT minting functionality that handles IPFS storage for you.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.edition.mint.to(walletAddress, nftMetadata);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc1155Mintable implements DetectableFeature {
|
||||
featureName = FEATURE_EDITION_MINTABLE.name;
|
||||
private contractWrapper: ContractWrapper<IMintableERC1155>;
|
||||
private erc1155: Erc1155;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
/**
|
||||
* Batch mint Tokens to many addresses
|
||||
*/
|
||||
public batch: Erc1155BatchMintable | undefined;
|
||||
|
||||
constructor(
|
||||
erc1155: Erc1155,
|
||||
contractWrapper: ContractWrapper<IMintableERC1155>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc1155 = erc1155;
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.storage = storage;
|
||||
this.batch = this.detectErc1155BatchMintable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint an NFT with a limited supply
|
||||
*
|
||||
* @remarks Mint an NFT with a limited supply to a specified wallet.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet you want to mint the NFT to
|
||||
* const toAddress = "{{wallet_address}}"
|
||||
*
|
||||
* // Custom metadata of the NFT, note that you can fully customize this metadata with other properties.
|
||||
* const metadata = {
|
||||
* name: "Cool NFT",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
|
||||
* }
|
||||
*
|
||||
* const metadataWithSupply = {
|
||||
* metadata,
|
||||
* supply: 1000, // The number of this NFT you want to mint
|
||||
* }
|
||||
*
|
||||
* const tx = await contract.edition.mint.to(toAddress, metadataWithSupply);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* const tokenId = tx.id; // the id of the NFT minted
|
||||
* const nft = await tx.data(); // (optional) fetch details of minted NFT
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
to = buildTransactionFunction(
|
||||
async (
|
||||
to: AddressOrEns,
|
||||
metadataWithSupply: EditionMetadataOrUri,
|
||||
): Promise<Transaction<TransactionResultWithId<NFT>>> => {
|
||||
const tx = (await this.getMintTransaction(
|
||||
to,
|
||||
metadataWithSupply,
|
||||
)) as any as Transaction<TransactionResultWithId<NFT>>;
|
||||
tx.setParse((receipt) => {
|
||||
const event = this.contractWrapper.parseLogs<TransferSingleEvent>(
|
||||
"TransferSingle",
|
||||
receipt?.logs,
|
||||
);
|
||||
if (event.length === 0) {
|
||||
throw new Error("TransferSingleEvent event not found");
|
||||
}
|
||||
const id = event[0].args.id;
|
||||
return {
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc1155.get(id.toString()),
|
||||
};
|
||||
});
|
||||
return tx;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @deprecated Use `contract.erc1155.mint.prepare(...args)` instead
|
||||
*/
|
||||
public async getMintTransaction(
|
||||
to: AddressOrEns,
|
||||
metadataWithSupply: EditionMetadataOrUri,
|
||||
): Promise<Transaction> {
|
||||
const uri = await uploadOrExtractURI(
|
||||
metadataWithSupply.metadata,
|
||||
this.storage,
|
||||
);
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "mintTo",
|
||||
args: [
|
||||
await resolveAddress(to),
|
||||
ethers.constants.MaxUint256,
|
||||
uri,
|
||||
metadataWithSupply.supply,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the supply of an existing NFT and mint it to a given wallet address
|
||||
*
|
||||
* @param to - the address to mint to
|
||||
* @param tokenId - the token id of the NFT to increase supply of
|
||||
* @param additionalSupply - the additional amount to mint
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet you want to mint the NFT to
|
||||
* const toAddress = "{{wallet_address}}"
|
||||
* const tokenId = 0;
|
||||
* const additionalSupply = 1000;
|
||||
*
|
||||
* const tx = await contract.edition.mint.additionalSupplyTo(toAddress, tokenId, additionalSupply);
|
||||
* ```
|
||||
*/
|
||||
additionalSupplyTo = buildTransactionFunction(
|
||||
async (
|
||||
to: AddressOrEns,
|
||||
tokenId: BigNumberish,
|
||||
additionalSupply: BigNumberish,
|
||||
): Promise<Transaction<TransactionResultWithId<NFT>>> => {
|
||||
const metadata = await this.erc1155.getTokenMetadata(tokenId);
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "mintTo",
|
||||
args: [
|
||||
await resolveAddress(to),
|
||||
tokenId,
|
||||
metadata.uri,
|
||||
additionalSupply,
|
||||
],
|
||||
parse: (receipt) => {
|
||||
return {
|
||||
id: BigNumber.from(tokenId),
|
||||
receipt,
|
||||
data: () => this.erc1155.get(tokenId),
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
private detectErc1155BatchMintable() {
|
||||
if (
|
||||
detectContractFeature<IMintableERC1155 & IMulticall>(
|
||||
this.contractWrapper,
|
||||
"ERC1155BatchMintable",
|
||||
)
|
||||
) {
|
||||
return new Erc1155BatchMintable(
|
||||
this.erc1155,
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,12 @@ import {
|
||||
import { assertEnabled } from "../../common/feature-detection/assertEnabled";
|
||||
import { detectContractFeature } from "../../common/feature-detection/detectContractFeature";
|
||||
import { hasFunction } from "../../common/feature-detection/hasFunction";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { FALLBACK_METADATA, fetchTokenMetadata } from "../../common/nft";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import {
|
||||
FALLBACK_METADATA,
|
||||
fetchTokenMetadata,
|
||||
uploadOrExtractURI,
|
||||
} from "../../common/nft";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import {
|
||||
FEATURE_EDITION,
|
||||
@@ -38,10 +42,6 @@ import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { UpdateableNetwork } from "../interfaces/contract";
|
||||
import { NetworkInput, TransactionResultWithId } from "../types";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc1155Burnable } from "./erc-1155-burnable";
|
||||
import { Erc1155Enumerable } from "./erc-1155-enumerable";
|
||||
import { Erc1155LazyMintable } from "./erc-1155-lazymintable";
|
||||
import { Erc1155Mintable } from "./erc-1155-mintable";
|
||||
import { Erc1155SignatureMintable } from "./erc-1155-signature-mintable";
|
||||
import { Transaction } from "./transactions";
|
||||
import type {
|
||||
@@ -57,6 +57,20 @@ import { BigNumber, BigNumberish, BytesLike, ethers } from "ethers";
|
||||
import { ERC1155Claimable } from "./erc-1155-claimable";
|
||||
import { Erc1155ClaimableWithConditions } from "./erc-1155-claimable-with-conditions";
|
||||
|
||||
import { DEFAULT_QUERY_ALL_COUNT } from "../../../core/schema/QueryParams";
|
||||
|
||||
import { getPrebuiltInfo } from "../../common/legacy";
|
||||
import { uploadOrExtractURIs } from "../../common/nft";
|
||||
import { BaseDelayedRevealERC1155 } from "../../types/eips";
|
||||
import { DelayedReveal } from "./delayed-reveal";
|
||||
import type { DropERC1155_V2 } from "@thirdweb-dev/contracts-js";
|
||||
import { TokensLazyMintedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/LazyMint";
|
||||
|
||||
import type { IMulticall } from "@thirdweb-dev/contracts-js";
|
||||
import { TransferSingleEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/ITokenERC1155";
|
||||
|
||||
import { TokensMintedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/TokenERC1155";
|
||||
|
||||
/**
|
||||
* Standard ERC1155 NFT functions
|
||||
* @remarks Basic functionality for a ERC1155 contract that handles IPFS storage for you.
|
||||
@@ -349,12 +363,12 @@ export class Erc1155<
|
||||
const input = await AirdropInputSchema.parseAsync(addresses);
|
||||
|
||||
const totalToAirdrop = input.reduce((prev, curr) => {
|
||||
return prev + Number(curr?.quantity || 1);
|
||||
}, 0);
|
||||
return BigNumber.from(prev).add(BigNumber.from(curr?.quantity || 1));
|
||||
}, BigNumber.from(0));
|
||||
|
||||
if (balanceOf.toNumber() < totalToAirdrop) {
|
||||
if (balanceOf.lt(BigNumber.from(totalToAirdrop))) {
|
||||
throw new Error(
|
||||
`The caller owns ${balanceOf.toNumber()} NFTs, but wants to airdrop ${totalToAirdrop} NFTs.`,
|
||||
`The caller owns ${balanceOf.toString()} NFTs, but wants to airdrop ${totalToAirdrop.toString()} NFTs.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1220,3 +1234,715 @@ export class Erc1155<
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class Erc1155Burnable implements DetectableFeature {
|
||||
featureName = FEATURE_EDITION_BURNABLE.name;
|
||||
|
||||
private contractWrapper: ContractWrapper<IBurnableERC1155>;
|
||||
|
||||
constructor(contractWrapper: ContractWrapper<IBurnableERC1155>) {
|
||||
this.contractWrapper = contractWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Burn a specified amount of a NFTs
|
||||
*
|
||||
* @remarks Burn the specified NFTs from the connected wallet
|
||||
*
|
||||
* @param tokenId - the token Id to burn
|
||||
* @param amount - amount to burn
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The token ID to burn NFTs of
|
||||
* const tokenId = 0;
|
||||
* // The amount of the NFT you want to burn
|
||||
* const amount = 2;
|
||||
*
|
||||
* const result = await contract.edition.burn.tokens(tokenId, amount);
|
||||
* ```
|
||||
*/
|
||||
tokens = buildTransactionFunction(
|
||||
async (tokenId: BigNumberish, amount: BigNumberish) => {
|
||||
const account = await this.contractWrapper.getSignerAddress();
|
||||
return this.from.prepare(account, tokenId, amount);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Burn a specified amount of a NFTs
|
||||
*
|
||||
* @remarks Burn the specified NFTs from a specified wallet
|
||||
*
|
||||
* @param account - the address to burn NFTs from
|
||||
* @param tokenId - the tokenId to burn
|
||||
* @param amount - amount to burn
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The address of the wallet to burn NFTS from
|
||||
* const account = "0x...";
|
||||
* // The token ID to burn NFTs of
|
||||
* const tokenId = 0;
|
||||
* // The amount of this NFT you want to burn
|
||||
* const amount = 2;
|
||||
*
|
||||
* const result = await contract.edition.burn.from(account, tokenId, amount);
|
||||
* ```
|
||||
*/
|
||||
from = buildTransactionFunction(
|
||||
async (
|
||||
account: AddressOrEns,
|
||||
tokenId: BigNumberish,
|
||||
amount: BigNumberish,
|
||||
) => {
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "burn",
|
||||
args: [await resolveAddress(account), tokenId, amount],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Burn a batch of NFTs
|
||||
*
|
||||
* @remarks Burn the batch NFTs from the connected wallet
|
||||
*
|
||||
* @param tokenIds - the tokenIds to burn
|
||||
* @param amounts - amount of each token to burn
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The token IDs to burn NFTs of
|
||||
* const tokenIds = [0, 1];
|
||||
* // The amounts of each NFT you want to burn
|
||||
* const amounts = [2, 2];
|
||||
*
|
||||
* const result = await contract.edition.burn.batch(tokenIds, amounts);
|
||||
* ```
|
||||
*/
|
||||
batch = buildTransactionFunction(
|
||||
async (tokenIds: BigNumberish[], amounts: BigNumberish[]) => {
|
||||
const account = await this.contractWrapper.getSignerAddress();
|
||||
return this.batchFrom.prepare(account, tokenIds, amounts);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Burn a batch of NFTs
|
||||
*
|
||||
* @remarks Burn the batch NFTs from the specified wallet
|
||||
*
|
||||
* @param account - the address to burn NFTs from
|
||||
* @param tokenIds - the tokenIds to burn
|
||||
* @param amounts - amount of each token to burn
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The address of the wallet to burn NFTS from
|
||||
* const account = "0x...";
|
||||
* // The token IDs to burn NFTs of
|
||||
* const tokenIds = [0, 1];
|
||||
* // The amounts of each NFT you want to burn
|
||||
* const amounts = [2, 2];
|
||||
*
|
||||
* const result = await contract.edition.burn.batchFrom(account, tokenIds, amounts);
|
||||
* ```
|
||||
*/
|
||||
batchFrom = buildTransactionFunction(
|
||||
async (
|
||||
account: AddressOrEns,
|
||||
tokenIds: BigNumberish[],
|
||||
amounts: BigNumberish[],
|
||||
) => {
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "burnBatch",
|
||||
args: [await resolveAddress(account), tokenIds, amounts],
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* List ERC1155 NFTs
|
||||
* @remarks Easily list all the NFTs in a ERC1155 contract.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* const nfts = await contract.edition.query.all();
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc1155Enumerable implements DetectableFeature {
|
||||
featureName = FEATURE_EDITION_ENUMERABLE.name;
|
||||
private contractWrapper: ContractWrapper<BaseERC1155 & IERC1155Enumerable>;
|
||||
private erc1155: Erc1155;
|
||||
|
||||
constructor(
|
||||
erc1155: Erc1155,
|
||||
contractWrapper: ContractWrapper<BaseERC1155 & IERC1155Enumerable>,
|
||||
) {
|
||||
this.erc1155 = erc1155;
|
||||
this.contractWrapper = contractWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get All NFTs
|
||||
*
|
||||
* @remarks Get all the data associated with every NFT in this contract.
|
||||
*
|
||||
* By default, returns the first 100 NFTs, use queryParams to fetch more.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const nfts = await contract.edition.query.all();
|
||||
* ```
|
||||
* @param queryParams - optional filtering to only fetch a subset of results.
|
||||
* @returns The NFT metadata for all NFTs queried.
|
||||
*/
|
||||
public async all(queryParams?: QueryAllParams): Promise<NFT[]> {
|
||||
const start = BigNumber.from(queryParams?.start || 0).toNumber();
|
||||
const count = BigNumber.from(
|
||||
queryParams?.count || DEFAULT_QUERY_ALL_COUNT,
|
||||
).toNumber();
|
||||
const maxId = Math.min((await this.totalCount()).toNumber(), start + count);
|
||||
return await Promise.all(
|
||||
[...Array(maxId - start).keys()].map((i) =>
|
||||
this.erc1155.get((start + i).toString()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of NFTs minted
|
||||
* @remarks This returns the total number of NFTs minted in this contract, **not** the total supply of a given token.
|
||||
*
|
||||
* @returns the total number of NFTs minted in this contract
|
||||
* @public
|
||||
*/
|
||||
public async totalCount(): Promise<BigNumber> {
|
||||
return await this.contractWrapper.readContract.nextTokenIdToMint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the supply of token for a given tokenId.
|
||||
* @remarks This is **not** the sum of supply of all NFTs in the contract.
|
||||
*
|
||||
* @returns the total number of NFTs minted in this contract
|
||||
* @public
|
||||
*/
|
||||
public async totalCirculatingSupply(
|
||||
tokenId: BigNumberish,
|
||||
): Promise<BigNumber> {
|
||||
return await this.contractWrapper.readContract.totalSupply(tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all NFTs owned by a specific wallet
|
||||
*
|
||||
* @remarks Get all the data associated with the NFTs owned by a specific wallet.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet to get the NFTs of
|
||||
* const address = "{{wallet_address}}";
|
||||
* const nfts = await contract.edition.query.owned(address);
|
||||
* ```
|
||||
*
|
||||
* @returns The NFT metadata for all NFTs in the contract.
|
||||
*/
|
||||
public async owned(walletAddress?: AddressOrEns): Promise<NFT[]> {
|
||||
const address = await resolveAddress(
|
||||
walletAddress || (await this.contractWrapper.getSignerAddress()),
|
||||
);
|
||||
const maxId = await this.contractWrapper.readContract.nextTokenIdToMint();
|
||||
const balances = await this.contractWrapper.readContract.balanceOfBatch(
|
||||
Array(maxId.toNumber()).fill(address),
|
||||
Array.from(Array(maxId.toNumber()).keys()),
|
||||
);
|
||||
|
||||
const ownedBalances = balances
|
||||
.map((b, i) => {
|
||||
return {
|
||||
tokenId: i,
|
||||
balance: b,
|
||||
};
|
||||
})
|
||||
.filter((b) => b.balance.gt(0));
|
||||
return await Promise.all(
|
||||
ownedBalances.map(async (b) => {
|
||||
const editionMetadata = await this.erc1155.get(b.tokenId.toString());
|
||||
return {
|
||||
...editionMetadata,
|
||||
owner: address,
|
||||
quantityOwned: b.balance.toString(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Erc1155LazyMintable implements DetectableFeature {
|
||||
featureName = FEATURE_EDITION_LAZY_MINTABLE_V2.name;
|
||||
|
||||
/**
|
||||
* Delayed reveal
|
||||
* @remarks Create a batch of encrypted NFTs that can be revealed at a later time.
|
||||
* @example
|
||||
* ```javascript
|
||||
* // the real NFTs, these will be encrypted until you reveal them
|
||||
* const realNFTs = [{
|
||||
* name: "Common NFT #1",
|
||||
* description: "Common NFT, one of many.",
|
||||
* image: fs.readFileSync("path/to/image.png"),
|
||||
* }, {
|
||||
* name: "Super Rare NFT #2",
|
||||
* description: "You got a Super Rare NFT!",
|
||||
* image: fs.readFileSync("path/to/image.png"),
|
||||
* }];
|
||||
* // A placeholder NFT that people will get immediately in their wallet, and will be converted to the real NFT at reveal time
|
||||
* const placeholderNFT = {
|
||||
* name: "Hidden NFT",
|
||||
* description: "Will be revealed next week!"
|
||||
* };
|
||||
* // Create and encrypt the NFTs
|
||||
* await contract.edition.drop.revealer.createDelayedRevealBatch(
|
||||
* placeholderNFT,
|
||||
* realNFTs,
|
||||
* "my secret password",
|
||||
* );
|
||||
* // Whenever you're ready, reveal your NFTs at any time
|
||||
* const batchId = 0; // the batch to reveal
|
||||
* await contract.edition.drop.revealer.reveal(batchId, "my secret password");
|
||||
* ```
|
||||
*/
|
||||
public revealer: DelayedReveal<BaseDelayedRevealERC1155> | undefined;
|
||||
|
||||
private contractWrapper: ContractWrapper<BaseDropERC1155>;
|
||||
private erc1155: Erc1155;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
erc1155: Erc1155,
|
||||
contractWrapper: ContractWrapper<BaseDropERC1155>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc1155 = erc1155;
|
||||
this.contractWrapper = contractWrapper;
|
||||
|
||||
this.storage = storage;
|
||||
this.revealer = this.detectErc1155Revealable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a batch of NFTs to be claimed in the future
|
||||
*
|
||||
* @remarks Create batch allows you to create a batch of many NFTs in one transaction.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Custom metadata of the NFTs to create
|
||||
* const metadatas = [{
|
||||
* name: "Cool NFT",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
|
||||
* }, {
|
||||
* name: "Cool NFT",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"),
|
||||
* }];
|
||||
*
|
||||
* const results = await contract.erc1155.lazyMint(metadatas); // uploads and creates the NFTs on chain
|
||||
* const firstTokenId = results[0].id; // token id of the first created NFT
|
||||
* const firstNFT = await results[0].data(); // (optional) fetch details of the first created NFT
|
||||
* ```
|
||||
*
|
||||
* @param metadatas - The metadata to include in the batch.
|
||||
* @param options - optional upload progress callback
|
||||
*/
|
||||
lazyMint = buildTransactionFunction(
|
||||
async (
|
||||
metadatas: NFTMetadataOrUri[],
|
||||
options?: {
|
||||
onProgress: (event: UploadProgressEvent) => void;
|
||||
},
|
||||
): Promise<Transaction<TransactionResultWithId<NFTMetadata>[]>> => {
|
||||
const startFileNumber = await this.erc1155.nextTokenIdToMint();
|
||||
const batch = await uploadOrExtractURIs(
|
||||
metadatas,
|
||||
this.storage,
|
||||
startFileNumber.toNumber(),
|
||||
options,
|
||||
);
|
||||
// ensure baseUri is the same for the entire batch
|
||||
const baseUri = batch[0].substring(0, batch[0].lastIndexOf("/"));
|
||||
for (let i = 0; i < batch.length; i++) {
|
||||
const uri = batch[i].substring(0, batch[i].lastIndexOf("/"));
|
||||
if (baseUri !== uri) {
|
||||
throw new Error(
|
||||
`Can only create batches with the same base URI for every entry in the batch. Expected '${baseUri}' but got '${uri}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const parse = (receipt: ethers.providers.TransactionReceipt) => {
|
||||
const event = this.contractWrapper.parseLogs<TokensLazyMintedEvent>(
|
||||
"TokensLazyMinted",
|
||||
receipt?.logs,
|
||||
);
|
||||
const startingIndex = event[0].args.startTokenId;
|
||||
const endingIndex = event[0].args.endTokenId;
|
||||
const results: TransactionResultWithId<NFTMetadata>[] = [];
|
||||
for (let id = startingIndex; id.lte(endingIndex); id = id.add(1)) {
|
||||
results.push({
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc1155.getTokenMetadata(id),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
const prebuiltInfo = await getPrebuiltInfo(
|
||||
this.contractWrapper.readContract.address,
|
||||
this.contractWrapper.getProvider(),
|
||||
);
|
||||
if (
|
||||
this.isLegacyEditionDropContract(this.contractWrapper, prebuiltInfo)
|
||||
) {
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "lazyMint",
|
||||
args: [
|
||||
batch.length,
|
||||
`${baseUri.endsWith("/") ? baseUri : `${baseUri}/`}`,
|
||||
],
|
||||
parse,
|
||||
});
|
||||
} else {
|
||||
// new contracts/extensions have support for delayed reveal that adds an extra parameter to lazyMint
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "lazyMint",
|
||||
args: [
|
||||
batch.length,
|
||||
`${baseUri.endsWith("/") ? baseUri : `${baseUri}/`}`,
|
||||
ethers.utils.toUtf8Bytes(""),
|
||||
],
|
||||
parse,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/** ******************************
|
||||
* PRIVATE FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
private detectErc1155Revealable():
|
||||
| DelayedReveal<BaseDelayedRevealERC1155>
|
||||
| undefined {
|
||||
if (
|
||||
detectContractFeature<BaseDelayedRevealERC1155>(
|
||||
this.contractWrapper,
|
||||
"ERC1155Revealable",
|
||||
)
|
||||
) {
|
||||
return new DelayedReveal(
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
FEATURE_EDITION_REVEALABLE.name,
|
||||
() => this.erc1155.nextTokenIdToMint(),
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private isLegacyEditionDropContract(
|
||||
contractWrapper: ContractWrapper<any>,
|
||||
info: Awaited<ReturnType<typeof getPrebuiltInfo>>,
|
||||
): contractWrapper is ContractWrapper<DropERC1155_V2> {
|
||||
return (info && info.type === "DropERC1155" && info.version < 3) || false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint ERC1155 NFTs
|
||||
* @remarks NFT minting functionality that handles IPFS storage for you.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.edition.mint.to(walletAddress, nftMetadata);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc1155Mintable implements DetectableFeature {
|
||||
featureName = FEATURE_EDITION_MINTABLE.name;
|
||||
private contractWrapper: ContractWrapper<IMintableERC1155>;
|
||||
private erc1155: Erc1155;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
/**
|
||||
* Batch mint Tokens to many addresses
|
||||
*/
|
||||
public batch: Erc1155BatchMintable | undefined;
|
||||
|
||||
constructor(
|
||||
erc1155: Erc1155,
|
||||
contractWrapper: ContractWrapper<IMintableERC1155>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc1155 = erc1155;
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.storage = storage;
|
||||
this.batch = this.detectErc1155BatchMintable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint an NFT with a limited supply
|
||||
*
|
||||
* @remarks Mint an NFT with a limited supply to a specified wallet.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet you want to mint the NFT to
|
||||
* const toAddress = "{{wallet_address}}"
|
||||
*
|
||||
* // Custom metadata of the NFT, note that you can fully customize this metadata with other properties.
|
||||
* const metadata = {
|
||||
* name: "Cool NFT",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
|
||||
* }
|
||||
*
|
||||
* const metadataWithSupply = {
|
||||
* metadata,
|
||||
* supply: 1000, // The number of this NFT you want to mint
|
||||
* }
|
||||
*
|
||||
* const tx = await contract.edition.mint.to(toAddress, metadataWithSupply);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* const tokenId = tx.id; // the id of the NFT minted
|
||||
* const nft = await tx.data(); // (optional) fetch details of minted NFT
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
to = buildTransactionFunction(
|
||||
async (
|
||||
to: AddressOrEns,
|
||||
metadataWithSupply: EditionMetadataOrUri,
|
||||
): Promise<Transaction<TransactionResultWithId<NFT>>> => {
|
||||
const tx = (await this.getMintTransaction(
|
||||
to,
|
||||
metadataWithSupply,
|
||||
)) as any as Transaction<TransactionResultWithId<NFT>>;
|
||||
tx.setParse((receipt) => {
|
||||
const event = this.contractWrapper.parseLogs<TransferSingleEvent>(
|
||||
"TransferSingle",
|
||||
receipt?.logs,
|
||||
);
|
||||
if (event.length === 0) {
|
||||
throw new Error("TransferSingleEvent event not found");
|
||||
}
|
||||
const id = event[0].args.id;
|
||||
return {
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc1155.get(id.toString()),
|
||||
};
|
||||
});
|
||||
return tx;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @deprecated Use `contract.erc1155.mint.prepare(...args)` instead
|
||||
*/
|
||||
public async getMintTransaction(
|
||||
to: AddressOrEns,
|
||||
metadataWithSupply: EditionMetadataOrUri,
|
||||
): Promise<Transaction> {
|
||||
const uri = await uploadOrExtractURI(
|
||||
metadataWithSupply.metadata,
|
||||
this.storage,
|
||||
);
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "mintTo",
|
||||
args: [
|
||||
await resolveAddress(to),
|
||||
ethers.constants.MaxUint256,
|
||||
uri,
|
||||
metadataWithSupply.supply,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the supply of an existing NFT and mint it to a given wallet address
|
||||
*
|
||||
* @param to - the address to mint to
|
||||
* @param tokenId - the token id of the NFT to increase supply of
|
||||
* @param additionalSupply - the additional amount to mint
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet you want to mint the NFT to
|
||||
* const toAddress = "{{wallet_address}}"
|
||||
* const tokenId = 0;
|
||||
* const additionalSupply = 1000;
|
||||
*
|
||||
* const tx = await contract.edition.mint.additionalSupplyTo(toAddress, tokenId, additionalSupply);
|
||||
* ```
|
||||
*/
|
||||
additionalSupplyTo = buildTransactionFunction(
|
||||
async (
|
||||
to: AddressOrEns,
|
||||
tokenId: BigNumberish,
|
||||
additionalSupply: BigNumberish,
|
||||
): Promise<Transaction<TransactionResultWithId<NFT>>> => {
|
||||
const metadata = await this.erc1155.getTokenMetadata(tokenId);
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "mintTo",
|
||||
args: [
|
||||
await resolveAddress(to),
|
||||
tokenId,
|
||||
metadata.uri,
|
||||
additionalSupply,
|
||||
],
|
||||
parse: (receipt) => {
|
||||
return {
|
||||
id: BigNumber.from(tokenId),
|
||||
receipt,
|
||||
data: () => this.erc1155.get(tokenId),
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
private detectErc1155BatchMintable() {
|
||||
if (
|
||||
detectContractFeature<IMintableERC1155 & IMulticall>(
|
||||
this.contractWrapper,
|
||||
"ERC1155BatchMintable",
|
||||
)
|
||||
) {
|
||||
return new Erc1155BatchMintable(
|
||||
this.erc1155,
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint Many ERC1155 NFTs at once
|
||||
* @remarks NFT batch minting functionality that handles IPFS storage for you.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.edition.mint.batch.to(walletAddress, [nftMetadataWithSupply1, nftMetadataWithSupply2, ...]);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc1155BatchMintable implements DetectableFeature {
|
||||
featureName = FEATURE_EDITION_BATCH_MINTABLE.name;
|
||||
private contractWrapper: ContractWrapper<IMintableERC1155 & IMulticall>;
|
||||
private erc1155: Erc1155;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
erc1155: Erc1155,
|
||||
contractWrapper: ContractWrapper<IMintableERC1155 & IMulticall>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc1155 = erc1155;
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint Many NFTs with limited supplies
|
||||
*
|
||||
* @remarks Mint many different NFTs with limited supplies to a specified wallet.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet you want to mint the NFT to
|
||||
* const toAddress = "{{wallet_address}}"
|
||||
*
|
||||
* // Custom metadata and supplies of your NFTs
|
||||
* const metadataWithSupply = [{
|
||||
* supply: 50, // The number of this NFT you want to mint
|
||||
* metadata: {
|
||||
* name: "Cool NFT #1",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
|
||||
* },
|
||||
* }, {
|
||||
* supply: 100,
|
||||
* metadata: {
|
||||
* name: "Cool NFT #2",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
|
||||
* },
|
||||
* }];
|
||||
*
|
||||
* const tx = await contract.edition.mint.batch.to(toAddress, metadataWithSupply);
|
||||
* const receipt = tx[0].receipt; // same transaction receipt for all minted NFTs
|
||||
* const firstTokenId = tx[0].id; // token id of the first minted NFT
|
||||
* const firstNFT = await tx[0].data(); // (optional) fetch details of the first minted NFT
|
||||
* ```
|
||||
*/
|
||||
to = buildTransactionFunction(
|
||||
async (
|
||||
to: AddressOrEns,
|
||||
metadataWithSupply: EditionMetadataOrUri[],
|
||||
): Promise<Transaction<TransactionResultWithId<NFT>[]>> => {
|
||||
const metadatas = metadataWithSupply.map((a) => a.metadata);
|
||||
const supplies = metadataWithSupply.map((a) => a.supply);
|
||||
const uris = await uploadOrExtractURIs(metadatas, this.storage);
|
||||
const resolvedAddress = await resolveAddress(to);
|
||||
const encoded = await Promise.all(
|
||||
uris.map(async (uri, index) =>
|
||||
this.contractWrapper.readContract.interface.encodeFunctionData(
|
||||
"mintTo",
|
||||
[
|
||||
resolvedAddress,
|
||||
ethers.constants.MaxUint256,
|
||||
uri,
|
||||
supplies[index],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "multicall",
|
||||
args: [encoded],
|
||||
parse: (receipt) => {
|
||||
const events = this.contractWrapper.parseLogs<TokensMintedEvent>(
|
||||
"TokensMinted",
|
||||
receipt.logs,
|
||||
);
|
||||
if (events.length === 0 || events.length < metadatas.length) {
|
||||
throw new Error("TokenMinted event not found, minting failed");
|
||||
}
|
||||
return events.map((e) => {
|
||||
const id = e.args.tokenIdMinted;
|
||||
return {
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc1155.get(id),
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_TOKEN_BATCH_MINTABLE } from "../../constants/erc20-features";
|
||||
import { TokenMintInput } from "../../schema/tokens/token";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc20 } from "./erc-20";
|
||||
import { Transaction } from "./transactions";
|
||||
import type { IMintableERC20, IMulticall } from "@thirdweb-dev/contracts-js";
|
||||
|
||||
/**
|
||||
* Mint Many ERC20 Tokens at once
|
||||
* @remarks Token batch minting functionality that handles unit parsing for you.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.token.mint.batch.to(walletAddress, [nftMetadata1, nftMetadata2, ...]);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc20BatchMintable implements DetectableFeature {
|
||||
featureName = FEATURE_TOKEN_BATCH_MINTABLE.name;
|
||||
private contractWrapper: ContractWrapper<IMintableERC20 & IMulticall>;
|
||||
private erc20: Erc20;
|
||||
|
||||
constructor(
|
||||
erc20: Erc20,
|
||||
contractWrapper: ContractWrapper<IMintableERC20 & IMulticall>,
|
||||
) {
|
||||
this.erc20 = erc20;
|
||||
this.contractWrapper = contractWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint Tokens To Many Wallets
|
||||
*
|
||||
* @remarks Mint tokens to many wallets in one transaction.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Data of the tokens you want to mint
|
||||
* const data = [
|
||||
* {
|
||||
* toAddress: "{{wallet_address}}", // Address to mint tokens to
|
||||
* amount: 0.2, // How many tokens to mint to specified address
|
||||
* },
|
||||
* {
|
||||
* toAddress: "0x...",
|
||||
* amount: 1.4,
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* await contract.token.mint.batch(data);
|
||||
* ```
|
||||
*/
|
||||
to = buildTransactionFunction(async (args: TokenMintInput[]) => {
|
||||
const encoded: string[] = [];
|
||||
for (const arg of args) {
|
||||
encoded.push(
|
||||
this.contractWrapper.readContract.interface.encodeFunctionData(
|
||||
"mintTo",
|
||||
[
|
||||
await resolveAddress(arg.toAddress),
|
||||
await this.erc20.normalizeAmount(arg.amount),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "multicall",
|
||||
args: [encoded],
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_TOKEN_BURNABLE } from "../../constants/erc20-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { Amount } from "../../types/currency";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc20 } from "./erc-20";
|
||||
import { Transaction } from "./transactions";
|
||||
import type { IBurnableERC20 } from "@thirdweb-dev/contracts-js";
|
||||
|
||||
export class Erc20Burnable implements DetectableFeature {
|
||||
featureName = FEATURE_TOKEN_BURNABLE.name;
|
||||
|
||||
private erc20: Erc20;
|
||||
private contractWrapper: ContractWrapper<IBurnableERC20>;
|
||||
|
||||
constructor(erc20: Erc20, contractWrapper: ContractWrapper<IBurnableERC20>) {
|
||||
this.erc20 = erc20;
|
||||
this.contractWrapper = contractWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Burn Tokens
|
||||
*
|
||||
* @remarks Burn tokens held by the connected wallet
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The amount of this token you want to burn
|
||||
* const amount = 1.2;
|
||||
*
|
||||
* await contract.token.burn.tokens(amount);
|
||||
* ```
|
||||
*/
|
||||
tokens = buildTransactionFunction(async (amount: Amount) => {
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "burn",
|
||||
args: [await this.erc20.normalizeAmount(amount)],
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Burn Tokens
|
||||
*
|
||||
* @remarks Burn tokens held by the specified wallet
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet sending the tokens
|
||||
* const holderAddress = "{{wallet_address}}";
|
||||
*
|
||||
* // The amount of this token you want to burn
|
||||
* const amount = 1.2;
|
||||
*
|
||||
* await contract.token.burn.from(holderAddress, amount);
|
||||
* ```
|
||||
*/
|
||||
from = buildTransactionFunction(
|
||||
async (holder: AddressOrEns, amount: Amount) => {
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "burnFrom",
|
||||
args: [
|
||||
await resolveAddress(holder),
|
||||
await this.erc20.normalizeAmount(amount),
|
||||
],
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_TOKEN_CLAIM_CONDITIONS_V2 } from "../../constants/erc20-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { CustomContractSchema } from "../../schema/contracts/custom";
|
||||
import { ClaimOptions } from "../../types";
|
||||
import { Amount } from "../../types/currency";
|
||||
import { BaseDropERC20 } from "../../types/eips";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { ContractMetadata } from "./contract-metadata";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { DropClaimConditions } from "./drop-claim-conditions";
|
||||
import { Erc20 } from "./erc-20";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
|
||||
/**
|
||||
* Configure and claim ERC20 tokens
|
||||
* @remarks Manage claim phases and claim ERC20 tokens that have been lazily minted.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.token.drop.claim.to("0x...", quantity);
|
||||
* ```
|
||||
*/
|
||||
export class Erc20ClaimableWithConditions implements DetectableFeature {
|
||||
featureName = FEATURE_TOKEN_CLAIM_CONDITIONS_V2.name;
|
||||
/**
|
||||
* Configure claim conditions
|
||||
* @remarks Define who can claim NFTs in the collection, when and how many.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const presaleStartTime = new Date();
|
||||
* const publicSaleStartTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const claimConditions = [
|
||||
* {
|
||||
* startTime: presaleStartTime, // start the presale now
|
||||
* maxClaimableSupply: 2, // limit how many mints for this presale
|
||||
* price: 0.01, // presale price
|
||||
* snapshot: ['0x...', '0x...'], // limit minting to only certain addresses
|
||||
* },
|
||||
* {
|
||||
* startTime: publicSaleStartTime, // 24h after presale, start public sale
|
||||
* price: 0.08, // public sale price
|
||||
* }
|
||||
* ]);
|
||||
* await contract.token.drop.claim.conditions.set(claimConditions);
|
||||
* ```
|
||||
*/
|
||||
public conditions: DropClaimConditions<BaseDropERC20>;
|
||||
private contractWrapper: ContractWrapper<BaseDropERC20>;
|
||||
private erc20: Erc20;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
erc20: Erc20,
|
||||
contractWrapper: ContractWrapper<BaseDropERC20>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc20 = erc20;
|
||||
this.contractWrapper = contractWrapper;
|
||||
|
||||
this.storage = storage;
|
||||
const metadata = new ContractMetadata(
|
||||
this.contractWrapper,
|
||||
CustomContractSchema,
|
||||
this.storage,
|
||||
);
|
||||
this.conditions = new DropClaimConditions(
|
||||
this.contractWrapper,
|
||||
metadata,
|
||||
this.storage,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim a certain amount of tokens to a specific Wallet
|
||||
*
|
||||
* @remarks Let the specified wallet claim Tokens.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const address = "{{wallet_address}}"; // address of the wallet you want to claim the NFTs
|
||||
* const quantity = 42.69; // how many tokens you want to claim
|
||||
*
|
||||
* const tx = await contract.token.drop.claim.to(address, quantity);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* ```
|
||||
*
|
||||
* @param destinationAddress - Address you want to send the token to
|
||||
* @param amount - Quantity of the tokens you want to claim
|
||||
* @param checkERC20Allowance - Optional, check if the wallet has enough ERC20 allowance to claim the tokens, and if not, approve the transfer
|
||||
* @param claimData
|
||||
* @returns - The transaction receipt
|
||||
*/
|
||||
to = buildTransactionFunction(
|
||||
async (
|
||||
destinationAddress: AddressOrEns,
|
||||
amount: Amount,
|
||||
options?: ClaimOptions,
|
||||
) => {
|
||||
const quantity = await this.erc20.normalizeAmount(amount);
|
||||
return await this.conditions.getClaimTransaction(
|
||||
destinationAddress,
|
||||
quantity,
|
||||
options,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { BaseDropERC20 } from "../../types/eips";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc20 } from "./erc-20";
|
||||
import { Erc20ClaimableWithConditions } from "./erc-20-claimable-with-conditions";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
|
||||
/**
|
||||
* Configure and claim ERC20 tokens
|
||||
* @remarks Manage claim phases and claim ERC20 tokens that have been lazily minted.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.token.drop.claim.to("0x...", quantity);
|
||||
* ```
|
||||
*/
|
||||
export class Erc20Droppable {
|
||||
/**
|
||||
* Configure claim conditions
|
||||
* @remarks Define who can claim NFTs in the collection, when and how many.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const presaleStartTime = new Date();
|
||||
* const publicSaleStartTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const claimConditions = [
|
||||
* {
|
||||
* startTime: presaleStartTime, // start the presale now
|
||||
* maxClaimableSupply: 2, // limit how many mints for this presale
|
||||
* price: 0.01, // presale price
|
||||
* snapshot: ['0x...', '0x...'], // limit minting to only certain addresses
|
||||
* },
|
||||
* {
|
||||
* startTime: publicSaleStartTime, // 24h after presale, start public sale
|
||||
* price: 0.08, // public sale price
|
||||
* }
|
||||
* ]);
|
||||
* await contract.nft.drop.claim.conditions.set(claimConditions);
|
||||
* ```
|
||||
*/
|
||||
public claim: Erc20ClaimableWithConditions;
|
||||
private contractWrapper: ContractWrapper<BaseDropERC20>;
|
||||
private erc20: Erc20;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
erc20: Erc20,
|
||||
contractWrapper: ContractWrapper<BaseDropERC20>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc20 = erc20;
|
||||
this.contractWrapper = contractWrapper;
|
||||
|
||||
this.storage = storage;
|
||||
this.claim = new Erc20ClaimableWithConditions(
|
||||
this.erc20,
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import { detectContractFeature } from "../../common/feature-detection/detectContractFeature";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_TOKEN_MINTABLE } from "../../constants/erc20-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { Amount } from "../../types";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc20 } from "./erc-20";
|
||||
import { Erc20BatchMintable } from "./erc-20-batch-mintable";
|
||||
import { Transaction } from "./transactions";
|
||||
import type { IMintableERC20, IMulticall } from "@thirdweb-dev/contracts-js";
|
||||
|
||||
/**
|
||||
* Mint ERC20 Tokens
|
||||
* @remarks Token minting functionality that handles unit parsing for you.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.nft.mint.to(walletAddress, nftMetadata);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc20Mintable implements DetectableFeature {
|
||||
featureName = FEATURE_TOKEN_MINTABLE.name;
|
||||
private contractWrapper: ContractWrapper<IMintableERC20>;
|
||||
private erc20: Erc20;
|
||||
|
||||
/**
|
||||
* Batch mint Tokens to many addresses
|
||||
*/
|
||||
public batch: Erc20BatchMintable | undefined;
|
||||
|
||||
constructor(erc20: Erc20, contractWrapper: ContractWrapper<IMintableERC20>) {
|
||||
this.erc20 = erc20;
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.batch = this.detectErc20BatchMintable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint Tokens
|
||||
*
|
||||
* @remarks Mint tokens to a specified address.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const toAddress = "{{wallet_address}}"; // Address of the wallet you want to mint the tokens to
|
||||
* const amount = "1.5"; // The amount of this token you want to mint
|
||||
* await contract.token.mint.to(toAddress, amount);
|
||||
* ```
|
||||
*/
|
||||
to = buildTransactionFunction(async (to: AddressOrEns, amount: Amount) => {
|
||||
return await this.getMintTransaction(to, amount);
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated Use `contract.erc20.mint.prepare(...args)` instead
|
||||
*/
|
||||
public async getMintTransaction(
|
||||
to: AddressOrEns,
|
||||
amount: Amount,
|
||||
): Promise<Transaction> {
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "mintTo",
|
||||
args: [
|
||||
await resolveAddress(to),
|
||||
await this.erc20.normalizeAmount(amount),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
private detectErc20BatchMintable() {
|
||||
if (
|
||||
detectContractFeature<IMintableERC20 & IMulticall>(
|
||||
this.contractWrapper,
|
||||
"ERC20BatchMintable",
|
||||
)
|
||||
) {
|
||||
return new Erc20BatchMintable(this.erc20, this.contractWrapper);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
import { normalizePriceValue } from "../../common/currency/normalizePriceValue";
|
||||
import { setErc20Allowance } from "../../common/currency/setErc20Allowance";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_TOKEN_SIGNATURE_MINTABLE } from "../../constants/erc20-features";
|
||||
import type { TokenInitializer } from "../../contracts";
|
||||
import {
|
||||
FilledSignaturePayload20,
|
||||
MintRequest20,
|
||||
PayloadToSign20,
|
||||
PayloadWithUri20,
|
||||
Signature20PayloadInput,
|
||||
Signature20PayloadOutput,
|
||||
SignedPayload20,
|
||||
} from "../../schema/contracts/common/signature";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { ContractRoles } from "./contract-roles";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Transaction } from "./transactions";
|
||||
import type { ITokenERC20, TokenERC20 } from "@thirdweb-dev/contracts-js";
|
||||
import { BigNumber, ethers } from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
* Enables generating ERC20 Tokens with rules and an associated signature, which can then be minted by anyone securely
|
||||
* @public
|
||||
*/
|
||||
// TODO consolidate into a single class
|
||||
export class Erc20SignatureMintable implements DetectableFeature {
|
||||
featureName = FEATURE_TOKEN_SIGNATURE_MINTABLE.name;
|
||||
|
||||
private contractWrapper: ContractWrapper<TokenERC20>;
|
||||
private roles:
|
||||
| ContractRoles<TokenERC20, (typeof TokenInitializer.roles)[number]>
|
||||
| undefined;
|
||||
|
||||
constructor(
|
||||
contractWrapper: ContractWrapper<TokenERC20>,
|
||||
roles?: ContractRoles<TokenERC20, (typeof TokenInitializer.roles)[number]>,
|
||||
) {
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint tokens from a signature
|
||||
*
|
||||
* @remarks Mint a certain amount of tokens from a previously generated signature.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // see how to craft a payload to sign in the `generate()` documentation
|
||||
* const signedPayload = contract.erc20.signature.generate(payload);
|
||||
*
|
||||
* // Use the signed payload to mint the tokens
|
||||
* const tx = contract.erc20.signature.mint(signedPayload);
|
||||
* ```
|
||||
* @param signedPayload - the previously generated payload and signature with {@link Erc20SignatureMintable.generate}
|
||||
* @twfeature ERC20SignatureMintable
|
||||
*/
|
||||
mint = buildTransactionFunction(async (signedPayload: SignedPayload20) => {
|
||||
const mintRequest = signedPayload.payload;
|
||||
const signature = signedPayload.signature;
|
||||
const message = await this.mapPayloadToContractStruct(mintRequest);
|
||||
const overrides = await this.contractWrapper.getCallOverrides();
|
||||
// TODO: Transaction Sequence Pattern
|
||||
await setErc20Allowance(
|
||||
this.contractWrapper,
|
||||
BigNumber.from(message.price),
|
||||
mintRequest.currencyAddress,
|
||||
overrides,
|
||||
);
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "mintWithSignature",
|
||||
args: [message, signature],
|
||||
overrides,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Mint any number of generated tokens signatures at once
|
||||
* @remarks Mint multiple token signatures in one transaction. Note that this is only possible for free mints (cannot batch mints with a price attached to it for security reasons)
|
||||
* @param signedPayloads - the array of signed payloads to mint
|
||||
* @twfeature ERC20SignatureMintable
|
||||
*/
|
||||
mintBatch = buildTransactionFunction(
|
||||
async (signedPayloads: SignedPayload20[]) => {
|
||||
const contractPayloads = await Promise.all(
|
||||
signedPayloads.map(async (s) => {
|
||||
const message = await this.mapPayloadToContractStruct(s.payload);
|
||||
const signature = s.signature;
|
||||
const price = s.payload.price;
|
||||
if (BigNumber.from(price).gt(0)) {
|
||||
throw new Error(
|
||||
"Can only batch free mints. For mints with a price, use regular mint()",
|
||||
);
|
||||
}
|
||||
return {
|
||||
message,
|
||||
signature,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const encoded = contractPayloads.map((p) => {
|
||||
return this.contractWrapper.readContract.interface.encodeFunctionData(
|
||||
"mintWithSignature",
|
||||
[p.message, p.signature],
|
||||
);
|
||||
});
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "multicall",
|
||||
args: [encoded],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Verify that a payload is correctly signed
|
||||
* @param signedPayload - the payload to verify
|
||||
* @twfeature ERC20SignatureMintable
|
||||
*
|
||||
* ```javascript
|
||||
* const startTime = new Date();
|
||||
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const payload = {
|
||||
* quantity: 4.2, // The quantity of tokens to be minted
|
||||
* to: {{wallet_address}}, // Who will receive the tokens
|
||||
* price: 0.5, // the price to pay for minting those tokens
|
||||
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
|
||||
* mintStartTime: startTime, // can mint anytime from now
|
||||
* mintEndTime: endTime, // to 24h from now,
|
||||
* primarySaleRecipient: "0x...", // custom sale recipient for this token mint
|
||||
* };
|
||||
*
|
||||
* const signedPayload = await contract.erc20.signature.generate(payload);
|
||||
* // Now you can verify if the signed payload is valid
|
||||
* const isValid = await contract.erc20.signature.verify(signedPayload);
|
||||
* ```
|
||||
*/
|
||||
public async verify(signedPayload: SignedPayload20): Promise<boolean> {
|
||||
const mintRequest = signedPayload.payload;
|
||||
const signature = signedPayload.signature;
|
||||
const message = await this.mapPayloadToContractStruct(mintRequest);
|
||||
const verification: [boolean, string] =
|
||||
await this.contractWrapper.readContract.verify(message, signature);
|
||||
return verification[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a signature that can be used to mint a certain amount of tokens
|
||||
*
|
||||
* @remarks Takes in a quantity of tokens, some conditions for how it can be minted and signs it with your private key. The generated signature can then be used to mint those tokens using the exact payload and signature generated.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const startTime = new Date();
|
||||
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const payload = {
|
||||
* quantity: 4.2, // The quantity of tokens to be minted
|
||||
* to: {{wallet_address}}, // Who will receive the tokens
|
||||
* price: 0.5, // the price to pay for minting those tokens
|
||||
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
|
||||
* mintStartTime: startTime, // can mint anytime from now
|
||||
* mintEndTime: endTime, // to 24h from now,
|
||||
* primarySaleRecipient: "0x...", // custom sale recipient for this token mint
|
||||
* };
|
||||
*
|
||||
* const signedPayload = await contract.erc20.signature.generate(payload);
|
||||
* // now anyone can use these to mint the NFT using `contract.erc20.signature.mint(signedPayload)`
|
||||
* ```
|
||||
* @param mintRequest - the payload to sign
|
||||
* @returns the signed payload and the corresponding signature
|
||||
* @twfeature ERC20SignatureMintable
|
||||
*/
|
||||
public async generate(
|
||||
mintRequest: PayloadToSign20,
|
||||
): Promise<SignedPayload20> {
|
||||
return (await this.generateBatch([mintRequest]))[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a batch of signatures that can be used to mint many token signatures.
|
||||
*
|
||||
* @remarks See {@link Erc20SignatureMintable.generate}
|
||||
*
|
||||
* @param payloadsToSign - the payloads to sign
|
||||
* @returns an array of payloads and signatures
|
||||
* @twfeature ERC20SignatureMintable
|
||||
*/
|
||||
public async generateBatch(
|
||||
payloadsToSign: PayloadToSign20[],
|
||||
): Promise<SignedPayload20[]> {
|
||||
await this.roles?.verify(
|
||||
["minter"],
|
||||
await this.contractWrapper.getSignerAddress(),
|
||||
);
|
||||
|
||||
const parsedRequests: FilledSignaturePayload20[] = await Promise.all(
|
||||
payloadsToSign.map((m) => Signature20PayloadInput.parseAsync(m)),
|
||||
);
|
||||
|
||||
const chainId = await this.contractWrapper.getChainID();
|
||||
const signer = this.contractWrapper.getSigner();
|
||||
invariant(signer, "No signer available");
|
||||
|
||||
// ERC20Permit (EIP-712) spec differs from signature mint 721, 1155.
|
||||
const name = await this.contractWrapper.readContract.name();
|
||||
|
||||
return await Promise.all(
|
||||
parsedRequests.map(async (m) => {
|
||||
const finalPayload = await Signature20PayloadOutput.parseAsync(m);
|
||||
const signature = await this.contractWrapper.signTypedData(
|
||||
signer,
|
||||
{
|
||||
name,
|
||||
version: "1",
|
||||
chainId,
|
||||
verifyingContract: this.contractWrapper.readContract.address,
|
||||
},
|
||||
{ MintRequest: MintRequest20 },
|
||||
await this.mapPayloadToContractStruct(finalPayload),
|
||||
);
|
||||
return {
|
||||
payload: finalPayload,
|
||||
signature: signature.toString(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/** ******************************
|
||||
* PRIVATE FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
/**
|
||||
* Maps a payload to the format expected by the contract
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param mintRequest - The payload to map.
|
||||
* @returns - The mapped payload.
|
||||
*/
|
||||
private async mapPayloadToContractStruct(
|
||||
mintRequest: PayloadWithUri20,
|
||||
): Promise<ITokenERC20.MintRequestStructOutput> {
|
||||
const normalizedPrice = await normalizePriceValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
mintRequest.price,
|
||||
mintRequest.currencyAddress,
|
||||
);
|
||||
const amountWithDecimals = ethers.utils.parseUnits(
|
||||
mintRequest.quantity,
|
||||
await this.contractWrapper.readContract.decimals(),
|
||||
);
|
||||
return {
|
||||
to: mintRequest.to,
|
||||
primarySaleRecipient: mintRequest.primarySaleRecipient,
|
||||
quantity: amountWithDecimals,
|
||||
price: normalizedPrice,
|
||||
currency: mintRequest.currencyAddress,
|
||||
validityEndTimestamp: mintRequest.mintEndTime,
|
||||
validityStartTimestamp: mintRequest.mintStartTime,
|
||||
uid: mintRequest.uid,
|
||||
} as ITokenERC20.MintRequestStructOutput;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AmountSchema } from "../../../core/schema/shared";
|
||||
import { assertEnabled } from "../../common/feature-detection/assertEnabled";
|
||||
import { detectContractFeature } from "../../common/feature-detection/detectContractFeature";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import {
|
||||
FEATURE_TOKEN,
|
||||
@@ -11,35 +11,57 @@ import {
|
||||
FEATURE_TOKEN_SIGNATURE_MINTABLE,
|
||||
FEATURE_TOKEN_CLAIM_CONDITIONS_V2,
|
||||
} from "../../constants/erc20-features";
|
||||
import { Address } from "../../schema/shared/Address";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { TokenMintInput } from "../../schema/tokens/token";
|
||||
import { Currency, CurrencyValue, Amount, ClaimOptions } from "../../types";
|
||||
import {
|
||||
import type { Address } from "../../schema/shared/Address";
|
||||
import type { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import type { TokenMintInput } from "../../schema/tokens/token";
|
||||
import type {
|
||||
Currency,
|
||||
CurrencyValue,
|
||||
Amount,
|
||||
ClaimOptions,
|
||||
} from "../../types";
|
||||
import type {
|
||||
BaseERC20,
|
||||
BaseSignatureMintERC20,
|
||||
BaseDropERC20,
|
||||
} from "../../types/eips";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import type { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { UpdateableNetwork } from "../interfaces/contract";
|
||||
import type { NetworkInput } from "../types";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc20Burnable } from "./erc-20-burnable";
|
||||
import { Erc20Droppable } from "./erc-20-droppable";
|
||||
import { Erc20Mintable } from "./erc-20-mintable";
|
||||
import { Erc20SignatureMintable } from "./erc-20-signature-mintable";
|
||||
import type { ContractWrapper } from "./contract-wrapper";
|
||||
import { Transaction } from "./transactions";
|
||||
import type {
|
||||
TokenERC20,
|
||||
DropERC20,
|
||||
IMintableERC20,
|
||||
IBurnableERC20,
|
||||
IMulticall,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { ethers, BigNumber, BigNumberish } from "ethers";
|
||||
import { fetchCurrencyMetadata } from "../../common/currency/fetchCurrencyMetadata";
|
||||
import { fetchCurrencyValue } from "../../common/currency/fetchCurrencyValue";
|
||||
|
||||
import { normalizePriceValue } from "../../common/currency/normalizePriceValue";
|
||||
import { setErc20Allowance } from "../../common/currency/setErc20Allowance";
|
||||
import type { TokenInitializer } from "../../contracts";
|
||||
import {
|
||||
FilledSignaturePayload20,
|
||||
MintRequest20,
|
||||
PayloadToSign20,
|
||||
PayloadWithUri20,
|
||||
Signature20PayloadInput,
|
||||
Signature20PayloadOutput,
|
||||
SignedPayload20,
|
||||
} from "../../schema/contracts/common/signature";
|
||||
import { ContractRoles } from "./contract-roles";
|
||||
import type { ITokenERC20 } from "@thirdweb-dev/contracts-js";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
import { CustomContractSchema } from "../../schema/contracts/custom";
|
||||
import { ContractMetadata } from "./contract-metadata";
|
||||
import { DropClaimConditions } from "./drop-claim-conditions";
|
||||
|
||||
/**
|
||||
* Standard ERC20 Token functions
|
||||
* @remarks Basic functionality for a ERC20 contract that handles all unit transformation for you.
|
||||
@@ -687,3 +709,599 @@ export class Erc20<
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class Erc20Burnable implements DetectableFeature {
|
||||
featureName = FEATURE_TOKEN_BURNABLE.name;
|
||||
|
||||
private erc20: Erc20;
|
||||
private contractWrapper: ContractWrapper<IBurnableERC20>;
|
||||
|
||||
constructor(erc20: Erc20, contractWrapper: ContractWrapper<IBurnableERC20>) {
|
||||
this.erc20 = erc20;
|
||||
this.contractWrapper = contractWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Burn Tokens
|
||||
*
|
||||
* @remarks Burn tokens held by the connected wallet
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // The amount of this token you want to burn
|
||||
* const amount = 1.2;
|
||||
*
|
||||
* await contract.token.burn.tokens(amount);
|
||||
* ```
|
||||
*/
|
||||
tokens = buildTransactionFunction(async (amount: Amount) => {
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "burn",
|
||||
args: [await this.erc20.normalizeAmount(amount)],
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Burn Tokens
|
||||
*
|
||||
* @remarks Burn tokens held by the specified wallet
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet sending the tokens
|
||||
* const holderAddress = "{{wallet_address}}";
|
||||
*
|
||||
* // The amount of this token you want to burn
|
||||
* const amount = 1.2;
|
||||
*
|
||||
* await contract.token.burn.from(holderAddress, amount);
|
||||
* ```
|
||||
*/
|
||||
from = buildTransactionFunction(
|
||||
async (holder: AddressOrEns, amount: Amount) => {
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "burnFrom",
|
||||
args: [
|
||||
await resolveAddress(holder),
|
||||
await this.erc20.normalizeAmount(amount),
|
||||
],
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure and claim ERC20 tokens
|
||||
* @remarks Manage claim phases and claim ERC20 tokens that have been lazily minted.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.token.drop.claim.to("0x...", quantity);
|
||||
* ```
|
||||
*/
|
||||
export class Erc20Droppable {
|
||||
/**
|
||||
* Configure claim conditions
|
||||
* @remarks Define who can claim NFTs in the collection, when and how many.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const presaleStartTime = new Date();
|
||||
* const publicSaleStartTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const claimConditions = [
|
||||
* {
|
||||
* startTime: presaleStartTime, // start the presale now
|
||||
* maxClaimableSupply: 2, // limit how many mints for this presale
|
||||
* price: 0.01, // presale price
|
||||
* snapshot: ['0x...', '0x...'], // limit minting to only certain addresses
|
||||
* },
|
||||
* {
|
||||
* startTime: publicSaleStartTime, // 24h after presale, start public sale
|
||||
* price: 0.08, // public sale price
|
||||
* }
|
||||
* ]);
|
||||
* await contract.nft.drop.claim.conditions.set(claimConditions);
|
||||
* ```
|
||||
*/
|
||||
public claim: Erc20ClaimableWithConditions;
|
||||
private contractWrapper: ContractWrapper<BaseDropERC20>;
|
||||
private erc20: Erc20;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
erc20: Erc20,
|
||||
contractWrapper: ContractWrapper<BaseDropERC20>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc20 = erc20;
|
||||
this.contractWrapper = contractWrapper;
|
||||
|
||||
this.storage = storage;
|
||||
this.claim = new Erc20ClaimableWithConditions(
|
||||
this.erc20,
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint ERC20 Tokens
|
||||
* @remarks Token minting functionality that handles unit parsing for you.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.nft.mint.to(walletAddress, nftMetadata);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc20Mintable implements DetectableFeature {
|
||||
featureName = FEATURE_TOKEN_MINTABLE.name;
|
||||
private contractWrapper: ContractWrapper<IMintableERC20>;
|
||||
private erc20: Erc20;
|
||||
|
||||
/**
|
||||
* Batch mint Tokens to many addresses
|
||||
*/
|
||||
public batch: Erc20BatchMintable | undefined;
|
||||
|
||||
constructor(erc20: Erc20, contractWrapper: ContractWrapper<IMintableERC20>) {
|
||||
this.erc20 = erc20;
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.batch = this.detectErc20BatchMintable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint Tokens
|
||||
*
|
||||
* @remarks Mint tokens to a specified address.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const toAddress = "{{wallet_address}}"; // Address of the wallet you want to mint the tokens to
|
||||
* const amount = "1.5"; // The amount of this token you want to mint
|
||||
* await contract.token.mint.to(toAddress, amount);
|
||||
* ```
|
||||
*/
|
||||
to = buildTransactionFunction(async (to: AddressOrEns, amount: Amount) => {
|
||||
return await this.getMintTransaction(to, amount);
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated Use `contract.erc20.mint.prepare(...args)` instead
|
||||
*/
|
||||
public async getMintTransaction(
|
||||
to: AddressOrEns,
|
||||
amount: Amount,
|
||||
): Promise<Transaction> {
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "mintTo",
|
||||
args: [
|
||||
await resolveAddress(to),
|
||||
await this.erc20.normalizeAmount(amount),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
private detectErc20BatchMintable() {
|
||||
if (
|
||||
detectContractFeature<IMintableERC20 & IMulticall>(
|
||||
this.contractWrapper,
|
||||
"ERC20BatchMintable",
|
||||
)
|
||||
) {
|
||||
return new Erc20BatchMintable(this.erc20, this.contractWrapper);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables generating ERC20 Tokens with rules and an associated signature, which can then be minted by anyone securely
|
||||
* @public
|
||||
*/
|
||||
// TODO consolidate into a single class
|
||||
export class Erc20SignatureMintable implements DetectableFeature {
|
||||
featureName = FEATURE_TOKEN_SIGNATURE_MINTABLE.name;
|
||||
|
||||
private contractWrapper: ContractWrapper<TokenERC20>;
|
||||
private roles:
|
||||
| ContractRoles<TokenERC20, (typeof TokenInitializer.roles)[number]>
|
||||
| undefined;
|
||||
|
||||
constructor(
|
||||
contractWrapper: ContractWrapper<TokenERC20>,
|
||||
roles?: ContractRoles<TokenERC20, (typeof TokenInitializer.roles)[number]>,
|
||||
) {
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint tokens from a signature
|
||||
*
|
||||
* @remarks Mint a certain amount of tokens from a previously generated signature.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // see how to craft a payload to sign in the `generate()` documentation
|
||||
* const signedPayload = contract.erc20.signature.generate(payload);
|
||||
*
|
||||
* // Use the signed payload to mint the tokens
|
||||
* const tx = contract.erc20.signature.mint(signedPayload);
|
||||
* ```
|
||||
* @param signedPayload - the previously generated payload and signature with {@link Erc20SignatureMintable.generate}
|
||||
* @twfeature ERC20SignatureMintable
|
||||
*/
|
||||
mint = buildTransactionFunction(async (signedPayload: SignedPayload20) => {
|
||||
const mintRequest = signedPayload.payload;
|
||||
const signature = signedPayload.signature;
|
||||
const message = await this.mapPayloadToContractStruct(mintRequest);
|
||||
const overrides = await this.contractWrapper.getCallOverrides();
|
||||
// TODO: Transaction Sequence Pattern
|
||||
await setErc20Allowance(
|
||||
this.contractWrapper,
|
||||
BigNumber.from(message.price),
|
||||
mintRequest.currencyAddress,
|
||||
overrides,
|
||||
);
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "mintWithSignature",
|
||||
args: [message, signature],
|
||||
overrides,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Mint any number of generated tokens signatures at once
|
||||
* @remarks Mint multiple token signatures in one transaction. Note that this is only possible for free mints (cannot batch mints with a price attached to it for security reasons)
|
||||
* @param signedPayloads - the array of signed payloads to mint
|
||||
* @twfeature ERC20SignatureMintable
|
||||
*/
|
||||
mintBatch = buildTransactionFunction(
|
||||
async (signedPayloads: SignedPayload20[]) => {
|
||||
const contractPayloads = await Promise.all(
|
||||
signedPayloads.map(async (s) => {
|
||||
const message = await this.mapPayloadToContractStruct(s.payload);
|
||||
const signature = s.signature;
|
||||
const price = s.payload.price;
|
||||
if (BigNumber.from(price).gt(0)) {
|
||||
throw new Error(
|
||||
"Can only batch free mints. For mints with a price, use regular mint()",
|
||||
);
|
||||
}
|
||||
return {
|
||||
message,
|
||||
signature,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const encoded = contractPayloads.map((p) => {
|
||||
return this.contractWrapper.readContract.interface.encodeFunctionData(
|
||||
"mintWithSignature",
|
||||
[p.message, p.signature],
|
||||
);
|
||||
});
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "multicall",
|
||||
args: [encoded],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Verify that a payload is correctly signed
|
||||
* @param signedPayload - the payload to verify
|
||||
* @twfeature ERC20SignatureMintable
|
||||
*
|
||||
* ```javascript
|
||||
* const startTime = new Date();
|
||||
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const payload = {
|
||||
* quantity: 4.2, // The quantity of tokens to be minted
|
||||
* to: {{wallet_address}}, // Who will receive the tokens
|
||||
* price: 0.5, // the price to pay for minting those tokens
|
||||
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
|
||||
* mintStartTime: startTime, // can mint anytime from now
|
||||
* mintEndTime: endTime, // to 24h from now,
|
||||
* primarySaleRecipient: "0x...", // custom sale recipient for this token mint
|
||||
* };
|
||||
*
|
||||
* const signedPayload = await contract.erc20.signature.generate(payload);
|
||||
* // Now you can verify if the signed payload is valid
|
||||
* const isValid = await contract.erc20.signature.verify(signedPayload);
|
||||
* ```
|
||||
*/
|
||||
public async verify(signedPayload: SignedPayload20): Promise<boolean> {
|
||||
const mintRequest = signedPayload.payload;
|
||||
const signature = signedPayload.signature;
|
||||
const message = await this.mapPayloadToContractStruct(mintRequest);
|
||||
const verification: [boolean, string] =
|
||||
await this.contractWrapper.readContract.verify(message, signature);
|
||||
return verification[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a signature that can be used to mint a certain amount of tokens
|
||||
*
|
||||
* @remarks Takes in a quantity of tokens, some conditions for how it can be minted and signs it with your private key. The generated signature can then be used to mint those tokens using the exact payload and signature generated.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const startTime = new Date();
|
||||
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const payload = {
|
||||
* quantity: 4.2, // The quantity of tokens to be minted
|
||||
* to: {{wallet_address}}, // Who will receive the tokens
|
||||
* price: 0.5, // the price to pay for minting those tokens
|
||||
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
|
||||
* mintStartTime: startTime, // can mint anytime from now
|
||||
* mintEndTime: endTime, // to 24h from now,
|
||||
* primarySaleRecipient: "0x...", // custom sale recipient for this token mint
|
||||
* };
|
||||
*
|
||||
* const signedPayload = await contract.erc20.signature.generate(payload);
|
||||
* // now anyone can use these to mint the NFT using `contract.erc20.signature.mint(signedPayload)`
|
||||
* ```
|
||||
* @param mintRequest - the payload to sign
|
||||
* @returns the signed payload and the corresponding signature
|
||||
* @twfeature ERC20SignatureMintable
|
||||
*/
|
||||
public async generate(
|
||||
mintRequest: PayloadToSign20,
|
||||
): Promise<SignedPayload20> {
|
||||
return (await this.generateBatch([mintRequest]))[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a batch of signatures that can be used to mint many token signatures.
|
||||
*
|
||||
* @remarks See {@link Erc20SignatureMintable.generate}
|
||||
*
|
||||
* @param payloadsToSign - the payloads to sign
|
||||
* @returns an array of payloads and signatures
|
||||
* @twfeature ERC20SignatureMintable
|
||||
*/
|
||||
public async generateBatch(
|
||||
payloadsToSign: PayloadToSign20[],
|
||||
): Promise<SignedPayload20[]> {
|
||||
await this.roles?.verify(
|
||||
["minter"],
|
||||
await this.contractWrapper.getSignerAddress(),
|
||||
);
|
||||
|
||||
const parsedRequests: FilledSignaturePayload20[] = await Promise.all(
|
||||
payloadsToSign.map((m) => Signature20PayloadInput.parseAsync(m)),
|
||||
);
|
||||
|
||||
const chainId = await this.contractWrapper.getChainID();
|
||||
const signer = this.contractWrapper.getSigner();
|
||||
invariant(signer, "No signer available");
|
||||
|
||||
// ERC20Permit (EIP-712) spec differs from signature mint 721, 1155.
|
||||
const name = await this.contractWrapper.readContract.name();
|
||||
|
||||
return await Promise.all(
|
||||
parsedRequests.map(async (m) => {
|
||||
const finalPayload = await Signature20PayloadOutput.parseAsync(m);
|
||||
const signature = await this.contractWrapper.signTypedData(
|
||||
signer,
|
||||
{
|
||||
name,
|
||||
version: "1",
|
||||
chainId,
|
||||
verifyingContract: this.contractWrapper.readContract.address,
|
||||
},
|
||||
{ MintRequest: MintRequest20 },
|
||||
await this.mapPayloadToContractStruct(finalPayload),
|
||||
);
|
||||
return {
|
||||
payload: finalPayload,
|
||||
signature: signature.toString(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/** ******************************
|
||||
* PRIVATE FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
/**
|
||||
* Maps a payload to the format expected by the contract
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param mintRequest - The payload to map.
|
||||
* @returns - The mapped payload.
|
||||
*/
|
||||
private async mapPayloadToContractStruct(
|
||||
mintRequest: PayloadWithUri20,
|
||||
): Promise<ITokenERC20.MintRequestStructOutput> {
|
||||
const normalizedPrice = await normalizePriceValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
mintRequest.price,
|
||||
mintRequest.currencyAddress,
|
||||
);
|
||||
const amountWithDecimals = ethers.utils.parseUnits(
|
||||
mintRequest.quantity,
|
||||
await this.contractWrapper.readContract.decimals(),
|
||||
);
|
||||
return {
|
||||
to: mintRequest.to,
|
||||
primarySaleRecipient: mintRequest.primarySaleRecipient,
|
||||
quantity: amountWithDecimals,
|
||||
price: normalizedPrice,
|
||||
currency: mintRequest.currencyAddress,
|
||||
validityEndTimestamp: mintRequest.mintEndTime,
|
||||
validityStartTimestamp: mintRequest.mintStartTime,
|
||||
uid: mintRequest.uid,
|
||||
} as ITokenERC20.MintRequestStructOutput;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure and claim ERC20 tokens
|
||||
* @remarks Manage claim phases and claim ERC20 tokens that have been lazily minted.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.token.drop.claim.to("0x...", quantity);
|
||||
* ```
|
||||
*/
|
||||
export class Erc20ClaimableWithConditions implements DetectableFeature {
|
||||
featureName = FEATURE_TOKEN_CLAIM_CONDITIONS_V2.name;
|
||||
/**
|
||||
* Configure claim conditions
|
||||
* @remarks Define who can claim NFTs in the collection, when and how many.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const presaleStartTime = new Date();
|
||||
* const publicSaleStartTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const claimConditions = [
|
||||
* {
|
||||
* startTime: presaleStartTime, // start the presale now
|
||||
* maxClaimableSupply: 2, // limit how many mints for this presale
|
||||
* price: 0.01, // presale price
|
||||
* snapshot: ['0x...', '0x...'], // limit minting to only certain addresses
|
||||
* },
|
||||
* {
|
||||
* startTime: publicSaleStartTime, // 24h after presale, start public sale
|
||||
* price: 0.08, // public sale price
|
||||
* }
|
||||
* ]);
|
||||
* await contract.token.drop.claim.conditions.set(claimConditions);
|
||||
* ```
|
||||
*/
|
||||
public conditions: DropClaimConditions<BaseDropERC20>;
|
||||
private contractWrapper: ContractWrapper<BaseDropERC20>;
|
||||
private erc20: Erc20;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
erc20: Erc20,
|
||||
contractWrapper: ContractWrapper<BaseDropERC20>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc20 = erc20;
|
||||
this.contractWrapper = contractWrapper;
|
||||
|
||||
this.storage = storage;
|
||||
const metadata = new ContractMetadata(
|
||||
this.contractWrapper,
|
||||
CustomContractSchema,
|
||||
this.storage,
|
||||
);
|
||||
this.conditions = new DropClaimConditions(
|
||||
this.contractWrapper,
|
||||
metadata,
|
||||
this.storage,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim a certain amount of tokens to a specific Wallet
|
||||
*
|
||||
* @remarks Let the specified wallet claim Tokens.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const address = "{{wallet_address}}"; // address of the wallet you want to claim the NFTs
|
||||
* const quantity = 42.69; // how many tokens you want to claim
|
||||
*
|
||||
* const tx = await contract.token.drop.claim.to(address, quantity);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* ```
|
||||
*
|
||||
* @param destinationAddress - Address you want to send the token to
|
||||
* @param amount - Quantity of the tokens you want to claim
|
||||
* @param checkERC20Allowance - Optional, check if the wallet has enough ERC20 allowance to claim the tokens, and if not, approve the transfer
|
||||
* @param claimData
|
||||
* @returns - The transaction receipt
|
||||
*/
|
||||
to = buildTransactionFunction(
|
||||
async (
|
||||
destinationAddress: AddressOrEns,
|
||||
amount: Amount,
|
||||
options?: ClaimOptions,
|
||||
) => {
|
||||
const quantity = await this.erc20.normalizeAmount(amount);
|
||||
return await this.conditions.getClaimTransaction(
|
||||
destinationAddress,
|
||||
quantity,
|
||||
options,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint Many ERC20 Tokens at once
|
||||
* @remarks Token batch minting functionality that handles unit parsing for you.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.token.mint.batch.to(walletAddress, [nftMetadata1, nftMetadata2, ...]);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc20BatchMintable implements DetectableFeature {
|
||||
featureName = FEATURE_TOKEN_BATCH_MINTABLE.name;
|
||||
private contractWrapper: ContractWrapper<IMintableERC20 & IMulticall>;
|
||||
private erc20: Erc20;
|
||||
|
||||
constructor(
|
||||
erc20: Erc20,
|
||||
contractWrapper: ContractWrapper<IMintableERC20 & IMulticall>,
|
||||
) {
|
||||
this.erc20 = erc20;
|
||||
this.contractWrapper = contractWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint Tokens To Many Wallets
|
||||
*
|
||||
* @remarks Mint tokens to many wallets in one transaction.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Data of the tokens you want to mint
|
||||
* const data = [
|
||||
* {
|
||||
* toAddress: "{{wallet_address}}", // Address to mint tokens to
|
||||
* amount: 0.2, // How many tokens to mint to specified address
|
||||
* },
|
||||
* {
|
||||
* toAddress: "0x...",
|
||||
* amount: 1.4,
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* await contract.token.mint.batch(data);
|
||||
* ```
|
||||
*/
|
||||
to = buildTransactionFunction(async (args: TokenMintInput[]) => {
|
||||
const encoded: string[] = [];
|
||||
for (const arg of args) {
|
||||
encoded.push(
|
||||
this.contractWrapper.readContract.interface.encodeFunctionData(
|
||||
"mintTo",
|
||||
[
|
||||
await resolveAddress(arg.toAddress),
|
||||
await this.erc20.normalizeAmount(arg.amount),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "multicall",
|
||||
args: [encoded],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import { NFT, NFTMetadataOrUri } from "../../../core/schema/nft";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { uploadOrExtractURIs } from "../../common/nft";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_NFT_BATCH_MINTABLE } from "../../constants/erc721-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResultWithId } from "../types";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc721 } from "./erc-721";
|
||||
import { Transaction } from "./transactions";
|
||||
import type { IMintableERC721, IMulticall } from "@thirdweb-dev/contracts-js";
|
||||
import { TokensMintedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/IMintableERC721";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
|
||||
/**
|
||||
* Mint Many ERC721 NFTs at once
|
||||
* @remarks NFT batch minting functionality that handles IPFS storage for you.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.nft.mint.batch.to(walletAddress, [nftMetadata1, nftMetadata2, ...]);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc721BatchMintable implements DetectableFeature {
|
||||
featureName = FEATURE_NFT_BATCH_MINTABLE.name;
|
||||
private contractWrapper: ContractWrapper<IMintableERC721 & IMulticall>;
|
||||
private storage: ThirdwebStorage;
|
||||
private erc721: Erc721;
|
||||
|
||||
constructor(
|
||||
erc721: Erc721,
|
||||
contractWrapper: ContractWrapper<IMintableERC721 & IMulticall>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc721 = erc721;
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint Many unique NFTs
|
||||
*
|
||||
* @remarks Mint many unique NFTs at once to a specified wallet.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet you want to mint the NFT to
|
||||
* const walletAddress = "{{wallet_address}}";
|
||||
*
|
||||
* // Custom metadata of the NFTs you want to mint.
|
||||
* const metadatas = [{
|
||||
* name: "Cool NFT #1",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
|
||||
* }, {
|
||||
* name: "Cool NFT #2",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/other/image.png"),
|
||||
* }];
|
||||
*
|
||||
* const tx = await contract.mint.batch.to(walletAddress, metadatas);
|
||||
* const receipt = tx[0].receipt; // same transaction receipt for all minted NFTs
|
||||
* const firstTokenId = tx[0].id; // token id of the first minted NFT
|
||||
* const firstNFT = await tx[0].data(); // (optional) fetch details of the first minted NFT
|
||||
* ```
|
||||
*/
|
||||
to = buildTransactionFunction(
|
||||
async (
|
||||
to: AddressOrEns,
|
||||
metadatas: NFTMetadataOrUri[],
|
||||
): Promise<Transaction<TransactionResultWithId<NFT>[]>> => {
|
||||
const uris = await uploadOrExtractURIs(metadatas, this.storage);
|
||||
const resolvedAddress = await resolveAddress(to);
|
||||
const encoded = await Promise.all(
|
||||
uris.map(async (uri) =>
|
||||
this.contractWrapper.readContract.interface.encodeFunctionData(
|
||||
"mintTo",
|
||||
[resolvedAddress, uri],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "multicall",
|
||||
args: [encoded],
|
||||
parse: (receipt) => {
|
||||
const events = this.contractWrapper.parseLogs<TokensMintedEvent>(
|
||||
"TokensMinted",
|
||||
receipt.logs,
|
||||
);
|
||||
if (events.length === 0 || events.length < metadatas.length) {
|
||||
throw new Error("TokenMinted event not found, minting failed");
|
||||
}
|
||||
return events.map((e) => {
|
||||
const id = e.args.tokenIdMinted;
|
||||
return {
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc721.get(id),
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
import { NFT } from "../../../core/schema/nft";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_NFT_CLAIM_CONDITIONS_V2 } from "../../constants/erc721-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { CustomContractSchema } from "../../schema/contracts/custom";
|
||||
import { ClaimOptions } from "../../types";
|
||||
import { BaseClaimConditionERC721 } from "../../types/eips";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResultWithId } from "../types";
|
||||
import { ContractMetadata } from "./contract-metadata";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { DropClaimConditions } from "./drop-claim-conditions";
|
||||
import { Erc721 } from "./erc-721";
|
||||
import { Transaction } from "./transactions";
|
||||
import { TokensClaimedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/Drop";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish } from "ethers";
|
||||
|
||||
/**
|
||||
* Configure and claim ERC721 NFTs
|
||||
* @remarks Manage claim phases and claim ERC721 NFTs that have been lazily minted.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.erc721.claim(quantity);
|
||||
* await contract.erc721.claimConditions.getActive();
|
||||
* ```
|
||||
*/
|
||||
export class Erc721ClaimableWithConditions implements DetectableFeature {
|
||||
featureName = FEATURE_NFT_CLAIM_CONDITIONS_V2.name;
|
||||
|
||||
/**
|
||||
* Configure claim conditions
|
||||
* @remarks Define who can claim NFTs in the collection, when and how many.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const presaleStartTime = new Date();
|
||||
* const publicSaleStartTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const claimConditions = [
|
||||
* {
|
||||
* startTime: presaleStartTime, // start the presale now
|
||||
* maxClaimableSupply: 2, // limit how many mints for this presale
|
||||
* price: 0.01, // presale price
|
||||
* snapshot: ['0x...', '0x...'], // limit minting to only certain addresses
|
||||
* },
|
||||
* {
|
||||
* startTime: publicSaleStartTime, // 24h after presale, start public sale
|
||||
* price: 0.08, // public sale price
|
||||
* }
|
||||
* ]);
|
||||
* await contract.erc721.claimConditions.set(claimConditions);
|
||||
* ```
|
||||
*/
|
||||
public conditions: DropClaimConditions<BaseClaimConditionERC721>;
|
||||
private contractWrapper: ContractWrapper<BaseClaimConditionERC721>;
|
||||
private erc721: Erc721;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
erc721: Erc721,
|
||||
contractWrapper: ContractWrapper<BaseClaimConditionERC721>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc721 = erc721;
|
||||
this.contractWrapper = contractWrapper;
|
||||
|
||||
this.storage = storage;
|
||||
const metadata = new ContractMetadata(
|
||||
this.contractWrapper,
|
||||
CustomContractSchema,
|
||||
this.storage,
|
||||
);
|
||||
this.conditions = new DropClaimConditions(
|
||||
this.contractWrapper,
|
||||
metadata,
|
||||
this.storage,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim unique NFTs to a specific Wallet
|
||||
*
|
||||
* @remarks Let the specified wallet claim NFTs.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const address = "{{wallet_address}}"; // address of the wallet you want to claim the NFTs
|
||||
* const quantity = 1; // how many unique NFTs you want to claim
|
||||
*
|
||||
* const tx = await contract.erc721.claimTo(address, quantity);
|
||||
* const receipt = tx[0].receipt; // the transaction receipt
|
||||
* const claimedTokenId = tx[0].id; // the id of the first NFT claimed
|
||||
* const claimedNFT = await tx[0].data(); // (optional) get the first claimed NFT metadata
|
||||
* ```
|
||||
*
|
||||
* @param destinationAddress - Address you want to send the token to
|
||||
* @param quantity - Quantity of the tokens you want to claim
|
||||
* @param options
|
||||
* @returns - an array of results containing the id of the token claimed, the transaction receipt and a promise to optionally fetch the nft metadata
|
||||
*/
|
||||
to = buildTransactionFunction(
|
||||
async (
|
||||
destinationAddress: AddressOrEns,
|
||||
quantity: BigNumberish,
|
||||
options?: ClaimOptions,
|
||||
): Promise<Transaction<TransactionResultWithId<NFT>[]>> => {
|
||||
// TODO: Transaction Sequence Pattern
|
||||
const tx = (await this.conditions.getClaimTransaction(
|
||||
destinationAddress,
|
||||
quantity,
|
||||
options,
|
||||
)) as any as Transaction<TransactionResultWithId<NFT>[]>;
|
||||
tx.setParse((receipt) => {
|
||||
const event = this.contractWrapper.parseLogs<TokensClaimedEvent>(
|
||||
"TokensClaimed",
|
||||
receipt?.logs,
|
||||
);
|
||||
const startingIndex: BigNumber = event[0].args.startTokenId;
|
||||
const endingIndex = startingIndex.add(quantity);
|
||||
const results: TransactionResultWithId<NFT>[] = [];
|
||||
for (let id = startingIndex; id.lt(endingIndex); id = id.add(1)) {
|
||||
results.push({
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc721.get(id),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
});
|
||||
|
||||
return tx;
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
import { NFT } from "../../../core/schema/nft";
|
||||
import { calculateClaimCost } from "../../common/claim-conditions/calculateClaimCost";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_NFT_CLAIM_CUSTOM } from "../../constants/erc721-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { ClaimOptions } from "../../types";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResultWithId } from "../types";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc721 } from "./erc-721";
|
||||
import { Transaction } from "./transactions";
|
||||
import type { IClaimableERC721 } from "@thirdweb-dev/contracts-js";
|
||||
import { TokensClaimedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/IClaimableERC721";
|
||||
import { BigNumber, BigNumberish, CallOverrides } from "ethers";
|
||||
|
||||
/**
|
||||
* Configure and claim ERC721 NFTs
|
||||
* @remarks Manage claim phases and claim ERC721 NFTs that have been lazily minted.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.erc721.claim(tokenId, quantity);
|
||||
* ```
|
||||
*/
|
||||
export class Erc721Claimable implements DetectableFeature {
|
||||
featureName = FEATURE_NFT_CLAIM_CUSTOM.name;
|
||||
|
||||
private erc721: Erc721;
|
||||
private contractWrapper: ContractWrapper<IClaimableERC721>;
|
||||
|
||||
constructor(
|
||||
erc721: Erc721,
|
||||
contractWrapper: ContractWrapper<IClaimableERC721>,
|
||||
) {
|
||||
this.erc721 = erc721;
|
||||
this.contractWrapper = contractWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a claim transaction without executing it.
|
||||
* This is useful for estimating the gas cost of a claim transaction, overriding transaction options and having fine grained control over the transaction execution.
|
||||
* @param destinationAddress - Address you want to send the token to
|
||||
* @param tokenId - Id of the token you want to claim
|
||||
* @param quantity - Quantity of the tokens you want to claim
|
||||
* @param options - Options for claiming the NFTs
|
||||
*
|
||||
* @deprecated Use `contract.erc721.claim.prepare(...args)` instead
|
||||
*/
|
||||
public async getClaimTransaction(
|
||||
destinationAddress: AddressOrEns,
|
||||
quantity: BigNumberish,
|
||||
options?: ClaimOptions,
|
||||
): Promise<Transaction> {
|
||||
// TODO: Transaction Sequence Pattern
|
||||
let overrides: CallOverrides = {};
|
||||
if (options && options.pricePerToken) {
|
||||
overrides = await calculateClaimCost(
|
||||
this.contractWrapper,
|
||||
options.pricePerToken,
|
||||
quantity,
|
||||
options.currencyAddress,
|
||||
options.checkERC20Allowance,
|
||||
);
|
||||
}
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "claim",
|
||||
args: [destinationAddress, quantity],
|
||||
overrides,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim NFTs to a specific Wallet
|
||||
*
|
||||
* @remarks Let the specified wallet claim NFTs.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const address = "{{wallet_address}}"; // address of the wallet you want to claim the NFTs
|
||||
* const tokenId = 0; // the id of the NFT you want to claim
|
||||
* const quantity = 1; // how many NFTs you want to claim
|
||||
*
|
||||
* const tx = await contract.erc721.claimTo(address, tokenId, quantity);
|
||||
* const receipt = tx[0].receipt; // the transaction receipt
|
||||
* ```
|
||||
*
|
||||
* @param destinationAddress - Address you want to send the token to
|
||||
* @param quantity - Quantity of the tokens you want to claim
|
||||
* @param options - Options for claiming the NFTs
|
||||
*
|
||||
* @returns - Receipt for the transaction
|
||||
*/
|
||||
to = buildTransactionFunction(
|
||||
async (
|
||||
destinationAddress: AddressOrEns,
|
||||
quantity: BigNumberish,
|
||||
options?: ClaimOptions,
|
||||
): Promise<Transaction<TransactionResultWithId<NFT>[]>> => {
|
||||
// TODO: Transaction Sequence Pattern
|
||||
const tx = (await this.getClaimTransaction(
|
||||
destinationAddress,
|
||||
quantity,
|
||||
options,
|
||||
)) as any as Transaction<TransactionResultWithId<NFT>[]>;
|
||||
tx.setParse((receipt) => {
|
||||
const event = this.contractWrapper.parseLogs<TokensClaimedEvent>(
|
||||
"TokensClaimed",
|
||||
receipt?.logs,
|
||||
);
|
||||
const startingIndex: BigNumber = event[0].args.startTokenId;
|
||||
const endingIndex = startingIndex.add(quantity);
|
||||
const results: TransactionResultWithId<NFT>[] = [];
|
||||
for (let id = startingIndex; id.lt(endingIndex); id = id.add(1)) {
|
||||
results.push({
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc721.get(id),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
});
|
||||
return tx;
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import { NFT } from "../../../core/schema/nft";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { FEATURE_NFT_ENUMERABLE } from "../../constants/erc721-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { BaseERC721 } from "../../types/eips";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc721 } from "./erc-721";
|
||||
import type { IERC721Enumerable } from "@thirdweb-dev/contracts-js";
|
||||
import { BigNumber } from "ethers";
|
||||
|
||||
/**
|
||||
* List owned ERC721 NFTs
|
||||
* @remarks Easily list all the NFTs from a ERC721 contract, owned by a certain wallet.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* const walletAddress = "0x...";
|
||||
* const ownedNFTs = await contract.nft.query.owned.all(walletAddress);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc721Enumerable implements DetectableFeature {
|
||||
featureName = FEATURE_NFT_ENUMERABLE.name;
|
||||
private contractWrapper: ContractWrapper<BaseERC721 & IERC721Enumerable>;
|
||||
private erc721: Erc721;
|
||||
|
||||
constructor(
|
||||
erc721: Erc721,
|
||||
contractWrapper: ContractWrapper<BaseERC721 & IERC721Enumerable>,
|
||||
) {
|
||||
this.erc721 = erc721;
|
||||
this.contractWrapper = contractWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all NFTs owned by a specific wallet
|
||||
*
|
||||
* @remarks Get all the data associated with the NFTs owned by a specific wallet.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet to get the NFTs of
|
||||
* const address = "{{wallet_address}}";
|
||||
* const nfts = await contract.nft.query.owned.all(address);
|
||||
* ```
|
||||
* @param walletAddress - the wallet address to query, defaults to the connected wallet
|
||||
* @returns The NFT metadata for all NFTs in the contract.
|
||||
*/
|
||||
public async all(walletAddress?: AddressOrEns): Promise<NFT[]> {
|
||||
const tokenIds = await this.tokenIds(walletAddress);
|
||||
return await Promise.all(
|
||||
tokenIds.map((tokenId) => this.erc721.get(tokenId.toString())),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all token ids of NFTs owned by a specific wallet.
|
||||
* @param walletAddress - the wallet address to query, defaults to the connected wallet
|
||||
*/
|
||||
public async tokenIds(walletAddress?: AddressOrEns): Promise<BigNumber[]> {
|
||||
const address = await resolveAddress(
|
||||
walletAddress || (await this.contractWrapper.getSignerAddress()),
|
||||
);
|
||||
|
||||
const balance = await this.contractWrapper.readContract.balanceOf(address);
|
||||
const indices = Array.from(Array(balance.toNumber()).keys());
|
||||
return await Promise.all(
|
||||
indices.map((i) =>
|
||||
this.contractWrapper.readContract.tokenOfOwnerByIndex(address, i),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
import { NFTMetadata, NFTMetadataOrUri } from "../../../core/schema/nft";
|
||||
import { detectContractFeature } from "../../common/feature-detection/detectContractFeature";
|
||||
import { getBaseUriFromBatch, uploadOrExtractURIs } from "../../common/nft";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import {
|
||||
FEATURE_NFT_LAZY_MINTABLE,
|
||||
FEATURE_NFT_REVEALABLE,
|
||||
} from "../../constants/erc721-features";
|
||||
import { UploadProgressEvent } from "../../types";
|
||||
import { BaseDelayedRevealERC721, BaseDropERC721 } from "../../types/eips";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResultWithId } from "../types";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { DelayedReveal } from "./delayed-reveal";
|
||||
import { Erc721 } from "./erc-721";
|
||||
import { Transaction } from "./transactions";
|
||||
import { TokensLazyMintedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/LazyMint";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
/**
|
||||
* Lazily mint and claim ERC721 NFTs
|
||||
* @remarks Manage claim phases and claim ERC721 NFTs that have been lazily minted.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.drop.claim(quantity);
|
||||
* ```
|
||||
*/
|
||||
export class Erc721LazyMintable implements DetectableFeature {
|
||||
featureName = FEATURE_NFT_LAZY_MINTABLE.name;
|
||||
|
||||
/**
|
||||
* Delayed reveal
|
||||
* @remarks Create a batch of encrypted NFTs that can be revealed at a later time.
|
||||
* @example
|
||||
* ```javascript
|
||||
* // the real NFTs, these will be encrypted until you reveal them
|
||||
* const realNFTs = [{
|
||||
* name: "Common NFT #1",
|
||||
* description: "Common NFT, one of many.",
|
||||
* image: fs.readFileSync("path/to/image.png"),
|
||||
* }, {
|
||||
* name: "Super Rare NFT #2",
|
||||
* description: "You got a Super Rare NFT!",
|
||||
* image: fs.readFileSync("path/to/image.png"),
|
||||
* }];
|
||||
* // A placeholder NFT that people will get immediately in their wallet, and will be converted to the real NFT at reveal time
|
||||
* const placeholderNFT = {
|
||||
* name: "Hidden NFT",
|
||||
* description: "Will be revealed next week!"
|
||||
* };
|
||||
* // Create and encrypt the NFTs
|
||||
* await contract.nft.drop.revealer.createDelayedRevealBatch(
|
||||
* placeholderNFT,
|
||||
* realNFTs,
|
||||
* "my secret password",
|
||||
* );
|
||||
* // Whenever you're ready, reveal your NFTs at any time
|
||||
* const batchId = 0; // the batch to reveal
|
||||
* await contract.erc721.revealer.reveal(batchId, "my secret password");
|
||||
* ```
|
||||
*/
|
||||
public revealer: DelayedReveal<BaseDelayedRevealERC721> | undefined;
|
||||
|
||||
private contractWrapper: ContractWrapper<BaseDropERC721>;
|
||||
private erc721: Erc721;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
erc721: Erc721,
|
||||
contractWrapper: ContractWrapper<BaseDropERC721>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc721 = erc721;
|
||||
this.contractWrapper = contractWrapper;
|
||||
|
||||
this.storage = storage;
|
||||
this.revealer = this.detectErc721Revealable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a batch of unique NFTs to be claimed in the future
|
||||
*
|
||||
* @remarks Create batch allows you to create a batch of many unique NFTs in one transaction.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Custom metadata of the NFTs to create
|
||||
* const metadatas = [{
|
||||
* name: "Cool NFT",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
|
||||
* }, {
|
||||
* name: "Cool NFT",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"),
|
||||
* }];
|
||||
*
|
||||
* const results = await contract.erc721.lazyMint(metadatas); // uploads and creates the NFTs on chain
|
||||
* const firstTokenId = results[0].id; // token id of the first created NFT
|
||||
* const firstNFT = await results[0].data(); // (optional) fetch details of the first created NFT
|
||||
* ```
|
||||
*
|
||||
* @param metadatas - The metadata to include in the batch.
|
||||
* @param options - optional upload progress callback
|
||||
*/
|
||||
lazyMint = buildTransactionFunction(
|
||||
async (
|
||||
metadatas: NFTMetadataOrUri[],
|
||||
options?: {
|
||||
onProgress: (event: UploadProgressEvent) => void;
|
||||
},
|
||||
): Promise<Transaction<TransactionResultWithId<NFTMetadata>[]>> => {
|
||||
const startFileNumber = await this.erc721.nextTokenIdToMint();
|
||||
const batch = await uploadOrExtractURIs(
|
||||
metadatas,
|
||||
this.storage,
|
||||
startFileNumber.toNumber(),
|
||||
options,
|
||||
);
|
||||
// ensure baseUri is the same for the entire batch
|
||||
const baseUri = getBaseUriFromBatch(batch);
|
||||
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "lazyMint",
|
||||
args: [
|
||||
batch.length,
|
||||
baseUri.endsWith("/") ? baseUri : `${baseUri}/`,
|
||||
ethers.utils.toUtf8Bytes(""),
|
||||
],
|
||||
parse: (receipt) => {
|
||||
const event = this.contractWrapper.parseLogs<TokensLazyMintedEvent>(
|
||||
"TokensLazyMinted",
|
||||
receipt?.logs,
|
||||
);
|
||||
const startingIndex = event[0].args.startTokenId;
|
||||
const endingIndex = event[0].args.endTokenId;
|
||||
const results: TransactionResultWithId<NFTMetadata>[] = [];
|
||||
for (let id = startingIndex; id.lte(endingIndex); id = id.add(1)) {
|
||||
results.push({
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc721.getTokenMetadata(id),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/** ******************************
|
||||
* PRIVATE FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
private detectErc721Revealable():
|
||||
| DelayedReveal<BaseDelayedRevealERC721>
|
||||
| undefined {
|
||||
if (
|
||||
detectContractFeature<BaseDelayedRevealERC721>(
|
||||
this.contractWrapper,
|
||||
"ERC721Revealable",
|
||||
)
|
||||
) {
|
||||
return new DelayedReveal(
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
FEATURE_NFT_REVEALABLE.name,
|
||||
() => this.erc721.nextTokenIdToMint(),
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
import { NFT, NFTMetadataOrUri } from "../../../core/schema/nft";
|
||||
import { detectContractFeature } from "../../common/feature-detection/detectContractFeature";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { uploadOrExtractURI } from "../../common/nft";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { FEATURE_NFT_MINTABLE } from "../../constants/erc721-features";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResultWithId } from "../types";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc721 } from "./erc-721";
|
||||
import { Erc721BatchMintable } from "./erc-721-batch-mintable";
|
||||
import { Transaction } from "./transactions";
|
||||
import type { IMintableERC721, IMulticall } from "@thirdweb-dev/contracts-js";
|
||||
import { TransferEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/ITokenERC721";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
|
||||
/**
|
||||
* Mint ERC721 NFTs
|
||||
* @remarks NFT minting functionality that handles IPFS storage for you.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* await contract.nft.mint.to(walletAddress, nftMetadata);
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc721Mintable implements DetectableFeature {
|
||||
featureName = FEATURE_NFT_MINTABLE.name;
|
||||
private contractWrapper: ContractWrapper<IMintableERC721>;
|
||||
private storage: ThirdwebStorage;
|
||||
private erc721: Erc721;
|
||||
|
||||
public batch: Erc721BatchMintable | undefined;
|
||||
|
||||
constructor(
|
||||
erc721: Erc721,
|
||||
contractWrapper: ContractWrapper<IMintableERC721>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc721 = erc721;
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.storage = storage;
|
||||
this.batch = this.detectErc721BatchMintable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint a unique NFT
|
||||
*
|
||||
* @remarks Mint a unique NFT to a specified wallet.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Address of the wallet you want to mint the NFT to
|
||||
* const walletAddress = "{{wallet_address}}";
|
||||
*
|
||||
* // Custom metadata of the NFT, note that you can fully customize this metadata with other properties.
|
||||
* const metadata = {
|
||||
* name: "Cool NFT",
|
||||
* description: "This is a cool NFT",
|
||||
* image: fs.readFileSync("path/to/image.png"), // This can be an image url or file
|
||||
* };
|
||||
*
|
||||
* const tx = await contract.nft.mint.to(walletAddress, metadata);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* const tokenId = tx.id; // the id of the NFT minted
|
||||
* const nft = await tx.data(); // (optional) fetch details of minted NFT
|
||||
* ```
|
||||
*/
|
||||
to = buildTransactionFunction(
|
||||
async (
|
||||
to: AddressOrEns,
|
||||
metadata: NFTMetadataOrUri,
|
||||
): Promise<Transaction<TransactionResultWithId<NFT>>> => {
|
||||
const uri = await uploadOrExtractURI(metadata, this.storage);
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "mintTo",
|
||||
args: [await resolveAddress(to), uri],
|
||||
parse: (receipt) => {
|
||||
const event = this.contractWrapper.parseLogs<TransferEvent>(
|
||||
"Transfer",
|
||||
receipt?.logs,
|
||||
);
|
||||
if (event.length === 0) {
|
||||
throw new Error("TransferEvent event not found");
|
||||
}
|
||||
const id = event[0].args.tokenId;
|
||||
return {
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc721.get(id),
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @deprecated Use `contract.erc721.mint.prepare(...args)` instead
|
||||
*/
|
||||
public async getMintTransaction(
|
||||
to: AddressOrEns,
|
||||
metadata: NFTMetadataOrUri,
|
||||
): Promise<Transaction> {
|
||||
return this.to.prepare(await resolveAddress(to), metadata);
|
||||
}
|
||||
|
||||
private detectErc721BatchMintable(): Erc721BatchMintable | undefined {
|
||||
if (
|
||||
detectContractFeature<IMintableERC721 & IMulticall>(
|
||||
this.contractWrapper,
|
||||
"ERC721BatchMintable",
|
||||
)
|
||||
) {
|
||||
return new Erc721BatchMintable(
|
||||
this.erc721,
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { QueryAllParams } from "../../../core/schema/QueryParams";
|
||||
import { NFT } from "../../../core/schema/nft";
|
||||
import { resolveAddress } from "../../common/ens";
|
||||
import { resolveAddress } from "../../common/ens/resolveAddress";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { AddressOrEns } from "../../schema/shared/AddressOrEnsSchema";
|
||||
import { Address } from "../../schema/shared/Address";
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
import {
|
||||
DEFAULT_QUERY_ALL_COUNT,
|
||||
QueryAllParams,
|
||||
} from "../../../core/schema/QueryParams";
|
||||
import { NFT } from "../../../core/schema/nft";
|
||||
import { detectContractFeature } from "../../common/feature-detection/detectContractFeature";
|
||||
import { FEATURE_NFT_SUPPLY } from "../../constants/erc721-features";
|
||||
import { BaseERC721 } from "../../types/eips";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc721 } from "./erc-721";
|
||||
import { Erc721Enumerable } from "./erc-721-enumerable";
|
||||
import type {
|
||||
IERC721Enumerable,
|
||||
IERC721Supply,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import { BigNumber, constants } from "ethers";
|
||||
|
||||
/**
|
||||
* List ERC721 NFTs
|
||||
* @remarks Easily list all the NFTs in a ERC721 contract.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const contract = await sdk.getContract("{{contract_address}}");
|
||||
* const nfts = await contract.nft.query.all();
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export class Erc721Supply implements DetectableFeature {
|
||||
featureName = FEATURE_NFT_SUPPLY.name;
|
||||
private contractWrapper: ContractWrapper<BaseERC721 & IERC721Supply>;
|
||||
private erc721: Erc721;
|
||||
|
||||
public owned: Erc721Enumerable | undefined;
|
||||
|
||||
constructor(
|
||||
erc721: Erc721,
|
||||
contractWrapper: ContractWrapper<BaseERC721 & IERC721Supply>,
|
||||
) {
|
||||
this.erc721 = erc721;
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.owned = this.detectErc721Owned();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all NFTs
|
||||
*
|
||||
* @remarks Get all the data associated with every NFT in this contract.
|
||||
*
|
||||
* By default, returns the first 100 NFTs, use queryParams to fetch more.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const nfts = await contract.nft.query.all();
|
||||
* ```
|
||||
* @param queryParams - optional filtering to only fetch a subset of results.
|
||||
* @returns The NFT metadata for all NFTs queried.
|
||||
*/
|
||||
public async all(queryParams?: QueryAllParams): Promise<NFT[]> {
|
||||
const start = BigNumber.from(queryParams?.start || 0).toNumber();
|
||||
const count = BigNumber.from(
|
||||
queryParams?.count || DEFAULT_QUERY_ALL_COUNT,
|
||||
).toNumber();
|
||||
|
||||
const maxSupply = await this.erc721.nextTokenIdToMint();
|
||||
const maxId = Math.min(maxSupply.toNumber(), start + count);
|
||||
return await Promise.all(
|
||||
[...Array(maxId - start).keys()].map((i) =>
|
||||
this.erc721.get((start + i).toString()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the owners of each token id in this contract
|
||||
* @returns
|
||||
*/
|
||||
public async allOwners() {
|
||||
return Promise.all(
|
||||
[...new Array((await this.totalCount()).toNumber()).keys()].map(
|
||||
async (i) => ({
|
||||
tokenId: i,
|
||||
owner: await this.erc721
|
||||
.ownerOf(i)
|
||||
.catch(() => constants.AddressZero),
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of NFTs minted
|
||||
* @remarks This returns the total number of NFTs minted in this contract, **not** the total supply of a given token.
|
||||
*
|
||||
* @returns the total number of NFTs minted in this contract
|
||||
* @public
|
||||
*/
|
||||
public async totalCount(): Promise<BigNumber> {
|
||||
return await this.erc721.nextTokenIdToMint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of NFTs of this contract currently owned by end users
|
||||
* @returns the total number of NFTs of this contract in circulation (minted & not burned)
|
||||
* @public
|
||||
*/
|
||||
public async totalCirculatingSupply(): Promise<BigNumber> {
|
||||
return await this.contractWrapper.readContract.totalSupply();
|
||||
}
|
||||
|
||||
private detectErc721Owned(): Erc721Enumerable | undefined {
|
||||
if (
|
||||
detectContractFeature<BaseERC721 & IERC721Enumerable>(
|
||||
this.contractWrapper,
|
||||
"ERC721Enumerable",
|
||||
)
|
||||
) {
|
||||
return new Erc721Enumerable(this.erc721, this.contractWrapper);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,427 +0,0 @@
|
||||
import {
|
||||
CommonNFTInput,
|
||||
NFT,
|
||||
NFTMetadata,
|
||||
NFTMetadataInput,
|
||||
NFTMetadataOrUri,
|
||||
} from "../../../core/schema/nft";
|
||||
import { getBaseUriFromBatch, uploadOrExtractURIs } from "../../common/nft";
|
||||
import { FEATURE_NFT_TIERED_DROP } from "../../constants/erc721-features";
|
||||
import { GenericRequest } from "../../schema/contracts/common";
|
||||
import {
|
||||
TieredDropPayloadInput,
|
||||
TieredDropPayloadOutput,
|
||||
TieredDropPayloadSchema,
|
||||
TieredDropPayloadWithSignature,
|
||||
} from "../../schema/contracts/tiered-drop";
|
||||
import { UploadProgressEvent } from "../../types/events";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResultWithId } from "../types";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc721 } from "./erc-721";
|
||||
import type { TieredDrop, ISignatureAction } from "@thirdweb-dev/contracts-js";
|
||||
import {
|
||||
TokensLazyMintedEvent,
|
||||
TokensClaimedEvent,
|
||||
} from "@thirdweb-dev/contracts-js/dist/declarations/src/TieredDrop";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumberish, ethers } from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
import { Transaction } from "./transactions";
|
||||
import { buildTransactionFunction } from "../../common/transactions";
|
||||
import { normalizePriceValue } from "../../common/currency/normalizePriceValue";
|
||||
import { setErc20Allowance } from "../../common/currency/setErc20Allowance";
|
||||
|
||||
export class Erc721TieredDrop implements DetectableFeature {
|
||||
featureName = FEATURE_NFT_TIERED_DROP.name;
|
||||
|
||||
private contractWrapper: ContractWrapper<TieredDrop>;
|
||||
private erc721: Erc721;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
erc721: Erc721,
|
||||
contractWrapper: ContractWrapper<TieredDrop>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc721 = erc721;
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
public async getMetadataInTier(
|
||||
tier: string,
|
||||
): Promise<Omit<NFTMetadata, "id">[]> {
|
||||
const tiers =
|
||||
await this.contractWrapper.readContract.getMetadataForAllTiers();
|
||||
const batches = tiers.find((t) => t.tier === tier);
|
||||
|
||||
if (!batches) {
|
||||
throw new Error("Tier not found in contract.");
|
||||
}
|
||||
|
||||
const nfts = await Promise.all(
|
||||
batches.ranges
|
||||
.map((range, i) => {
|
||||
const nftsInRange = [];
|
||||
const baseUri = batches.baseURIs[i];
|
||||
for (
|
||||
let j = range.startIdInclusive.toNumber();
|
||||
j < range.endIdNonInclusive.toNumber();
|
||||
j++
|
||||
) {
|
||||
const uri = `${baseUri}/${j}`;
|
||||
const metadata = this.storage.downloadJSON(uri);
|
||||
nftsInRange.push(metadata);
|
||||
}
|
||||
|
||||
return nftsInRange;
|
||||
})
|
||||
.flat(),
|
||||
);
|
||||
|
||||
return nfts;
|
||||
}
|
||||
|
||||
public async getTokensInTier(tier: string): Promise<NFT[]> {
|
||||
const endIndex =
|
||||
await this.contractWrapper.readContract.getTokensInTierLen();
|
||||
if (endIndex.eq(0)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ranges = await this.contractWrapper.readContract.getTokensInTier(
|
||||
tier,
|
||||
0,
|
||||
endIndex,
|
||||
);
|
||||
|
||||
const nfts = await Promise.all(
|
||||
ranges
|
||||
.map((range) => {
|
||||
const nftsInRange = [];
|
||||
for (
|
||||
let i = range.startIdInclusive.toNumber();
|
||||
i < range.endIdNonInclusive.toNumber();
|
||||
i++
|
||||
) {
|
||||
nftsInRange.push(this.erc721.get(i));
|
||||
}
|
||||
return nftsInRange;
|
||||
})
|
||||
.flat(),
|
||||
);
|
||||
|
||||
return nfts;
|
||||
}
|
||||
|
||||
createBatchWithTier = buildTransactionFunction(
|
||||
async (
|
||||
metadatas: NFTMetadataOrUri[],
|
||||
tier: string,
|
||||
options?: {
|
||||
onProgress: (event: UploadProgressEvent) => void;
|
||||
},
|
||||
): Promise<Transaction<TransactionResultWithId<NFTMetadata>[]>> => {
|
||||
// TODO: Change this to on extension
|
||||
const startFileNumber = await this.erc721.nextTokenIdToMint();
|
||||
const batch = await uploadOrExtractURIs(
|
||||
metadatas,
|
||||
this.storage,
|
||||
startFileNumber.toNumber(),
|
||||
options,
|
||||
);
|
||||
const baseUri = getBaseUriFromBatch(batch);
|
||||
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "lazyMint",
|
||||
args: [
|
||||
batch.length,
|
||||
baseUri.endsWith("/") ? baseUri : `${baseUri}/`,
|
||||
tier,
|
||||
ethers.utils.toUtf8Bytes(""),
|
||||
],
|
||||
parse: (receipt) => {
|
||||
const event = this.contractWrapper.parseLogs<TokensLazyMintedEvent>(
|
||||
"TokensLazyMinted",
|
||||
receipt?.logs,
|
||||
);
|
||||
|
||||
const startingIndex = event[0].args[1];
|
||||
const endingIndex = event[0].args[2];
|
||||
const results: TransactionResultWithId<NFTMetadata>[] = [];
|
||||
for (let id = startingIndex; id.lte(endingIndex); id = id.add(1)) {
|
||||
results.push({
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc721.getTokenMetadata(id),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
createDelayedRevealBatchWithTier = buildTransactionFunction(
|
||||
async (
|
||||
placeholder: NFTMetadataInput,
|
||||
metadatas: NFTMetadataInput[],
|
||||
password: string,
|
||||
tier: string,
|
||||
options?: {
|
||||
onProgress: (event: UploadProgressEvent) => void;
|
||||
},
|
||||
): Promise<Transaction<TransactionResultWithId<NFTMetadata>[]>> => {
|
||||
if (!password) {
|
||||
throw new Error("Password is required");
|
||||
}
|
||||
|
||||
const placeholderUris = await this.storage.uploadBatch(
|
||||
[CommonNFTInput.parse(placeholder)],
|
||||
{
|
||||
rewriteFileNames: {
|
||||
fileStartNumber: 0,
|
||||
},
|
||||
},
|
||||
);
|
||||
const placeholderUri = getBaseUriFromBatch(placeholderUris);
|
||||
const startFileNumber = await this.erc721.nextTokenIdToMint();
|
||||
const uris = await this.storage.uploadBatch(
|
||||
metadatas.map((m) => CommonNFTInput.parse(m)),
|
||||
{
|
||||
onProgress: options?.onProgress,
|
||||
rewriteFileNames: {
|
||||
fileStartNumber: startFileNumber.toNumber(),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const baseUri = getBaseUriFromBatch(uris);
|
||||
const baseUriId =
|
||||
await this.contractWrapper.readContract.getBaseURICount();
|
||||
const chainId = await this.contractWrapper.getChainID();
|
||||
const hashedPassword = ethers.utils.solidityKeccak256(
|
||||
["string", "uint256", "uint256", "address"],
|
||||
[
|
||||
password,
|
||||
chainId,
|
||||
baseUriId,
|
||||
this.contractWrapper.readContract.address,
|
||||
],
|
||||
);
|
||||
|
||||
const encryptedBaseUri =
|
||||
await this.contractWrapper.readContract.encryptDecrypt(
|
||||
ethers.utils.toUtf8Bytes(baseUri),
|
||||
hashedPassword,
|
||||
);
|
||||
|
||||
let data: string;
|
||||
const provenanceHash = ethers.utils.solidityKeccak256(
|
||||
["bytes", "bytes", "uint256"],
|
||||
[ethers.utils.toUtf8Bytes(baseUri), hashedPassword, chainId],
|
||||
);
|
||||
data = ethers.utils.defaultAbiCoder.encode(
|
||||
["bytes", "bytes32"],
|
||||
[encryptedBaseUri, provenanceHash],
|
||||
);
|
||||
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "lazyMint",
|
||||
args: [
|
||||
uris.length,
|
||||
placeholderUri.endsWith("/") ? placeholderUri : `${placeholderUri}/`,
|
||||
tier,
|
||||
data,
|
||||
],
|
||||
parse: (receipt) => {
|
||||
const event = this.contractWrapper.parseLogs<TokensLazyMintedEvent>(
|
||||
"TokensLazyMinted",
|
||||
receipt?.logs,
|
||||
);
|
||||
const startingIndex = event[0].args[1];
|
||||
const endingIndex = event[0].args[2];
|
||||
const results: TransactionResultWithId<NFTMetadata>[] = [];
|
||||
for (let id = startingIndex; id.lte(endingIndex); id = id.add(1)) {
|
||||
results.push({
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc721.getTokenMetadata(id),
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
reveal = buildTransactionFunction(
|
||||
async (batchId: BigNumberish, password: string) => {
|
||||
if (!password) {
|
||||
throw new Error("Password is required");
|
||||
}
|
||||
const chainId = await this.contractWrapper.getChainID();
|
||||
const key = ethers.utils.solidityKeccak256(
|
||||
["string", "uint256", "uint256", "address"],
|
||||
[password, chainId, batchId, this.contractWrapper.readContract.address],
|
||||
);
|
||||
// performing the reveal locally to make sure it'd succeed before sending the transaction
|
||||
try {
|
||||
const decryptedUri = await this.contractWrapper
|
||||
.callStatic()
|
||||
.reveal(batchId, key);
|
||||
// basic sanity check for making sure decryptedUri is valid
|
||||
// this is optional because invalid decryption key would result in non-utf8 bytes and
|
||||
// ethers would throw when trying to decode it
|
||||
if (!decryptedUri.includes("://") || !decryptedUri.endsWith("/")) {
|
||||
throw new Error("invalid password");
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error("invalid password");
|
||||
}
|
||||
|
||||
return Transaction.fromContractWrapper({
|
||||
contractWrapper: this.contractWrapper,
|
||||
method: "reveal",
|
||||
args: [batchId, key],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
public async generate(
|
||||
payloadToSign: TieredDropPayloadInput,
|
||||
): Promise<TieredDropPayloadWithSignature> {
|
||||
const [payload] = await this.generateBatch([payloadToSign]);
|
||||
return payload;
|
||||
}
|
||||
|
||||
public async generateBatch(
|
||||
payloadsToSign: TieredDropPayloadInput[],
|
||||
): Promise<TieredDropPayloadWithSignature[]> {
|
||||
const parsedPayloads = await Promise.all(
|
||||
payloadsToSign.map((payload) =>
|
||||
TieredDropPayloadSchema.parseAsync(payload),
|
||||
),
|
||||
);
|
||||
const chainId = await this.contractWrapper.getChainID();
|
||||
const signer = this.contractWrapper.getSigner();
|
||||
invariant(signer, "No signer available");
|
||||
|
||||
return await Promise.all(
|
||||
parsedPayloads.map(async (payload) => {
|
||||
const signature = await this.contractWrapper.signTypedData(
|
||||
signer,
|
||||
{
|
||||
name: "SignatureAction",
|
||||
version: "1",
|
||||
chainId,
|
||||
verifyingContract: this.contractWrapper.readContract.address,
|
||||
},
|
||||
{ GenericRequest: GenericRequest },
|
||||
await this.mapPayloadToContractStruct(payload),
|
||||
);
|
||||
|
||||
return { payload, signature: signature.toString() };
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async verify(
|
||||
signedPayload: TieredDropPayloadWithSignature,
|
||||
): Promise<boolean> {
|
||||
const message = await this.mapPayloadToContractStruct(
|
||||
signedPayload.payload,
|
||||
);
|
||||
const verification = await this.contractWrapper.readContract.verify(
|
||||
message,
|
||||
signedPayload.signature,
|
||||
);
|
||||
return verification[0];
|
||||
}
|
||||
|
||||
public async claimWithSignature(
|
||||
signedPayload: TieredDropPayloadWithSignature,
|
||||
): Promise<TransactionResultWithId<NFT>[]> {
|
||||
const message = await this.mapPayloadToContractStruct(
|
||||
signedPayload.payload,
|
||||
);
|
||||
const normalizedTotalPrice = await normalizePriceValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
signedPayload.payload.price,
|
||||
signedPayload.payload.currencyAddress,
|
||||
);
|
||||
|
||||
const overrides = await this.contractWrapper.getCallOverrides();
|
||||
await setErc20Allowance(
|
||||
this.contractWrapper,
|
||||
normalizedTotalPrice,
|
||||
signedPayload.payload.currencyAddress,
|
||||
overrides,
|
||||
);
|
||||
|
||||
const receipt = await this.contractWrapper.sendTransaction(
|
||||
"claimWithSignature",
|
||||
[message, signedPayload.signature],
|
||||
overrides,
|
||||
);
|
||||
const event = this.contractWrapper.parseLogs<TokensClaimedEvent>(
|
||||
"TokensClaimed",
|
||||
receipt?.logs,
|
||||
);
|
||||
const startingIndex = event[0].args.startTokenId;
|
||||
const endingIndex = startingIndex.add(event[0].args.quantityClaimed);
|
||||
const results: TransactionResultWithId<NFT>[] = [];
|
||||
for (let id = startingIndex; id.lt(endingIndex); id = id.add(1)) {
|
||||
results.push({
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc721.get(id),
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private async mapPayloadToContractStruct(
|
||||
payload: TieredDropPayloadOutput,
|
||||
): Promise<ISignatureAction.GenericRequestStruct> {
|
||||
const normalizedTotalPrice = await normalizePriceValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
payload.price,
|
||||
payload.currencyAddress,
|
||||
);
|
||||
const data = ethers.utils.defaultAbiCoder.encode(
|
||||
[
|
||||
"string[]",
|
||||
"address",
|
||||
"address",
|
||||
"uint256",
|
||||
"address",
|
||||
"uint256",
|
||||
"uint256",
|
||||
"address",
|
||||
],
|
||||
[
|
||||
payload.tierPriority,
|
||||
payload.to,
|
||||
payload.royaltyRecipient,
|
||||
payload.royaltyBps,
|
||||
payload.primarySaleRecipient,
|
||||
payload.quantity,
|
||||
normalizedTotalPrice,
|
||||
payload.currencyAddress,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
uid: payload.uid,
|
||||
validityStartTimestamp: payload.mintStartTime,
|
||||
validityEndTimestamp: payload.mintEndTime,
|
||||
data,
|
||||
} as ISignatureAction.GenericRequestStruct;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user