Compare commits

...
Author SHA1 Message Date
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
b4e005be7f Version Packages (#401)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-11-22 14:31:57 -08:00
Nacho IacovinoandGitHub ccb7db4873 [Solana + React] Update hooks for creators/royalties (#405) 2022-11-22 19:22:51 +00:00
Jonas DanielsandGitHub 9151aa7dc8 [SDK] - expose getDefaultTrustedForwarders helper fn (#404) 2022-11-22 03:35:01 +00:00
5a01c537e3 [DO NOT MERGE] Update to Metaplex v0.17 (#305)
Co-authored-by: Jonas Daniels <[email protected]>
2022-11-21 16:14:39 -08:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
f09c153ff7 Version Packages (#391)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-11-21 15:22:56 -08:00
Nacho IacovinoandGitHub 15431621fd [React] Remove console.log (#400) 2022-11-21 20:03:32 +00:00
Jonas DanielsandGitHub 887af24450 [SDK] - fix error handling for nullish error values (#399) 2022-11-21 18:52:02 +00:00
Jonas DanielsandGitHub 15514f190a [SDK] - add default value option to constructorParams schema (#398) 2022-11-21 18:28:18 +00:00
Adam MajmudarandGitHub 72589ec234 Call user callback in getUser (#397) 2022-11-21 17:46:18 +00:00
Jonas DanielsandGitHub f8bceec10c fix solana claim condition native token (#395) 2022-11-19 05:36:42 +00:00
Adam MajmudarandGitHub 7c0744a006 Add support for total supply and pagination on get all (#388) 2022-11-18 02:21:32 +00:00
Nacho IacovinoandGitHub 846022e2cf [React] Fix unlimited price (#392) 2022-11-17 22:25:25 +00:00
Joaquim VergesandGitHub 96bd92e724 [SDK] Expose getAllDetectedFeatures(ABI) helper function (#390) 2022-11-17 14:07:37 -08:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
c2b6d977ed Version Packages (#383)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-11-16 14:02:50 -08:00
7cb8e59be4 [SDK] Add new Drop contract implementation addresses (#364)
Co-authored-by: Jonas Daniels <[email protected]>
Co-authored-by: Adam Majmudar <[email protected]>
2022-11-16 21:52:01 +00:00
Adam MajmudarandGitHub 04a47e8971 Add retroactive creator and royalty update option (#385) 2022-11-16 21:37:45 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8a6a97a3e9 Bump loader-utils from 2.0.3 to 2.0.4 (#384)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-16 19:51:40 +00:00
Jonas DanielsandGitHub 612bcdd16f [REACT] - update gnosis connector (#386) 2022-11-16 19:34:27 +00:00
Jonas DanielsandGitHub 0ed77d2c9e [SDK] - add constructorParams to schema for better release -> deploy (#382) 2022-11-15 20:47:54 +00:00
78 changed files with 5519 additions and 4107 deletions
+10
View File
@@ -1,5 +1,15 @@
# @thirdweb-dev/auth
## 2.0.32
## 2.0.31
### Patch Changes
- [#397](https://github.com/thirdweb-dev/js/pull/397) [`72589ec`](https://github.com/thirdweb-dev/js/commit/72589ec234d86939b67b0ca8b3b789f2e9c54cc2) Thanks [@adam-maj](https://github.com/adam-maj)! - Call user callback in getUser
## 2.0.30
## 2.0.29
## 2.0.28
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/auth",
"version": "2.0.29",
"version": "2.0.32",
"exports": {
"./next": {
"module": "./next/dist/thirdweb-dev-auth-next.esm.js",
@@ -73,7 +73,7 @@
"eslint-plugin-inclusive-language": "^2.2.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-tsdoc": "^0.2.16",
"ethers": "^5.7.1",
"ethers": "^5.7.2",
"express": "^4.18.1",
"next": "^12.2.0",
"next-auth": "^4.10.3",
+7 -1
View File
@@ -70,7 +70,13 @@ export function ThirdwebAuth(cfg: ThirdwebAuthConfig) {
if (token) {
try {
const address = await sdk.auth.authenticate(domain, token);
user = { address };
let data = {};
if (ctx.callbacks?.user) {
data = await ctx.callbacks.user(address);
}
user = { ...data, address };
} catch {
// No-op
}
+7 -1
View File
@@ -70,7 +70,13 @@ export function ThirdwebAuth(cfg: ThirdwebAuthConfig) {
if (token) {
try {
const address = await sdk.auth.authenticate(domain, token);
user = { address };
let data = {};
if (ctx.callbacks?.user) {
data = await ctx.callbacks.user(address);
}
user = { ...data, address };
} catch {
// No-op
}
+21
View File
@@ -1,5 +1,26 @@
# thirdweb
## 0.8.18
### Patch Changes
- Updated dependencies [[`ccb7db4`](https://github.com/thirdweb-dev/js/commit/ccb7db48739b8dddcb2c032b3b6e3e5200485715), [`9151aa7`](https://github.com/thirdweb-dev/js/commit/9151aa7dc80754a7aa4fd3f6ce0207bccbb6451f), [`5a01c53`](https://github.com/thirdweb-dev/js/commit/5a01c537e30bd7ef562aad1012413990de6faedf)]:
- @thirdweb-dev/sdk@3.6.2
## 0.8.17
### Patch Changes
- Updated dependencies [[`15514f1`](https://github.com/thirdweb-dev/js/commit/15514f190a844d809ae5b1edb580aab46a682485), [`7c0744a`](https://github.com/thirdweb-dev/js/commit/7c0744a006e78987ad8b271b2c64f4bac7759510), [`f8bceec`](https://github.com/thirdweb-dev/js/commit/f8bceec10c60b30dc3d6f45926e2a9e018202578), [`887af24`](https://github.com/thirdweb-dev/js/commit/887af24450e316c2ea390cd49c0563cebe793c0f), [`96bd92e`](https://github.com/thirdweb-dev/js/commit/96bd92e724ec7d880d23348071b26ab8c21dd868)]:
- @thirdweb-dev/sdk@3.6.1
## 0.8.16
### Patch Changes
- Updated dependencies [[`7cb8e59`](https://github.com/thirdweb-dev/js/commit/7cb8e59be48dc923316eb36ab43a1bef7364f6b1), [`0ed77d2`](https://github.com/thirdweb-dev/js/commit/0ed77d2c9e7526444ef660aab82a950865234aa6), [`04a47e8`](https://github.com/thirdweb-dev/js/commit/04a47e8971d28da2532ebd9db824eeb459c17bd4)]:
- @thirdweb-dev/sdk@3.6.0
## 0.8.15
### Patch Changes
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "thirdweb",
"main": "dist/cli/index.js",
"version": "0.8.15",
"version": "0.8.18",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -10,7 +10,7 @@
},
"devDependencies": {
"@gmrchk/cli-testing-library": "^0.1.2",
"@types/async-retry": "1.3.0",
"@types/async-retry": "^1.3.0",
"@types/cross-spawn": "^6.0.2",
"@types/inquirer": "^8.2.1",
"@types/jest": "^29.0.0",
@@ -33,13 +33,13 @@
"async-retry": "^1.3.3",
"commander": "^9.1.0",
"enquirer": "^2.3.6",
"ethers": "^5.7.0",
"ethers": "^5.7.2",
"got": "11.8.5",
"inquirer": "^8.2.3",
"open": "^8.4.0",
"prompts": "^2.4.2",
"rimraf": "^3.0.2",
"solc": "0.8.13",
"solc": "0.8.17",
"tar": "^6.1.11",
"tslog": "^3.3.3",
"update-notifier": "^6.0.2",
+1 -1
View File
@@ -13,7 +13,7 @@
"@babel/preset-typescript": "^7.18.6",
"@preconstruct/cli": "^2.2.1",
"@typechain/ethers-v5": "10.0.0",
"ethers": "^5.7.0",
"ethers": "^5.7.2",
"typechain": "^8.1.0",
"typescript": "^4.7.4"
},
+1 -1
View File
@@ -6,7 +6,7 @@
"dependencies": {
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"eslint-config-next": "^12.0.8",
"eslint-config-next": "12.3.1",
"eslint-config-prettier": "^8.3.0",
"eslint-config-turbo": "latest",
"eslint-plugin-react": "^7.28.0"
+22
View File
@@ -1,5 +1,27 @@
# @thirdweb-dev/react
## 3.6.2
### Patch Changes
- [#405](https://github.com/thirdweb-dev/js/pull/405) [`ccb7db4`](https://github.com/thirdweb-dev/js/commit/ccb7db48739b8dddcb2c032b3b6e3e5200485715) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Add updateAll param to creators and royalty update hooks for Solana
## 3.6.1
### Patch Changes
- [#392](https://github.com/thirdweb-dev/js/pull/392) [`846022e`](https://github.com/thirdweb-dev/js/commit/846022e2cfb12b846aae029b85899fb72fd2c9ad) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Fix unlimited price for useActiveClaimConditionForWallet
- [#388](https://github.com/thirdweb-dev/js/pull/388) [`7c0744a`](https://github.com/thirdweb-dev/js/commit/7c0744a006e78987ad8b271b2c64f4bac7759510) Thanks [@adam-maj](https://github.com/adam-maj)! - Add support for total supply and pagination on get all
## 3.6.0
### Patch Changes
- [#386](https://github.com/thirdweb-dev/js/pull/386) [`612bcdd`](https://github.com/thirdweb-dev/js/commit/612bcdd16fc242d8b04bc70d609e44b0df47ed3f) Thanks [@jnsdls](https://github.com/jnsdls)! - [Gnosis Connector] - switch to safe.global hostname & add support for bsc and optimism chains
- [#364](https://github.com/thirdweb-dev/js/pull/364) [`7cb8e59`](https://github.com/thirdweb-dev/js/commit/7cb8e59be48dc923316eb36ab43a1bef7364f6b1) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Add experimental `useActiveClaimConditionForWallet()` hook
## 3.5.2
### Patch Changes
+4 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react",
"version": "3.5.2",
"version": "3.6.2",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
@@ -62,7 +62,7 @@
},
"sideEffects:": false,
"devDependencies": {
"@babel/core": "^7.18.10",
"@babel/core": "^7.19.3",
"@babel/preset-env": "^7.18.10",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
@@ -73,6 +73,7 @@
"@preconstruct/cli": "^2.2.1",
"@solana/wallet-adapter-base": "^0.9.17",
"@solana/wallet-adapter-react": "^0.15.19",
"@solana/wallet-adapter-wallets": "^0.19.0",
"@solana/web3.js": "^1.62.0",
"@thirdweb-dev/sdk": "*",
"@types/color": "^3.0.3",
@@ -87,7 +88,7 @@
"eslint-plugin-inclusive-language": "^2.2.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-tsdoc": "^0.2.16",
"ethers": "^5.7.1",
"ethers": "^5.7.2",
"prettier": "^2.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -9,16 +9,21 @@ import {
useContext as useWagmiContext,
} from "wagmi";
// exerpt from https://docs.gnosis-safe.io/backend/available-services
const CHAIN_ID_TO_GNOSIS_SERVER_URL = {
// mainnet
1: "https://safe-transaction.mainnet.gnosis.io",
1: "https://safe-transaction-mainnet.safe.global",
// avalanche
43114: "https://safe-transaction.avalanche.gnosis.io",
43114: "https://safe-transaction-avalanche.safe.global",
// polygon
137: "https://safe-transaction.polygon.gnosis.io",
137: "https://safe-transaction-polygon.safe.global",
// goerli
5: "https://safe-transaction.goerli.gnosis.io",
};
5: "https://safe-transaction-goerli.safe.global",
// bsc
56: "https://safe-transaction-bsc.safe.global",
// optimism
10: "https://safe-transaction-optimism.safe.global",
} as const;
export interface GnosisConnectorArguments {
safeAddress: string;
@@ -28,6 +33,8 @@ export interface GnosisConnectorArguments {
const __IS_SERVER__ = typeof window === "undefined";
export class GnosisSafeConnector extends Connector {
static supportedChains = Object.keys(CHAIN_ID_TO_GNOSIS_SERVER_URL);
public supportedChains = GnosisSafeConnector.supportedChains;
id = "gnosis";
ready = __IS_SERVER__;
name = "Gnosis Safe";
@@ -60,6 +67,11 @@ export class GnosisSafeConnector extends Connector {
};
}
public isChainSupported(chainId: string | number) {
const id = normalizeChainId(chainId);
return !this.isChainUnsupported(id);
}
private async createSafeSigner() {
const signer = await this.previousConnector?.getSigner();
const safeAddress = this.config?.safeAddress;
@@ -2,7 +2,7 @@ import {
RequiredParam,
requiredParamInvariant,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../../providers/base";
import { useSDK, useSDKChainId } from "../../providers/base";
import { getErcs, DropContract, WalletAddress } from "../../types";
import {
cacheKeys,
@@ -14,8 +14,12 @@ import {
ClaimCondition,
ClaimConditionFetchOptions,
ClaimConditionInput,
SnapshotEntryWithProof,
fetchCurrencyValue,
convertToReadableQuantity,
fetchCurrencyMetadata,
} from "@thirdweb-dev/sdk";
import { BigNumberish } from "ethers";
import { BigNumberish, constants, utils } from "ethers";
import invariant from "tiny-invariant";
/**
@@ -129,7 +133,7 @@ export function useActiveClaimCondition(
* @twfeature ERC721ClaimableWithConditions | ERC1155ClaimableWithConditions | ERC20ClaimableWithConditions
* @beta
*/
export function useClaimerProofs(
export function useClaimerProofs(
contract: RequiredParam<DropContract>,
claimerAddress: string,
tokenId?: BigNumberish,
@@ -149,13 +153,23 @@ export function useActiveClaimCondition(
tokenId,
"tokenId is required for ERC1155 claim conditions",
);
return erc1155.claimConditions.getClaimerProofs(tokenId, claimerAddress, claimConditionId);
return erc1155.claimConditions.getClaimerProofs(
tokenId,
claimerAddress,
claimConditionId,
);
}
if (erc721) {
return erc721.claimConditions.getClaimerProofs(claimerAddress, claimConditionId);
return erc721.claimConditions.getClaimerProofs(
claimerAddress,
claimConditionId,
);
}
if (erc20) {
return erc20.claimConditions.getClaimerProofs(claimerAddress, claimConditionId);
return erc20.claimConditions.getClaimerProofs(
claimerAddress,
claimConditionId,
);
}
throw new Error("Contract must be ERC721, ERC1155 or ERC20");
},
@@ -305,6 +319,136 @@ export function useClaimIneligibilityReasons(
);
}
/**
* Use this to check the claim condition for a given wallet address (including checking for overrides that may exist for that given wallet address).
*
* @param contract - an instance of a contract that extends the ERC20, ERC721 or ERC1155 spec and implements the `claimConditions` extension.
* @param walletAddress - the wallet address to check the active claim condition for
* @param tokenId - the id of the token to fetch the claim conditions for (if the contract is an ERC1155 contract)
* @returns the active claim conditon for the wallet address or null if there is no active claim condition
*
* @beta
*/
export function useActiveClaimConditionForWallet(
contract: RequiredParam<DropContract>,
walletAddress: RequiredParam<WalletAddress>,
tokenId?: BigNumberish,
) {
const sdk = useSDK();
const contractAddress = contract?.getAddress();
const { erc1155, erc721, erc20 } = getErcs(contract);
return useQueryWithNetwork<ClaimCondition | null>(
cacheKeys.extensions.claimConditions.useActiveClaimConditionForWallet(
contractAddress,
walletAddress || "_NO_WALLET_",
tokenId,
),
async () => {
// if we do not have a walletAddress just do the same logic as basic useClaimCondition
if (!walletAddress) {
if (erc1155) {
requiredParamInvariant(
tokenId,
"tokenId is required for ERC1155 claim conditions",
);
return erc1155.claimConditions.getActive(tokenId);
}
if (erc721) {
return erc721.claimConditions.getActive();
}
if (erc20) {
return erc20.claimConditions.getActive();
}
throw new Error("Contract must be ERC721, ERC1155 or ERC20");
}
invariant(sdk, "sdk is required");
let activeGeneralClaimCondition: ClaimCondition | null = null;
let claimerProofForWallet: SnapshotEntryWithProof | null = null;
if (erc1155) {
requiredParamInvariant(tokenId, "tokenId is required for ERC1155");
const [cc, cp] = await Promise.all([
erc1155.claimConditions.getActive(tokenId),
erc1155.claimConditions.getClaimerProofs(tokenId, walletAddress),
]);
activeGeneralClaimCondition = cc;
claimerProofForWallet = cp;
}
if (erc721) {
const [cc, cp] = await Promise.all([
erc721.claimConditions.getActive(),
erc721.claimConditions.getClaimerProofs(walletAddress),
]);
activeGeneralClaimCondition = cc;
claimerProofForWallet = cp;
}
if (erc20) {
const [cc, cp] = await Promise.all([
erc20.claimConditions.getActive(),
erc20.claimConditions.getClaimerProofs(walletAddress),
]);
activeGeneralClaimCondition = cc;
claimerProofForWallet = cp;
}
// if there is no active claim condition nothing matters, return null
if (!activeGeneralClaimCondition) {
return null;
}
// if there is no claimer proof then just fall back to the active general claim condition
if (!claimerProofForWallet) {
return activeGeneralClaimCondition;
}
const { maxClaimable, currencyAddress, price } = claimerProofForWallet;
const currencyWithOverride =
currencyAddress || activeGeneralClaimCondition.currencyAddress;
const currencyMetadata = await fetchCurrencyMetadata(
sdk.getProvider(),
currencyWithOverride,
);
const normalizedPrize = price
? price === "unlimited"
? constants.MaxUint256
: utils.parseUnits(price, currencyMetadata.decimals)
: null;
const priceWithOverride =
normalizedPrize || activeGeneralClaimCondition.price;
const maxClaimableWithOverride =
// have to transform this the same way that claim conditions do it in SDK
convertToReadableQuantity(maxClaimable, currencyMetadata.decimals) ||
activeGeneralClaimCondition.maxClaimablePerWallet;
const currencyValueWithOverride = await fetchCurrencyValue(
sdk.getProvider(),
currencyWithOverride,
priceWithOverride,
);
return {
// inherit the entire claim condition
...activeGeneralClaimCondition,
// overwrite all keys that could be changed based on overwrites
maxClaimablePerWallet: maxClaimableWithOverride,
price: priceWithOverride,
currency: currencyWithOverride,
currencyAddress: currencyWithOverride,
currencyMetadata: currencyValueWithOverride,
};
},
{
// Checks that happen here:
// 1. if the contract is based on ERC1155 contract => tokenId cannot be `undefined`
// 2. if the contract is NOT based on ERC1155 => we have to have either an ERC721 or ERC20 contract
enabled: erc1155 ? tokenId !== undefined : !!erc721 || !!erc20,
},
);
}
/** **********************/
/** WRITE HOOKS **/
/** **********************/
@@ -313,6 +313,23 @@ export const cacheKeys = {
]
: ["claimConditions", "getIneligibilityReasons", params],
),
// combinations of queries cache keys
useActiveClaimConditionForWallet: (
contractAddress: RequiredParam<ContractAddress>,
walletAddress: WalletAddress,
tokenId?: BigNumberish,
) =>
createContractCacheKey(
contractAddress,
tokenId
? [
"claimConditions",
"useActiveClaimConditionForWallet",
{ tokenId, walletAddress },
,
]
: ["claimConditions", "getIneligibilityReasons", { walletAddress }],
),
},
// primary sale contracts
+12 -4
View File
@@ -4,17 +4,22 @@ import {
RequiredParam,
} from "../../../core/query-utils/required-param";
import { useQuery } from "@tanstack/react-query";
import { QueryAllParams } from "@thirdweb-dev/sdk";
import type { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
export function nftGetAllQuery(
program: RequiredParam<NFTCollection | NFTDrop>,
queryParams?: QueryAllParams,
) {
return {
queryKey: createSOLProgramQueryKey(program, ["getAll"] as const),
queryKey: createSOLProgramQueryKey(program, [
"getAll",
queryParams,
] as const),
queryFn: async () => {
requiredParamInvariant(program, "program is required");
return await program.getAll();
return program.getAll(queryParams);
},
enabled: !!program,
};
@@ -40,6 +45,9 @@ export function nftGetAllQuery(
*
* @public
*/
export function useNFTs(program: RequiredParam<NFTCollection | NFTDrop>) {
return useQuery(nftGetAllQuery(program));
export function useNFTs(
program: RequiredParam<NFTCollection | NFTDrop>,
queryParams?: QueryAllParams,
) {
return useQuery(nftGetAllQuery(program, queryParams));
}
@@ -0,0 +1,47 @@
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
import {
requiredParamInvariant,
RequiredParam,
} from "../../../core/query-utils/required-param";
import { useQuery } from "@tanstack/react-query";
import type { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
export function nftTotalSupplyQuery(
program: RequiredParam<NFTCollection | NFTDrop>,
) {
return {
queryKey: createSOLProgramQueryKey(program, ["supply"] as const),
queryFn: async () => {
requiredParamInvariant(program, "program is required");
return program.totalSupply();
},
enabled: !!program,
};
}
/**
* Get the totaly supply of NFTs on the program
* @param program - The NFT program to get the total supply of
*
* @example
* ```jsx
* import { useProgram, useTotalSupply } from "@thirdweb-dev/react/solana";
*
* export default function Component() {
* const { program } = useProgram("{{program_address}}");
* const { data: supply, isLoading } = useTotalSupply(program);
*
* return (
* <pre>{JSON.stringify(supply)}</pre>
* )
* }
* ```
*
* @public
*/
export function useTotalSupply(
program: RequiredParam<NFTCollection | NFTDrop>,
) {
return useQuery(nftTotalSupplyQuery(program));
}
@@ -4,7 +4,7 @@ import {
RequiredParam,
} from "../../../core/query-utils/required-param";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import type { CreatorInput, NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
import type { NFTCollection, NFTDrop, UpdateCreatorInput } from "@thirdweb-dev/sdk/solana";
/**
* Update the creators for an NFT program
@@ -35,9 +35,9 @@ export function useUpdateCreators(
) {
const queryClient = useQueryClient();
return useMutation(
async (creators: CreatorInput[]) => {
async ({ creators, updateAll }: UpdateCreatorInput) => {
requiredParamInvariant(program, "program is required");
return await program.updateCreators(creators);
return await program.updateCreators(creators, updateAll);
},
{
onSettled: () =>
@@ -4,7 +4,7 @@ import {
RequiredParam,
} from "../../../core/query-utils/required-param";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import type { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
import type { NFTCollection, NFTDrop, UpdateRoyaltySettingsInput } from "@thirdweb-dev/sdk/solana";
/**
* Update the royalty for an NFT program
@@ -35,9 +35,9 @@ export function useUpdateRoyaltySettings(
) {
const queryClient = useQueryClient();
return useMutation(
async (sellerFeeBasisPoints: number) => {
async ({ sellerFeeBasisPoints, updateAll }: UpdateRoyaltySettingsInput) => {
requiredParamInvariant(program, "program is required");
return await program.updateRoyalty(sellerFeeBasisPoints);
return await program.updateRoyalty(sellerFeeBasisPoints, updateAll);
},
{
onSettled: () =>
+1
View File
@@ -18,6 +18,7 @@ export * from "./hooks/nft/useRoyaltySettings";
export * from "./hooks/nft/useUpdateRoyaltySettings";
export * from "./hooks/nft/useCreators";
export * from "./hooks/nft/useUpdateCreators";
export * from "./hooks/nft/useTotalSupply";
// NFT Collection
export * from "./hooks/nft/collection/useMintNFT";
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+36
View File
@@ -1,5 +1,41 @@
# @thirdweb-dev/sdk
## 3.6.2
### Patch Changes
- [#405](https://github.com/thirdweb-dev/js/pull/405) [`ccb7db4`](https://github.com/thirdweb-dev/js/commit/ccb7db48739b8dddcb2c032b3b6e3e5200485715) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Add updateAll param to creators and royalty update hooks for Solana
- [#404](https://github.com/thirdweb-dev/js/pull/404) [`9151aa7`](https://github.com/thirdweb-dev/js/commit/9151aa7dc80754a7aa4fd3f6ce0207bccbb6451f) Thanks [@jnsdls](https://github.com/jnsdls)! - [EVM] - expose `getDefaultTrustedForwarders` helper function
- [#305](https://github.com/thirdweb-dev/js/pull/305) [`5a01c53`](https://github.com/thirdweb-dev/js/commit/5a01c537e30bd7ef562aad1012413990de6faedf) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update to Metaplex 0.17
## 3.6.1
### Patch Changes
- [#398](https://github.com/thirdweb-dev/js/pull/398) [`15514f1`](https://github.com/thirdweb-dev/js/commit/15514f190a844d809ae5b1edb580aab46a682485) Thanks [@jnsdls](https://github.com/jnsdls)! - [EVM] - add `defaultValue` to constructorParams schema for releases
- [#388](https://github.com/thirdweb-dev/js/pull/388) [`7c0744a`](https://github.com/thirdweb-dev/js/commit/7c0744a006e78987ad8b271b2c64f4bac7759510) Thanks [@adam-maj](https://github.com/adam-maj)! - Add support for total supply and pagination on get all
- [#395](https://github.com/thirdweb-dev/js/pull/395) [`f8bceec`](https://github.com/thirdweb-dev/js/commit/f8bceec10c60b30dc3d6f45926e2a9e018202578) Thanks [@jnsdls](https://github.com/jnsdls)! - [Solana] - fix claim condition reset to native token from custom
- [#399](https://github.com/thirdweb-dev/js/pull/399) [`887af24`](https://github.com/thirdweb-dev/js/commit/887af24450e316c2ea390cd49c0563cebe793c0f) Thanks [@jnsdls](https://github.com/jnsdls)! - [EVM] - fix custom error handling for null-ish error values
- [#390](https://github.com/thirdweb-dev/js/pull/390) [`96bd92e`](https://github.com/thirdweb-dev/js/commit/96bd92e724ec7d880d23348071b26ab8c21dd868) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - [SDK] Expose `getAllDetectedFeatures(ABI)` helper function
## 3.6.0
### Minor Changes
- [#364](https://github.com/thirdweb-dev/js/pull/364) [`7cb8e59`](https://github.com/thirdweb-dev/js/commit/7cb8e59be48dc923316eb36ab43a1bef7364f6b1) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Enable new Drop contracts
### Patch Changes
- [#382](https://github.com/thirdweb-dev/js/pull/382) [`0ed77d2`](https://github.com/thirdweb-dev/js/commit/0ed77d2c9e7526444ef660aab82a950865234aa6) Thanks [@jnsdls](https://github.com/jnsdls)! - Add `constructorParams` to `ExtraPublishMetadataSchemaInput` schema to support better deploys from releases.
- [#385](https://github.com/thirdweb-dev/js/pull/385) [`04a47e8`](https://github.com/thirdweb-dev/js/commit/04a47e8971d28da2532ebd9db824eeb459c17bd4) Thanks [@adam-maj](https://github.com/adam-maj)! - Add retroactive royalty and creator setting
## 3.5.2
### Patch Changes
+12 -12
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/sdk",
"version": "3.5.2",
"version": "3.6.2",
"description": "The main thirdweb SDK.",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/sdk",
"license": "Apache-2.0",
@@ -57,7 +57,7 @@
"test:evm:all": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/evm/.mocharc.json' --timeout 30000 --parallel './test/evm/**/*.test.ts'",
"test:evm": "make test-evm",
"test:evm:single": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/evm/.mocharc.json' --timeout 30000",
"node:solana:start": "DEBUG='amman:(info|error|debug)' amman start",
"node:solana:start": "DEBUG='amman:(info|error|debug)' amman start --forceClone",
"node:solana:stop": "amman stop",
"test:solana:all": "SWC_NODE_PROJECT=./tsconfig.test.json mocha --config './test/solana/.mocharc.json' --timeout 30000 --parallel './test/solana/**/*.test.ts'",
"test:solana": "make test-sol",
@@ -65,18 +65,18 @@
"push": "yalc push"
},
"devDependencies": {
"@metaplex-foundation/amman": "0.10.0",
"@metaplex-foundation/amman-client": "0.2.1",
"@metaplex-foundation/amman": "^0.12.0",
"@metaplex-foundation/amman-client": "^0.2.1",
"@microsoft/api-documenter": "^7.19.4",
"@microsoft/api-extractor": "^7.29.2",
"@microsoft/tsdoc": "^0.14.1",
"@nomiclabs/hardhat-ethers": "2.0.5",
"@nomiclabs/hardhat-ethers": "^2.0.5",
"@preconstruct/cli": "^2.2.1",
"@swc-node/register": "^1.5.1",
"@swc/core": "^1.2.177",
"@types/chai": "^4.3.3",
"@types/deep-equal-in-any-order": "^1.0.1",
"@types/mocha": "^9.1.1",
"@types/mocha": "^10.0.0",
"@types/node": "^18.0.0",
"@types/uuid": "^8.3.3",
"@typescript-eslint/eslint-plugin": "^5.33.0",
@@ -89,7 +89,7 @@
"eslint-plugin-inclusive-language": "^2.2.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-tsdoc": "^0.2.16",
"ethers": "^5.7.0",
"ethers": "^5.7.2",
"hardhat": "^2.9.3",
"mocha": "^10.0.0",
"prettier": "^2.7.1",
@@ -99,11 +99,11 @@
"ethers": "^5"
},
"dependencies": {
"@metaplex-foundation/js": "0.15.0",
"@metaplex-foundation/mpl-token-metadata": "2.2.2",
"@project-serum/anchor": "0.25.0",
"@solana/spl-token": "0.2.0",
"@solana/web3.js": "1.62.0",
"@metaplex-foundation/js": "^0.17.6",
"@metaplex-foundation/mpl-token-metadata": "^2.3.3",
"@project-serum/anchor": "^0.25.0",
"@solana/spl-token": "^0.3.5",
"@solana/web3.js": "^1.62.0",
"@thirdweb-dev/contracts-js": "*",
"@thirdweb-dev/storage": "*",
"bn.js": "^5.2.1",
+16 -10
View File
@@ -1,19 +1,25 @@
import { z } from "zod";
/**
* @internal
*/
export const DEFAULT_QUERY_ALL_COUNT = 100;
export const QueryAllParamsSchema = z
.object({
start: z.number().default(0),
count: z.number().default(DEFAULT_QUERY_ALL_COUNT),
})
.default({
start: 0,
count: DEFAULT_QUERY_ALL_COUNT,
});
/**
* Pagination Parameters
* @public
*/
export interface QueryAllParams {
/**
* the index to start from (default: 0)
*/
start?: number;
/**
* how many items to return (default: 100)
*/
count?: number;
}
export type QueryAllParams = Exclude<
z.input<typeof QueryAllParamsSchema>,
undefined
>;
+11 -65
View File
@@ -31,8 +31,8 @@ import {
SnapshotFormatVersion,
} from "./sharded-merkle-tree";
import { createSnapshot } from "./snapshots";
import { IClaimCondition } from "@thirdweb-dev/contracts-js/dist/declarations/src/Drop";
import { IDropClaimCondition_V2 } from "@thirdweb-dev/contracts-js/dist/declarations/src/IDropERC20_V2";
import { IClaimCondition } from "@thirdweb-dev/contracts-js/src/IDrop";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import {
BigNumber,
@@ -50,6 +50,7 @@ import {
* @returns - `overrides` and `proofs` as an object.
*/
export async function prepareClaim(
addressToClaim: string,
quantity: BigNumberish,
activeClaimCondition: ClaimCondition,
merkleMetadataFetcher: () => Promise<Record<string, string>>,
@@ -59,7 +60,6 @@ export async function prepareClaim(
checkERC20Allowance: boolean,
snapshotFormatVersion: SnapshotFormatVersion,
): Promise<ClaimVerification> {
const addressToClaim = await contractWrapper.getSignerAddress();
let maxClaimable = convertQuantityToBigNumber(
activeClaimCondition.maxClaimablePerWallet,
tokenDecimals,
@@ -154,8 +154,10 @@ export async function prepareClaim(
overrides,
proofs,
maxClaimable,
price: priceInProof,
currencyAddress: currencyAddressInProof,
price: pricePerToken,
currencyAddress: currencyAddress,
priceInProof,
currencyAddressInProof,
};
}
@@ -284,66 +286,6 @@ 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
* @param provider
* @param snapshotFormatVersion
* @returns - The proof for the current signer for the specified condition.
*/
export async function getClaimerProofs(
addressToClaim: string,
merkleRoot: string,
tokenDecimals: number,
merkleMetadata: Record<string, string>,
storage: ThirdwebStorage,
provider: ethers.providers.Provider,
snapshotFormatVersion: SnapshotFormatVersion,
): Promise<
| {
proof: string[];
maxClaimable: BigNumber;
price: BigNumber;
currencyAddress: string;
}
| undefined
> {
const claim = await fetchSnapshotEntryForAddress(
addressToClaim,
merkleRoot,
merkleMetadata,
provider,
storage,
snapshotFormatVersion,
);
if (!claim) {
return undefined;
}
const price =
claim.price === "unlimited" || claim.price === undefined
? ethers.constants.MaxUint256
: await normalizePriceValue(
provider,
claim.price,
claim.currencyAddress || ethers.constants.AddressZero,
);
const maxClaimable =
claim.maxClaimable === "unlimited"
? ethers.constants.MaxUint256
: ethers.utils.parseUnits(claim.maxClaimable, tokenDecimals);
return {
proof: claim.proof,
maxClaimable,
price,
currencyAddress: claim.currencyAddress || ethers.constants.AddressZero,
};
}
/**
* @internal
* Decorates claim conditions with merkle roots from snapshots if present
@@ -578,6 +520,10 @@ export async function transformResultToClaimCondition(
pm.supplyClaimed,
tokenDecimals,
);
let resolvedMetadata;
if (pm.metadata) {
resolvedMetadata = await storage.downloadJSON(pm.metadata);
}
return ClaimConditionOutputSchema.parse({
startTime: pm.startTimestamp,
maxClaimableSupply,
@@ -593,7 +539,7 @@ export async function transformResultToClaimCondition(
snapshot: shouldDownloadSnapshot
? await fetchSnapshot(pm.merkleRoot, merkleMetadata, storage)
: undefined,
metadata: pm.metadata,
metadata: resolvedMetadata,
});
}
+23
View File
@@ -33,6 +33,14 @@ export function cleanCurrencyAddress(currencyAddress: string): string {
return currencyAddress;
}
/**
*
* @param provider
* @param inputPrice
* @param currencyAddress
* @returns
* @internal
*/
export async function normalizePriceValue(
provider: providers.Provider,
inputPrice: Price,
@@ -42,6 +50,13 @@ export async function normalizePriceValue(
return utils.parseUnits(AmountSchema.parse(inputPrice), metadata.decimals);
}
/**
*
* @param provider
* @param asset
* @returns
* @internal
*/
export async function fetchCurrencyMetadata(
provider: providers.Provider,
asset: string,
@@ -73,6 +88,14 @@ export async function fetchCurrencyMetadata(
}
}
/**
*
* @param providerOrSigner
* @param asset
* @param price
* @returns
* @internal
*/
export async function fetchCurrencyValue(
providerOrSigner: providers.Provider,
asset: string,
+5 -2
View File
@@ -459,9 +459,12 @@ function parseMessageParts(regex: RegExp, raw: string): string {
* @param message
*/
export function includesErrorMessage(err: any, message: string): boolean {
if (!err) {
return false;
}
return (
(err && err.toString().includes(message)) ||
(err.message && err.message.toString().includes(message)) ||
(err.error && err.error.toString().includes(message))
(err && err.message && err.message.toString().includes(message)) ||
(err && err.error && err.error.toString().includes(message))
);
}
+103 -22
View File
@@ -8,6 +8,7 @@ import { ContractWrapper } from "../core/classes/contract-wrapper";
import { DetectableFeature } from "../core/interfaces/DetectableFeature";
import { decode } from "../lib/cbor-decode.js";
import {
Abi,
AbiEvent,
AbiFunction,
AbiSchema,
@@ -32,26 +33,57 @@ import { z } from "zod";
* @param abi
* @param feature
*/
function matchesAbiInterface(
abi: z.input<typeof AbiSchema>,
feature: Feature,
): boolean {
function matchesAbiInterface(abi: Abi, feature: Feature): boolean {
// returns true if all the functions in `interfaceToMatch` are found in `contract` (removing any duplicates)
const contractFn = [
...new Set(extractFunctionsFromAbi(abi).map((f) => f.name)),
];
const interfaceFn = [
...new Set(
feature.abis
.flatMap((i: any) => extractFunctionsFromAbi(i))
.map((f: AbiFunction) => f.name),
),
];
return hasMatchingAbi(abi, feature.abis);
}
return (
contractFn.filter((k) => interfaceFn.includes(k)).length ===
interfaceFn.length
/**
* @internal
* @param contractWrapper
* @param abi
* @returns
*/
export function matchesPrebuiltAbi<T extends BaseContract>(
contractWrapper: ContractWrapper<BaseContract>,
abi: Abi,
): contractWrapper is ContractWrapper<T> {
return hasMatchingAbi(contractWrapper.abi as Abi, [abi]);
}
/**
* @internal
* @param contractAbi
* @param featureAbis
* @returns
*/
export function hasMatchingAbi(contractAbi: Abi, featureAbis: readonly Abi[]) {
const contractFn = extractFunctionsFromAbi(contractAbi);
const interfaceFn = featureAbis.flatMap((i: any) =>
extractFunctionsFromAbi(i),
);
// match every function and their arguments
const intersection = contractFn.filter((fn) => {
const match = interfaceFn.find(
(iFn) =>
iFn.name === fn.name &&
iFn.inputs.length === fn.inputs.length &&
iFn.inputs.every((i, index) => {
if (i.type === "tuple" || i.type === "tuple[]") {
// check that all properties in the tuple are the same type
return (
i.type === fn.inputs[index].type &&
i.components?.every((c, cIndex) => {
return c.type === fn.inputs[index].components?.[cIndex]?.type;
})
);
}
return i.type === fn.inputs[index].type;
}),
);
return match !== undefined;
});
return intersection.length === interfaceFn.length;
}
/**
@@ -145,7 +177,7 @@ export function extractFunctionParamsFromAbi(
* @param metadata
*/
export function extractFunctionsFromAbi(
abi: z.input<typeof AbiSchema>,
abi: Abi,
metadata?: Record<string, any>,
): AbiFunction[] {
const functions = (abi || []).filter((el) => el.type === "function");
@@ -178,7 +210,7 @@ export function extractFunctionsFromAbi(
* @param metadata
*/
export function extractEventsFromAbi(
abi: z.input<typeof AbiSchema>,
abi: Abi,
metadata?: Record<string, any>,
): AbiEvent[] {
const events = (abi || []).filter((el) => el.type === "event");
@@ -405,7 +437,9 @@ export async function fetchSourceFilesFromMetadata(
Object.entries(publishedMetadata.metadata.sources).map(
async ([path, info]) => {
const urls = (info as any).urls as string[];
const ipfsLink = urls.find((url) => url.includes("ipfs"));
const ipfsLink = urls
? urls.find((url) => url.includes("ipfs"))
: undefined;
if (ipfsLink) {
const ipfsHash = ipfsLink.split("ipfs/")[1];
// 5 sec timeout for sources that haven't been uploaded to ipfs
@@ -423,7 +457,9 @@ export async function fetchSourceFilesFromMetadata(
} else {
return {
filename: path,
source: "Could not find source for this contract",
source:
(info as any).content ||
"Could not find source for this contract",
};
}
},
@@ -489,7 +525,7 @@ export async function fetchExtendedReleaseMetadata(
* @returns the nested struct of all features and whether they're detected in the abi
*/
export function detectFeatures(
abi: z.input<typeof AbiSchema>,
abi: Abi,
features: Record<string, Feature> = SUPPORTED_FEATURES,
): Record<string, FeatureWithEnabled> {
const results: Record<string, FeatureWithEnabled> = {};
@@ -506,6 +542,51 @@ export function detectFeatures(
return results;
}
function extractFeatures(
input: Record<string, FeatureWithEnabled>,
enabledExtensions: FeatureWithEnabled[],
) {
if (!input) {
return;
}
for (const extensionKey in input) {
const extension = input[extensionKey];
// if extension is enabled, then add it to enabledFeatures
if (extension.enabled) {
enabledExtensions.push(extension);
}
// recurse
extractFeatures(
extension.features,
enabledExtensions,
);
}
}
/**
* Return all the detected features in the abi
* @param abi - parsed array of abi entries
* @returns array of all detected extensions with full information on each feature
* @public
*/
export function getAllDetectedFeatures(abi: Abi): FeatureWithEnabled[] {
const features: FeatureWithEnabled[] = [];
extractFeatures(detectFeatures(abi), features);
return features;
}
/**
* Return all the detected features names in the abi
* @param abi - parsed array of abi entries
* @returns array of all detected features names
* @public
*/
export function getAllDetectedFeatureNames(abi: Abi): string[] {
const features: FeatureWithEnabled[] = [];
extractFeatures(detectFeatures(abi), features);
return features.map((f) => f.name);
}
/**
* Checks whether the given ABI supports a given feature
* @internal
+7
View File
@@ -3,3 +3,10 @@ export * from "./snapshots";
export * from "./role";
export * from "./feature-detection";
export * from "./version-checker";
// export helpful util
export {
fetchCurrencyValue,
fetchCurrencyMetadata,
normalizePriceValue,
} from "./currency";
export { convertToReadableQuantity } from "./claim-conditions";
+135 -1
View File
@@ -1,4 +1,5 @@
import { ChainId, SUPPORTED_CHAIN_ID } from "./chains";
import { PrebuiltContractType } from "../core/types";
import { ChainId, SUPPORTED_CHAIN_ID, SUPPORTED_CHAIN_IDS } from "./chains";
import { constants } from "ethers";
/**
@@ -142,6 +143,118 @@ export const CONTRACT_ADDRESSES: Record<
},
};
type DropContract = Extract<
PrebuiltContractType,
"nft-drop" | "token-drop" | "edition-drop" | "signature-drop"
>;
export const APPROVED_IMPLEMENTATIONS: Record<
SUPPORTED_CHAIN_ID,
Record<DropContract, string>
> = {
[ChainId.Mainnet]: {
"nft-drop": "0x60fF9952e0084A6DEac44203838cDC91ABeC8736",
"edition-drop": "0x74af262d0671F378F97a1EDC3d0970Dbe8A1C550",
"token-drop": "0xE1eE43D23f247b6A9aF81fcE2766E76709482728",
"signature-drop": "0x6fD690EB509BdE4C50028C5D9C0dE3750C2Fad6A",
},
[ChainId.Polygon]: {
"nft-drop": "0xB96508050Ba0925256184103560EBADA912Fcc69",
"edition-drop": "0x74af262d0671F378F97a1EDC3d0970Dbe8A1C550",
"token-drop": "0x5A8eA4Adad8289746D073947BA06D69A62499aaf",
"signature-drop": "0xBE2fDc35410E268e41Bec62DBb01AEb43245c7d5",
},
[ChainId.Fantom]: {
"nft-drop": "0x2A396b2D90BAcEF19cDa973586B2633d22710fC2",
"edition-drop": "0x06395FCF9AC6ED827f9dD6e776809cEF1Be0d21B",
"token-drop": "0x0148b28a38efaaC31b6aa0a6D9FEb70FE7C91FFa",
"signature-drop": "0xe135Ef65C2B2213C3fD56d0Bd6500A2cA147aC10",
},
[ChainId.Avalanche]: {
"nft-drop": "0x9cF91118C8ee2913F0588e0F10e36B3d63F68bF6",
"edition-drop": "0x135fC9D26E5eC51260ece1DF4ED424E2f55c7766",
"token-drop": "0xca0B071899E575BA86495D46c5066971b6f3A901",
"signature-drop": "0x1d47526C3292B0130ef0afD5F02c1DA052A017B3",
},
[ChainId.Optimism]: {
"nft-drop": "0xFBd7D24d80ee005671E731a7287DEB6073264dD1",
"edition-drop": "0xe135Ef65C2B2213C3fD56d0Bd6500A2cA147aC10",
"token-drop": "0x902Dd246e66d8C3CE652375a723F2a52b43b9AAE",
"signature-drop": "0x8a4cd3549e548bbEEb38C16E041FFf040a5acabD",
},
[ChainId.Arbitrum]: {
"nft-drop": "0xC4903c1Ff5367b9ac2c349B63DC2409421AaEE2a",
"edition-drop": "0xCcddcec1831646Beff2753249f1B9C580327E89F",
"token-drop": "0x1b5947e1a2d5a29D0df20931DeAB0B87818209B9",
"signature-drop": "0x2dF9851af45dd41C8584ac55D983C604da985Bc7",
},
[ChainId.BinanceSmartChainMainnet]: {
"nft-drop": "0x902Dd246e66d8C3CE652375a723F2a52b43b9AAE",
"edition-drop": "0x2A396b2D90BAcEF19cDa973586B2633d22710fC2",
"token-drop": "0xe135Ef65C2B2213C3fD56d0Bd6500A2cA147aC10",
"signature-drop": "0xFBd7D24d80ee005671E731a7287DEB6073264dD1",
},
[ChainId.Goerli]: {
"nft-drop": "0xD11c97DD5F5546B5bBd630D7D1d7327481B0b92C",
"edition-drop": "0x5A8eA4Adad8289746D073947BA06D69A62499aaf",
"token-drop": "0x5680933221B752EB443654a014f88B101F868d50",
"signature-drop": "0x1b5947e1a2d5a29D0df20931DeAB0B87818209B9",
},
[ChainId.Mumbai]: {
"nft-drop": "0xC4903c1Ff5367b9ac2c349B63DC2409421AaEE2a",
"edition-drop": "0xCcddcec1831646Beff2753249f1B9C580327E89F",
"token-drop": "0x1b5947e1a2d5a29D0df20931DeAB0B87818209B9",
"signature-drop": "0x2dF9851af45dd41C8584ac55D983C604da985Bc7",
},
[ChainId.FantomTestnet]: {
"nft-drop": "0x8a4cd3549e548bbEEb38C16E041FFf040a5acabD",
"edition-drop": "0x902Dd246e66d8C3CE652375a723F2a52b43b9AAE",
"token-drop": "0xFBd7D24d80ee005671E731a7287DEB6073264dD1",
"signature-drop": "0x5A8eA4Adad8289746D073947BA06D69A62499aaf",
},
[ChainId.AvalancheFujiTestnet]: {
"nft-drop": "0xD11c97DD5F5546B5bBd630D7D1d7327481B0b92C",
"edition-drop": "0xE1eE43D23f247b6A9aF81fcE2766E76709482728",
"token-drop": "0x6fD690EB509BdE4C50028C5D9C0dE3750C2Fad6A",
"signature-drop": "0xCcddcec1831646Beff2753249f1B9C580327E89F",
},
[ChainId.OptimismGoerli]: {
"nft-drop": "0xCcddcec1831646Beff2753249f1B9C580327E89F",
"edition-drop": "0x6fD690EB509BdE4C50028C5D9C0dE3750C2Fad6A",
"token-drop": "0xD11c97DD5F5546B5bBd630D7D1d7327481B0b92C",
"signature-drop": "0x1b5947e1a2d5a29D0df20931DeAB0B87818209B9",
},
[ChainId.ArbitrumGoerli]: {
"nft-drop": "0x9CfE807a5b124b962064Fa8F7FD823Cc701255b6",
"edition-drop": "0x9cF91118C8ee2913F0588e0F10e36B3d63F68bF6",
"token-drop": "0x1d47526C3292B0130ef0afD5F02c1DA052A017B3",
"signature-drop": "0xE1eE43D23f247b6A9aF81fcE2766E76709482728",
},
[ChainId.BinanceSmartChainTestnet]: {
"nft-drop": "",
"edition-drop": "",
"token-drop": "",
"signature-drop": "", // TODO
},
};
/**
* @internal
* @param chainId
* @param contractType
*/
export function getApprovedImplementation(
chainId: SUPPORTED_CHAIN_ID, // TODO use SupportedChainId once we deploy to all chains
contractType: PrebuiltContractType,
): string | null {
if (chainId in APPROVED_IMPLEMENTATIONS) {
const approvedImpls = APPROVED_IMPLEMENTATIONS[chainId];
if (contractType in approvedImpls) {
return approvedImpls[contractType as keyof typeof approvedImpls];
}
}
return null;
}
/**
* @internal
*/
@@ -177,3 +290,24 @@ export function getContractPublisherAddress() {
return ContractPublisher_address;
}
}
/**
*
* @param chainId - chain id
* @returns the array of trusted forwarders for the given chain id
* @internal
*/
export function getDefaultTrustedForwarders(
chainId: SUPPORTED_CHAIN_ID,
): string[] {
const chainEnum = SUPPORTED_CHAIN_IDS.find((c) => c === chainId);
const biconomyForwarder = chainEnum
? CONTRACT_ADDRESSES[chainEnum].biconomyForwarder
: constants.AddressZero;
const openzeppelinForwarder = chainEnum
? CONTRACT_ADDRESSES[chainEnum].openzeppelinForwarder
: constants.AddressZero;
return biconomyForwarder !== constants.AddressZero
? [openzeppelinForwarder, biconomyForwarder]
: [openzeppelinForwarder];
}
-1
View File
@@ -6,7 +6,6 @@ export enum ChainId {
Goerli = 5,
Polygon = 137,
Mumbai = 80001,
Harmony = 1666600000,
Localhost = 1337,
Hardhat = 31337,
Fantom = 250,
@@ -3,35 +3,45 @@ import {
FEATURE_TOKEN_BATCH_MINTABLE,
FEATURE_TOKEN_BURNABLE,
FEATURE_TOKEN_MINTABLE,
FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS,
FEATURE_TOKEN_SIGNATURE_MINTABLE,
FEATURE_TOKEN_CLAIM_CONDITIONS_V1,
FEATURE_TOKEN_CLAIM_CONDITIONS_V2,
FEATURE_TOKEN_CLAIM_PHASES_V2,
FEATURE_TOKEN_CLAIM_PHASES_V1,
} from "./erc20-features";
import {
FEATURE_NFT,
FEATURE_NFT_BATCH_MINTABLE,
FEATURE_NFT_BURNABLE,
FEATURE_NFT_CLAIMABLE,
FEATURE_NFT_CLAIM_CUSTOM,
FEATURE_NFT_LAZY_MINTABLE,
FEATURE_NFT_ENUMERABLE,
FEATURE_NFT_MINTABLE,
FEATURE_NFT_REVEALABLE,
FEATURE_NFT_SIGNATURE_MINTABLE,
FEATURE_NFT_SUPPLY,
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V1,
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2,
FEATURE_NFT_CLAIM_CONDITIONS_V1,
FEATURE_NFT_CLAIM_CONDITIONS_V2,
FEATURE_NFT_CLAIM_PHASES_V2,
FEATURE_NFT_TIERED_DROP,
FEATURE_NFT_CLAIM_PHASES_V1,
FEATURE_NFT_SIGNATURE_MINTABLE_V1,
FEATURE_NFT_SIGNATURE_MINTABLE_V2,
} from "./erc721-features";
import {
FEATURE_EDITION,
FEATURE_EDITION_BATCH_MINTABLE,
FEATURE_EDITION_BURNABLE,
FEATURE_EDITION_CLAIMABLE,
FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS,
FEATURE_EDITION_CLAIM_CUSTOM,
FEATURE_EDITION_CLAIM_CONDITIONS_V1,
FEATURE_EDITION_CLAIM_CONDITIONS_V2,
FEATURE_EDITION_CLAIM_PHASES_V2,
FEATURE_EDITION_ENUMERABLE,
FEATURE_EDITION_LAZY_MINTABLE,
FEATURE_EDITION_LAZY_MINTABLE_V1,
FEATURE_EDITION_LAZY_MINTABLE_V2,
FEATURE_EDITION_MINTABLE,
FEATURE_EDITION_REVEALABLE,
FEATURE_EDITION_SIGNATURE_MINTABLE,
FEATURE_EDITION_CLAIM_PHASES_V1,
} from "./erc1155-features";
import {
FEATURE_METADATA,
@@ -52,7 +62,10 @@ export type Feature =
| typeof FEATURE_TOKEN_MINTABLE
| typeof FEATURE_TOKEN_BATCH_MINTABLE
| typeof FEATURE_TOKEN_BURNABLE
| typeof FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS
| typeof FEATURE_TOKEN_CLAIM_CONDITIONS_V1
| typeof FEATURE_TOKEN_CLAIM_CONDITIONS_V2
| typeof FEATURE_TOKEN_CLAIM_PHASES_V1
| typeof FEATURE_TOKEN_CLAIM_PHASES_V2
| typeof FEATURE_TOKEN_SIGNATURE_MINTABLE
| typeof FEATURE_NFT
| typeof FEATURE_NFT_SUPPLY
@@ -60,19 +73,26 @@ export type Feature =
| typeof FEATURE_NFT_MINTABLE
| typeof FEATURE_NFT_BATCH_MINTABLE
| typeof FEATURE_NFT_LAZY_MINTABLE
| typeof FEATURE_NFT_CLAIMABLE
| typeof FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V1
| typeof FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2
| typeof FEATURE_NFT_CLAIM_CUSTOM
| typeof FEATURE_NFT_CLAIM_CONDITIONS_V1
| typeof FEATURE_NFT_CLAIM_CONDITIONS_V2
| typeof FEATURE_NFT_CLAIM_PHASES_V1
| typeof FEATURE_NFT_CLAIM_PHASES_V2
| typeof FEATURE_NFT_REVEALABLE
| typeof FEATURE_NFT_SIGNATURE_MINTABLE
| typeof FEATURE_NFT_SIGNATURE_MINTABLE_V1
| typeof FEATURE_NFT_SIGNATURE_MINTABLE_V2
| typeof FEATURE_NFT_BURNABLE
| typeof FEATURE_NFT_TIERED_DROP
| typeof FEATURE_EDITION
| typeof FEATURE_EDITION_ENUMERABLE
| typeof FEATURE_EDITION_MINTABLE
| typeof FEATURE_EDITION_CLAIMABLE
| typeof FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS
| typeof FEATURE_EDITION_LAZY_MINTABLE
| typeof FEATURE_EDITION_CLAIM_CUSTOM
| typeof FEATURE_EDITION_CLAIM_CONDITIONS_V1
| typeof FEATURE_EDITION_CLAIM_CONDITIONS_V2
| typeof FEATURE_EDITION_CLAIM_PHASES_V1
| typeof FEATURE_EDITION_CLAIM_PHASES_V2
| typeof FEATURE_EDITION_LAZY_MINTABLE_V1
| typeof FEATURE_EDITION_LAZY_MINTABLE_V2
| typeof FEATURE_EDITION_REVEALABLE
| typeof FEATURE_EDITION_BATCH_MINTABLE
| typeof FEATURE_EDITION_BURNABLE
@@ -1,7 +1,10 @@
import DelayedRevealAbi from "@thirdweb-dev/contracts-js/dist/abis/DelayedReveal.json";
import DropERC1155_V2Abi from "@thirdweb-dev/contracts-js/dist/abis/DropERC1155_V2.json";
import IBurnableERC1155Abi from "@thirdweb-dev/contracts-js/dist/abis/IBurnableERC1155.json";
import IClaimableERC1155 from "@thirdweb-dev/contracts-js/dist/abis/IClaimableERC1155.json";
import DelayedRevealAbi from "@thirdweb-dev/contracts-js/dist/abis/IDelayedReveal.json";
import IDropMultiPhase1155 from "@thirdweb-dev/contracts-js/dist/abis/IDrop1155.json";
import IDropSinglePhase1155 from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase1155.json";
import IDropSinglePhase1155_V1 from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase1155_V1.json";
import Erc1155Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC1155.json";
import Erc1155EnumerableAbi from "@thirdweb-dev/contracts-js/dist/abis/IERC1155Enumerable.json";
import ILazyMintAbi from "@thirdweb-dev/contracts-js/dist/abis/ILazyMint.json";
@@ -20,8 +23,19 @@ export const FEATURE_EDITION_BURNABLE = {
features: {},
} as const;
export const FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS = {
name: "ERC1155ClaimableWithConditions",
export const FEATURE_EDITION_CLAIM_CONDITIONS_V1 = {
name: "ERC1155ClaimConditionsV1",
namespace: "edition.drop.claim",
docLinks: {
sdk: "sdk.erc1155claimable",
contracts: "DropSinglePhase1155",
},
abis: [Erc1155Abi, ILazyMintAbi, IDropSinglePhase1155_V1],
features: {},
} as const;
export const FEATURE_EDITION_CLAIM_CONDITIONS_V2 = {
name: "ERC1155ClaimConditionsV2",
namespace: "edition.drop.claim",
docLinks: {
sdk: "sdk.erc1155claimable",
@@ -31,8 +45,30 @@ export const FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS = {
features: {},
} as const;
export const FEATURE_EDITION_CLAIMABLE = {
name: "ERC1155Claimable",
export const FEATURE_EDITION_CLAIM_PHASES_V2 = {
name: "ERC1155ClaimPhasesV2",
namespace: "edition.drop.claim",
docLinks: {
sdk: "sdk.erc1155claimable",
contracts: "Drop1155",
},
abis: [Erc1155Abi, ILazyMintAbi, IDropMultiPhase1155],
features: {},
} as const;
export const FEATURE_EDITION_CLAIM_PHASES_V1 = {
name: "ERC1155ClaimPhasesV1",
namespace: "edition.drop.claim",
docLinks: {
sdk: "sdk.erc1155claimable",
contracts: "Drop1155",
},
abis: [DropERC1155_V2Abi],
features: {},
} as const;
export const FEATURE_EDITION_CLAIM_CUSTOM = {
name: "ERC1155ClaimCustom",
namespace: "edition.drop.claim",
docLinks: {
sdk: "sdk.erc1155claimable",
@@ -42,21 +78,6 @@ export const FEATURE_EDITION_CLAIMABLE = {
features: {},
} as const;
export const FEATURE_EDITION_LAZY_MINTABLE = {
name: "ERC1155LazyMintable",
namespace: "edition.drop",
docLinks: {
sdk: "sdk.erc1155droppable",
contracts: "LazyMint",
},
abis: [Erc1155Abi, ILazyMintAbi],
features: {
[FEATURE_EDITION_CLAIMABLE.name]: FEATURE_EDITION_CLAIMABLE,
[FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS.name]:
FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS,
},
} as const;
export const FEATURE_EDITION_REVEALABLE = {
name: "ERC1155Revealable",
namespace: "edition.drop.revealer",
@@ -68,6 +89,38 @@ export const FEATURE_EDITION_REVEALABLE = {
features: {},
} as const;
export const FEATURE_EDITION_LAZY_MINTABLE_V2 = {
name: "ERC1155LazyMintableV2",
namespace: "edition.drop",
docLinks: {
sdk: "sdk.erc1155droppable",
contracts: "LazyMint",
},
abis: [Erc1155Abi, ILazyMintAbi],
features: {
[FEATURE_EDITION_REVEALABLE.name]: FEATURE_EDITION_REVEALABLE,
[FEATURE_EDITION_CLAIM_CUSTOM.name]: FEATURE_EDITION_CLAIM_CUSTOM,
[FEATURE_EDITION_CLAIM_CONDITIONS_V1.name]:
FEATURE_EDITION_CLAIM_CONDITIONS_V1,
[FEATURE_EDITION_CLAIM_CONDITIONS_V2.name]:
FEATURE_EDITION_CLAIM_CONDITIONS_V2,
[FEATURE_EDITION_CLAIM_PHASES_V2.name]: FEATURE_EDITION_CLAIM_PHASES_V2,
},
} as const;
export const FEATURE_EDITION_LAZY_MINTABLE_V1 = {
name: "ERC1155LazyMintableV1",
namespace: "edition.drop",
docLinks: {
sdk: "sdk.erc1155droppable",
contracts: "LazyMint",
},
abis: [DropERC1155_V2Abi],
features: {
[FEATURE_EDITION_CLAIM_PHASES_V1.name]: FEATURE_EDITION_CLAIM_PHASES_V1,
},
} as const;
export const FEATURE_EDITION_SIGNATURE_MINTABLE = {
name: "ERC1155SignatureMintable",
namespace: "edition.signature",
@@ -126,7 +179,8 @@ export const FEATURE_EDITION = {
[FEATURE_EDITION_BURNABLE.name]: FEATURE_EDITION_BURNABLE,
[FEATURE_EDITION_ENUMERABLE.name]: FEATURE_EDITION_ENUMERABLE,
[FEATURE_EDITION_MINTABLE.name]: FEATURE_EDITION_MINTABLE,
[FEATURE_EDITION_LAZY_MINTABLE.name]: FEATURE_EDITION_LAZY_MINTABLE,
[FEATURE_EDITION_LAZY_MINTABLE_V1.name]: FEATURE_EDITION_LAZY_MINTABLE_V1,
[FEATURE_EDITION_LAZY_MINTABLE_V2.name]: FEATURE_EDITION_LAZY_MINTABLE_V2,
[FEATURE_EDITION_REVEALABLE.name]: FEATURE_EDITION_REVEALABLE,
[FEATURE_EDITION_SIGNATURE_MINTABLE.name]:
FEATURE_EDITION_SIGNATURE_MINTABLE,
@@ -1,12 +1,26 @@
import DropERC20_V2Abi from "@thirdweb-dev/contracts-js/dist/abis/DropERC20_V2.json";
import IBurnableERC20Abi from "@thirdweb-dev/contracts-js/dist/abis/IBurnableERC20.json";
import IDrop from "@thirdweb-dev/contracts-js/dist/abis/IDrop.json";
import IDropSinglePhase from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase.json";
import IDropSinglePhaseV1 from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase_V1.json";
import ERC20Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC20.json";
import IMintableERC20Abi from "@thirdweb-dev/contracts-js/dist/abis/IMintableERC20.json";
import MulticallAbi from "@thirdweb-dev/contracts-js/dist/abis/IMulticall.json";
import ISignatureMintERC20Abi from "@thirdweb-dev/contracts-js/dist/abis/ISignatureMintERC20.json";
export const FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS = {
name: "ERC20ClaimableWithConditions",
export const FEATURE_TOKEN_CLAIM_CONDITIONS_V1 = {
name: "ERC20ClaimConditionsV1",
namespace: "token.drop.claim",
docLinks: {
sdk: "sdk.erc20dclaimable",
contracts: "DropSinglePhase_V1",
},
abis: [ERC20Abi, IDropSinglePhaseV1],
features: {},
} as const;
export const FEATURE_TOKEN_CLAIM_CONDITIONS_V2 = {
name: "ERC20ClaimConditionsV2",
namespace: "token.drop.claim",
docLinks: {
sdk: "sdk.erc20dclaimable",
@@ -16,6 +30,28 @@ export const FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS = {
features: {},
} as const;
export const FEATURE_TOKEN_CLAIM_PHASES_V2 = {
name: "ERC20ClaimPhasesV2",
namespace: "token.drop.claim",
docLinks: {
sdk: "sdk.erc20dclaimable",
contracts: "Drop",
},
abis: [ERC20Abi, IDrop],
features: {},
} as const;
export const FEATURE_TOKEN_CLAIM_PHASES_V1 = {
name: "ERC20ClaimPhasesV1",
namespace: "token.drop.claim",
docLinks: {
sdk: "sdk.erc20dclaimable",
contracts: "Drop",
},
abis: [DropERC20_V2Abi],
features: {},
} as const;
export const FEATURE_TOKEN_BURNABLE = {
name: "ERC20Burnable",
namespace: "token.burn",
@@ -73,8 +109,10 @@ export const FEATURE_TOKEN = {
features: {
[FEATURE_TOKEN_BURNABLE.name]: FEATURE_TOKEN_BURNABLE,
[FEATURE_TOKEN_MINTABLE.name]: FEATURE_TOKEN_MINTABLE,
[FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS.name]:
FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS,
[FEATURE_TOKEN_CLAIM_CONDITIONS_V1.name]: FEATURE_TOKEN_CLAIM_CONDITIONS_V1,
[FEATURE_TOKEN_CLAIM_CONDITIONS_V2.name]: FEATURE_TOKEN_CLAIM_CONDITIONS_V2,
[FEATURE_TOKEN_CLAIM_PHASES_V1.name]: FEATURE_TOKEN_CLAIM_PHASES_V1,
[FEATURE_TOKEN_CLAIM_PHASES_V2.name]: FEATURE_TOKEN_CLAIM_PHASES_V2,
[FEATURE_TOKEN_SIGNATURE_MINTABLE.name]: FEATURE_TOKEN_SIGNATURE_MINTABLE,
},
} as const;
@@ -1,6 +1,8 @@
import DropERC721_V3Abi from "@thirdweb-dev/contracts-js/dist/abis/DropERC721_V3.json";
import IBurnableERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/IBurnableERC721.json";
import IClaimableERC721 from "@thirdweb-dev/contracts-js/dist/abis/IClaimableERC721.json";
import DelayedRevealAbi from "@thirdweb-dev/contracts-js/dist/abis/IDelayedReveal.json";
import IDrop from "@thirdweb-dev/contracts-js/dist/abis/IDrop.json";
import IDropSinglePhase from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase.json";
import IDropSinglePhaseV1 from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase_V1.json";
import Erc721Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC721.json";
@@ -9,8 +11,9 @@ import Erc721SupplyAbi from "@thirdweb-dev/contracts-js/dist/abis/IERC721Supply.
import ILazyMintAbi from "@thirdweb-dev/contracts-js/dist/abis/ILazyMint.json";
import IMintableERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/IMintableERC721.json";
import MulticallAbi from "@thirdweb-dev/contracts-js/dist/abis/IMulticall.json";
import SignatureMintERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/ISignatureMintERC721.json";
import TieredDropAbi from "@thirdweb-dev/contracts-js/dist/abis/LazyMintWithTier.json";
import SignatureMintERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/SignatureMintERC721.json";
import TokenERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/TokenERC721.json";
export const FEATURE_NFT_BURNABLE = {
name: "ERC721Burnable",
@@ -45,19 +48,8 @@ export const FEATURE_NFT_TIERED_DROP = {
features: {},
} as const;
export const FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2 = {
name: "ERC721ClaimableWithConditionsV2",
namespace: "nft.drop.claim",
docLinks: {
sdk: "sdk.erc721claimable",
contracts: "DropSinglePhase",
},
abis: [Erc721Abi, ILazyMintAbi, IDropSinglePhase],
features: {},
} as const;
export const FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V1 = {
name: "ERC721ClaimableWithConditionsV1",
export const FEATURE_NFT_CLAIM_CONDITIONS_V1 = {
name: "ERC721ClaimConditionsV1",
namespace: "nft.drop.claim",
docLinks: {
sdk: "sdk.erc721claimable",
@@ -67,8 +59,41 @@ export const FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V1 = {
features: {},
} as const;
export const FEATURE_NFT_CLAIMABLE = {
name: "ERC721Claimable",
export const FEATURE_NFT_CLAIM_CONDITIONS_V2 = {
name: "ERC721ClaimConditionsV2",
namespace: "nft.drop.claim",
docLinks: {
sdk: "sdk.erc721claimable",
contracts: "DropSinglePhase",
},
abis: [Erc721Abi, ILazyMintAbi, IDropSinglePhase],
features: {},
} as const;
export const FEATURE_NFT_CLAIM_PHASES_V1 = {
name: "ERC721ClaimPhasesV1",
namespace: "nft.drop.claim",
docLinks: {
sdk: "sdk.erc721claimable",
contracts: "Drop",
},
abis: [DropERC721_V3Abi],
features: {},
} as const;
export const FEATURE_NFT_CLAIM_PHASES_V2 = {
name: "ERC721ClaimPhasesV2",
namespace: "nft.drop.claim",
docLinks: {
sdk: "sdk.erc721claimable",
contracts: "Drop",
},
abis: [Erc721Abi, ILazyMintAbi, IDrop],
features: {},
} as const;
export const FEATURE_NFT_CLAIM_CUSTOM = {
name: "ERC721ClaimCustom",
namespace: "nft.drop.claim",
docLinks: {
sdk: "sdk.erc721claimable",
@@ -88,11 +113,11 @@ export const FEATURE_NFT_LAZY_MINTABLE = {
abis: [Erc721Abi, ILazyMintAbi],
features: {
[FEATURE_NFT_REVEALABLE.name]: FEATURE_NFT_REVEALABLE,
[FEATURE_NFT_CLAIMABLE.name]: FEATURE_NFT_CLAIMABLE,
[FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2.name]:
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2,
[FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V1.name]:
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V1,
[FEATURE_NFT_CLAIM_CUSTOM.name]: FEATURE_NFT_CLAIM_CUSTOM,
[FEATURE_NFT_CLAIM_CONDITIONS_V1.name]: FEATURE_NFT_CLAIM_CONDITIONS_V1,
[FEATURE_NFT_CLAIM_CONDITIONS_V2.name]: FEATURE_NFT_CLAIM_CONDITIONS_V2,
[FEATURE_NFT_CLAIM_PHASES_V1.name]: FEATURE_NFT_CLAIM_PHASES_V1,
[FEATURE_NFT_CLAIM_PHASES_V2.name]: FEATURE_NFT_CLAIM_PHASES_V2,
},
} as const;
@@ -120,8 +145,8 @@ export const FEATURE_NFT_MINTABLE = {
},
} as const;
export const FEATURE_NFT_SIGNATURE_MINTABLE = {
name: "ERC721SignatureMint",
export const FEATURE_NFT_SIGNATURE_MINTABLE_V2 = {
name: "ERC721SignatureMintV2",
namespace: "nft.signature",
docLinks: {
sdk: "sdk.erc721signaturemint",
@@ -131,6 +156,17 @@ export const FEATURE_NFT_SIGNATURE_MINTABLE = {
features: {},
} as const;
export const FEATURE_NFT_SIGNATURE_MINTABLE_V1 = {
name: "ERC721SignatureMintV1",
namespace: "nft.signature",
docLinks: {
sdk: "sdk.erc721signaturemint",
contracts: "ISignatureMintERC721",
},
abis: [TokenERC721Abi],
features: {},
} as const;
export const FEATURE_NFT_ENUMERABLE = {
name: "ERC721Enumerable",
namespace: "nft.query.owned",
@@ -168,7 +204,8 @@ export const FEATURE_NFT = {
[FEATURE_NFT_SUPPLY.name]: FEATURE_NFT_SUPPLY,
[FEATURE_NFT_MINTABLE.name]: FEATURE_NFT_MINTABLE,
[FEATURE_NFT_LAZY_MINTABLE.name]: FEATURE_NFT_LAZY_MINTABLE,
[FEATURE_NFT_SIGNATURE_MINTABLE.name]: FEATURE_NFT_SIGNATURE_MINTABLE,
[FEATURE_NFT_SIGNATURE_MINTABLE_V1.name]: FEATURE_NFT_SIGNATURE_MINTABLE_V1,
[FEATURE_NFT_SIGNATURE_MINTABLE_V2.name]: FEATURE_NFT_SIGNATURE_MINTABLE_V2,
[FEATURE_NFT_TIERED_DROP.name]: FEATURE_NFT_TIERED_DROP,
},
} as const;
-12
View File
@@ -40,10 +40,6 @@ export const EditionDropInitializer = {
...[network, address, storage, options]: InitalizeParams
) => {
const [, provider] = getSignerAndProvider(network, options);
const contractInfo = await getPrebuiltInfo(address, provider);
if (!contractInfo || contractInfo.type !== "DropERC1155") {
throw new Error("Contract is not a DropERC1155");
}
const [abi, contract, _network] = await Promise.all([
await EditionDropInitializer.getAbi(address, provider),
import("./prebuilt-implementations/edition-drop"),
@@ -213,10 +209,6 @@ export const NFTDropInitializer = {
...[network, address, storage, options]: InitalizeParams
) => {
const [, provider] = getSignerAndProvider(network, options);
const contractInfo = await getPrebuiltInfo(address, provider);
if (!contractInfo || contractInfo.type !== "DropERC721") {
throw new Error("Contract is not a DropERC721");
}
const [abi, contract, _network] = await Promise.all([
NFTDropInitializer.getAbi(address, provider),
import("./prebuilt-implementations/nft-drop"),
@@ -368,10 +360,6 @@ export const TokenDropInitializer = {
...[network, address, storage, options]: InitalizeParams
) => {
const [, provider] = getSignerAndProvider(network, options);
const contractInfo = await getPrebuiltInfo(address, provider);
if (!contractInfo || contractInfo.type !== "DropERC20") {
throw new Error("Contract is not a DropERC20");
}
const [abi, contract, _network] = await Promise.all([
TokenDropInitializer.getAbi(address, provider),
import("./prebuilt-implementations/token-drop"),
@@ -323,9 +323,15 @@ export class Marketplace implements UpdateableNetwork {
*/
public async getOffers(listingId: BigNumberish): Promise<Offer[]> {
// get all new offer events from this contract
const events = await this.events.getEvents<NewOfferEventObject>("NewOffer");
// get only the events for this listing id
const listingEvents = events.filter((e) => e.data.listingId.eq(listingId));
const listingEvents = await this.events.getEvents<NewOfferEventObject>(
"NewOffer",
{
order: "desc",
filters: {
listingId,
},
},
);
// derive the offers from the events
return await Promise.all(
listingEvents.map(async (e): Promise<Offer> => {
@@ -4,10 +4,12 @@ import {
fetchExtendedReleaseMetadata,
fetchPreDeployMetadata,
} from "../../common/index";
import { ChainId } from "../../constants";
import { getContractAddressByChainId } from "../../constants/addresses";
import {
EditionDropInitializer,
EditionInitializer,
getContractName,
MarketplaceInitializer,
MultiwrapInitializer,
NFTCollectionInitializer,
@@ -397,14 +399,61 @@ export class ContractDeployer extends RPCConnectionHandler {
contractMetadata: z.input<
DeploySchemaForPrebuiltContractType<TContractType>
>,
version?: number,
version: string = "latest",
): Promise<string> {
const parsed =
const activeChainId = (await this.getProvider().getNetwork()).chainId;
const parsedMetadata =
PREBUILT_CONTRACTS_MAP[contractType].schema.deploy.parse(
contractMetadata,
);
const factory = await this.getFactory();
return await factory.deploy(contractType, parsed, version);
if (
activeChainId === ChainId.Hardhat ||
activeChainId === ChainId.Localhost
) {
//
// old behavior for hardhat and localhost chains
//
// parse version into the first number of the version string (or undefined if unparseable)
let parsedVersion: number | undefined = undefined;
try {
parsedVersion = parseInt(version);
if (isNaN(parsedVersion)) {
parsedVersion = undefined;
}
} catch (e) {
parsedVersion = undefined;
}
return await factory.deploy(contractType, parsedMetadata, parsedVersion);
}
//
// new behavior for all other chains
//
// resolve contract name from type
const contractName = getContractName(contractType);
invariant(contractName, "contract name not found");
// get deploy arugments for the contractType
// first upload the contractmetadata
const contractURI = await this.storage.upload(parsedMetadata);
// the get the deploy arguments
const constructorParams = await factory.getDeployArguments(
contractType,
parsedMetadata,
contractURI,
);
return this.deployReleasedContract(
// 0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024 === deployer.thirdweb.eth
"0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024",
contractName,
constructorParams,
version,
);
}
/**
@@ -428,13 +477,14 @@ export class ContractDeployer extends RPCConnectionHandler {
releaserAddress: string,
contractName: string,
constructorParams: any[],
version = "latest",
): Promise<string> {
const release = await new ThirdwebSDK("polygon")
.getPublisher()
.getLatest(releaserAddress, contractName);
.getVersion(releaserAddress, contractName, version);
if (!release) {
throw new Error(
`No release found for '${contractName}' by ${releaserAddress}`,
`No release found for '${contractName}' at version '${version}' by '${releaserAddress}'`,
);
}
return await this.deployContractFromUri(
@@ -293,6 +293,30 @@ export class ContractPublisher extends RPCConnectionHandler {
return contractStructs.map((d) => this.toPublishedContract(d));
}
public async getVersion(
publisherAddress: string,
contractId: string,
version = "latest",
): Promise<PublishedContract | undefined> {
if (version === "latest") {
return this.getLatest(publisherAddress, contractId);
}
const allVersions = await this.getAllVersions(publisherAddress, contractId);
// get the metadata for each version
const versionMetadata = await Promise.all(
allVersions.map((contract) => this.fetchPublishedContractInfo(contract)),
);
// find the version that matches the version string
const versionMatch = versionMetadata.find(
(metadata) => metadata.publishedMetadata.version === version,
);
invariant(versionMatch, "Contract version not found");
// match the version back to the contract based on the release timestamp
return allVersions.find(
(contract) => contract.timestamp === versionMatch.publishedTimestamp,
);
}
public async getLatest(
publisherAddress: string,
contractId: string,
@@ -5,7 +5,6 @@ import {
abstractContractModelToNew,
convertQuantityToBigNumber,
fetchSnapshotEntryForAddress,
getClaimerProofs,
legacyContractModelToAbstract,
newContractModelToAbstract,
prepareClaim,
@@ -279,20 +278,9 @@ export class DropClaimConditions<
claimCondition.merkleRootHash,
);
const hasAllowList = merkleRootArray.length > 0;
let allowListEntry;
let allowListEntry: SnapshotEntryWithProof | null = null;
if (hasAllowList) {
const merkleLower = claimCondition.merkleRootHash.toString();
const metadata = await this.metadata.get();
allowListEntry = await getClaimerProofs(
addressToCheck,
merkleLower,
await this.getTokenDecimals(),
metadata.merkle,
this.storage,
this.contractWrapper.getProvider(),
this.getSnapshotFormatVersion(),
);
allowListEntry = await this.getClaimerProofs(addressToCheck);
if (
!allowListEntry &&
(this.isLegacySinglePhaseDrop(this.contractWrapper) ||
@@ -305,6 +293,13 @@ export class DropClaimConditions<
if (allowListEntry) {
try {
const claimVerification = await this.prepareClaim(
quantity,
false,
decimals,
addressToCheck,
);
let validMerkleProof;
if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
activeConditionIndex =
@@ -315,8 +310,8 @@ export class DropClaimConditions<
activeConditionIndex,
addressToCheck,
quantity,
allowListEntry.proof,
allowListEntry.maxClaimable,
claimVerification.proofs,
claimVerification.maxClaimable,
);
if (!validMerkleProof) {
reasons.push(ClaimEligibility.AddressNotAllowed);
@@ -328,8 +323,8 @@ export class DropClaimConditions<
addressToCheck,
quantity,
{
proof: allowListEntry.proof,
maxQuantityInAllowlist: allowListEntry.maxClaimable,
proof: claimVerification.proofs,
maxQuantityInAllowlist: claimVerification.maxClaimable,
},
);
if (!validMerkleProof) {
@@ -340,25 +335,23 @@ export class DropClaimConditions<
await this.contractWrapper.readContract.verifyClaim(
addressToCheck,
quantity,
claimCondition.currencyAddress,
claimCondition.price,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: allowListEntry.proof,
quantityLimitPerWallet: allowListEntry.maxClaimable,
currency: allowListEntry.currencyAddress,
pricePerToken: allowListEntry.price,
proof: claimVerification.proofs,
quantityLimitPerWallet: claimVerification.maxClaimable,
currency: claimVerification.currencyAddressInProof,
pricePerToken: claimVerification.priceInProof,
} as IDropSinglePhase.AllowlistProofStruct,
);
// TODO (cc) in new override format, anyone can claim (no allow list restriction)
// TODO (cc) instead check if maxClaimablePerWallet is 0 and this address has no overrides
// TODO (cc) meaning this address is not allowed to claim
if (
(convertQuantityToBigNumber(
claimCondition.maxClaimablePerWallet,
decimals,
).eq(0) &&
allowListEntry.maxClaimable === ethers.constants.MaxUint256) ||
allowListEntry.maxClaimable === BigNumber.from(0)
claimVerification.maxClaimable ===
ethers.constants.MaxUint256) ||
claimVerification.maxClaimable === BigNumber.from(0)
) {
reasons.push(ClaimEligibility.AddressNotAllowed);
return reasons;
@@ -370,13 +363,13 @@ export class DropClaimConditions<
activeConditionIndex,
addressToCheck,
quantity,
claimCondition.currencyAddress,
claimCondition.price,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: allowListEntry.proof,
quantityLimitPerWallet: allowListEntry.maxClaimable,
currency: allowListEntry.currencyAddress,
pricePerToken: allowListEntry.price,
proof: claimVerification.proofs,
quantityLimitPerWallet: claimVerification.maxClaimable,
currency: claimVerification.currencyAddressInProof,
pricePerToken: claimVerification.priceInProof,
} as IDropSinglePhase.AllowlistProofStruct,
);
if (
@@ -384,8 +377,9 @@ export class DropClaimConditions<
claimCondition.maxClaimablePerWallet,
decimals,
).eq(0) &&
allowListEntry.maxClaimable === ethers.constants.MaxUint256) ||
allowListEntry.maxClaimable === BigNumber.from(0)
claimVerification.maxClaimable ===
ethers.constants.MaxUint256) ||
claimVerification.maxClaimable === BigNumber.from(0)
) {
reasons.push(ClaimEligibility.AddressNotAllowed);
return reasons;
@@ -578,6 +572,48 @@ export class DropClaimConditions<
);
}
}
// if using new snapshot format, make sure that maxClaimablePerWallet is set if allowlist is set as well
if (
this.isNewSinglePhaseDrop(this.contractWrapper) ||
this.isNewMultiphaseDrop(this.contractWrapper)
) {
claimConditionsProcessed.forEach((cc) => {
if (
cc.snapshot &&
cc.snapshot.length > 0 &&
(cc.maxClaimablePerWallet === undefined ||
cc.maxClaimablePerWallet === "unlimited")
) {
throw new Error(
"maxClaimablePerWallet must be set to a specific value when an allowlist is set.\n" +
"Example: Set it to 0 to only allow addresses in the allowlist to claim the amount specified in the allowlist.\n" +
"contract.claimConditions.set([{ snapshot: [{ address: '0x...', maxClaimable: 1 }], maxClaimablePerWallet: 0 }])",
);
}
if (
cc.snapshot &&
cc.snapshot.length > 0 &&
cc.maxClaimablePerWallet?.toString() === "0" &&
cc.snapshot
.map((s) => {
if (typeof s === "string") {
return 0;
} else {
return Number(s.maxClaimable?.toString() || 0);
}
})
.reduce((acc, current) => {
return acc + current;
}, 0) === 0
) {
throw new Error(
"maxClaimablePerWallet is set to 0, and all addresses in the allowlist have max claimable 0. This means that no one can claim.",
);
}
});
}
// process inputs
const { snapshotInfos, sortedConditions } =
await processClaimConditionInputs(
@@ -706,8 +742,13 @@ export class DropClaimConditions<
quantity: BigNumberish,
checkERC20Allowance: boolean,
decimals = 0,
address?: string,
): Promise<ClaimVerification> {
const addressToClaim = address
? address
: await this.contractWrapper.getSignerAddress();
return prepareClaim(
addressToClaim,
quantity,
await this.getActive(),
async () => (await this.metadata.get()).merkle,
@@ -724,13 +765,12 @@ export class DropClaimConditions<
quantity: BigNumberish,
claimVerification: ClaimVerification,
): Promise<any[]> {
const activeClaimCondition = await this.getActive();
if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
return [
destinationAddress,
quantity,
activeClaimCondition.currencyAddress,
activeClaimCondition.price,
claimVerification.currencyAddress,
claimVerification.price,
claimVerification.proofs,
claimVerification.maxClaimable,
];
@@ -738,8 +778,8 @@ export class DropClaimConditions<
return [
destinationAddress,
quantity,
activeClaimCondition.currencyAddress,
activeClaimCondition.price,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: claimVerification.proofs,
maxQuantityInAllowlist: claimVerification.maxClaimable,
@@ -750,13 +790,13 @@ export class DropClaimConditions<
return [
destinationAddress,
quantity,
activeClaimCondition.currencyAddress,
activeClaimCondition.price,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: claimVerification.proofs,
quantityLimitPerWallet: claimVerification.maxClaimable,
pricePerToken: claimVerification.price,
currency: claimVerification.currencyAddress,
pricePerToken: claimVerification.priceInProof,
currency: claimVerification.currencyAddressInProof,
} as IDropSinglePhase.AllowlistProofStruct,
ethers.utils.toUtf8Bytes(""),
];
@@ -803,8 +843,14 @@ export class DropClaimConditions<
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<DropSinglePhase> {
return (
!hasFunction<DropSinglePhase>("getClaimConditionById", contractWrapper) &&
hasFunction<DropSinglePhase>("getSupplyClaimedByWallet", contractWrapper)
detectContractFeature<DropSinglePhase>(
contractWrapper,
"ERC721ClaimConditionsV2",
) ||
detectContractFeature<DropSinglePhase>(
contractWrapper,
"ERC20ClaimConditionsV2",
)
);
}
@@ -812,8 +858,8 @@ export class DropClaimConditions<
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<Drop> {
return (
hasFunction<Drop>("getClaimConditionById", contractWrapper) &&
hasFunction<Drop>("getSupplyClaimedByWallet", contractWrapper)
detectContractFeature<Drop>(contractWrapper, "ERC721ClaimPhasesV2") ||
detectContractFeature<Drop>(contractWrapper, "ERC20ClaimPhasesV2")
);
}
@@ -821,10 +867,14 @@ export class DropClaimConditions<
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<DropSinglePhase_V1> {
return (
!hasFunction<DropSinglePhase_V1>(
"getClaimConditionById",
detectContractFeature<DropSinglePhase_V1>(
contractWrapper,
) && hasFunction<DropSinglePhase_V1>("getClaimTimestamp", contractWrapper)
"ERC721ClaimConditionsV1",
) ||
detectContractFeature<DropSinglePhase_V1>(
contractWrapper,
"ERC20ClaimConditionsV1",
)
);
}
@@ -832,8 +882,14 @@ export class DropClaimConditions<
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<DropERC721_V3 | DropERC20_V2> {
return (
hasFunction<DropERC721_V3>("getClaimConditionById", contractWrapper) &&
hasFunction<DropERC721_V3>("setWalletClaimCount", contractWrapper)
detectContractFeature<DropERC721_V3 | DropERC20_V2>(
contractWrapper,
"ERC721ClaimPhasesV1",
) ||
detectContractFeature<DropERC721_V3 | DropERC20_V2>(
contractWrapper,
"ERC20ClaimPhasesV1",
)
);
}
@@ -3,7 +3,6 @@ import {
abstractContractModelToLegacy,
abstractContractModelToNew,
fetchSnapshotEntryForAddress,
getClaimerProofs,
legacyContractModelToAbstract,
newContractModelToAbstract,
prepareClaim,
@@ -12,7 +11,10 @@ import {
updateExistingClaimConditions,
} from "../../common/claim-conditions";
import { isNativeToken } from "../../common/currency";
import { hasFunction } from "../../common/feature-detection";
import {
detectContractFeature,
hasFunction,
} from "../../common/feature-detection";
import { SnapshotFormatVersion } from "../../common/sharded-merkle-tree";
import { isNode } from "../../common/utils";
import { ClaimEligibility } from "../../enums";
@@ -272,20 +274,9 @@ export class DropErc1155ClaimConditions<
claimCondition.merkleRootHash,
);
const hasAllowList = merkleRootArray.length > 0;
let allowListEntry;
let allowListEntry: SnapshotEntryWithProof | null = null;
if (hasAllowList) {
const merkleLower = claimCondition.merkleRootHash.toString();
const metadata = await this.metadata.get();
allowListEntry = await getClaimerProofs(
addressToCheck,
merkleLower,
0,
metadata.merkle,
this.storage,
this.contractWrapper.getProvider(),
this.getSnapshotFormatVersion(),
);
allowListEntry = await this.getClaimerProofs(tokenId, addressToCheck);
if (
!allowListEntry &&
(this.isLegacySinglePhaseDrop(this.contractWrapper) ||
@@ -298,6 +289,13 @@ export class DropErc1155ClaimConditions<
if (allowListEntry) {
try {
const claimVerification = await this.prepareClaim(
tokenId,
quantity,
false,
addressToCheck,
);
let validMerkleProof;
if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
activeConditionIndex =
@@ -311,8 +309,8 @@ export class DropErc1155ClaimConditions<
addressToCheck,
tokenId,
quantity,
allowListEntry.proof,
allowListEntry.maxClaimable,
claimVerification.proofs,
claimVerification.maxClaimable,
);
if (!validMerkleProof) {
reasons.push(ClaimEligibility.AddressNotAllowed);
@@ -325,8 +323,8 @@ export class DropErc1155ClaimConditions<
addressToCheck,
quantity,
{
proof: allowListEntry.proof,
maxQuantityInAllowlist: allowListEntry.maxClaimable,
proof: claimVerification.proofs,
maxQuantityInAllowlist: claimVerification.maxClaimable,
},
);
if (!validMerkleProof) {
@@ -338,13 +336,13 @@ export class DropErc1155ClaimConditions<
tokenId,
addressToCheck,
quantity,
claimCondition.currencyAddress,
claimCondition.price,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: allowListEntry.proof,
quantityLimitPerWallet: allowListEntry.maxClaimable,
currency: allowListEntry.currencyAddress,
pricePerToken: allowListEntry.price,
proof: claimVerification.proofs,
quantityLimitPerWallet: claimVerification.maxClaimable,
currency: claimVerification.currencyAddressInProof,
pricePerToken: claimVerification.priceInProof,
} as IDropSinglePhase.AllowlistProofStruct,
);
// TODO (cc) in new override format, anyone can claim (no allow list restriction)
@@ -352,8 +350,9 @@ export class DropErc1155ClaimConditions<
// TODO (cc) meaning this address is not allowed to claim
if (
(claimCondition.maxClaimablePerWallet === "0" &&
allowListEntry.maxClaimable === ethers.constants.MaxUint256) ||
allowListEntry.maxClaimable === BigNumber.from(0)
claimVerification.maxClaimable ===
ethers.constants.MaxUint256) ||
claimVerification.maxClaimable === BigNumber.from(0)
) {
reasons.push(ClaimEligibility.AddressNotAllowed);
return reasons;
@@ -368,19 +367,20 @@ export class DropErc1155ClaimConditions<
addressToCheck,
tokenId,
quantity,
claimCondition.currencyAddress,
claimCondition.price,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: allowListEntry.proof,
quantityLimitPerWallet: allowListEntry.maxClaimable,
currency: allowListEntry.currencyAddress,
pricePerToken: allowListEntry.price,
proof: claimVerification.proofs,
quantityLimitPerWallet: claimVerification.maxClaimable,
currency: claimVerification.currencyAddressInProof,
pricePerToken: claimVerification.priceInProof,
} as IDropSinglePhase.AllowlistProofStruct,
);
if (
(claimCondition.maxClaimablePerWallet === "0" &&
allowListEntry.maxClaimable === ethers.constants.MaxUint256) ||
allowListEntry.maxClaimable === BigNumber.from(0)
claimVerification.maxClaimable ===
ethers.constants.MaxUint256) ||
claimVerification.maxClaimable === BigNumber.from(0)
) {
reasons.push(ClaimEligibility.AddressNotAllowed);
return reasons;
@@ -621,6 +621,47 @@ export class DropErc1155ClaimConditions<
);
}
}
// if using new snapshot format, make sure that maxClaimablePerWallet is set if allowlist is set as well
if (
this.isNewSinglePhaseDrop(this.contractWrapper) ||
this.isNewMultiphaseDrop(this.contractWrapper)
) {
claimConditionsProcessed.forEach((cc) => {
if (
cc.snapshot &&
cc.snapshot.length > 0 &&
(cc.maxClaimablePerWallet === undefined ||
cc.maxClaimablePerWallet === "unlimited")
) {
throw new Error(
"maxClaimablePerWallet must be set to a specific value when an allowlist is set.\n" +
"Set it to 0 to only allow addresses in the allowlist to claim the amount specified in the allowlist." +
"\n\nex:\n" +
"contract.claimConditions.set(tokenId, [{ snapshot: [{ address: '0x...', maxClaimable: 1 }], maxClaimablePerWallet: 0 }])",
);
}
if (
cc.snapshot &&
cc.snapshot.length > 0 &&
cc.maxClaimablePerWallet?.toString() === "0" &&
cc.snapshot
.map((s) => {
if (typeof s === "string") {
return 0;
} else {
return Number(s.maxClaimable?.toString() || 0);
}
})
.reduce((acc, current) => {
return acc + current;
}, 0) === 0
) {
throw new Error(
"maxClaimablePerWallet is set to 0, and all addresses in the allowlist have max claimable 0. This means that no one can claim.",
);
}
});
}
// process inputs
const { snapshotInfos, sortedConditions } =
await processClaimConditionInputs(
@@ -763,8 +804,13 @@ export class DropErc1155ClaimConditions<
tokenId: BigNumberish,
quantity: BigNumberish,
checkERC20Allowance: boolean,
address?: string,
): Promise<ClaimVerification> {
const addressToClaim = address
? address
: await this.contractWrapper.getSignerAddress();
return prepareClaim(
addressToClaim,
quantity,
await this.getActive(tokenId),
async () => (await this.metadata.get()).merkle,
@@ -782,14 +828,13 @@ export class DropErc1155ClaimConditions<
quantity: BigNumberish,
claimVerification: ClaimVerification,
): Promise<any[]> {
const activeClaimCondition = await this.getActive(tokenId);
if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
return [
destinationAddress,
tokenId,
quantity,
activeClaimCondition.currencyAddress,
activeClaimCondition.price,
claimVerification.currencyAddress,
claimVerification.price,
claimVerification.proofs,
claimVerification.maxClaimable,
];
@@ -798,8 +843,8 @@ export class DropErc1155ClaimConditions<
destinationAddress,
tokenId,
quantity,
activeClaimCondition.currencyAddress,
activeClaimCondition.price,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: claimVerification.proofs,
maxQuantityInAllowlist: claimVerification.maxClaimable,
@@ -811,13 +856,13 @@ export class DropErc1155ClaimConditions<
destinationAddress,
tokenId,
quantity,
activeClaimCondition.currencyAddress,
activeClaimCondition.price,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: claimVerification.proofs,
quantityLimitPerWallet: claimVerification.maxClaimable,
pricePerToken: claimVerification.price,
currency: claimVerification.currencyAddress,
pricePerToken: claimVerification.priceInProof,
currency: claimVerification.currencyAddressInProof,
} as IDropSinglePhase.AllowlistProofStruct,
ethers.utils.toUtf8Bytes(""),
];
@@ -862,45 +907,36 @@ export class DropErc1155ClaimConditions<
isNewSinglePhaseDrop(
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<DropSinglePhase1155> {
return (
!hasFunction<DropSinglePhase1155>(
"getClaimConditionById",
contractWrapper,
) &&
hasFunction<DropSinglePhase1155>(
"getSupplyClaimedByWallet",
contractWrapper,
)
return detectContractFeature<DropSinglePhase1155>(
contractWrapper,
"ERC1155ClaimConditionsV2",
);
}
isNewMultiphaseDrop(
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<Drop1155> {
return (
hasFunction<Drop1155>("getClaimConditionById", contractWrapper) &&
hasFunction<Drop1155>("getSupplyClaimedByWallet", contractWrapper)
return detectContractFeature<Drop1155>(
contractWrapper,
"ERC1155ClaimPhasesV2",
);
}
isLegacySinglePhaseDrop(
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<DropSinglePhase1155_V1> {
return (
!hasFunction<DropSinglePhase1155_V1>(
"getClaimConditionById",
contractWrapper,
) &&
hasFunction<DropSinglePhase1155_V1>("getClaimTimestamp", contractWrapper)
return detectContractFeature<DropSinglePhase1155_V1>(
contractWrapper,
"ERC1155ClaimConditionsV1",
);
}
isLegacyMultiPhaseDrop(
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<DropERC1155_V2> {
return (
hasFunction<DropERC1155_V2>("getClaimConditionById", contractWrapper) &&
hasFunction<DropERC1155_V2>("setWalletClaimCount", contractWrapper)
return detectContractFeature<DropERC1155_V2>(
contractWrapper,
"ERC1155ClaimPhasesV1",
);
}
@@ -1,4 +1,4 @@
import { FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS } from "../../constants/erc1155-features";
import { FEATURE_EDITION_CLAIM_CONDITIONS_V2 } from "../../constants/erc1155-features";
import { CustomContractSchema } from "../../schema/contracts/custom";
import { ClaimOptions } from "../../types/claim-conditions/claim-conditions";
import { BaseClaimConditionERC1155 } from "../../types/eips";
@@ -21,7 +21,7 @@ import { BigNumberish } from "ethers";
* ```
*/
export class Erc1155ClaimableWithConditions implements DetectableFeature {
featureName = FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS.name;
featureName = FEATURE_EDITION_CLAIM_CONDITIONS_V2.name;
public conditions: DropErc1155ClaimConditions<BaseClaimConditionERC1155>;
private contractWrapper: ContractWrapper<BaseClaimConditionERC1155>;
@@ -1,5 +1,5 @@
import { calculateClaimCost } from "../../common/claim-conditions";
import { FEATURE_EDITION_CLAIMABLE } from "../../constants/erc1155-features";
import { FEATURE_EDITION_CLAIM_CUSTOM } from "../../constants/erc1155-features";
import { ClaimOptions } from "../../types";
import { DetectableFeature } from "../interfaces/DetectableFeature";
import { TransactionResult } from "../types";
@@ -18,7 +18,7 @@ import { BigNumberish, CallOverrides } from "ethers";
* ```
*/
export class ERC1155Claimable implements DetectableFeature {
featureName = FEATURE_EDITION_CLAIMABLE.name;
featureName = FEATURE_EDITION_CLAIM_CUSTOM.name;
private contractWrapper: ContractWrapper<IClaimableERC1155>;
@@ -1,12 +1,9 @@
import { NFTMetadata, NFTMetadataOrUri } from "../../../core/schema/nft";
import {
detectContractFeature,
hasFunction,
} from "../../common/feature-detection";
import { detectContractFeature } from "../../common/feature-detection";
import { getPrebuiltInfo } from "../../common/legacy";
import { uploadOrExtractURIs } from "../../common/nft";
import {
FEATURE_EDITION_LAZY_MINTABLE,
FEATURE_EDITION_LAZY_MINTABLE_V2,
FEATURE_EDITION_REVEALABLE,
} from "../../constants/erc1155-features";
import {
@@ -28,7 +25,7 @@ import { ThirdwebStorage } from "@thirdweb-dev/storage";
import { ethers } from "ethers";
export class Erc1155LazyMintable implements DetectableFeature {
featureName = FEATURE_EDITION_LAZY_MINTABLE.name;
featureName = FEATURE_EDITION_LAZY_MINTABLE_V2.name;
/**
* Delayed reveal
@@ -91,7 +88,7 @@ export class Erc1155LazyMintable implements DetectableFeature {
this.storage = storage;
this.claim = this.detectErc1155Claimable();
this.claimWithConditions = this.detectErc1155ClaimableWithConditions();
this.revealer = this.detectErc721Revealable();
this.revealer = this.detectErc1155Revealable();
}
/**
@@ -184,9 +181,8 @@ export class Erc1155LazyMintable implements DetectableFeature {
if (
detectContractFeature<IClaimableERC1155>(
this.contractWrapper,
"ERC1155Claimable",
) &&
!hasFunction("setClaimConditions", this.contractWrapper)
"ERC1155ClaimCustom",
)
) {
return new ERC1155Claimable(this.contractWrapper);
}
@@ -199,7 +195,19 @@ export class Erc1155LazyMintable implements DetectableFeature {
if (
detectContractFeature<BaseClaimConditionERC1155>(
this.contractWrapper,
"ERC1155ClaimableWithConditions",
"ERC1155ClaimConditionsV1",
) ||
detectContractFeature<BaseClaimConditionERC1155>(
this.contractWrapper,
"ERC1155ClaimConditionsV2",
) ||
detectContractFeature<BaseClaimConditionERC1155>(
this.contractWrapper,
"ERC1155ClaimPhasesV1",
) ||
detectContractFeature<BaseClaimConditionERC1155>(
this.contractWrapper,
"ERC1155ClaimPhasesV2",
)
) {
return new Erc1155ClaimableWithConditions(
@@ -210,7 +218,7 @@ export class Erc1155LazyMintable implements DetectableFeature {
return undefined;
}
private detectErc721Revealable():
private detectErc1155Revealable():
| DelayedReveal<BaseDelayedRevealERC1155>
| undefined {
if (
+12 -8
View File
@@ -12,13 +12,13 @@ import {
FEATURE_EDITION,
FEATURE_EDITION_BATCH_MINTABLE,
FEATURE_EDITION_BURNABLE,
FEATURE_EDITION_LAZY_MINTABLE,
FEATURE_EDITION_ENUMERABLE,
FEATURE_EDITION_MINTABLE,
FEATURE_EDITION_REVEALABLE,
FEATURE_EDITION_SIGNATURE_MINTABLE,
FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS,
FEATURE_EDITION_CLAIMABLE,
FEATURE_EDITION_CLAIM_CUSTOM,
FEATURE_EDITION_CLAIM_CONDITIONS_V2,
FEATURE_EDITION_LAZY_MINTABLE_V2,
} from "../../constants/erc1155-features";
import { AirdropInputSchema } from "../../schema/contracts/common/airdrop";
import { EditionMetadataOrUri } from "../../schema/tokens/edition";
@@ -770,7 +770,7 @@ export class Erc1155<
): Promise<TransactionResultWithId<NFTMetadata>[]> {
return assertEnabled(
this.lazyMintable,
FEATURE_EDITION_LAZY_MINTABLE,
FEATURE_EDITION_LAZY_MINTABLE_V2,
).lazyMint(metadatas, options);
}
@@ -808,7 +808,7 @@ export class Erc1155<
options,
);
}
throw new ExtensionNotImplementedError(FEATURE_EDITION_CLAIMABLE);
throw new ExtensionNotImplementedError(FEATURE_EDITION_CLAIM_CUSTOM);
}
/**
@@ -887,7 +887,7 @@ export class Erc1155<
if (claim) {
return claim.to(destinationAddress, tokenId, quantity, options);
}
throw new ExtensionNotImplementedError(FEATURE_EDITION_CLAIMABLE);
throw new ExtensionNotImplementedError(FEATURE_EDITION_CLAIM_CUSTOM);
}
/**
@@ -916,7 +916,7 @@ export class Erc1155<
get claimConditions() {
return assertEnabled(
this.lazyMintable?.claimWithConditions,
FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS,
FEATURE_EDITION_CLAIM_CONDITIONS_V2,
).conditions;
}
@@ -1041,7 +1041,11 @@ export class Erc1155<
if (
detectContractFeature<BaseDropERC1155>(
this.contractWrapper,
"ERC1155LazyMintable",
"ERC1155LazyMintableV1",
) ||
detectContractFeature<BaseDropERC1155>(
this.contractWrapper,
"ERC1155LazyMintableV2",
)
) {
return new Erc1155LazyMintable(this, this.contractWrapper, this.storage);
@@ -1,4 +1,4 @@
import { FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS } from "../../constants/erc20-features";
import { FEATURE_TOKEN_CLAIM_CONDITIONS_V2 } from "../../constants/erc20-features";
import { CustomContractSchema } from "../../schema/contracts/custom";
import { ClaimOptions } from "../../types";
import { Amount } from "../../types/currency";
@@ -21,7 +21,7 @@ import { ThirdwebStorage } from "@thirdweb-dev/storage";
* ```
*/
export class Erc20ClaimableWithConditions implements DetectableFeature {
featureName = FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS.name;
featureName = FEATURE_TOKEN_CLAIM_CONDITIONS_V2.name;
/**
* Configure claim conditions
* @remarks Define who can claim NFTs in the collection, when and how many.
+16 -4
View File
@@ -11,7 +11,7 @@ import {
FEATURE_TOKEN_BATCH_MINTABLE,
FEATURE_TOKEN_BURNABLE,
FEATURE_TOKEN_SIGNATURE_MINTABLE,
FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS,
FEATURE_TOKEN_CLAIM_CONDITIONS_V2,
} from "../../constants/erc20-features";
import { TokenMintInput } from "../../schema";
import { Currency, CurrencyValue, Amount, ClaimOptions } from "../../types";
@@ -516,7 +516,7 @@ export class Erc20<
): Promise<TransactionResult> {
return assertEnabled(
this.droppable?.claim,
FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS,
FEATURE_TOKEN_CLAIM_CONDITIONS_V2,
).to(destinationAddress, amount, options);
}
@@ -546,7 +546,7 @@ export class Erc20<
get claimConditions() {
return assertEnabled(
this.droppable?.claim,
FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS,
FEATURE_TOKEN_CLAIM_CONDITIONS_V2,
).conditions;
}
@@ -620,7 +620,19 @@ export class Erc20<
if (
detectContractFeature<BaseDropERC20>(
this.contractWrapper,
"ERC20ClaimableWithConditions",
"ERC20ClaimConditionsV1",
) ||
detectContractFeature<BaseDropERC20>(
this.contractWrapper,
"ERC20ClaimConditionsV2",
) ||
detectContractFeature<BaseDropERC20>(
this.contractWrapper,
"ERC20ClaimPhasesV1",
) ||
detectContractFeature<BaseDropERC20>(
this.contractWrapper,
"ERC20ClaimPhasesV2",
)
) {
return new Erc20Droppable(this, this.contractWrapper, this.storage);
@@ -1,5 +1,5 @@
import { NFT } from "../../../core/schema/nft";
import { FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2 } from "../../constants/erc721-features";
import { FEATURE_NFT_CLAIM_CONDITIONS_V2 } from "../../constants/erc721-features";
import { CustomContractSchema } from "../../schema/contracts/custom";
import { ClaimOptions } from "../../types";
import { BaseClaimConditionERC721 } from "../../types/eips";
@@ -24,7 +24,7 @@ import { BigNumber, BigNumberish } from "ethers";
* ```
*/
export class Erc721ClaimableWithConditions implements DetectableFeature {
featureName = FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2.name;
featureName = FEATURE_NFT_CLAIM_CONDITIONS_V2.name;
/**
* Configure claim conditions
@@ -1,6 +1,6 @@
import { NFT } from "../../../core/schema/nft";
import { calculateClaimCost } from "../../common/claim-conditions";
import { FEATURE_NFT_CLAIMABLE } from "../../constants/erc721-features";
import { FEATURE_NFT_CLAIM_CUSTOM } from "../../constants/erc721-features";
import { ClaimOptions } from "../../types";
import { DetectableFeature } from "../interfaces/DetectableFeature";
import { TransactionResultWithId } from "../types";
@@ -21,7 +21,7 @@ import { BigNumber, BigNumberish, CallOverrides } from "ethers";
* ```
*/
export class Erc721Claimable implements DetectableFeature {
featureName = FEATURE_NFT_CLAIMABLE.name;
featureName = FEATURE_NFT_CLAIM_CUSTOM.name;
private erc721: Erc721;
private contractWrapper: ContractWrapper<IClaimableERC721>;
@@ -1,8 +1,5 @@
import { NFTMetadata, NFTMetadataOrUri } from "../../../core/schema/nft";
import {
detectContractFeature,
hasFunction,
} from "../../common/feature-detection";
import { detectContractFeature } from "../../common/feature-detection";
import { getBaseUriFromBatch, uploadOrExtractURIs } from "../../common/nft";
import {
FEATURE_NFT_LAZY_MINTABLE,
@@ -194,11 +191,19 @@ export class Erc721LazyMintable implements DetectableFeature {
if (
detectContractFeature<BaseClaimConditionERC721>(
this.contractWrapper,
"ERC721ClaimableWithConditionsV1",
"ERC721ClaimConditionsV1",
) ||
detectContractFeature<BaseClaimConditionERC721>(
this.contractWrapper,
"ERC721ClaimableWithConditionsV2",
"ERC721ClaimConditionsV2",
) ||
detectContractFeature<BaseClaimConditionERC721>(
this.contractWrapper,
"ERC721ClaimPhasesV1",
) ||
detectContractFeature<BaseClaimConditionERC721>(
this.contractWrapper,
"ERC721ClaimPhasesV2",
)
) {
return new Erc721ClaimableWithConditions(
@@ -214,9 +219,8 @@ export class Erc721LazyMintable implements DetectableFeature {
if (
detectContractFeature<IClaimableERC721>(
this.contractWrapper,
"ERC721Claimable",
) &&
!hasFunction("setClaimConditions", this.contractWrapper)
"ERC721ClaimCustom",
)
) {
return new Erc721Claimable(this.erc721, this.contractWrapper);
}
@@ -1,7 +1,7 @@
import { hasFunction } from "../../common";
import { normalizePriceValue, setErc20Allowance } from "../../common/currency";
import { uploadOrExtractURIs } from "../../common/nft";
import { FEATURE_NFT_SIGNATURE_MINTABLE } from "../../constants/erc721-features";
import { FEATURE_NFT_SIGNATURE_MINTABLE_V2 } from "../../constants/erc721-features";
import {
MintRequest721,
MintRequest721withQuantity,
@@ -30,7 +30,7 @@ import invariant from "tiny-invariant";
* @public
*/
export class Erc721WithQuantitySignatureMintable implements DetectableFeature {
featureName = FEATURE_NFT_SIGNATURE_MINTABLE.name;
featureName = FEATURE_NFT_SIGNATURE_MINTABLE_V2.name;
private contractWrapper: ContractWrapper<SignatureMintERC721 | TokenERC721>;
+12 -8
View File
@@ -12,14 +12,14 @@ import {
FEATURE_NFT,
FEATURE_NFT_BATCH_MINTABLE,
FEATURE_NFT_BURNABLE,
FEATURE_NFT_CLAIMABLE,
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2,
FEATURE_NFT_CLAIM_CUSTOM,
FEATURE_NFT_CLAIM_CONDITIONS_V2,
FEATURE_NFT_LAZY_MINTABLE,
FEATURE_NFT_MINTABLE,
FEATURE_NFT_REVEALABLE,
FEATURE_NFT_SIGNATURE_MINTABLE,
FEATURE_NFT_SUPPLY,
FEATURE_NFT_TIERED_DROP,
FEATURE_NFT_SIGNATURE_MINTABLE_V2,
} from "../../constants/erc721-features";
import { BaseDropERC721, BaseERC721 } from "../../types/eips";
import { ClaimOptions, UploadProgressEvent } from "../../types/index";
@@ -600,7 +600,7 @@ export class Erc721<
if (claim) {
return claim.to(destinationAddress, quantity, options);
}
throw new ExtensionNotImplementedError(FEATURE_NFT_CLAIMABLE);
throw new ExtensionNotImplementedError(FEATURE_NFT_CLAIM_CUSTOM);
}
/**
@@ -627,7 +627,7 @@ export class Erc721<
if (claim) {
return claim.getClaimTransaction(destinationAddress, quantity, options);
}
throw new ExtensionNotImplementedError(FEATURE_NFT_CLAIMABLE);
throw new ExtensionNotImplementedError(FEATURE_NFT_CLAIM_CUSTOM);
}
public async totalClaimedSupply(): Promise<BigNumber> {
@@ -687,7 +687,7 @@ export class Erc721<
get claimConditions() {
return assertEnabled(
this.lazyMintable?.claimWithConditions,
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2,
FEATURE_NFT_CLAIM_CONDITIONS_V2,
).conditions;
}
@@ -721,7 +721,7 @@ export class Erc721<
get signature() {
return assertEnabled(
this.signatureMintable,
FEATURE_NFT_SIGNATURE_MINTABLE,
FEATURE_NFT_SIGNATURE_MINTABLE_V2,
);
}
@@ -861,7 +861,11 @@ export class Erc721<
if (
detectContractFeature<ISignatureMintERC721>(
this.contractWrapper,
"ERC721SignatureMint",
"ERC721SignatureMintV1",
) ||
detectContractFeature<ISignatureMintERC721>(
this.contractWrapper,
"ERC721SignatureMintV2",
)
) {
return new Erc721WithQuantitySignatureMintable(
+27 -12
View File
@@ -1,4 +1,7 @@
import { CONTRACT_ADDRESSES, SUPPORTED_CHAIN_IDS } from "../../constants";
import {
getApprovedImplementation,
getDefaultTrustedForwarders,
} from "../../constants";
import {
EditionDropInitializer,
EditionInitializer,
@@ -150,7 +153,15 @@ export class ContractFactory extends ContractWrapper<TWFactory> {
return events[0].args.proxy;
}
private async getDeployArguments<TContractType extends PrebuiltContractType>(
/**
*
* @param contractType
* @param metadata
* @param contractURI
* @returns
* @internal
*/
public async getDeployArguments<TContractType extends PrebuiltContractType>(
contractType: TContractType,
metadata: z.input<DeploySchemaForPrebuiltContractType<TContractType>>,
contractURI: string,
@@ -284,16 +295,7 @@ export class ContractFactory extends ContractWrapper<TWFactory> {
private async getDefaultTrustedForwarders(): Promise<string[]> {
const chainId = await this.getChainID();
const chainEnum = SUPPORTED_CHAIN_IDS.find((c) => c === chainId);
const biconomyForwarder = chainEnum
? CONTRACT_ADDRESSES[chainEnum].biconomyForwarder
: constants.AddressZero;
const openzeppelinForwarder = chainEnum
? CONTRACT_ADDRESSES[chainEnum].openzeppelinForwarder
: constants.AddressZero;
return biconomyForwarder !== constants.AddressZero
? [openzeppelinForwarder, biconomyForwarder]
: [openzeppelinForwarder];
return getDefaultTrustedForwarders(chainId);
}
private async getImplementation(
@@ -301,6 +303,19 @@ export class ContractFactory extends ContractWrapper<TWFactory> {
version?: number,
) {
const encodedType = ethers.utils.formatBytes32String(contract.name);
const chainId = await this.getChainID();
const approvedImplementation = getApprovedImplementation(
chainId,
contract.contractType,
);
// return approved implementation if it exists and we're not overriding the version
if (
approvedImplementation &&
approvedImplementation.length > 0 &&
version === undefined
) {
return approvedImplementation;
}
return this.readContract.getImplementation(
encodedType,
version !== undefined
@@ -14,6 +14,15 @@ import { SnapshotInputSchema } from "./snapshots";
import { BigNumber, BigNumberish, utils } from "ethers";
import { z } from "zod";
/**
* @internal
*/
export const ClaimConditionMetadataSchema = z
.object({
name: z.string().optional(),
})
.catchall(z.unknown());
/**
* @internal
*/
@@ -26,7 +35,7 @@ export const ClaimConditionInputSchema = z.object({
waitInSeconds: BigNumberishSchema.default(0),
merkleRootHash: BytesLikeSchema.default(utils.hexZeroPad([0], 32)),
snapshot: z.optional(SnapshotInputSchema).nullable(),
metadata: z.union([z.string(), z.object({})]).optional(),
metadata: ClaimConditionMetadataSchema.optional(),
});
/**
@@ -57,7 +66,6 @@ export const ClaimConditionOutputSchema = ClaimConditionInputSchema.extend({
waitInSeconds: BigNumberSchema,
startTime: BigNumberSchema.transform((n) => new Date(n.toNumber() * 1000)),
snapshot: SnapshotInputSchema.optional().nullable(),
metadata: z.string().optional(),
});
export type AbstractClaimConditionContractStruct = {
@@ -96,6 +96,7 @@ export const AbiObjectSchema = z
* @internal
*/
export const AbiSchema = z.array(AbiObjectSchema);
export type Abi = z.input<typeof AbiSchema>;
/**
* @internal
@@ -154,6 +155,18 @@ export const ExtraPublishMetadataSchemaInput = z
isDeployableViaFactory: z.boolean().optional(),
isDeployableViaProxy: z.boolean().optional(),
factoryDeploymentData: FactoryDeploymentSchema.optional(),
constructorParams: z
.record(
z.string(),
z
.object({
displayName: z.string().optional(),
description: z.string().optional(),
defaultValue: z.string().optional(),
})
.catchall(z.any()),
)
.optional(),
})
.catchall(z.any());
@@ -56,6 +56,8 @@ export type ClaimVerification = {
maxClaimable: BigNumber;
price: BigNumber;
currencyAddress: string;
priceInProof: BigNumber;
currencyAddressInProof: string;
};
export type ClaimConditionsForToken = {
@@ -7,7 +7,7 @@ import {
import { toCurrencyValue } from "../utils/token";
import {
Amount,
CandyMachineEndSettings,
CandyMachineV2EndSettings,
DateTime,
Metaplex,
sol,
@@ -168,8 +168,7 @@ export class ClaimConditions {
if (parsed.currencyAddress && parsed.price) {
const fetchedToken = await this.metaplex
.tokens()
.findMintByAddress({ address: new PublicKey(parsed.currencyAddress) })
.run();
.findMintByAddress({ address: new PublicKey(parsed.currencyAddress) });
price = token(Number(parsed.price), fetchedToken.decimals);
tokenMint = fetchedToken.address;
}
@@ -180,7 +179,7 @@ export class ClaimConditions {
: undefined;
// max claimable
const endSettings: CandyMachineEndSettings | null | undefined =
const endSettings: CandyMachineV2EndSettings | null | undefined =
parsed.maxClaimable
? parsed.maxClaimable === "unlimited"
? null
@@ -194,20 +193,22 @@ export class ClaimConditions {
const data = {
...(wallet && { wallet }),
...(tokenMint && { tokenMint }),
...(tokenMint
? { tokenMint }
: // if passing currencyAddress explcitly as null we need to honor that!
parsed.currencyAddress === null
? { tokenMint: null }
: {}),
...(price && { price }),
...(goLiveDate && { goLiveDate }),
...(sellerFeeBasisPoints && { sellerFeeBasisPoints }),
...(endSettings !== undefined && { endSettings }),
};
const result = await this.metaplex
.candyMachines()
.update({
candyMachine: await this.getCandyMachine(),
...data,
})
.run();
const result = await this.metaplex.candyMachinesV2().update({
candyMachine: await this.getCandyMachine(),
...data,
});
return {
signature: result.response.signature,
@@ -216,8 +217,7 @@ export class ClaimConditions {
private async getCandyMachine() {
return this.metaplex
.candyMachines()
.findByAddress({ address: this.dropMintAddress })
.run();
.candyMachinesV2()
.findByAddress({ address: this.dropMintAddress });
}
}
+13 -13
View File
@@ -234,7 +234,7 @@ export class Deployer {
// initialize candy machine with default config
// final claim conditions can be updated later
const candyMachineTx = await this.metaplex
.candyMachines()
.candyMachinesV2()
.builders()
.create({
itemsAvailable: toBigNumber(candyMachineInfo.totalSupply),
@@ -264,23 +264,20 @@ export class Deployer {
const block = await this.metaplex.connection.getLatestBlockhash();
const dropTx = collectionTx
.add(candyMachineTx)
.setFeePayer(this.metaplex.identity())
.setTransactionOptions({
blockhash: block.blockhash,
feePayer: this.metaplex.identity().publicKey,
lastValidBlockHeight: block.lastValidBlockHeight,
});
const regTx = TransactionBuilder.make()
.setFeePayer(this.metaplex.identity());
const regTransaction = TransactionBuilder.make()
.add(...registryInstructions)
.setFeePayer(this.metaplex.identity())
.setTransactionOptions({
.toTransaction({
blockhash: block.blockhash,
feePayer: this.metaplex.identity().publicKey,
lastValidBlockHeight: block.lastValidBlockHeight,
});
const dropSigners = [this.metaplex.identity(), ...dropTx.getSigners()];
const { keypairs } = getSignerHistogram(dropSigners);
const dropTransaction = dropTx.toTransaction();
const dropTransaction = dropTx.toTransaction({
blockhash: block.blockhash,
lastValidBlockHeight: block.lastValidBlockHeight,
});
// partially sign with the PDA keypairs
if (keypairs.length > 0) {
dropTransaction.partialSign(...keypairs);
@@ -288,7 +285,7 @@ export class Deployer {
// make the connected wallet sign both candyMachine + registry transactions
const signedTx = await this.metaplex
.identity()
.signAllTransactions([dropTransaction, regTx.toTransaction()]);
.signAllTransactions([dropTransaction, regTransaction]);
// send the signed transactions *sequentially* the drop creation needs to succeed first before adding to registry
const signatures: string[] = [];
@@ -301,7 +298,10 @@ export class Deployer {
// wait for confirmations in parallel
const confirmations = await Promise.all(
signatures.map((sig) => {
return this.metaplex.rpc().confirmTransaction(sig);
return this.metaplex.rpc().confirmTransaction(sig, {
blockhash: block.blockhash,
lastValidBlockHeight: block.lastValidBlockHeight,
});
}),
);
@@ -1,6 +1,6 @@
import {
QueryAllParams,
DEFAULT_QUERY_ALL_COUNT,
QueryAllParamsSchema,
} from "../../../core/schema/QueryParams";
import { NFT } from "../../../core/schema/nft";
import {
@@ -8,7 +8,9 @@ import {
METAPLEX_PROGRAM_ID,
} from "../../constants/addresses";
import { TransactionResult } from "../../types/common";
import { CreatorOutput } from "../../types/programs";
import { getPublicRpc } from "../../utils/urls";
import { parseCreators } from "./creators-helper";
import {
findMasterEditionV2Pda,
GmaBuilder,
@@ -46,13 +48,14 @@ export class NFTHelper {
this.connection = metaplex.connection;
}
async getRaw(nftAddress: string) {
return await this.metaplex.nfts().findByMint({
mintAddress: new PublicKey(nftAddress),
});
}
async get(nftAddress: string): Promise<NFT> {
const meta = await this.metaplex
.nfts()
.findByMint({
mintAddress: new PublicKey(nftAddress),
})
.run();
const meta = await this.getRaw(nftAddress);
return await this.toNFTMetadata(meta);
}
@@ -61,20 +64,22 @@ export class NFTHelper {
nftAddress: string,
quantity: number = 1,
): Promise<TransactionResult> {
const result = await this.metaplex
.nfts()
.send({
mintAddress: new PublicKey(nftAddress),
toOwner: new PublicKey(receiverAddress),
amount: token(quantity, 0),
})
.run();
const result = await this.metaplex.nfts().send({
mintAddress: new PublicKey(nftAddress),
toOwner: new PublicKey(receiverAddress),
amount: token(quantity, 0),
});
return {
signature: result.response.signature,
};
}
async creatorsOf(nftAddress: string): Promise<CreatorOutput[]> {
const meta = await this.getRaw(nftAddress);
return parseCreators(meta.creators);
}
async balanceOf(walletAddress: string, nftAddress: string): Promise<number> {
const address = await getAssociatedTokenAddress(
new PublicKey(nftAddress),
@@ -110,13 +115,73 @@ export class NFTHelper {
}
}
async supplyOf(nftAddress: string): Promise<number> {
let originalEdition;
const originalEditionAccount = await this.metaplex
.rpc()
.getAccount(findMasterEditionV2Pda(new PublicKey(nftAddress)));
if (originalEditionAccount.exists) {
originalEdition = toNftOriginalEdition(
toOriginalEditionAccount(originalEditionAccount),
);
} else {
return 0;
}
// Add one to supply to account for the master edition
return originalEdition.supply.toNumber() + 1;
}
async totalSupply(collectionAddress: string): Promise<number> {
const metadataAddresses = await this.getAllMetadataAddresses(
collectionAddress,
);
return metadataAddresses.length;
}
async getAll(
collectionAddress: string,
queryParams?: QueryAllParams,
): Promise<NFT[]> {
const { start, count } = QueryAllParamsSchema.parse(queryParams);
const metadataAddresses = await this.getAllMetadataAddresses(
collectionAddress,
);
// Metaplex GmaBuilder has a weird thing where they always start at 1 not 0
// so we workaround it by adding an extra address, and shifting the count to get the actual count we want
const fixedMetadataAddresses = (
start === 0 ? [PublicKey.default] : []
).concat(metadataAddresses);
const metadataInfos = await GmaBuilder.make(
this.metaplex,
fixedMetadataAddresses,
).getBetween(start, start === 0 ? count + 1 : start + count);
// Parse each account into a metadata account
const metadataParsed: Metadata<JsonMetadata<string>>[] = [];
for (const metadataInfo of metadataInfos) {
if (metadataInfo.exists) {
try {
metadataParsed.push(toMetadata(toMetadataAccount(metadataInfo)));
} catch (error) {
// no-op
}
}
}
// Finally, fetch the metadata + mint for each in parallel
const nfts = await Promise.all(
metadataParsed.map((m) => this.toNFTMetadata(m)),
);
return nfts;
}
private async getAllMetadataAddresses(collectionAddress: string) {
const collectionKey = new PublicKey(collectionAddress);
const start = queryParams?.start || 0;
const count = queryParams?.count || DEFAULT_QUERY_ALL_COUNT;
// TODO cache signatures <> transactions mapping in memory so pagination doesn't re-request this everytime
const allSignatures: ConfirmedSignatureInfo[] = [];
@@ -194,52 +259,7 @@ export class NFTHelper {
}
}
// Metaplex GmaBuilder has a weird thing where they always start at 1 not 0
// so we workaround it by adding an extra address, and shifting the count to get the actual count we want
const fixedMetadataAddresses = (
start === 0 ? [PublicKey.default] : []
).concat(metadataAddresses);
const metadataInfos = await GmaBuilder.make(
this.metaplex,
fixedMetadataAddresses,
).getBetween(start, start === 0 ? count + 1 : start + count);
// parse each account into a metadata account
const metadataParsed: Metadata<JsonMetadata<string>>[] = [];
for (const metadataInfo of metadataInfos) {
if (metadataInfo.exists) {
try {
metadataParsed.push(toMetadata(toMetadataAccount(metadataInfo)));
} catch (error) {
// ignore
}
}
}
// finally fetch the metadta + mint for each in parallel
const nfts = await Promise.all(
metadataParsed.map((m) => this.toNFTMetadata(m)),
);
return nfts;
}
async supplyOf(nftAddress: string): Promise<number> {
let originalEdition;
const originalEditionAccount = await this.metaplex
.rpc()
.getAccount(findMasterEditionV2Pda(new PublicKey(nftAddress)));
if (originalEditionAccount.exists) {
originalEdition = toNftOriginalEdition(
toOriginalEditionAccount(originalEditionAccount),
);
} else {
return 0;
}
// Add one to supply to account for the master edition
return originalEdition.supply.toNumber() + 1;
return metadataAddresses;
}
async toNFTMetadata(
@@ -253,10 +273,7 @@ export class NFTHelper {
let mint = "mint" in meta ? meta.mint : undefined;
let fullModel = meta;
if (meta.model === "metadata") {
fullModel = await this.metaplex
.nfts()
.findByMint({ mintAddress: meta.mintAddress })
.run();
fullModel = await this.getRaw(meta.mintAddress.toBase58());
mint = fullModel.mint;
}
if (!mint) {
+15 -20
View File
@@ -6,12 +6,12 @@ import { WalletAccount } from "../types/common";
import { RegisteredProgram } from "../types/programs";
import { UserWallet } from "./user-wallet";
import {
CandyMachine,
CandyMachineV2,
InstructionWithSigners,
Metadata,
Metaplex,
Pda,
TokenProgram,
TokenGpaBuilder,
} from "@metaplex-foundation/js";
import { TokenStandard } from "@metaplex-foundation/mpl-token-metadata";
import type { BN, Idl } from "@project-serum/anchor";
@@ -38,9 +38,8 @@ export class Registry {
public async getProgramType(address: string) {
try {
const candyMachine = await this.metaplex
.candyMachines()
.findByAddress({ address: new PublicKey(address) })
.run();
.candyMachinesV2()
.findByAddress({ address: new PublicKey(address) });
if (candyMachine) {
return "nft-drop";
}
@@ -49,8 +48,7 @@ export class Registry {
}
const metadata = await this.metaplex
.nfts()
.findByMint({ mintAddress: new PublicKey(address) })
.run();
.findByMint({ mintAddress: new PublicKey(address) });
if (metadata) {
if (metadata.collectionDetails) {
@@ -198,7 +196,10 @@ export class Registry {
return account.exists;
}
private getDropForCollection(candyMachines: CandyMachine[], meta: Metadata) {
private getDropForCollection(
candyMachines: CandyMachineV2[],
meta: Metadata,
) {
return candyMachines.find(
(candyMachine) =>
candyMachine.collectionMintAddress?.toBase58() ===
@@ -211,18 +212,12 @@ export class Registry {
walletAddress: string,
): Promise<WalletAccount[]> {
const mints = await this.getOwnedTokenAddreses(walletAddress);
const metadatas = await this.metaplex
.nfts()
.findAllByMintList({ mints })
.run();
const metadatas = await this.metaplex.nfts().findAllByMintList({ mints });
const candyMachines = await this.metaplex
.candyMachines()
.findAllBy({
type: "authority",
publicKey: new PublicKey(walletAddress),
})
.run();
const candyMachines = await this.metaplex.candyMachinesV2().findAllBy({
type: "authority",
publicKey: new PublicKey(walletAddress),
});
return metadatas
.map((mintMetadata) => {
@@ -260,7 +255,7 @@ export class Registry {
}
private async getOwnedTokenAddreses(walletAddress: string) {
return await TokenProgram.tokenAccounts(this.metaplex)
return await new TokenGpaBuilder(this.metaplex)
.selectMint()
.whereOwner(new PublicKey(walletAddress))
.getDataAsPublicKeys();
@@ -16,6 +16,7 @@ import {
toBigNumber,
toNftOriginalEdition,
toOriginalEditionAccount,
TransactionBuilder,
} from "@metaplex-foundation/js";
import { PublicKey } from "@solana/web3.js";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
@@ -139,6 +140,18 @@ export class NFTCollection {
return this.nft.getAll(this.publicKey.toBase58(), queryParams);
}
/**
* Get the total number of nfts minted on this program
* @returns the total number of NFTs minted on this program
*
* ```jsx
* const totalSupply = await program.totalSupply();
* ```
*/
async totalSupply(): Promise<number> {
return this.nft.totalSupply(this.publicKey.toBase58());
}
/**
* Get the NFT balance of the connected wallet
* @returns the NFT balance
@@ -295,19 +308,18 @@ export class NFTCollection {
: await this.storage.upload(metadata);
const name =
typeof metadata === "string" ? "" : metadata.name?.toString() || "";
const { nft } = await this.metaplex
.nfts()
.create({
name,
uri,
sellerFeeBasisPoints: 0,
collection: this.publicKey,
collectionAuthority: this.metaplex.identity(),
tokenOwner: new PublicKey(to),
// Always sets max supply to unlimited so editions can be minted
maxSupply: null,
})
.run();
const collection = await this.getCollection();
const { nft } = await this.metaplex.nfts().create({
name,
uri,
creators: collection.creators,
sellerFeeBasisPoints: collection.sellerFeeBasisPoints,
collection: this.publicKey,
collectionAuthority: this.metaplex.identity(),
tokenOwner: new PublicKey(to),
// Always sets max supply to unlimited so editions can be minted
maxSupply: null,
});
return nft.address.toBase58();
}
@@ -408,13 +420,11 @@ export class NFTCollection {
* ```
*/
async burn(nftAddress: string): Promise<TransactionResult> {
const tx = await this.metaplex
.nfts()
.delete({
mintAddress: new PublicKey(nftAddress),
collection: this.publicKey,
})
.run();
const tx = await this.metaplex.nfts().delete({
mintAddress: new PublicKey(nftAddress),
collection: this.publicKey,
});
return {
signature: tx.response.signature,
};
@@ -427,41 +437,125 @@ export class NFTCollection {
/**
* Update the creators of the collection
* @param creators - the creators to update
* @param updateAll - whether or not to retroactively update the creators of all past NFTs
*/
async updateCreators(creators: CreatorInput[]) {
const tx = await this.metaplex
.nfts()
.update({
async updateCreators(
creators: CreatorInput[],
updateAll: boolean = false,
): Promise<TransactionResult[]> {
if (updateAll) {
const txs: TransactionBuilder[] = [];
txs.push(
this.metaplex
.nfts()
.builders()
.update({
nftOrSft: await this.getCollection(),
creators: enforceCreator(
creators,
this.metaplex.identity().publicKey,
),
}),
);
// TODO: Don't use a get all here, all we need is mint addresses :(
const allNfts = await this.nft.getAll(this.publicKey.toBase58());
await Promise.all(
allNfts.map(async (nft) => {
const metaplexNft = await this.metaplex
.nfts()
.findByMint({ mintAddress: new PublicKey(nft.metadata.id) });
txs.push(
this.metaplex
.nfts()
.builders()
.update({
collection: this.publicKey,
nftOrSft: metaplexNft,
creators: enforceCreator(
creators,
this.metaplex.identity().publicKey,
),
}),
);
}),
);
return await sendMultipartTransaction(txs, this.metaplex);
} else {
const tx = await this.metaplex.nfts().update({
nftOrSft: await this.getCollection(),
creators: enforceCreator(creators, this.metaplex.identity().publicKey),
})
.run();
return {
signature: tx.response.signature,
};
});
return [
{
signature: tx.response.signature,
},
];
}
}
/**
* Update the royalty basis points of the collection
* @param sellerFeeBasisPoints - the royalty basis points of the collection
* @param updateAll - whether or not to retroactively update the royalty basis points of all past NFTs
*/
async updateRoyalty(sellerFeeBasisPoints: number) {
const tx = await this.metaplex
.nfts()
.update({
async updateRoyalty(
sellerFeeBasisPoints: number,
updateAll: boolean = false,
) {
if (updateAll) {
const txs: TransactionBuilder[] = [];
txs.push(
this.metaplex
.nfts()
.builders()
.update({
nftOrSft: await this.getCollection(),
sellerFeeBasisPoints,
}),
);
// TODO: Don't use a get all here, all we need is mint addresses :(
const allNfts = await this.nft.getAll(this.publicKey.toBase58());
await Promise.all(
allNfts.map(async (nft) => {
const metaplexNft = await this.metaplex
.nfts()
.findByMint({ mintAddress: new PublicKey(nft.metadata.id) });
txs.push(
this.metaplex.nfts().builders().update({
collection: this.publicKey,
nftOrSft: metaplexNft,
sellerFeeBasisPoints,
}),
);
}),
);
return await sendMultipartTransaction(txs, this.metaplex);
} else {
const tx = await this.metaplex.nfts().update({
nftOrSft: await this.getCollection(),
sellerFeeBasisPoints,
})
.run();
return {
signature: tx.response.signature,
};
});
return [
{
signature: tx.response.signature,
},
];
}
}
private async getCollection() {
return await this.metaplex
.nfts()
.findByMint({ mintAddress: this.publicKey })
.run();
.findByMint({ mintAddress: this.publicKey });
}
}
+77 -46
View File
@@ -1,3 +1,7 @@
import {
QueryAllParams,
QueryAllParamsSchema,
} from "../../core/schema/QueryParams";
import {
CommonNFTInput,
NFT,
@@ -16,7 +20,7 @@ import { CreatorInput, CreatorOutput } from "../types/programs";
import { sendMultipartTransaction } from "../utils/transactions";
import { getNework } from "../utils/urls";
import {
CandyMachineItem,
CandyMachineV2Item,
Metaplex,
toBigNumber,
} from "@metaplex-foundation/js";
@@ -99,8 +103,7 @@ export class NFTDrop {
invariant(info.collectionMintAddress, "Collection mint address not found");
const metadata = await this.metaplex
.nfts()
.findByMint({ mintAddress: info.collectionMintAddress })
.run();
.findByMint({ mintAddress: info.collectionMintAddress });
return (await this.nft.toNFTMetadata(metadata)).metadata;
}
@@ -166,22 +169,37 @@ export class NFTDrop {
* console.log(nfts[0].metadata.name);
* ```
*/
async getAll(): Promise<NFT[]> {
// TODO: Add pagination to get NFT functions
async getAll(queryParams?: QueryAllParams): Promise<NFT[]> {
const parsedQueryParams = QueryAllParamsSchema.parse(queryParams);
const info = await this.getCandyMachine();
const claimed = await this.getAllClaimed();
return await Promise.all(
info.items.map(async (item) => {
// Check if the NFT has been claimed
// TODO: This only checks name/uri which is potentially not unique
const claimedNFT = claimed.find(
// First, get all the claimed NFTs within the query params range
const claimedNfts = await this.getAllClaimed(parsedQueryParams);
const totalClaimed = await this.totalClaimedSupply();
// Then filter out all claimed NFTs from items to leave only unclaimed remaining
const unclaimedItems: CandyMachineV2Item[] = [];
info.items.forEach((item) => {
const isClaimed =
claimedNfts.filter(
(nft) =>
nft.metadata.name === item.name && nft.metadata.uri === item.uri,
);
if (claimedNFT) {
return claimedNFT;
}
// not claimed yet, return a unclaimed NFT with just the metadata
).length > 0;
if (!isClaimed) {
unclaimedItems.push(item);
}
});
// Only fill the remaining count left over after claimed NFTs with unclaimed NFTs
const startIndex = Math.max(0, parsedQueryParams.start - totalClaimed);
const endIndex = Math.max(
0,
startIndex + parsedQueryParams.count - claimedNfts.length,
);
const unclaimedNfts = await Promise.all(
unclaimedItems.slice(startIndex, endIndex).map(async (item) => {
const metadata: NFTMetadata = await this.storage.downloadJSON(item.uri);
return {
metadata: {
@@ -195,6 +213,9 @@ export class NFTDrop {
} as NFT;
}),
);
// Always return claimed NFTs first, and then fill remaining query count with unclaimed NFTs
return [...claimedNfts, ...unclaimedNfts];
}
/**
@@ -208,11 +229,14 @@ export class NFTDrop {
* console.log(nfts[0].name)
* ```
*/
async getAllClaimed(): Promise<NFT[]> {
async getAllClaimed(queryParams?: QueryAllParams): Promise<NFT[]> {
// using getAll from collection here because candy machin findAllMinted doesn't return anything
const candy = await this.getCandyMachine();
invariant(candy.collectionMintAddress, "Collection mint address not found");
return await this.nft.getAll(candy.collectionMintAddress.toBase58());
return await this.nft.getAll(
candy.collectionMintAddress.toBase58(),
queryParams,
);
}
/**
@@ -268,6 +292,21 @@ export class NFTDrop {
return this.nft.ownerOf(nftAddress);
}
/**
* Get the total minted supply of this drop
* @returns the total supply
*
* @example
* ```jsx
* // Get the total number of NFTs that have been minted on this drop
* const supply = await program.totalSupply();
* ```
*/
async totalSupply(): Promise<number> {
const info = await this.getCandyMachine();
return info.itemsLoaded.toNumber();
}
/**
* Get the total unclaimed supply of this drop
* @returns the total supply
@@ -361,20 +400,20 @@ export class NFTDrop {
CommonNFTInput.parse(metadata),
);
const uris = await this.storage.uploadBatch(parsedMetadatas, options);
const items: CandyMachineItem[] = uris.map((uri, i) => ({
const items: CandyMachineV2Item[] = uris.map((uri, i) => ({
name: parsedMetadatas[i].name?.toString() || "",
uri,
}));
// turn items into batches of $LAZY_MINT_BATCH_SIZE
const batches: CandyMachineItem[][] = [];
const batches: CandyMachineV2Item[][] = [];
while (items.length) {
batches.push(items.splice(0, LAZY_MINT_BATCH_SIZE));
}
const builders = batches.map((batch, i) =>
this.metaplex
.candyMachines()
.candyMachinesV2()
.builders()
.insertItems({
candyMachine,
@@ -425,7 +464,7 @@ export class NFTDrop {
const builders = await Promise.all(
[...Array(amount).keys()].map(async () => {
return await this.metaplex
.candyMachines()
.candyMachinesV2()
.builders()
.mint({
candyMachine,
@@ -458,13 +497,10 @@ export class NFTDrop {
const collection = candyMachine.collectionMintAddress
? candyMachine.collectionMintAddress
: undefined;
const tx = await this.metaplex
.nfts()
.delete({
mintAddress: new PublicKey(nftAddress),
collection,
})
.run();
const tx = await this.metaplex.nfts().delete({
mintAddress: new PublicKey(nftAddress),
collection,
});
return {
signature: tx.response.signature,
};
@@ -475,13 +511,11 @@ export class NFTDrop {
* @param creators - the creators to update
*/
async updateCreators(creators: CreatorInput[]) {
const tx = await this.metaplex
.candyMachines()
.update({
candyMachine: await this.getCandyMachine(),
creators: enforceCreator(creators, this.metaplex.identity().publicKey),
})
.run();
const tx = await this.metaplex.candyMachinesV2().update({
candyMachine: await this.getCandyMachine(),
creators: enforceCreator(creators, this.metaplex.identity().publicKey),
});
return {
signature: tx.response.signature,
};
@@ -492,13 +526,11 @@ export class NFTDrop {
* @param sellerFeeBasisPoints - the royalty basis points of the collection
*/
async updateRoyalty(sellerFeeBasisPoints: number) {
const tx = await this.metaplex
.candyMachines()
.update({
candyMachine: await this.getCandyMachine(),
sellerFeeBasisPoints,
})
.run();
const tx = await this.metaplex.candyMachinesV2().update({
candyMachine: await this.getCandyMachine(),
sellerFeeBasisPoints,
});
return {
signature: tx.response.signature,
};
@@ -506,8 +538,7 @@ export class NFTDrop {
private async getCandyMachine() {
return this.metaplex
.candyMachines()
.findByAddress({ address: this.publicKey })
.run();
.candyMachinesV2()
.findByAddress({ address: this.publicKey });
}
}
+11 -18
View File
@@ -131,14 +131,11 @@ export class Token {
): Promise<TransactionResult> {
const amountParsed = AmountSchema.parse(amount);
const info = await this.getMint();
const result = await this.metaplex
.tokens()
.mint({
amount: token(amountParsed, info.decimals),
mintAddress: this.publicKey,
toOwner: new PublicKey(receiverAddress),
})
.run();
const result = await this.metaplex.tokens().mint({
amount: token(amountParsed, info.decimals),
mintAddress: this.publicKey,
toOwner: new PublicKey(receiverAddress),
});
return {
signature: result.response.signature,
};
@@ -165,14 +162,11 @@ export class Token {
amount: Amount,
): Promise<TransactionResult> {
const info = await this.getMint();
const result = await this.metaplex
.tokens()
.send({
mintAddress: this.publicKey,
amount: token(amount, info.decimals),
toOwner: new PublicKey(receiverAddress),
})
.run();
const result = await this.metaplex.tokens().send({
mintAddress: this.publicKey,
amount: token(amount, info.decimals),
toOwner: new PublicKey(receiverAddress),
});
return {
signature: result.response.signature,
};
@@ -228,7 +222,6 @@ export class Token {
private async getMint() {
return await this.metaplex
.tokens()
.findMintByAddress({ address: this.publicKey })
.run(); // TODO abstract types away
.findMintByAddress({ address: this.publicKey });
}
}
@@ -69,6 +69,22 @@ export type TokenMetadataInput = z.input<typeof TokenMetadataInputSchema>;
*/
export type CreatorInput = z.input<typeof CreatorInputSchema>;
/**
* @public
*/
export type UpdateCreatorInput = {
creators: CreatorInput[];
updateAll: boolean;
};
/**
* @public
*/
export type UpdateRoyaltySettingsInput = {
sellerFeeBasisPoints: number;
updateAll: boolean;
};
/**
* @public
*/
@@ -11,17 +11,14 @@ export async function sendMultipartTransaction(
): Promise<TransactionResult[]> {
const block = await metaplex.connection.getLatestBlockhash();
const txns = builders.map((builder) => {
const builderTx = builder
.setTransactionOptions({
blockhash: block.blockhash,
feePayer: metaplex.identity().publicKey,
lastValidBlockHeight: block.lastValidBlockHeight,
})
.setFeePayer(metaplex.identity());
const builderTx = builder.setFeePayer(metaplex.identity());
const dropSigners = [metaplex.identity(), ...builderTx.getSigners()];
const { keypairs } = getSignerHistogram(dropSigners);
const tx = builderTx.toTransaction();
const tx = builderTx.toTransaction({
blockhash: block.blockhash,
lastValidBlockHeight: block.lastValidBlockHeight,
});
if (keypairs.length > 0) {
tx.partialSign(...keypairs);
@@ -44,7 +41,10 @@ export async function sendMultipartTransaction(
// wait for confirmations in parallel
const confirmations = await Promise.all(
signatures.map((sig) => {
return metaplex.rpc().confirmTransaction(sig);
return metaplex.rpc().confirmTransaction(sig, {
blockhash: block.blockhash,
lastValidBlockHeight: block.lastValidBlockHeight,
});
}),
);
+1 -1
View File
@@ -158,7 +158,7 @@ export const mochaHooks = {
if (contractType === "custom") {
continue;
}
let factories = [];
let factories: any[] = [];
switch (contractType) {
case TokenInitializer.contractType:
factories.push(TokenERC20__factory);
+1 -1
View File
@@ -275,7 +275,7 @@ describe("Custom Contracts", async () => {
{
startTime: new Date(new Date().getTime() - 1000 * 60 * 60),
price: 0,
maxQuantity: 10,
maxClaimableSupply: 10,
},
]);
@@ -64,11 +64,13 @@ describe("Edition Drop Contract (V4)", async () => {
await bdContract.claimConditions.set(0, [
{
startTime: new Date(Date.now() / 2),
maxClaimablePerWallet: 1,
snapshot: [bobWallet.address, samWallet.address, abbyWallet.address],
price: 1,
},
{
startTime: new Date(),
maxClaimablePerWallet: 1,
snapshot: [bobWallet.address],
},
]);
@@ -147,6 +149,7 @@ describe("Edition Drop Contract (V4)", async () => {
await bdContract.claimConditions.set("0", [
{
maxClaimableSupply: 1000,
maxClaimablePerWallet: 1,
snapshot: members,
},
]);
@@ -257,6 +260,7 @@ describe("Edition Drop Contract (V4)", async () => {
]);
await bdContract.claimConditions.set(0, [
{
maxClaimablePerWallet: 0,
snapshot: [
{ address: w1.address, maxClaimable: 2 },
{ address: w2.address, maxClaimable: 1 },
@@ -273,6 +277,24 @@ describe("Edition Drop Contract (V4)", async () => {
}
});
it("should not allow setting a claim condition where no one can claim", async () => {
await bdContract.createBatch([{ name: "test", description: "test" }]);
try {
await bdContract.claimConditions.set(0, [
{
maxClaimablePerWallet: 0,
snapshot: [adminWallet.address, bobWallet.address],
},
]);
assert.fail(
"should not allow setting a claim condition where no one can claim",
);
} catch (e) {
expectError(e, "no one can claim");
}
});
it("should allow a default claim condition to be used to claim", async () => {
await bdContract.createBatch([
{
@@ -555,6 +577,7 @@ describe("Edition Drop Contract (V4)", async () => {
await bdContract.claimConditions.set("0", [
{
maxClaimableSupply: 1,
maxClaimablePerWallet: 1,
snapshot: [w1.address],
},
]);
@@ -634,6 +657,7 @@ describe("Edition Drop Contract (V4)", async () => {
await bdContract.claimConditions.set("0", [
{
maxClaimableSupply: 10,
maxClaimablePerWallet: 1,
price: "100",
currencyAddress: NATIVE_TOKEN_ADDRESS,
snapshot: [w1.address, w2.address, w3.address],
@@ -676,6 +700,7 @@ describe("Edition Drop Contract (V4)", async () => {
await bdContract.claimConditions.set(0, [
{
maxClaimablePerWallet: 1,
snapshot: [samWallet.address],
},
]);
@@ -725,12 +750,14 @@ describe("Edition Drop Contract (V4)", async () => {
await bdContract.claimConditions.set("1", [
{
maxClaimablePerWallet: 1,
snapshot: [w1.address, w2.address, bobWallet.address],
},
]);
await bdContract.claimConditions.set("2", [
{
maxClaimablePerWallet: 1,
snapshot: [w3.address, w1.address, w2.address, adminWallet.address],
},
]);
+188 -3
View File
@@ -49,6 +49,149 @@ describe("NFT Drop Contract (v4)", async () => {
dropContract = await sdk.getNFTDrop(address);
});
it("comprehensive test", async () => {
const metadata = [];
for (let i = 0; i < 10; i++) {
metadata.push({ name: `test${i}`, description: `desc${i}` });
}
await dropContract.createBatch(metadata);
// claiming with default conditions
await dropContract.claimConditions.set([{}]);
await dropContract.claim(1);
// claiming with max supply
await dropContract.claimConditions.set([
{
maxClaimableSupply: 2,
},
]);
try {
await dropContract.claim(2);
expect.fail("should not be able to claim 2 - maxSupply");
} catch (e) {
expectError(e, "!MaxSupply");
}
await dropContract.claim(1);
// claiming with max per wallet
await dropContract.claimConditions.set(
[
{
maxClaimablePerWallet: 1,
},
],
true,
);
try {
await dropContract.claim(2);
expect.fail("should not be able to claim 2 - maxClaimablePerWallet");
} catch (e) {
expectError(e, "!Qty");
}
await dropContract.claim(1);
expect((await dropContract.totalClaimedSupply()).toString()).eq("3");
});
it("comprehensive test with allowlist and price override", async () => {
const metadata = [];
for (let i = 0; i < 10; i++) {
metadata.push({ name: `test${i}`, description: `desc${i}` });
}
await dropContract.createBatch(metadata);
// claiming with default conditions
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 0,
price: 0.1,
snapshot: [
{ address: adminWallet.address, maxClaimable: 1, price: 0.1 },
],
},
]);
console.log(
await dropContract.claimConditions.getClaimIneligibilityReasons(1),
);
await dropContract.claim(1);
});
it("comprehensive test with allowlist", async () => {
const metadata = [];
for (let i = 0; i < 10; i++) {
metadata.push({ name: `test${i}`, description: `desc${i}` });
}
await dropContract.createBatch(metadata);
// claiming with default conditions
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 0,
snapshot: [{ address: adminWallet.address, maxClaimable: 1 }],
},
]);
try {
sdk.updateSignerOrProvider(bobWallet);
await dropContract.claim(1);
expect.fail("should not be able to claim - not in allowlist");
} catch (e) {
expectError(e, "!Qty");
}
sdk.updateSignerOrProvider(adminWallet);
await dropContract.claim(1);
// // claiming with max supply
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 0,
snapshot: [{ address: adminWallet.address, maxClaimable: 10 }],
maxClaimableSupply: 2,
},
]);
try {
await dropContract.claim(2);
expect.fail("should not be able to claim - maxClaimableSupply");
} catch (e) {
expectError(e, "!MaxSupply");
}
await dropContract.claim(1);
// claiming with max per wallet
await dropContract.claimConditions.set(
[
{
snapshot: [adminWallet.address],
maxClaimablePerWallet: 1,
},
],
true,
);
try {
await dropContract.claim(2);
expect.fail("should not be able to claim - maxClaimablePerWallet");
} catch (e) {
expectError(e, "!Qty");
}
await dropContract.claim(1);
// claiming with max per wallet in snapshot
await dropContract.claimConditions.set(
[
{
snapshot: [{ address: adminWallet.address, maxClaimable: 1 }],
maxClaimablePerWallet: 0,
},
],
true,
);
try {
await dropContract.claim(2);
expect.fail("should not be able to claim - proof maxClaimable");
} catch (e) {
expectError(e, "!Qty");
}
await dropContract.claim(1);
try {
await dropContract.claim(1);
expect.fail("should not be able to claim - proof used");
} catch (e) {
expectError(e, "!Qty");
}
});
it("should lazy mint with URI", async () => {
const uri = await storage.upload({
name: "Test1",
@@ -59,8 +202,25 @@ describe("NFT Drop Contract (v4)", async () => {
assert.equal(nft.metadata.name, "Test1");
});
it("should upload and resolve claim condition metadata", async () => {
const uri = await storage.upload({
name: "Test1",
});
await dropContract.createBatch([uri]);
await dropContract.claimConditions.set([
{
metadata: {
name: "Test",
description: "Description",
},
},
]);
const condition = await dropContract.claimConditions.getActive();
expect(condition.metadata?.name).to.equal("Test");
});
it("should respect global maxClaimable per wallet with default snapshot", async () => {
const metadata = [];
const metadata: NFTMetadataInput[] = [];
for (let i = 0; i < 10; i++) {
metadata.push({ name: `test${i}`, description: `desc${i}` });
}
@@ -101,11 +261,13 @@ describe("NFT Drop Contract (v4)", async () => {
await dropContract.claimConditions.set([
{
startTime: new Date(Date.now() / 2),
maxClaimablePerWallet: 1,
snapshot: [bobWallet.address, samWallet.address, abbyWallet.address],
price: 1,
},
{
startTime: new Date(),
maxClaimablePerWallet: 1,
snapshot: [bobWallet.address],
},
]);
@@ -126,7 +288,7 @@ describe("NFT Drop Contract (v4)", async () => {
const proof = await dropContract.claimConditions.getClaimerProofs(
bobWallet.address,
);
expect(proof.address).to.eq(bobWallet.address);
expect(proof?.address).to.eq(bobWallet.address);
});
it("should return snapshot data on claim conditions", async () => {
@@ -137,6 +299,7 @@ describe("NFT Drop Contract (v4)", async () => {
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 1,
snapshot: [samWallet.address],
},
]);
@@ -153,10 +316,12 @@ describe("NFT Drop Contract (v4)", async () => {
{
startTime: new Date(),
waitInSeconds: 10,
maxClaimablePerWallet: 1,
snapshot: [bobWallet.address, samWallet.address, abbyWallet.address],
},
{
startTime: new Date(Date.now() + 60 * 60 * 1000),
maxClaimablePerWallet: 1,
snapshot: [bobWallet.address],
},
]);
@@ -229,12 +394,31 @@ describe("NFT Drop Contract (v4)", async () => {
}
});
it("should not allow setting a claim condition where no one can claim", async () => {
await dropContract.createBatch([{ name: "test", description: "test" }]);
try {
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 0,
snapshot: [adminWallet.address, bobWallet.address],
},
]);
assert.fail(
"should not allow setting a claim condition where no one can claim",
);
} catch (e) {
expectError(e, "no one can claim");
}
});
it("allow one address in the merkle tree to claim", async () => {
const testWallets: SignerWithAddress[] = [bobWallet];
const members = testWallets.map((w) => w.address);
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 1,
snapshot: members,
},
]);
@@ -544,6 +728,7 @@ describe("NFT Drop Contract (v4)", async () => {
await dropContract.claimConditions.set([
{
maxClaimableSupply: 10,
maxClaimablePerWallet: 1,
price: "100",
currencyAddress: NATIVE_TOKEN_ADDRESS,
snapshot: [w1.address, w2.address, w3.address],
@@ -569,7 +754,7 @@ describe("NFT Drop Contract (v4)", async () => {
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 0,
snapshot: [w1.address],
snapshot: [{ address: w1.address, maxClaimable: 1 }],
},
]);
+14 -14
View File
@@ -1,6 +1,5 @@
import {
ChainId,
isFeatureEnabled,
ChainId, getAllDetectedFeatureNames, isFeatureEnabled,
resolveContractUriFromAddress,
ThirdwebSDK,
} from "../../src/evm";
@@ -8,8 +7,7 @@ import { implementations, signers } from "./before-setup";
import { AddressZero } from "@ethersproject/constants";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import {
DropERC721__factory,
DropERC721_V3__factory,
DropERC721__factory, DropERC721_V3__factory,
TokenERC721__factory,
} from "@thirdweb-dev/contracts-js";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
@@ -85,10 +83,7 @@ describe("Publishing", async () => {
// Drop
expect(
isFeatureEnabled(
DropERC721__factory.abi,
"ERC721ClaimableWithConditionsV2",
),
isFeatureEnabled(DropERC721__factory.abi, "ERC721ClaimPhasesV2"),
).to.eq(true);
expect(isFeatureEnabled(DropERC721__factory.abi, "ERC721Supply")).to.eq(
true,
@@ -96,14 +91,19 @@ describe("Publishing", async () => {
expect(isFeatureEnabled(DropERC721__factory.abi, "ERC721Mintable")).to.eq(
false,
);
});
// Drop legacy
it("should extract all features", async () => {
const tokenFeatures = getAllDetectedFeatureNames(TokenERC721__factory.abi);
expect(
isFeatureEnabled(
DropERC721_V3__factory.abi,
"ERC721ClaimableWithConditionsV1",
),
).to.eq(true);
tokenFeatures
).to.contain("ERC721Enumerable");
expect(
getAllDetectedFeatureNames(DropERC721__factory.abi)
).to.contain("ERC721ClaimPhasesV2");
expect(
getAllDetectedFeatureNames(DropERC721_V3__factory.abi)
).to.contain("ERC721ClaimPhasesV1");
});
it("should update bio", async () => {
@@ -486,6 +486,7 @@ describe("Signature drop tests (v5)", async () => {
await signatureDropContract.claimConditions.set([
{
maxClaimablePerWallet: 1,
snapshot: [samWallet.address],
},
]);
@@ -500,6 +501,7 @@ describe("Signature drop tests (v5)", async () => {
await signatureDropContract.claimConditions.set([
{
startTime: new Date(),
maxClaimablePerWallet: 1,
snapshot: [bobWallet.address, samWallet.address, abbyWallet.address],
},
]);
@@ -715,6 +717,7 @@ describe("Signature drop tests (v5)", async () => {
]);
await signatureDropContract.claimConditions.set([
{
maxClaimablePerWallet: 0,
snapshot: [
{ address: w1.address, maxClaimable: 2 },
{ address: w2.address, maxClaimable: 1 },
@@ -13,7 +13,7 @@ import invariant from "tiny-invariant";
global.fetch = require("cross-fetch");
describe("Token Drop Contract (v3)", async () => {
describe("Token Drop Contract (v4)", async () => {
let dropContract: TokenDrop;
let adminWallet: SignerWithAddress,
samWallet: SignerWithAddress,
@@ -49,11 +49,13 @@ describe("Token Drop Contract (v3)", async () => {
await dropContract.claimConditions.set([
{
startTime: new Date(Date.now() / 2),
maxClaimablePerWallet: 1,
snapshot: [bobWallet.address, samWallet.address, abbyWallet.address],
price: 1,
},
{
startTime: new Date(),
maxClaimablePerWallet: 1,
snapshot: [bobWallet.address],
},
]);
@@ -77,6 +79,7 @@ describe("Token Drop Contract (v3)", async () => {
it("should return snapshot data on claim conditions", async () => {
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 1,
snapshot: [samWallet.address],
},
]);
@@ -93,10 +96,12 @@ describe("Token Drop Contract (v3)", async () => {
{
startTime: new Date(),
waitInSeconds: 10,
maxClaimablePerWallet: 1,
snapshot: [bobWallet.address, samWallet.address, abbyWallet.address],
},
{
startTime: new Date(Date.now() + 60 * 60 * 1000),
maxClaimablePerWallet: 1,
snapshot: [bobWallet.address],
},
]);
@@ -141,6 +146,7 @@ describe("Token Drop Contract (v3)", async () => {
);
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 2.1,
snapshot: members,
},
]);
@@ -159,6 +165,7 @@ describe("Token Drop Contract (v3)", async () => {
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 1,
snapshot: members,
},
]);
@@ -220,6 +227,7 @@ describe("Token Drop Contract (v3)", async () => {
it("should allow setting max claims per wallet", async () => {
await dropContract.claimConditions.set([
{
maxClaimablePerWallet: 0,
snapshot: [
{ address: w1.address, maxClaimable: "2.1" },
{ address: w2.address, maxClaimable: 1 },
@@ -344,6 +352,7 @@ describe("Token Drop Contract (v3)", async () => {
await dropContract.claimConditions.set([
{
maxClaimableSupply: 10,
maxClaimablePerWallet: 1,
price: "100",
currencyAddress: NATIVE_TOKEN_ADDRESS,
snapshot: [w1.address, w2.address, w3.address],
@@ -12,6 +12,12 @@ describe("NFTCollection", async () => {
name: "Test Collection",
description: "Test Description",
symbol: "TC",
creators: [
{
address: sdk.wallet.getAddress() || "",
share: 100,
},
],
});
collection = await sdk.getNFTCollection(addr);
});
@@ -25,6 +31,7 @@ describe("NFTCollection", async () => {
expect(nft.metadata.name).to.eq("Test NFT");
expect(nft.owner).to.eq(sdk.wallet.getAddress());
expect(nft.supply).to.eq(1);
expect(nft.metadata);
});
it("should fetch NFTs", async () => {
@@ -142,10 +149,16 @@ describe("NFTCollection", async () => {
expect(creators[0].share).to.equal(100);
const newCreator = Keypair.generate().publicKey.toBase58();
await collection.updateCreators([
{ address: sdk.wallet.getAddress() as string, share: 75 },
{ address: newCreator, share: 25 },
]);
await collection.updateCreators(
[
{
address: sdk.wallet.getAddress() as string,
share: 75,
},
{ address: newCreator, share: 25 },
],
true,
);
creators = await collection.getCreators();
expect(creators.length).to.equal(2);
@@ -153,13 +166,36 @@ describe("NFTCollection", async () => {
expect(creators[0].share).to.equal(75);
expect(creators[1].address).to.equal(newCreator);
expect(creators[1].share).to.equal(25);
const all = await collection.getAll();
for (const nft of all) {
// @ts-ignore
const creatorsOfNft = await collection.nft.creatorsOf(nft.metadata.id);
expect(creatorsOfNft.length).to.equal(2);
expect(creatorsOfNft[0].address).to.equal(sdk.wallet.getAddress());
expect(creatorsOfNft[0].share).to.equal(75);
expect(creatorsOfNft[1].address).to.equal(newCreator);
expect(creatorsOfNft[1].share).to.equal(25);
}
});
it("Should update royalty", async () => {
let royalty = await collection.getRoyalty();
expect(royalty).to.equal(0);
await collection.updateRoyalty(100);
const mintAddress = (await collection.getAll())[0].metadata.id;
// @ts-ignore
let nft = await collection.nft.getRaw(mintAddress);
expect(nft.isMutable).to.equal(true);
expect(nft.sellerFeeBasisPoints).to.equal(0);
await collection.updateRoyalty(100, true);
royalty = await collection.getRoyalty();
expect(royalty).to.equal(100);
// @ts-ignore
nft = await collection.nft.getRaw(mintAddress);
expect(nft.sellerFeeBasisPoints).to.equal(100);
});
});
+40
View File
@@ -152,4 +152,44 @@ describe("NFTDrop", async () => {
royalty = await drop.getRoyalty();
expect(royalty).to.equal(100);
});
it("Should get all correctly", async () => {
const address = await sdk.deployer.createNftDrop({
name: "NFT Drop #1",
totalSupply: 5,
});
const contract = await sdk.getNFTDrop(address);
await contract.lazyMint([
{ name: "NFT #1", description: "This is the #1 NFT" },
{ name: "NFT #2", description: "This is the #2 NFT" },
{ name: "NFT #3", description: "This is the #3 NFT" },
{ name: "NFT #4", description: "This is the #4 NFT" },
{ name: "NFT #5", description: "This is the #5 NFT" },
]);
let all = await contract.getAll();
expect(all.length).to.equal(5);
expect(all[0].metadata.name).to.equal("NFT #1");
expect(all.filter((nft) => nft.supply > 0).length).to.equal(0);
await contract.claimConditions.set({
price: 0,
maxClaimable: 5,
});
await contract.claim(2);
all = await contract.getAll();
expect(all.length).to.equal(5);
expect(all[0].supply).to.equal(1);
expect(all[1].supply).to.equal(1);
expect(all[2].supply).to.equal(0);
all = await contract.getAll({ start: 2, count: 3 });
expect(all.length).to.equal(3);
expect(all.filter((nft) => nft.supply > 0).length).to.equal(0);
all = await contract.getAll({ start: 0, count: 3 });
expect(all.filter((nft) => nft.supply > 0).length).to.equal(2);
});
});
+1 -1
View File
@@ -43,7 +43,7 @@
"@preconstruct/cli": "^2.2.1",
"@swc-node/register": "^1.5.1",
"@types/chai": "^4.3.3",
"@types/mocha": "^9.1.1",
"@types/mocha": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"chai": "^4.3.6",
+2 -1
View File
@@ -29,5 +29,6 @@
"devDependencies": {
"@playwright/test": "^1.27.1",
"serve": "^14.0.1"
}
},
"repository": "https://github.com/thirdweb-dev/js/tree/main/test/e2e/cra-5"
}
+4 -3
View File
@@ -11,13 +11,14 @@
"@thirdweb-dev/react": "*",
"@thirdweb-dev/sdk": "*",
"ethers": "^5.7.2",
"next": "^12",
"next": "^12.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@playwright/test": "^1.27.1",
"eslint": "8.25.0",
"eslint": "^8.21.0",
"eslint-config-next": "12.3.1"
}
},
"repository": "https://github.com/thirdweb-dev/js/tree/main/test/e2e/next-12"
}
+4 -3
View File
@@ -15,9 +15,10 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
"@playwright/test": "^1.27.1",
"@vitejs/plugin-react": "^2",
"vite": "^3",
"serve": "^14.0.1",
"@playwright/test": "^1.27.1"
}
"vite": "^3"
},
"repository": "https://github.com/thirdweb-dev/js/tree/main/test/e2e/vite-3"
}
+3477 -3423
View File
File diff suppressed because it is too large Load Diff