Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9b564feaf | ||
|
|
87fd6ab14e | ||
|
|
df74340fa5 | ||
|
|
a57b4f01f3 | ||
|
|
126752d7d0 | ||
|
|
89ff921a7d | ||
|
|
9124b3180c | ||
|
|
f41a443c57 | ||
|
|
b7cd744cd0 | ||
|
|
bb1b4e6ac3 | ||
|
|
2f8ec89c4c | ||
|
|
6ae52664e5 | ||
|
|
4f867e15ed | ||
|
|
cc8f983615 | ||
|
|
f45ef62128 | ||
|
|
9727502eb3 | ||
|
|
b6eec6146c | ||
|
|
88daa3c5cb | ||
|
|
53c6507669 | ||
|
|
782b58d3f6 | ||
|
|
e17e41f4d1 | ||
|
|
712b11a43e |
@@ -1,5 +1,11 @@
|
||||
# @thirdweb-dev/auth
|
||||
|
||||
## 2.0.19
|
||||
|
||||
## 2.0.18
|
||||
|
||||
## 2.0.17
|
||||
|
||||
## 2.0.16
|
||||
|
||||
## 2.0.15
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "2.0.16",
|
||||
"version": "2.0.19",
|
||||
"exports": {
|
||||
"./next": {
|
||||
"module": "./next/dist/thirdweb-dev-auth-next.esm.js",
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# thirdweb
|
||||
|
||||
## 0.8.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#326](https://github.com/thirdweb-dev/js/pull/326) [`126752d`](https://github.com/thirdweb-dev/js/commit/126752d7d02c9a808a63fb9e67a9df6658b5682b) Thanks [@jnsdls](https://github.com/jnsdls)! - remove tons of unused dependencies, use SDK function for extracting ipfs has from bytecode
|
||||
|
||||
- Updated dependencies [[`126752d`](https://github.com/thirdweb-dev/js/commit/126752d7d02c9a808a63fb9e67a9df6658b5682b), [`df74340`](https://github.com/thirdweb-dev/js/commit/df74340fa51323304c32419761d6f18628b060fa), [`89ff921`](https://github.com/thirdweb-dev/js/commit/89ff921a7d9f0e42b4e6707c8b56d0aab95c1aa0), [`a57b4f0`](https://github.com/thirdweb-dev/js/commit/a57b4f01f3a49590b897101f2730582e4124a554)]:
|
||||
- @thirdweb-dev/sdk@3.3.0
|
||||
|
||||
## 0.8.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4f867e1`](https://github.com/thirdweb-dev/js/commit/4f867e15edbe5757afbf54cab207458711a8986c), [`b6eec61`](https://github.com/thirdweb-dev/js/commit/b6eec6146c23b66a3681bbb48b173b9019254214), [`9727502`](https://github.com/thirdweb-dev/js/commit/9727502eb30ac139382b7c7c8e8fc0967cbfbcf1), [`782b58d`](https://github.com/thirdweb-dev/js/commit/782b58d3f6a3b49ff9b3d837551db5161f96171a), [`f41a443`](https://github.com/thirdweb-dev/js/commit/f41a443c5785240197607b5bf93ff18a1c8e8307), [`f45ef62`](https://github.com/thirdweb-dev/js/commit/f45ef62128cfefe06275a38440c90d45e9f3cef7), [`2f8ec89`](https://github.com/thirdweb-dev/js/commit/2f8ec89c4c83ae577092c840c198e6fcfb114e69), [`b7cd744`](https://github.com/thirdweb-dev/js/commit/b7cd744cd0482a6c3fe5ae0c858f20cd380d4114), [`53c6507`](https://github.com/thirdweb-dev/js/commit/53c65076696a43bd9a7e58a7493d4e65c172900f)]:
|
||||
- @thirdweb-dev/sdk@3.2.6
|
||||
|
||||
## 0.8.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`712b11a`](https://github.com/thirdweb-dev/js/commit/712b11a43e389ae10b7f09648779a69c05b2c913)]:
|
||||
- @thirdweb-dev/sdk@3.2.5
|
||||
|
||||
## 0.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"main": "dist/cli/index.js",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.5",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,24 +31,17 @@
|
||||
"dependencies": {
|
||||
"@thirdweb-dev/sdk": "*",
|
||||
"async-retry": "^1.3.3",
|
||||
"cbor": "^8.1.0",
|
||||
"commander": "^9.1.0",
|
||||
"cpy": "^9.0.1",
|
||||
"enquirer": "^2.3.6",
|
||||
"ethers": "^5.7.0",
|
||||
"form-data": "^4.0.0",
|
||||
"got": "11.8.5",
|
||||
"inquirer": "^8.2.3",
|
||||
"ipfs-unixfs": "^6.0.7",
|
||||
"ipld-dag-pb": "^0.22.3",
|
||||
"multihashes": "^4.0.3",
|
||||
"open": "^8.4.0",
|
||||
"prompts": "^2.4.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"solc": "0.8.13",
|
||||
"tar": "^6.1.11",
|
||||
"tslog": "^3.3.3",
|
||||
"update-check": "^1.5.4",
|
||||
"update-notifier": "^6.0.2",
|
||||
"validate-npm-package-name": "^4.0.0",
|
||||
"yaml": "^2.1.1"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { extractIPFSHashFromBytecode } from "../helpers/ipfs";
|
||||
import { logger } from "../helpers/logger";
|
||||
import { CompileOptions, IBuilder } from "../interfaces/Builder";
|
||||
import { ContractPayload } from "../interfaces/ContractPayload";
|
||||
import { extractIPFSHashFromBytecode } from "@thirdweb-dev/sdk/evm";
|
||||
|
||||
export abstract class BaseBuilder implements IBuilder {
|
||||
abstract compile(
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import { decodeFirstSync } from "cbor";
|
||||
import { UnixFS } from "ipfs-unixfs";
|
||||
import { DAGNode } from "ipld-dag-pb";
|
||||
import { toB58String } from "multihashes";
|
||||
|
||||
/**
|
||||
* Derives IPFS hash of string
|
||||
* @param {String} str
|
||||
* @return {String} IPFS hash (ex: "Qm")
|
||||
*/
|
||||
export async function getIPFSHash(str: string) {
|
||||
const file = new UnixFS({
|
||||
type: "file",
|
||||
data: Buffer.from(str),
|
||||
});
|
||||
const node = new DAGNode(file.marshal());
|
||||
const metadataLink = await node.toDAGLink();
|
||||
return toB58String(metadataLink.Hash.multihash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the IPFS hash from the given bytecode
|
||||
* @param bytecode
|
||||
* @returns
|
||||
*/
|
||||
export function extractIPFSHashFromBytecode(
|
||||
bytecode: string,
|
||||
): string | undefined {
|
||||
const numericBytecode = hexToBytes(bytecode);
|
||||
const cborLength: number =
|
||||
numericBytecode[numericBytecode.length - 2] * 0x100 +
|
||||
numericBytecode[numericBytecode.length - 1];
|
||||
const bytecodeBuffer = Buffer.from(
|
||||
numericBytecode.slice(numericBytecode.length - 2 - cborLength, -2),
|
||||
);
|
||||
const cborData = decodeFirstSync(bytecodeBuffer);
|
||||
if (cborData["ipfs"]) {
|
||||
const uri = toB58String(cborData["ipfs"]);
|
||||
return uri;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function hexToBytes(hex: string | number) {
|
||||
hex = hex.toString(16);
|
||||
if (!hex.startsWith("0x")) {
|
||||
hex = `0x${hex}`;
|
||||
}
|
||||
if (!isHexStrict(hex)) {
|
||||
throw new Error('Given value "' + hex + '" is not a valid hex string.');
|
||||
}
|
||||
|
||||
hex = hex.replace(/^0x/i, "");
|
||||
|
||||
for (var bytes = [], c = 0; c < hex.length; c += 2) {
|
||||
bytes.push(parseInt(hex.slice(c, c + 2), 16));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function isHexStrict(hex: string | number) {
|
||||
return (
|
||||
(typeof hex === "string" || typeof hex === "number") &&
|
||||
/^(-)?0x[0-9a-f]*$/i.test(hex.toString())
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,8 @@ module.exports = {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "error",
|
||||
"@typescript-eslint/no-parameter-properties": "error",
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
// making this an error going forward
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
|
||||
// import rules
|
||||
"import/first": "error",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @thirdweb-dev/react
|
||||
|
||||
## 3.3.0
|
||||
|
||||
## 3.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#316](https://github.com/thirdweb-dev/js/pull/316) [`6ae5266`](https://github.com/thirdweb-dev/js/commit/6ae52664e5865564cd4fc2e00e3120675b9e0ca3) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix SIWE when using an external signer
|
||||
|
||||
- [#310](https://github.com/thirdweb-dev/js/pull/310) [`9727502`](https://github.com/thirdweb-dev/js/commit/9727502eb30ac139382b7c7c8e8fc0967cbfbcf1) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - [SOL] - add useMintNFTSupply hook in react and accomodate inputs for it in SDK
|
||||
|
||||
- [#319](https://github.com/thirdweb-dev/js/pull/319) [`2f8ec89`](https://github.com/thirdweb-dev/js/commit/2f8ec89c4c83ae577092c840c198e6fcfb114e69) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Expose total claimed and unclaimed supply for erc721 drop contracts
|
||||
|
||||
- [#318](https://github.com/thirdweb-dev/js/pull/318) [`bb1b4e6`](https://github.com/thirdweb-dev/js/commit/bb1b4e6ac39d98cc68b8e31e659cd68be3a5b967) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Add useMinimumNextBid hook and change invariant for other marketplace hooks
|
||||
|
||||
## 3.2.5
|
||||
|
||||
## 3.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react",
|
||||
"version": "3.2.4",
|
||||
"version": "3.3.0",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../types/shared";
|
||||
import { RequiredParam } from "./required-param";
|
||||
import type { QueryKey } from "@tanstack/react-query";
|
||||
import type { ValidContractInstance } from "@thirdweb-dev/sdk";
|
||||
import type {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
* Makes a parameter required to be passed, but still allowes it to be null or undefined.
|
||||
*
|
||||
* @beta
|
||||
*/
|
||||
export type RequiredParam<T> = T | null | undefined;
|
||||
|
||||
export function requiredParamInvariant<TValue>(
|
||||
condition: RequiredParam<TValue>,
|
||||
message?: string,
|
||||
): asserts condition {
|
||||
invariant(condition !== null || condition !== undefined, message);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Makes a parameter required to be passed, but still allowes it to be null or undefined.
|
||||
*
|
||||
* @beta
|
||||
*/
|
||||
export type RequiredParam<T> = T | null | undefined;
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
SupportedChainId,
|
||||
defaultSupportedChains,
|
||||
} from "../constants/chain";
|
||||
import { ChainId } from "@thirdweb-dev/sdk";
|
||||
import { DEFAULT_RPC_URLS } from "@thirdweb-dev/sdk";
|
||||
import React, { PropsWithChildren, createContext, useContext } from "react";
|
||||
|
||||
interface ThirdwebConfigContext {
|
||||
@@ -11,17 +11,8 @@ interface ThirdwebConfigContext {
|
||||
supportedChains: Chain[];
|
||||
}
|
||||
|
||||
export const defaultChainRpc: Record<SupportedChainId | number, string> = {
|
||||
[ChainId.Mainnet]: "mainnet",
|
||||
[ChainId.Goerli]: "goerli",
|
||||
[ChainId.Polygon]: "polygon",
|
||||
[ChainId.Mumbai]: "mumbai",
|
||||
[ChainId.Fantom]: "fantom",
|
||||
[ChainId.Avalanche]: "avalanche",
|
||||
};
|
||||
|
||||
const ThirdwebConfigContext = createContext<ThirdwebConfigContext>({
|
||||
rpcUrlMap: defaultChainRpc,
|
||||
rpcUrlMap: DEFAULT_RPC_URLS,
|
||||
supportedChains: defaultSupportedChains,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
RequiredParam,
|
||||
requiredParamInvariant,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useSDKChainId } from "../../providers/base";
|
||||
import { getErcs, DropContract, WalletAddress } from "../../types";
|
||||
import {
|
||||
@@ -7,7 +10,11 @@ import {
|
||||
} from "../../utils/cache-keys";
|
||||
import { useQueryWithNetwork } from "../query-utils/useQueryWithNetwork";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { ClaimCondition, ClaimConditionInput } from "@thirdweb-dev/sdk";
|
||||
import {
|
||||
ClaimCondition,
|
||||
ClaimConditionFetchOptions,
|
||||
ClaimConditionInput,
|
||||
} from "@thirdweb-dev/sdk";
|
||||
import { BigNumberish } from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
@@ -62,25 +69,30 @@ export type SetClaimConditionsParams = {
|
||||
export function useActiveClaimCondition(
|
||||
contract: RequiredParam<DropContract>,
|
||||
tokenId?: BigNumberish,
|
||||
options?: ClaimConditionFetchOptions,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
const { erc1155, erc721, erc20 } = getErcs(contract);
|
||||
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.extensions.claimConditions.getActive(contractAddress, tokenId),
|
||||
cacheKeys.extensions.claimConditions.getActive(
|
||||
contractAddress,
|
||||
tokenId,
|
||||
options,
|
||||
),
|
||||
() => {
|
||||
if (erc1155) {
|
||||
invariant(
|
||||
tokenId !== undefined,
|
||||
requiredParamInvariant(
|
||||
tokenId,
|
||||
"tokenId is required for ERC1155 claim conditions",
|
||||
);
|
||||
return erc1155.claimConditions.getActive(tokenId);
|
||||
return erc1155.claimConditions.getActive(tokenId, options);
|
||||
}
|
||||
if (erc721) {
|
||||
return erc721.claimConditions.getActive();
|
||||
return erc721.claimConditions.getActive(options);
|
||||
}
|
||||
if (erc20) {
|
||||
return erc20.claimConditions.getActive();
|
||||
return erc20.claimConditions.getActive(options);
|
||||
}
|
||||
throw new Error("Contract must be ERC721, ERC1155 or ERC20");
|
||||
},
|
||||
@@ -118,22 +130,30 @@ export function useActiveClaimCondition(
|
||||
export function useClaimConditions(
|
||||
contract: RequiredParam<DropContract>,
|
||||
tokenId?: BigNumberish,
|
||||
options?: ClaimConditionFetchOptions,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
const { erc1155, erc721, erc20 } = getErcs(contract);
|
||||
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.extensions.claimConditions.getAll(contractAddress, tokenId),
|
||||
cacheKeys.extensions.claimConditions.getAll(
|
||||
contractAddress,
|
||||
tokenId,
|
||||
options,
|
||||
),
|
||||
() => {
|
||||
if (erc1155) {
|
||||
invariant(tokenId, "tokenId is required for ERC1155 claim conditions");
|
||||
return erc1155.claimConditions.getAll(tokenId);
|
||||
requiredParamInvariant(
|
||||
tokenId,
|
||||
"tokenId is required for ERC1155 claim conditions",
|
||||
);
|
||||
return erc1155.claimConditions.getAll(tokenId, options);
|
||||
}
|
||||
if (erc721) {
|
||||
return erc721.claimConditions.getAll();
|
||||
return erc721.claimConditions.getAll(options);
|
||||
}
|
||||
if (erc20) {
|
||||
return erc20.claimConditions.getAll();
|
||||
return erc20.claimConditions.getAll(options);
|
||||
}
|
||||
throw new Error("Contract must be ERC721, ERC1155 or ERC20");
|
||||
},
|
||||
@@ -184,7 +204,7 @@ export function useClaimIneligibilityReasons(
|
||||
),
|
||||
() => {
|
||||
if (erc1155) {
|
||||
invariant(
|
||||
requiredParamInvariant(
|
||||
tokenId,
|
||||
"tokenId is required for ERC1155 claim ineligibility reasons",
|
||||
);
|
||||
@@ -270,11 +290,14 @@ export function useSetClaimConditions(
|
||||
|
||||
return useMutation(
|
||||
async (data: SetClaimConditionsParams) => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
const { phases, reset = false } = data;
|
||||
invariant(phases, 'No "phases" provided');
|
||||
if (erc1155) {
|
||||
invariant(tokenId, "tokenId is required for ERC1155 claim conditions");
|
||||
requiredParamInvariant(
|
||||
tokenId,
|
||||
"tokenId is required for ERC1155 claim conditions",
|
||||
);
|
||||
return erc1155.claimConditions.set(tokenId, phases, reset);
|
||||
}
|
||||
if (erc721) {
|
||||
@@ -341,7 +364,7 @@ export function useResetClaimConditions(
|
||||
|
||||
return useMutation(
|
||||
async () => {
|
||||
const cleanConditions = (conditions: ClaimCondition[]) => {
|
||||
const cleanConditions = async (conditions: ClaimCondition[]) => {
|
||||
return conditions.map((c) => ({
|
||||
...c,
|
||||
price: c.currencyMetadata.displayValue,
|
||||
@@ -351,25 +374,34 @@ export function useResetClaimConditions(
|
||||
};
|
||||
|
||||
if (erc1155) {
|
||||
invariant(tokenId, "tokenId is required for ERC1155 claim conditions");
|
||||
const claimConditions = await erc1155.claimConditions.getAll(tokenId);
|
||||
requiredParamInvariant(
|
||||
tokenId,
|
||||
"tokenId is required for ERC1155 claim conditions",
|
||||
);
|
||||
const claimConditions = await erc1155.claimConditions.getAll(tokenId, {
|
||||
withAllowList: true,
|
||||
});
|
||||
return erc1155.claimConditions.set(
|
||||
tokenId,
|
||||
cleanConditions(claimConditions || []),
|
||||
await cleanConditions(claimConditions || []),
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (erc721) {
|
||||
const claimConditions = await erc721.claimConditions.getAll();
|
||||
const claimConditions = await erc721.claimConditions.getAll({
|
||||
withAllowList: true,
|
||||
});
|
||||
return await erc721.claimConditions.set(
|
||||
cleanConditions(claimConditions || []),
|
||||
await cleanConditions(claimConditions || []),
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (erc20) {
|
||||
const claimConditions = await erc20.claimConditions.getAll();
|
||||
const claimConditions = await erc20.claimConditions.getAll({
|
||||
withAllowList: true,
|
||||
});
|
||||
return await erc20.claimConditions.set(
|
||||
cleanConditions(claimConditions || []),
|
||||
await cleanConditions(claimConditions || []),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
RequiredParam,
|
||||
requiredParamInvariant,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useSDKChainId } from "../../providers/base";
|
||||
import { WalletAddress } from "../../types";
|
||||
import {
|
||||
@@ -35,7 +38,7 @@ export function usePrimarySaleRecipient(
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.extensions.sales.getRecipient(contractAddress),
|
||||
() => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(
|
||||
"sales" in contract && contract.sales,
|
||||
"Contract does not support primarySale",
|
||||
@@ -86,7 +89,7 @@ export function useUpdatePrimarySaleRecipient(
|
||||
const activeChainId = useSDKChainId();
|
||||
return useMutation(
|
||||
(newRecipient: WalletAddress) => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(
|
||||
"sales" in contract && contract.sales,
|
||||
"Contract does not support primarySale",
|
||||
@@ -128,7 +131,7 @@ export function useRoyaltySettings(
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.extensions.royalties.getDefaultRoyaltyInfo(contractAddress),
|
||||
() => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(
|
||||
"royalties" in contract && contract.royalties,
|
||||
"Contract does not support royalties",
|
||||
@@ -182,7 +185,7 @@ export function useUpdateRoyaltySettings(
|
||||
seller_fee_basis_points?: number;
|
||||
fee_recipient?: WalletAddress;
|
||||
}) => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(
|
||||
"royalties" in contract && contract.royalties,
|
||||
"Contract does not support royalties",
|
||||
@@ -224,7 +227,7 @@ export function usePlatformFees(
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.extensions.platformFees.get(contractAddress),
|
||||
() => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(
|
||||
"platformFees" in contract && contract.platformFees,
|
||||
"Contract does not support platformFees",
|
||||
@@ -277,7 +280,7 @@ export function useUpdatePlatformFees(
|
||||
platform_fee_basis_points?: number;
|
||||
fee_recipient?: WalletAddress;
|
||||
}) => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(
|
||||
"platformFees" in contract && contract.platformFees,
|
||||
"Contract does not support platformFees",
|
||||
@@ -317,7 +320,7 @@ export function useMetadata(contract: RequiredParam<ValidContractInstance>) {
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.extensions.metadata.get(contractAddress),
|
||||
() => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(
|
||||
"metadata" in contract && contract.metadata,
|
||||
"Contract does not support metadata",
|
||||
@@ -366,7 +369,7 @@ export function useUpdateMetadata(
|
||||
const activeChainId = useSDKChainId();
|
||||
return useMutation(
|
||||
(updatePayload: CustomContractMetadata) => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(
|
||||
"metadata" in contract && contract.metadata,
|
||||
"Contract does not support metadata",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { neverPersist } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
RequiredParam,
|
||||
requiredParamInvariant,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useSDK, useSDKChainId } from "../../providers/base";
|
||||
import { ContractAddress } from "../../types";
|
||||
import {
|
||||
@@ -216,7 +219,7 @@ export function useContract(
|
||||
{ wallet, walletChainId, sdkTimestamp },
|
||||
]),
|
||||
async () => {
|
||||
invariant(contractAddress, "contract address is required");
|
||||
requiredParamInvariant(contractAddress, "contract address is required");
|
||||
invariant(sdk, "SDK not initialized");
|
||||
invariant(activeChainId, "active chain id is required");
|
||||
|
||||
@@ -290,7 +293,7 @@ export function useContractMetadata<TContract extends ValidContractInstance>(
|
||||
>(
|
||||
cacheKeys.contract.metadata(contract?.getAddress()),
|
||||
async () => {
|
||||
invariant(contract, "contract is required");
|
||||
requiredParamInvariant(contract, "contract is required");
|
||||
return await contract.metadata.get();
|
||||
},
|
||||
{
|
||||
@@ -311,7 +314,7 @@ export function useContractMetadataUpdate(
|
||||
|
||||
return useMutation(
|
||||
async (metadata: CommonContractSchemaInput) => {
|
||||
invariant(contract, "contract must be defined");
|
||||
requiredParamInvariant(contract, "contract must be defined");
|
||||
|
||||
return contract.metadata.update(metadata);
|
||||
},
|
||||
@@ -406,7 +409,7 @@ export function useContractEvents(
|
||||
return useQuery(
|
||||
cacheKey,
|
||||
() => {
|
||||
invariant(contract, "contract must be defined");
|
||||
requiredParamInvariant(contract, "contract must be defined");
|
||||
if (eventName) {
|
||||
return contract.events.getEvents(eventName, options.queryFilter);
|
||||
}
|
||||
@@ -447,8 +450,8 @@ export function useContractRead(
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.call(contractAddress, functionName, args),
|
||||
() => {
|
||||
invariant(contract, "contract must be defined");
|
||||
invariant(functionName, "function name must be provided");
|
||||
requiredParamInvariant(contract, "contract must be defined");
|
||||
requiredParamInvariant(functionName, "function name must be provided");
|
||||
return contract.call(functionName, ...args);
|
||||
},
|
||||
{
|
||||
@@ -485,8 +488,8 @@ export function useContractWrite(
|
||||
|
||||
return useMutation(
|
||||
async (callParams?: unknown[] | [...unknown[], CallOverrides]) => {
|
||||
invariant(contract, "contract must be defined");
|
||||
invariant(functionName, "function name must be provided");
|
||||
requiredParamInvariant(contract, "contract must be defined");
|
||||
requiredParamInvariant(functionName, "function name must be provided");
|
||||
if (!callParams?.length) {
|
||||
return contract.call(functionName);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
RequiredParam,
|
||||
requiredParamInvariant,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useSDKChainId } from "../../providers/base";
|
||||
import {
|
||||
ClaimNFTParams,
|
||||
@@ -51,7 +54,7 @@ export function useUnclaimedNFTs(
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.nft.drop.getAllUnclaimed(contractAddress, queryParams),
|
||||
() => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
// TODO make this work for custom contracts (needs ABI change)
|
||||
invariant(
|
||||
contract.getAllUnclaimed,
|
||||
@@ -94,18 +97,18 @@ export function useClaimedNFTs(
|
||||
*/
|
||||
export function useUnclaimedNFTSupply(contract: RequiredParam<DropContract>) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
const { erc721 } = getErcs(contract);
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.nft.drop.totalUnclaimedSupply(contractAddress),
|
||||
() => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
// TODO make this work for custom contracts (needs ABI change)
|
||||
invariant(
|
||||
"totalUnclaimedSupply" in contract && contract.totalUnclaimedSupply,
|
||||
"Contract instance does not support fetching unclaimed supply",
|
||||
);
|
||||
return contract.totalUnclaimedSupply();
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
if (erc721) {
|
||||
invariant(erc721, "No ERC721 Contract instance provided");
|
||||
return erc721.totalUnclaimedSupply();
|
||||
}
|
||||
invariant(false, "Contract is not an instance of ERC721")
|
||||
},
|
||||
{ enabled: !!contract },
|
||||
{ enabled: !!erc721 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,18 +121,17 @@ export function useUnclaimedNFTSupply(contract: RequiredParam<DropContract>) {
|
||||
*/
|
||||
export function useClaimedNFTSupply(contract: RequiredParam<DropContract>) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
const { erc721 } = getErcs(contract);
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.nft.drop.totalClaimedSupply(contractAddress),
|
||||
() => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
// TODO make this work for custom contracts (needs ABI change)
|
||||
invariant(
|
||||
"totalClaimedSupply" in contract && contract.totalClaimedSupply,
|
||||
"Contract instance does not support fetching unclaimed supply",
|
||||
);
|
||||
return contract.totalClaimedSupply();
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
if (erc721) {
|
||||
return erc721.totalClaimedSupply();
|
||||
}
|
||||
invariant(false, "Contract is not an instance of ERC721")
|
||||
},
|
||||
{ enabled: !!contract },
|
||||
{ enabled: !!erc721 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -206,10 +208,10 @@ export function useClaimNFT<TContract extends DropContract>(
|
||||
|
||||
return useMutation(
|
||||
async (data: ClaimNFTParams) => {
|
||||
invariant(contract, "contract is undefined");
|
||||
requiredParamInvariant(contract, "contract is undefined");
|
||||
|
||||
if (erc1155) {
|
||||
invariant(!!data.tokenId, "tokenId not provided");
|
||||
requiredParamInvariant(data.tokenId, "tokenId not provided");
|
||||
if (!data.to) {
|
||||
return (await erc1155.claim(
|
||||
data.tokenId,
|
||||
@@ -270,7 +272,7 @@ export function useLazyMint<TContract extends DropContract>(
|
||||
|
||||
return useMutation(
|
||||
async (data: { metadatas: NFTMetadataInput[] }) => {
|
||||
invariant(contract, "contract is undefined");
|
||||
requiredParamInvariant(contract, "contract is undefined");
|
||||
let options;
|
||||
if (onProgress) {
|
||||
options = {
|
||||
@@ -316,7 +318,7 @@ export function useDelayedRevealLazyMint<TContract extends RevealableContract>(
|
||||
|
||||
return useMutation(
|
||||
async (data: DelayedRevealLazyMintInput) => {
|
||||
invariant(contract, "contract is undefined");
|
||||
requiredParamInvariant(contract, "contract is undefined");
|
||||
let options;
|
||||
if (onProgress) {
|
||||
options = {
|
||||
@@ -370,7 +372,7 @@ export function useRevealLazyMint<TContract extends RevealableContract>(
|
||||
|
||||
return useMutation(
|
||||
async (data: RevealLazyMintInput) => {
|
||||
invariant(contract, "contract is undefined");
|
||||
requiredParamInvariant(contract, "contract is undefined");
|
||||
const { erc721, erc1155 } = getErcs(contract);
|
||||
if (erc721) {
|
||||
return await erc721.revealer.reveal(data.batchId, data.password);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
RequiredParam,
|
||||
requiredParamInvariant,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useSDKChainId } from "../../providers/base";
|
||||
import { BuyNowParams, MakeBidParams } from "../../types";
|
||||
import {
|
||||
@@ -17,7 +20,7 @@ import type {
|
||||
} from "@thirdweb-dev/sdk";
|
||||
import { ListingType } from "@thirdweb-dev/sdk";
|
||||
import { Marketplace } from "@thirdweb-dev/sdk/dist/declarations/src/evm/contracts/prebuilt-implementations/marketplace";
|
||||
import { BigNumber, BigNumberish } from "ethers";
|
||||
import { BigNumberish } from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/** **********************/
|
||||
@@ -45,8 +48,9 @@ export function useListing(
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.getListing(contractAddress, listingId),
|
||||
() => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
return contract.getListing(BigNumber.from(listingId || 0));
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(listingId, "No listing id provided");
|
||||
return contract.getListing(listingId);
|
||||
},
|
||||
{
|
||||
enabled: !!contract,
|
||||
@@ -76,7 +80,7 @@ export function useListings(
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.getAllListings(contractAddress, filter),
|
||||
() => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
return contract.getAllListings(filter);
|
||||
},
|
||||
{
|
||||
@@ -103,7 +107,7 @@ export function useListingsCount(contract: RequiredParam<Marketplace>) {
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.getTotalCount(contractAddress),
|
||||
() => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
return contract.getTotalCount();
|
||||
},
|
||||
{
|
||||
@@ -133,7 +137,7 @@ export function useActiveListings(
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.getActiveListings(contractAddress, filter),
|
||||
() => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
|
||||
return contract.getActiveListings(filter);
|
||||
},
|
||||
@@ -168,8 +172,9 @@ export function useWinningBid(
|
||||
listingId,
|
||||
),
|
||||
() => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
return contract.auction.getWinningBid(BigNumber.from(listingId || 0));
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(listingId, "No listing id provided");
|
||||
return contract.auction.getWinningBid(listingId);
|
||||
},
|
||||
{
|
||||
enabled: !!contract && listingId !== undefined,
|
||||
@@ -201,12 +206,11 @@ export function useAuctionWinner(
|
||||
listingId,
|
||||
),
|
||||
async () => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(listingId, "No listing id provided");
|
||||
let winner: string | undefined;
|
||||
try {
|
||||
winner = await contract.auction.getWinner(
|
||||
BigNumber.from(listingId || 0),
|
||||
);
|
||||
winner = await contract.auction.getWinner(listingId);
|
||||
} catch (err) {
|
||||
if (!(err as Error)?.message?.includes("Could not find auction")) {
|
||||
throw err;
|
||||
@@ -238,7 +242,7 @@ export function useBidBuffer(contract: RequiredParam<Marketplace>) {
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.getBidBufferBps(contractAddress),
|
||||
() => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
return contract.getBidBufferBps();
|
||||
},
|
||||
{
|
||||
@@ -247,6 +251,40 @@ export function useBidBuffer(contract: RequiredParam<Marketplace>) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get the minimum next bid for the auction listing from your marketplace contract.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: minimumNextBid, isLoading, error } = useMinimumNextBid(<YourMarketplaceContractInstance>, <listingId>);
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a marketplace contract
|
||||
* @param listingId - the listing id to check
|
||||
* @returns a response object that includes the minimum next bid for the auction listing
|
||||
* @beta
|
||||
*/
|
||||
export function useMinimumNextBid(
|
||||
contract: RequiredParam<Marketplace>,
|
||||
listingId: RequiredParam<BigNumberish>,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.marketplace.auction.getWinner(
|
||||
contractAddress,
|
||||
listingId,
|
||||
),
|
||||
async () => {
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(listingId, "No listing id provided");
|
||||
return await contract.auction.getMinimumNextBid(listingId);
|
||||
},
|
||||
{
|
||||
enabled: !!contract && listingId !== undefined,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** **********************/
|
||||
/** WRITE HOOKS **/
|
||||
/** **********************/
|
||||
@@ -290,8 +328,9 @@ export function useCreateDirectListing(contract: RequiredParam<Marketplace>) {
|
||||
return useMutation(
|
||||
async (data: NewDirectListing) => {
|
||||
invariant(walletAddress, "no wallet connected, cannot create listing");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
invariant(
|
||||
contract?.direct?.createListing,
|
||||
contract.direct.createListing,
|
||||
"contract does not support direct.createListing",
|
||||
);
|
||||
return await contract.direct.createListing(data);
|
||||
@@ -346,8 +385,9 @@ export function useCreateAuctionListing(contract: RequiredParam<Marketplace>) {
|
||||
return useMutation(
|
||||
async (data: NewAuctionListing) => {
|
||||
invariant(walletAddress, "no wallet connected, cannot create listing");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
invariant(
|
||||
contract?.direct?.createListing,
|
||||
contract.direct.createListing,
|
||||
"contract does not support auction.createListing",
|
||||
);
|
||||
return await contract.auction.createListing(data);
|
||||
@@ -456,8 +496,9 @@ export function useMakeBid(contract: RequiredParam<Marketplace>) {
|
||||
return useMutation(
|
||||
async (data: MakeBidParams) => {
|
||||
invariant(walletAddress, "no wallet connected, cannot make bid");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
invariant(
|
||||
contract?.auction?.makeBid,
|
||||
contract.auction.makeBid,
|
||||
"contract does not support auction.makeBid",
|
||||
);
|
||||
return await contract.auction.makeBid(data.listingId, data.bid);
|
||||
@@ -512,9 +553,10 @@ export function useBuyNow(contract: RequiredParam<Marketplace>) {
|
||||
return useMutation(
|
||||
async (data: BuyNowParams) => {
|
||||
invariant(walletAddress, "no wallet connected, cannot make bid");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
if (data.type === ListingType.Direct) {
|
||||
invariant(
|
||||
contract?.direct.buyoutListing,
|
||||
contract.direct.buyoutListing,
|
||||
"contract does not support direct.buyoutListing",
|
||||
);
|
||||
|
||||
@@ -525,7 +567,7 @@ export function useBuyNow(contract: RequiredParam<Marketplace>) {
|
||||
);
|
||||
}
|
||||
invariant(
|
||||
contract?.auction?.buyoutListing,
|
||||
contract.auction.buyoutListing,
|
||||
"contract does not support auction.buyoutListing",
|
||||
);
|
||||
return await contract.auction.buyoutListing(data.id);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useSDKChainId } from "../../providers/base";
|
||||
import {
|
||||
AirdropNFTParams,
|
||||
@@ -50,7 +53,7 @@ export function useNFT<TContract extends NFTContract>(
|
||||
return useQueryWithNetwork<NFT>(
|
||||
cacheKeys.contract.nft.get(contractAddress, tokenId),
|
||||
async () => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
|
||||
if (erc1155) {
|
||||
invariant(erc1155.get, "Contract instance does not support get");
|
||||
@@ -93,7 +96,7 @@ export function useNFTs<TContract extends NFTContract>(
|
||||
return useQueryWithNetwork<NFT[]>(
|
||||
cacheKeys.contract.nft.query.all(contractAddress, queryParams),
|
||||
async () => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
|
||||
if (erc721) {
|
||||
invariant(erc721.getAll, "Contract instance does not support getAll");
|
||||
@@ -134,7 +137,7 @@ export function useTotalCount<TContract extends NFTContract>(
|
||||
return useQueryWithNetwork<BigNumber>(
|
||||
cacheKeys.contract.nft.query.totalCount(contractAddress),
|
||||
async () => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
|
||||
if (erc1155) {
|
||||
invariant(
|
||||
@@ -182,14 +185,14 @@ export function useTotalCirculatingSupply(
|
||||
return useQueryWithNetwork<BigNumber>(
|
||||
cacheKeys.contract.nft.query.totalCirculatingSupply(contractAddress),
|
||||
async () => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
|
||||
if (erc1155) {
|
||||
invariant(
|
||||
erc1155.totalCirculatingSupply,
|
||||
"Contract instance does not support totalCirculatingSupply",
|
||||
);
|
||||
invariant(tokenId, "No tokenId provided");
|
||||
requiredParamInvariant(tokenId, "No tokenId provided");
|
||||
return await erc1155.totalCirculatingSupply(tokenId);
|
||||
}
|
||||
if (erc721) {
|
||||
@@ -232,7 +235,7 @@ export function useOwnedNFTs<TContract extends NFTContract>(
|
||||
return useQueryWithNetwork<NFT[]>(
|
||||
cacheKeys.contract.nft.query.owned.all(contractAddress, ownerWalletAddress),
|
||||
async () => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
invariant(ownerWalletAddress, "No wallet address provided");
|
||||
if (erc721) {
|
||||
return await erc721.getOwned(ownerWalletAddress);
|
||||
@@ -277,11 +280,11 @@ export function useNFTBalance(
|
||||
tokenId,
|
||||
),
|
||||
() => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
|
||||
invariant(ownerWalletAddress, "No owner wallet address provided");
|
||||
if (erc1155) {
|
||||
invariant(tokenId, "No tokenId provided");
|
||||
requiredParamInvariant(tokenId, "No tokenId provided");
|
||||
invariant(
|
||||
erc1155.balanceOf,
|
||||
"Contract instance does not support balanceOf",
|
||||
@@ -375,7 +378,7 @@ export function useMintNFT<TContract extends NFTContract>(
|
||||
return useMutation(
|
||||
async (data: MintNFTParams) => {
|
||||
invariant(data.to, 'No "to" address provided');
|
||||
invariant(contract, "contract is undefined");
|
||||
requiredParamInvariant(contract, "contract is undefined");
|
||||
if (erc1155) {
|
||||
invariant("supply" in data, "supply not provided");
|
||||
const { to, metadata, supply } = data;
|
||||
@@ -444,9 +447,9 @@ export function useMintNFTSupply(contract: Erc1155) {
|
||||
return useMutation(
|
||||
async (data: MintNFTSupplyParams) => {
|
||||
invariant(data.to, 'No "to" address provided');
|
||||
invariant(contract, "contract is undefined");
|
||||
requiredParamInvariant(contract, "contract is undefined");
|
||||
|
||||
invariant("tokenId" in data, "tokenId not provided");
|
||||
requiredParamInvariant(data.tokenId, "tokenId not provided");
|
||||
invariant("additionalSupply" in data, "additionalSupply not provided");
|
||||
const { to, tokenId, additionalSupply } = data;
|
||||
return await contract.mintAdditionalSupplyTo(
|
||||
@@ -512,7 +515,7 @@ export function useTransferNFT<TContract extends NFTContract>(
|
||||
invariant("to" in data, "to not provided");
|
||||
if (erc1155) {
|
||||
invariant(erc1155.transfer, "contract does not support transfer");
|
||||
invariant("tokenId" in data, "tokenId not provided");
|
||||
requiredParamInvariant(data.tokenId, "tokenId not provided");
|
||||
invariant("amount" in data, "amount not provided");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return erc1155.transfer(data.to, data.tokenId, data.amount!);
|
||||
@@ -602,7 +605,8 @@ export function useAirdropNFT(contract: Erc1155) {
|
||||
|
||||
return useMutation(
|
||||
({ tokenId, addresses }: AirdropNFTParams) => {
|
||||
invariant(contract?.airdrop, "contract does not support airdrop");
|
||||
requiredParamInvariant(contract, "contract is undefined");
|
||||
invariant(contract.airdrop, "contract does not support airdrop");
|
||||
|
||||
return contract.airdrop(tokenId, addresses);
|
||||
},
|
||||
@@ -688,8 +692,8 @@ export function useBurnNFT<TContract extends NFTContract>(
|
||||
|
||||
return useMutation(
|
||||
async (data: BurnNFTParams) => {
|
||||
invariant(data.tokenId, "No tokenId provided");
|
||||
invariant(contract, "contract is undefined");
|
||||
requiredParamInvariant(data.tokenId, "No tokenId provided");
|
||||
requiredParamInvariant(contract, "contract is undefined");
|
||||
if (erc1155) {
|
||||
invariant("amount" in data, "amount not provided");
|
||||
const { tokenId, amount } = data;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useSDKChainId } from "../../providers/base";
|
||||
import { WalletAddress } from "../../types";
|
||||
import {
|
||||
@@ -61,7 +64,7 @@ export function useAllRoleMembers<TContract extends ContractWithRoles>(
|
||||
return useQueryWithNetwork<Awaited<GetAllReturnType<TContract>>>(
|
||||
cacheKeys.extensions.roles.getAll(contractAddress),
|
||||
() => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(contract.roles, "Contract does not support roles");
|
||||
// have to cast to any because of role bs, type is defined in the useQueryWithNetwork definition above
|
||||
return contract.roles.getAll() as any;
|
||||
@@ -94,7 +97,7 @@ export function useRoleMembers<TContract extends ContractWithRoles>(
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.extensions.roles.get(contractAddress, role),
|
||||
() => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(contract.roles, "Contract does not support roles");
|
||||
return contract.roles.get(role);
|
||||
},
|
||||
@@ -192,7 +195,7 @@ export function useSetAllRoleMembers<TContract extends ContractWithRoles>(
|
||||
async (rolesWithAddresses: {
|
||||
[role in RolesForContract<TContract>]: string[];
|
||||
}) => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(contract.roles, "Contract does not support roles");
|
||||
await contract.roles.setAll(rolesWithAddresses);
|
||||
},
|
||||
@@ -253,7 +256,7 @@ export function useGrantRole<TContract extends ContractWithRoles>(
|
||||
role: RolesForContract<TContract>;
|
||||
address: WalletAddress;
|
||||
}) => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(contract.roles, "Contract does not support roles");
|
||||
await contract.roles.grant(params.role as any, params.address);
|
||||
},
|
||||
@@ -311,7 +314,7 @@ export function useRevokeRole<TContract extends ContractWithRoles>(
|
||||
role: RolesForContract<TContract>;
|
||||
address: WalletAddress;
|
||||
}) => {
|
||||
invariant(contract, "No contract provided");
|
||||
requiredParamInvariant(contract, "No contract provided");
|
||||
invariant(contract.roles, "Contract does not support roles");
|
||||
await contract.roles.revoke(params.role as any, params.address);
|
||||
},
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useSDKChainId } from "../../providers/base";
|
||||
import {
|
||||
ClaimTokenParams,
|
||||
@@ -39,7 +42,7 @@ export function useTokenSupply(contract: RequiredParam<TokenContract>) {
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.token.totalSupply(contractAddress),
|
||||
() => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
const erc20 = getErc20(contract);
|
||||
if (erc20) {
|
||||
return erc20.totalSupply();
|
||||
@@ -75,7 +78,7 @@ export function useTokenBalance(
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.token.balanceOf(contractAddress, walletAddress),
|
||||
async () => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
invariant(walletAddress, "No address provided");
|
||||
if (erc20) {
|
||||
return await erc20.balanceOf(walletAddress);
|
||||
@@ -109,7 +112,7 @@ export function useTokenDecimals(contract: RequiredParam<TokenContract>) {
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.contract.token.decimals(contractAddress),
|
||||
async () => {
|
||||
invariant(contract, "No Contract instance provided");
|
||||
requiredParamInvariant(contract, "No Contract instance provided");
|
||||
|
||||
if (erc20) {
|
||||
return (await erc20.get()).decimals;
|
||||
@@ -168,7 +171,7 @@ export function useMintToken(contract: RequiredParam<TokenContract>) {
|
||||
return useMutation(
|
||||
(data: TokenParams) => {
|
||||
const { to, amount } = data;
|
||||
invariant(contract, "contract is undefined");
|
||||
requiredParamInvariant(contract, "contract is undefined");
|
||||
if (erc20) {
|
||||
return erc20.mintTo(to, amount);
|
||||
}
|
||||
@@ -350,7 +353,7 @@ export function useTransferBatchToken(contract: RequiredParam<TokenContract>) {
|
||||
(data: TokenParams[]) => {
|
||||
if (erc20) {
|
||||
invariant(
|
||||
erc20?.transferBatch,
|
||||
erc20.transferBatch,
|
||||
"contract does not support transferBatch",
|
||||
);
|
||||
const convertedData = data.map((token) => ({
|
||||
@@ -415,9 +418,9 @@ export function useBurnToken(contract: RequiredParam<TokenContract>) {
|
||||
return useMutation(
|
||||
(data: TokenBurnParams) => {
|
||||
const { amount } = data;
|
||||
invariant(contract, "contract is undefined");
|
||||
requiredParamInvariant(contract, "contract is undefined");
|
||||
if (erc20) {
|
||||
invariant(erc20?.burn, "contract does not support burn");
|
||||
invariant(erc20.burn, "contract does not support burn");
|
||||
return erc20.burn(amount);
|
||||
}
|
||||
invariant(false, "Smart contract is not a valid erc20 contract");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { showDeprecationWarning } from "../../utils/deprecation-warning";
|
||||
import { useContract } from "../async/contracts";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { showDeprecationWarning } from "../../utils/deprecation-warning";
|
||||
import { useContract } from "../async/contracts";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { showDeprecationWarning } from "../../utils/deprecation-warning";
|
||||
import { useContract } from "../async/contracts";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { showDeprecationWarning } from "../../utils/deprecation-warning";
|
||||
import { useContract } from "../async/contracts";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { showDeprecationWarning } from "../../utils/deprecation-warning";
|
||||
import { useContract } from "../async/contracts";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { showDeprecationWarning } from "../../utils/deprecation-warning";
|
||||
import { useContract } from "../async/contracts";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { showDeprecationWarning } from "../../utils/deprecation-warning";
|
||||
import { useContract } from "../async/contracts";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { showDeprecationWarning } from "../../utils/deprecation-warning";
|
||||
import { useContract } from "../async/contracts";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { showDeprecationWarning } from "../../utils/deprecation-warning";
|
||||
import { useContract } from "../async/contracts";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { showDeprecationWarning } from "../../utils/deprecation-warning";
|
||||
import { useContract } from "../async/contracts";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { showDeprecationWarning } from "../../utils/deprecation-warning";
|
||||
import { useContract } from "../async/contracts";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { showDeprecationWarning } from "../../utils/deprecation-warning";
|
||||
import { useContract } from "../async/contracts";
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import {
|
||||
QueryClientProviderProps,
|
||||
QueryClientProviderWithDefault,
|
||||
} from "../../core/providers/query-client";
|
||||
import { RequiredParam } from "../../core/query-utils/required-param";
|
||||
import { ComponentWithChildren } from "../../core/types/component";
|
||||
import { RequiredParam } from "../../core/types/shared";
|
||||
import {
|
||||
ThirdwebAuthConfig,
|
||||
ThirdwebAuthConfigProvider,
|
||||
|
||||
@@ -9,10 +9,7 @@ import {
|
||||
defaultSupportedChains,
|
||||
} from "../constants/chain";
|
||||
import { ThirdwebAuthConfig } from "../contexts/thirdweb-auth";
|
||||
import {
|
||||
ThirdwebConfigProvider,
|
||||
defaultChainRpc,
|
||||
} from "../contexts/thirdweb-config";
|
||||
import { ThirdwebConfigProvider } from "../contexts/thirdweb-config";
|
||||
import { ThirdwebSDKProvider, ThirdwebSDKProviderProps } from "./base";
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
@@ -20,6 +17,7 @@ import {
|
||||
getProviderForNetwork,
|
||||
SDKOptionsOutput,
|
||||
} from "@thirdweb-dev/sdk";
|
||||
import { DEFAULT_RPC_URLS } from "@thirdweb-dev/sdk";
|
||||
import type { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import React, { useMemo } from "react";
|
||||
import {
|
||||
@@ -223,7 +221,7 @@ export const ThirdwebProvider = <
|
||||
TSupportedChain extends SupportedChain = SupportedChain,
|
||||
>({
|
||||
sdkOptions,
|
||||
chainRpc = defaultChainRpc,
|
||||
chainRpc = DEFAULT_RPC_URLS,
|
||||
supportedChains = defaultSupportedChains.map(
|
||||
(c) => c.id,
|
||||
) as TSupportedChain[],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RequiredParam } from "../core/types/shared";
|
||||
import { RequiredParam } from "../core/query-utils/required-param";
|
||||
import type {
|
||||
AirdropInput,
|
||||
Amount,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { RequiredParam } from "../../core/types/shared";
|
||||
import { RequiredParam } from "../../core/query-utils/required-param";
|
||||
import { SupportedChainId } from "../constants/chain";
|
||||
import { ContractAddress, WalletAddress } from "../types";
|
||||
import { QueryClient, QueryKey } from "@tanstack/react-query";
|
||||
import type {
|
||||
ClaimConditionFetchOptions,
|
||||
MarketplaceFilter,
|
||||
QueryAllParams,
|
||||
SUPPORTED_CHAIN_ID,
|
||||
@@ -267,22 +268,24 @@ export const cacheKeys = {
|
||||
getActive: (
|
||||
contractAddress: RequiredParam<ContractAddress>,
|
||||
tokenId?: BigNumberish,
|
||||
options?: ClaimConditionFetchOptions,
|
||||
) =>
|
||||
createContractCacheKey(
|
||||
contractAddress,
|
||||
tokenId
|
||||
? ["claimConditions", "getActive", { tokenId }]
|
||||
: ["claimConditions", "getActive"],
|
||||
? ["claimConditions", "getActive", { tokenId }, options]
|
||||
: ["claimConditions", "getActive", options],
|
||||
),
|
||||
getAll: (
|
||||
contractAddress: RequiredParam<ContractAddress>,
|
||||
tokenId?: BigNumberish,
|
||||
options?: ClaimConditionFetchOptions,
|
||||
) =>
|
||||
createContractCacheKey(
|
||||
contractAddress,
|
||||
tokenId
|
||||
? ["claimConditions", "getAll", { tokenId }]
|
||||
: ["claimConditions", "getAll"],
|
||||
? ["claimConditions", "getAll", { tokenId }, options]
|
||||
: ["claimConditions", "getAll", options],
|
||||
),
|
||||
getClaimIneligibilityReasons: (
|
||||
contractAddress: RequiredParam<ContractAddress>,
|
||||
|
||||
@@ -3,4 +3,4 @@ export * from "./evm";
|
||||
|
||||
// core exports
|
||||
export { shouldNeverPersistQuery } from "./core/query-utils/query-key";
|
||||
export type { RequiredParam } from "./core/types/shared";
|
||||
export type { RequiredParam } from "./core/query-utils/required-param";
|
||||
|
||||
+8
-10
@@ -1,6 +1,6 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { MintNFTParams } from "../../../evm/types";
|
||||
import { createSOLProgramQueryKey } from "../../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../../core/query-utils/required-param";
|
||||
import { MintNFTParams } from "../../../types";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { NFTCollection } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
@@ -15,15 +15,10 @@ import invariant from "tiny-invariant";
|
||||
*
|
||||
* export default function Component() {
|
||||
* const { program } = useProgram("{{program_address}}");
|
||||
* const { mutateAsync: mint, isLoading, error } = useMintNFT(program);
|
||||
*
|
||||
* function mintNFT() {
|
||||
* const metadata = { name: "First NFT", description: "This is a cool NFT!" };
|
||||
* mint({ to: "{{wallet_address}}", metadata });
|
||||
* }
|
||||
* const { mutateAsync: mintNFT, isLoading, error } = useMintNFT(program);
|
||||
*
|
||||
* return (
|
||||
* <button onClick={() => mintNFT()}>
|
||||
* <button onClick={() => mintNFT({ metadata: { name: "First NFT" } })}>
|
||||
* Mint
|
||||
* </button>
|
||||
* )
|
||||
@@ -37,6 +32,9 @@ export function useMintNFT(program: RequiredParam<NFTCollection>) {
|
||||
return useMutation(
|
||||
async (data: MintNFTParams) => {
|
||||
invariant(program, "program is required");
|
||||
if (!data.to) {
|
||||
return await program.mint(data.metadata);
|
||||
}
|
||||
return await program.mintTo(data.to, data.metadata);
|
||||
},
|
||||
{
|
||||
@@ -0,0 +1,51 @@
|
||||
import { createSOLProgramQueryKey } from "../../../../core/query-utils/query-key";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../../core/query-utils/required-param";
|
||||
import { MintNFTSupplyParams } from "../../../types";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { NFTCollection } from "@thirdweb-dev/sdk/solana";
|
||||
|
||||
/**
|
||||
* Mint additional supply for an NFT on your NFT program
|
||||
* @param program - The NFT program to mint additional NFTs to
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* import { useProgram, useMintNFTSupply } from "@thirdweb-dev/react/solana";
|
||||
*
|
||||
* export default function Component() {
|
||||
* const { program } = useProgram("{{program_address}}");
|
||||
* const { mutateAsync: mintSupply, isLoading, error } = useMintNFTSupply(program);
|
||||
*
|
||||
* return (
|
||||
* <button onClick={() => mintSupply({ nftAddress: "111...", amount: 10 })}>
|
||||
* Mint Additional Supply
|
||||
* </button>
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function useMintNFTSupply(program: RequiredParam<NFTCollection>) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
async (data: MintNFTSupplyParams) => {
|
||||
requiredParamInvariant(program, "program is required");
|
||||
if (!data.to) {
|
||||
return await program.mintAdditionalSupply(data.nftAddress, data.amount);
|
||||
}
|
||||
return await program.mintAdditionalSupplyTo(
|
||||
data.to,
|
||||
data.nftAddress,
|
||||
data.amount,
|
||||
);
|
||||
},
|
||||
{
|
||||
onSettled: () =>
|
||||
queryClient.invalidateQueries(createSOLProgramQueryKey(program)),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createSOLProgramQueryKey } from "../../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../../core/query-utils/required-param";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createSOLProgramQueryKey } from "../../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../../core/query-utils/required-param";
|
||||
import { ClaimNFTParams } from "../../../types";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { createSOLProgramQueryKey } from "../../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../../core/query-utils/required-param";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
export function dropTotalClaimedSupplyQuery(program: RequiredParam<NFTDrop>) {
|
||||
return {
|
||||
@@ -11,7 +13,7 @@ export function dropTotalClaimedSupplyQuery(program: RequiredParam<NFTDrop>) {
|
||||
] as const),
|
||||
|
||||
queryFn: async () => {
|
||||
invariant(program, "program is required");
|
||||
requiredParamInvariant(program, "program is required");
|
||||
|
||||
return await program.totalClaimedSupply();
|
||||
},
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { createSOLProgramQueryKey } from "../../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../../core/query-utils/required-param";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { NFTMetadataInput, UploadProgressEvent } from "@thirdweb-dev/sdk";
|
||||
import type { NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
* Lazy mint NFTs on an NFT Drop program
|
||||
@@ -34,7 +36,7 @@ export function useLazyMint(
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
async (data: { metadatas: NFTMetadataInput[] }) => {
|
||||
invariant(program, "program is required");
|
||||
requiredParamInvariant(program, "program is required");
|
||||
let options;
|
||||
if (onProgress) {
|
||||
options = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createSOLProgramQueryKey } from "../../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../../core/query-utils/required-param";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { NFTDrop, NFTDropConditionsInput } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { createSOLProgramQueryKey } from "../../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../../core/query-utils/required-param";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
export function dropUnclaimedSupplyQuery(program: RequiredParam<NFTDrop>) {
|
||||
return {
|
||||
@@ -11,7 +13,7 @@ export function dropUnclaimedSupplyQuery(program: RequiredParam<NFTDrop>) {
|
||||
] as const),
|
||||
|
||||
queryFn: async () => {
|
||||
invariant(program, "program is required");
|
||||
requiredParamInvariant(program, "program is required");
|
||||
|
||||
return await program.totalUnclaimedSupply();
|
||||
},
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
* Burn an NFT owned by the connected wallet
|
||||
@@ -32,7 +34,7 @@ export function useBurnNFT(program: RequiredParam<NFTCollection | NFTDrop>) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
async (nftAddress: string) => {
|
||||
invariant(program, "program is required");
|
||||
requiredParamInvariant(program, "program is required");
|
||||
return await program.burn(nftAddress);
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
|
||||
export function nftCreatorsQuery(
|
||||
program: RequiredParam<NFTCollection | NFTDrop>,
|
||||
) {
|
||||
return {
|
||||
queryKey: createSOLProgramQueryKey(program, ["creators"] as const),
|
||||
|
||||
queryFn: async () => {
|
||||
requiredParamInvariant(program, "program is required");
|
||||
return await program.getCreators();
|
||||
},
|
||||
enabled: !!program,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the creators for an NFT program
|
||||
* @param program - The NFT program to get the creators for
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* import { useProgram, useCreators } from "@thirdweb-dev/react/solana";
|
||||
*
|
||||
* export default function Component() {
|
||||
* const { program } = useProgram("{{program_address}}");
|
||||
* const { data: creators, isLoading } = useCreators(program);
|
||||
*
|
||||
* return (
|
||||
* <pre>{JSON.stringify(creators)}</pre>
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function useCreators(program: RequiredParam<NFTCollection | NFTDrop>) {
|
||||
return useQuery(nftCreatorsQuery(program));
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
export function nftGetOneQuery(
|
||||
program: RequiredParam<NFTCollection | NFTDrop>,
|
||||
@@ -15,8 +17,8 @@ export function nftGetOneQuery(
|
||||
] as const),
|
||||
|
||||
queryFn: async () => {
|
||||
invariant(program, "program is required");
|
||||
invariant(tokenAddress, "tokenAddress is required");
|
||||
requiredParamInvariant(program, "program is required");
|
||||
requiredParamInvariant(tokenAddress, "tokenAddress is required");
|
||||
return await program.get(tokenAddress);
|
||||
},
|
||||
enabled: !!program && !!tokenAddress,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
export function nftGetAllQuery(
|
||||
program: RequiredParam<NFTCollection | NFTDrop>,
|
||||
@@ -11,7 +13,7 @@ export function nftGetAllQuery(
|
||||
queryKey: createSOLProgramQueryKey(program, ["getAll"] as const),
|
||||
|
||||
queryFn: async () => {
|
||||
invariant(program, "program is required");
|
||||
requiredParamInvariant(program, "program is required");
|
||||
return await program.getAll();
|
||||
},
|
||||
enabled: !!program,
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
|
||||
export function nftRoyaltyQuery(
|
||||
program: RequiredParam<NFTCollection | NFTDrop>,
|
||||
) {
|
||||
return {
|
||||
queryKey: createSOLProgramQueryKey(program, ["royalty"] as const),
|
||||
|
||||
queryFn: async () => {
|
||||
requiredParamInvariant(program, "program is required");
|
||||
return await program.getRoyalty();
|
||||
},
|
||||
enabled: !!program,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the royalty for an NFT program
|
||||
* @param program - The NFT program to get the royalty for
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* import { useProgram, useRoyalty } from "@thirdweb-dev/react/solana";
|
||||
*
|
||||
* export default function Component() {
|
||||
* const { program } = useProgram("{{program_address}}");
|
||||
* const { data: royalty, isLoading } = useRoyalty(program);
|
||||
*
|
||||
* return (
|
||||
* <p>{royalty}</p>
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function useRoyalty(program: RequiredParam<NFTCollection | NFTDrop>) {
|
||||
return useQuery(nftRoyaltyQuery(program));
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
export type TransferNFTMutationParams = {
|
||||
receiverAddress: string;
|
||||
@@ -42,7 +44,7 @@ export function useTransferNFT(
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
async ({ tokenAddress, receiverAddress }: TransferNFTMutationParams) => {
|
||||
invariant(program, "program is required");
|
||||
requiredParamInvariant(program, "program is required");
|
||||
return await program.transfer(receiverAddress, tokenAddress);
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,7 +2,10 @@ import {
|
||||
createSOLQueryKeyWithNetwork,
|
||||
neverPersist,
|
||||
} from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useSDK } from "../../providers/base";
|
||||
import { programAccountTypeQuery } from "./useProgramAccountType";
|
||||
import {
|
||||
@@ -17,7 +20,6 @@ import type {
|
||||
ThirdwebSDK,
|
||||
Token,
|
||||
} from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
type ProgramMap = Readonly<{
|
||||
"nft-collection": NFTCollection;
|
||||
@@ -42,8 +44,8 @@ export function programQuery<TProgramType extends ProgramType>(
|
||||
),
|
||||
),
|
||||
queryFn: (async () => {
|
||||
invariant(sdk, "sdk is required");
|
||||
invariant(address, "Address is required");
|
||||
requiredParamInvariant(sdk, "sdk is required");
|
||||
requiredParamInvariant(address, "Address is required");
|
||||
// if the type is not passed in explicitly then we'll try to resolve it
|
||||
if (!type) {
|
||||
// why do we call `fetchQuery` here instead of calling the sdk directly?
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { createSOLQueryKeyWithNetwork } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useSDK } from "../../providers/base";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { ThirdwebSDK } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
export function programAccountTypeQuery(
|
||||
sdk: RequiredParam<ThirdwebSDK>,
|
||||
@@ -16,8 +18,8 @@ export function programAccountTypeQuery(
|
||||
network || null,
|
||||
),
|
||||
queryFn: async () => {
|
||||
invariant(sdk, "sdk is required");
|
||||
invariant(address, "Address is required");
|
||||
requiredParamInvariant(sdk, "sdk is required");
|
||||
requiredParamInvariant(address, "Address is required");
|
||||
|
||||
return await sdk.registry.getProgramType(address);
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { UseProgramResult } from "./useProgram";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import type { TokenParams } from "../../../evm";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { Token } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
* Mint tokens on your token program
|
||||
@@ -31,7 +33,7 @@ export function useMintToken(program: RequiredParam<Token>) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
async (params: TokenParams) => {
|
||||
invariant(program, "program is required");
|
||||
requiredParamInvariant(program, "program is required");
|
||||
// TODO switch this to use mintTo once that is exposed
|
||||
return await program.mint(params.amount);
|
||||
},
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Token } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
export function tokenBalanceQuery(
|
||||
program: RequiredParam<Token>,
|
||||
@@ -14,9 +16,8 @@ export function tokenBalanceQuery(
|
||||
{ walletAddress },
|
||||
] as const),
|
||||
queryFn: async () => {
|
||||
invariant(program, "program is required");
|
||||
|
||||
invariant(walletAddress, "Wallet address is required");
|
||||
requiredParamInvariant(program, "program is required");
|
||||
requiredParamInvariant(walletAddress, "Wallet address is required");
|
||||
return await program.balanceOf(walletAddress);
|
||||
},
|
||||
enabled: !!program && !!walletAddress,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Token } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import {
|
||||
requiredParamInvariant,
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { Amount, Token } from "@thirdweb-dev/sdk/solana";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
export type TransferTokenMutationParams = {
|
||||
amount: Amount;
|
||||
@@ -35,7 +37,7 @@ export function useTransferToken(program: RequiredParam<Token>) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
async ({ amount, receiverAddress }: TransferTokenMutationParams) => {
|
||||
invariant(program, "program is required");
|
||||
requiredParamInvariant(program, "program is required");
|
||||
return await program.transfer(receiverAddress, amount);
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createSOLQueryKeyWithNetwork } from "../../../core/query-utils/query-key";
|
||||
import { RequiredParam } from "../../../core/types/shared";
|
||||
import { RequiredParam } from "../../../core/query-utils/required-param";
|
||||
import { useSDK } from "../../providers/base";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ThirdwebSDK } from "@thirdweb-dev/sdk/solana";
|
||||
|
||||
@@ -12,11 +12,13 @@ export * from "./hooks/program/useProgramAccountType";
|
||||
|
||||
// NFT Shared
|
||||
export * from "./hooks/nft/useNFTs";
|
||||
export * from "./hooks/nft/useMintNFT";
|
||||
export * from "./hooks/nft/useNFT";
|
||||
export * from "./hooks/nft/useTransferNFT";
|
||||
export * from "./hooks/nft/useBurnNFT";
|
||||
|
||||
// NFT Collection
|
||||
export * from "./hooks/nft/collection/useMintNFT";
|
||||
export * from "./hooks/nft/collection/useMintNFTSupply";
|
||||
|
||||
// NFT Drop
|
||||
export * from "./hooks/nft/drop/useLazyMint";
|
||||
export * from "./hooks/nft/drop/useClaimedSupply";
|
||||
|
||||
@@ -2,8 +2,8 @@ import {
|
||||
QueryClientProviderProps,
|
||||
QueryClientProviderWithDefault,
|
||||
} from "../../core/providers/query-client";
|
||||
import { RequiredParam } from "../../core/query-utils/required-param";
|
||||
import { ComponentWithChildren } from "../../core/types/component";
|
||||
import { RequiredParam } from "../../core/types/shared";
|
||||
import {
|
||||
ThirdwebAuthConfig,
|
||||
ThirdwebAuthConfigProvider,
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
import { Amount } from "@thirdweb-dev/sdk";
|
||||
import { Amount, NFTMetadataOrUri } from "@thirdweb-dev/sdk";
|
||||
|
||||
/**
|
||||
* The params for the {@link useMintNFT} hook mutation.
|
||||
*
|
||||
* @beta
|
||||
*/
|
||||
export type MintNFTParams = {
|
||||
to?: string;
|
||||
metadata: NFTMetadataOrUri;
|
||||
amount?: Amount;
|
||||
};
|
||||
|
||||
/**
|
||||
* The params for the {@link useClaimNFT} hook mutation.
|
||||
@@ -9,3 +20,13 @@ export type ClaimNFTParams = {
|
||||
to?: string;
|
||||
amount: Amount;
|
||||
};
|
||||
|
||||
/**
|
||||
* The params to pass to {@link useMintNFTSupply} hook mutation
|
||||
* @beta
|
||||
*/
|
||||
export type MintNFTSupplyParams = {
|
||||
to?: string;
|
||||
nftAddress: string;
|
||||
amount: Amount;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,71 @@
|
||||
# @thirdweb-dev/sdk
|
||||
|
||||
## 3.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#315](https://github.com/thirdweb-dev/js/pull/315) [`89ff921`](https://github.com/thirdweb-dev/js/commit/89ff921a7d9f0e42b4e6707c8b56d0aab95c1aa0) Thanks [@furqanrydhan](https://github.com/furqanrydhan)! - fixing vite, multihash moved to non lazy load + cbor-x instead of cbor-web
|
||||
|
||||
- [#321](https://github.com/thirdweb-dev/js/pull/321) [`a57b4f0`](https://github.com/thirdweb-dev/js/commit/a57b4f01f3a49590b897101f2730582e4124a554) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - [SDK] Implement sharded merkle trees for lightweight allowlist checks
|
||||
|
||||
**Behavior change**
|
||||
|
||||
We've made allowlists much more performant using sharded merkle trees. This allows us to process large allowlists (1M+) efficiently.
|
||||
|
||||
To support those large allowlists, fetching claim conditions does not fetch the allowlist data by default anymore. Instead, you can pass an options object to additionally fetch the allowlist data along with the rest of the claim conditions data.
|
||||
|
||||
This affects `ClaimConditions.getActive()` and `ClaimConditions.getAll()`
|
||||
|
||||
Examples:
|
||||
|
||||
```ts
|
||||
const activeClaimCondition =
|
||||
await contract.erc721.claimConditions.getActive();
|
||||
// `activeClaimCondition.snapshot` is undefined
|
||||
const activeclaimConditionWithtAllowList =
|
||||
await contract.erc721.claimConditions.getActive({
|
||||
withAllowList: true,
|
||||
});
|
||||
// `activeClaimCondition.snapshot` returns the allowlist data
|
||||
```
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#326](https://github.com/thirdweb-dev/js/pull/326) [`126752d`](https://github.com/thirdweb-dev/js/commit/126752d7d02c9a808a63fb9e67a9df6658b5682b) Thanks [@jnsdls](https://github.com/jnsdls)! - [EVM] - expose fn to get ipfs hash from bytecote publicly
|
||||
|
||||
- [#328](https://github.com/thirdweb-dev/js/pull/328) [`df74340`](https://github.com/thirdweb-dev/js/commit/df74340fa51323304c32419761d6f18628b060fa) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update cbor-x import to work with es2018 builds (embeds)
|
||||
|
||||
- Updated dependencies [[`87fd6ab`](https://github.com/thirdweb-dev/js/commit/87fd6ab14e1a67a1b12e72bd397fb21769537307)]:
|
||||
- @thirdweb-dev/[email protected]
|
||||
|
||||
## 3.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#317](https://github.com/thirdweb-dev/js/pull/317) [`4f867e1`](https://github.com/thirdweb-dev/js/commit/4f867e15edbe5757afbf54cab207458711a8986c) Thanks [@adam-maj](https://github.com/adam-maj)! - Add supply to nft.get
|
||||
|
||||
- [#312](https://github.com/thirdweb-dev/js/pull/312) [`b6eec61`](https://github.com/thirdweb-dev/js/commit/b6eec6146c23b66a3681bbb48b173b9019254214) Thanks [@adam-maj](https://github.com/adam-maj)! - Add quantity to mint additional supply
|
||||
|
||||
- [#310](https://github.com/thirdweb-dev/js/pull/310) [`9727502`](https://github.com/thirdweb-dev/js/commit/9727502eb30ac139382b7c7c8e8fc0967cbfbcf1) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - [SOL] - add useMintNFTSupply hook in react and accomodate inputs for it in SDK
|
||||
|
||||
- [#307](https://github.com/thirdweb-dev/js/pull/307) [`782b58d`](https://github.com/thirdweb-dev/js/commit/782b58d3f6a3b49ff9b3d837551db5161f96171a) Thanks [@kumaryash90](https://github.com/kumaryash90)! - fix multicall gasless
|
||||
|
||||
- [#320](https://github.com/thirdweb-dev/js/pull/320) [`f41a443`](https://github.com/thirdweb-dev/js/commit/f41a443c5785240197607b5bf93ff18a1c8e8307) Thanks [@adam-maj](https://github.com/adam-maj)! - Add update creators and update royalty
|
||||
|
||||
- [#313](https://github.com/thirdweb-dev/js/pull/313) [`f45ef62`](https://github.com/thirdweb-dev/js/commit/f45ef62128cfefe06275a38440c90d45e9f3cef7) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Return default metadata when failing to fetch a given tokenId on NFT contracts
|
||||
|
||||
- [#319](https://github.com/thirdweb-dev/js/pull/319) [`2f8ec89`](https://github.com/thirdweb-dev/js/commit/2f8ec89c4c83ae577092c840c198e6fcfb114e69) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Expose total claimed and unclaimed supply for erc721 drop contracts
|
||||
|
||||
- [#322](https://github.com/thirdweb-dev/js/pull/322) [`b7cd744`](https://github.com/thirdweb-dev/js/commit/b7cd744cd0482a6c3fe5ae0c858f20cd380d4114) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - [SOL] Allow signing multiple claims at once
|
||||
|
||||
- [#306](https://github.com/thirdweb-dev/js/pull/306) [`53c6507`](https://github.com/thirdweb-dev/js/commit/53c65076696a43bd9a7e58a7493d4e65c172900f) Thanks [@adam-maj](https://github.com/adam-maj)! - Add contract deploy listener
|
||||
|
||||
## 3.2.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#303](https://github.com/thirdweb-dev/js/pull/303) [`712b11a`](https://github.com/thirdweb-dev/js/commit/712b11a43e389ae10b7f09648779a69c05b2c913) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - [SOL] Fix getting real NFT owners
|
||||
|
||||
## 3.2.4
|
||||
|
||||
## 3.2.3
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/sdk",
|
||||
"version": "3.2.4",
|
||||
"version": "3.3.0",
|
||||
"description": "The main thirdweb SDK.",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/sdk",
|
||||
"license": "Apache-2.0",
|
||||
@@ -65,7 +65,6 @@
|
||||
"push": "yalc push"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gmrchk/cli-testing-library": "^0.1.2",
|
||||
"@metaplex-foundation/amman": "^0.10.0",
|
||||
"@metaplex-foundation/amman-client": "^0.2.1",
|
||||
"@microsoft/api-documenter": "^7.19.4",
|
||||
@@ -82,7 +81,6 @@
|
||||
"@types/uuid": "^8.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"cbor": "^8.1.0",
|
||||
"chai": "^4.3.6",
|
||||
"deep-equal-in-any-order": "^1.1.18",
|
||||
"eslint": "^8.21.0",
|
||||
@@ -91,11 +89,9 @@
|
||||
"eslint-plugin-inclusive-language": "^2.2.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-tsdoc": "^0.2.16",
|
||||
"esm": "^3.2.25",
|
||||
"ethers": "^5.7.0",
|
||||
"hardhat": "^2.9.3",
|
||||
"mocha": "^10.0.0",
|
||||
"node-stdlib-browser": "^1.2.0",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
@@ -112,13 +108,11 @@
|
||||
"@thirdweb-dev/storage": "*",
|
||||
"bn.js": "^5.2.1",
|
||||
"bs58": "^5.0.0",
|
||||
"cbor-web": "^8.1.0",
|
||||
"cbor-x": "^1.4.1",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"form-data": "^4.0.0",
|
||||
"merkletreejs": "^0.2.24",
|
||||
"multihashes": "^4.0.3",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"uuid": "^9.0.0",
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
ClaimConditionOutputSchema,
|
||||
} from "../schema/contracts/common/claim-conditions";
|
||||
import {
|
||||
SnapshotEntry,
|
||||
SnapshotEntryWithProof,
|
||||
SnapshotInputSchema,
|
||||
SnapshotSchema,
|
||||
} from "../schema/contracts/common/snapshots";
|
||||
@@ -23,17 +25,18 @@ import {
|
||||
isNativeToken,
|
||||
normalizePriceValue,
|
||||
} from "./currency";
|
||||
import { ShardedMerkleTree } from "./sharded-merkle-tree";
|
||||
import { createSnapshot } from "./snapshots";
|
||||
import { IDropClaimCondition } from "@thirdweb-dev/contracts-js/dist/declarations/src/DropERC20";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import {
|
||||
BigNumber,
|
||||
BigNumberish,
|
||||
ethers,
|
||||
CallOverrides,
|
||||
constants,
|
||||
ethers,
|
||||
providers,
|
||||
utils,
|
||||
CallOverrides,
|
||||
} from "ethers";
|
||||
|
||||
/**
|
||||
@@ -59,21 +62,17 @@ export async function prepareClaim(
|
||||
.toString()
|
||||
.startsWith(constants.AddressZero)
|
||||
) {
|
||||
const claims = await fetchSnapshot(
|
||||
const claim = await fetchSnapshotEntryForAddress(
|
||||
addressToClaim,
|
||||
activeClaimCondition.merkleRootHash.toString(),
|
||||
await merkleMetadataFetcher(),
|
||||
storage,
|
||||
);
|
||||
const item =
|
||||
claims &&
|
||||
claims.find(
|
||||
(c) => c.address.toLowerCase() === addressToClaim.toLowerCase(),
|
||||
);
|
||||
if (item === undefined) {
|
||||
if (!claim) {
|
||||
throw new Error("No claim found for this address");
|
||||
}
|
||||
proofs = item.proof;
|
||||
maxClaimable = ethers.utils.parseUnits(item.maxClaimable, tokenDecimals);
|
||||
proofs = claim.proof;
|
||||
maxClaimable = ethers.utils.parseUnits(claim.maxClaimable, tokenDecimals);
|
||||
}
|
||||
} catch (e) {
|
||||
// have to handle the valid error case that we *do* want to throw on
|
||||
@@ -114,14 +113,6 @@ export async function prepareClaim(
|
||||
};
|
||||
}
|
||||
|
||||
type Snapshot =
|
||||
| {
|
||||
address: string;
|
||||
proof: string[];
|
||||
maxClaimable: string;
|
||||
}[]
|
||||
| undefined;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param merkleRoot
|
||||
@@ -132,20 +123,55 @@ export async function fetchSnapshot(
|
||||
merkleRoot: string,
|
||||
merkleMetadata: Record<string, string> | undefined,
|
||||
storage: ThirdwebStorage,
|
||||
): Promise<Snapshot> {
|
||||
): Promise<SnapshotEntry[] | null> {
|
||||
if (!merkleMetadata) {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
const snapshotUri = merkleMetadata[merkleRoot];
|
||||
let snapshot: Snapshot = undefined;
|
||||
if (snapshotUri) {
|
||||
const raw = await storage.downloadJSON(snapshotUri);
|
||||
const snapshotData = SnapshotSchema.parse(raw);
|
||||
if (merkleRoot === snapshotData.merkleRoot) {
|
||||
snapshot = snapshotData.claims;
|
||||
if (raw.isShardedMerkleTree && raw.merkleRoot === merkleRoot) {
|
||||
const smt = await ShardedMerkleTree.fromUri(snapshotUri, storage);
|
||||
return smt?.getAllEntries() || null;
|
||||
} else {
|
||||
const snapshotData = SnapshotSchema.parse(raw);
|
||||
if (merkleRoot === snapshotData.merkleRoot) {
|
||||
return snapshotData.claims.map((claim) => ({
|
||||
address: claim.address,
|
||||
maxClaimable: claim.maxClaimable,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
return snapshot;
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function fetchSnapshotEntryForAddress(
|
||||
address: string,
|
||||
merkleRoot: string,
|
||||
merkleMetadata: Record<string, string> | undefined,
|
||||
storage: ThirdwebStorage,
|
||||
): Promise<SnapshotEntryWithProof | null> {
|
||||
if (!merkleMetadata) {
|
||||
return null;
|
||||
}
|
||||
const snapshotUri = merkleMetadata[merkleRoot];
|
||||
if (snapshotUri) {
|
||||
const raw = await storage.downloadJSON(snapshotUri);
|
||||
if (raw.isShardedMerkleTree && raw.merkleRoot === merkleRoot) {
|
||||
const merkleTree = await ShardedMerkleTree.fromShardedMerkleTreeInfo(
|
||||
raw,
|
||||
storage,
|
||||
);
|
||||
return await merkleTree.getProof(address);
|
||||
}
|
||||
// legacy non-sharded, just fetch it all and filter out
|
||||
const snapshotData = SnapshotSchema.parse(raw);
|
||||
if (merkleRoot === snapshotData.merkleRoot) {
|
||||
return snapshotData.claims.find((c) => c.address === address) || null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,7 +229,11 @@ export async function updateExistingClaimConditions(
|
||||
/**
|
||||
* Fetches the proof for the current signer for a particular wallet.
|
||||
*
|
||||
* @param addressToClaim
|
||||
* @param merkleRoot - The merkle root of the condition to check.
|
||||
* @param tokenDecimals
|
||||
* @param merkleMetadata
|
||||
* @param storage
|
||||
* @returns - The proof for the current signer for the specified condition.
|
||||
*/
|
||||
export async function getClaimerProofs(
|
||||
@@ -213,30 +243,21 @@ export async function getClaimerProofs(
|
||||
merkleMetadata: Record<string, string>,
|
||||
storage: ThirdwebStorage,
|
||||
): Promise<{ maxClaimable: BigNumber; proof: string[] }> {
|
||||
const claims: Snapshot = await fetchSnapshot(
|
||||
const claim = await fetchSnapshotEntryForAddress(
|
||||
addressToClaim,
|
||||
merkleRoot,
|
||||
merkleMetadata,
|
||||
storage,
|
||||
);
|
||||
if (claims === undefined) {
|
||||
return {
|
||||
proof: [],
|
||||
maxClaimable: BigNumber.from(0),
|
||||
};
|
||||
}
|
||||
const item = claims.find(
|
||||
(c) => c.address.toLowerCase() === addressToClaim?.toLowerCase(),
|
||||
);
|
||||
|
||||
if (item === undefined) {
|
||||
if (!claim) {
|
||||
return {
|
||||
proof: [],
|
||||
maxClaimable: BigNumber.from(0),
|
||||
};
|
||||
}
|
||||
return {
|
||||
proof: item.proof,
|
||||
maxClaimable: ethers.utils.parseUnits(item.maxClaimable, tokenDecimals),
|
||||
proof: claim.proof,
|
||||
maxClaimable: ethers.utils.parseUnits(claim.maxClaimable, tokenDecimals),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -356,10 +377,10 @@ export async function transformResultToClaimCondition(
|
||||
provider: providers.Provider,
|
||||
merkleMetadata: Record<string, string> | undefined,
|
||||
storage: ThirdwebStorage,
|
||||
shouldDownloadSnapshot: boolean,
|
||||
): Promise<ClaimCondition> {
|
||||
const cv = await fetchCurrencyValue(provider, pm.currency, pm.pricePerToken);
|
||||
|
||||
const claims = await fetchSnapshot(pm.merkleRoot, merkleMetadata, storage);
|
||||
const maxClaimableSupply = convertToReadableQuantity(
|
||||
pm.maxClaimableSupply,
|
||||
tokenDecimals,
|
||||
@@ -388,7 +409,9 @@ export async function transformResultToClaimCondition(
|
||||
currencyAddress: pm.currency,
|
||||
currencyMetadata: cv,
|
||||
merkleRootHash: pm.merkleRoot,
|
||||
snapshot: claims,
|
||||
snapshot: shouldDownloadSnapshot
|
||||
? await fetchSnapshot(pm.merkleRoot, merkleMetadata, storage)
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
} from "../schema/contracts/custom";
|
||||
import { ExtensionNotImplementedError } from "./error";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import bs58 from "bs58";
|
||||
import { decode } from "cbor-x";
|
||||
import { BaseContract, BigNumber, ethers } from "ethers";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -179,7 +181,7 @@ export function extractEventsFromAbi(
|
||||
abi: z.input<typeof AbiSchema>,
|
||||
metadata?: Record<string, any>,
|
||||
): AbiEvent[] {
|
||||
const events = abi.filter((el) => el.type === "event");
|
||||
const events = (abi || []).filter((el) => el.type === "event");
|
||||
const parsed: AbiEvent[] = [];
|
||||
for (const e of events) {
|
||||
const doc = extractCommentFromMetadata(e.name, metadata, "events");
|
||||
@@ -282,9 +284,9 @@ export async function resolveContractUriFromAddress(
|
||||
* @internal
|
||||
* @param bytecode
|
||||
*/
|
||||
async function extractIPFSHashFromBytecode(
|
||||
export function extractIPFSHashFromBytecode(
|
||||
bytecode: string,
|
||||
): Promise<string | undefined> {
|
||||
): string | undefined {
|
||||
const numericBytecode = hexToBytes(bytecode);
|
||||
const cborLength: number =
|
||||
numericBytecode[numericBytecode.length - 2] * 0x100 +
|
||||
@@ -293,15 +295,13 @@ async function extractIPFSHashFromBytecode(
|
||||
numericBytecode.slice(numericBytecode.length - 2 - cborLength, -2),
|
||||
);
|
||||
|
||||
// load these lazily to avoid loading them when they are not needed
|
||||
const [cbor, multiHashes] = await Promise.all([
|
||||
import("cbor-web"),
|
||||
import("multihashes"),
|
||||
]);
|
||||
|
||||
const cborData = cbor.decodeFirstSync(bytecodeBuffer);
|
||||
if (cborData["ipfs"]) {
|
||||
return `ipfs://${multiHashes.toB58String(cborData["ipfs"])}`;
|
||||
const cborData = decode(bytecodeBuffer);
|
||||
if ("ipfs" in cborData && cborData["ipfs"]) {
|
||||
try {
|
||||
return `ipfs://${bs58.encode(cborData["ipfs"])}`;
|
||||
} catch (e) {
|
||||
console.warn("feature-detection ipfs cbor failed", e);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import type {
|
||||
} from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish, Contract, ethers, providers } from "ethers";
|
||||
|
||||
const FALLBACK_METADATA = {
|
||||
export const FALLBACK_METADATA = {
|
||||
name: "Failed to load NFT metadata",
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import {
|
||||
ShardData,
|
||||
ShardedMerkleTreeInfo,
|
||||
ShardedSnapshot,
|
||||
SnapshotEntry,
|
||||
SnapshotEntryWithProof,
|
||||
SnapshotInputSchema,
|
||||
} from "../schema";
|
||||
import { SnapshotInput } from "../types";
|
||||
import { hashLeafNode } from "./snapshots";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { utils } from "ethers";
|
||||
import { MerkleTree } from "merkletreejs";
|
||||
|
||||
// shard using the first 2 hex character of the address
|
||||
// this splits the merkle tree into 256 shards
|
||||
// shard files will be 00.json, 01.json, 02.json, ..., ff.json
|
||||
const SHARD_NYBBLES = 2;
|
||||
|
||||
export class ShardedMerkleTree {
|
||||
private shardNybbles: number;
|
||||
private shards: Record<string, ShardData>;
|
||||
private trees: Record<string, MerkleTree>;
|
||||
private storage: ThirdwebStorage;
|
||||
private baseUri: string;
|
||||
private originalEntriesUri: string;
|
||||
private tokenDecimals: number;
|
||||
|
||||
constructor(
|
||||
storage: ThirdwebStorage,
|
||||
baseUri: string,
|
||||
originalEntriesUri: string,
|
||||
shardNybbles: number,
|
||||
tokenDecimals: number,
|
||||
) {
|
||||
this.storage = storage;
|
||||
this.shardNybbles = shardNybbles;
|
||||
this.baseUri = baseUri;
|
||||
this.originalEntriesUri = originalEntriesUri;
|
||||
this.tokenDecimals = tokenDecimals;
|
||||
this.shards = {};
|
||||
this.trees = {};
|
||||
}
|
||||
|
||||
static async fromUri(
|
||||
uri: string,
|
||||
storage: ThirdwebStorage,
|
||||
): Promise<ShardedMerkleTree | undefined> {
|
||||
try {
|
||||
const shardedMerkleTreeInfo =
|
||||
await storage.downloadJSON<ShardedMerkleTreeInfo>(uri);
|
||||
if (shardedMerkleTreeInfo.isShardedMerkleTree) {
|
||||
return ShardedMerkleTree.fromShardedMerkleTreeInfo(
|
||||
shardedMerkleTreeInfo,
|
||||
storage,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static async fromShardedMerkleTreeInfo(
|
||||
info: ShardedMerkleTreeInfo,
|
||||
storage: ThirdwebStorage,
|
||||
): Promise<ShardedMerkleTree> {
|
||||
return new ShardedMerkleTree(
|
||||
storage,
|
||||
info.baseUri,
|
||||
info.originalEntriesUri,
|
||||
info.shardNybbles,
|
||||
info.tokenDecimals,
|
||||
);
|
||||
}
|
||||
|
||||
static hashEntry(entry: SnapshotEntry, tokenDecimals: number): string {
|
||||
return hashLeafNode(
|
||||
entry.address,
|
||||
utils.parseUnits(entry.maxClaimable, tokenDecimals),
|
||||
);
|
||||
}
|
||||
|
||||
static async buildAndUpload(
|
||||
snapshotInput: SnapshotInput,
|
||||
tokenDecimals: number,
|
||||
storage: ThirdwebStorage,
|
||||
shardNybbles = SHARD_NYBBLES,
|
||||
): Promise<ShardedSnapshot> {
|
||||
const inputs = SnapshotInputSchema.parse(snapshotInput);
|
||||
// TODO Could also derive shardNybbles from input size
|
||||
const shards: Record<string, SnapshotEntry[]> = {};
|
||||
for (const snapshotEntry of inputs) {
|
||||
const shard = snapshotEntry.address
|
||||
.slice(2, 2 + shardNybbles)
|
||||
.toLowerCase();
|
||||
if (shards[shard] === undefined) {
|
||||
shards[shard] = [];
|
||||
}
|
||||
shards[shard].push(snapshotEntry);
|
||||
}
|
||||
// create shard => subtree root map
|
||||
const roots = Object.fromEntries(
|
||||
Object.entries(shards).map(([shard, entries]) => [
|
||||
shard,
|
||||
new MerkleTree(
|
||||
entries.map((entry) =>
|
||||
ShardedMerkleTree.hashEntry(entry, tokenDecimals),
|
||||
),
|
||||
utils.keccak256,
|
||||
{
|
||||
sort: true,
|
||||
},
|
||||
).getHexRoot(),
|
||||
]),
|
||||
);
|
||||
// create master tree from shard => subtree root map
|
||||
const tree = new MerkleTree(Object.values(roots), utils.keccak256, {
|
||||
sort: true,
|
||||
});
|
||||
|
||||
const shardsToUpload = [];
|
||||
for (const [shardId, entries] of Object.entries(shards)) {
|
||||
const data: ShardData = {
|
||||
proofs: tree
|
||||
.getProof(roots[shardId])
|
||||
.map((value) => "0x" + value.data.toString("hex")),
|
||||
entries,
|
||||
};
|
||||
shardsToUpload.push({
|
||||
data: JSON.stringify(data),
|
||||
name: `${shardId}.json`,
|
||||
});
|
||||
}
|
||||
const uris = await storage.uploadBatch(shardsToUpload);
|
||||
const baseUri = uris[0].slice(0, uris[0].lastIndexOf("/"));
|
||||
|
||||
const originalEntriesUri = await storage.upload(inputs);
|
||||
|
||||
const shardedMerkleInfo: ShardedMerkleTreeInfo = {
|
||||
merkleRoot: tree.getHexRoot(),
|
||||
baseUri,
|
||||
originalEntriesUri,
|
||||
shardNybbles,
|
||||
tokenDecimals,
|
||||
isShardedMerkleTree: true,
|
||||
};
|
||||
|
||||
const masterUri = await storage.upload(shardedMerkleInfo);
|
||||
|
||||
return {
|
||||
shardedMerkleInfo,
|
||||
uri: masterUri,
|
||||
};
|
||||
}
|
||||
|
||||
public async getProof(
|
||||
address: string,
|
||||
): Promise<SnapshotEntryWithProof | null> {
|
||||
const shardId = address.slice(2, 2 + this.shardNybbles).toLowerCase();
|
||||
let shard = this.shards[shardId];
|
||||
if (shard === undefined) {
|
||||
try {
|
||||
shard = this.shards[shardId] =
|
||||
await this.storage.downloadJSON<ShardData>(
|
||||
`${this.baseUri}/${shardId}.json`,
|
||||
);
|
||||
this.trees[shardId] = new MerkleTree(
|
||||
shard.entries.map((entry) =>
|
||||
ShardedMerkleTree.hashEntry(entry, this.tokenDecimals),
|
||||
),
|
||||
utils.keccak256,
|
||||
{ sort: true },
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn("No merkle entry found for address", address);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const entry = shard.entries.find(
|
||||
(i) => i.address.toLowerCase() === address.toLowerCase(),
|
||||
);
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
const leaf = ShardedMerkleTree.hashEntry(entry, this.tokenDecimals);
|
||||
const proof = this.trees[shardId]
|
||||
.getProof(leaf)
|
||||
.map((i) => "0x" + i.data.toString("hex"));
|
||||
return {
|
||||
address,
|
||||
proof: proof.concat(shard.proofs),
|
||||
maxClaimable: entry.maxClaimable,
|
||||
};
|
||||
}
|
||||
|
||||
public async getAllEntries(): Promise<SnapshotEntry[]> {
|
||||
try {
|
||||
const entries = await this.storage.downloadJSON<SnapshotEntry[]>(
|
||||
this.originalEntriesUri,
|
||||
);
|
||||
return entries;
|
||||
} catch (e) {
|
||||
console.warn("Could not fetch original snapshot entries", e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private computeAllShardIds(shardNybbles: number) {
|
||||
const max = parseInt(`0x${"f".repeat(shardNybbles)}`, 16);
|
||||
const allShards = [];
|
||||
for (let i = 0; i <= max; i++) {
|
||||
allShards.push(i.toString(16).padStart(shardNybbles, "0"));
|
||||
}
|
||||
return allShards;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
import {
|
||||
SnapshotInputSchema,
|
||||
SnapshotSchema,
|
||||
} from "../schema/contracts/common/snapshots";
|
||||
import { SnapshotInputSchema } from "../schema/contracts/common/snapshots";
|
||||
import {
|
||||
SnapshotInfo,
|
||||
SnapshotInput,
|
||||
} from "../types/claim-conditions/claim-conditions";
|
||||
import { DuplicateLeafsError } from "./error";
|
||||
import { ShardedMerkleTree } from "./sharded-merkle-tree";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish, utils } from "ethers";
|
||||
|
||||
@@ -29,33 +27,14 @@ export async function createSnapshot(
|
||||
if (hasDuplicates) {
|
||||
throw new DuplicateLeafsError();
|
||||
}
|
||||
|
||||
const hashedLeafs = input.map((i) =>
|
||||
hashLeafNode(i.address, utils.parseUnits(i.maxClaimable, tokenDecimals)),
|
||||
const tree = await ShardedMerkleTree.buildAndUpload(
|
||||
input,
|
||||
tokenDecimals,
|
||||
storage,
|
||||
);
|
||||
// import dynamically to avoid bloating bundle size for non-merkle tree users
|
||||
const MerkleTree = (await import("merkletreejs")).MerkleTree;
|
||||
const tree = new MerkleTree(hashedLeafs, utils.keccak256, {
|
||||
sort: true,
|
||||
});
|
||||
|
||||
const snapshot = SnapshotSchema.parse({
|
||||
merkleRoot: tree.getHexRoot(),
|
||||
claims: input.map((i, index) => {
|
||||
const proof = tree.getHexProof(hashedLeafs[index]);
|
||||
return {
|
||||
address: i.address,
|
||||
maxClaimable: i.maxClaimable,
|
||||
proof,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
const uri = await storage.upload(snapshot);
|
||||
return {
|
||||
merkleRoot: tree.getHexRoot(),
|
||||
snapshotUri: uri,
|
||||
snapshot,
|
||||
merkleRoot: tree.shardedMerkleInfo.merkleRoot,
|
||||
snapshotUri: tree.uri,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { SignerOrProvider } from "../core/types";
|
||||
import { StaticJsonRpcBatchProvider } from "../lib/static-batch-rpc";
|
||||
import { ChainId, SUPPORTED_CHAIN_ID, SUPPORTED_CHAIN_IDS } from "./chains";
|
||||
import { ethers, providers } from "ethers";
|
||||
|
||||
/**
|
||||
@@ -26,13 +27,15 @@ export const PINATA_IPFS_URL = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* This is a community API key that is subject to rate limiting. Please use your own key.
|
||||
*/
|
||||
export type ChainOrRpc =
|
||||
const DEFAULT_API_KEY = "_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC";
|
||||
|
||||
type ChainNames =
|
||||
| "mumbai"
|
||||
| "polygon"
|
||||
// common alias for `polygon`
|
||||
| "matic"
|
||||
| "rinkeby"
|
||||
| "goerli"
|
||||
| "mainnet"
|
||||
// common alias for `mainnet`
|
||||
@@ -44,21 +47,53 @@ export type ChainOrRpc =
|
||||
// actual name
|
||||
| "avalanche-fuji"
|
||||
| "optimism"
|
||||
| "optimism-kovan"
|
||||
| "optimism-goerli"
|
||||
| "arbitrum"
|
||||
| "arbitrum-rinkeby"
|
||||
| "arbitrum-goerli"
|
||||
| "binance"
|
||||
| "binance-testnet"
|
||||
// ideally we could use `https://${string}` notation here, but doing that causes anything that is a generic string to throw a type error => not worth the hassle for now
|
||||
| (string & {});
|
||||
|
||||
| "binance-testnet";
|
||||
/**
|
||||
* @internal
|
||||
* This is a community API key that is subject to rate limiting. Please use your own key.
|
||||
*/
|
||||
const DEFAULT_API_KEY = "_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC";
|
||||
export type ChainOrRpc =
|
||||
// ideally we could use `https://${string}` notation here, but doing that causes anything that is a generic string to throw a type error => not worth the hassle for now
|
||||
ChainNames | (string & {});
|
||||
|
||||
export const CHAIN_NAME_TO_ID: Record<ChainNames, SUPPORTED_CHAIN_ID> = {
|
||||
"avalanche-fuji": ChainId.AvalancheFujiTestnet,
|
||||
"avalanche-testnet": ChainId.AvalancheFujiTestnet,
|
||||
"fantom-testnet": ChainId.FantomTestnet,
|
||||
ethereum: ChainId.Mainnet,
|
||||
matic: ChainId.Polygon,
|
||||
mumbai: ChainId.Mumbai,
|
||||
goerli: ChainId.Goerli,
|
||||
polygon: ChainId.Polygon,
|
||||
mainnet: ChainId.Mainnet,
|
||||
optimism: ChainId.Optimism,
|
||||
"optimism-goerli": ChainId.OptimismGoerli,
|
||||
arbitrum: ChainId.Arbitrum,
|
||||
"arbitrum-goerli": ChainId.ArbitrumGoerli,
|
||||
fantom: ChainId.Fantom,
|
||||
avalanche: ChainId.Avalanche,
|
||||
binance: ChainId.BinanceSmartChainMainnet,
|
||||
"binance-testnet": ChainId.BinanceSmartChainTestnet,
|
||||
};
|
||||
|
||||
export const CHAIN_ID_TO_NAME = Object.fromEntries(
|
||||
Object.entries(CHAIN_NAME_TO_ID).map(([name, id]) => [id, name]),
|
||||
) as Record<ChainId, ChainNames>;
|
||||
|
||||
function buildDefaultMap() {
|
||||
return SUPPORTED_CHAIN_IDS.reduce((previousValue, currentValue) => {
|
||||
previousValue[currentValue] = getProviderForNetwork(
|
||||
CHAIN_ID_TO_NAME[currentValue],
|
||||
) as string;
|
||||
return previousValue;
|
||||
}, {} as Record<SUPPORTED_CHAIN_ID, string>);
|
||||
}
|
||||
|
||||
export const DEFAULT_RPC_URLS: Record<SUPPORTED_CHAIN_ID, string> =
|
||||
buildDefaultMap();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
} from "../../../core/schema/QueryParams";
|
||||
import { NFT, NFTMetadata, NFTMetadataOrUri } from "../../../core/schema/nft";
|
||||
import { getRoleHash } from "../../common";
|
||||
import { prepareClaim } from "../../common/claim-conditions";
|
||||
import { uploadOrExtractURIs } from "../../common/nft";
|
||||
import { FEATURE_NFT_REVEALABLE } from "../../constants/erc721-features";
|
||||
import { TransactionTask } from "../../core/classes/TransactionTask";
|
||||
@@ -31,7 +30,6 @@ import {
|
||||
import { PaperCheckout } from "../../integrations/paper-xyz";
|
||||
import { DropErc721ContractSchema } from "../../schema/contracts/drop-erc721";
|
||||
import { SDKOptions } from "../../schema/sdk-options";
|
||||
import { ClaimVerification } from "../../types";
|
||||
import { UploadProgressEvent } from "../../types/events";
|
||||
import type { DropERC721 } from "@thirdweb-dev/contracts-js";
|
||||
import type ABI from "@thirdweb-dev/contracts-js/dist/abis/DropERC721.json";
|
||||
@@ -319,7 +317,7 @@ export class NFTDrop extends StandardErc721<DropERC721> {
|
||||
* @returns the unclaimed supply
|
||||
*/
|
||||
public async totalClaimedSupply(): Promise<BigNumber> {
|
||||
return await this.contractWrapper.readContract.nextTokenIdToClaim();
|
||||
return this.erc721.totalClaimedSupply();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,9 +333,7 @@ export class NFTDrop extends StandardErc721<DropERC721> {
|
||||
* @returns the unclaimed supply
|
||||
*/
|
||||
public async totalUnclaimedSupply(): Promise<BigNumber> {
|
||||
return (await this.contractWrapper.readContract.nextTokenIdToMint()).sub(
|
||||
await this.totalClaimedSupply(),
|
||||
);
|
||||
return this.erc721.totalUnclaimedSupply();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -625,27 +621,6 @@ export class NFTDrop extends StandardErc721<DropERC721> {
|
||||
/** ******************************
|
||||
* PRIVATE FUNCTIONS
|
||||
*******************************/
|
||||
|
||||
/**
|
||||
* Returns proofs and the overrides required for the transaction.
|
||||
*
|
||||
* @returns - `overrides` and `proofs` as an object.
|
||||
*/
|
||||
private async prepareClaim(
|
||||
quantity: BigNumberish,
|
||||
checkERC20Allowance: boolean,
|
||||
): Promise<ClaimVerification> {
|
||||
return prepareClaim(
|
||||
quantity,
|
||||
await this.claimConditions.getActive(),
|
||||
async () => (await this.metadata.get()).merkle,
|
||||
0,
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
checkERC20Allowance,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -328,7 +328,7 @@ export class SignatureDrop extends StandardErc721<SignatureDropContract> {
|
||||
* @returns the claimed supply
|
||||
*/
|
||||
public async totalClaimedSupply(): Promise<BigNumber> {
|
||||
return await this.contractWrapper.readContract.totalMinted();
|
||||
return this.erc721.totalClaimedSupply();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -344,10 +344,7 @@ export class SignatureDrop extends StandardErc721<SignatureDropContract> {
|
||||
* @returns the unclaimed supply
|
||||
*/
|
||||
public async totalUnclaimedSupply(): Promise<BigNumber> {
|
||||
const maxSupply =
|
||||
await this.contractWrapper.readContract.nextTokenIdToMint();
|
||||
|
||||
return maxSupply.sub(await this.totalClaimedSupply());
|
||||
return this.erc721.totalUnclaimedSupply();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
PrebuiltContractType,
|
||||
} from "../types";
|
||||
import { ContractFactory } from "./factory";
|
||||
import { FactoryEvents } from "./factory-events";
|
||||
import { ContractRegistry } from "./registry";
|
||||
import { RPCConnectionHandler } from "./rpc-connection-handler";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
@@ -65,6 +66,7 @@ export class ContractDeployer extends RPCConnectionHandler {
|
||||
* should never be accessed directly, use {@link ContractDeployer.getRegistry} instead
|
||||
*/
|
||||
private _registry: Promise<ContractRegistry> | undefined;
|
||||
public events: FactoryEvents | undefined;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
@@ -74,6 +76,10 @@ export class ContractDeployer extends RPCConnectionHandler {
|
||||
) {
|
||||
super(network, options);
|
||||
this.storage = storage;
|
||||
|
||||
// Initialize factory and registry (we don't need to make these calls async)
|
||||
this.getFactory();
|
||||
this.getRegistry();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -527,12 +533,15 @@ export class ContractDeployer extends RPCConnectionHandler {
|
||||
chainId,
|
||||
"twFactory",
|
||||
);
|
||||
return new ContractFactory(
|
||||
const factory = new ContractFactory(
|
||||
factoryAddress,
|
||||
this.getSignerOrProvider(),
|
||||
this.storage,
|
||||
this.options,
|
||||
);
|
||||
this.events = new FactoryEvents(factory);
|
||||
|
||||
return factory;
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ListenerFn } from "eventemitter3";
|
||||
* @public
|
||||
*/
|
||||
export class ContractEvents<TContract extends BaseContract> {
|
||||
private contractWrapper;
|
||||
protected contractWrapper;
|
||||
|
||||
constructor(contractWrapper: ContractWrapper<TContract>) {
|
||||
this.contractWrapper = contractWrapper;
|
||||
|
||||
@@ -343,6 +343,13 @@ export class ContractWrapper<
|
||||
("openzeppelin" in this.options.gasless ||
|
||||
"biconomy" in this.options.gasless)
|
||||
) {
|
||||
if (fn === "multicall" && Array.isArray(args[0]) && args[0].length > 0) {
|
||||
const from = await this.getSignerAddress();
|
||||
args[0] = args[0].map((tx: any) =>
|
||||
ethers.utils.solidityPack(["bytes", "address"], [tx, from]),
|
||||
);
|
||||
}
|
||||
|
||||
const provider = this.getProvider();
|
||||
const txHash = await this.sendGaslessTransaction(fn, args, callOverrides);
|
||||
this.emitTransactionEvent("submitted", txHash);
|
||||
|
||||
@@ -18,6 +18,7 @@ import { AmountSchema } from "../../schema";
|
||||
import {
|
||||
Amount,
|
||||
ClaimCondition,
|
||||
ClaimConditionFetchOptions,
|
||||
ClaimConditionInput,
|
||||
ClaimVerification,
|
||||
} from "../../types";
|
||||
@@ -75,7 +76,9 @@ export class DropClaimConditions<
|
||||
*
|
||||
* @returns the claim condition metadata
|
||||
*/
|
||||
public async getActive(): Promise<ClaimCondition> {
|
||||
public async getActive(
|
||||
options?: ClaimConditionFetchOptions,
|
||||
): Promise<ClaimCondition> {
|
||||
const cc = await this.get();
|
||||
const metadata = await this.metadata.get();
|
||||
return await transformResultToClaimCondition(
|
||||
@@ -84,6 +87,7 @@ export class DropClaimConditions<
|
||||
this.contractWrapper.getProvider(),
|
||||
metadata.merkle || {},
|
||||
this.storage,
|
||||
options?.withAllowList || false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -104,7 +108,9 @@ export class DropClaimConditions<
|
||||
*
|
||||
* @returns the claim conditions metadata
|
||||
*/
|
||||
public async getAll(): Promise<ClaimCondition[]> {
|
||||
public async getAll(
|
||||
options?: ClaimConditionFetchOptions,
|
||||
): Promise<ClaimCondition[]> {
|
||||
if (this.isMultiPhaseDropContract(this.contractWrapper)) {
|
||||
const claimCondition =
|
||||
(await this.contractWrapper.readContract.claimCondition()) as {
|
||||
@@ -129,11 +135,12 @@ export class DropClaimConditions<
|
||||
this.contractWrapper.getProvider(),
|
||||
metadata.merkle,
|
||||
this.storage,
|
||||
options?.withAllowList || false,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return [await this.getActive()];
|
||||
return [await this.getActive(options)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { NATIVE_TOKEN_ADDRESS } from "../../constants/index";
|
||||
import { ClaimEligibility } from "../../enums";
|
||||
import {
|
||||
ClaimCondition,
|
||||
ClaimConditionFetchOptions,
|
||||
ClaimConditionInput,
|
||||
ClaimConditionsForToken,
|
||||
ClaimVerification,
|
||||
@@ -63,7 +64,10 @@ export class DropErc1155ClaimConditions<
|
||||
*
|
||||
* @returns the claim condition metadata
|
||||
*/
|
||||
public async getActive(tokenId: BigNumberish): Promise<ClaimCondition> {
|
||||
public async getActive(
|
||||
tokenId: BigNumberish,
|
||||
options?: ClaimConditionFetchOptions,
|
||||
): Promise<ClaimCondition> {
|
||||
const mc = await this.get(tokenId);
|
||||
const metadata = await this.metadata.get();
|
||||
return await transformResultToClaimCondition(
|
||||
@@ -72,6 +76,7 @@ export class DropErc1155ClaimConditions<
|
||||
this.contractWrapper.getProvider(),
|
||||
metadata.merkle,
|
||||
this.storage,
|
||||
options?.withAllowList || false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -101,7 +106,10 @@ export class DropErc1155ClaimConditions<
|
||||
*
|
||||
* @returns the claim conditions metadata
|
||||
*/
|
||||
public async getAll(tokenId: BigNumberish): Promise<ClaimCondition[]> {
|
||||
public async getAll(
|
||||
tokenId: BigNumberish,
|
||||
options?: ClaimConditionFetchOptions,
|
||||
): Promise<ClaimCondition[]> {
|
||||
if (this.isMultiPhaseDropContract(this.contractWrapper)) {
|
||||
const claimCondition =
|
||||
(await this.contractWrapper.readContract.claimCondition(tokenId)) as {
|
||||
@@ -128,6 +136,7 @@ export class DropErc1155ClaimConditions<
|
||||
this.contractWrapper.getProvider(),
|
||||
metadata.merkle,
|
||||
this.storage,
|
||||
options?.withAllowList || false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
hasFunction,
|
||||
NotFoundError,
|
||||
} from "../../common";
|
||||
import { fetchTokenMetadata } from "../../common/nft";
|
||||
import { FALLBACK_METADATA, fetchTokenMetadata } from "../../common/nft";
|
||||
import {
|
||||
FEATURE_EDITION,
|
||||
FEATURE_EDITION_BATCH_MINTABLE,
|
||||
@@ -128,7 +128,11 @@ export class Erc1155<
|
||||
this.contractWrapper.readContract
|
||||
.totalSupply(tokenId)
|
||||
.catch(() => BigNumber.from(0)),
|
||||
this.getTokenMetadata(tokenId),
|
||||
this.getTokenMetadata(tokenId).catch(() => ({
|
||||
id: tokenId.toString(),
|
||||
uri: "",
|
||||
...FALLBACK_METADATA,
|
||||
})),
|
||||
]);
|
||||
return {
|
||||
owner: ethers.constants.AddressZero,
|
||||
|
||||
@@ -98,7 +98,7 @@ export class Erc721ClaimableWithConditions implements DetectableFeature {
|
||||
quantity,
|
||||
options?.checkERC20Allowance === undefined
|
||||
? true
|
||||
: options.checkERC20Allowance
|
||||
: options.checkERC20Allowance,
|
||||
);
|
||||
|
||||
return TransactionTask.make({
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
hasFunction,
|
||||
NotFoundError,
|
||||
} from "../../common";
|
||||
import { fetchTokenMetadata } from "../../common/nft";
|
||||
import { FALLBACK_METADATA, fetchTokenMetadata } from "../../common/nft";
|
||||
import {
|
||||
FEATURE_NFT,
|
||||
FEATURE_NFT_BATCH_MINTABLE,
|
||||
@@ -124,7 +124,11 @@ export class Erc721<
|
||||
public async get(tokenId: BigNumberish): Promise<NFT> {
|
||||
const [owner, metadata] = await Promise.all([
|
||||
this.ownerOf(tokenId).catch(() => constants.AddressZero),
|
||||
this.getTokenMetadata(tokenId),
|
||||
this.getTokenMetadata(tokenId).catch(() => ({
|
||||
id: tokenId.toString(),
|
||||
uri: "",
|
||||
...FALLBACK_METADATA,
|
||||
})),
|
||||
]);
|
||||
return { owner, metadata, type: "ERC721", supply: 1 };
|
||||
}
|
||||
@@ -574,6 +578,7 @@ export class Erc721<
|
||||
*
|
||||
* @param destinationAddress - Address you want to send the token to
|
||||
* @param quantity - Quantity of the tokens you want to claim
|
||||
* @param options
|
||||
* @twfeature ERC721Claimable
|
||||
* @returns - an array of results containing the id of the token claimed, the transaction receipt and a promise to optionally fetch the nft metadata
|
||||
*/
|
||||
@@ -598,6 +603,7 @@ export class Erc721<
|
||||
* 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
|
||||
* @param quantity
|
||||
* @param options
|
||||
*/
|
||||
public async getClaimTransaction(
|
||||
destinationAddress: string,
|
||||
@@ -619,6 +625,37 @@ export class Erc721<
|
||||
throw new ExtensionNotImplementedError(FEATURE_NFT_CLAIMABLE);
|
||||
}
|
||||
|
||||
public async totalClaimedSupply(): Promise<BigNumber> {
|
||||
const contract = this.contractWrapper;
|
||||
if (hasFunction<DropERC721>("nextTokenIdToClaim", contract)) {
|
||||
return contract.readContract.nextTokenIdToClaim();
|
||||
}
|
||||
if (hasFunction<SignatureDrop>("totalMinted", contract)) {
|
||||
return contract.readContract.totalMinted();
|
||||
}
|
||||
throw new Error(
|
||||
"No function found on contract to get total claimed supply",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unclaimed supply
|
||||
*
|
||||
* @remarks Get the number of unclaimed NFTs in this Drop.
|
||||
*
|
||||
* * @example
|
||||
* ```javascript
|
||||
* const unclaimedNFTCount = await contract.totalUnclaimedSupply();
|
||||
* console.log(`NFTs left to claim: ${unclaimedNFTCount}`);
|
||||
* ```
|
||||
* @returns the unclaimed supply
|
||||
*/
|
||||
public async totalUnclaimedSupply(): Promise<BigNumber> {
|
||||
return (await this.nextTokenIdToMint()).sub(
|
||||
await this.totalClaimedSupply(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure claim conditions
|
||||
* @remarks Define who can claim NFTs in the collection, when and how many.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ContractEvents } from "./contract-events";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import type { TWFactory } from "@thirdweb-dev/contracts-js";
|
||||
import { ProxyDeployedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/TWFactory";
|
||||
|
||||
interface DeployEvent {
|
||||
transactionHash: string;
|
||||
contractAddress: string;
|
||||
}
|
||||
|
||||
export class FactoryEvents extends ContractEvents<TWFactory> {
|
||||
constructor(contractWrapper: ContractWrapper<TWFactory>) {
|
||||
super(contractWrapper);
|
||||
}
|
||||
|
||||
public addDeployListener(listener: (event: DeployEvent) => void) {
|
||||
this.addTransactionListener(async (event) => {
|
||||
if (!event.transactionHash) {
|
||||
return;
|
||||
}
|
||||
|
||||
const receipt = await this.contractWrapper
|
||||
.getProvider()
|
||||
.getTransactionReceipt(event.transactionHash);
|
||||
const events = this.contractWrapper.parseLogs<ProxyDeployedEvent>(
|
||||
"ProxyDeployed",
|
||||
receipt.logs,
|
||||
);
|
||||
|
||||
if (events.length > 0) {
|
||||
listener({
|
||||
...event,
|
||||
contractAddress: events[0].args.proxy,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,7 @@ export class ContractFactory extends ContractWrapper<TWFactory> {
|
||||
encodedFunc,
|
||||
]);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
// if the error is caused by user cancelling the transaction, just re-throw it
|
||||
if (
|
||||
(e as TransactionError).message
|
||||
|
||||
@@ -23,7 +23,7 @@ export const ClaimConditionInputSchema = z.object({
|
||||
quantityLimitPerTransaction: QuantitySchema,
|
||||
waitInSeconds: BigNumberishSchema.default(0),
|
||||
merkleRootHash: BytesLikeSchema.default(utils.hexZeroPad([0], 32)),
|
||||
snapshot: z.optional(SnapshotInputSchema),
|
||||
snapshot: z.optional(SnapshotInputSchema).nullable(),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -53,4 +53,5 @@ export const ClaimConditionOutputSchema = ClaimConditionInputSchema.extend({
|
||||
price: BigNumberSchema,
|
||||
waitInSeconds: BigNumberSchema,
|
||||
startTime: BigNumberSchema.transform((n) => new Date(n.toNumber() * 1000)),
|
||||
snapshot: SnapshotInputSchema.optional().nullable(),
|
||||
});
|
||||
|
||||
@@ -11,25 +11,47 @@ export const MerkleSchema = z.object({
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const SnapshotAddressInput = z.object({
|
||||
export const SnapshotEntryInput = z.object({
|
||||
address: AddressSchema,
|
||||
maxClaimable: AmountSchema.default(0),
|
||||
});
|
||||
|
||||
export type SnapshotEntry = z.output<typeof SnapshotEntryInput>;
|
||||
export type ShardData = {
|
||||
proofs: string[];
|
||||
entries: SnapshotEntry[];
|
||||
};
|
||||
export type ShardedMerkleTreeInfo = {
|
||||
merkleRoot: string;
|
||||
baseUri: string;
|
||||
originalEntriesUri: string;
|
||||
shardNybbles: number;
|
||||
tokenDecimals: number;
|
||||
isShardedMerkleTree: true;
|
||||
};
|
||||
|
||||
export type ShardedSnapshot = {
|
||||
shardedMerkleInfo: ShardedMerkleTreeInfo;
|
||||
uri: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const SnapshotInputSchema = z.union([
|
||||
z.array(z.string()).transform((strings) =>
|
||||
strings.map((address) =>
|
||||
SnapshotAddressInput.parse({
|
||||
SnapshotEntryInput.parse({
|
||||
address,
|
||||
}),
|
||||
),
|
||||
),
|
||||
z.array(SnapshotAddressInput),
|
||||
z.array(SnapshotEntryInput),
|
||||
]);
|
||||
|
||||
const SnapshotEntryWithProofSchema = SnapshotEntryInput.extend({
|
||||
proof: z.array(z.string()),
|
||||
});
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -38,12 +60,14 @@ export const SnapshotSchema = z.object({
|
||||
* The merkle root
|
||||
*/
|
||||
merkleRoot: z.string(),
|
||||
claims: z.array(
|
||||
SnapshotAddressInput.extend({
|
||||
proof: z.array(z.string()),
|
||||
}),
|
||||
),
|
||||
claims: z.array(SnapshotEntryWithProofSchema),
|
||||
});
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type SnapshotEntryWithProof = z.output<
|
||||
typeof SnapshotEntryWithProofSchema
|
||||
>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -51,5 +75,4 @@ export const SnapshotSchema = z.object({
|
||||
export const SnapshotInfoSchema = z.object({
|
||||
merkleRoot: z.string(),
|
||||
snapshotUri: z.string(),
|
||||
snapshot: SnapshotSchema,
|
||||
});
|
||||
|
||||
@@ -77,3 +77,7 @@ export type ClaimOptions = {
|
||||
*/
|
||||
checkERC20Allowance?: boolean;
|
||||
};
|
||||
|
||||
export type ClaimConditionFetchOptions = {
|
||||
withAllowList?: boolean;
|
||||
};
|
||||
|
||||
@@ -200,7 +200,7 @@ export class Deployer {
|
||||
* const metadata = {
|
||||
* name: "My NFT Drop",
|
||||
* symbol: "NFT",
|
||||
* totalSuppply: 5,
|
||||
* totalSupply: 5,
|
||||
* };
|
||||
*
|
||||
* const address = await sdk.deployer.createNftDrop(metadata);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CreatorInput } from "../../types/programs";
|
||||
import { CreatorInput, CreatorOutput } from "../../types/programs";
|
||||
import type { Creator } from "@metaplex-foundation/js";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
@@ -23,3 +23,13 @@ export function enforceCreator(
|
||||
share: creator.sharePercentage,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function parseCreators(creators: Creator[]): CreatorOutput[] {
|
||||
return creators.map((creator) => ({
|
||||
...creator,
|
||||
address: creator.address.toBase58(),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import {
|
||||
METAPLEX_PROGRAM_ID,
|
||||
} from "../../constants/addresses";
|
||||
import { TransactionResult } from "../../types/common";
|
||||
import { getPublicRpc } from "../../utils/urls";
|
||||
import {
|
||||
findMasterEditionV2Pda,
|
||||
GmaBuilder,
|
||||
JsonMetadata,
|
||||
Metadata,
|
||||
@@ -21,9 +23,16 @@ import {
|
||||
token,
|
||||
toMetadata,
|
||||
toMetadataAccount,
|
||||
toNftOriginalEdition,
|
||||
toOriginalEditionAccount,
|
||||
} from "@metaplex-foundation/js";
|
||||
import { getAccount, getAssociatedTokenAddress } from "@solana/spl-token";
|
||||
import { ConfirmedSignatureInfo, Connection, PublicKey } from "@solana/web3.js";
|
||||
import {
|
||||
ConfirmedSignatureInfo,
|
||||
Connection,
|
||||
ParsedAccountData,
|
||||
PublicKey,
|
||||
} from "@solana/web3.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -80,6 +89,27 @@ export class NFTHelper {
|
||||
}
|
||||
}
|
||||
|
||||
async ownerOf(nftAddress: string): Promise<string | undefined> {
|
||||
try {
|
||||
// TODO switch back to normal connection when alchemy supports getTokenLargestAccounts
|
||||
const connection = new Connection(
|
||||
getPublicRpc(this.metaplex),
|
||||
"confirmed",
|
||||
);
|
||||
const largestAccounts = await connection.getTokenLargestAccounts(
|
||||
new PublicKey(nftAddress),
|
||||
);
|
||||
const largestAccountInfo = await connection.getParsedAccountInfo(
|
||||
largestAccounts.value[0].address,
|
||||
);
|
||||
const parsedData = largestAccountInfo?.value?.data as ParsedAccountData;
|
||||
const owner = parsedData ? parsedData.parsed.info.owner : undefined;
|
||||
return owner;
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async getAll(
|
||||
collectionAddress: string,
|
||||
queryParams?: QueryAllParams,
|
||||
@@ -193,6 +223,25 @@ export class NFTHelper {
|
||||
return nfts;
|
||||
}
|
||||
|
||||
async supplyOf(nftAddress: string): Promise<number> {
|
||||
let originalEdition;
|
||||
|
||||
const originalEditionAccount = await this.metaplex
|
||||
.rpc()
|
||||
.getAccount(findMasterEditionV2Pda(new PublicKey(nftAddress)));
|
||||
|
||||
if (originalEditionAccount.exists) {
|
||||
originalEdition = toNftOriginalEdition(
|
||||
toOriginalEditionAccount(originalEditionAccount),
|
||||
);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Add one to supply to account for the master edition
|
||||
return originalEdition.supply.toNumber() + 1;
|
||||
}
|
||||
|
||||
async toNFTMetadata(
|
||||
meta:
|
||||
| Nft
|
||||
@@ -213,18 +262,24 @@ export class NFTHelper {
|
||||
if (!mint) {
|
||||
throw new Error("No mint found for NFT");
|
||||
}
|
||||
return this.toNFTMetadataResolved(mint, fullModel);
|
||||
const [owner, supply] = await Promise.all([
|
||||
this.ownerOf(mint.address.toBase58()),
|
||||
this.supplyOf(mint.address.toBase58()),
|
||||
]);
|
||||
return this.toNFTMetadataResolved(mint, owner, supply, fullModel);
|
||||
}
|
||||
|
||||
private toNFTMetadataResolved(
|
||||
private async toNFTMetadataResolved(
|
||||
mint: Mint,
|
||||
owner: string | undefined,
|
||||
supply: number,
|
||||
fullModel:
|
||||
| Nft
|
||||
| Sft
|
||||
| NftWithToken
|
||||
| SftWithToken
|
||||
| Metadata<JsonMetadata<string>>,
|
||||
): NFT {
|
||||
): Promise<NFT> {
|
||||
return {
|
||||
metadata: {
|
||||
id: mint.address.toBase58(),
|
||||
@@ -233,8 +288,8 @@ export class NFTHelper {
|
||||
symbol: fullModel.symbol,
|
||||
...fullModel.json,
|
||||
},
|
||||
owner: fullModel.updateAuthorityAddress.toBase58(),
|
||||
supply: mint.supply.basisPoints.toNumber(),
|
||||
owner: owner || PublicKey.default.toBase58(),
|
||||
supply: supply,
|
||||
type: "metaplex",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { QueryAllParams } from "../../core/schema/QueryParams";
|
||||
import { NFT, NFTMetadata, NFTMetadataOrUri } from "../../core/schema/nft";
|
||||
import {
|
||||
NFT,
|
||||
NFTMetadata,
|
||||
NFTMetadataInput,
|
||||
NFTMetadataOrUri,
|
||||
} from "../../core/schema/nft";
|
||||
import { enforceCreator } from "../classes/helpers/creators-helper";
|
||||
enforceCreator,
|
||||
parseCreators,
|
||||
} from "../classes/helpers/creators-helper";
|
||||
import { NFTHelper } from "../classes/helpers/nft-helper";
|
||||
import { TransactionResult } from "../types/common";
|
||||
import { CreatorInput } from "../types/programs";
|
||||
import { Amount, TransactionResult } from "../types/common";
|
||||
import { CreatorInput, CreatorOutput } from "../types/programs";
|
||||
import { sendMultipartTransaction } from "../utils/transactions";
|
||||
import { getNework } from "../utils/urls";
|
||||
import {
|
||||
findEditionMarkerPda,
|
||||
findMasterEditionV2Pda,
|
||||
Metaplex,
|
||||
toBigNumber,
|
||||
toNftOriginalEdition,
|
||||
toOriginalEditionAccount,
|
||||
} from "@metaplex-foundation/js";
|
||||
import { EditionMarker } from "@metaplex-foundation/mpl-token-metadata";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
/**
|
||||
* A collection of associated NFTs
|
||||
@@ -41,15 +43,7 @@ export class NFTCollection {
|
||||
public publicKey: PublicKey;
|
||||
public accountType = "nft-collection" as const;
|
||||
public get network() {
|
||||
const url = new URL(this.metaplex.connection.rpcEndpoint);
|
||||
// try this first to avoid hitting `custom` network for alchemy urls
|
||||
if (url.hostname.includes("devnet")) {
|
||||
return "devnet";
|
||||
}
|
||||
if (url.hostname.includes("mainnet")) {
|
||||
return "mainnet-beta";
|
||||
}
|
||||
return this.metaplex.cluster;
|
||||
return getNework(this.metaplex);
|
||||
}
|
||||
|
||||
constructor(
|
||||
@@ -70,18 +64,44 @@ export class NFTCollection {
|
||||
* @example
|
||||
* ```jsx
|
||||
* const metadata = await program.getMetadata();
|
||||
* console.log(metadata.name);
|
||||
* console.log(metadata);
|
||||
* ```
|
||||
*/
|
||||
async getMetadata(): Promise<NFTMetadata> {
|
||||
const metadata = await this.metaplex
|
||||
.nfts()
|
||||
.findByMint({ mintAddress: this.publicKey })
|
||||
.run();
|
||||
|
||||
const metadata = await this.getCollection();
|
||||
return (await this.nft.toNFTMetadata(metadata)).metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the creators of this program.
|
||||
* @returns program metadata
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* const creators = await program.getCreators();
|
||||
* console.log(creators);
|
||||
* ```
|
||||
*/
|
||||
async getCreators(): Promise<CreatorOutput[]> {
|
||||
const metadata = await this.getCollection();
|
||||
return parseCreators(metadata.creators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the royalty basis points for this collection
|
||||
* @returns royalty basis points
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* const royalty = await program.getRoyalty();
|
||||
* console.log(royalty);
|
||||
* ```
|
||||
*/
|
||||
async getRoyalty(): Promise<number> {
|
||||
const metadata = await this.getCollection();
|
||||
return metadata.sellerFeeBasisPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the metadata for a specific NFT
|
||||
* @param nftAddress - the mint address of the NFT to get
|
||||
@@ -155,6 +175,21 @@ export class NFTCollection {
|
||||
return this.nft.balanceOf(walletAddress, nftAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current owner of the given NFT
|
||||
* @param nftAddress - the mint address of the NFT to get the owner of
|
||||
* @returns the owner of the NFT
|
||||
* @example
|
||||
* ```jsx
|
||||
* const nftAddress = "..."
|
||||
* const owner = await program.ownerOf(nftAddress);
|
||||
* console.log(owner);
|
||||
* ```
|
||||
*/
|
||||
async ownerOf(nftAddress: string): Promise<string | undefined> {
|
||||
return this.nft.ownerOf(nftAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the supply of NFT editions minted from a specific NFT
|
||||
* @param nftAddress - the mint address of the NFT to check the supply of
|
||||
@@ -163,58 +198,11 @@ export class NFTCollection {
|
||||
* @example
|
||||
* ```jsx
|
||||
* const address = "...";
|
||||
* const supply = await program.supplyOf(addres);
|
||||
* const supply = await program.supplyOf(address);
|
||||
* ```
|
||||
*/
|
||||
async supplyOf(nftAddress: string): Promise<bigint> {
|
||||
let editionMarkerNumber = 0;
|
||||
let totalSupply = 1;
|
||||
|
||||
cursedBitwiseLogicLoop: while (true) {
|
||||
const editionMarkerAddress = findEditionMarkerPda(
|
||||
new PublicKey(nftAddress),
|
||||
toBigNumber(editionMarkerNumber * 248),
|
||||
);
|
||||
const editionMarker = await EditionMarker.fromAccountAddress(
|
||||
this.metaplex.connection,
|
||||
editionMarkerAddress,
|
||||
);
|
||||
|
||||
// WARNING: Ugly bitwise operations because of Rust :(
|
||||
const indexCap = editionMarkerNumber === 0 ? 247 : 248;
|
||||
for (let editionIndex = 0; editionIndex < indexCap; editionIndex++) {
|
||||
const ledgerIndex =
|
||||
editionMarkerNumber > 0
|
||||
? Math.floor(editionIndex / 8)
|
||||
: editionIndex < 7
|
||||
? 0
|
||||
: Math.floor((editionIndex - 7) / 8) + 1;
|
||||
const size = editionMarkerNumber === 0 && ledgerIndex === 0 ? 7 : 8;
|
||||
const shiftBase = 0b1 << (size - 1);
|
||||
|
||||
const bitmask =
|
||||
ledgerIndex === 0
|
||||
? shiftBase >> editionIndex
|
||||
: editionMarkerNumber > 0
|
||||
? shiftBase >> editionIndex % (ledgerIndex * 8)
|
||||
: ledgerIndex === 1
|
||||
? shiftBase >> (editionIndex - 7)
|
||||
: shiftBase >> (editionIndex - 7) % ((ledgerIndex - 1) * 8);
|
||||
|
||||
const editionExists =
|
||||
(editionMarker.ledger[ledgerIndex] & bitmask) !== 0;
|
||||
|
||||
if (editionExists) {
|
||||
totalSupply += 1;
|
||||
} else {
|
||||
break cursedBitwiseLogicLoop;
|
||||
}
|
||||
}
|
||||
|
||||
editionMarkerNumber++;
|
||||
}
|
||||
|
||||
return BigInt(totalSupply);
|
||||
async supplyOf(nftAddress: string): Promise<number> {
|
||||
return this.nft.supplyOf(nftAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,7 +252,7 @@ export class NFTCollection {
|
||||
* console.log(address);
|
||||
* ```
|
||||
*/
|
||||
async mint(metadata: NFTMetadataInput): Promise<string> {
|
||||
async mint(metadata: NFTMetadataOrUri): Promise<string> {
|
||||
const address = this.metaplex.identity().publicKey.toBase58();
|
||||
return this.mintTo(address, metadata);
|
||||
}
|
||||
@@ -326,25 +314,29 @@ export class NFTCollection {
|
||||
/**
|
||||
* Mint additional supply of an NFT to the connected wallet
|
||||
* @param nftAddress - the mint address to mint additional supply to
|
||||
* @param amount - the amount of additional NFTs to mint
|
||||
* @returns the mint address of the minted NFT
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* // The address of the already minted NFT
|
||||
* const nftAddress = "..."
|
||||
* // The amount of additional NFTs to mint
|
||||
* const amount = 1;
|
||||
* // Mint an additional NFT of the original NFT
|
||||
* const address = await program.mintAdditionalSupply(nftAddress);
|
||||
* const addresses = await program.mintAdditionalSupply(nftAddress, amount);
|
||||
* ```
|
||||
*/
|
||||
async mintAdditionalSupply(nftAddress: string) {
|
||||
async mintAdditionalSupply(nftAddress: string, amount: Amount) {
|
||||
const address = this.metaplex.identity().publicKey.toBase58();
|
||||
return this.mintAdditionalSupplyTo(address, nftAddress);
|
||||
return this.mintAdditionalSupplyTo(address, nftAddress, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mint additional supply of an NFT to the specified wallet
|
||||
* @param to - the address to mint the NFT to
|
||||
* @param nftAddress - the mint address to mint additional supply to
|
||||
* @param amount - the amount of NFTs to mint
|
||||
* @returns the mint address of the minted NFT
|
||||
*
|
||||
* @example
|
||||
@@ -353,23 +345,52 @@ export class NFTCollection {
|
||||
* const to = "..."
|
||||
* // The address of the already minted NFT
|
||||
* const nftAddress = "..."
|
||||
* * // The amount of additional NFTs to mint
|
||||
* const amount = 1;
|
||||
* // Mint an additional NFT of the original NFT
|
||||
* const address = await program.mintAdditionalSupplyTo(to, nftAddress);
|
||||
* const addresses = await program.mintAdditionalSupplyTo(to, nftAddress, amount);
|
||||
* ```
|
||||
*/
|
||||
async mintAdditionalSupplyTo(
|
||||
to: string,
|
||||
nftAddress: string,
|
||||
): Promise<string> {
|
||||
// TODO add quantity param
|
||||
const result = await this.metaplex
|
||||
.nfts()
|
||||
.printNewEdition({
|
||||
originalMint: new PublicKey(nftAddress),
|
||||
newOwner: new PublicKey(to),
|
||||
})
|
||||
.run();
|
||||
return result.nft.address.toBase58();
|
||||
amount: Amount,
|
||||
): Promise<string[]> {
|
||||
// need to coerce amount to number always (to make the Array(<number>) work)
|
||||
amount = typeof amount === "string" ? parseInt(amount) : amount;
|
||||
// ensure that whatever got passed actually is a number now
|
||||
invariant(
|
||||
isNaN(amount) === false,
|
||||
"amount must be possible to convert to a number",
|
||||
);
|
||||
|
||||
// Better to use metaplex functions directly then our supplyOf function for types/consistency
|
||||
const originalEditionAccount = await this.metaplex
|
||||
.rpc()
|
||||
.getAccount(findMasterEditionV2Pda(new PublicKey(nftAddress)));
|
||||
const originalEdition = toNftOriginalEdition(
|
||||
toOriginalEditionAccount(originalEditionAccount),
|
||||
);
|
||||
|
||||
const builders = await Promise.all(
|
||||
[...Array(amount).keys()].map(async (_, i) => {
|
||||
return await this.metaplex
|
||||
.nfts()
|
||||
.builders()
|
||||
.printNewEdition({
|
||||
originalSupply: toBigNumber(
|
||||
originalEdition.supply.add(toBigNumber(i)),
|
||||
),
|
||||
originalMint: new PublicKey(nftAddress),
|
||||
newOwner: new PublicKey(to),
|
||||
});
|
||||
}),
|
||||
);
|
||||
const mintAddresses = builders.map((builder) =>
|
||||
builder.getContext().mintSigner.publicKey.toBase58(),
|
||||
);
|
||||
await sendMultipartTransaction(builders, this.metaplex);
|
||||
return mintAddresses;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,22 +420,41 @@ export class NFTCollection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the settings of the collection
|
||||
* @param settings - the settings to update
|
||||
* SETTINGS
|
||||
*/
|
||||
async updateSettings(settings: { creators?: CreatorInput[] }) {
|
||||
const updateData = {
|
||||
...(settings.creators && {
|
||||
creators: enforceCreator(
|
||||
settings.creators,
|
||||
this.metaplex.identity().publicKey,
|
||||
),
|
||||
}),
|
||||
|
||||
/**
|
||||
* Update the creators of the collection
|
||||
* @param creators - the creators to update
|
||||
*/
|
||||
async updateCreators(creators: CreatorInput[]) {
|
||||
const tx = await this.metaplex
|
||||
.nfts()
|
||||
.update({
|
||||
nftOrSft: await this.getCollection(),
|
||||
creators: enforceCreator(creators, this.metaplex.identity().publicKey),
|
||||
})
|
||||
.run();
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the royalty basis points of the collection
|
||||
* @param sellerFeeBasisPoints - the royalty basis points of the collection
|
||||
*/
|
||||
async updateRoyalty(sellerFeeBasisPoints: number) {
|
||||
const tx = await this.metaplex
|
||||
.nfts()
|
||||
.update({
|
||||
nftOrSft: await this.getCollection(),
|
||||
sellerFeeBasisPoints,
|
||||
})
|
||||
.run();
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
this.metaplex.nfts().update({
|
||||
nftOrSft: await this.getCollection(),
|
||||
...updateData,
|
||||
});
|
||||
}
|
||||
|
||||
private async getCollection() {
|
||||
|
||||
@@ -5,12 +5,18 @@ import {
|
||||
NFTMetadataInput,
|
||||
} from "../../core/schema/nft";
|
||||
import { ClaimConditions } from "../classes/claim-conditions";
|
||||
import {
|
||||
enforceCreator,
|
||||
parseCreators,
|
||||
} from "../classes/helpers/creators-helper";
|
||||
import { NFTHelper } from "../classes/helpers/nft-helper";
|
||||
import { Amount, TransactionResult } from "../types/common";
|
||||
import { CreatorInput, CreatorOutput } from "../types/programs";
|
||||
import { sendMultipartTransaction } from "../utils/transactions";
|
||||
import { getNework } from "../utils/urls";
|
||||
import {
|
||||
CandyMachineItem,
|
||||
Metaplex,
|
||||
MintCandyMachineOutput,
|
||||
toBigNumber,
|
||||
} from "@metaplex-foundation/js";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
@@ -42,15 +48,7 @@ export class NFTDrop {
|
||||
public accountType = "nft-drop" as const;
|
||||
public publicKey: PublicKey;
|
||||
public get network() {
|
||||
const url = new URL(this.metaplex.connection.rpcEndpoint);
|
||||
// try this first to avoid hitting `custom` network for alchemy urls
|
||||
if (url.hostname.includes("devnet")) {
|
||||
return "devnet";
|
||||
}
|
||||
if (url.hostname.includes("mainnet")) {
|
||||
return "mainnet-beta";
|
||||
}
|
||||
return this.metaplex.cluster;
|
||||
return getNework(this.metaplex);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,9 +100,40 @@ export class NFTDrop {
|
||||
.nfts()
|
||||
.findByMint({ mintAddress: info.collectionMintAddress })
|
||||
.run();
|
||||
|
||||
return (await this.nft.toNFTMetadata(metadata)).metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the creators of this program.
|
||||
* @returns program metadata
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* const creators = await program.getCreators();
|
||||
* console.log(creators);
|
||||
* ```
|
||||
*/
|
||||
async getCreators(): Promise<CreatorOutput[]> {
|
||||
const info = await this.getCandyMachine();
|
||||
return parseCreators(info.creators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the royalty basis points for this collection
|
||||
* @returns royalty basis points
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* const royalty = await program.getRoyalty();
|
||||
* console.log(royalty);
|
||||
* ```
|
||||
*/
|
||||
async getRoyalty(): Promise<number> {
|
||||
const info = await this.getCandyMachine();
|
||||
return info.sellerFeeBasisPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the metadata for a specific NFT
|
||||
* @param nftAddress - the mint address of the NFT to get
|
||||
@@ -223,6 +252,21 @@ export class NFTDrop {
|
||||
return this.nft.balanceOf(walletAddress, nftAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current owner of the given NFT
|
||||
* @param nftAddress - the mint address of the NFT to get the owner of
|
||||
* @returns the owner of the NFT
|
||||
* @example
|
||||
* ```jsx
|
||||
* const nftAddress = "..."
|
||||
* const owner = await program.ownerOf(nftAddress);
|
||||
* console.log(owner);
|
||||
* ```
|
||||
*/
|
||||
async ownerOf(nftAddress: string): Promise<string | undefined> {
|
||||
return this.nft.ownerOf(nftAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total unclaimed supply of this drop
|
||||
* @returns the total supply
|
||||
@@ -281,6 +325,7 @@ export class NFTDrop {
|
||||
/**
|
||||
* Lazy mint NFTs to be claimed later
|
||||
* @param metadatas - The metadata of the NFTs to lazy mint
|
||||
* @param options
|
||||
* @returns the transaction result of the lazy mint
|
||||
*
|
||||
* @example
|
||||
@@ -326,9 +371,7 @@ export class NFTDrop {
|
||||
batches.push(items.splice(0, LAZY_MINT_BATCH_SIZE));
|
||||
}
|
||||
|
||||
const block = await this.metaplex.connection.getLatestBlockhash();
|
||||
|
||||
const txns = batches.map((batch, i) =>
|
||||
const builders = batches.map((batch, i) =>
|
||||
this.metaplex
|
||||
.candyMachines()
|
||||
.builders()
|
||||
@@ -339,38 +382,9 @@ export class NFTDrop {
|
||||
index: toBigNumber(
|
||||
i * LAZY_MINT_BATCH_SIZE + candyMachine.itemsLoaded.toNumber(),
|
||||
),
|
||||
})
|
||||
.setTransactionOptions({
|
||||
blockhash: block.blockhash,
|
||||
feePayer: this.metaplex.identity().publicKey,
|
||||
lastValidBlockHeight: block.lastValidBlockHeight,
|
||||
})
|
||||
.setFeePayer(this.metaplex.identity())
|
||||
.toTransaction(),
|
||||
}),
|
||||
);
|
||||
|
||||
// make the connected wallet sign both candyMachine + registry transactions
|
||||
const signedTx = await this.metaplex.identity().signAllTransactions(txns);
|
||||
|
||||
// send the signed transactions
|
||||
const signatures = await Promise.all(
|
||||
signedTx.map((tx) =>
|
||||
this.metaplex.connection.sendRawTransaction(tx.serialize()),
|
||||
),
|
||||
);
|
||||
|
||||
// wait for confirmations in parallel
|
||||
const confirmations = await Promise.all(
|
||||
signatures.map((sig) => {
|
||||
return this.metaplex.rpc().confirmTransaction(sig);
|
||||
}),
|
||||
);
|
||||
|
||||
if (confirmations.length === 0) {
|
||||
throw new Error("Transaction failed");
|
||||
}
|
||||
|
||||
return signatures.map((signature) => ({ signature }));
|
||||
return await sendMultipartTransaction(builders, this.metaplex);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,20 +418,25 @@ export class NFTDrop {
|
||||
* console.log("Claimed NFT at address", claimedAddresses[0]);
|
||||
* ```
|
||||
*/
|
||||
async claimTo(receiverAddress: string, quantity: Amount): Promise<string[]> {
|
||||
async claimTo(receiverAddress: string, amount: Amount): Promise<string[]> {
|
||||
const candyMachine = await this.getCandyMachine();
|
||||
await this.claimConditions.assertCanClaimable(Number(quantity));
|
||||
const results: MintCandyMachineOutput[] = [];
|
||||
// has to claim sequentially
|
||||
for (let i = 0; i < quantity; i++) {
|
||||
results.push(
|
||||
await this.metaplex
|
||||
await this.claimConditions.assertCanClaimable(Number(amount));
|
||||
const builders = await Promise.all(
|
||||
[...Array(amount).keys()].map(async () => {
|
||||
return await this.metaplex
|
||||
.candyMachines()
|
||||
.mint({ candyMachine, newOwner: new PublicKey(receiverAddress) })
|
||||
.run(),
|
||||
);
|
||||
}
|
||||
return results.map((result) => result.nft.address.toBase58());
|
||||
.builders()
|
||||
.mint({
|
||||
candyMachine,
|
||||
newOwner: new PublicKey(receiverAddress),
|
||||
});
|
||||
}),
|
||||
);
|
||||
const mintAddresses = builders.map((builder) =>
|
||||
builder.getContext().mintSigner.publicKey.toBase58(),
|
||||
);
|
||||
await sendMultipartTransaction(builders, this.metaplex);
|
||||
return mintAddresses;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -450,6 +469,40 @@ export class NFTDrop {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the creators of the collection
|
||||
* @param creators - the creators to update
|
||||
*/
|
||||
async updateCreators(creators: CreatorInput[]) {
|
||||
const tx = await this.metaplex
|
||||
.candyMachines()
|
||||
.update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
creators: enforceCreator(creators, this.metaplex.identity().publicKey),
|
||||
})
|
||||
.run();
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the royalty basis points of the collection
|
||||
* @param sellerFeeBasisPoints - the royalty basis points of the collection
|
||||
*/
|
||||
async updateRoyalty(sellerFeeBasisPoints: number) {
|
||||
const tx = await this.metaplex
|
||||
.candyMachines()
|
||||
.update({
|
||||
candyMachine: await this.getCandyMachine(),
|
||||
sellerFeeBasisPoints,
|
||||
})
|
||||
.run();
|
||||
return {
|
||||
signature: tx.response.signature,
|
||||
};
|
||||
}
|
||||
|
||||
private async getCandyMachine() {
|
||||
return this.metaplex
|
||||
.candyMachines()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CurrencyValue, TokenMetadata } from "../../core/schema/token";
|
||||
import { Amount, AmountSchema, TransactionResult } from "../types/common";
|
||||
import { toCurrencyValue } from "../utils/token";
|
||||
import { getNework } from "../utils/urls";
|
||||
import {
|
||||
findMetadataPda,
|
||||
Metaplex,
|
||||
@@ -36,15 +37,7 @@ export class Token {
|
||||
public accountType = "token" as const;
|
||||
public publicKey: PublicKey;
|
||||
public get network() {
|
||||
const url = new URL(this.metaplex.connection.rpcEndpoint);
|
||||
// try this first to avoid hitting `custom` network for alchemy urls
|
||||
if (url.hostname.includes("devnet")) {
|
||||
return "devnet";
|
||||
}
|
||||
if (url.hostname.includes("mainnet")) {
|
||||
return "mainnet-beta";
|
||||
}
|
||||
return this.metaplex.cluster;
|
||||
return getNework(this.metaplex);
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AmountSchema, FileOrBufferOrStringSchema } from "../common";
|
||||
import { Creator } from "@metaplex-foundation/js";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
@@ -64,6 +65,13 @@ export type TokenMetadataInput = z.input<typeof TokenMetadataInputSchema>;
|
||||
*/
|
||||
export type CreatorInput = z.input<typeof CreatorInputSchema>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type CreatorOutput = Omit<Creator, "address"> & {
|
||||
readonly address: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { TransactionResult } from "../types/common";
|
||||
import {
|
||||
getSignerHistogram,
|
||||
Metaplex,
|
||||
TransactionBuilder,
|
||||
} from "@metaplex-foundation/js";
|
||||
|
||||
export async function sendMultipartTransaction(
|
||||
builders: TransactionBuilder[],
|
||||
metaplex: Metaplex,
|
||||
): Promise<TransactionResult[]> {
|
||||
const block = await metaplex.connection.getLatestBlockhash();
|
||||
const txns = builders.map((builder) => {
|
||||
const builderTx = builder
|
||||
.setTransactionOptions({
|
||||
blockhash: block.blockhash,
|
||||
feePayer: metaplex.identity().publicKey,
|
||||
lastValidBlockHeight: block.lastValidBlockHeight,
|
||||
})
|
||||
.setFeePayer(metaplex.identity());
|
||||
|
||||
const dropSigners = [metaplex.identity(), ...builderTx.getSigners()];
|
||||
const { keypairs } = getSignerHistogram(dropSigners);
|
||||
const tx = builderTx.toTransaction();
|
||||
|
||||
if (keypairs.length > 0) {
|
||||
tx.partialSign(...keypairs);
|
||||
}
|
||||
return tx;
|
||||
});
|
||||
|
||||
// make the connected wallet sign all transactions
|
||||
const signedTx = await metaplex.identity().signAllTransactions(txns);
|
||||
|
||||
// send the signed transactions
|
||||
let signatures = [];
|
||||
for (const tx of signedTx) {
|
||||
const signature = await metaplex.connection.sendRawTransaction(
|
||||
tx.serialize(),
|
||||
);
|
||||
signatures.push(signature);
|
||||
}
|
||||
|
||||
// wait for confirmations in parallel
|
||||
const confirmations = await Promise.all(
|
||||
signatures.map((sig) => {
|
||||
return metaplex.rpc().confirmTransaction(sig);
|
||||
}),
|
||||
);
|
||||
|
||||
if (confirmations.length === 0) {
|
||||
throw new Error("Transaction failed");
|
||||
}
|
||||
|
||||
return signatures.map((signature) => ({ signature }));
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Network } from "../types/index";
|
||||
import { Metaplex } from "@metaplex-foundation/js";
|
||||
import { Cluster, clusterApiUrl } from "@solana/web3.js";
|
||||
|
||||
/**
|
||||
@@ -24,3 +25,26 @@ export function getUrlForNetwork(network: Network) {
|
||||
return network;
|
||||
}
|
||||
}
|
||||
|
||||
export function getNework(metaplex: Metaplex) {
|
||||
const url = new URL(metaplex.connection.rpcEndpoint);
|
||||
// try this first to avoid hitting `custom` network for alchemy urls
|
||||
if (url.hostname.includes("devnet")) {
|
||||
return "devnet";
|
||||
}
|
||||
if (url.hostname.includes("mainnet")) {
|
||||
return "mainnet-beta";
|
||||
}
|
||||
return metaplex.cluster;
|
||||
}
|
||||
|
||||
export function getPublicRpc(metaplex: Metaplex) {
|
||||
const url = new URL(metaplex.connection.rpcEndpoint);
|
||||
if (url.hostname.includes("devnet")) {
|
||||
return "https://api.devnet.solana.com";
|
||||
}
|
||||
if (url.hostname.includes("mainnet")) {
|
||||
return "https://api.mainnet-beta.solana.com";
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
@@ -639,7 +639,9 @@ describe("Edition Drop Contract", async () => {
|
||||
snapshot: [samWallet.address],
|
||||
},
|
||||
]);
|
||||
const conditions = await bdContract.claimConditions.getAll(0);
|
||||
const conditions = await bdContract.claimConditions.getAll(0, {
|
||||
withAllowList: true,
|
||||
});
|
||||
assert.lengthOf(conditions, 1);
|
||||
invariant(conditions[0].snapshot);
|
||||
expect(conditions[0].snapshot[0].address).to.eq(samWallet.address);
|
||||
|
||||
@@ -90,6 +90,21 @@ describe("Events", async () => {
|
||||
dropContract.events.removeAllListeners();
|
||||
});
|
||||
|
||||
it("Should emit deploy events", async () => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
expect(sdk.deployer.events).to.not.be.undefined;
|
||||
let txAddress = "";
|
||||
sdk.deployer.events?.addDeployListener((event) => {
|
||||
txAddress = event.contractAddress;
|
||||
});
|
||||
const address = await sdk.deployer.deployMarketplace({
|
||||
name: "Marketplace",
|
||||
});
|
||||
// Wait for the async listener to get called
|
||||
await new Promise((res) => setTimeout(res, 2000));
|
||||
expect(txAddress).to.equal(address);
|
||||
});
|
||||
|
||||
it("should emit Contract events", async () => {
|
||||
const events: ContractEvent[] = [];
|
||||
const remove = dropContract.events.addEventListener(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import { ThirdwebSDK } from "../../src/evm";
|
||||
import { assert, expect } from "chai";
|
||||
import { ethers, Wallet } from "ethers";
|
||||
|
||||
const RPC_URL = "https://rpc-mumbai.maticvigil.com/";
|
||||
@@ -47,4 +48,43 @@ describe("Gasless Forwarder", async () => {
|
||||
|
||||
await pack.open(0, 0);
|
||||
});
|
||||
|
||||
it.skip("should mint batch via multicall tx with relayer", async () => {
|
||||
const NFT_COLLECTION_ADDRESS = "0x24A5aB2878B63716B001aa9AbE816c2662192B12";
|
||||
const provider = ethers.getDefaultProvider(RPC_URL);
|
||||
// const wallet = Wallet.createRandom().connect(provider);
|
||||
const wallet = new Wallet("<PRIVATE KEY>", provider);
|
||||
const sdk = new ThirdwebSDK(wallet, {
|
||||
gasless: {
|
||||
openzeppelin: {
|
||||
relayerUrl: "<RELAYER URL>",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const nftCollection = await sdk.getContract(
|
||||
NFT_COLLECTION_ADDRESS,
|
||||
"nft-collection",
|
||||
);
|
||||
|
||||
const metas = [
|
||||
{
|
||||
name: "Test5",
|
||||
},
|
||||
{
|
||||
name: "Test6",
|
||||
},
|
||||
];
|
||||
|
||||
const batch = await nftCollection.mintBatch(metas);
|
||||
|
||||
assert.lengthOf(batch, 2);
|
||||
|
||||
for (const meta of metas) {
|
||||
const nft = batch.find(
|
||||
async (n) => (await n.data()).metadata.name === meta.name,
|
||||
);
|
||||
assert.isDefined(nft);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
NFTDrop,
|
||||
TokenInitializer,
|
||||
} from "../../src/evm";
|
||||
import { ShardedMerkleTree } from "../../src/evm/common/sharded-merkle-tree";
|
||||
import { expectError, sdk, signers, storage } from "./before-setup";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
|
||||
@@ -109,7 +110,9 @@ describe("NFT Drop Contract", async () => {
|
||||
snapshot: [samWallet.address],
|
||||
},
|
||||
]);
|
||||
const conditions = await dropContract.claimConditions.getAll();
|
||||
const conditions = await dropContract.claimConditions.getAll({
|
||||
withAllowList: true,
|
||||
});
|
||||
assert.lengthOf(conditions, 1);
|
||||
invariant(conditions[0].snapshot);
|
||||
expect(conditions[0].snapshot[0].address).to.eq(samWallet.address);
|
||||
@@ -386,9 +389,11 @@ describe("NFT Drop Contract", async () => {
|
||||
ethers.utils.solidityKeccak256(["address", "uint256"], [leaf, 0]),
|
||||
);
|
||||
|
||||
const actualProof = snapshot.snapshot.claims.find(
|
||||
(c) => c.address === leaf,
|
||||
const smt = await ShardedMerkleTree.fromUri(
|
||||
snapshot.snapshotUri,
|
||||
storage,
|
||||
);
|
||||
const actualProof = await smt?.getProof(leaf);
|
||||
assert.isDefined(actualProof);
|
||||
expect(actualProof?.proof).to.include.ordered.members(expectedProof);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Token,
|
||||
TokenInitializer,
|
||||
} from "../../src/evm";
|
||||
import { ShardedMerkleTree } from "../../src/evm/common/sharded-merkle-tree";
|
||||
import { expectError, sdk, signers, storage } from "./before-setup";
|
||||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
|
||||
import { assert, expect } from "chai";
|
||||
@@ -481,8 +482,9 @@ describe("Signature drop tests", async () => {
|
||||
snapshot: [samWallet.address],
|
||||
},
|
||||
]);
|
||||
const conditions =
|
||||
await signatureDropContract.claimConditions.getActive();
|
||||
const conditions = await signatureDropContract.claimConditions.getActive({
|
||||
withAllowList: true,
|
||||
});
|
||||
invariant(conditions.snapshot);
|
||||
expect(conditions.snapshot[0].address).to.eq(samWallet.address);
|
||||
});
|
||||
@@ -749,9 +751,11 @@ describe("Signature drop tests", async () => {
|
||||
ethers.utils.solidityKeccak256(["address", "uint256"], [leaf, 0]),
|
||||
);
|
||||
|
||||
const actualProof = snapshot.snapshot.claims.find(
|
||||
(c) => c.address === leaf,
|
||||
const smt = await ShardedMerkleTree.fromUri(
|
||||
snapshot.snapshotUri,
|
||||
storage,
|
||||
);
|
||||
const actualProof = await smt?.getProof(leaf);
|
||||
assert.isDefined(actualProof);
|
||||
expect(actualProof?.proof).to.include.ordered.members(expectedProof);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user