Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7129aef6e1 | ||
|
|
f50b74819d | ||
|
|
3eba69afd7 | ||
|
|
98c2253e8f | ||
|
|
32d91ef709 | ||
|
|
b79dc18f7a | ||
|
|
4af785c37c | ||
|
|
6f818f393e | ||
|
|
97ed4779a9 | ||
|
|
2d3fca5237 | ||
|
|
7016019f2a | ||
|
|
4e7b293c54 | ||
|
|
c9c0e4ff39 | ||
|
|
06c24c153a | ||
|
|
aa6d9ed75d | ||
|
|
42a606588d | ||
|
|
2820761003 | ||
|
|
5200d5d7e7 | ||
|
|
e731c1a7c3 |
+2
-1
@@ -16,4 +16,5 @@ public/dist
|
||||
.yalc/
|
||||
yalc.lock
|
||||
build/
|
||||
playwright-report/
|
||||
playwright-report/
|
||||
.vscode/
|
||||
+3
-3
@@ -15,8 +15,8 @@
|
||||
"scripts": {
|
||||
"test": "turbo run test test:evm test:solana",
|
||||
"e2e": "turbo run e2e",
|
||||
"build": "turbo run build",
|
||||
"build:release": "turbo run build --force",
|
||||
"build": "turbo run build --filter=./packages/*",
|
||||
"build:release": "turbo run build --force --filter=./packages/*",
|
||||
"dev": "turbo run dev --no-cache --parallel --continue",
|
||||
"fix:repo": "manypkg fix",
|
||||
"lint": "turbo run lint",
|
||||
@@ -28,7 +28,7 @@
|
||||
"release": "node ./scripts/pre-release.mjs && changeset publish",
|
||||
"version-packages:nightly": "changeset version --snapshot nightly",
|
||||
"release:nightly": "node ./scripts/pre-release.mjs && changeset publish --tag nightly",
|
||||
"push": "turbo run push"
|
||||
"push": "turbo run push --filter=./packages/*"
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @thirdweb-dev/auth
|
||||
|
||||
## 2.0.29
|
||||
|
||||
## 2.0.28
|
||||
|
||||
## 2.0.27
|
||||
|
||||
## 2.0.26
|
||||
|
||||
## 2.0.25
|
||||
|
||||
## 2.0.24
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/auth",
|
||||
"version": "2.0.25",
|
||||
"version": "2.0.29",
|
||||
"exports": {
|
||||
"./next": {
|
||||
"module": "./next/dist/thirdweb-dev-auth-next.esm.js",
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# thirdweb
|
||||
|
||||
## 0.8.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3eba69a`](https://github.com/thirdweb-dev/js/commit/3eba69afd78b7aa472a9c00c9d4220bb69ad1d3e), [`4af785c`](https://github.com/thirdweb-dev/js/commit/4af785c37c831ecd7087c1c9eb9095922cac0855), [`6f818f3`](https://github.com/thirdweb-dev/js/commit/6f818f393eb9023b53a3358d62d60ec23a9246bd), [`b79dc18`](https://github.com/thirdweb-dev/js/commit/b79dc18f7ab5155bbf4af02dc1a953546160bad0)]:
|
||||
- @thirdweb-dev/sdk@3.5.2
|
||||
|
||||
## 0.8.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4e7b293`](https://github.com/thirdweb-dev/js/commit/4e7b293c543244de79c150ca8c37d941221fc9a3), [`c9c0e4f`](https://github.com/thirdweb-dev/js/commit/c9c0e4ff39b054af746f3aec75fcbd0a3ef7239d)]:
|
||||
- @thirdweb-dev/sdk@3.5.1
|
||||
|
||||
## 0.8.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`5200d5d`](https://github.com/thirdweb-dev/js/commit/5200d5d7e730da58bd4d3e7c222e3c661265f913), [`42a6065`](https://github.com/thirdweb-dev/js/commit/42a606588dfed03c67c57357dd0ee1ad19fad6ea)]:
|
||||
- @thirdweb-dev/sdk@3.5.0
|
||||
|
||||
## 0.8.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e731c1a`](https://github.com/thirdweb-dev/js/commit/e731c1a7c341e6f2b9cc42c88d5c1b036dcf4a7c)]:
|
||||
- @thirdweb-dev/sdk@3.4.5
|
||||
|
||||
## 0.8.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "thirdweb",
|
||||
"main": "dist/cli/index.js",
|
||||
"version": "0.8.11",
|
||||
"version": "0.8.15",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @thirdweb-dev/contracts-js
|
||||
|
||||
## 1.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#376](https://github.com/thirdweb-dev/js/pull/376) [`4af785c`](https://github.com/thirdweb-dev/js/commit/4af785c37c831ecd7087c1c9eb9095922cac0855) Thanks [@adam-maj](https://github.com/adam-maj)! - Update tiered-drop with tokens claimed
|
||||
|
||||
- [#373](https://github.com/thirdweb-dev/js/pull/373) [`6f818f3`](https://github.com/thirdweb-dev/js/commit/6f818f393eb9023b53a3358d62d60ec23a9246bd) Thanks [@adam-maj](https://github.com/adam-maj)! - Update tiered-drop contract version
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#333](https://github.com/thirdweb-dev/js/pull/333) [`aa6d9ed`](https://github.com/thirdweb-dev/js/commit/aa6d9ed75d126d7a0cca9fb8fc389ff94e9d1e14) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update to contracts package v3.2.0
|
||||
|
||||
## 1.1.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "dist/thirdweb-dev-contracts-js-factories-TWMultichainRegistry__factory.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js-factories-TWMultichainRegistry__factory.esm.js"
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/contracts-js",
|
||||
"version": "1.1.9",
|
||||
"version": "1.2.1",
|
||||
"main": "dist/thirdweb-dev-contracts-js.cjs.js",
|
||||
"module": "dist/thirdweb-dev-contracts-js.esm.js",
|
||||
"license": "Apache-2.0",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/contracts-js",
|
||||
"dependencies": {
|
||||
"@thirdweb-dev/contracts": "3.1.8-0"
|
||||
"@thirdweb-dev/contracts": "^3.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @thirdweb-dev/react
|
||||
|
||||
## 3.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#369](https://github.com/thirdweb-dev/js/pull/369) [`2d3fca5`](https://github.com/thirdweb-dev/js/commit/2d3fca52373046c88392139922e9c9da79f9a2d4) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Add useClaimerProofs hook
|
||||
|
||||
- [#378](https://github.com/thirdweb-dev/js/pull/378) [`32d91ef`](https://github.com/thirdweb-dev/js/commit/32d91ef709c8fb92a903e52c65b27538a80ea4eb) Thanks [@mykcryptodev](https://github.com/mykcryptodev)! - round borders for buttons and menus on safari
|
||||
|
||||
- [#377](https://github.com/thirdweb-dev/js/pull/377) [`b79dc18`](https://github.com/thirdweb-dev/js/commit/b79dc18f7ab5155bbf4af02dc1a953546160bad0) Thanks [@adam-maj](https://github.com/adam-maj)! - Add event filters
|
||||
|
||||
## 3.5.1
|
||||
|
||||
## 3.5.0
|
||||
|
||||
## 3.4.5
|
||||
|
||||
## 3.4.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/react",
|
||||
"version": "3.4.4",
|
||||
"version": "3.5.2",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/react",
|
||||
"author": "thirdweb eng <[email protected]>",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -37,8 +37,19 @@ const BaseButton = styled.button<BaseButtonProps>`
|
||||
cursor: not-allowed;
|
||||
}
|
||||
&:focus {
|
||||
outline: 2px solid ${(props) => (props.theme as TwUiTheme).colors.accent};
|
||||
outline-offset: 1px;
|
||||
position: relative;
|
||||
border-radius: 0.5em;
|
||||
outline: 0;
|
||||
}
|
||||
&:focus:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
bottom: -5px;
|
||||
left: -5px;
|
||||
border: 2px solid ${(props) => (props.theme as TwUiTheme).colors.accent};
|
||||
border-radius: 11px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -8,10 +8,23 @@ export const Menu = styled.ul<MenuProps>`
|
||||
margin: 0;
|
||||
background: ${(props) => (props.theme as TwUiTheme).colors.background};
|
||||
color: ${(props) => (props.theme as TwUiTheme).colors.text};
|
||||
outline-color: ${(props) => (props.theme as TwUiTheme).colors.accent};
|
||||
outline-style: solid;
|
||||
outline-offset: 0px;
|
||||
list-style: none;
|
||||
border-radius: 0.5em;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
pointer-events: auto;
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border: 1px solid ${(props) => (props.theme as TwUiTheme).colors.accent};
|
||||
border-radius: 0.5em;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -45,7 +45,7 @@ export type SetClaimConditionsParams = {
|
||||
/** **********************/
|
||||
|
||||
/**
|
||||
* Use this to get the active claim conditon for ERC20, ERC721 or ERC1155 based contracts. They need to extend the `claimCondition` extension for this hook to work.
|
||||
* Use this to get the active claim condition for ERC20, ERC721 or ERC1155 based contracts. They need to extend the `claimCondition` extension for this hook to work.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
@@ -60,7 +60,7 @@ export type SetClaimConditionsParams = {
|
||||
* const { data: activeClaimCondition, isLoading, error } = useActiveClaimCondition(<YourERC1155ContractInstance>, <tokenId>);
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a contract that extends the ERC721 or ERC1155 spec and implements the `claimConditions` extension.
|
||||
* @param contract - an instance of a contract that extends the ERC721, ERC1155 or ERC20 spec and implements the `claimConditions` extension.
|
||||
* @param tokenId - the id of the token to fetch the claim conditions for (if the contract is an ERC1155 contract)
|
||||
* @returns a response object with the currently active claim condition
|
||||
* @twfeature ERC721ClaimableWithConditions | ERC1155ClaimableWithConditions | ERC20ClaimableWithConditions
|
||||
@@ -106,7 +106,70 @@ export function useActiveClaimCondition(
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get all claim conditons for ERC20, ERC721 or ERC1155 based contracts. They need to extend the `claimCondition` extension for this hook to work.
|
||||
* Use this to get the claimer proofs for an adddress for ERC20, ERC721 or ERC1155 based contracts. They need to extend the `claimCondition` extension for this hook to work.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: claimerProofs, isLoading, error } = useClaimerProofs(<YourERC20ContractInstance>);
|
||||
* ```
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: claimerProofs, isLoading, error } = useClaimerProofs(<YourERC721ContractInstance>);
|
||||
* ```
|
||||
* @example
|
||||
* ```javascript
|
||||
* const { data: claimerProofs, isLoading, error } = useClaimerProofs(<YourERC1155ContractInstance>, <tokenId>);
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a contract that extends the ERC721, ERC1155 or ERC20 spec and implements the `claimConditions` extension.
|
||||
* @param claimerAddress - the address of the claimer to fetch the claimer proofs for
|
||||
* @param tokenId - the id of the token to fetch the claimer proofs for (if the contract is an ERC1155 contract)
|
||||
* @param claimConditionId - optional the claim condition id to get the proofs for
|
||||
* @returns a response object with the snapshot for the provided address
|
||||
* @twfeature ERC721ClaimableWithConditions | ERC1155ClaimableWithConditions | ERC20ClaimableWithConditions
|
||||
* @beta
|
||||
*/
|
||||
export function useClaimerProofs(
|
||||
contract: RequiredParam<DropContract>,
|
||||
claimerAddress: string,
|
||||
tokenId?: BigNumberish,
|
||||
claimConditionId?: BigNumberish,
|
||||
) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
const { erc1155, erc721, erc20 } = getErcs(contract);
|
||||
|
||||
return useQueryWithNetwork(
|
||||
cacheKeys.extensions.claimConditions.getClaimerProofs(
|
||||
contractAddress,
|
||||
tokenId,
|
||||
),
|
||||
() => {
|
||||
if (erc1155) {
|
||||
requiredParamInvariant(
|
||||
tokenId,
|
||||
"tokenId is required for ERC1155 claim conditions",
|
||||
);
|
||||
return erc1155.claimConditions.getClaimerProofs(tokenId, claimerAddress, claimConditionId);
|
||||
}
|
||||
if (erc721) {
|
||||
return erc721.claimConditions.getClaimerProofs(claimerAddress, claimConditionId);
|
||||
}
|
||||
if (erc20) {
|
||||
return erc20.claimConditions.getClaimerProofs(claimerAddress, claimConditionId);
|
||||
}
|
||||
throw new Error("Contract must be ERC721, ERC1155 or ERC20");
|
||||
},
|
||||
{
|
||||
// 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,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to get all claim conditions for ERC20, ERC721 or ERC1155 based contracts. They need to extend the `claimCondition` extension for this hook to work.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
@@ -121,7 +184,7 @@ export function useActiveClaimCondition(
|
||||
* const { data: claimConditions, isLoading, error } = useClaimConditions(<YourERC1155ContractInstance>, <tokenId>);
|
||||
* ```
|
||||
*
|
||||
* @param contract - an instance of a contract that extends the ERC721 or ERC1155 spec and implements the `claimConditions` extension.
|
||||
* @param contract - an instance of a contract that extends the ERC721, ERC1155 or ERC20 spec and implements the `claimConditions` extension.
|
||||
* @param tokenId - the id of the token to fetch the claim conditions for (if the contract is an ERC1155 contract)
|
||||
* @returns a response object with the list of claim conditions
|
||||
* @twfeature ERC721ClaimableWithConditions | ERC1155ClaimableWithConditions | ERC20ClaimableWithConditions
|
||||
@@ -368,8 +431,6 @@ export function useResetClaimConditions(
|
||||
return conditions.map((c) => ({
|
||||
...c,
|
||||
price: c.currencyMetadata.displayValue,
|
||||
maxQuantity: c.maxQuantity.toString(),
|
||||
quantityLimitPerTransaction: c.quantityLimitPerTransaction.toString(),
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
ContractEvent,
|
||||
ContractForPrebuiltContractType,
|
||||
ContractType,
|
||||
EventQueryFilter,
|
||||
EventQueryOptions,
|
||||
PrebuiltContractType,
|
||||
SUPPORTED_CHAIN_ID,
|
||||
ThirdwebSDK,
|
||||
@@ -346,7 +346,7 @@ export function useContractMetadataUpdate(
|
||||
export function useContractEvents(
|
||||
contract: RequiredParam<ValidContractInstance>,
|
||||
eventName?: string,
|
||||
options: { queryFilter?: EventQueryFilter; subscribe?: boolean } = {
|
||||
options: { queryFilter?: EventQueryOptions; subscribe?: boolean } = {
|
||||
subscribe: true,
|
||||
},
|
||||
) {
|
||||
|
||||
@@ -232,11 +232,9 @@ export function useClaimToken(contract: RequiredParam<TokenContract>) {
|
||||
invariant(data.to, 'No "to" address provided');
|
||||
if (erc20) {
|
||||
invariant(erc20?.claimTo, "contract does not support claimTo");
|
||||
return await erc20.claimTo(
|
||||
data.to,
|
||||
data.amount,
|
||||
data.checkERC20Allowance,
|
||||
);
|
||||
return await erc20.claimTo(data.to, data.amount, {
|
||||
checkERC20Allowance: data.checkERC20Allowance,
|
||||
});
|
||||
}
|
||||
invariant(false, "Smart contract is not a valid erc20 contract");
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useContract } from "../async/contracts";
|
||||
* import { useContract } from '@thirdweb-dev/react'
|
||||
*
|
||||
* export default function Component() {
|
||||
* const { contract } = useNFTCollection("<YOUR-CONTRACT-ADDRESS>", "nft-collection")
|
||||
* const { contract, isLoading, error } = useContract("<YOUR-CONTRACT-ADDRESS>", "nft-collection")
|
||||
*
|
||||
* // Now you can use the nftCollection contract in the rest of the component
|
||||
*
|
||||
|
||||
@@ -287,6 +287,16 @@ export const cacheKeys = {
|
||||
? ["claimConditions", "getAll", { tokenId }, options]
|
||||
: ["claimConditions", "getAll", options],
|
||||
),
|
||||
getClaimerProofs: (
|
||||
contractAddress: RequiredParam<ContractAddress>,
|
||||
tokenId?: BigNumberish,
|
||||
) =>
|
||||
createContractCacheKey(
|
||||
contractAddress,
|
||||
tokenId
|
||||
? ["claimConditions", "getClaimerProofs", { tokenId }]
|
||||
: ["claimConditions", "getClaimerProofs"],
|
||||
),
|
||||
getClaimIneligibilityReasons: (
|
||||
contractAddress: RequiredParam<ContractAddress>,
|
||||
params: { walletAddress?: WalletAddress; quantity: string | number },
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
RequiredParam,
|
||||
} from "../../../core/query-utils/required-param";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import type { Amount, Token } from "@thirdweb-dev/sdk/solana";
|
||||
import type { Amount } from "@thirdweb-dev/sdk";
|
||||
import type { Token } from "@thirdweb-dev/sdk/solana";
|
||||
|
||||
export type TransferTokenMutationParams = {
|
||||
amount: Amount;
|
||||
|
||||
@@ -1,5 +1,60 @@
|
||||
# @thirdweb-dev/sdk
|
||||
|
||||
## 3.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#380](https://github.com/thirdweb-dev/js/pull/380) [`3eba69a`](https://github.com/thirdweb-dev/js/commit/3eba69afd78b7aa472a9c00c9d4220bb69ad1d3e) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Expose sdk.wallet.getChainId()
|
||||
|
||||
- [#376](https://github.com/thirdweb-dev/js/pull/376) [`4af785c`](https://github.com/thirdweb-dev/js/commit/4af785c37c831ecd7087c1c9eb9095922cac0855) Thanks [@adam-maj](https://github.com/adam-maj)! - Update tiered-drop with tokens claimed
|
||||
|
||||
- [#373](https://github.com/thirdweb-dev/js/pull/373) [`6f818f3`](https://github.com/thirdweb-dev/js/commit/6f818f393eb9023b53a3358d62d60ec23a9246bd) Thanks [@adam-maj](https://github.com/adam-maj)! - Update tiered-drop contract version
|
||||
|
||||
- [#377](https://github.com/thirdweb-dev/js/pull/377) [`b79dc18`](https://github.com/thirdweb-dev/js/commit/b79dc18f7ab5155bbf4af02dc1a953546160bad0) Thanks [@adam-maj](https://github.com/adam-maj)! - Add event filters
|
||||
|
||||
- Updated dependencies [[`4af785c`](https://github.com/thirdweb-dev/js/commit/4af785c37c831ecd7087c1c9eb9095922cac0855), [`6f818f3`](https://github.com/thirdweb-dev/js/commit/6f818f393eb9023b53a3358d62d60ec23a9246bd)]:
|
||||
- @thirdweb-dev/contracts-js@1.2.1
|
||||
|
||||
## 3.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#368](https://github.com/thirdweb-dev/js/pull/368) [`4e7b293`](https://github.com/thirdweb-dev/js/commit/4e7b293c543244de79c150ca8c37d941221fc9a3) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix handling of old allowlist proof format
|
||||
|
||||
- [#365](https://github.com/thirdweb-dev/js/pull/365) [`c9c0e4f`](https://github.com/thirdweb-dev/js/commit/c9c0e4ff39b054af746f3aec75fcbd0a3ef7239d) Thanks [@jnsdls](https://github.com/jnsdls)! - [EVM] - now React Native compatible
|
||||
|
||||
## 3.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#329](https://github.com/thirdweb-dev/js/pull/329) [`5200d5d`](https://github.com/thirdweb-dev/js/commit/5200d5d7e730da58bd4d3e7c222e3c661265f913) Thanks [@adam-maj](https://github.com/adam-maj)! - ### Breaking changes:
|
||||
|
||||
1. claim conditions had some changes to support the new drop contracts:
|
||||
|
||||
- `maxClaimablePerTransaction` is now named `maxClaimablePerWallet`
|
||||
- `maxQuantity` is now named `maxClaimable`
|
||||
|
||||
2. signature minting now requires a `to` address to be set for security purposees
|
||||
|
||||
### Main Changes:
|
||||
|
||||
- Support for new optimized Drop contracts
|
||||
- Support for new claim conditions with overrides
|
||||
- Don't allow zero address recipient on signature minting
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#362](https://github.com/thirdweb-dev/js/pull/362) [`42a6065`](https://github.com/thirdweb-dev/js/commit/42a606588dfed03c67c57357dd0ee1ad19fad6ea) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - [SDK] Fix fetching NFTs with numbers as ids
|
||||
|
||||
- Updated dependencies [[`aa6d9ed`](https://github.com/thirdweb-dev/js/commit/aa6d9ed75d126d7a0cca9fb8fc389ff94e9d1e14)]:
|
||||
- @thirdweb-dev/contracts-js@1.2.0
|
||||
|
||||
## 3.4.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#359](https://github.com/thirdweb-dev/js/pull/359) [`e731c1a`](https://github.com/thirdweb-dev/js/commit/e731c1a7c341e6f2b9cc42c88d5c1b036dcf4a7c) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - [SDK] Allow fetching claimerProofs from any claim condition
|
||||
|
||||
## 3.4.4
|
||||
|
||||
## 3.4.3
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@thirdweb-dev/sdk",
|
||||
"version": "3.4.4",
|
||||
"version": "3.5.2",
|
||||
"description": "The main thirdweb SDK.",
|
||||
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/sdk",
|
||||
"license": "Apache-2.0",
|
||||
@@ -108,7 +108,6 @@
|
||||
"@thirdweb-dev/storage": "*",
|
||||
"bn.js": "^5.2.1",
|
||||
"bs58": "^5.0.0",
|
||||
"cbor-x": "^1.4.1",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
|
||||
@@ -2,8 +2,14 @@ import BN from "bn.js";
|
||||
import { BigNumber } from "ethers";
|
||||
import { z } from "zod";
|
||||
|
||||
const isBrowser = () => typeof window !== "undefined";
|
||||
const FileOrBufferUnionSchema = isBrowser()
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const isBrowser = () => typeof window !== "undefined";
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const FileOrBufferUnionSchema = isBrowser()
|
||||
? (z.instanceof(File) as z.ZodType<InstanceType<typeof File>>)
|
||||
: (z.instanceof(Buffer) as z.ZodTypeAny); // @fixme, this is a hack to make browser happy for now
|
||||
|
||||
@@ -96,6 +102,11 @@ export const AmountSchema = z
|
||||
])
|
||||
.transform((arg) => (typeof arg === "number" ? arg.toString() : arg));
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type Amount = z.input<typeof AmountSchema>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -103,6 +114,8 @@ export const QuantitySchema = z
|
||||
.union([AmountSchema, z.literal("unlimited")])
|
||||
.default("unlimited");
|
||||
|
||||
export type Quantity = z.output<typeof QuantitySchema>;
|
||||
|
||||
export const RawDateSchema = z.date().transform((i) => {
|
||||
return BigNumber.from(Math.floor(i.getTime() / 1000));
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Quantity } from "../../core/schema/shared";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../constants";
|
||||
import { ContractWrapper } from "../core/classes/contract-wrapper";
|
||||
import {
|
||||
AbstractClaimConditionContractStruct,
|
||||
ClaimConditionInputArray,
|
||||
ClaimConditionInputSchema,
|
||||
ClaimConditionOutputSchema,
|
||||
@@ -8,7 +10,6 @@ import {
|
||||
import {
|
||||
SnapshotEntry,
|
||||
SnapshotEntryWithProof,
|
||||
SnapshotInputSchema,
|
||||
SnapshotSchema,
|
||||
} from "../schema/contracts/common/snapshots";
|
||||
import {
|
||||
@@ -25,9 +26,13 @@ import {
|
||||
isNativeToken,
|
||||
normalizePriceValue,
|
||||
} from "./currency";
|
||||
import { ShardedMerkleTree } from "./sharded-merkle-tree";
|
||||
import {
|
||||
ShardedMerkleTree,
|
||||
SnapshotFormatVersion,
|
||||
} from "./sharded-merkle-tree";
|
||||
import { createSnapshot } from "./snapshots";
|
||||
import { IDropClaimCondition } from "@thirdweb-dev/contracts-js/dist/declarations/src/DropERC20";
|
||||
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,
|
||||
@@ -52,27 +57,58 @@ export async function prepareClaim(
|
||||
contractWrapper: ContractWrapper<any>,
|
||||
storage: ThirdwebStorage,
|
||||
checkERC20Allowance: boolean,
|
||||
snapshotFormatVersion: SnapshotFormatVersion,
|
||||
): Promise<ClaimVerification> {
|
||||
const addressToClaim = await contractWrapper.getSignerAddress();
|
||||
let maxClaimable = BigNumber.from(0);
|
||||
let maxClaimable = convertQuantityToBigNumber(
|
||||
activeClaimCondition.maxClaimablePerWallet,
|
||||
tokenDecimals,
|
||||
);
|
||||
let proofs = [utils.hexZeroPad([0], 32)];
|
||||
let priceInProof = activeClaimCondition.price; // the price to send to the contract in claim proofs
|
||||
let currencyAddressInProof = activeClaimCondition.currencyAddress;
|
||||
try {
|
||||
if (
|
||||
!activeClaimCondition.merkleRootHash
|
||||
.toString()
|
||||
.startsWith(constants.AddressZero)
|
||||
) {
|
||||
const claim = await fetchSnapshotEntryForAddress(
|
||||
const snapshotEntry = await fetchSnapshotEntryForAddress(
|
||||
addressToClaim,
|
||||
activeClaimCondition.merkleRootHash.toString(),
|
||||
await merkleMetadataFetcher(),
|
||||
contractWrapper.getProvider(),
|
||||
storage,
|
||||
snapshotFormatVersion,
|
||||
);
|
||||
if (!claim) {
|
||||
throw new Error("No claim found for this address");
|
||||
if (snapshotEntry) {
|
||||
proofs = snapshotEntry.proof;
|
||||
// override only if not default values (unlimited for quantity, zero addr for currency)
|
||||
maxClaimable =
|
||||
snapshotEntry.maxClaimable === "unlimited"
|
||||
? ethers.constants.MaxUint256
|
||||
: ethers.utils.parseUnits(
|
||||
snapshotEntry.maxClaimable,
|
||||
tokenDecimals,
|
||||
);
|
||||
priceInProof =
|
||||
snapshotEntry.price === undefined ||
|
||||
snapshotEntry.price === "unlimited"
|
||||
? ethers.constants.MaxUint256
|
||||
: await normalizePriceValue(
|
||||
contractWrapper.getProvider(),
|
||||
snapshotEntry.price,
|
||||
snapshotEntry.currencyAddress || ethers.constants.AddressZero,
|
||||
);
|
||||
currencyAddressInProof =
|
||||
snapshotEntry.currencyAddress || ethers.constants.AddressZero;
|
||||
} else {
|
||||
// if no snapshot entry, and it's a v1 format (exclusive allowlist) then address can't claim
|
||||
if (snapshotFormatVersion === SnapshotFormatVersion.V1) {
|
||||
throw new Error("No claim found for this address");
|
||||
}
|
||||
// but if its snapshot v2 (override list behavior) then address can still claim with default settings
|
||||
}
|
||||
proofs = claim.proof;
|
||||
maxClaimable = ethers.utils.parseUnits(claim.maxClaimable, tokenDecimals);
|
||||
}
|
||||
} catch (e) {
|
||||
// have to handle the valid error case that we *do* want to throw on
|
||||
@@ -87,18 +123,28 @@ export async function prepareClaim(
|
||||
}
|
||||
|
||||
const overrides = (await contractWrapper.getCallOverrides()) || {};
|
||||
const price = activeClaimCondition.price;
|
||||
const currencyAddress = activeClaimCondition.currencyAddress;
|
||||
if (price.gt(0)) {
|
||||
// the actual price to check allowance against
|
||||
// if proof price is unlimited, then we use the price from the claim condition
|
||||
// this mimics the contract behavior
|
||||
const pricePerToken =
|
||||
priceInProof.toString() !== ethers.constants.MaxUint256.toString()
|
||||
? priceInProof
|
||||
: activeClaimCondition.price;
|
||||
// same for currency address
|
||||
const currencyAddress =
|
||||
currencyAddressInProof !== ethers.constants.AddressZero
|
||||
? currencyAddressInProof
|
||||
: activeClaimCondition.currencyAddress;
|
||||
if (pricePerToken.gt(0)) {
|
||||
if (isNativeToken(currencyAddress)) {
|
||||
overrides["value"] = BigNumber.from(price)
|
||||
overrides["value"] = BigNumber.from(pricePerToken)
|
||||
.mul(quantity)
|
||||
.div(ethers.utils.parseUnits("1", tokenDecimals));
|
||||
} else if (checkERC20Allowance) {
|
||||
await approveErc20Allowance(
|
||||
contractWrapper,
|
||||
currencyAddress,
|
||||
price,
|
||||
pricePerToken,
|
||||
quantity,
|
||||
tokenDecimals,
|
||||
);
|
||||
@@ -107,9 +153,9 @@ export async function prepareClaim(
|
||||
return {
|
||||
overrides,
|
||||
proofs,
|
||||
maxQuantityPerTransaction: maxClaimable,
|
||||
price,
|
||||
currencyAddress,
|
||||
maxClaimable,
|
||||
price: priceInProof,
|
||||
currencyAddress: currencyAddressInProof,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -139,6 +185,8 @@ export async function fetchSnapshot(
|
||||
return snapshotData.claims.map((claim) => ({
|
||||
address: claim.address,
|
||||
maxClaimable: claim.maxClaimable,
|
||||
price: claim.price,
|
||||
currencyAddress: claim.currencyAddress,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -150,7 +198,9 @@ export async function fetchSnapshotEntryForAddress(
|
||||
address: string,
|
||||
merkleRoot: string,
|
||||
merkleMetadata: Record<string, string> | undefined,
|
||||
provider: ethers.providers.Provider,
|
||||
storage: ThirdwebStorage,
|
||||
snapshotFormatVersion: SnapshotFormatVersion,
|
||||
): Promise<SnapshotEntryWithProof | null> {
|
||||
if (!merkleMetadata) {
|
||||
return null;
|
||||
@@ -163,7 +213,11 @@ export async function fetchSnapshotEntryForAddress(
|
||||
raw,
|
||||
storage,
|
||||
);
|
||||
return await merkleTree.getProof(address);
|
||||
return await merkleTree.getProof(
|
||||
address,
|
||||
provider,
|
||||
snapshotFormatVersion,
|
||||
);
|
||||
}
|
||||
// legacy non-sharded, just fetch it all and filter out
|
||||
const snapshotData = SnapshotSchema.parse(raw);
|
||||
@@ -238,6 +292,8 @@ export async function updateExistingClaimConditions(
|
||||
* @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(
|
||||
@@ -246,38 +302,63 @@ export async function getClaimerProofs(
|
||||
tokenDecimals: number,
|
||||
merkleMetadata: Record<string, string>,
|
||||
storage: ThirdwebStorage,
|
||||
): Promise<{ maxClaimable: BigNumber; proof: string[] }> {
|
||||
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 {
|
||||
proof: [],
|
||||
maxClaimable: BigNumber.from(0),
|
||||
};
|
||||
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: ethers.utils.parseUnits(claim.maxClaimable, tokenDecimals),
|
||||
maxClaimable,
|
||||
price,
|
||||
currencyAddress: claim.currencyAddress || ethers.constants.AddressZero,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and uploads snapshots + converts claim conditions to contract format
|
||||
* @internal
|
||||
* Decorates claim conditions with merkle roots from snapshots if present
|
||||
* @param claimConditionInputs
|
||||
* @param tokenDecimals
|
||||
* @param provider
|
||||
* @param storage
|
||||
* @internal
|
||||
* @param snapshotFormatVersion
|
||||
*/
|
||||
export async function processClaimConditionInputs(
|
||||
async function processSnapshotData(
|
||||
claimConditionInputs: ClaimConditionInput[],
|
||||
tokenDecimals: number,
|
||||
provider: providers.Provider,
|
||||
provider: ethers.providers.Provider,
|
||||
storage: ThirdwebStorage,
|
||||
snapshotFormatVersion: SnapshotFormatVersion,
|
||||
) {
|
||||
const snapshotInfos: SnapshotInfo[] = [];
|
||||
const inputsWithSnapshots = await Promise.all(
|
||||
@@ -285,9 +366,11 @@ export async function processClaimConditionInputs(
|
||||
// check snapshots and upload if provided
|
||||
if (conditionInput.snapshot && conditionInput.snapshot.length > 0) {
|
||||
const snapshotInfo = await createSnapshot(
|
||||
SnapshotInputSchema.parse(conditionInput.snapshot),
|
||||
conditionInput.snapshot,
|
||||
tokenDecimals,
|
||||
provider,
|
||||
storage,
|
||||
snapshotFormatVersion,
|
||||
);
|
||||
snapshotInfos.push(snapshotInfo);
|
||||
conditionInput.merkleRootHash = snapshotInfo.merkleRoot;
|
||||
@@ -299,26 +382,54 @@ export async function processClaimConditionInputs(
|
||||
return conditionInput;
|
||||
}),
|
||||
);
|
||||
return { inputsWithSnapshots, snapshotInfos };
|
||||
}
|
||||
|
||||
function compare(a: BigNumberish, b: BigNumberish) {
|
||||
const left = BigNumber.from(a);
|
||||
const right = BigNumber.from(b);
|
||||
if (left.eq(right)) {
|
||||
return 0;
|
||||
} else if (left.gt(right)) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and uploads snapshots + converts claim conditions to contract format
|
||||
* @param claimConditionInputs
|
||||
* @param tokenDecimals
|
||||
* @param provider
|
||||
* @param storage
|
||||
* @param snapshotFormatVersion
|
||||
* @internal
|
||||
*/
|
||||
export async function processClaimConditionInputs(
|
||||
claimConditionInputs: ClaimConditionInput[],
|
||||
tokenDecimals: number,
|
||||
provider: providers.Provider,
|
||||
storage: ThirdwebStorage,
|
||||
snapshotFormatVersion: SnapshotFormatVersion,
|
||||
) {
|
||||
const { inputsWithSnapshots, snapshotInfos } = await processSnapshotData(
|
||||
claimConditionInputs,
|
||||
tokenDecimals,
|
||||
provider,
|
||||
storage,
|
||||
snapshotFormatVersion,
|
||||
);
|
||||
const parsedInputs = ClaimConditionInputArray.parse(inputsWithSnapshots);
|
||||
|
||||
// Convert processed inputs to the format the contract expects, and sort by timestamp
|
||||
const sortedConditions: IDropClaimCondition.ClaimConditionStruct[] = (
|
||||
const sortedConditions: AbstractClaimConditionContractStruct[] = (
|
||||
await Promise.all(
|
||||
parsedInputs.map((c) =>
|
||||
convertToContractModel(c, tokenDecimals, provider),
|
||||
convertToContractModel(c, tokenDecimals, provider, storage),
|
||||
),
|
||||
)
|
||||
).sort((a, b) => {
|
||||
const left = BigNumber.from(a.startTimestamp);
|
||||
const right = BigNumber.from(b.startTimestamp);
|
||||
if (left.eq(right)) {
|
||||
return 0;
|
||||
} else if (left.gt(right)) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
return compare(a.startTimestamp, b.startTimestamp);
|
||||
});
|
||||
return { snapshotInfos, sortedConditions };
|
||||
}
|
||||
@@ -328,41 +439,106 @@ export async function processClaimConditionInputs(
|
||||
* @param c
|
||||
* @param tokenDecimals
|
||||
* @param provider
|
||||
* @param storage
|
||||
* @internal
|
||||
*/
|
||||
async function convertToContractModel(
|
||||
c: FilledConditionInput,
|
||||
tokenDecimals: number,
|
||||
provider: providers.Provider,
|
||||
): Promise<IDropClaimCondition.ClaimConditionStruct> {
|
||||
storage: ThirdwebStorage,
|
||||
): Promise<AbstractClaimConditionContractStruct> {
|
||||
const currency =
|
||||
c.currencyAddress === constants.AddressZero
|
||||
? NATIVE_TOKEN_ADDRESS
|
||||
: c.currencyAddress;
|
||||
let maxClaimableSupply;
|
||||
let quantityLimitPerTransaction;
|
||||
if (c.maxQuantity === "unlimited") {
|
||||
maxClaimableSupply = ethers.constants.MaxUint256.toString();
|
||||
} else {
|
||||
maxClaimableSupply = ethers.utils.parseUnits(c.maxQuantity, tokenDecimals);
|
||||
}
|
||||
if (c.quantityLimitPerTransaction === "unlimited") {
|
||||
quantityLimitPerTransaction = ethers.constants.MaxUint256.toString();
|
||||
} else {
|
||||
quantityLimitPerTransaction = ethers.utils.parseUnits(
|
||||
c.quantityLimitPerTransaction,
|
||||
tokenDecimals,
|
||||
);
|
||||
const maxClaimableSupply = convertQuantityToBigNumber(
|
||||
c.maxClaimableSupply,
|
||||
tokenDecimals,
|
||||
);
|
||||
const maxClaimablePerWallet = convertQuantityToBigNumber(
|
||||
c.maxClaimablePerWallet,
|
||||
tokenDecimals,
|
||||
);
|
||||
let metadataOrUri;
|
||||
if (c.metadata) {
|
||||
if (typeof c.metadata === "string") {
|
||||
metadataOrUri = c.metadata;
|
||||
} else {
|
||||
metadataOrUri = await storage.upload(c.metadata);
|
||||
}
|
||||
}
|
||||
return {
|
||||
startTimestamp: c.startTime,
|
||||
maxClaimableSupply,
|
||||
supplyClaimed: 0,
|
||||
quantityLimitPerTransaction,
|
||||
waitTimeInSecondsBetweenClaims: c.waitInSeconds,
|
||||
maxClaimablePerWallet,
|
||||
pricePerToken: await normalizePriceValue(provider, c.price, currency),
|
||||
currency,
|
||||
merkleRoot: c.merkleRootHash,
|
||||
merkleRoot: c.merkleRootHash.toString(),
|
||||
waitTimeInSecondsBetweenClaims: c.waitInSeconds || 0,
|
||||
metadata: metadataOrUri,
|
||||
};
|
||||
}
|
||||
|
||||
export function abstractContractModelToLegacy(
|
||||
model: AbstractClaimConditionContractStruct,
|
||||
): IDropClaimCondition_V2.ClaimConditionStruct {
|
||||
return {
|
||||
startTimestamp: model.startTimestamp,
|
||||
maxClaimableSupply: model.maxClaimableSupply,
|
||||
supplyClaimed: model.supplyClaimed,
|
||||
merkleRoot: model.merkleRoot,
|
||||
pricePerToken: model.pricePerToken,
|
||||
currency: model.currency,
|
||||
quantityLimitPerTransaction: model.maxClaimablePerWallet,
|
||||
waitTimeInSecondsBetweenClaims: model.waitTimeInSecondsBetweenClaims || 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function abstractContractModelToNew(
|
||||
model: AbstractClaimConditionContractStruct,
|
||||
): IClaimCondition.ClaimConditionStruct {
|
||||
return {
|
||||
startTimestamp: model.startTimestamp,
|
||||
maxClaimableSupply: model.maxClaimableSupply,
|
||||
supplyClaimed: model.supplyClaimed,
|
||||
merkleRoot: model.merkleRoot,
|
||||
pricePerToken: model.pricePerToken,
|
||||
currency: model.currency,
|
||||
quantityLimitPerWallet: model.maxClaimablePerWallet,
|
||||
metadata: model.metadata || "",
|
||||
};
|
||||
}
|
||||
|
||||
export function legacyContractModelToAbstract(
|
||||
model: IDropClaimCondition_V2.ClaimConditionStruct,
|
||||
): AbstractClaimConditionContractStruct {
|
||||
return {
|
||||
startTimestamp: model.startTimestamp,
|
||||
maxClaimableSupply: model.maxClaimableSupply,
|
||||
supplyClaimed: model.supplyClaimed,
|
||||
merkleRoot: model.merkleRoot.toString(),
|
||||
pricePerToken: model.pricePerToken,
|
||||
currency: model.currency,
|
||||
maxClaimablePerWallet: model.quantityLimitPerTransaction,
|
||||
waitTimeInSecondsBetweenClaims: model.waitTimeInSecondsBetweenClaims,
|
||||
};
|
||||
}
|
||||
|
||||
export function newContractModelToAbstract(
|
||||
model: IClaimCondition.ClaimConditionStruct,
|
||||
): AbstractClaimConditionContractStruct {
|
||||
return {
|
||||
startTimestamp: model.startTimestamp,
|
||||
maxClaimableSupply: model.maxClaimableSupply,
|
||||
supplyClaimed: model.supplyClaimed,
|
||||
merkleRoot: model.merkleRoot.toString(),
|
||||
pricePerToken: model.pricePerToken,
|
||||
currency: model.currency,
|
||||
maxClaimablePerWallet: model.quantityLimitPerWallet,
|
||||
waitTimeInSecondsBetweenClaims: 0,
|
||||
metadata: model.metadata,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -373,10 +549,11 @@ async function convertToContractModel(
|
||||
* @param provider
|
||||
* @param merkleMetadata
|
||||
* @param storage
|
||||
* @param shouldDownloadSnapshot
|
||||
* @internal
|
||||
*/
|
||||
export async function transformResultToClaimCondition(
|
||||
pm: IDropClaimCondition.ClaimConditionStructOutput,
|
||||
pm: AbstractClaimConditionContractStruct,
|
||||
tokenDecimals: number,
|
||||
provider: providers.Provider,
|
||||
merkleMetadata: Record<string, string> | undefined,
|
||||
@@ -389,8 +566,8 @@ export async function transformResultToClaimCondition(
|
||||
pm.maxClaimableSupply,
|
||||
tokenDecimals,
|
||||
);
|
||||
const quantityLimitPerTransaction = convertToReadableQuantity(
|
||||
pm.quantityLimitPerTransaction,
|
||||
const maxClaimablePerWallet = convertToReadableQuantity(
|
||||
pm.maxClaimablePerWallet,
|
||||
tokenDecimals,
|
||||
);
|
||||
const availableSupply = convertToReadableQuantity(
|
||||
@@ -403,11 +580,11 @@ export async function transformResultToClaimCondition(
|
||||
);
|
||||
return ClaimConditionOutputSchema.parse({
|
||||
startTime: pm.startTimestamp,
|
||||
maxQuantity: maxClaimableSupply,
|
||||
maxClaimableSupply,
|
||||
maxClaimablePerWallet,
|
||||
currentMintSupply,
|
||||
availableSupply,
|
||||
quantityLimitPerTransaction,
|
||||
waitInSeconds: pm.waitTimeInSecondsBetweenClaims.toString(),
|
||||
waitInSeconds: pm.waitTimeInSecondsBetweenClaims?.toString(),
|
||||
price: BigNumber.from(pm.pricePerToken),
|
||||
currency: pm.currency,
|
||||
currencyAddress: pm.currency,
|
||||
@@ -416,10 +593,19 @@ export async function transformResultToClaimCondition(
|
||||
snapshot: shouldDownloadSnapshot
|
||||
? await fetchSnapshot(pm.merkleRoot, merkleMetadata, storage)
|
||||
: undefined,
|
||||
metadata: pm.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
function convertToReadableQuantity(bn: BigNumber, tokenDecimals: number) {
|
||||
/**
|
||||
* @internal
|
||||
* @param bn
|
||||
* @param tokenDecimals
|
||||
*/
|
||||
export function convertToReadableQuantity(
|
||||
bn: BigNumberish,
|
||||
tokenDecimals: number,
|
||||
) {
|
||||
if (bn.toString() === ethers.constants.MaxUint256.toString()) {
|
||||
return "unlimited";
|
||||
} else {
|
||||
@@ -427,6 +613,22 @@ function convertToReadableQuantity(bn: BigNumber, tokenDecimals: number) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param quantity
|
||||
* @param tokenDecimals
|
||||
*/
|
||||
export function convertQuantityToBigNumber(
|
||||
quantity: Quantity,
|
||||
tokenDecimals: number,
|
||||
) {
|
||||
if (quantity === "unlimited") {
|
||||
return ethers.constants.MaxUint256;
|
||||
} else {
|
||||
return ethers.utils.parseUnits(quantity, tokenDecimals);
|
||||
}
|
||||
}
|
||||
|
||||
export async function calculateClaimCost(
|
||||
contractWrapper: ContractWrapper<any>,
|
||||
pricePerToken: Price,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { AmountSchema } from "../../core/schema/shared";
|
||||
import {
|
||||
getNativeTokenByChainId,
|
||||
NATIVE_TOKEN_ADDRESS,
|
||||
} from "../constants/currency";
|
||||
import { ContractWrapper } from "../core/classes/contract-wrapper";
|
||||
import { AmountSchema } from "../schema/shared";
|
||||
import { Amount, Currency, CurrencyValue, Price } from "../types/currency";
|
||||
import { BaseERC20 } from "../types/eips";
|
||||
import type { IERC20, IERC20Metadata } from "@thirdweb-dev/contracts-js";
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "../constants/contract-features";
|
||||
import { ContractWrapper } from "../core/classes/contract-wrapper";
|
||||
import { DetectableFeature } from "../core/interfaces/DetectableFeature";
|
||||
import { decode } from "../lib/cbor-decode.js";
|
||||
import {
|
||||
AbiEvent,
|
||||
AbiFunction,
|
||||
@@ -23,7 +24,6 @@ import {
|
||||
import { ExtensionNotImplementedError } from "./error";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import bs58 from "bs58";
|
||||
import { decode } from "cbor-x";
|
||||
import { BaseContract, BigNumber, ethers } from "ethers";
|
||||
import { z } from "zod";
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import IThirdwebContractABI from "@thirdweb-dev/contracts-js/dist/abis/IThirdwebContract.json";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
export async function getPrebuiltInfo(
|
||||
address: string,
|
||||
provider: ethers.providers.Provider,
|
||||
): Promise<{ type: string; version: number } | undefined> {
|
||||
try {
|
||||
const contract = new ethers.Contract(
|
||||
address,
|
||||
IThirdwebContractABI,
|
||||
provider,
|
||||
);
|
||||
const [type, version] = await Promise.all([
|
||||
ethers.utils
|
||||
.toUtf8String(await contract.contractType()) // eslint-disable-next-line no-control-regex
|
||||
.replace(/\x00/g, ""),
|
||||
await contract.contractVersion(),
|
||||
]);
|
||||
return {
|
||||
type,
|
||||
version,
|
||||
};
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { MAX_BPS } from "../../core/schema/shared";
|
||||
import {
|
||||
InterfaceId_IERC1155,
|
||||
InterfaceId_IERC721,
|
||||
} from "../constants/contract";
|
||||
import { SignerOrProvider } from "../core";
|
||||
import { ContractWrapper } from "../core/classes/contract-wrapper";
|
||||
import { MAX_BPS } from "../schema/shared";
|
||||
import {
|
||||
NewAuctionListing,
|
||||
NewDirectListing,
|
||||
|
||||
@@ -67,9 +67,9 @@ export async function fetchTokenMetadata(
|
||||
}
|
||||
|
||||
return CommonNFTOutput.parse({
|
||||
...jsonMetadata,
|
||||
id: BigNumber.from(tokenId).toString(),
|
||||
uri: tokenUri,
|
||||
...jsonMetadata,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ import {
|
||||
ShardedSnapshot,
|
||||
SnapshotEntry,
|
||||
SnapshotEntryWithProof,
|
||||
SnapshotEntryWithProofSchema,
|
||||
SnapshotInputSchema,
|
||||
} from "../schema";
|
||||
} from "../schema/contracts/common/snapshots";
|
||||
import { SnapshotInput } from "../types";
|
||||
import { hashLeafNode } from "./snapshots";
|
||||
import { convertQuantityToBigNumber } from "./claim-conditions";
|
||||
import { fetchCurrencyMetadata } from "./currency";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { utils } from "ethers";
|
||||
import { ethers, utils } from "ethers";
|
||||
import { MerkleTree } from "merkletreejs";
|
||||
|
||||
// shard using the first 2 hex character of the address
|
||||
@@ -17,6 +19,11 @@ import { MerkleTree } from "merkletreejs";
|
||||
// shard files will be 00.json, 01.json, 02.json, ..., ff.json
|
||||
const SHARD_NYBBLES = 2;
|
||||
|
||||
export enum SnapshotFormatVersion {
|
||||
V1 = 1, // address and maxClaimable
|
||||
V2 = 2, // address, maxClaimable, price, currencyAddress
|
||||
}
|
||||
|
||||
export class ShardedMerkleTree {
|
||||
private shardNybbles: number;
|
||||
private shards: Record<string, ShardData>;
|
||||
@@ -73,17 +80,64 @@ export class ShardedMerkleTree {
|
||||
);
|
||||
}
|
||||
|
||||
static hashEntry(entry: SnapshotEntry, tokenDecimals: number): string {
|
||||
return hashLeafNode(
|
||||
entry.address,
|
||||
utils.parseUnits(entry.maxClaimable, tokenDecimals),
|
||||
);
|
||||
static hashEntry(
|
||||
entry: SnapshotEntry,
|
||||
tokenDecimals: number,
|
||||
currencyDecimals: number,
|
||||
snapshotFormatVersion: SnapshotFormatVersion,
|
||||
): string {
|
||||
switch (snapshotFormatVersion) {
|
||||
case SnapshotFormatVersion.V1:
|
||||
return utils.solidityKeccak256(
|
||||
["address", "uint256"],
|
||||
[
|
||||
entry.address,
|
||||
convertQuantityToBigNumber(entry.maxClaimable, tokenDecimals),
|
||||
],
|
||||
);
|
||||
case SnapshotFormatVersion.V2:
|
||||
return utils.solidityKeccak256(
|
||||
["address", "uint256", "uint256", "address"],
|
||||
[
|
||||
entry.address,
|
||||
convertQuantityToBigNumber(entry.maxClaimable, tokenDecimals),
|
||||
convertQuantityToBigNumber(
|
||||
entry.price || "unlimited",
|
||||
currencyDecimals,
|
||||
),
|
||||
entry.currencyAddress || ethers.constants.AddressZero,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static async fetchAndCacheDecimals(
|
||||
cache: Record<string, number>,
|
||||
provider: ethers.providers.Provider,
|
||||
currencyAddress?: string,
|
||||
): Promise<number> {
|
||||
if (!currencyAddress) {
|
||||
return 18;
|
||||
}
|
||||
// cache decimals for each currency to avoid refetching for every address
|
||||
let currencyDecimals = cache[currencyAddress];
|
||||
if (currencyDecimals === undefined) {
|
||||
const currencyMetadata = await fetchCurrencyMetadata(
|
||||
provider,
|
||||
currencyAddress,
|
||||
);
|
||||
currencyDecimals = currencyMetadata.decimals;
|
||||
cache[currencyAddress] = currencyDecimals;
|
||||
}
|
||||
return currencyDecimals;
|
||||
}
|
||||
|
||||
static async buildAndUpload(
|
||||
snapshotInput: SnapshotInput,
|
||||
tokenDecimals: number,
|
||||
provider: ethers.providers.Provider,
|
||||
storage: ThirdwebStorage,
|
||||
snapshotFormatVersion: SnapshotFormatVersion,
|
||||
shardNybbles = SHARD_NYBBLES,
|
||||
): Promise<ShardedSnapshot> {
|
||||
const inputs = SnapshotInputSchema.parse(snapshotInput);
|
||||
@@ -98,13 +152,28 @@ export class ShardedMerkleTree {
|
||||
}
|
||||
shards[shard].push(snapshotEntry);
|
||||
}
|
||||
const currencyDecimalMap: Record<string, number> = {};
|
||||
// create shard => subtree root map
|
||||
const roots = Object.fromEntries(
|
||||
Object.entries(shards).map(([shard, entries]) => [
|
||||
const subTrees = await Promise.all(
|
||||
Object.entries(shards).map(async ([shard, entries]) => [
|
||||
shard,
|
||||
new MerkleTree(
|
||||
entries.map((entry) =>
|
||||
ShardedMerkleTree.hashEntry(entry, tokenDecimals),
|
||||
await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
// cache decimals for each currency to avoid refetching for every address
|
||||
const currencyDecimals =
|
||||
await ShardedMerkleTree.fetchAndCacheDecimals(
|
||||
currencyDecimalMap,
|
||||
provider,
|
||||
entry.currencyAddress,
|
||||
);
|
||||
return ShardedMerkleTree.hashEntry(
|
||||
entry,
|
||||
tokenDecimals,
|
||||
currencyDecimals,
|
||||
snapshotFormatVersion,
|
||||
);
|
||||
}),
|
||||
),
|
||||
utils.keccak256,
|
||||
{
|
||||
@@ -113,6 +182,7 @@ export class ShardedMerkleTree {
|
||||
).getHexRoot(),
|
||||
]),
|
||||
);
|
||||
const roots = Object.fromEntries(subTrees);
|
||||
// create master tree from shard => subtree root map
|
||||
const tree = new MerkleTree(Object.values(roots), utils.keccak256, {
|
||||
sort: true,
|
||||
@@ -155,22 +225,38 @@ export class ShardedMerkleTree {
|
||||
|
||||
public async getProof(
|
||||
address: string,
|
||||
provider: ethers.providers.Provider,
|
||||
snapshotFormatVersion: SnapshotFormatVersion,
|
||||
): Promise<SnapshotEntryWithProof | null> {
|
||||
const shardId = address.slice(2, 2 + this.shardNybbles).toLowerCase();
|
||||
let shard = this.shards[shardId];
|
||||
const currencyDecimalMap: Record<string, number> = {};
|
||||
if (shard === undefined) {
|
||||
try {
|
||||
shard = this.shards[shardId] =
|
||||
await this.storage.downloadJSON<ShardData>(
|
||||
`${this.baseUri}/${shardId}.json`,
|
||||
);
|
||||
this.trees[shardId] = new MerkleTree(
|
||||
shard.entries.map((entry) =>
|
||||
ShardedMerkleTree.hashEntry(entry, this.tokenDecimals),
|
||||
),
|
||||
utils.keccak256,
|
||||
{ sort: true },
|
||||
const hashedEntries = await Promise.all(
|
||||
shard.entries.map(async (entry) => {
|
||||
// cache decimals for each currency to avoid refetching for every address
|
||||
const currencyDecimals =
|
||||
await ShardedMerkleTree.fetchAndCacheDecimals(
|
||||
currencyDecimalMap,
|
||||
provider,
|
||||
entry.currencyAddress,
|
||||
);
|
||||
return ShardedMerkleTree.hashEntry(
|
||||
entry,
|
||||
this.tokenDecimals,
|
||||
currencyDecimals,
|
||||
snapshotFormatVersion,
|
||||
);
|
||||
}),
|
||||
);
|
||||
this.trees[shardId] = new MerkleTree(hashedEntries, utils.keccak256, {
|
||||
sort: true,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn("No merkle entry found for address", address);
|
||||
return null;
|
||||
@@ -182,35 +268,34 @@ export class ShardedMerkleTree {
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
const leaf = ShardedMerkleTree.hashEntry(entry, this.tokenDecimals);
|
||||
const currencyDecimals = await ShardedMerkleTree.fetchAndCacheDecimals(
|
||||
currencyDecimalMap,
|
||||
provider,
|
||||
entry.currencyAddress,
|
||||
);
|
||||
const leaf = ShardedMerkleTree.hashEntry(
|
||||
entry,
|
||||
this.tokenDecimals,
|
||||
currencyDecimals,
|
||||
snapshotFormatVersion,
|
||||
);
|
||||
const proof = this.trees[shardId]
|
||||
.getProof(leaf)
|
||||
.map((i) => "0x" + i.data.toString("hex"));
|
||||
return {
|
||||
address,
|
||||
return SnapshotEntryWithProofSchema.parse({
|
||||
...entry,
|
||||
proof: proof.concat(shard.proofs),
|
||||
maxClaimable: entry.maxClaimable,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public async getAllEntries(): Promise<SnapshotEntry[]> {
|
||||
try {
|
||||
const entries = await this.storage.downloadJSON<SnapshotEntry[]>(
|
||||
return await this.storage.downloadJSON<SnapshotEntry[]>(
|
||||
this.originalEntriesUri,
|
||||
);
|
||||
return entries;
|
||||
} catch (e) {
|
||||
console.warn("Could not fetch original snapshot entries", e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private computeAllShardIds(shardNybbles: number) {
|
||||
const max = parseInt(`0x${"f".repeat(shardNybbles)}`, 16);
|
||||
const allShards = [];
|
||||
for (let i = 0; i <= max; i++) {
|
||||
allShards.push(i.toString(16).padStart(shardNybbles, "0"));
|
||||
}
|
||||
return allShards;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,32 @@
|
||||
import { SnapshotInputSchema } from "../schema/contracts/common/snapshots";
|
||||
import { SnapshotInputSchema } from "../schema";
|
||||
import {
|
||||
SnapshotInfo,
|
||||
SnapshotInput,
|
||||
} from "../types/claim-conditions/claim-conditions";
|
||||
import { DuplicateLeafsError } from "./error";
|
||||
import { ShardedMerkleTree } from "./sharded-merkle-tree";
|
||||
import {
|
||||
ShardedMerkleTree,
|
||||
SnapshotFormatVersion,
|
||||
} from "./sharded-merkle-tree";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish, utils } from "ethers";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
/**
|
||||
* Create a snapshot (merkle tree) from a list of addresses and uploads it to IPFS
|
||||
* @param snapshotInput - the list of addresses to hash
|
||||
* @param tokenDecimals - the token decimals
|
||||
* @param provider
|
||||
* @param storage - the storage to upload to
|
||||
* @param snapshotFormatVersion
|
||||
* @returns the generated snapshot and URI
|
||||
* @internal
|
||||
*/
|
||||
export async function createSnapshot(
|
||||
snapshotInput: SnapshotInput,
|
||||
tokenDecimals: number,
|
||||
provider: ethers.providers.Provider,
|
||||
storage: ThirdwebStorage,
|
||||
snapshotFormatVersion: SnapshotFormatVersion,
|
||||
): Promise<SnapshotInfo> {
|
||||
const input = SnapshotInputSchema.parse(snapshotInput);
|
||||
const addresses = input.map((i) => i.address);
|
||||
@@ -30,26 +37,12 @@ export async function createSnapshot(
|
||||
const tree = await ShardedMerkleTree.buildAndUpload(
|
||||
input,
|
||||
tokenDecimals,
|
||||
provider,
|
||||
storage,
|
||||
snapshotFormatVersion,
|
||||
);
|
||||
return {
|
||||
merkleRoot: tree.shardedMerkleInfo.merkleRoot,
|
||||
snapshotUri: tree.uri,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash an address and the corresponding claimable amount
|
||||
* @internal
|
||||
* @param address - the address
|
||||
* @param maxClaimableAmount - the amount
|
||||
*/
|
||||
export function hashLeafNode(
|
||||
address: string,
|
||||
maxClaimableAmount: BigNumberish,
|
||||
): string {
|
||||
return utils.solidityKeccak256(
|
||||
["address", "uint256"],
|
||||
[address, BigNumber.from(maxClaimableAmount)],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ import {
|
||||
FEATURE_NFT_REVEALABLE,
|
||||
FEATURE_NFT_SIGNATURE_MINTABLE,
|
||||
FEATURE_NFT_SUPPLY,
|
||||
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS,
|
||||
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V1,
|
||||
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2,
|
||||
FEATURE_NFT_TIERED_DROP,
|
||||
} from "./erc721-features";
|
||||
import {
|
||||
FEATURE_EDITION,
|
||||
@@ -59,10 +61,12 @@ export type Feature =
|
||||
| typeof FEATURE_NFT_BATCH_MINTABLE
|
||||
| typeof FEATURE_NFT_LAZY_MINTABLE
|
||||
| typeof FEATURE_NFT_CLAIMABLE
|
||||
| typeof FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS
|
||||
| typeof FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V1
|
||||
| typeof FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2
|
||||
| typeof FEATURE_NFT_REVEALABLE
|
||||
| typeof FEATURE_NFT_SIGNATURE_MINTABLE
|
||||
| typeof FEATURE_NFT_BURNABLE
|
||||
| typeof FEATURE_NFT_TIERED_DROP
|
||||
| typeof FEATURE_EDITION
|
||||
| typeof FEATURE_EDITION_ENUMERABLE
|
||||
| typeof FEATURE_EDITION_MINTABLE
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import DelayedRevealAbi from "@thirdweb-dev/contracts-js/dist/abis/DelayedReveal.json";
|
||||
import DropSinglePhase1155 from "@thirdweb-dev/contracts-js/dist/abis/DropSinglePhase1155.json";
|
||||
import IBurnableERC1155Abi from "@thirdweb-dev/contracts-js/dist/abis/IBurnableERC1155.json";
|
||||
import IClaimableERC1155 from "@thirdweb-dev/contracts-js/dist/abis/IClaimableERC1155.json";
|
||||
import IDropSinglePhase1155 from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase1155.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";
|
||||
@@ -27,7 +27,7 @@ export const FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS = {
|
||||
sdk: "sdk.erc1155claimable",
|
||||
contracts: "DropSinglePhase1155",
|
||||
},
|
||||
abis: [Erc1155Abi, ILazyMintAbi, DropSinglePhase1155],
|
||||
abis: [Erc1155Abi, ILazyMintAbi, IDropSinglePhase1155],
|
||||
features: {},
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -2,12 +2,14 @@ import IBurnableERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/IBurnableER
|
||||
import IClaimableERC721 from "@thirdweb-dev/contracts-js/dist/abis/IClaimableERC721.json";
|
||||
import DelayedRevealAbi from "@thirdweb-dev/contracts-js/dist/abis/IDelayedReveal.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";
|
||||
import Erc721EnumerableAbi from "@thirdweb-dev/contracts-js/dist/abis/IERC721Enumerable.json";
|
||||
import Erc721SupplyAbi from "@thirdweb-dev/contracts-js/dist/abis/IERC721Supply.json";
|
||||
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 TieredDropAbi from "@thirdweb-dev/contracts-js/dist/abis/LazyMintWithTier.json";
|
||||
import SignatureMintERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/SignatureMintERC721.json";
|
||||
|
||||
export const FEATURE_NFT_BURNABLE = {
|
||||
@@ -25,15 +27,26 @@ export const FEATURE_NFT_REVEALABLE = {
|
||||
name: "ERC721Revealable",
|
||||
namespace: "nft.drop.revealer",
|
||||
docLinks: {
|
||||
sdk: "sdk.drop.delayedreveal",
|
||||
sdk: "sdk.delayedreveal",
|
||||
contracts: "DelayedReveal",
|
||||
},
|
||||
abis: [Erc721Abi, ILazyMintAbi, DelayedRevealAbi],
|
||||
features: {},
|
||||
} as const;
|
||||
|
||||
export const FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS = {
|
||||
name: "ERC721ClaimableWithConditions",
|
||||
export const FEATURE_NFT_TIERED_DROP = {
|
||||
name: "ERC721TieredDrop",
|
||||
namespace: "nft.tieredDrop",
|
||||
docLinks: {
|
||||
sdk: "sdk.erc721tiereddrop",
|
||||
contracts: "TieredDrop",
|
||||
},
|
||||
abis: [Erc721Abi, TieredDropAbi],
|
||||
features: {},
|
||||
} as const;
|
||||
|
||||
export const FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2 = {
|
||||
name: "ERC721ClaimableWithConditionsV2",
|
||||
namespace: "nft.drop.claim",
|
||||
docLinks: {
|
||||
sdk: "sdk.erc721claimable",
|
||||
@@ -43,6 +56,17 @@ export const FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS = {
|
||||
features: {},
|
||||
} as const;
|
||||
|
||||
export const FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V1 = {
|
||||
name: "ERC721ClaimableWithConditionsV1",
|
||||
namespace: "nft.drop.claim",
|
||||
docLinks: {
|
||||
sdk: "sdk.erc721claimable",
|
||||
contracts: "DropSinglePhase_V1",
|
||||
},
|
||||
abis: [Erc721Abi, ILazyMintAbi, IDropSinglePhaseV1],
|
||||
features: {},
|
||||
} as const;
|
||||
|
||||
export const FEATURE_NFT_CLAIMABLE = {
|
||||
name: "ERC721Claimable",
|
||||
namespace: "nft.drop.claim",
|
||||
@@ -65,8 +89,10 @@ export const FEATURE_NFT_LAZY_MINTABLE = {
|
||||
features: {
|
||||
[FEATURE_NFT_REVEALABLE.name]: FEATURE_NFT_REVEALABLE,
|
||||
[FEATURE_NFT_CLAIMABLE.name]: FEATURE_NFT_CLAIMABLE,
|
||||
[FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS.name]:
|
||||
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS,
|
||||
[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,
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -143,5 +169,6 @@ export const FEATURE_NFT = {
|
||||
[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_TIERED_DROP.name]: FEATURE_NFT_TIERED_DROP,
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { getPrebuiltInfo } from "../common/legacy";
|
||||
import { ALL_ROLES } from "../common/role";
|
||||
import { getSignerAndProvider } from "../core/classes/rpc-connection-handler";
|
||||
import type { ContractType, NetworkOrSignerOrProvider } from "../core/types";
|
||||
import type {
|
||||
ContractType,
|
||||
NetworkOrSignerOrProvider,
|
||||
PrebuiltContractType,
|
||||
} from "../core/types";
|
||||
import {
|
||||
DropErc1155ContractSchema,
|
||||
DropErc721ContractSchema,
|
||||
@@ -17,6 +22,7 @@ import { CustomContractSchema } from "../schema/contracts/custom";
|
||||
import { DropErc20ContractSchema } from "../schema/contracts/drop-erc20";
|
||||
import { MultiwrapContractSchema } from "../schema/contracts/multiwrap";
|
||||
import type { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
type InitalizeParams = [
|
||||
network: NetworkOrSignerOrProvider,
|
||||
@@ -34,8 +40,12 @@ 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([
|
||||
EditionDropInitializer.getAbi(),
|
||||
await EditionDropInitializer.getAbi(address, provider),
|
||||
import("./prebuilt-implementations/edition-drop"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
@@ -49,9 +59,21 @@ export const EditionDropInitializer = {
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async () =>
|
||||
(await import("@thirdweb-dev/contracts-js/dist/abis/DropERC1155.json"))
|
||||
.default,
|
||||
getAbi: async (address: string, provider: ethers.providers.Provider) => {
|
||||
const contractInfo = await assertContractType(
|
||||
address,
|
||||
provider,
|
||||
"edition-drop",
|
||||
);
|
||||
return contractInfo.version > 2
|
||||
? (await import("@thirdweb-dev/contracts-js/dist/abis/DropERC1155.json"))
|
||||
.default
|
||||
: (
|
||||
await import(
|
||||
"@thirdweb-dev/contracts-js/dist/abis/DropERC1155_V2.json"
|
||||
)
|
||||
).default;
|
||||
},
|
||||
};
|
||||
|
||||
export const EditionInitializer = {
|
||||
@@ -64,7 +86,7 @@ export const EditionInitializer = {
|
||||
) => {
|
||||
const [, provider] = getSignerAndProvider(network, options);
|
||||
const [abi, contract, _network] = await Promise.all([
|
||||
EditionInitializer.getAbi(),
|
||||
EditionInitializer.getAbi(address, provider),
|
||||
import("./prebuilt-implementations/edition"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
@@ -78,9 +100,12 @@ export const EditionInitializer = {
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async () =>
|
||||
(await import("@thirdweb-dev/contracts-js/dist/abis/TokenERC1155.json"))
|
||||
.default,
|
||||
getAbi: async (address: string, provider: ethers.providers.Provider) => {
|
||||
await assertContractType(address, provider, "edition");
|
||||
return (
|
||||
await import("@thirdweb-dev/contracts-js/dist/abis/TokenERC1155.json")
|
||||
).default;
|
||||
},
|
||||
};
|
||||
|
||||
export const MarketplaceInitializer = {
|
||||
@@ -93,7 +118,7 @@ export const MarketplaceInitializer = {
|
||||
) => {
|
||||
const [, provider] = getSignerAndProvider(network, options);
|
||||
const [abi, contract, _network] = await Promise.all([
|
||||
MarketplaceInitializer.getAbi(),
|
||||
MarketplaceInitializer.getAbi(address, provider),
|
||||
import("./prebuilt-implementations/marketplace"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
@@ -107,9 +132,12 @@ export const MarketplaceInitializer = {
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async () =>
|
||||
(await import("@thirdweb-dev/contracts-js/dist/abis/Marketplace.json"))
|
||||
.default,
|
||||
getAbi: async (address: string, provider: ethers.providers.Provider) => {
|
||||
await assertContractType(address, provider, "marketplace");
|
||||
return (
|
||||
await import("@thirdweb-dev/contracts-js/dist/abis/Marketplace.json")
|
||||
).default;
|
||||
},
|
||||
};
|
||||
|
||||
export const MultiwrapInitializer = {
|
||||
@@ -122,7 +150,7 @@ export const MultiwrapInitializer = {
|
||||
) => {
|
||||
const [, provider] = getSignerAndProvider(network, options);
|
||||
const [abi, contract, _network] = await Promise.all([
|
||||
MultiwrapInitializer.getAbi(),
|
||||
MultiwrapInitializer.getAbi(address, provider),
|
||||
import("./prebuilt-implementations/multiwrap"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
@@ -136,9 +164,11 @@ export const MultiwrapInitializer = {
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async () =>
|
||||
(await import("@thirdweb-dev/contracts-js/dist/abis/Multiwrap.json"))
|
||||
.default,
|
||||
getAbi: async (address: string, provider: ethers.providers.Provider) => {
|
||||
await assertContractType(address, provider, "multiwrap");
|
||||
return (await import("@thirdweb-dev/contracts-js/dist/abis/Multiwrap.json"))
|
||||
.default;
|
||||
},
|
||||
};
|
||||
|
||||
export const NFTCollectionInitializer = {
|
||||
@@ -152,7 +182,7 @@ export const NFTCollectionInitializer = {
|
||||
) => {
|
||||
const [, provider] = getSignerAndProvider(network, options);
|
||||
const [abi, contract, _network] = await Promise.all([
|
||||
NFTCollectionInitializer.getAbi(),
|
||||
NFTCollectionInitializer.getAbi(address, provider),
|
||||
import("./prebuilt-implementations/nft-collection"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
@@ -166,9 +196,12 @@ export const NFTCollectionInitializer = {
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async () =>
|
||||
(await import("@thirdweb-dev/contracts-js/dist/abis/TokenERC721.json"))
|
||||
.default,
|
||||
getAbi: async (address: string, provider: ethers.providers.Provider) => {
|
||||
await assertContractType(address, provider, "nft-collection");
|
||||
return (
|
||||
await import("@thirdweb-dev/contracts-js/dist/abis/TokenERC721.json")
|
||||
).default;
|
||||
},
|
||||
};
|
||||
|
||||
export const NFTDropInitializer = {
|
||||
@@ -180,8 +213,12 @@ 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(),
|
||||
NFTDropInitializer.getAbi(address, provider),
|
||||
import("./prebuilt-implementations/nft-drop"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
@@ -195,9 +232,21 @@ export const NFTDropInitializer = {
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async () =>
|
||||
(await import("@thirdweb-dev/contracts-js/dist/abis/DropERC721.json"))
|
||||
.default,
|
||||
getAbi: async (address: string, provider: ethers.providers.Provider) => {
|
||||
const contractInfo = await assertContractType(
|
||||
address,
|
||||
provider,
|
||||
"nft-drop",
|
||||
);
|
||||
return contractInfo.version > 3
|
||||
? (await import("@thirdweb-dev/contracts-js/dist/abis/DropERC721.json"))
|
||||
.default
|
||||
: (
|
||||
await import(
|
||||
"@thirdweb-dev/contracts-js/dist/abis/DropERC721_V3.json"
|
||||
)
|
||||
).default;
|
||||
},
|
||||
};
|
||||
|
||||
export const PackInitializer = {
|
||||
@@ -211,7 +260,7 @@ export const PackInitializer = {
|
||||
) => {
|
||||
const [, provider] = getSignerAndProvider(network, options);
|
||||
const [abi, contract, _network] = await Promise.all([
|
||||
PackInitializer.getAbi(),
|
||||
PackInitializer.getAbi(address, provider),
|
||||
import("./prebuilt-implementations/pack"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
@@ -225,8 +274,11 @@ export const PackInitializer = {
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async () =>
|
||||
(await import("@thirdweb-dev/contracts-js/dist/abis/Pack.json")).default,
|
||||
getAbi: async (address: string, provider: ethers.providers.Provider) => {
|
||||
await assertContractType(address, provider, "pack");
|
||||
return (await import("@thirdweb-dev/contracts-js/dist/abis/Pack.json"))
|
||||
.default;
|
||||
},
|
||||
};
|
||||
|
||||
export const SignatureDropInitializer = {
|
||||
@@ -240,7 +292,7 @@ export const SignatureDropInitializer = {
|
||||
) => {
|
||||
const [, provider] = getSignerAndProvider(network, options);
|
||||
const [abi, contract, _network] = await Promise.all([
|
||||
SignatureDropInitializer.getAbi(),
|
||||
SignatureDropInitializer.getAbi(address, provider),
|
||||
import("./prebuilt-implementations/signature-drop"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
@@ -254,9 +306,24 @@ export const SignatureDropInitializer = {
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async () =>
|
||||
(await import("@thirdweb-dev/contracts-js/dist/abis/SignatureDrop.json"))
|
||||
.default,
|
||||
getAbi: async (address: string, provider: ethers.providers.Provider) => {
|
||||
const contractInfo = await assertContractType(
|
||||
address,
|
||||
provider,
|
||||
"signature-drop",
|
||||
);
|
||||
return contractInfo.version > 4
|
||||
? (
|
||||
await import(
|
||||
"@thirdweb-dev/contracts-js/dist/abis/SignatureDrop.json"
|
||||
)
|
||||
).default
|
||||
: (
|
||||
await import(
|
||||
"@thirdweb-dev/contracts-js/dist/abis/SignatureDrop_V4.json"
|
||||
)
|
||||
).default;
|
||||
},
|
||||
};
|
||||
|
||||
export const SplitInitializer = {
|
||||
@@ -270,7 +337,7 @@ export const SplitInitializer = {
|
||||
) => {
|
||||
const [, provider] = getSignerAndProvider(network, options);
|
||||
const [abi, contract, _network] = await Promise.all([
|
||||
SplitInitializer.getAbi(),
|
||||
SplitInitializer.getAbi(address, provider),
|
||||
import("./prebuilt-implementations/split"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
@@ -284,8 +351,11 @@ export const SplitInitializer = {
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async () =>
|
||||
(await import("@thirdweb-dev/contracts-js/dist/abis/Split.json")).default,
|
||||
getAbi: async (address: string, provider: ethers.providers.Provider) => {
|
||||
await assertContractType(address, provider, "split");
|
||||
return (await import("@thirdweb-dev/contracts-js/dist/abis/Split.json"))
|
||||
.default;
|
||||
},
|
||||
};
|
||||
|
||||
export const TokenDropInitializer = {
|
||||
@@ -298,8 +368,12 @@ 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(),
|
||||
TokenDropInitializer.getAbi(address, provider),
|
||||
import("./prebuilt-implementations/token-drop"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
@@ -313,9 +387,18 @@ export const TokenDropInitializer = {
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async () =>
|
||||
(await import("@thirdweb-dev/contracts-js/dist/abis/DropERC20.json"))
|
||||
.default,
|
||||
getAbi: async (address: string, provider: ethers.providers.Provider) => {
|
||||
const contractInfo = await assertContractType(
|
||||
address,
|
||||
provider,
|
||||
"token-drop",
|
||||
);
|
||||
return contractInfo.version > 2
|
||||
? (await import("@thirdweb-dev/contracts-js/dist/abis/DropERC20.json"))
|
||||
.default
|
||||
: (await import("@thirdweb-dev/contracts-js/dist/abis/DropERC20_V2.json"))
|
||||
.default;
|
||||
},
|
||||
};
|
||||
|
||||
export const TokenInitializer = {
|
||||
@@ -328,7 +411,7 @@ export const TokenInitializer = {
|
||||
) => {
|
||||
const [, provider] = getSignerAndProvider(network, options);
|
||||
const [abi, contract, _network] = await Promise.all([
|
||||
TokenInitializer.getAbi(),
|
||||
TokenInitializer.getAbi(address, provider),
|
||||
import("./prebuilt-implementations/token"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
@@ -342,9 +425,12 @@ export const TokenInitializer = {
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async () =>
|
||||
(await import("@thirdweb-dev/contracts-js/dist/abis/TokenERC20.json"))
|
||||
.default,
|
||||
getAbi: async (address: string, provider: ethers.providers.Provider) => {
|
||||
await assertContractType(address, provider, "token");
|
||||
return (
|
||||
await import("@thirdweb-dev/contracts-js/dist/abis/TokenERC20.json")
|
||||
).default;
|
||||
},
|
||||
};
|
||||
|
||||
export const VoteInitializer = {
|
||||
@@ -358,7 +444,7 @@ export const VoteInitializer = {
|
||||
) => {
|
||||
const [, provider] = getSignerAndProvider(network, options);
|
||||
const [abi, contract, _network] = await Promise.all([
|
||||
VoteInitializer.getAbi(),
|
||||
VoteInitializer.getAbi(address, provider),
|
||||
import("./prebuilt-implementations/vote"),
|
||||
provider.getNetwork(),
|
||||
]);
|
||||
@@ -372,11 +458,25 @@ export const VoteInitializer = {
|
||||
_network.chainId,
|
||||
);
|
||||
},
|
||||
getAbi: async () =>
|
||||
(await import("@thirdweb-dev/contracts-js/dist/abis/VoteERC20.json"))
|
||||
.default,
|
||||
getAbi: async (address: string, provider: ethers.providers.Provider) => {
|
||||
await assertContractType(address, provider, "vote");
|
||||
return (await import("@thirdweb-dev/contracts-js/dist/abis/VoteERC20.json"))
|
||||
.default;
|
||||
},
|
||||
};
|
||||
|
||||
async function assertContractType(
|
||||
address: string,
|
||||
provider: ethers.providers.Provider,
|
||||
type: PrebuiltContractType,
|
||||
) {
|
||||
const contractInfo = await getPrebuiltInfo(address, provider);
|
||||
if (!contractInfo || contractInfo.type !== getContractName(type)) {
|
||||
throw new Error(`Contract is not a ${type}`);
|
||||
}
|
||||
return contractInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* a map from contractType -> contract metadata
|
||||
* @internal
|
||||
@@ -417,3 +517,11 @@ export function getContractTypeForRemoteName(name: string): ContractType {
|
||||
?.contractType || "custom"
|
||||
);
|
||||
}
|
||||
|
||||
export function getContractName(
|
||||
type: PrebuiltContractType,
|
||||
): string | undefined {
|
||||
return Object.values(CONTRACTS_MAP).find(
|
||||
(contract) => contract.contractType === type,
|
||||
)?.name;
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ import {
|
||||
TransactionResult,
|
||||
TransactionResultWithId,
|
||||
} from "../../core/types";
|
||||
import { PaperCheckout } from "../../integrations/paper-xyz";
|
||||
import { PaperCheckout } from "../../integrations/thirdweb-checkout";
|
||||
import { DropErc1155ContractSchema } from "../../schema/contracts/drop-erc1155";
|
||||
import { SDKOptions } from "../../schema/sdk-options";
|
||||
import type { DropERC1155 } from "@thirdweb-dev/contracts-js";
|
||||
import { PrebuiltEditionDrop } from "../../types/eips";
|
||||
import type ABI from "@thirdweb-dev/contracts-js/dist/abis/DropERC1155.json";
|
||||
import { ThirdwebStorage, UploadProgressEvent } from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish, CallOverrides, constants } from "ethers";
|
||||
@@ -39,26 +39,26 @@ import { BigNumber, BigNumberish, CallOverrides, constants } from "ethers";
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = sdk.getContract("{{contract_address}}", "edition-drop");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "edition-drop");
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class EditionDrop extends StandardErc1155<DropERC1155> {
|
||||
export class EditionDrop extends StandardErc1155<PrebuiltEditionDrop> {
|
||||
private static contractRoles = ["admin", "minter", "transfer"] as const;
|
||||
|
||||
public abi: typeof ABI;
|
||||
public sales: ContractPrimarySale<DropERC1155>;
|
||||
public platformFees: ContractPlatformFee<DropERC1155>;
|
||||
public encoder: ContractEncoder<DropERC1155>;
|
||||
public estimator: GasCostEstimator<DropERC1155>;
|
||||
public events: ContractEvents<DropERC1155>;
|
||||
public sales: ContractPrimarySale<PrebuiltEditionDrop>;
|
||||
public platformFees: ContractPlatformFee<PrebuiltEditionDrop>;
|
||||
public encoder: ContractEncoder<PrebuiltEditionDrop>;
|
||||
public estimator: GasCostEstimator<PrebuiltEditionDrop>;
|
||||
public events: ContractEvents<PrebuiltEditionDrop>;
|
||||
public metadata: ContractMetadata<
|
||||
DropERC1155,
|
||||
PrebuiltEditionDrop,
|
||||
typeof DropErc1155ContractSchema
|
||||
>;
|
||||
public roles: ContractRoles<
|
||||
DropERC1155,
|
||||
PrebuiltEditionDrop,
|
||||
typeof EditionDrop.contractRoles[number]
|
||||
>;
|
||||
/**
|
||||
@@ -79,7 +79,7 @@ export class EditionDrop extends StandardErc1155<DropERC1155> {
|
||||
* ```
|
||||
*/
|
||||
public royalties: ContractRoyalty<
|
||||
DropERC1155,
|
||||
PrebuiltEditionDrop,
|
||||
typeof DropErc1155ContractSchema
|
||||
>;
|
||||
/**
|
||||
@@ -106,18 +106,18 @@ export class EditionDrop extends StandardErc1155<DropERC1155> {
|
||||
* await contract.claimConditions.set(tokenId, claimConditions);
|
||||
* ```
|
||||
*/
|
||||
public claimConditions: DropErc1155ClaimConditions<DropERC1155>;
|
||||
public claimConditions: DropErc1155ClaimConditions<PrebuiltEditionDrop>;
|
||||
|
||||
/**
|
||||
* Checkout
|
||||
* @remarks Create a FIAT currency checkout for your NFT drop.
|
||||
*/
|
||||
public checkout: PaperCheckout<DropERC1155>;
|
||||
public checkout: PaperCheckout<PrebuiltEditionDrop>;
|
||||
|
||||
public history: DropErc1155History;
|
||||
public interceptor: ContractInterceptor<DropERC1155>;
|
||||
public erc1155: Erc1155<DropERC1155>;
|
||||
public owner: ContractOwner<DropERC1155>;
|
||||
public interceptor: ContractInterceptor<PrebuiltEditionDrop>;
|
||||
public erc1155: Erc1155<PrebuiltEditionDrop>;
|
||||
public owner: ContractOwner<PrebuiltEditionDrop>;
|
||||
|
||||
constructor(
|
||||
network: NetworkOrSignerOrProvider,
|
||||
@@ -126,7 +126,7 @@ export class EditionDrop extends StandardErc1155<DropERC1155> {
|
||||
options: SDKOptions = {},
|
||||
abi: typeof ABI,
|
||||
chainId: number,
|
||||
contractWrapper = new ContractWrapper<DropERC1155>(
|
||||
contractWrapper = new ContractWrapper<PrebuiltEditionDrop>(
|
||||
network,
|
||||
address,
|
||||
abi,
|
||||
|
||||
@@ -37,7 +37,7 @@ import { BigNumber, BigNumberish, CallOverrides, constants } from "ethers";
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = sdk.getContract("{{contract_address}}", "edition");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "edition");
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
|
||||
@@ -3,10 +3,7 @@ import { ListingNotFoundError } from "../../common";
|
||||
import { isNativeToken } from "../../common/currency";
|
||||
import { mapOffer } from "../../common/marketplace";
|
||||
import { getRoleHash } from "../../common/role";
|
||||
import {
|
||||
NATIVE_TOKENS,
|
||||
SUPPORTED_CHAIN_ID,
|
||||
} from "../../constants";
|
||||
import { NATIVE_TOKENS, SUPPORTED_CHAIN_ID } from "../../constants";
|
||||
import { ContractEncoder } from "../../core/classes/contract-encoder";
|
||||
import { ContractEvents } from "../../core/classes/contract-events";
|
||||
import { ContractInterceptor } from "../../core/classes/contract-interceptor";
|
||||
@@ -42,7 +39,7 @@ import invariant from "tiny-invariant";
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = sdk.getContract("{{contract_address}}", "marketplace");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "marketplace");
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
|
||||
@@ -47,7 +47,7 @@ import { BigNumberish, CallOverrides, ethers } from "ethers";
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = sdk.getContract("{{contract_address}}", "multiwrap");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "multiwrap");
|
||||
* ```
|
||||
*
|
||||
* @beta
|
||||
|
||||
@@ -35,7 +35,7 @@ import { BigNumberish, CallOverrides, constants } from "ethers";
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = sdk.getContract("{{contract_address}}", "nft-collection");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "nft-collection");
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
} from "../../../core/schema/QueryParams";
|
||||
import { NFT, NFTMetadata, NFTMetadataOrUri } from "../../../core/schema/nft";
|
||||
import { getRoleHash } from "../../common";
|
||||
import { uploadOrExtractURIs } from "../../common/nft";
|
||||
import { FEATURE_NFT_REVEALABLE } from "../../constants/erc721-features";
|
||||
import { TransactionTask } from "../../core/classes/TransactionTask";
|
||||
import { ContractEncoder } from "../../core/classes/contract-encoder";
|
||||
@@ -27,21 +26,14 @@ import {
|
||||
TransactionResult,
|
||||
TransactionResultWithId,
|
||||
} from "../../core/types";
|
||||
import { PaperCheckout } from "../../integrations/paper-xyz";
|
||||
import { PaperCheckout } from "../../integrations/thirdweb-checkout";
|
||||
import { DropErc721ContractSchema } from "../../schema/contracts/drop-erc721";
|
||||
import { SDKOptions } from "../../schema/sdk-options";
|
||||
import { PrebuiltNFTDrop } from "../../types/eips";
|
||||
import { UploadProgressEvent } from "../../types/events";
|
||||
import type { DropERC721 } from "@thirdweb-dev/contracts-js";
|
||||
import type ABI from "@thirdweb-dev/contracts-js/dist/abis/DropERC721.json";
|
||||
import { TokensLazyMintedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/DropERC721";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import {
|
||||
BigNumber,
|
||||
BigNumberish,
|
||||
CallOverrides,
|
||||
constants,
|
||||
ethers,
|
||||
} from "ethers";
|
||||
import { BigNumber, BigNumberish, CallOverrides, constants } from "ethers";
|
||||
|
||||
/**
|
||||
* Setup a collection of one-of-one NFTs that are minted as users claim them.
|
||||
@@ -52,29 +44,32 @@ import {
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = sdk.getContract("{{contract_address}}", "nft-drop");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "nft-drop");
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class NFTDrop extends StandardErc721<DropERC721> {
|
||||
export class NFTDrop extends StandardErc721<PrebuiltNFTDrop> {
|
||||
static contractRoles = ["admin", "minter", "transfer"] as const;
|
||||
|
||||
public abi: typeof ABI;
|
||||
public encoder: ContractEncoder<DropERC721>;
|
||||
public estimator: GasCostEstimator<DropERC721>;
|
||||
public encoder: ContractEncoder<PrebuiltNFTDrop>;
|
||||
public estimator: GasCostEstimator<PrebuiltNFTDrop>;
|
||||
public metadata: ContractMetadata<
|
||||
DropERC721,
|
||||
PrebuiltNFTDrop,
|
||||
typeof DropErc721ContractSchema
|
||||
>;
|
||||
public sales: ContractPrimarySale<DropERC721>;
|
||||
public platformFees: ContractPlatformFee<DropERC721>;
|
||||
public events: ContractEvents<DropERC721>;
|
||||
public roles: ContractRoles<DropERC721, typeof NFTDrop.contractRoles[number]>;
|
||||
public sales: ContractPrimarySale<PrebuiltNFTDrop>;
|
||||
public platformFees: ContractPlatformFee<PrebuiltNFTDrop>;
|
||||
public events: ContractEvents<PrebuiltNFTDrop>;
|
||||
public roles: ContractRoles<
|
||||
PrebuiltNFTDrop,
|
||||
typeof NFTDrop.contractRoles[number]
|
||||
>;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public interceptor: ContractInterceptor<DropERC721>;
|
||||
public interceptor: ContractInterceptor<PrebuiltNFTDrop>;
|
||||
/**
|
||||
* Configure royalties
|
||||
* @remarks Set your own royalties for the entire contract or per token
|
||||
@@ -93,7 +88,7 @@ export class NFTDrop extends StandardErc721<DropERC721> {
|
||||
* ```
|
||||
*/
|
||||
public royalties: ContractRoyalty<
|
||||
DropERC721,
|
||||
PrebuiltNFTDrop,
|
||||
typeof DropErc721ContractSchema
|
||||
>;
|
||||
/**
|
||||
@@ -118,7 +113,7 @@ export class NFTDrop extends StandardErc721<DropERC721> {
|
||||
* await contract.claimConditions.set(claimConditions);
|
||||
* ```
|
||||
*/
|
||||
public claimConditions: DropClaimConditions<DropERC721>;
|
||||
public claimConditions: DropClaimConditions<PrebuiltNFTDrop>;
|
||||
/**
|
||||
* Delayed reveal
|
||||
* @remarks Create a batch of encrypted NFTs that can be revealed at a later time.
|
||||
@@ -150,16 +145,16 @@ export class NFTDrop extends StandardErc721<DropERC721> {
|
||||
* await contract.revealer.reveal(batchId, "my secret password");
|
||||
* ```
|
||||
*/
|
||||
public revealer: DelayedReveal<DropERC721>;
|
||||
public revealer: DelayedReveal<PrebuiltNFTDrop>;
|
||||
|
||||
/**
|
||||
* Checkout
|
||||
* @remarks Create a FIAT currency checkout for your NFT drop.
|
||||
*/
|
||||
public checkout: PaperCheckout<DropERC721>;
|
||||
public checkout: PaperCheckout<PrebuiltNFTDrop>;
|
||||
|
||||
public erc721: Erc721<DropERC721>;
|
||||
public owner: ContractOwner<DropERC721>;
|
||||
public erc721: Erc721<PrebuiltNFTDrop>;
|
||||
public owner: ContractOwner<PrebuiltNFTDrop>;
|
||||
|
||||
constructor(
|
||||
network: NetworkOrSignerOrProvider,
|
||||
@@ -168,7 +163,7 @@ export class NFTDrop extends StandardErc721<DropERC721> {
|
||||
options: SDKOptions = {},
|
||||
abi: typeof ABI,
|
||||
chainId: number,
|
||||
contractWrapper = new ContractWrapper<DropERC721>(
|
||||
contractWrapper = new ContractWrapper<PrebuiltNFTDrop>(
|
||||
network,
|
||||
address,
|
||||
abi,
|
||||
@@ -195,7 +190,7 @@ export class NFTDrop extends StandardErc721<DropERC721> {
|
||||
this.events = new ContractEvents(this.contractWrapper);
|
||||
this.platformFees = new ContractPlatformFee(this.contractWrapper);
|
||||
this.erc721 = new Erc721(this.contractWrapper, this.storage, chainId);
|
||||
this.revealer = new DelayedReveal<DropERC721>(
|
||||
this.revealer = new DelayedReveal(
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
FEATURE_NFT_REVEALABLE.name,
|
||||
@@ -383,44 +378,7 @@ export class NFTDrop extends StandardErc721<DropERC721> {
|
||||
onProgress: (event: UploadProgressEvent) => void;
|
||||
},
|
||||
): Promise<TransactionResultWithId<NFTMetadata>[]> {
|
||||
const startFileNumber =
|
||||
await this.contractWrapper.readContract.nextTokenIdToMint();
|
||||
const batch = await uploadOrExtractURIs(
|
||||
metadatas,
|
||||
this.storage,
|
||||
startFileNumber.toNumber(),
|
||||
options,
|
||||
);
|
||||
// ensure baseUri is the same for the entire batch
|
||||
const baseUri = batch[0].substring(0, batch[0].lastIndexOf("/"));
|
||||
for (let i = 0; i < batch.length; i++) {
|
||||
const uri = batch[i].substring(0, batch[i].lastIndexOf("/"));
|
||||
if (baseUri !== uri) {
|
||||
throw new Error(
|
||||
`Can only create batches with the same base URI for every entry in the batch. Expected '${baseUri}' but got '${uri}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const receipt = await this.contractWrapper.sendTransaction("lazyMint", [
|
||||
batch.length,
|
||||
baseUri.endsWith("/") ? baseUri : `${baseUri}/`,
|
||||
ethers.utils.toUtf8Bytes(""),
|
||||
]);
|
||||
const event = this.contractWrapper.parseLogs<TokensLazyMintedEvent>(
|
||||
"TokensLazyMinted",
|
||||
receipt?.logs,
|
||||
);
|
||||
const startingIndex = event[0].args.startTokenId;
|
||||
const endingIndex = event[0].args.endTokenId;
|
||||
const results: TransactionResultWithId<NFTMetadata>[] = [];
|
||||
for (let id = startingIndex; id.lte(endingIndex); id = id.add(1)) {
|
||||
results.push({
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc721.getTokenMetadata(id),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
return this.erc721.lazyMint(metadatas, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,9 +409,9 @@ export class NFTDrop extends StandardErc721<DropERC721> {
|
||||
* const quantity = 1; // how many unique NFTs you want to claim
|
||||
*
|
||||
* const tx = await contract.claimTo(address, quantity);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* const claimedTokenId = tx.id; // the id of the NFT claimed
|
||||
* const claimedNFT = await tx.data(); // (optional) get the claimed NFT metadata
|
||||
* const receipt = tx[0].receipt; // the transaction receipt
|
||||
* const claimedTokenId = tx[0].id; // the id of the NFT claimed
|
||||
* const claimedNFT = await tx[0].data(); // (optional) get the claimed NFT metadata
|
||||
* ```
|
||||
*
|
||||
* @param destinationAddress - Address you want to send the token to
|
||||
|
||||
@@ -52,7 +52,7 @@ import { BigNumber, BigNumberish, CallOverrides, ethers } from "ethers";
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = sdk.getContract("{{contract_address}}", "pack");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "pack");
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
TransactionResult,
|
||||
TransactionResultWithId,
|
||||
} from "../../core/types";
|
||||
import { PaperCheckout } from "../../integrations/paper-xyz";
|
||||
import { PaperCheckout } from "../../integrations/thirdweb-checkout";
|
||||
import { DropErc721ContractSchema } from "../../schema/contracts/drop-erc721";
|
||||
import { SDKOptions } from "../../schema/sdk-options";
|
||||
import { ClaimOptions, UploadProgressEvent } from "../../types";
|
||||
@@ -47,7 +47,7 @@ import { BigNumber, BigNumberish, CallOverrides, constants } from "ethers";
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = sdk.getContract("{{contract_address}}", "signature-drop");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "signature-drop");
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
|
||||
@@ -30,7 +30,7 @@ import { BigNumber, CallOverrides, Contract } from "ethers";
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = sdk.getContract("{{contract_address}}", "split");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "split");
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
|
||||
@@ -14,7 +14,7 @@ import { NetworkOrSignerOrProvider, TransactionResult } from "../../core/types";
|
||||
import { DropErc20ContractSchema } from "../../schema/contracts/drop-erc20";
|
||||
import { SDKOptions } from "../../schema/sdk-options";
|
||||
import { Amount, CurrencyValue } from "../../types";
|
||||
import type { DropERC20 } from "@thirdweb-dev/contracts-js";
|
||||
import { PrebuiltTokenDrop } from "../../types/eips";
|
||||
import type ABI from "@thirdweb-dev/contracts-js/dist/abis/DropERC20.json";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { CallOverrides, constants } from "ethers";
|
||||
@@ -28,24 +28,27 @@ import { CallOverrides, constants } from "ethers";
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = sdk.getContract("{{contract_address}}", "token-drop");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "token-drop");
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
export class TokenDrop extends StandardErc20<DropERC20> {
|
||||
export class TokenDrop extends StandardErc20<PrebuiltTokenDrop> {
|
||||
static contractRoles = ["admin", "transfer"] as const;
|
||||
|
||||
public abi: typeof ABI;
|
||||
public metadata: ContractMetadata<DropERC20, typeof DropErc20ContractSchema>;
|
||||
public metadata: ContractMetadata<
|
||||
PrebuiltTokenDrop,
|
||||
typeof DropErc20ContractSchema
|
||||
>;
|
||||
public roles: ContractRoles<
|
||||
DropERC20,
|
||||
PrebuiltTokenDrop,
|
||||
typeof TokenDrop.contractRoles[number]
|
||||
>;
|
||||
public encoder: ContractEncoder<DropERC20>;
|
||||
public estimator: GasCostEstimator<DropERC20>;
|
||||
public sales: ContractPrimarySale<DropERC20>;
|
||||
public platformFees: ContractPlatformFee<DropERC20>;
|
||||
public events: ContractEvents<DropERC20>;
|
||||
public encoder: ContractEncoder<PrebuiltTokenDrop>;
|
||||
public estimator: GasCostEstimator<PrebuiltTokenDrop>;
|
||||
public sales: ContractPrimarySale<PrebuiltTokenDrop>;
|
||||
public platformFees: ContractPlatformFee<PrebuiltTokenDrop>;
|
||||
public events: ContractEvents<PrebuiltTokenDrop>;
|
||||
/**
|
||||
* Configure claim conditions
|
||||
* @remarks Define who can claim Tokens, when and how many.
|
||||
@@ -68,11 +71,11 @@ export class TokenDrop extends StandardErc20<DropERC20> {
|
||||
* await contract.claimConditions.set(claimConditions);
|
||||
* ```
|
||||
*/
|
||||
public claimConditions: DropClaimConditions<DropERC20>;
|
||||
public claimConditions: DropClaimConditions<PrebuiltTokenDrop>;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public interceptor: ContractInterceptor<DropERC20>;
|
||||
public interceptor: ContractInterceptor<PrebuiltTokenDrop>;
|
||||
|
||||
constructor(
|
||||
network: NetworkOrSignerOrProvider,
|
||||
@@ -81,7 +84,7 @@ export class TokenDrop extends StandardErc20<DropERC20> {
|
||||
options: SDKOptions = {},
|
||||
abi: typeof ABI,
|
||||
chainId: number,
|
||||
contractWrapper = new ContractWrapper<DropERC20>(
|
||||
contractWrapper = new ContractWrapper<PrebuiltTokenDrop>(
|
||||
network,
|
||||
address,
|
||||
abi,
|
||||
@@ -105,7 +108,7 @@ export class TokenDrop extends StandardErc20<DropERC20> {
|
||||
this.sales = new ContractPrimarySale(this.contractWrapper);
|
||||
this.platformFees = new ContractPlatformFee(this.contractWrapper);
|
||||
this.interceptor = new ContractInterceptor(this.contractWrapper);
|
||||
this.claimConditions = new DropClaimConditions<DropERC20>(
|
||||
this.claimConditions = new DropClaimConditions<PrebuiltTokenDrop>(
|
||||
this.contractWrapper,
|
||||
this.metadata,
|
||||
this.storage,
|
||||
@@ -210,7 +213,9 @@ export class TokenDrop extends StandardErc20<DropERC20> {
|
||||
amount: Amount,
|
||||
checkERC20Allowance = true,
|
||||
): Promise<TransactionResult> {
|
||||
return this.erc20.claimTo(destinationAddress, amount, checkERC20Allowance);
|
||||
return this.erc20.claimTo(destinationAddress, amount, {
|
||||
checkERC20Allowance,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,7 @@ import { CallOverrides, constants } from "ethers";
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = sdk.getContract("{{contract_address}}", "token");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "token");
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
|
||||
@@ -46,7 +46,7 @@ import {
|
||||
* import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
*
|
||||
* const sdk = new ThirdwebSDK("{{chainName}}");
|
||||
* const contract = sdk.getContract("{{contract_address}}", "vote");
|
||||
* const contract = await sdk.getContract("{{contract_address}}", "vote");
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
@@ -319,7 +319,7 @@ export class Vote implements UpdateableNetwork {
|
||||
this.contractWrapper.readContract.votingDelay(),
|
||||
this.contractWrapper.readContract.votingPeriod(),
|
||||
this.contractWrapper.readContract.token(),
|
||||
this.contractWrapper.readContract.quorumNumerator(),
|
||||
this.contractWrapper.readContract["quorumNumerator()"](),
|
||||
this.contractWrapper.readContract.proposalThreshold(),
|
||||
]);
|
||||
const votingTokenMetadata = await fetchCurrencyMetadata(
|
||||
|
||||
@@ -387,6 +387,7 @@ export class ContractDeployer extends RPCConnectionHandler {
|
||||
* @internal
|
||||
* @param contractType - the type of contract to deploy
|
||||
* @param contractMetadata - the metadata to deploy the contract with
|
||||
* @param version
|
||||
* @returns a promise of the address of the newly deployed contract
|
||||
*/
|
||||
public async deployBuiltInContract<
|
||||
@@ -396,13 +397,25 @@ export class ContractDeployer extends RPCConnectionHandler {
|
||||
contractMetadata: z.input<
|
||||
DeploySchemaForPrebuiltContractType<TContractType>
|
||||
>,
|
||||
version?: number,
|
||||
): Promise<string> {
|
||||
const parsed =
|
||||
PREBUILT_CONTRACTS_MAP[contractType].schema.deploy.parse(
|
||||
contractMetadata,
|
||||
);
|
||||
const factory = await this.getFactory();
|
||||
return await factory.deploy(contractType, parsed);
|
||||
return await factory.deploy(contractType, parsed, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param contractType
|
||||
*/
|
||||
public async getLatestBuiltInContractVersion<
|
||||
TContractType extends PrebuiltContractType,
|
||||
>(contractType: TContractType) {
|
||||
const factory = await this.getFactory();
|
||||
return await factory.getLatestVersion(contractType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EventType } from "../../constants";
|
||||
import { ContractEvent, EventQueryFilter } from "../../types/index";
|
||||
import { ContractEvent, EventQueryOptions } from "../../types/index";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { EventFragment } from "@ethersproject/abi";
|
||||
import { BaseContract, Event, providers } from "ethers";
|
||||
@@ -198,7 +198,7 @@ export class ContractEvents<TContract extends BaseContract> {
|
||||
* @returns The event objects of the events emitted with event names and data for each event
|
||||
*/
|
||||
public async getAllEvents<TEvent extends Record<string, any>>(
|
||||
filters: EventQueryFilter = {
|
||||
filters: Omit<EventQueryOptions, "filters"> = {
|
||||
fromBlock: 0,
|
||||
toBlock: "latest",
|
||||
order: "desc",
|
||||
@@ -226,23 +226,34 @@ export class ContractEvents<TContract extends BaseContract> {
|
||||
* ```javascript
|
||||
* // The name of the event to get logs for
|
||||
* const eventName = "Transfer";
|
||||
* // Optionally pass in filters to limit the blocks from which events are retrieved
|
||||
* const filters = {
|
||||
*
|
||||
* // Optionally pass in options to limit the blocks from which events are retrieved
|
||||
* const options = {
|
||||
* fromBlock: 0,
|
||||
* toBlock: 1000000,
|
||||
* }
|
||||
* const events = await contract.events.getEvents(eventName, filters);
|
||||
* toBlock: 1000000, // can also pass "latest"
|
||||
* order: "desc",
|
||||
* // Configure event filters (filter on indexed event parameters)
|
||||
* filters: {
|
||||
* from: "0x...",
|
||||
* to: "0x..."
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* const events = await contract.events.getEvents(eventName, options);
|
||||
* console.log(events[0].eventName);
|
||||
* console.log(events[0].data);
|
||||
* ```
|
||||
*
|
||||
* @param eventName - The name of the event to get logs for
|
||||
* @param filters - Specify the from and to block numbers to get events for, defaults to all blocks. @see EventQueryFilter
|
||||
* @param options - Specify the from and to block numbers to get events for, defaults to all blocks. @see EventQueryOptions
|
||||
* @returns The requested event objects with event data
|
||||
*/
|
||||
public async getEvents<TEvent extends Record<string, any>>(
|
||||
public async getEvents<
|
||||
TEvent extends Record<string, any> = Record<string, any>,
|
||||
TFilter extends Record<string, any> = Record<string, any>,
|
||||
>(
|
||||
eventName: string,
|
||||
filters: EventQueryFilter = {
|
||||
options: EventQueryOptions<TFilter> = {
|
||||
fromBlock: 0,
|
||||
toBlock: "latest",
|
||||
order: "desc",
|
||||
@@ -251,24 +262,32 @@ export class ContractEvents<TContract extends BaseContract> {
|
||||
const event = this.contractWrapper.readContract.interface.getEvent(
|
||||
eventName as string,
|
||||
);
|
||||
const filter = this.contractWrapper.readContract.filters[event.name];
|
||||
|
||||
const eventInterface =
|
||||
this.contractWrapper.readContract.interface.getEvent(eventName);
|
||||
const args = options.filters
|
||||
? eventInterface.inputs.map((e) => (options.filters as TFilter)[e.name])
|
||||
: [];
|
||||
const filter = this.contractWrapper.readContract.filters[event.name](
|
||||
...args,
|
||||
);
|
||||
|
||||
const events = await this.contractWrapper.readContract.queryFilter(
|
||||
filter(),
|
||||
filters.fromBlock,
|
||||
filters.toBlock,
|
||||
filter,
|
||||
options.fromBlock,
|
||||
options.toBlock,
|
||||
);
|
||||
|
||||
const orderedEvents = events.sort((a, b) => {
|
||||
return filters.order === "desc"
|
||||
return options.order === "desc"
|
||||
? b.blockNumber - a.blockNumber
|
||||
: a.blockNumber - b.blockNumber;
|
||||
});
|
||||
|
||||
return this.parseEvents(orderedEvents);
|
||||
return this.parseEvents<TEvent>(orderedEvents);
|
||||
}
|
||||
|
||||
private parseEvents<TEvent extends Record<string, any>>(
|
||||
private parseEvents<TEvent = Record<string, any>>(
|
||||
events: Event[],
|
||||
): ContractEvent<TEvent>[] {
|
||||
return events.map((e) => {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { UploadProgressEvent } from "../../types/events";
|
||||
import { TransactionResult, TransactionResultWithId } from "../index";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import type {
|
||||
DropERC721,
|
||||
DropERC721_V3,
|
||||
IThirdwebContract,
|
||||
SignatureDrop,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
@@ -33,7 +33,7 @@ import { BigNumber, BigNumberish, ethers } from "ethers";
|
||||
*/
|
||||
export class DelayedReveal<
|
||||
T extends
|
||||
| DropERC721
|
||||
| DropERC721_V3
|
||||
| BaseDelayedRevealERC721
|
||||
| SignatureDrop
|
||||
| BaseDelayedRevealERC1155,
|
||||
@@ -246,7 +246,9 @@ export class DelayedReveal<
|
||||
return this.contractWrapper.readContract.getBatchIdAtIndex(i);
|
||||
}
|
||||
|
||||
if (hasFunction<DropERC721>("baseURIIndices", this.contractWrapper)) {
|
||||
if (
|
||||
hasFunction<DropERC721_V3>("baseURIIndices", this.contractWrapper)
|
||||
) {
|
||||
return this.contractWrapper.readContract.baseURIIndices(i);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { AmountSchema } from "../../../core/schema/shared";
|
||||
import { includesErrorMessage } from "../../common";
|
||||
import {
|
||||
abstractContractModelToLegacy,
|
||||
abstractContractModelToNew,
|
||||
convertQuantityToBigNumber,
|
||||
fetchSnapshotEntryForAddress,
|
||||
getClaimerProofs,
|
||||
legacyContractModelToAbstract,
|
||||
newContractModelToAbstract,
|
||||
prepareClaim,
|
||||
processClaimConditionInputs,
|
||||
transformResultToClaimCondition,
|
||||
@@ -12,32 +18,46 @@ import {
|
||||
detectContractFeature,
|
||||
hasFunction,
|
||||
} from "../../common/feature-detection";
|
||||
import { SnapshotFormatVersion } from "../../common/sharded-merkle-tree";
|
||||
import { isNode } from "../../common/utils";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../constants/index";
|
||||
import { ClaimEligibility } from "../../enums";
|
||||
import { AmountSchema, SnapshotEntryWithProof } from "../../schema";
|
||||
import {
|
||||
AbstractClaimConditionContractStruct,
|
||||
SnapshotEntryWithProof,
|
||||
} from "../../schema";
|
||||
import {
|
||||
Amount,
|
||||
ClaimCondition,
|
||||
ClaimConditionFetchOptions,
|
||||
ClaimConditionInput,
|
||||
ClaimOptions,
|
||||
ClaimVerification,
|
||||
} from "../../types";
|
||||
import { BaseClaimConditionERC721, BaseDropERC20 } from "../../types/eips";
|
||||
import {
|
||||
BaseClaimConditionERC721,
|
||||
BaseDropERC20,
|
||||
PrebuiltNFTDrop,
|
||||
PrebuiltTokenDrop,
|
||||
} from "../../types/eips";
|
||||
import { TransactionResult } from "../types";
|
||||
import { TransactionTask } from "./TransactionTask";
|
||||
import { ContractMetadata } from "./contract-metadata";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import type {
|
||||
ContractMetadata as ContractMetadataContract,
|
||||
DropERC20,
|
||||
DropERC721,
|
||||
Drop,
|
||||
DropERC20_V2,
|
||||
DropERC721_V3,
|
||||
DropSinglePhase,
|
||||
DropSinglePhase_V1,
|
||||
IDropSinglePhase_V1,
|
||||
IERC20,
|
||||
IERC20Metadata,
|
||||
SignatureDrop,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import ERC20Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC20.json";
|
||||
import { IDropClaimCondition } from "@thirdweb-dev/contracts-js/dist/declarations/src/DropERC20";
|
||||
import type { IDropClaimCondition_V2 } from "@thirdweb-dev/contracts-js/dist/declarations/src/IDropERC20_V2";
|
||||
import type { IDropSinglePhase } from "@thirdweb-dev/contracts-js/src/DropSinglePhase";
|
||||
import type { IClaimCondition } from "@thirdweb-dev/contracts-js/src/IDrop";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish, constants, ethers, utils } from "ethers";
|
||||
import deepEqual from "fast-deep-equal";
|
||||
@@ -48,11 +68,10 @@ import deepEqual from "fast-deep-equal";
|
||||
*/
|
||||
export class DropClaimConditions<
|
||||
TContract extends
|
||||
| DropERC721
|
||||
| DropERC20
|
||||
| PrebuiltNFTDrop
|
||||
| PrebuiltTokenDrop
|
||||
| BaseClaimConditionERC721
|
||||
| BaseDropERC20
|
||||
| SignatureDrop,
|
||||
| BaseDropERC20,
|
||||
> {
|
||||
private contractWrapper;
|
||||
private metadata;
|
||||
@@ -92,13 +111,37 @@ export class DropClaimConditions<
|
||||
);
|
||||
}
|
||||
|
||||
private async get(): Promise<IDropClaimCondition.ClaimConditionStructOutput> {
|
||||
if (this.isSinglePhaseDropContract(this.contractWrapper)) {
|
||||
return (await this.contractWrapper.readContract.claimCondition()) as IDropClaimCondition.ClaimConditionStructOutput;
|
||||
} else if (this.isMultiPhaseDropContract(this.contractWrapper)) {
|
||||
private async get(
|
||||
conditionId?: BigNumberish,
|
||||
): Promise<AbstractClaimConditionContractStruct> {
|
||||
if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
|
||||
const contractModel =
|
||||
(await this.contractWrapper.readContract.claimCondition()) as IDropClaimCondition_V2.ClaimConditionStructOutput;
|
||||
return legacyContractModelToAbstract(contractModel);
|
||||
} else if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
|
||||
const id =
|
||||
await this.contractWrapper.readContract.getActiveClaimConditionId();
|
||||
return await this.contractWrapper.readContract.getClaimConditionById(id);
|
||||
conditionId !== undefined
|
||||
? conditionId
|
||||
: await this.contractWrapper.readContract.getActiveClaimConditionId();
|
||||
const contractModel =
|
||||
(await this.contractWrapper.readContract.getClaimConditionById(
|
||||
id,
|
||||
)) as IDropClaimCondition_V2.ClaimConditionStructOutput;
|
||||
return legacyContractModelToAbstract(contractModel);
|
||||
} else if (this.isNewSinglePhaseDrop(this.contractWrapper)) {
|
||||
const contractModel =
|
||||
(await this.contractWrapper.readContract.claimCondition()) as IClaimCondition.ClaimConditionStructOutput;
|
||||
return newContractModelToAbstract(contractModel);
|
||||
} else if (this.isNewMultiphaseDrop(this.contractWrapper)) {
|
||||
const id =
|
||||
conditionId !== undefined
|
||||
? conditionId
|
||||
: await this.contractWrapper.readContract.getActiveClaimConditionId();
|
||||
const contractModel =
|
||||
(await this.contractWrapper.readContract.getClaimConditionById(
|
||||
id,
|
||||
)) as IClaimCondition.ClaimConditionStruct;
|
||||
return newContractModelToAbstract(contractModel);
|
||||
} else {
|
||||
throw new Error("Contract does not support claim conditions");
|
||||
}
|
||||
@@ -112,7 +155,10 @@ export class DropClaimConditions<
|
||||
public async getAll(
|
||||
options?: ClaimConditionFetchOptions,
|
||||
): Promise<ClaimCondition[]> {
|
||||
if (this.isMultiPhaseDropContract(this.contractWrapper)) {
|
||||
if (
|
||||
this.isLegacyMultiPhaseDrop(this.contractWrapper) ||
|
||||
this.isNewMultiphaseDrop(this.contractWrapper)
|
||||
) {
|
||||
const claimCondition =
|
||||
(await this.contractWrapper.readContract.claimCondition()) as {
|
||||
currentStartId: BigNumber;
|
||||
@@ -120,11 +166,9 @@ export class DropClaimConditions<
|
||||
};
|
||||
const startId = claimCondition.currentStartId.toNumber();
|
||||
const count = claimCondition.count.toNumber();
|
||||
const conditions: IDropClaimCondition.ClaimConditionStructOutput[] = [];
|
||||
const conditions: AbstractClaimConditionContractStruct[] = [];
|
||||
for (let i = startId; i < startId + count; i++) {
|
||||
conditions.push(
|
||||
await this.contractWrapper.readContract.getClaimConditionById(i),
|
||||
);
|
||||
conditions.push(await this.get(i));
|
||||
}
|
||||
const metadata = await this.metadata.get();
|
||||
const decimals = await this.getTokenDecimals();
|
||||
@@ -234,82 +278,181 @@ export class DropClaimConditions<
|
||||
const merkleRootArray = ethers.utils.stripZeros(
|
||||
claimCondition.merkleRootHash,
|
||||
);
|
||||
if (merkleRootArray.length > 0) {
|
||||
const hasAllowList = merkleRootArray.length > 0;
|
||||
let allowListEntry;
|
||||
if (hasAllowList) {
|
||||
const merkleLower = claimCondition.merkleRootHash.toString();
|
||||
const metadata = await this.metadata.get();
|
||||
const proofs = await getClaimerProofs(
|
||||
allowListEntry = await getClaimerProofs(
|
||||
addressToCheck,
|
||||
merkleLower,
|
||||
await this.getTokenDecimals(),
|
||||
metadata.merkle,
|
||||
this.storage,
|
||||
this.contractWrapper.getProvider(),
|
||||
this.getSnapshotFormatVersion(),
|
||||
);
|
||||
|
||||
try {
|
||||
let validMerkleProof;
|
||||
if (this.isMultiPhaseDropContract(this.contractWrapper)) {
|
||||
activeConditionIndex =
|
||||
await this.contractWrapper.readContract.getActiveClaimConditionId();
|
||||
// legacy verifyClaimerMerkleProofs function
|
||||
[validMerkleProof] =
|
||||
await this.contractWrapper.readContract.verifyClaimMerkleProof(
|
||||
activeConditionIndex,
|
||||
addressToCheck,
|
||||
quantity,
|
||||
proofs.proof,
|
||||
proofs.maxClaimable,
|
||||
);
|
||||
} else if (this.isSinglePhaseDropContract(this.contractWrapper)) {
|
||||
[validMerkleProof] =
|
||||
await this.contractWrapper.readContract.verifyClaimMerkleProof(
|
||||
addressToCheck,
|
||||
quantity,
|
||||
{
|
||||
proof: proofs.proof,
|
||||
maxQuantityInAllowlist: proofs.maxClaimable,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!validMerkleProof) {
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
} catch (e) {
|
||||
if (
|
||||
!allowListEntry &&
|
||||
(this.isLegacySinglePhaseDrop(this.contractWrapper) ||
|
||||
this.isLegacyMultiPhaseDrop(this.contractWrapper))
|
||||
) {
|
||||
// exclusive allowlist behavior
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
|
||||
if (allowListEntry) {
|
||||
try {
|
||||
let validMerkleProof;
|
||||
if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
|
||||
activeConditionIndex =
|
||||
await this.contractWrapper.readContract.getActiveClaimConditionId();
|
||||
// legacy verifyClaimerMerkleProofs function
|
||||
[validMerkleProof] =
|
||||
await this.contractWrapper.readContract.verifyClaimMerkleProof(
|
||||
activeConditionIndex,
|
||||
addressToCheck,
|
||||
quantity,
|
||||
allowListEntry.proof,
|
||||
allowListEntry.maxClaimable,
|
||||
);
|
||||
if (!validMerkleProof) {
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
} else if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
|
||||
[validMerkleProof] =
|
||||
await this.contractWrapper.readContract.verifyClaimMerkleProof(
|
||||
addressToCheck,
|
||||
quantity,
|
||||
{
|
||||
proof: allowListEntry.proof,
|
||||
maxQuantityInAllowlist: allowListEntry.maxClaimable,
|
||||
},
|
||||
);
|
||||
if (!validMerkleProof) {
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
} else if (this.isNewSinglePhaseDrop(this.contractWrapper)) {
|
||||
await this.contractWrapper.readContract.verifyClaim(
|
||||
addressToCheck,
|
||||
quantity,
|
||||
claimCondition.currencyAddress,
|
||||
claimCondition.price,
|
||||
{
|
||||
proof: allowListEntry.proof,
|
||||
quantityLimitPerWallet: allowListEntry.maxClaimable,
|
||||
currency: allowListEntry.currencyAddress,
|
||||
pricePerToken: allowListEntry.price,
|
||||
} 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)
|
||||
) {
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
} else if (this.isNewMultiphaseDrop(this.contractWrapper)) {
|
||||
activeConditionIndex =
|
||||
await this.contractWrapper.readContract.getActiveClaimConditionId();
|
||||
await this.contractWrapper.readContract.verifyClaim(
|
||||
activeConditionIndex,
|
||||
addressToCheck,
|
||||
quantity,
|
||||
claimCondition.currencyAddress,
|
||||
claimCondition.price,
|
||||
{
|
||||
proof: allowListEntry.proof,
|
||||
quantityLimitPerWallet: allowListEntry.maxClaimable,
|
||||
currency: allowListEntry.currencyAddress,
|
||||
pricePerToken: allowListEntry.price,
|
||||
} as IDropSinglePhase.AllowlistProofStruct,
|
||||
);
|
||||
if (
|
||||
(convertQuantityToBigNumber(
|
||||
claimCondition.maxClaimablePerWallet,
|
||||
decimals,
|
||||
).eq(0) &&
|
||||
allowListEntry.maxClaimable === ethers.constants.MaxUint256) ||
|
||||
allowListEntry.maxClaimable === BigNumber.from(0)
|
||||
) {
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn(
|
||||
"Merkle proof verification failed:",
|
||||
"reason" in e ? e.reason : e,
|
||||
);
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for claim timestamp between claims
|
||||
let [lastClaimedTimestamp, timestampForNextClaim] = [
|
||||
BigNumber.from(0),
|
||||
BigNumber.from(0),
|
||||
];
|
||||
if (this.isMultiPhaseDropContract(this.contractWrapper)) {
|
||||
activeConditionIndex =
|
||||
await this.contractWrapper.readContract.getActiveClaimConditionId();
|
||||
[lastClaimedTimestamp, timestampForNextClaim] =
|
||||
await this.contractWrapper.readContract.getClaimTimestamp(
|
||||
activeConditionIndex,
|
||||
addressToCheck,
|
||||
);
|
||||
} else if (this.isSinglePhaseDropContract(this.contractWrapper)) {
|
||||
// check for claim timestamp between claims
|
||||
[lastClaimedTimestamp, timestampForNextClaim] =
|
||||
await this.contractWrapper.readContract.getClaimTimestamp(
|
||||
addressToCheck,
|
||||
);
|
||||
// TODO (cc) check for max claimable per wallet and how much each wallet has claimed
|
||||
if (
|
||||
this.isNewSinglePhaseDrop(this.contractWrapper) ||
|
||||
this.isNewMultiphaseDrop(this.contractWrapper)
|
||||
) {
|
||||
if (!hasAllowList || (hasAllowList && !allowListEntry)) {
|
||||
if (
|
||||
convertQuantityToBigNumber(
|
||||
claimCondition.maxClaimablePerWallet,
|
||||
decimals,
|
||||
).eq(0)
|
||||
) {
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const now = BigNumber.from(Date.now()).div(1000);
|
||||
// check for claim timestamp between claims (ONLY FOR LEGACY)
|
||||
if (
|
||||
this.isLegacySinglePhaseDrop(this.contractWrapper) ||
|
||||
this.isLegacyMultiPhaseDrop(this.contractWrapper)
|
||||
) {
|
||||
let [lastClaimedTimestamp, timestampForNextClaim] = [
|
||||
BigNumber.from(0),
|
||||
BigNumber.from(0),
|
||||
];
|
||||
if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
|
||||
activeConditionIndex =
|
||||
await this.contractWrapper.readContract.getActiveClaimConditionId();
|
||||
[lastClaimedTimestamp, timestampForNextClaim] =
|
||||
await this.contractWrapper.readContract.getClaimTimestamp(
|
||||
activeConditionIndex,
|
||||
addressToCheck,
|
||||
);
|
||||
} else if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
|
||||
// check for claim timestamp between claims
|
||||
[lastClaimedTimestamp, timestampForNextClaim] =
|
||||
await this.contractWrapper.readContract.getClaimTimestamp(
|
||||
addressToCheck,
|
||||
);
|
||||
}
|
||||
|
||||
if (lastClaimedTimestamp.gt(0) && now.lt(timestampForNextClaim)) {
|
||||
// contract will return MaxUint256 if user has already claimed and cannot claim again
|
||||
if (timestampForNextClaim.eq(constants.MaxUint256)) {
|
||||
reasons.push(ClaimEligibility.AlreadyClaimed);
|
||||
} else {
|
||||
reasons.push(ClaimEligibility.WaitBeforeNextClaimTransaction);
|
||||
const now = BigNumber.from(Date.now()).div(1000);
|
||||
if (lastClaimedTimestamp.gt(0) && now.lt(timestampForNextClaim)) {
|
||||
// contract will return MaxUint256 if user has already claimed and cannot claim again
|
||||
if (timestampForNextClaim.eq(constants.MaxUint256)) {
|
||||
reasons.push(ClaimEligibility.AlreadyClaimed);
|
||||
} else {
|
||||
reasons.push(ClaimEligibility.WaitBeforeNextClaimTransaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,23 +485,25 @@ export class DropClaimConditions<
|
||||
|
||||
/**
|
||||
* Returns allow list information and merkle proofs for the given address.
|
||||
* @param claimerAddress
|
||||
* @param claimerAddress - the claimer address
|
||||
* @param claimConditionId - optional the claim condition id to get the proofs for
|
||||
*/
|
||||
public async getClaimerProofs(
|
||||
claimerAddress: string,
|
||||
claimConditionId?: BigNumberish,
|
||||
): Promise<SnapshotEntryWithProof | null> {
|
||||
const claimCondition = await this.getActive();
|
||||
const merkeRoot = claimCondition.merkleRootHash;
|
||||
const merkleRootArray = ethers.utils.stripZeros(
|
||||
claimCondition.merkleRootHash,
|
||||
);
|
||||
const claimCondition = await this.get(claimConditionId);
|
||||
const merkleRoot = claimCondition.merkleRoot;
|
||||
const merkleRootArray = ethers.utils.stripZeros(merkleRoot);
|
||||
if (merkleRootArray.length > 0) {
|
||||
const metadata = await this.metadata.get();
|
||||
return await fetchSnapshotEntryForAddress(
|
||||
claimerAddress,
|
||||
merkeRoot.toString(),
|
||||
merkleRoot.toString(),
|
||||
metadata.merkle,
|
||||
this.contractWrapper.getProvider(),
|
||||
this.storage,
|
||||
this.getSnapshotFormatVersion(),
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
@@ -409,16 +554,19 @@ export class DropClaimConditions<
|
||||
resetClaimEligibilityForAll = false,
|
||||
): Promise<TransactionResult> {
|
||||
let claimConditionsProcessed = claimConditionInputs;
|
||||
if (this.isSinglePhaseDropContract(this.contractWrapper)) {
|
||||
if (
|
||||
this.isLegacySinglePhaseDrop(this.contractWrapper) ||
|
||||
this.isNewSinglePhaseDrop(this.contractWrapper)
|
||||
) {
|
||||
resetClaimEligibilityForAll = true;
|
||||
if (claimConditionInputs.length === 0) {
|
||||
claimConditionsProcessed = [
|
||||
{
|
||||
startTime: new Date(0),
|
||||
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
||||
currencyAddress: ethers.constants.AddressZero,
|
||||
price: 0,
|
||||
maxQuantity: 0,
|
||||
quantityLimitPerTransaction: 0,
|
||||
maxClaimableSupply: 0,
|
||||
maxClaimablePerWallet: 0,
|
||||
waitInSeconds: 0,
|
||||
merkleRootHash: utils.hexZeroPad([0], 32),
|
||||
snapshot: [],
|
||||
@@ -437,6 +585,7 @@ export class DropClaimConditions<
|
||||
await this.getTokenDecimals(),
|
||||
this.contractWrapper.getProvider(),
|
||||
this.storage,
|
||||
this.getSnapshotFormatVersion(),
|
||||
);
|
||||
|
||||
const merkleInfo: { [key: string]: string } = {};
|
||||
@@ -457,6 +606,8 @@ export class DropClaimConditions<
|
||||
mergedMetadata,
|
||||
);
|
||||
|
||||
// TODO (cc) we could write the merkle tree info on the claim condition metadata instead
|
||||
// TODO (cc) but we still need to maintain the behavior here for older contracts
|
||||
if (
|
||||
hasFunction<ContractMetadataContract>(
|
||||
"setContractURI",
|
||||
@@ -477,17 +628,31 @@ export class DropClaimConditions<
|
||||
}
|
||||
|
||||
const cw = this.contractWrapper;
|
||||
if (this.isSinglePhaseDropContract(cw)) {
|
||||
if (this.isLegacySinglePhaseDrop(cw)) {
|
||||
encoded.push(
|
||||
cw.readContract.interface.encodeFunctionData("setClaimConditions", [
|
||||
sortedConditions[0],
|
||||
abstractContractModelToLegacy(sortedConditions[0]),
|
||||
resetClaimEligibilityForAll,
|
||||
]),
|
||||
);
|
||||
} else if (this.isMultiPhaseDropContract(cw)) {
|
||||
} else if (this.isLegacyMultiPhaseDrop(cw)) {
|
||||
encoded.push(
|
||||
cw.readContract.interface.encodeFunctionData("setClaimConditions", [
|
||||
sortedConditions,
|
||||
sortedConditions.map(abstractContractModelToLegacy),
|
||||
resetClaimEligibilityForAll,
|
||||
]),
|
||||
);
|
||||
} else if (this.isNewSinglePhaseDrop(cw)) {
|
||||
encoded.push(
|
||||
cw.readContract.interface.encodeFunctionData("setClaimConditions", [
|
||||
abstractContractModelToNew(sortedConditions[0]),
|
||||
resetClaimEligibilityForAll,
|
||||
]),
|
||||
);
|
||||
} else if (this.isNewMultiphaseDrop(cw)) {
|
||||
encoded.push(
|
||||
cw.readContract.interface.encodeFunctionData("setClaimConditions", [
|
||||
sortedConditions.map(abstractContractModelToNew),
|
||||
resetClaimEligibilityForAll,
|
||||
]),
|
||||
);
|
||||
@@ -550,21 +715,132 @@ export class DropClaimConditions<
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
checkERC20Allowance,
|
||||
this.getSnapshotFormatVersion(),
|
||||
);
|
||||
}
|
||||
|
||||
private isSinglePhaseDropContract(
|
||||
public async getClaimArguments(
|
||||
destinationAddress: string,
|
||||
quantity: BigNumberish,
|
||||
claimVerification: ClaimVerification,
|
||||
): Promise<any[]> {
|
||||
const activeClaimCondition = await this.getActive();
|
||||
if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
|
||||
return [
|
||||
destinationAddress,
|
||||
quantity,
|
||||
activeClaimCondition.currencyAddress,
|
||||
activeClaimCondition.price,
|
||||
claimVerification.proofs,
|
||||
claimVerification.maxClaimable,
|
||||
];
|
||||
} else if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
|
||||
return [
|
||||
destinationAddress,
|
||||
quantity,
|
||||
activeClaimCondition.currencyAddress,
|
||||
activeClaimCondition.price,
|
||||
{
|
||||
proof: claimVerification.proofs,
|
||||
maxQuantityInAllowlist: claimVerification.maxClaimable,
|
||||
} as IDropSinglePhase_V1.AllowlistProofStruct,
|
||||
ethers.utils.toUtf8Bytes(""),
|
||||
];
|
||||
}
|
||||
return [
|
||||
destinationAddress,
|
||||
quantity,
|
||||
activeClaimCondition.currencyAddress,
|
||||
activeClaimCondition.price,
|
||||
{
|
||||
proof: claimVerification.proofs,
|
||||
quantityLimitPerWallet: claimVerification.maxClaimable,
|
||||
pricePerToken: claimVerification.price,
|
||||
currency: claimVerification.currencyAddress,
|
||||
} as IDropSinglePhase.AllowlistProofStruct,
|
||||
ethers.utils.toUtf8Bytes(""),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a claim transaction without executing it.
|
||||
* This is useful for estimating the gas cost of a claim transaction, overriding transaction options and having fine grained control over the transaction execution.
|
||||
* @param destinationAddress
|
||||
* @param quantity
|
||||
* @param options
|
||||
*/
|
||||
public async getClaimTransaction(
|
||||
destinationAddress: string,
|
||||
quantity: BigNumberish,
|
||||
options?: ClaimOptions,
|
||||
): Promise<TransactionTask> {
|
||||
if (options?.pricePerToken) {
|
||||
throw new Error(
|
||||
"Price per token is be set via claim conditions by calling `contract.erc721.claimConditions.set()`",
|
||||
);
|
||||
}
|
||||
const claimVerification = await this.prepareClaim(
|
||||
quantity,
|
||||
options?.checkERC20Allowance === undefined
|
||||
? true
|
||||
: options.checkERC20Allowance,
|
||||
await this.getTokenDecimals(),
|
||||
);
|
||||
|
||||
return TransactionTask.make({
|
||||
contractWrapper: this.contractWrapper,
|
||||
functionName: "claim",
|
||||
args: await this.getClaimArguments(
|
||||
destinationAddress,
|
||||
quantity,
|
||||
claimVerification,
|
||||
),
|
||||
overrides: claimVerification.overrides,
|
||||
});
|
||||
}
|
||||
|
||||
isNewSinglePhaseDrop(
|
||||
contractWrapper: ContractWrapper<any>,
|
||||
): contractWrapper is ContractWrapper<DropSinglePhase> {
|
||||
return !hasFunction<DropSinglePhase>(
|
||||
"getClaimConditionById",
|
||||
contractWrapper,
|
||||
return (
|
||||
!hasFunction<DropSinglePhase>("getClaimConditionById", contractWrapper) &&
|
||||
hasFunction<DropSinglePhase>("getSupplyClaimedByWallet", contractWrapper)
|
||||
);
|
||||
}
|
||||
|
||||
private isMultiPhaseDropContract(
|
||||
isNewMultiphaseDrop(
|
||||
contractWrapper: ContractWrapper<any>,
|
||||
): contractWrapper is ContractWrapper<DropERC721 | DropERC20> {
|
||||
return hasFunction<DropERC721>("getClaimConditionById", contractWrapper);
|
||||
): contractWrapper is ContractWrapper<Drop> {
|
||||
return (
|
||||
hasFunction<Drop>("getClaimConditionById", contractWrapper) &&
|
||||
hasFunction<Drop>("getSupplyClaimedByWallet", contractWrapper)
|
||||
);
|
||||
}
|
||||
|
||||
isLegacySinglePhaseDrop(
|
||||
contractWrapper: ContractWrapper<any>,
|
||||
): contractWrapper is ContractWrapper<DropSinglePhase_V1> {
|
||||
return (
|
||||
!hasFunction<DropSinglePhase_V1>(
|
||||
"getClaimConditionById",
|
||||
contractWrapper,
|
||||
) && hasFunction<DropSinglePhase_V1>("getClaimTimestamp", contractWrapper)
|
||||
);
|
||||
}
|
||||
|
||||
isLegacyMultiPhaseDrop(
|
||||
contractWrapper: ContractWrapper<any>,
|
||||
): contractWrapper is ContractWrapper<DropERC721_V3 | DropERC20_V2> {
|
||||
return (
|
||||
hasFunction<DropERC721_V3>("getClaimConditionById", contractWrapper) &&
|
||||
hasFunction<DropERC721_V3>("setWalletClaimCount", contractWrapper)
|
||||
);
|
||||
}
|
||||
|
||||
private getSnapshotFormatVersion(): SnapshotFormatVersion {
|
||||
return this.isLegacyMultiPhaseDrop(this.contractWrapper) ||
|
||||
this.isLegacySinglePhaseDrop(this.contractWrapper)
|
||||
? SnapshotFormatVersion.V1
|
||||
: SnapshotFormatVersion.V2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { includesErrorMessage } from "../../common";
|
||||
import {
|
||||
abstractContractModelToLegacy,
|
||||
abstractContractModelToNew,
|
||||
fetchSnapshotEntryForAddress,
|
||||
getClaimerProofs,
|
||||
legacyContractModelToAbstract,
|
||||
newContractModelToAbstract,
|
||||
prepareClaim,
|
||||
processClaimConditionInputs,
|
||||
transformResultToClaimCondition,
|
||||
@@ -9,29 +13,41 @@ import {
|
||||
} from "../../common/claim-conditions";
|
||||
import { isNativeToken } from "../../common/currency";
|
||||
import { hasFunction } from "../../common/feature-detection";
|
||||
import { SnapshotFormatVersion } from "../../common/sharded-merkle-tree";
|
||||
import { isNode } from "../../common/utils";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../constants/index";
|
||||
import { ClaimEligibility } from "../../enums";
|
||||
import { SnapshotEntryWithProof } from "../../schema";
|
||||
import {
|
||||
AbstractClaimConditionContractStruct,
|
||||
SnapshotEntryWithProof,
|
||||
} from "../../schema";
|
||||
import {
|
||||
ClaimCondition,
|
||||
ClaimConditionFetchOptions,
|
||||
ClaimConditionInput,
|
||||
ClaimConditionsForToken,
|
||||
ClaimOptions,
|
||||
ClaimVerification,
|
||||
} from "../../types";
|
||||
import { BaseClaimConditionERC1155 } from "../../types/eips";
|
||||
import { TransactionResult } from "../index";
|
||||
import {
|
||||
BaseClaimConditionERC1155,
|
||||
PrebuiltEditionDrop,
|
||||
} from "../../types/eips";
|
||||
import { TransactionResult, TransactionTask } from "../index";
|
||||
import { ContractMetadata } from "./contract-metadata";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import type {
|
||||
ContractMetadata as ContractMetadataContract,
|
||||
DropERC1155,
|
||||
Drop1155,
|
||||
DropERC1155_V2,
|
||||
DropSinglePhase1155,
|
||||
DropSinglePhase1155_V1,
|
||||
IDropSinglePhase,
|
||||
IDropSinglePhase_V1,
|
||||
IERC20,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import IERC20ABI from "@thirdweb-dev/contracts-js/dist/abis/IERC20.json";
|
||||
import { IDropClaimCondition } from "@thirdweb-dev/contracts-js/dist/declarations/src/DropERC1155";
|
||||
import type { IDropClaimCondition_V2 } from "@thirdweb-dev/contracts-js/dist/declarations/src/DropERC20_V2";
|
||||
import type { IClaimCondition } from "@thirdweb-dev/contracts-js/src/IDrop";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish, constants, ethers, utils } from "ethers";
|
||||
import deepEqual from "fast-deep-equal";
|
||||
@@ -41,7 +57,7 @@ import deepEqual from "fast-deep-equal";
|
||||
* @public
|
||||
*/
|
||||
export class DropErc1155ClaimConditions<
|
||||
TContract extends DropERC1155 | BaseClaimConditionERC1155,
|
||||
TContract extends PrebuiltEditionDrop | BaseClaimConditionERC1155,
|
||||
> {
|
||||
private contractWrapper;
|
||||
private metadata;
|
||||
@@ -84,20 +100,45 @@ export class DropErc1155ClaimConditions<
|
||||
|
||||
private async get(
|
||||
tokenId: BigNumberish,
|
||||
): Promise<IDropClaimCondition.ClaimConditionStructOutput> {
|
||||
if (this.isSinglePhaseDropContract(this.contractWrapper)) {
|
||||
return (await this.contractWrapper.readContract.claimCondition(
|
||||
tokenId,
|
||||
)) as IDropClaimCondition.ClaimConditionStructOutput;
|
||||
} else if (this.isMultiPhaseDropContract(this.contractWrapper)) {
|
||||
conditionId?: BigNumberish,
|
||||
): Promise<AbstractClaimConditionContractStruct> {
|
||||
if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
|
||||
const contractModel = await (
|
||||
this.contractWrapper.readContract as DropSinglePhase1155_V1
|
||||
).claimCondition(tokenId);
|
||||
return legacyContractModelToAbstract(contractModel);
|
||||
} else if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
|
||||
const id =
|
||||
await this.contractWrapper.readContract.getActiveClaimConditionId(
|
||||
conditionId !== undefined
|
||||
? conditionId
|
||||
: await this.contractWrapper.readContract.getActiveClaimConditionId(
|
||||
tokenId,
|
||||
);
|
||||
const contractModel =
|
||||
(await this.contractWrapper.readContract.getClaimConditionById(
|
||||
tokenId,
|
||||
);
|
||||
return await this.contractWrapper.readContract.getClaimConditionById(
|
||||
tokenId,
|
||||
id,
|
||||
);
|
||||
id,
|
||||
)) as IDropClaimCondition_V2.ClaimConditionStructOutput;
|
||||
return legacyContractModelToAbstract(contractModel);
|
||||
} else if (this.isNewSinglePhaseDrop(this.contractWrapper)) {
|
||||
const contractModel =
|
||||
(await this.contractWrapper.readContract.claimCondition(
|
||||
tokenId,
|
||||
)) as IClaimCondition.ClaimConditionStructOutput;
|
||||
return newContractModelToAbstract(contractModel);
|
||||
} else if (this.isNewMultiphaseDrop(this.contractWrapper)) {
|
||||
const id =
|
||||
conditionId !== undefined
|
||||
? conditionId
|
||||
: await this.contractWrapper.readContract.getActiveClaimConditionId(
|
||||
tokenId,
|
||||
);
|
||||
const contractModel =
|
||||
(await this.contractWrapper.readContract.getClaimConditionById(
|
||||
tokenId,
|
||||
id,
|
||||
)) as IClaimCondition.ClaimConditionStruct;
|
||||
return newContractModelToAbstract(contractModel);
|
||||
} else {
|
||||
throw new Error("Contract does not support claim conditions");
|
||||
}
|
||||
@@ -112,7 +153,10 @@ export class DropErc1155ClaimConditions<
|
||||
tokenId: BigNumberish,
|
||||
options?: ClaimConditionFetchOptions,
|
||||
): Promise<ClaimCondition[]> {
|
||||
if (this.isMultiPhaseDropContract(this.contractWrapper)) {
|
||||
if (
|
||||
this.isLegacyMultiPhaseDrop(this.contractWrapper) ||
|
||||
this.isNewMultiphaseDrop(this.contractWrapper)
|
||||
) {
|
||||
const claimCondition =
|
||||
(await this.contractWrapper.readContract.claimCondition(tokenId)) as {
|
||||
currentStartId: BigNumber;
|
||||
@@ -120,14 +164,9 @@ export class DropErc1155ClaimConditions<
|
||||
};
|
||||
const startId = claimCondition.currentStartId.toNumber();
|
||||
const count = claimCondition.count.toNumber();
|
||||
const conditions: IDropClaimCondition.ClaimConditionStructOutput[] = [];
|
||||
const conditions: AbstractClaimConditionContractStruct[] = [];
|
||||
for (let i = startId; i < startId + count; i++) {
|
||||
conditions.push(
|
||||
await this.contractWrapper.readContract.getClaimConditionById(
|
||||
tokenId,
|
||||
i,
|
||||
),
|
||||
);
|
||||
conditions.push(await this.get(tokenId, i));
|
||||
}
|
||||
const metadata = await this.metadata.get();
|
||||
return Promise.all(
|
||||
@@ -143,7 +182,7 @@ export class DropErc1155ClaimConditions<
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return [await this.getActive(tokenId)];
|
||||
return [await this.getActive(tokenId, options)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,52 +271,142 @@ export class DropErc1155ClaimConditions<
|
||||
const merkleRootArray = ethers.utils.stripZeros(
|
||||
claimCondition.merkleRootHash,
|
||||
);
|
||||
if (merkleRootArray.length > 0) {
|
||||
const hasAllowList = merkleRootArray.length > 0;
|
||||
let allowListEntry;
|
||||
if (hasAllowList) {
|
||||
const merkleLower = claimCondition.merkleRootHash.toString();
|
||||
const metadata = await this.metadata.get();
|
||||
const proofs = await getClaimerProofs(
|
||||
allowListEntry = await getClaimerProofs(
|
||||
addressToCheck,
|
||||
merkleLower,
|
||||
0,
|
||||
metadata.merkle,
|
||||
this.storage,
|
||||
this.contractWrapper.getProvider(),
|
||||
this.getSnapshotFormatVersion(),
|
||||
);
|
||||
try {
|
||||
let validMerkleProof;
|
||||
if (this.isMultiPhaseDropContract(this.contractWrapper)) {
|
||||
activeConditionIndex =
|
||||
await this.contractWrapper.readContract.getActiveClaimConditionId(
|
||||
|
||||
if (
|
||||
!allowListEntry &&
|
||||
(this.isLegacySinglePhaseDrop(this.contractWrapper) ||
|
||||
this.isLegacyMultiPhaseDrop(this.contractWrapper))
|
||||
) {
|
||||
// exclusive allowlist behavior
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
|
||||
if (allowListEntry) {
|
||||
try {
|
||||
let validMerkleProof;
|
||||
if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
|
||||
activeConditionIndex =
|
||||
await this.contractWrapper.readContract.getActiveClaimConditionId(
|
||||
tokenId,
|
||||
);
|
||||
// legacy verifyClaimerMerkleProofs function
|
||||
[validMerkleProof] =
|
||||
await this.contractWrapper.readContract.verifyClaimMerkleProof(
|
||||
activeConditionIndex,
|
||||
addressToCheck,
|
||||
tokenId,
|
||||
quantity,
|
||||
allowListEntry.proof,
|
||||
allowListEntry.maxClaimable,
|
||||
);
|
||||
if (!validMerkleProof) {
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
} else if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
|
||||
[validMerkleProof] =
|
||||
await this.contractWrapper.readContract.verifyClaimMerkleProof(
|
||||
tokenId,
|
||||
addressToCheck,
|
||||
quantity,
|
||||
{
|
||||
proof: allowListEntry.proof,
|
||||
maxQuantityInAllowlist: allowListEntry.maxClaimable,
|
||||
},
|
||||
);
|
||||
if (!validMerkleProof) {
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
} else if (this.isNewSinglePhaseDrop(this.contractWrapper)) {
|
||||
await this.contractWrapper.readContract.verifyClaim(
|
||||
tokenId,
|
||||
addressToCheck,
|
||||
quantity,
|
||||
claimCondition.currencyAddress,
|
||||
claimCondition.price,
|
||||
{
|
||||
proof: allowListEntry.proof,
|
||||
quantityLimitPerWallet: allowListEntry.maxClaimable,
|
||||
currency: allowListEntry.currencyAddress,
|
||||
pricePerToken: allowListEntry.price,
|
||||
} as IDropSinglePhase.AllowlistProofStruct,
|
||||
);
|
||||
[validMerkleProof] =
|
||||
await this.contractWrapper.readContract.verifyClaimMerkleProof(
|
||||
// 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 (
|
||||
(claimCondition.maxClaimablePerWallet === "0" &&
|
||||
allowListEntry.maxClaimable === ethers.constants.MaxUint256) ||
|
||||
allowListEntry.maxClaimable === BigNumber.from(0)
|
||||
) {
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
} else if (this.isNewMultiphaseDrop(this.contractWrapper)) {
|
||||
activeConditionIndex =
|
||||
await this.contractWrapper.readContract.getActiveClaimConditionId(
|
||||
tokenId,
|
||||
);
|
||||
await this.contractWrapper.readContract.verifyClaim(
|
||||
activeConditionIndex,
|
||||
addressToCheck,
|
||||
tokenId,
|
||||
quantity,
|
||||
proofs.proof,
|
||||
proofs.maxClaimable,
|
||||
);
|
||||
} else if (this.isSinglePhaseDropContract(this.contractWrapper)) {
|
||||
[validMerkleProof] =
|
||||
await this.contractWrapper.readContract.verifyClaimMerkleProof(
|
||||
tokenId,
|
||||
addressToCheck,
|
||||
quantity,
|
||||
claimCondition.currencyAddress,
|
||||
claimCondition.price,
|
||||
{
|
||||
proof: proofs.proof,
|
||||
maxQuantityInAllowlist: proofs.maxClaimable,
|
||||
},
|
||||
proof: allowListEntry.proof,
|
||||
quantityLimitPerWallet: allowListEntry.maxClaimable,
|
||||
currency: allowListEntry.currencyAddress,
|
||||
pricePerToken: allowListEntry.price,
|
||||
} as IDropSinglePhase.AllowlistProofStruct,
|
||||
);
|
||||
}
|
||||
|
||||
if (!validMerkleProof) {
|
||||
if (
|
||||
(claimCondition.maxClaimablePerWallet === "0" &&
|
||||
allowListEntry.maxClaimable === ethers.constants.MaxUint256) ||
|
||||
allowListEntry.maxClaimable === BigNumber.from(0)
|
||||
) {
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn(
|
||||
"Merkle proof verification failed:",
|
||||
"reason" in e ? e.reason : e,
|
||||
);
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (cc) check for max claimable per wallet and how much each wallet has claimed
|
||||
if (
|
||||
this.isNewSinglePhaseDrop(this.contractWrapper) ||
|
||||
this.isNewMultiphaseDrop(this.contractWrapper)
|
||||
) {
|
||||
if (!hasAllowList || (hasAllowList && !allowListEntry)) {
|
||||
if (claimCondition.maxClaimablePerWallet === "0") {
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
} catch (e) {
|
||||
reasons.push(ClaimEligibility.AddressNotAllowed);
|
||||
return reasons;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +415,7 @@ export class DropErc1155ClaimConditions<
|
||||
BigNumber.from(0),
|
||||
BigNumber.from(0),
|
||||
];
|
||||
if (this.isMultiPhaseDropContract(this.contractWrapper)) {
|
||||
if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
|
||||
activeConditionIndex =
|
||||
await this.contractWrapper.readContract.getActiveClaimConditionId(
|
||||
tokenId,
|
||||
@@ -297,7 +426,7 @@ export class DropErc1155ClaimConditions<
|
||||
activeConditionIndex,
|
||||
addressToCheck,
|
||||
);
|
||||
} else if (this.isSinglePhaseDropContract(this.contractWrapper)) {
|
||||
} else if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
|
||||
[lastClaimedTimestamp, timestampForNextClaim] =
|
||||
await this.contractWrapper.readContract.getClaimTimestamp(
|
||||
tokenId,
|
||||
@@ -346,25 +475,27 @@ export class DropErc1155ClaimConditions<
|
||||
|
||||
/**
|
||||
* Returns allow list information and merkle proofs for the given address.
|
||||
* @param tokenId
|
||||
* @param claimerAddress
|
||||
* @param tokenId - the token ID to check
|
||||
* @param claimerAddress - the claimer address
|
||||
* @param claimConditionId - optional the claim condition id to get the proofs for
|
||||
*/
|
||||
public async getClaimerProofs(
|
||||
tokenId: BigNumberish,
|
||||
claimerAddress: string,
|
||||
claimConditionId?: BigNumberish,
|
||||
): Promise<SnapshotEntryWithProof | null> {
|
||||
const claimCondition = await this.getActive(tokenId);
|
||||
const merkeRoot = claimCondition.merkleRootHash;
|
||||
const merkleRootArray = ethers.utils.stripZeros(
|
||||
claimCondition.merkleRootHash,
|
||||
);
|
||||
const claimCondition = await this.get(tokenId, claimConditionId);
|
||||
const merkleRoot = claimCondition.merkleRoot;
|
||||
const merkleRootArray = ethers.utils.stripZeros(merkleRoot);
|
||||
if (merkleRootArray.length > 0) {
|
||||
const metadata = await this.metadata.get();
|
||||
return await fetchSnapshotEntryForAddress(
|
||||
claimerAddress,
|
||||
merkeRoot.toString(),
|
||||
merkleRoot.toString(),
|
||||
metadata.merkle,
|
||||
this.contractWrapper.getProvider(),
|
||||
this.storage,
|
||||
this.getSnapshotFormatVersion(),
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
@@ -469,16 +600,16 @@ export class DropErc1155ClaimConditions<
|
||||
claimConditionsForToken.map(async ({ tokenId, claimConditions }) => {
|
||||
// sanitize for single phase deletions
|
||||
let claimConditionsProcessed = claimConditions;
|
||||
if (this.isSinglePhaseDropContract(this.contractWrapper)) {
|
||||
if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
|
||||
resetClaimEligibilityForAll = true;
|
||||
if (claimConditions.length === 0) {
|
||||
claimConditionsProcessed = [
|
||||
{
|
||||
startTime: new Date(0),
|
||||
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
||||
currencyAddress: ethers.constants.AddressZero,
|
||||
price: 0,
|
||||
maxQuantity: 0,
|
||||
quantityLimitPerTransaction: 0,
|
||||
maxClaimableSupply: 0,
|
||||
maxClaimablePerWallet: 0,
|
||||
waitInSeconds: 0,
|
||||
merkleRootHash: utils.hexZeroPad([0], 32),
|
||||
snapshot: [],
|
||||
@@ -497,6 +628,7 @@ export class DropErc1155ClaimConditions<
|
||||
0,
|
||||
this.contractWrapper.getProvider(),
|
||||
this.storage,
|
||||
this.getSnapshotFormatVersion(),
|
||||
);
|
||||
|
||||
snapshotInfos.forEach((s) => {
|
||||
@@ -548,18 +680,48 @@ export class DropErc1155ClaimConditions<
|
||||
}
|
||||
|
||||
processedClaimConditions.forEach(({ tokenId, sortedConditions }) => {
|
||||
if (this.isSinglePhaseDropContract(this.contractWrapper)) {
|
||||
if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
|
||||
encoded.push(
|
||||
this.contractWrapper.readContract.interface.encodeFunctionData(
|
||||
"setClaimConditions",
|
||||
[tokenId, sortedConditions[0], resetClaimEligibilityForAll],
|
||||
[
|
||||
tokenId,
|
||||
abstractContractModelToLegacy(sortedConditions[0]),
|
||||
resetClaimEligibilityForAll,
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (this.isMultiPhaseDropContract(this.contractWrapper)) {
|
||||
} else if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
|
||||
encoded.push(
|
||||
this.contractWrapper.readContract.interface.encodeFunctionData(
|
||||
"setClaimConditions",
|
||||
[tokenId, sortedConditions, resetClaimEligibilityForAll],
|
||||
[
|
||||
tokenId,
|
||||
sortedConditions.map(abstractContractModelToLegacy),
|
||||
resetClaimEligibilityForAll,
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (this.isNewSinglePhaseDrop(this.contractWrapper)) {
|
||||
encoded.push(
|
||||
this.contractWrapper.readContract.interface.encodeFunctionData(
|
||||
"setClaimConditions",
|
||||
[
|
||||
tokenId,
|
||||
abstractContractModelToNew(sortedConditions[0]),
|
||||
resetClaimEligibilityForAll,
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (this.isNewMultiphaseDrop(this.contractWrapper)) {
|
||||
encoded.push(
|
||||
this.contractWrapper.readContract.interface.encodeFunctionData(
|
||||
"setClaimConditions",
|
||||
[
|
||||
tokenId,
|
||||
sortedConditions.map(abstractContractModelToNew),
|
||||
resetClaimEligibilityForAll,
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -610,21 +772,142 @@ export class DropErc1155ClaimConditions<
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
checkERC20Allowance,
|
||||
this.getSnapshotFormatVersion(),
|
||||
);
|
||||
}
|
||||
|
||||
private isSinglePhaseDropContract(
|
||||
public async getClaimArguments(
|
||||
tokenId: BigNumberish,
|
||||
destinationAddress: string,
|
||||
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.proofs,
|
||||
claimVerification.maxClaimable,
|
||||
];
|
||||
} else if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
|
||||
return [
|
||||
destinationAddress,
|
||||
tokenId,
|
||||
quantity,
|
||||
activeClaimCondition.currencyAddress,
|
||||
activeClaimCondition.price,
|
||||
{
|
||||
proof: claimVerification.proofs,
|
||||
maxQuantityInAllowlist: claimVerification.maxClaimable,
|
||||
} as IDropSinglePhase_V1.AllowlistProofStruct,
|
||||
ethers.utils.toUtf8Bytes(""),
|
||||
];
|
||||
}
|
||||
return [
|
||||
destinationAddress,
|
||||
tokenId,
|
||||
quantity,
|
||||
activeClaimCondition.currencyAddress,
|
||||
activeClaimCondition.price,
|
||||
{
|
||||
proof: claimVerification.proofs,
|
||||
quantityLimitPerWallet: claimVerification.maxClaimable,
|
||||
pricePerToken: claimVerification.price,
|
||||
currency: claimVerification.currencyAddress,
|
||||
} as IDropSinglePhase.AllowlistProofStruct,
|
||||
ethers.utils.toUtf8Bytes(""),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a claim transaction without executing it.
|
||||
* This is useful for estimating the gas cost of a claim transaction, overriding transaction options and having fine grained control over the transaction execution.
|
||||
* @param destinationAddress - Address you want to send the token to
|
||||
* @param tokenId - Id of the token you want to claim
|
||||
* @param quantity - Quantity of the tokens you want to claim
|
||||
*/
|
||||
public async getClaimTransaction(
|
||||
destinationAddress: string,
|
||||
tokenId: BigNumberish,
|
||||
quantity: BigNumberish,
|
||||
options?: ClaimOptions,
|
||||
): Promise<TransactionTask> {
|
||||
if (options?.pricePerToken) {
|
||||
throw new Error(
|
||||
"Price per token should be set via claim conditions by calling `contract.erc1155.claimConditions.set()`",
|
||||
);
|
||||
}
|
||||
const claimVerification = await this.prepareClaim(
|
||||
tokenId,
|
||||
quantity,
|
||||
options?.checkERC20Allowance || true,
|
||||
);
|
||||
return TransactionTask.make({
|
||||
contractWrapper: this.contractWrapper,
|
||||
functionName: "claim",
|
||||
args: await this.getClaimArguments(
|
||||
tokenId,
|
||||
destinationAddress,
|
||||
quantity,
|
||||
claimVerification,
|
||||
),
|
||||
overrides: claimVerification.overrides,
|
||||
});
|
||||
}
|
||||
|
||||
isNewSinglePhaseDrop(
|
||||
contractWrapper: ContractWrapper<any>,
|
||||
): contractWrapper is ContractWrapper<DropSinglePhase1155> {
|
||||
return !hasFunction<DropSinglePhase1155>(
|
||||
"getClaimConditionById",
|
||||
contractWrapper,
|
||||
return (
|
||||
!hasFunction<DropSinglePhase1155>(
|
||||
"getClaimConditionById",
|
||||
contractWrapper,
|
||||
) &&
|
||||
hasFunction<DropSinglePhase1155>(
|
||||
"getSupplyClaimedByWallet",
|
||||
contractWrapper,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private isMultiPhaseDropContract(
|
||||
isNewMultiphaseDrop(
|
||||
contractWrapper: ContractWrapper<any>,
|
||||
): contractWrapper is ContractWrapper<DropERC1155> {
|
||||
return hasFunction<DropERC1155>("getClaimConditionById", contractWrapper);
|
||||
): contractWrapper is ContractWrapper<Drop1155> {
|
||||
return (
|
||||
hasFunction<Drop1155>("getClaimConditionById", contractWrapper) &&
|
||||
hasFunction<Drop1155>("getSupplyClaimedByWallet", contractWrapper)
|
||||
);
|
||||
}
|
||||
|
||||
isLegacySinglePhaseDrop(
|
||||
contractWrapper: ContractWrapper<any>,
|
||||
): contractWrapper is ContractWrapper<DropSinglePhase1155_V1> {
|
||||
return (
|
||||
!hasFunction<DropSinglePhase1155_V1>(
|
||||
"getClaimConditionById",
|
||||
contractWrapper,
|
||||
) &&
|
||||
hasFunction<DropSinglePhase1155_V1>("getClaimTimestamp", contractWrapper)
|
||||
);
|
||||
}
|
||||
|
||||
isLegacyMultiPhaseDrop(
|
||||
contractWrapper: ContractWrapper<any>,
|
||||
): contractWrapper is ContractWrapper<DropERC1155_V2> {
|
||||
return (
|
||||
hasFunction<DropERC1155_V2>("getClaimConditionById", contractWrapper) &&
|
||||
hasFunction<DropERC1155_V2>("setWalletClaimCount", contractWrapper)
|
||||
);
|
||||
}
|
||||
|
||||
private getSnapshotFormatVersion(): SnapshotFormatVersion {
|
||||
return this.isLegacyMultiPhaseDrop(this.contractWrapper) ||
|
||||
this.isLegacySinglePhaseDrop(this.contractWrapper)
|
||||
? SnapshotFormatVersion.V1
|
||||
: SnapshotFormatVersion.V2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PrebuiltEditionDrop } from "../../types/eips";
|
||||
import { ContractEvents } from "./contract-events";
|
||||
import type { DropERC1155 } from "@thirdweb-dev/contracts-js";
|
||||
import { BigNumber, BigNumberish } from "ethers";
|
||||
|
||||
/**
|
||||
@@ -9,7 +9,7 @@ import { BigNumber, BigNumberish } from "ethers";
|
||||
export class DropErc1155History {
|
||||
private events;
|
||||
|
||||
constructor(events: ContractEvents<DropERC1155>) {
|
||||
constructor(events: ContractEvents<PrebuiltEditionDrop>) {
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
import { hasFunction } from "../../common";
|
||||
import { FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS } from "../../constants/erc1155-features";
|
||||
import { CustomContractSchema } from "../../schema/contracts/custom";
|
||||
import {
|
||||
ClaimOptions,
|
||||
ClaimVerification,
|
||||
} from "../../types/claim-conditions/claim-conditions";
|
||||
import { ClaimOptions } from "../../types/claim-conditions/claim-conditions";
|
||||
import { BaseClaimConditionERC1155 } from "../../types/eips";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResult } from "../types";
|
||||
import { TransactionTask } from "./TransactionTask";
|
||||
import { ContractMetadata } from "./contract-metadata";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { DropErc1155ClaimConditions } from "./drop-erc1155-claim-conditions";
|
||||
import type { DropERC1155 } from "@thirdweb-dev/contracts-js";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumberish, ethers } from "ethers";
|
||||
import { BigNumberish } from "ethers";
|
||||
|
||||
/**
|
||||
* Configure and claim ERC1155 NFTs
|
||||
@@ -52,42 +46,6 @@ export class Erc1155ClaimableWithConditions implements DetectableFeature {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a claim transaction without executing it.
|
||||
* This is useful for estimating the gas cost of a claim transaction, overriding transaction options and having fine grained control over the transaction execution.
|
||||
* @param destinationAddress - Address you want to send the token to
|
||||
* @param tokenId - Id of the token you want to claim
|
||||
* @param quantity - Quantity of the tokens you want to claim
|
||||
*/
|
||||
public async getClaimTransaction(
|
||||
destinationAddress: string,
|
||||
tokenId: BigNumberish,
|
||||
quantity: BigNumberish,
|
||||
options?: ClaimOptions,
|
||||
): Promise<TransactionTask> {
|
||||
if (options?.pricePerToken) {
|
||||
throw new Error(
|
||||
"In ERC1155ClaimableWithConditions, price per token is be set via claim conditions by calling `contract.erc1155.claimConditions.set()`",
|
||||
);
|
||||
}
|
||||
const claimVerification = await this.conditions.prepareClaim(
|
||||
tokenId,
|
||||
quantity,
|
||||
options?.checkERC20Allowance || true,
|
||||
);
|
||||
return TransactionTask.make({
|
||||
contractWrapper: this.contractWrapper,
|
||||
functionName: "claim",
|
||||
args: await this.getArgs(
|
||||
tokenId,
|
||||
destinationAddress,
|
||||
quantity,
|
||||
claimVerification,
|
||||
),
|
||||
overrides: claimVerification.overrides,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim NFTs to a specific Wallet
|
||||
*
|
||||
@@ -115,7 +73,7 @@ export class Erc1155ClaimableWithConditions implements DetectableFeature {
|
||||
quantity: BigNumberish,
|
||||
options?: ClaimOptions,
|
||||
): Promise<TransactionResult> {
|
||||
const tx = await this.getClaimTransaction(
|
||||
const tx = await this.conditions.getClaimTransaction(
|
||||
destinationAddress,
|
||||
tokenId,
|
||||
quantity,
|
||||
@@ -123,51 +81,4 @@ export class Erc1155ClaimableWithConditions implements DetectableFeature {
|
||||
);
|
||||
return await tx.execute();
|
||||
}
|
||||
|
||||
private async getArgs(
|
||||
tokenId: BigNumberish,
|
||||
destinationAddress: string,
|
||||
quantity: BigNumberish,
|
||||
claimVerification: ClaimVerification,
|
||||
): Promise<any[]> {
|
||||
const isLegacyNFTContract = await this.isLegacyNFTContract();
|
||||
if (isLegacyNFTContract) {
|
||||
return [
|
||||
destinationAddress,
|
||||
tokenId,
|
||||
quantity,
|
||||
claimVerification.currencyAddress,
|
||||
claimVerification.price,
|
||||
claimVerification.proofs,
|
||||
claimVerification.maxQuantityPerTransaction,
|
||||
];
|
||||
}
|
||||
return [
|
||||
destinationAddress,
|
||||
tokenId,
|
||||
quantity,
|
||||
claimVerification.currencyAddress,
|
||||
claimVerification.price,
|
||||
{
|
||||
proof: claimVerification.proofs,
|
||||
maxQuantityInAllowlist: claimVerification.maxQuantityPerTransaction,
|
||||
},
|
||||
ethers.utils.toUtf8Bytes(""),
|
||||
];
|
||||
}
|
||||
|
||||
private async isLegacyNFTContract() {
|
||||
if (hasFunction<DropERC1155>("contractType", this.contractWrapper)) {
|
||||
try {
|
||||
const contractType = ethers.utils.toUtf8String(
|
||||
await this.contractWrapper.readContract.contractType(),
|
||||
);
|
||||
return contractType.includes("DropERC1155");
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
detectContractFeature,
|
||||
hasFunction,
|
||||
} from "../../common/feature-detection";
|
||||
import { getPrebuiltInfo } from "../../common/legacy";
|
||||
import { uploadOrExtractURIs } from "../../common/nft";
|
||||
import {
|
||||
FEATURE_EDITION_LAZY_MINTABLE,
|
||||
@@ -21,10 +22,7 @@ import { DelayedReveal } from "./delayed-reveal";
|
||||
import { Erc1155 } from "./erc-1155";
|
||||
import { ERC1155Claimable } from "./erc-1155-claimable";
|
||||
import { Erc1155ClaimableWithConditions } from "./erc-1155-claimable-with-conditions";
|
||||
import type {
|
||||
IClaimableERC1155,
|
||||
TokenERC721,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import type { IClaimableERC1155 } from "@thirdweb-dev/contracts-js";
|
||||
import { TokensLazyMintedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/LazyMint";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { ethers } from "ethers";
|
||||
@@ -232,17 +230,10 @@ export class Erc1155LazyMintable implements DetectableFeature {
|
||||
}
|
||||
|
||||
private async isLegacyEditionDropContract() {
|
||||
if (hasFunction<TokenERC721>("contractType", this.contractWrapper)) {
|
||||
try {
|
||||
const contractType = ethers.utils.toUtf8String(
|
||||
await this.contractWrapper.readContract.contractType(),
|
||||
);
|
||||
return contractType.includes("DropERC1155");
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
const info = await getPrebuiltInfo(
|
||||
this.contractWrapper.readContract.address,
|
||||
this.contractWrapper.getProvider(),
|
||||
);
|
||||
return info && info.type === "DropERC1155" && info.version < 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ export class Erc1155SignatureMintable implements DetectableFeature {
|
||||
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const payload = {
|
||||
* metadata: nftMetadata, // The NFT to mint
|
||||
* to: {{wallet_address}}, // Who will receive the NFT (or AddressZero for anyone)
|
||||
* to: {{wallet_address}}, // Who will receive the NFT
|
||||
* quantity: 2, // the quantity of NFTs to mint
|
||||
* price: 0.5, // the price per NFT
|
||||
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
|
||||
@@ -214,7 +214,7 @@ export class Erc1155SignatureMintable implements DetectableFeature {
|
||||
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const payload = {
|
||||
* metadata: nftMetadata, // The NFT to mint
|
||||
* to: {{wallet_address}}, // Who will receive the NFT (or AddressZero for anyone)
|
||||
* to: {{wallet_address}}, // Who will receive the NFT
|
||||
* quantity: 2, // the quantity of NFTs to mint
|
||||
* price: 0.5, // the price per NFT
|
||||
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
|
||||
|
||||
@@ -793,7 +793,7 @@ export class Erc1155<
|
||||
const claimWithConditions = this.lazyMintable?.claimWithConditions;
|
||||
const claim = this.lazyMintable?.claim;
|
||||
if (claimWithConditions) {
|
||||
return claimWithConditions.getClaimTransaction(
|
||||
return claimWithConditions.conditions.getClaimTransaction(
|
||||
destinationAddress,
|
||||
tokenId,
|
||||
quantity,
|
||||
|
||||
+8
-31
@@ -1,6 +1,6 @@
|
||||
import { FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS } from "../../constants/erc20-features";
|
||||
import { CustomContractSchema } from "../../schema/contracts/custom";
|
||||
import { ClaimVerification } from "../../types";
|
||||
import { ClaimOptions } from "../../types";
|
||||
import { Amount } from "../../types/currency";
|
||||
import { BaseDropERC20 } from "../../types/eips";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
@@ -20,7 +20,7 @@ import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
* await contract.token.drop.claim.to("0x...", quantity);
|
||||
* ```
|
||||
*/
|
||||
export class Erc20Claimable implements DetectableFeature {
|
||||
export class Erc20ClaimableWithConditions implements DetectableFeature {
|
||||
featureName = FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS.name;
|
||||
/**
|
||||
* Configure claim conditions
|
||||
@@ -93,37 +93,14 @@ export class Erc20Claimable implements DetectableFeature {
|
||||
public async to(
|
||||
destinationAddress: string,
|
||||
amount: Amount,
|
||||
checkERC20Allowance = true,
|
||||
claimData?: ClaimVerification,
|
||||
options?: ClaimOptions,
|
||||
): Promise<TransactionResult> {
|
||||
const quantity = await this.erc20.normalizeAmount(amount);
|
||||
|
||||
let claimVerification = claimData;
|
||||
if (this.conditions && !claimData) {
|
||||
claimVerification = await this.conditions.prepareClaim(
|
||||
quantity,
|
||||
checkERC20Allowance,
|
||||
await this.contractWrapper.readContract.decimals(),
|
||||
);
|
||||
}
|
||||
if (!claimVerification) {
|
||||
throw new Error(
|
||||
"Claim verification Data is required - either pass it in as 'claimData' or set claim conditions via 'conditions.set()'",
|
||||
);
|
||||
}
|
||||
|
||||
const receipt = await this.contractWrapper.sendTransaction(
|
||||
"claim",
|
||||
[
|
||||
destinationAddress,
|
||||
quantity,
|
||||
claimVerification.currencyAddress,
|
||||
claimVerification.price,
|
||||
claimVerification.proofs,
|
||||
claimVerification.maxQuantityPerTransaction,
|
||||
],
|
||||
claimVerification.overrides,
|
||||
const task = await this.conditions.getClaimTransaction(
|
||||
destinationAddress,
|
||||
quantity,
|
||||
options,
|
||||
);
|
||||
return { receipt };
|
||||
return await task.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BaseDropERC20 } from "../../types/eips";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc20 } from "./erc-20";
|
||||
import { Erc20Claimable } from "./erc-20-claimable";
|
||||
import { Erc20ClaimableWithConditions } from "./erc-20-claimable-with-conditions";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
|
||||
/**
|
||||
@@ -36,7 +36,7 @@ export class Erc20Droppable {
|
||||
* await contract.nft.drop.claim.conditions.set(claimConditions);
|
||||
* ```
|
||||
*/
|
||||
public claim: Erc20Claimable;
|
||||
public claim: Erc20ClaimableWithConditions;
|
||||
private contractWrapper: ContractWrapper<BaseDropERC20>;
|
||||
private erc20: Erc20;
|
||||
private storage: ThirdwebStorage;
|
||||
@@ -50,7 +50,7 @@ export class Erc20Droppable {
|
||||
this.contractWrapper = contractWrapper;
|
||||
|
||||
this.storage = storage;
|
||||
this.claim = new Erc20Claimable(
|
||||
this.claim = new Erc20ClaimableWithConditions(
|
||||
this.erc20,
|
||||
this.contractWrapper,
|
||||
this.storage,
|
||||
|
||||
@@ -123,7 +123,7 @@ export class Erc20SignatureMintable implements DetectableFeature {
|
||||
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const payload = {
|
||||
* quantity: 4.2, // The quantity of tokens to be minted
|
||||
* to: {{wallet_address}}, // Who will receive the tokens (or AddressZero for anyone)
|
||||
* to: {{wallet_address}}, // Who will receive the tokens
|
||||
* price: 0.5, // the price to pay for minting those tokens
|
||||
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
|
||||
* mintStartTime: startTime, // can mint anytime from now
|
||||
@@ -156,7 +156,7 @@ export class Erc20SignatureMintable implements DetectableFeature {
|
||||
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const payload = {
|
||||
* quantity: 4.2, // The quantity of tokens to be minted
|
||||
* to: {{wallet_address}}, // Who will receive the tokens (or AddressZero for anyone)
|
||||
* to: {{wallet_address}}, // Who will receive the tokens
|
||||
* price: 0.5, // the price to pay for minting those tokens
|
||||
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
|
||||
* mintStartTime: startTime, // can mint anytime from now
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NetworkOrSignerOrProvider, TransactionResult } from "..";
|
||||
import { AmountSchema } from "../../../core/schema/shared";
|
||||
import { assertEnabled, detectContractFeature } from "../../common";
|
||||
import {
|
||||
fetchCurrencyMetadata,
|
||||
@@ -12,13 +13,8 @@ import {
|
||||
FEATURE_TOKEN_SIGNATURE_MINTABLE,
|
||||
FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS,
|
||||
} from "../../constants/erc20-features";
|
||||
import { TokenMintInput, AmountSchema } from "../../schema";
|
||||
import {
|
||||
Currency,
|
||||
CurrencyValue,
|
||||
Amount,
|
||||
ClaimVerification,
|
||||
} from "../../types";
|
||||
import { TokenMintInput } from "../../schema";
|
||||
import { Currency, CurrencyValue, Amount, ClaimOptions } from "../../types";
|
||||
import {
|
||||
BaseERC20,
|
||||
BaseSignatureMintERC20,
|
||||
@@ -483,14 +479,12 @@ export class Erc20<
|
||||
*/
|
||||
public async claim(
|
||||
amount: Amount,
|
||||
checkERC20Allowance = true,
|
||||
claimData?: ClaimVerification,
|
||||
options?: ClaimOptions,
|
||||
): Promise<TransactionResult> {
|
||||
return this.claimTo(
|
||||
await this.contractWrapper.getSignerAddress(),
|
||||
amount,
|
||||
checkERC20Allowance,
|
||||
claimData,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -518,13 +512,12 @@ export class Erc20<
|
||||
public async claimTo(
|
||||
destinationAddress: string,
|
||||
amount: Amount,
|
||||
checkERC20Allowance = true,
|
||||
claimData?: ClaimVerification,
|
||||
options?: ClaimOptions,
|
||||
): Promise<TransactionResult> {
|
||||
return assertEnabled(
|
||||
this.droppable?.claim,
|
||||
FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS,
|
||||
).to(destinationAddress, amount, checkERC20Allowance, claimData);
|
||||
).to(destinationAddress, amount, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import { NFT } from "../../../core/schema/nft";
|
||||
import { hasFunction } from "../../common";
|
||||
import { FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS } from "../../constants/erc721-features";
|
||||
import { FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2 } from "../../constants/erc721-features";
|
||||
import { CustomContractSchema } from "../../schema/contracts/custom";
|
||||
import { ClaimOptions, ClaimVerification } from "../../types";
|
||||
import { ClaimOptions } from "../../types";
|
||||
import { BaseClaimConditionERC721 } from "../../types/eips";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResultWithId } from "../types";
|
||||
import { TransactionTask } from "./TransactionTask";
|
||||
import { ContractMetadata } from "./contract-metadata";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { DropClaimConditions } from "./drop-claim-conditions";
|
||||
import { Erc721 } from "./erc-721";
|
||||
import type { DropERC721 } from "@thirdweb-dev/contracts-js";
|
||||
import { TokensClaimedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/Drop";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumber, BigNumberish, ethers } from "ethers";
|
||||
import { BigNumber, BigNumberish } from "ethers";
|
||||
|
||||
/**
|
||||
* Configure and claim ERC721 NFTs
|
||||
@@ -27,7 +24,7 @@ import { BigNumber, BigNumberish, ethers } from "ethers";
|
||||
* ```
|
||||
*/
|
||||
export class Erc721ClaimableWithConditions implements DetectableFeature {
|
||||
featureName = FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS.name;
|
||||
featureName = FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2.name;
|
||||
|
||||
/**
|
||||
* Configure claim conditions
|
||||
@@ -77,38 +74,6 @@ export class Erc721ClaimableWithConditions implements DetectableFeature {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a claim transaction without executing it.
|
||||
* This is useful for estimating the gas cost of a claim transaction, overriding transaction options and having fine grained control over the transaction execution.
|
||||
* @param destinationAddress
|
||||
* @param quantity
|
||||
* @param options
|
||||
*/
|
||||
public async getClaimTransaction(
|
||||
destinationAddress: string,
|
||||
quantity: BigNumberish,
|
||||
options?: ClaimOptions,
|
||||
): Promise<TransactionTask> {
|
||||
if (options?.pricePerToken) {
|
||||
throw new Error(
|
||||
"In ERC721ClaimableWithConditions, price per token is be set via claim conditions by calling `contract.erc721.claimConditions.set()`",
|
||||
);
|
||||
}
|
||||
const claimVerification = await this.conditions.prepareClaim(
|
||||
quantity,
|
||||
options?.checkERC20Allowance === undefined
|
||||
? true
|
||||
: options.checkERC20Allowance,
|
||||
);
|
||||
|
||||
return TransactionTask.make({
|
||||
contractWrapper: this.contractWrapper,
|
||||
functionName: "claim",
|
||||
args: await this.getArgs(destinationAddress, quantity, claimVerification),
|
||||
overrides: claimVerification.overrides,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim unique NFTs to a specific Wallet
|
||||
*
|
||||
@@ -127,6 +92,7 @@ export class Erc721ClaimableWithConditions implements DetectableFeature {
|
||||
*
|
||||
* @param destinationAddress - Address you want to send the token to
|
||||
* @param quantity - Quantity of the tokens you want to claim
|
||||
* @param options
|
||||
* @returns - an array of results containing the id of the token claimed, the transaction receipt and a promise to optionally fetch the nft metadata
|
||||
*/
|
||||
public async to(
|
||||
@@ -134,7 +100,7 @@ export class Erc721ClaimableWithConditions implements DetectableFeature {
|
||||
quantity: BigNumberish,
|
||||
options?: ClaimOptions,
|
||||
): Promise<TransactionResultWithId<NFT>[]> {
|
||||
const task = await this.getClaimTransaction(
|
||||
const task = await this.conditions.getClaimTransaction(
|
||||
destinationAddress,
|
||||
quantity,
|
||||
options,
|
||||
@@ -156,48 +122,4 @@ export class Erc721ClaimableWithConditions implements DetectableFeature {
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private async getArgs(
|
||||
destinationAddress: string,
|
||||
quantity: BigNumberish,
|
||||
claimVerification: ClaimVerification,
|
||||
): Promise<any[]> {
|
||||
const isLegacyNFTContract = await this.isLegacyNFTContract();
|
||||
if (isLegacyNFTContract) {
|
||||
return [
|
||||
destinationAddress,
|
||||
quantity,
|
||||
claimVerification.currencyAddress,
|
||||
claimVerification.price,
|
||||
claimVerification.proofs,
|
||||
claimVerification.maxQuantityPerTransaction,
|
||||
];
|
||||
}
|
||||
return [
|
||||
destinationAddress,
|
||||
quantity,
|
||||
claimVerification.currencyAddress,
|
||||
claimVerification.price,
|
||||
{
|
||||
proof: claimVerification.proofs,
|
||||
maxQuantityInAllowlist: claimVerification.maxQuantityPerTransaction,
|
||||
},
|
||||
ethers.utils.toUtf8Bytes(""),
|
||||
];
|
||||
}
|
||||
|
||||
private async isLegacyNFTContract() {
|
||||
if (hasFunction<DropERC721>("contractType", this.contractWrapper)) {
|
||||
try {
|
||||
const contractType = ethers.utils.toUtf8String(
|
||||
await this.contractWrapper.readContract.contractType(),
|
||||
);
|
||||
return contractType.includes("DropERC721");
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export class Erc721Claimable implements DetectableFeature {
|
||||
* const quantity = 1; // how many NFTs you want to claim
|
||||
*
|
||||
* const tx = await contract.erc721.claimTo(address, tokenId, quantity);
|
||||
* const receipt = tx.receipt; // the transaction receipt
|
||||
* const receipt = tx[0].receipt; // the transaction receipt
|
||||
* ```
|
||||
*
|
||||
* @param destinationAddress - Address you want to send the token to
|
||||
|
||||
@@ -194,7 +194,11 @@ export class Erc721LazyMintable implements DetectableFeature {
|
||||
if (
|
||||
detectContractFeature<BaseClaimConditionERC721>(
|
||||
this.contractWrapper,
|
||||
"ERC721ClaimableWithConditions",
|
||||
"ERC721ClaimableWithConditionsV1",
|
||||
) ||
|
||||
detectContractFeature<BaseClaimConditionERC721>(
|
||||
this.contractWrapper,
|
||||
"ERC721ClaimableWithConditionsV2",
|
||||
)
|
||||
) {
|
||||
return new Erc721ClaimableWithConditions(
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
import {
|
||||
CommonNFTInput,
|
||||
NFT,
|
||||
NFTMetadata,
|
||||
NFTMetadataInput,
|
||||
NFTMetadataOrUri,
|
||||
} from "../../../core/schema/nft";
|
||||
import { normalizePriceValue, setErc20Allowance } from "../../common/currency";
|
||||
import { getBaseUriFromBatch, uploadOrExtractURIs } from "../../common/nft";
|
||||
import { FEATURE_NFT_TIERED_DROP } from "../../constants/erc721-features";
|
||||
import { GenericRequest } from "../../schema";
|
||||
import {
|
||||
TieredDropPayloadInput,
|
||||
TieredDropPayloadOutput,
|
||||
TieredDropPayloadSchema,
|
||||
TieredDropPayloadWithSignature,
|
||||
} from "../../schema/contracts/tiered-drop";
|
||||
import { UploadProgressEvent } from "../../types/events";
|
||||
import { DetectableFeature } from "../interfaces/DetectableFeature";
|
||||
import { TransactionResult, TransactionResultWithId } from "../types";
|
||||
import { ContractWrapper } from "./contract-wrapper";
|
||||
import { Erc721 } from "./erc-721";
|
||||
import type { TieredDrop, ISignatureAction } from "@thirdweb-dev/contracts-js";
|
||||
import {
|
||||
TokensLazyMintedEvent,
|
||||
TokensClaimedEvent,
|
||||
} from "@thirdweb-dev/contracts-js/dist/declarations/src/TieredDrop";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
import { BigNumberish, ethers } from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
export class Erc721TieredDrop implements DetectableFeature {
|
||||
featureName = FEATURE_NFT_TIERED_DROP.name;
|
||||
|
||||
private contractWrapper: ContractWrapper<TieredDrop>;
|
||||
private erc721: Erc721;
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
constructor(
|
||||
erc721: Erc721,
|
||||
contractWrapper: ContractWrapper<TieredDrop>,
|
||||
storage: ThirdwebStorage,
|
||||
) {
|
||||
this.erc721 = erc721;
|
||||
this.contractWrapper = contractWrapper;
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
public async getMetadataInTier(
|
||||
tier: string,
|
||||
): Promise<Omit<NFTMetadata, "id">[]> {
|
||||
const tiers =
|
||||
await this.contractWrapper.readContract.getMetadataForAllTiers();
|
||||
const batches = tiers.find((t) => t.tier === tier);
|
||||
|
||||
if (!batches) {
|
||||
throw new Error("Tier not found in contract.");
|
||||
}
|
||||
|
||||
const nfts = await Promise.all(
|
||||
batches.ranges
|
||||
.map((range, i) => {
|
||||
const nftsInRange = [];
|
||||
const baseUri = batches.baseURIs[i];
|
||||
for (
|
||||
let j = range.startIdInclusive.toNumber();
|
||||
j < range.endIdNonInclusive.toNumber();
|
||||
j++
|
||||
) {
|
||||
const uri = `${baseUri}/${j}`;
|
||||
const metadata = this.storage.downloadJSON(uri);
|
||||
nftsInRange.push(metadata);
|
||||
}
|
||||
|
||||
return nftsInRange;
|
||||
})
|
||||
.flat(),
|
||||
);
|
||||
|
||||
return nfts;
|
||||
}
|
||||
|
||||
public async getTokensInTier(tier: string): Promise<NFT[]> {
|
||||
const endIndex =
|
||||
await this.contractWrapper.readContract.getTokensInTierLen();
|
||||
if (endIndex.eq(0)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ranges = await this.contractWrapper.readContract.getTokensInTier(
|
||||
tier,
|
||||
0,
|
||||
endIndex,
|
||||
);
|
||||
|
||||
const nfts = await Promise.all(
|
||||
ranges
|
||||
.map((range) => {
|
||||
const nftsInRange = [];
|
||||
for (
|
||||
let i = range.startIdInclusive.toNumber();
|
||||
i < range.endIdNonInclusive.toNumber();
|
||||
i++
|
||||
) {
|
||||
nftsInRange.push(this.erc721.get(i));
|
||||
}
|
||||
return nftsInRange;
|
||||
})
|
||||
.flat(),
|
||||
);
|
||||
|
||||
return nfts;
|
||||
}
|
||||
|
||||
public async createBatchWithTier(
|
||||
metadatas: NFTMetadataOrUri[],
|
||||
tier: string,
|
||||
options?: {
|
||||
onProgress: (event: UploadProgressEvent) => void;
|
||||
},
|
||||
): Promise<TransactionResultWithId<NFTMetadata>[]> {
|
||||
// TODO: Change this to on extension
|
||||
const startFileNumber = await this.erc721.nextTokenIdToMint();
|
||||
const batch = await uploadOrExtractURIs(
|
||||
metadatas,
|
||||
this.storage,
|
||||
startFileNumber.toNumber(),
|
||||
options,
|
||||
);
|
||||
const baseUri = getBaseUriFromBatch(batch);
|
||||
|
||||
const receipt = await this.contractWrapper.sendTransaction("lazyMint", [
|
||||
batch.length,
|
||||
baseUri.endsWith("/") ? baseUri : `${baseUri}/`,
|
||||
tier,
|
||||
ethers.utils.toUtf8Bytes(""),
|
||||
]);
|
||||
|
||||
const event = this.contractWrapper.parseLogs<TokensLazyMintedEvent>(
|
||||
"TokensLazyMinted",
|
||||
receipt?.logs,
|
||||
);
|
||||
|
||||
const startingIndex = event[0].args[1];
|
||||
const endingIndex = event[0].args[2];
|
||||
const results: TransactionResultWithId<NFTMetadata>[] = [];
|
||||
for (let id = startingIndex; id.lte(endingIndex); id = id.add(1)) {
|
||||
results.push({
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc721.getTokenMetadata(id),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public async createDelayedRevealBatchWithTier(
|
||||
placeholder: NFTMetadataInput,
|
||||
metadatas: NFTMetadataInput[],
|
||||
password: string,
|
||||
tier: string,
|
||||
options?: {
|
||||
onProgress: (event: UploadProgressEvent) => void;
|
||||
},
|
||||
): Promise<TransactionResultWithId<NFTMetadata>[]> {
|
||||
if (!password) {
|
||||
throw new Error("Password is required");
|
||||
}
|
||||
|
||||
const placeholderUris = await this.storage.uploadBatch(
|
||||
[CommonNFTInput.parse(placeholder)],
|
||||
{
|
||||
rewriteFileNames: {
|
||||
fileStartNumber: 0,
|
||||
},
|
||||
},
|
||||
);
|
||||
const placeholderUri = getBaseUriFromBatch(placeholderUris);
|
||||
const startFileNumber = await this.erc721.nextTokenIdToMint();
|
||||
const uris = await this.storage.uploadBatch(
|
||||
metadatas.map((m) => CommonNFTInput.parse(m)),
|
||||
{
|
||||
onProgress: options?.onProgress,
|
||||
rewriteFileNames: {
|
||||
fileStartNumber: startFileNumber.toNumber(),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const baseUri = getBaseUriFromBatch(uris);
|
||||
const baseUriId = await this.contractWrapper.readContract.getBaseURICount();
|
||||
const chainId = await this.contractWrapper.getChainID();
|
||||
const hashedPassword = ethers.utils.solidityKeccak256(
|
||||
["string", "uint256", "uint256", "address"],
|
||||
[password, chainId, baseUriId, this.contractWrapper.readContract.address],
|
||||
);
|
||||
|
||||
const encryptedBaseUri =
|
||||
await this.contractWrapper.readContract.encryptDecrypt(
|
||||
ethers.utils.toUtf8Bytes(baseUri),
|
||||
hashedPassword,
|
||||
);
|
||||
|
||||
let data: string;
|
||||
const provenanceHash = ethers.utils.solidityKeccak256(
|
||||
["bytes", "bytes", "uint256"],
|
||||
[ethers.utils.toUtf8Bytes(baseUri), hashedPassword, chainId],
|
||||
);
|
||||
data = ethers.utils.defaultAbiCoder.encode(
|
||||
["bytes", "bytes32"],
|
||||
[encryptedBaseUri, provenanceHash],
|
||||
);
|
||||
|
||||
const receipt = await this.contractWrapper.sendTransaction("lazyMint", [
|
||||
uris.length,
|
||||
placeholderUri.endsWith("/") ? placeholderUri : `${placeholderUri}/`,
|
||||
tier,
|
||||
data,
|
||||
]);
|
||||
|
||||
const event = this.contractWrapper.parseLogs<TokensLazyMintedEvent>(
|
||||
"TokensLazyMinted",
|
||||
receipt?.logs,
|
||||
);
|
||||
const startingIndex = event[0].args[1];
|
||||
const endingIndex = event[0].args[2];
|
||||
const results: TransactionResultWithId<NFTMetadata>[] = [];
|
||||
for (let id = startingIndex; id.lte(endingIndex); id = id.add(1)) {
|
||||
results.push({
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc721.getTokenMetadata(id),
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public async reveal(
|
||||
batchId: BigNumberish,
|
||||
password: string,
|
||||
): Promise<TransactionResult> {
|
||||
if (!password) {
|
||||
throw new Error("Password is required");
|
||||
}
|
||||
const chainId = await this.contractWrapper.getChainID();
|
||||
const key = ethers.utils.solidityKeccak256(
|
||||
["string", "uint256", "uint256", "address"],
|
||||
[password, chainId, batchId, this.contractWrapper.readContract.address],
|
||||
);
|
||||
// performing the reveal locally to make sure it'd succeed before sending the transaction
|
||||
try {
|
||||
const decryptedUri = await this.contractWrapper
|
||||
.callStatic()
|
||||
.reveal(batchId, key);
|
||||
// basic sanity check for making sure decryptedUri is valid
|
||||
// this is optional because invalid decryption key would result in non-utf8 bytes and
|
||||
// ethers would throw when trying to decode it
|
||||
if (!decryptedUri.includes("://") || !decryptedUri.endsWith("/")) {
|
||||
throw new Error("invalid password");
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error("invalid password");
|
||||
}
|
||||
|
||||
return {
|
||||
receipt: await this.contractWrapper.sendTransaction("reveal", [
|
||||
batchId,
|
||||
key,
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
public async generate(
|
||||
payloadToSign: TieredDropPayloadInput,
|
||||
): Promise<TieredDropPayloadWithSignature> {
|
||||
const [payload] = await this.generateBatch([payloadToSign]);
|
||||
return payload;
|
||||
}
|
||||
|
||||
public async generateBatch(
|
||||
payloadsToSign: TieredDropPayloadInput[],
|
||||
): Promise<TieredDropPayloadWithSignature[]> {
|
||||
const parsedPayloads = payloadsToSign.map((payload) =>
|
||||
TieredDropPayloadSchema.parse(payload),
|
||||
);
|
||||
const chainId = await this.contractWrapper.getChainID();
|
||||
const signer = this.contractWrapper.getSigner();
|
||||
invariant(signer, "No signer available");
|
||||
|
||||
return await Promise.all(
|
||||
parsedPayloads.map(async (payload) => {
|
||||
const signature = await this.contractWrapper.signTypedData(
|
||||
signer,
|
||||
{
|
||||
name: "SignatureAction",
|
||||
version: "1",
|
||||
chainId,
|
||||
verifyingContract: this.contractWrapper.readContract.address,
|
||||
},
|
||||
{ GenericRequest: GenericRequest },
|
||||
await this.mapPayloadToContractStruct(payload),
|
||||
);
|
||||
|
||||
return { payload, signature: signature.toString() };
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async verify(
|
||||
signedPayload: TieredDropPayloadWithSignature,
|
||||
): Promise<boolean> {
|
||||
const message = await this.mapPayloadToContractStruct(
|
||||
signedPayload.payload,
|
||||
);
|
||||
const verification = await this.contractWrapper.readContract.verify(
|
||||
message,
|
||||
signedPayload.signature,
|
||||
);
|
||||
return verification[0];
|
||||
}
|
||||
|
||||
public async claimWithSignature(
|
||||
signedPayload: TieredDropPayloadWithSignature,
|
||||
): Promise<TransactionResultWithId<NFT>[]> {
|
||||
const message = await this.mapPayloadToContractStruct(
|
||||
signedPayload.payload,
|
||||
);
|
||||
const pricePerToken = await normalizePriceValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
signedPayload.payload.price,
|
||||
signedPayload.payload.currencyAddress,
|
||||
);
|
||||
const price = pricePerToken.mul(signedPayload.payload.quantity);
|
||||
|
||||
const overrides = await this.contractWrapper.getCallOverrides();
|
||||
await setErc20Allowance(
|
||||
this.contractWrapper,
|
||||
price,
|
||||
signedPayload.payload.currencyAddress,
|
||||
overrides,
|
||||
);
|
||||
|
||||
const receipt = await this.contractWrapper.sendTransaction(
|
||||
"claimWithSignature",
|
||||
[message, signedPayload.signature],
|
||||
overrides,
|
||||
);
|
||||
const event = this.contractWrapper.parseLogs<TokensClaimedEvent>(
|
||||
"TokensClaimed",
|
||||
receipt?.logs,
|
||||
);
|
||||
const startingIndex = event[0].args.startTokenId;
|
||||
const endingIndex = startingIndex.add(event[0].args.quantityClaimed);
|
||||
const results: TransactionResultWithId<NFT>[] = [];
|
||||
for (let id = startingIndex; id.lt(endingIndex); id = id.add(1)) {
|
||||
results.push({
|
||||
id,
|
||||
receipt,
|
||||
data: () => this.erc721.get(id),
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private async mapPayloadToContractStruct(
|
||||
payload: TieredDropPayloadOutput,
|
||||
): Promise<ISignatureAction.GenericRequestStruct> {
|
||||
const normalizedPricePerToken = await normalizePriceValue(
|
||||
this.contractWrapper.getProvider(),
|
||||
payload.price,
|
||||
payload.currencyAddress,
|
||||
);
|
||||
const data = ethers.utils.defaultAbiCoder.encode(
|
||||
[
|
||||
"string[]",
|
||||
"address",
|
||||
"address",
|
||||
"uint256",
|
||||
"address",
|
||||
"uint256",
|
||||
"uint256",
|
||||
"address",
|
||||
],
|
||||
[
|
||||
payload.tierPriority,
|
||||
payload.to,
|
||||
payload.royaltyRecipient,
|
||||
payload.royaltyBps,
|
||||
payload.primarySaleRecipient,
|
||||
payload.quantity,
|
||||
normalizedPricePerToken,
|
||||
payload.currencyAddress,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
uid: payload.uid,
|
||||
validityStartTimestamp: payload.mintStartTime,
|
||||
validityEndTimestamp: payload.mintEndTime,
|
||||
data,
|
||||
} as ISignatureAction.GenericRequestStruct;
|
||||
}
|
||||
}
|
||||
@@ -190,7 +190,7 @@ export class Erc721WithQuantitySignatureMintable implements DetectableFeature {
|
||||
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const payload = {
|
||||
* metadata: nftMetadata, // The NFT to mint
|
||||
* to: {{wallet_address}}, // Who will receive the NFT (or AddressZero for anyone)
|
||||
* to: {{wallet_address}}, // Who will receive the NFT
|
||||
* quantity: 2, // the quantity of NFTs to mint
|
||||
* price: 0.5, // the price per NFT
|
||||
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
|
||||
@@ -247,7 +247,7 @@ export class Erc721WithQuantitySignatureMintable implements DetectableFeature {
|
||||
* const endTime = new Date(Date.now() + 60 * 60 * 24 * 1000);
|
||||
* const payload = {
|
||||
* metadata: nftMetadata, // The NFT to mint
|
||||
* to: {{wallet_address}}, // Who will receive the NFT (or AddressZero for anyone)
|
||||
* to: {{wallet_address}}, // Who will receive the NFT
|
||||
* quantity: 2, // the quantity of NFTs to mint
|
||||
* price: 0.5, // the price per NFT
|
||||
* currencyAddress: NATIVE_TOKEN_ADDRESS, // the currency to pay with
|
||||
|
||||
@@ -13,12 +13,13 @@ import {
|
||||
FEATURE_NFT_BATCH_MINTABLE,
|
||||
FEATURE_NFT_BURNABLE,
|
||||
FEATURE_NFT_CLAIMABLE,
|
||||
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS,
|
||||
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2,
|
||||
FEATURE_NFT_LAZY_MINTABLE,
|
||||
FEATURE_NFT_MINTABLE,
|
||||
FEATURE_NFT_REVEALABLE,
|
||||
FEATURE_NFT_SIGNATURE_MINTABLE,
|
||||
FEATURE_NFT_SUPPLY,
|
||||
FEATURE_NFT_TIERED_DROP,
|
||||
} from "../../constants/erc721-features";
|
||||
import { BaseDropERC721, BaseERC721 } from "../../types/eips";
|
||||
import { ClaimOptions, UploadProgressEvent } from "../../types/index";
|
||||
@@ -34,6 +35,7 @@ import { Erc721Burnable } from "./erc-721-burnable";
|
||||
import { Erc721LazyMintable } from "./erc-721-lazymintable";
|
||||
import { Erc721Mintable } from "./erc-721-mintable";
|
||||
import { Erc721Supply } from "./erc-721-supply";
|
||||
import { Erc721TieredDrop } from "./erc-721-tiered-drop";
|
||||
import { Erc721WithQuantitySignatureMintable } from "./erc-721-with-quantity-signature-mintable";
|
||||
import type {
|
||||
DropERC721,
|
||||
@@ -43,6 +45,7 @@ import type {
|
||||
ISignatureMintERC721,
|
||||
Multiwrap,
|
||||
SignatureDrop,
|
||||
TieredDrop,
|
||||
TokenERC721,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
@@ -72,6 +75,7 @@ export class Erc721<
|
||||
private mintable: Erc721Mintable | undefined;
|
||||
private burnable: Erc721Burnable | undefined;
|
||||
private lazyMintable: Erc721LazyMintable | undefined;
|
||||
private tieredDropable: Erc721TieredDrop | undefined;
|
||||
private signatureMintable: Erc721WithQuantitySignatureMintable | undefined;
|
||||
protected contractWrapper: ContractWrapper<T>;
|
||||
protected storage: ThirdwebStorage;
|
||||
@@ -92,6 +96,7 @@ export class Erc721<
|
||||
this.mintable = this.detectErc721Mintable();
|
||||
this.burnable = this.detectErc721Burnable();
|
||||
this.lazyMintable = this.detectErc721LazyMintable();
|
||||
this.tieredDropable = this.detectErc721TieredDrop();
|
||||
this.signatureMintable = this.detectErc721SignatureMintable();
|
||||
this._chainId = chainId;
|
||||
}
|
||||
@@ -613,7 +618,7 @@ export class Erc721<
|
||||
const claimWithConditions = this.lazyMintable?.claimWithConditions;
|
||||
const claim = this.lazyMintable?.claim;
|
||||
if (claimWithConditions) {
|
||||
return claimWithConditions.getClaimTransaction(
|
||||
return claimWithConditions.conditions.getClaimTransaction(
|
||||
destinationAddress,
|
||||
quantity,
|
||||
options,
|
||||
@@ -682,10 +687,20 @@ export class Erc721<
|
||||
get claimConditions() {
|
||||
return assertEnabled(
|
||||
this.lazyMintable?.claimWithConditions,
|
||||
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS,
|
||||
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS_V2,
|
||||
).conditions;
|
||||
}
|
||||
|
||||
////// ERC721 Tiered Drop Extension //////
|
||||
|
||||
/**
|
||||
* Tiered Drop
|
||||
* @remarks Drop lazy minted NFTs using a tiered drop mechanism.
|
||||
*/
|
||||
get tieredDrop() {
|
||||
return assertEnabled(this.tieredDropable, FEATURE_NFT_TIERED_DROP);
|
||||
}
|
||||
|
||||
////// ERC721 SignatureMint Extension //////
|
||||
|
||||
/**
|
||||
@@ -828,6 +843,18 @@ export class Erc721<
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private detectErc721TieredDrop(): Erc721TieredDrop | undefined {
|
||||
if (
|
||||
detectContractFeature<TieredDrop>(
|
||||
this.contractWrapper,
|
||||
"ERC721TieredDrop",
|
||||
)
|
||||
) {
|
||||
return new Erc721TieredDrop(this, this.contractWrapper, this.storage);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private detectErc721SignatureMintable():
|
||||
| Erc721WithQuantitySignatureMintable
|
||||
| undefined {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { TransactionError } from "../../common";
|
||||
import { CONTRACT_ADDRESSES, SUPPORTED_CHAIN_IDS } from "../../constants";
|
||||
import {
|
||||
EditionDropInitializer,
|
||||
EditionInitializer,
|
||||
getContractName,
|
||||
MarketplaceInitializer,
|
||||
MultiwrapInitializer,
|
||||
NFTCollectionInitializer,
|
||||
@@ -41,6 +41,22 @@ import { z } from "zod";
|
||||
export class ContractFactory extends ContractWrapper<TWFactory> {
|
||||
private storage: ThirdwebStorage;
|
||||
|
||||
// Map from contract type to version to deploy specific versions by default
|
||||
private DEFAULT_VERSION_MAP: Record<PrebuiltContractType, number> = {
|
||||
[NFTDropInitializer.contractType]: 3,
|
||||
[NFTCollectionInitializer.contractType]: 1,
|
||||
[SignatureDropInitializer.contractType]: 4,
|
||||
[MultiwrapInitializer.contractType]: 1,
|
||||
[EditionDropInitializer.contractType]: 2,
|
||||
[EditionInitializer.contractType]: 1,
|
||||
[TokenDropInitializer.contractType]: 2,
|
||||
[TokenInitializer.contractType]: 1,
|
||||
[VoteInitializer.contractType]: 1,
|
||||
[SplitInitializer.contractType]: 1,
|
||||
[MarketplaceInitializer.contractType]: 2,
|
||||
[PackInitializer.contractType]: 2,
|
||||
};
|
||||
|
||||
constructor(
|
||||
factoryAddr: string,
|
||||
network: NetworkOrSignerOrProvider,
|
||||
@@ -56,6 +72,7 @@ export class ContractFactory extends ContractWrapper<TWFactory> {
|
||||
contractMetadata: z.input<
|
||||
DeploySchemaForPrebuiltContractType<TContractType>
|
||||
>,
|
||||
version?: number,
|
||||
): Promise<string> {
|
||||
const contract = PREBUILT_CONTRACTS_MAP[contractType];
|
||||
const metadata = contract.schema.deploy.parse(contractMetadata);
|
||||
@@ -63,42 +80,35 @@ export class ContractFactory extends ContractWrapper<TWFactory> {
|
||||
// TODO: is there any special pre-processing we need to do before uploading?
|
||||
const contractURI = await this.storage.upload(metadata);
|
||||
|
||||
const ABI = await contract.getAbi();
|
||||
const implementationAddress = await this.getImplementation(
|
||||
contract,
|
||||
version,
|
||||
);
|
||||
|
||||
if (
|
||||
!implementationAddress ||
|
||||
implementationAddress === constants.AddressZero
|
||||
) {
|
||||
throw new Error(`No implementation found for ${contractType}`);
|
||||
}
|
||||
|
||||
const ABI = await contract.getAbi(
|
||||
implementationAddress,
|
||||
this.getProvider(),
|
||||
);
|
||||
|
||||
const encodedFunc = Contract.getInterface(ABI).encodeFunctionData(
|
||||
"initialize",
|
||||
await this.getDeployArguments(contractType, metadata, contractURI),
|
||||
);
|
||||
|
||||
const encodedType = ethers.utils.formatBytes32String(contract.name);
|
||||
let receipt;
|
||||
try {
|
||||
receipt = await this.sendTransaction("deployProxy", [
|
||||
encodedType,
|
||||
encodedFunc,
|
||||
]);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
// if the error is caused by user cancelling the transaction, just re-throw it
|
||||
if (
|
||||
(e as TransactionError).message
|
||||
.toLowerCase()
|
||||
.includes("user rejected transaction") ||
|
||||
(e as TransactionError).reason
|
||||
.toLowerCase()
|
||||
.includes("user rejected transaction")
|
||||
) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// deploy might fail due to salt already used, fallback to deterministic deploy
|
||||
const blockNumber = await this.getProvider().getBlockNumber();
|
||||
receipt = await this.sendTransaction("deployProxyDeterministic", [
|
||||
encodedType,
|
||||
encodedFunc,
|
||||
ethers.utils.formatBytes32String(blockNumber.toString()),
|
||||
]);
|
||||
}
|
||||
const blockNumber = await this.getProvider().getBlockNumber();
|
||||
const salt = ethers.utils.formatBytes32String(blockNumber.toString());
|
||||
const receipt = await this.sendTransaction("deployProxyByImplementation", [
|
||||
implementationAddress,
|
||||
encodedFunc,
|
||||
salt,
|
||||
]);
|
||||
|
||||
const events = this.parseLogs<ProxyDeployedEvent>(
|
||||
"ProxyDeployed",
|
||||
@@ -285,4 +295,26 @@ export class ContractFactory extends ContractWrapper<TWFactory> {
|
||||
? [openzeppelinForwarder, biconomyForwarder]
|
||||
: [openzeppelinForwarder];
|
||||
}
|
||||
|
||||
private async getImplementation(
|
||||
contract: typeof PREBUILT_CONTRACTS_MAP[PrebuiltContractType],
|
||||
version?: number,
|
||||
) {
|
||||
const encodedType = ethers.utils.formatBytes32String(contract.name);
|
||||
return this.readContract.getImplementation(
|
||||
encodedType,
|
||||
version !== undefined
|
||||
? version
|
||||
: this.DEFAULT_VERSION_MAP[contract.contractType],
|
||||
);
|
||||
}
|
||||
|
||||
public async getLatestVersion(contractType: PrebuiltContractType) {
|
||||
const name = getContractName(contractType);
|
||||
if (!name) {
|
||||
throw new Error(`Invalid contract type ${contractType}`);
|
||||
}
|
||||
const encodedType = ethers.utils.formatBytes32String(name);
|
||||
return this.readContract.currentVersion(encodedType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +401,7 @@ export class ThirdwebSDK extends RPCConnectionHandler {
|
||||
// otherwise if it's a prebuilt contract we can just use the contract type
|
||||
const contractAbi = await PREBUILT_CONTRACTS_MAP[
|
||||
resolvedContractType
|
||||
].getAbi();
|
||||
].getAbi(address, this.getProvider());
|
||||
newContract = await this.getContractFromAbi(address, contractAbi);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,10 +139,31 @@ export class UserWallet {
|
||||
* const address = await sdk.wallet.getAddress();
|
||||
* ```
|
||||
*/
|
||||
async getAddress(): Promise<string> {
|
||||
public async getAddress(): Promise<string> {
|
||||
return await this.requireWallet().getAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently connected wallet's chainId
|
||||
* @internal
|
||||
*/
|
||||
public async getChainId(): Promise<number> {
|
||||
return await this.requireWallet().getChainId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether there's a signer connected with the SDK
|
||||
* @internal
|
||||
*/
|
||||
public isConnected(): boolean {
|
||||
try {
|
||||
this.requireWallet();
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign any message with the connected wallet private key
|
||||
* @param message - the message to sign
|
||||
@@ -157,7 +178,7 @@ export class UserWallet {
|
||||
* const signature = await sdk.wallet.sign(message);
|
||||
* ```
|
||||
*/
|
||||
async sign(message: string): Promise<string> {
|
||||
public async sign(message: string): Promise<string> {
|
||||
const signer = this.requireWallet();
|
||||
return await signer.signMessage(message);
|
||||
}
|
||||
@@ -187,7 +208,7 @@ export class UserWallet {
|
||||
* Send a raw transaction to the blockchain from the connected wallet
|
||||
* @param transactionRequest - raw transaction data to send to the blockchain
|
||||
*/
|
||||
async sendRawTransaction(
|
||||
public async sendRawTransaction(
|
||||
transactionRequest: providers.TransactionRequest,
|
||||
): Promise<TransactionResult> {
|
||||
const signer = this.requireWallet();
|
||||
|
||||
@@ -29,7 +29,7 @@ export * from "./constants";
|
||||
export * from "./contracts";
|
||||
|
||||
// export integration things
|
||||
export * from "./integrations/paper-xyz";
|
||||
export * from "./integrations/thirdweb-checkout";
|
||||
|
||||
// explcitly export the *TYPES* of prebuilt contracts
|
||||
export type { Edition } from "./contracts/prebuilt-implementations/edition";
|
||||
|
||||
+7
-8
@@ -1,10 +1,9 @@
|
||||
import { ChainId } from "../constants";
|
||||
import { ContractWrapper } from "../core/classes/contract-wrapper";
|
||||
import { SignedPayload721WithQuantitySignature } from "../schema/contracts/common/signature";
|
||||
import { DropERC721 } from "@thirdweb-dev/contracts-js/dist/declarations/src/DropERC721";
|
||||
import { DropERC1155 } from "@thirdweb-dev/contracts-js/dist/declarations/src/DropERC1155";
|
||||
import { PrebuiltEditionDrop, PrebuiltNFTDrop } from "../types/eips";
|
||||
import { SignatureDrop } from "@thirdweb-dev/contracts-js/dist/declarations/src/SignatureDrop";
|
||||
import fetch from "cross-fetch";
|
||||
// import fetch from "cross-fetch";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
const PAPER_API_BASE = `https://paper.xyz/api` as const;
|
||||
@@ -133,9 +132,9 @@ export type PaperCreateCheckoutLinkShardParams = {
|
||||
* @internal
|
||||
*/
|
||||
export type PaperCreateCheckoutLinkIntentParams<
|
||||
TContract extends DropERC721 | DropERC1155 | SignatureDrop,
|
||||
TContract extends PrebuiltNFTDrop | PrebuiltEditionDrop | SignatureDrop,
|
||||
> = PaperCreateCheckoutLinkShardParams &
|
||||
(TContract extends DropERC1155
|
||||
(TContract extends PrebuiltEditionDrop
|
||||
? {
|
||||
contractArgs: { tokenId: string };
|
||||
}
|
||||
@@ -143,7 +142,7 @@ export type PaperCreateCheckoutLinkIntentParams<
|
||||
? {
|
||||
contractArgs: SignedPayload721WithQuantitySignature;
|
||||
}
|
||||
: TContract extends DropERC721
|
||||
: TContract extends PrebuiltNFTDrop
|
||||
? {}
|
||||
: never);
|
||||
|
||||
@@ -165,7 +164,7 @@ const DEFAULT_PARAMS: Partial<PaperCreateCheckoutLinkShardParams> = {
|
||||
* @internal
|
||||
*/
|
||||
export async function createCheckoutLinkIntent<
|
||||
TContract extends DropERC721 | DropERC1155 | SignatureDrop,
|
||||
TContract extends PrebuiltNFTDrop | PrebuiltEditionDrop | SignatureDrop,
|
||||
>(
|
||||
contractId: string,
|
||||
params: PaperCreateCheckoutLinkIntentParams<TContract>,
|
||||
@@ -203,7 +202,7 @@ export async function createCheckoutLinkIntent<
|
||||
* @internal
|
||||
*/
|
||||
export class PaperCheckout<
|
||||
TContract extends DropERC721 | DropERC1155 | SignatureDrop,
|
||||
TContract extends PrebuiltNFTDrop | PrebuiltEditionDrop | SignatureDrop,
|
||||
> {
|
||||
private contractWrapper;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
import { AddressSchema, AmountSchema } from "../../shared";
|
||||
import { AmountSchema } from "../../../../core/schema/shared";
|
||||
import { AddressSchema } from "../../shared";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { QuantitySchema } from "../../../../core/schema/shared";
|
||||
import {
|
||||
AmountSchema,
|
||||
BytesLikeSchema,
|
||||
QuantitySchema,
|
||||
} from "../../../../core/schema/shared";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../../constants/currency";
|
||||
import {
|
||||
BigNumberishSchema,
|
||||
BigNumberSchema,
|
||||
BytesLikeSchema,
|
||||
AmountSchema,
|
||||
StartDateSchema,
|
||||
} from "../../shared";
|
||||
import { CurrencyValueSchema } from "./currency";
|
||||
import { SnapshotInputSchema } from "./snapshots";
|
||||
import { BigNumber, utils } from "ethers";
|
||||
import { BigNumber, BigNumberish, utils } from "ethers";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
@@ -19,11 +21,12 @@ export const ClaimConditionInputSchema = z.object({
|
||||
startTime: StartDateSchema,
|
||||
currencyAddress: z.string().default(NATIVE_TOKEN_ADDRESS),
|
||||
price: AmountSchema.default(0),
|
||||
maxQuantity: QuantitySchema,
|
||||
quantityLimitPerTransaction: QuantitySchema,
|
||||
maxClaimableSupply: QuantitySchema,
|
||||
maxClaimablePerWallet: QuantitySchema,
|
||||
waitInSeconds: BigNumberishSchema.default(0),
|
||||
merkleRootHash: BytesLikeSchema.default(utils.hexZeroPad([0], 32)),
|
||||
snapshot: z.optional(SnapshotInputSchema).nullable(),
|
||||
metadata: z.union([z.string(), z.object({})]).optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -54,4 +57,17 @@ 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 = {
|
||||
startTimestamp: BigNumberish;
|
||||
maxClaimableSupply: BigNumberish;
|
||||
supplyClaimed: BigNumberish;
|
||||
maxClaimablePerWallet: BigNumberish;
|
||||
merkleRoot: string;
|
||||
pricePerToken: BigNumberish;
|
||||
currency: string;
|
||||
waitTimeInSecondsBetweenClaims?: BigNumberish; // only in legacy claim conditions
|
||||
metadata?: string; // only for new claim conditions
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
AddressSchema,
|
||||
BasisPointsSchema,
|
||||
FileOrBufferOrStringSchema,
|
||||
} from "../../shared";
|
||||
} from "../../../../core/schema/shared";
|
||||
import { AddressSchema } from "../../shared";
|
||||
import { constants } from "ethers";
|
||||
import { z } from "zod";
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { NFTInputOrUriSchema } from "../../../../core/schema/nft";
|
||||
import {
|
||||
AmountSchema,
|
||||
BasisPointsSchema,
|
||||
} from "../../../../core/schema/shared";
|
||||
import { resolveOrGenerateId } from "../../../common/signature-minting";
|
||||
import { NATIVE_TOKEN_ADDRESS } from "../../../constants/currency";
|
||||
import {
|
||||
BasisPointsSchema,
|
||||
BigNumberishSchema,
|
||||
BigNumberSchema,
|
||||
EndDateSchema,
|
||||
AmountSchema,
|
||||
StartDateSchema,
|
||||
} from "../../shared";
|
||||
import { constants } from "ethers";
|
||||
@@ -16,7 +18,11 @@ import { z } from "zod";
|
||||
* @internal
|
||||
*/
|
||||
export const BaseSignaturePayloadInput = z.object({
|
||||
to: z.string().default(constants.AddressZero),
|
||||
to: z
|
||||
.string()
|
||||
.refine((address) => address.toLowerCase() !== constants.AddressZero, {
|
||||
message: "Cannot create payload to mint to zero address",
|
||||
}),
|
||||
price: AmountSchema.default(0),
|
||||
currencyAddress: z.string().default(NATIVE_TOKEN_ADDRESS),
|
||||
mintStartTime: StartDateSchema,
|
||||
@@ -255,3 +261,10 @@ export const MintRequest721withQuantity = [
|
||||
{ name: "validityEndTimestamp", type: "uint128" },
|
||||
{ name: "uid", type: "bytes32" },
|
||||
];
|
||||
|
||||
export const GenericRequest = [
|
||||
{ name: "validityStartTimestamp", type: "uint128" },
|
||||
{ name: "validityEndTimestamp", type: "uint128" },
|
||||
{ name: "uid", type: "bytes32" },
|
||||
{ name: "data", type: "bytes" },
|
||||
];
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { AddressSchema, AmountSchema } from "../../shared";
|
||||
import { QuantitySchema } from "../../../../core/schema/shared";
|
||||
import { AddressSchema } from "../../shared";
|
||||
import { ethers } from "ethers";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
@@ -8,12 +10,13 @@ export const MerkleSchema = z.object({
|
||||
merkle: z.record(z.string()).default({}),
|
||||
});
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const SnapshotEntryInput = z.object({
|
||||
address: AddressSchema,
|
||||
maxClaimable: AmountSchema.default(0),
|
||||
maxClaimable: QuantitySchema.default(0), // defaults to 0
|
||||
price: QuantitySchema.optional(), // defaults to unlimited, but can be undefined in old snapshots
|
||||
currencyAddress: AddressSchema.default(
|
||||
ethers.constants.AddressZero,
|
||||
).optional(), // defaults to AddressZero, but can be undefined for old snapshots
|
||||
});
|
||||
|
||||
export type SnapshotEntry = z.output<typeof SnapshotEntryInput>;
|
||||
@@ -49,7 +52,7 @@ export const SnapshotInputSchema = z.union([
|
||||
z.array(SnapshotEntryInput),
|
||||
]);
|
||||
|
||||
const SnapshotEntryWithProofSchema = SnapshotEntryInput.extend({
|
||||
export const SnapshotEntryWithProofSchema = SnapshotEntryInput.extend({
|
||||
proof: z.array(z.string()),
|
||||
});
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { FileOrBufferOrStringSchema } from "../../../core/schema/shared";
|
||||
import { toSemver } from "../../common/index";
|
||||
import {
|
||||
AddressSchema,
|
||||
BigNumberishSchema,
|
||||
BigNumberTransformSchema,
|
||||
FileOrBufferOrStringSchema,
|
||||
} from "../shared";
|
||||
import {
|
||||
CommonContractOutputSchema,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AddressSchema, BasisPointsSchema } from "../shared";
|
||||
import { BasisPointsSchema } from "../../../core/schema/shared";
|
||||
import { AddressSchema } from "../shared";
|
||||
import {
|
||||
CommonContractOutputSchema,
|
||||
CommonContractSchema,
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { BasisPointsSchema } from "../../../core/schema/shared";
|
||||
import { BigNumberSchema } from "../shared";
|
||||
import { BaseSignaturePayloadInput } from "./common";
|
||||
import { constants } from "ethers";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const TieredDropPayloadSchema = BaseSignaturePayloadInput.extend({
|
||||
tierPriority: z.array(z.string()),
|
||||
royaltyRecipient: z.string().default(constants.AddressZero),
|
||||
royaltyBps: BasisPointsSchema.default(0),
|
||||
quantity: BigNumberSchema.default(1),
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type TieredDropPayloadInput = z.input<typeof TieredDropPayloadSchema>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type TieredDropPayloadOutput = z.output<typeof TieredDropPayloadSchema>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type TieredDropPayloadWithSignature = {
|
||||
payload: TieredDropPayloadOutput;
|
||||
signature: string;
|
||||
};
|
||||
|
||||
export const GenericRequest = [
|
||||
{
|
||||
name: "validityStartTimestamp",
|
||||
type: "uint128",
|
||||
},
|
||||
{
|
||||
name: "validityEndTimestamp",
|
||||
type: "uint128",
|
||||
},
|
||||
{
|
||||
name: "uid",
|
||||
type: "bytes32",
|
||||
},
|
||||
{
|
||||
name: "data",
|
||||
type: "bytes",
|
||||
},
|
||||
];
|
||||
@@ -1,9 +1,5 @@
|
||||
import {
|
||||
AddressSchema,
|
||||
BigNumberishSchema,
|
||||
BigNumberSchema,
|
||||
PercentSchema,
|
||||
} from "../shared";
|
||||
import { PercentSchema } from "../../../core/schema/shared";
|
||||
import { AddressSchema, BigNumberishSchema, BigNumberSchema } from "../shared";
|
||||
import {
|
||||
CommonContractOutputSchema,
|
||||
CommonContractSchema,
|
||||
|
||||
@@ -1,34 +1,6 @@
|
||||
import { BigNumber, CallOverrides, utils } from "ethers";
|
||||
import { z } from "zod";
|
||||
|
||||
const isBrowser = () => typeof window !== "undefined";
|
||||
const FileOrBufferUnionSchema = isBrowser()
|
||||
? (z.instanceof(File) as z.ZodType<InstanceType<typeof File>>)
|
||||
: (z.instanceof(Buffer) as z.ZodTypeAny); // @fixme, this is a hack to make browser happy for now
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const FileOrBufferSchema = z.union([
|
||||
FileOrBufferUnionSchema,
|
||||
z.object({
|
||||
data: z.union([FileOrBufferUnionSchema, z.string()]),
|
||||
name: z.string(),
|
||||
}),
|
||||
]);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const FileOrBufferOrStringSchema = z.union([
|
||||
FileOrBufferSchema,
|
||||
z.string(),
|
||||
]);
|
||||
|
||||
export const MAX_BPS = 10000;
|
||||
|
||||
export const BytesLikeSchema = z.union([z.array(z.number()), z.string()]);
|
||||
|
||||
export const BigNumberSchema = z
|
||||
.union([
|
||||
z.string(),
|
||||
@@ -55,16 +27,6 @@ export const BigNumberTransformSchema = z
|
||||
return BigNumber.from(arg).toString();
|
||||
});
|
||||
|
||||
export const BasisPointsSchema = z
|
||||
.number()
|
||||
.max(MAX_BPS, "Cannot exceed 100%")
|
||||
.min(0, "Cannot be below 0%");
|
||||
|
||||
export const PercentSchema = z
|
||||
.number()
|
||||
.max(100, "Cannot exceed 100%")
|
||||
.min(0, "Cannot be below 0%");
|
||||
|
||||
export const AddressSchema = z.string().refine(
|
||||
(arg) => utils.isAddress(arg),
|
||||
(out) => {
|
||||
@@ -74,13 +36,6 @@ export const AddressSchema = z.string().refine(
|
||||
},
|
||||
);
|
||||
|
||||
export const AmountSchema = z
|
||||
.union([
|
||||
z.string().regex(/^([0-9]+\.?[0-9]*|\.[0-9]+)$/, "Invalid amount"),
|
||||
z.number().min(0, "Amount cannot be negative"),
|
||||
])
|
||||
.transform((arg) => (typeof arg === "number" ? arg.toString() : arg));
|
||||
|
||||
export const RawDateSchema = z.date().transform((i) => {
|
||||
return BigNumber.from(Math.floor(i.getTime() / 1000));
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AddressSchema, BigNumberishSchema, AmountSchema } from "../../shared";
|
||||
import { AmountSchema } from "../../../../core/schema/shared";
|
||||
import { AddressSchema, BigNumberishSchema } from "../../shared";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NFTInputOrUriSchema } from "../../../core/schema/nft";
|
||||
import { BigNumberishSchema, AmountSchema, RawDateSchema } from "../shared";
|
||||
import { AmountSchema } from "../../../core/schema/shared";
|
||||
import { BigNumberishSchema, RawDateSchema } from "../shared";
|
||||
import {
|
||||
ERC1155WrappableSchema,
|
||||
ERC20WrappableSchema,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AddressSchema, AmountSchema } from "../shared";
|
||||
import { AmountSchema } from "../../../core/schema/shared";
|
||||
import { AddressSchema } from "../shared";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,7 +53,7 @@ export type FilledConditionInput = z.output<typeof ClaimConditionInputSchema>;
|
||||
export type ClaimVerification = {
|
||||
overrides: CallOverrides;
|
||||
proofs: BytesLike[];
|
||||
maxQuantityPerTransaction: BigNumber;
|
||||
maxClaimable: BigNumber;
|
||||
price: BigNumber;
|
||||
currencyAddress: string;
|
||||
};
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
* Currency metadata.
|
||||
* @public
|
||||
*/
|
||||
import { AmountSchema } from "../../core/schema/shared";
|
||||
import {
|
||||
CurrencySchema,
|
||||
CurrencyValueSchema,
|
||||
} from "../schema/contracts/common/currency";
|
||||
import { AmountSchema } from "../schema/shared";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,12 @@ import type {
|
||||
ISignatureMintERC721,
|
||||
LazyMint,
|
||||
DropSinglePhase1155,
|
||||
DropERC1155,
|
||||
DropERC20_V2,
|
||||
DropERC20,
|
||||
DropERC721,
|
||||
DropERC721_V3,
|
||||
DropERC1155_V2,
|
||||
} from "@thirdweb-dev/contracts-js";
|
||||
|
||||
export type BaseERC20 = IERC20 & IERC20Metadata;
|
||||
@@ -28,3 +34,7 @@ export type BaseSignatureMintERC721 = BaseERC721 & ISignatureMintERC721;
|
||||
export type BaseSignatureMintERC20 = BaseERC20 & ISignatureMintERC20;
|
||||
export type BaseSignatureMintERC1155 = BaseERC1155 & ISignatureMintERC1155;
|
||||
export type BaseDelayedRevealERC1155 = BaseDropERC1155 & DelayedReveal;
|
||||
|
||||
export type PrebuiltTokenDrop = DropERC20 | DropERC20_V2;
|
||||
export type PrebuiltNFTDrop = DropERC721 | DropERC721_V3;
|
||||
export type PrebuiltEditionDrop = DropERC1155 | DropERC1155_V2;
|
||||
|
||||
@@ -37,8 +37,11 @@ export type ContractEvent<TEvent = Record<string, any>> = {
|
||||
/**
|
||||
* Filters for querying past events
|
||||
*/
|
||||
export interface EventQueryFilter {
|
||||
export interface EventQueryOptions<
|
||||
TFilter extends Record<string, any> = Record<string, any>,
|
||||
> {
|
||||
fromBlock?: string | number;
|
||||
toBlock?: string | number;
|
||||
order?: "asc" | "desc";
|
||||
filters?: TFilter;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { QueryAllParams } from "../../core/schema/QueryParams";
|
||||
import { NFT, NFTMetadata, NFTMetadataOrUri } from "../../core/schema/nft";
|
||||
import { Amount } from "../../core/schema/shared";
|
||||
import {
|
||||
enforceCreator,
|
||||
parseCreators,
|
||||
} from "../classes/helpers/creators-helper";
|
||||
import { NFTHelper } from "../classes/helpers/nft-helper";
|
||||
import { Amount, TransactionResult } from "../types/common";
|
||||
import { TransactionResult } from "../types/common";
|
||||
import { CreatorInput, CreatorOutput } from "../types/programs";
|
||||
import { sendMultipartTransaction } from "../utils/transactions";
|
||||
import { getNework } from "../utils/urls";
|
||||
|
||||
@@ -4,13 +4,14 @@ import {
|
||||
NFTMetadata,
|
||||
NFTMetadataInput,
|
||||
} from "../../core/schema/nft";
|
||||
import { Amount } from "../../core/schema/shared";
|
||||
import { ClaimConditions } from "../classes/claim-conditions";
|
||||
import {
|
||||
enforceCreator,
|
||||
parseCreators,
|
||||
} from "../classes/helpers/creators-helper";
|
||||
import { NFTHelper } from "../classes/helpers/nft-helper";
|
||||
import { Amount, TransactionResult } from "../types/common";
|
||||
import { TransactionResult } from "../types/common";
|
||||
import { CreatorInput, CreatorOutput } from "../types/programs";
|
||||
import { sendMultipartTransaction } from "../utils/transactions";
|
||||
import { getNework } from "../utils/urls";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Amount, AmountSchema } from "../../core/schema/shared";
|
||||
import { CurrencyValue, TokenMetadata } from "../../core/schema/token";
|
||||
import { Amount, AmountSchema, TransactionResult } from "../types/common";
|
||||
import { TransactionResult } from "../types/common";
|
||||
import { toCurrencyValue } from "../utils/token";
|
||||
import { getNework } from "../utils/urls";
|
||||
import {
|
||||
|
||||
@@ -2,52 +2,6 @@ import { Signer, WalletAdapter } from "@metaplex-foundation/js";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const MAX_BPS = 10000;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const BasisPointsSchema = z
|
||||
.number()
|
||||
.max(MAX_BPS, "Cannot exceed 100%")
|
||||
.min(0, "Cannot be below 0%");
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const PercentSchema = z
|
||||
.number()
|
||||
.max(100, "Cannot exceed 100%")
|
||||
.min(0, "Cannot be below 0%");
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const JsonLiteral = z.union([
|
||||
z.string(),
|
||||
z.number(),
|
||||
z.boolean(),
|
||||
z.null(),
|
||||
]);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type JsonLiteral = number | string | null | boolean;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type Json = JsonLiteral | JsonObject | Json[];
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type JsonObject = { [key: string]: Json };
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -60,16 +14,6 @@ export const OptionalPropertiesInput = z
|
||||
.union([z.array(PropertiesInput), PropertiesInput])
|
||||
.optional();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const AmountSchema = z
|
||||
.union([
|
||||
z.string().regex(/^([0-9]+\.?[0-9]*|\.[0-9]+)$/, "Invalid amount"),
|
||||
z.number().min(0, "Amount cannot be negative"),
|
||||
])
|
||||
.transform((arg) => (typeof arg === "number" ? arg.toString() : arg));
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -77,11 +21,6 @@ export const RawDateSchema = z.date().transform((i) => {
|
||||
return Math.floor(i.getTime() / 1000);
|
||||
});
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type Amount = z.input<typeof AmountSchema>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -115,27 +54,3 @@ export const AddressSchema = z.union([
|
||||
z.string(),
|
||||
z.instanceof(PublicKey).transform((key) => key.toBase58()),
|
||||
]);
|
||||
|
||||
const isBrowser = () => typeof window !== "undefined";
|
||||
const FileOrBufferUnionSchema = isBrowser()
|
||||
? (z.instanceof(File) as z.ZodType<InstanceType<typeof File>>)
|
||||
: (z.instanceof(Buffer) as z.ZodTypeAny); // @fixme, this is a hack to make browser happy for now
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const FileOrBufferSchema = z.union([
|
||||
FileOrBufferUnionSchema,
|
||||
z.object({
|
||||
data: z.union([FileOrBufferUnionSchema, z.string()]),
|
||||
name: z.string(),
|
||||
}),
|
||||
]);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const FileOrBufferOrStringSchema = z.union([
|
||||
FileOrBufferSchema,
|
||||
z.string(),
|
||||
]);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { AmountSchema, FileOrBufferOrStringSchema, PercentSchema } from "../common";
|
||||
import {
|
||||
AmountSchema,
|
||||
FileOrBufferOrStringSchema,
|
||||
PercentSchema,
|
||||
} from "../../../core/schema/shared";
|
||||
import { Creator } from "@metaplex-foundation/js";
|
||||
import { z } from "zod";
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { NFTCollectionMetadataInputSchema } from ".";
|
||||
import { BasisPointsSchema, QuantitySchema } from "../../../core/schema/shared";
|
||||
import {
|
||||
AmountSchema,
|
||||
BasisPointsSchema,
|
||||
QuantitySchema,
|
||||
} from "../../../core/schema/shared";
|
||||
import { CurrencyValueSchema } from "../../../core/schema/token";
|
||||
import { AmountSchema } from "../common";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,14 +24,18 @@ import {
|
||||
ContractPublisher,
|
||||
ContractPublisher__factory,
|
||||
DropERC1155__factory,
|
||||
DropERC1155_V2__factory,
|
||||
DropERC20__factory,
|
||||
DropERC20_V2__factory,
|
||||
DropERC721__factory,
|
||||
DropERC721_V3__factory,
|
||||
Marketplace__factory,
|
||||
MockContractPublisher,
|
||||
MockContractPublisher__factory,
|
||||
Multiwrap__factory,
|
||||
Pack__factory,
|
||||
SignatureDrop__factory,
|
||||
SignatureDrop_V4__factory,
|
||||
Split__factory,
|
||||
TokenERC1155__factory,
|
||||
TokenERC20__factory,
|
||||
@@ -154,64 +158,67 @@ export const mochaHooks = {
|
||||
if (contractType === "custom") {
|
||||
continue;
|
||||
}
|
||||
let factory;
|
||||
let factories = [];
|
||||
switch (contractType) {
|
||||
case TokenInitializer.contractType:
|
||||
factory = TokenERC20__factory;
|
||||
factories.push(TokenERC20__factory);
|
||||
break;
|
||||
case TokenDropInitializer.contractType:
|
||||
factory = DropERC20__factory;
|
||||
factories.push(DropERC20_V2__factory, DropERC20__factory);
|
||||
break;
|
||||
case NFTCollectionInitializer.contractType:
|
||||
factory = TokenERC721__factory;
|
||||
factories.push(TokenERC721__factory);
|
||||
break;
|
||||
case NFTDropInitializer.contractType:
|
||||
factory = DropERC721__factory;
|
||||
factories.push(DropERC721_V3__factory, DropERC721__factory);
|
||||
break;
|
||||
case SignatureDropInitializer.contractType:
|
||||
factory = SignatureDrop__factory;
|
||||
factories.push(SignatureDrop_V4__factory, SignatureDrop__factory);
|
||||
break;
|
||||
case EditionInitializer.contractType:
|
||||
factory = TokenERC1155__factory;
|
||||
factories.push(TokenERC1155__factory);
|
||||
break;
|
||||
case EditionDropInitializer.contractType:
|
||||
factory = DropERC1155__factory;
|
||||
factories.push(DropERC1155_V2__factory, DropERC1155__factory);
|
||||
break;
|
||||
case SplitInitializer.contractType:
|
||||
factory = Split__factory;
|
||||
factories.push(Split__factory);
|
||||
break;
|
||||
case VoteInitializer.contractType:
|
||||
factory = VoteERC20__factory;
|
||||
factories.push(VoteERC20__factory);
|
||||
break;
|
||||
case MarketplaceInitializer.contractType:
|
||||
factory = Marketplace__factory;
|
||||
factories.push(Marketplace__factory);
|
||||
break;
|
||||
case PackInitializer.contractType:
|
||||
factory = Pack__factory;
|
||||
factories.push(Pack__factory);
|
||||
break;
|
||||
case MultiwrapInitializer.contractType:
|
||||
factory = Multiwrap__factory;
|
||||
factories.push(Multiwrap__factory);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`No factory for contract: ${contractType}`);
|
||||
}
|
||||
|
||||
const contractFactory = new ethers.ContractFactory(
|
||||
factory.abi,
|
||||
factory.bytecode,
|
||||
).connect(signer);
|
||||
for (const factory of factories) {
|
||||
const contractFactory = new ethers.ContractFactory(
|
||||
factory.abi,
|
||||
factory.bytecode,
|
||||
).connect(signer);
|
||||
|
||||
const deployedContract: ethers.Contract = await deployContract(
|
||||
contractFactory,
|
||||
contractType as ContractType,
|
||||
);
|
||||
const deployedContract: ethers.Contract = await deployContract(
|
||||
contractFactory,
|
||||
contractType as ContractType,
|
||||
);
|
||||
|
||||
await deployedContract.deployed();
|
||||
const tx = await thirdwebFactoryDeployer.addImplementation(
|
||||
deployedContract.address,
|
||||
);
|
||||
await tx.wait();
|
||||
implementations[contractType as ContractType] = deployedContract.address;
|
||||
await deployedContract.deployed();
|
||||
const tx = await thirdwebFactoryDeployer.addImplementation(
|
||||
deployedContract.address,
|
||||
);
|
||||
await tx.wait();
|
||||
implementations[contractType as ContractType] =
|
||||
deployedContract.address;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
|
||||
@@ -0,0 +1,818 @@
|
||||
import {
|
||||
ClaimEligibility,
|
||||
EditionDrop,
|
||||
EditionDropInitializer,
|
||||
NATIVE_TOKEN_ADDRESS,
|
||||
TokenInitializer,
|
||||
} from "../../src/evm";
|
||||
import { expectError, sdk, signers, storage } from "./before-setup";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
|
||||
import { DropERC1155_V2__factory } from "@thirdweb-dev/contracts-js";
|
||||
import { assert, expect, use } from "chai";
|
||||
import { BigNumber, ethers } from "ethers";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
global.fetch = require("cross-fetch");
|
||||
|
||||
const deepEqualInAnyOrder = require("deep-equal-in-any-order");
|
||||
|
||||
use(deepEqualInAnyOrder);
|
||||
|
||||
describe("Edition Drop Contract (V2)", async () => {
|
||||
let bdContract: EditionDrop;
|
||||
let adminWallet: SignerWithAddress,
|
||||
samWallet: SignerWithAddress,
|
||||
abbyWallet: SignerWithAddress,
|
||||
bobWallet: SignerWithAddress,
|
||||
w1: SignerWithAddress,
|
||||
w2: SignerWithAddress,
|
||||
w3: SignerWithAddress,
|
||||
w4: SignerWithAddress;
|
||||
|
||||
before(() => {
|
||||
[adminWallet, samWallet, bobWallet, abbyWallet, w1, w2, w3, w4] = signers;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
sdk.updateSignerOrProvider(adminWallet);
|
||||
const address = await sdk.deployer.deployBuiltInContract(
|
||||
EditionDropInitializer.contractType,
|
||||
{
|
||||
name: `Testing edition drop from SDK`,
|
||||
description: "Test contract from tests",
|
||||
image:
|
||||
"https://pbs.twimg.com/profile_images/1433508973215367176/XBCfBn3g_400x400.jpg",
|
||||
primary_sale_recipient: adminWallet.address,
|
||||
seller_fee_basis_points: 500,
|
||||
fee_recipient: AddressZero,
|
||||
platform_fee_basis_points: 10,
|
||||
platform_fee_recipient: adminWallet.address,
|
||||
},
|
||||
2,
|
||||
);
|
||||
bdContract = await sdk.getEditionDrop(address);
|
||||
});
|
||||
|
||||
it("should estimate gas cost", async () => {
|
||||
const cost = await bdContract.estimator.gasCostOf("lazyMint", [
|
||||
1000,
|
||||
"mock://12398172398172389/0",
|
||||
]);
|
||||
expect(parseFloat(cost)).gt(0);
|
||||
});
|
||||
|
||||
it("comprehensive test", async () => {
|
||||
const metadata = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
metadata.push({ name: `test${i}`, description: `desc${i}` });
|
||||
}
|
||||
await bdContract.createBatch(metadata);
|
||||
// claiming with default conditions
|
||||
await bdContract.claimConditions.set(0, [{}]);
|
||||
await bdContract.claim(0, 1);
|
||||
// claiming with max supply
|
||||
await bdContract.claimConditions.set(0, [
|
||||
{
|
||||
maxClaimableSupply: 2,
|
||||
},
|
||||
]);
|
||||
try {
|
||||
await bdContract.claim(0, 2);
|
||||
expect.fail();
|
||||
} catch (e) {
|
||||
expectError(e, "exceed max mint supply");
|
||||
}
|
||||
await bdContract.claim(0, 1);
|
||||
// claiming with max per wallet
|
||||
await bdContract.claimConditions.set(0, [
|
||||
{
|
||||
maxClaimablePerWallet: 1,
|
||||
},
|
||||
]);
|
||||
try {
|
||||
await bdContract.claim(0, 2);
|
||||
expect.fail();
|
||||
} catch (e) {
|
||||
expectError(e, "invalid quantity claimed");
|
||||
}
|
||||
await bdContract.claim(0, 1);
|
||||
expect((await bdContract.totalSupply(0)).toString()).eq("3");
|
||||
});
|
||||
|
||||
it("comprehensive test with allowlist", async () => {
|
||||
const metadata = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
metadata.push({ name: `test${i}`, description: `desc${i}` });
|
||||
}
|
||||
await bdContract.createBatch(metadata);
|
||||
// claiming with default conditions
|
||||
await bdContract.claimConditions.set(0, [
|
||||
{
|
||||
snapshot: [adminWallet.address],
|
||||
},
|
||||
]);
|
||||
try {
|
||||
sdk.updateSignerOrProvider(bobWallet);
|
||||
await bdContract.claim(0, 1);
|
||||
expect.fail();
|
||||
} catch (e) {
|
||||
expectError(e, "No claim found");
|
||||
}
|
||||
sdk.updateSignerOrProvider(adminWallet);
|
||||
await bdContract.claim(0, 1);
|
||||
// claiming with max supply
|
||||
await bdContract.claimConditions.set(1, [
|
||||
{
|
||||
snapshot: [adminWallet.address],
|
||||
maxClaimableSupply: 1,
|
||||
},
|
||||
]);
|
||||
try {
|
||||
await bdContract.claim(1, 2);
|
||||
expect.fail();
|
||||
} catch (e) {
|
||||
expectError(e, "exceed max mint supply");
|
||||
}
|
||||
await bdContract.claim(1, 1);
|
||||
// claiming with max per wallet
|
||||
await bdContract.claimConditions.set(2, [
|
||||
{
|
||||
snapshot: [adminWallet.address],
|
||||
maxClaimablePerWallet: 1,
|
||||
},
|
||||
]);
|
||||
try {
|
||||
await bdContract.claim(2, 2);
|
||||
expect.fail();
|
||||
} catch (e) {
|
||||
expectError(e, "invalid quantity claimed");
|
||||
}
|
||||
await bdContract.claim(2, 1);
|
||||
// claiming with max per wallet in snapshot
|
||||
await bdContract.claimConditions.set(3, [
|
||||
{
|
||||
snapshot: [{ address: adminWallet.address, maxClaimable: 1 }],
|
||||
maxClaimablePerWallet: 0,
|
||||
},
|
||||
]);
|
||||
try {
|
||||
await bdContract.claim(3, 2);
|
||||
expect.fail();
|
||||
} catch (e) {
|
||||
expectError(e, "invalid quantity proof");
|
||||
}
|
||||
await bdContract.claim(3, 1);
|
||||
try {
|
||||
await bdContract.claim(3, 1);
|
||||
expect.fail();
|
||||
} catch (e) {
|
||||
expectError(e, "proof claimed");
|
||||
}
|
||||
});
|
||||
|
||||
it("should allow you to set claim conditions", async () => {
|
||||
await bdContract.createBatch([
|
||||
{ name: "test", description: "test" },
|
||||
{ name: "test", description: "test" },
|
||||
]);
|
||||
await bdContract.claimConditions.set(BigNumber.from("0"), [{}]);
|
||||
const conditions = await bdContract.claimConditions.getAll(0);
|
||||
assert.lengthOf(conditions, 1);
|
||||
});
|
||||
|
||||
it("should get all", async () => {
|
||||
await bdContract.createBatch([
|
||||
{ name: "test", description: "test" },
|
||||
{ name: "test", description: "test" },
|
||||
]);
|
||||
const all = await bdContract.getAll();
|
||||
expect(all.length).to.eq(2);
|
||||
});
|
||||
|
||||
it("allow all addresses in the merkle tree to claim", async () => {
|
||||
await bdContract.createBatch([
|
||||
{
|
||||
name: "test",
|
||||
description: "test",
|
||||
},
|
||||
]);
|
||||
|
||||
const testWallets: SignerWithAddress[] = [
|
||||
bobWallet,
|
||||
samWallet,
|
||||
abbyWallet,
|
||||
w1,
|
||||
w2,
|
||||
w3,
|
||||
w4,
|
||||
];
|
||||
const members = testWallets.map((w) => w.address);
|
||||
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxClaimableSupply: 1000,
|
||||
snapshot: members,
|
||||
},
|
||||
]);
|
||||
|
||||
for (const member of testWallets) {
|
||||
await sdk.updateSignerOrProvider(member);
|
||||
await bdContract.claim("0", 1);
|
||||
}
|
||||
const bundle = await bdContract.get("0");
|
||||
assert(bundle.supply === testWallets.length);
|
||||
|
||||
const claimers = await bdContract.history.getAllClaimerAddresses("0");
|
||||
expect(claimers.length).to.eq(testWallets.length);
|
||||
expect(claimers).to.include(bobWallet.address);
|
||||
});
|
||||
|
||||
it("allow all addresses in the merkle tree to claim using useSnapshot", async () => {
|
||||
await bdContract.createBatch([
|
||||
{
|
||||
name: "test",
|
||||
description: "test",
|
||||
},
|
||||
]);
|
||||
|
||||
const testWallets: SignerWithAddress[] = [
|
||||
bobWallet,
|
||||
samWallet,
|
||||
abbyWallet,
|
||||
w1,
|
||||
w2,
|
||||
w3,
|
||||
];
|
||||
const members = testWallets.map((w) => w.address);
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxClaimableSupply: 1000,
|
||||
snapshot: members,
|
||||
},
|
||||
]);
|
||||
testWallets.push(w4);
|
||||
|
||||
for (const member of testWallets) {
|
||||
try {
|
||||
sdk.updateSignerOrProvider(member);
|
||||
await bdContract.claim("0", 1);
|
||||
} catch (e) {
|
||||
if (member !== w4) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
const bundle = await bdContract.get("0");
|
||||
assert(bundle.supply === testWallets.length - 1);
|
||||
});
|
||||
|
||||
it("should return the newly minted tokens", async () => {
|
||||
const tokens = [
|
||||
{
|
||||
name: "test 0",
|
||||
},
|
||||
{
|
||||
name: "test 1",
|
||||
},
|
||||
{
|
||||
name: "test 2",
|
||||
},
|
||||
{
|
||||
name: "test 3",
|
||||
},
|
||||
{
|
||||
name: "test 4",
|
||||
},
|
||||
];
|
||||
const result = await bdContract.createBatch(tokens);
|
||||
assert.lengthOf(result, tokens.length);
|
||||
for (const token of tokens) {
|
||||
const found = result.find(
|
||||
async (t) => (await t.data()).name === token.name,
|
||||
);
|
||||
assert.isDefined(found);
|
||||
}
|
||||
|
||||
const moreTokens = [
|
||||
{
|
||||
name: "test 5",
|
||||
},
|
||||
{
|
||||
name: "test 6",
|
||||
},
|
||||
];
|
||||
const moreResult = await bdContract.createBatch(moreTokens);
|
||||
assert.lengthOf(moreResult, moreTokens.length);
|
||||
for (const token of moreTokens) {
|
||||
const found = moreResult.find(
|
||||
async (t) => (await t.data()).name === token.name,
|
||||
);
|
||||
assert.isDefined(found);
|
||||
}
|
||||
});
|
||||
|
||||
it("should allow setting max claims per wallet", async () => {
|
||||
await bdContract.createBatch([
|
||||
{ name: "name", description: "description" },
|
||||
]);
|
||||
await bdContract.claimConditions.set(0, [
|
||||
{
|
||||
snapshot: [
|
||||
{ address: w1.address, maxClaimable: 2 },
|
||||
{ address: w2.address, maxClaimable: 1 },
|
||||
],
|
||||
},
|
||||
]);
|
||||
await sdk.updateSignerOrProvider(w1);
|
||||
await bdContract.claim(0, 2);
|
||||
try {
|
||||
await sdk.updateSignerOrProvider(w2);
|
||||
await bdContract.claim(0, 2);
|
||||
} catch (e) {
|
||||
expectError(e, "invalid quantity proof");
|
||||
}
|
||||
});
|
||||
|
||||
it("should allow a default claim condition to be used to claim", async () => {
|
||||
await bdContract.createBatch([
|
||||
{
|
||||
name: "test 0",
|
||||
},
|
||||
{
|
||||
name: "test 1",
|
||||
},
|
||||
{
|
||||
name: "test 2",
|
||||
},
|
||||
]);
|
||||
|
||||
await bdContract.claimConditions.set("0", [{}]);
|
||||
await bdContract.claim("0", 1);
|
||||
});
|
||||
|
||||
it("should return addresses of all the claimers", async () => {
|
||||
await bdContract.createBatch([
|
||||
{
|
||||
name: "test 0",
|
||||
},
|
||||
{
|
||||
name: "test 1",
|
||||
},
|
||||
]);
|
||||
|
||||
await bdContract.claimConditions.set("0", [{}]);
|
||||
await bdContract.claimConditions.set("1", [{}]);
|
||||
await bdContract.claim("0", 1);
|
||||
|
||||
await sdk.updateSignerOrProvider(samWallet);
|
||||
await bdContract.claim("0", 1);
|
||||
|
||||
// TODO some asserts
|
||||
// const claimers = await bdContract.getAllClaimerAddresses("0");
|
||||
// expect(claimers).to.deep.equalInAnyOrder([
|
||||
// samWallet.address,
|
||||
// adminWallet.address,
|
||||
// ]);
|
||||
|
||||
await sdk.updateSignerOrProvider(w1);
|
||||
await bdContract.claim("1", 1);
|
||||
await sdk.updateSignerOrProvider(w2);
|
||||
await bdContract.claim("1", 1);
|
||||
|
||||
const ownedW1 = await bdContract.getOwned(w1.address);
|
||||
assert(ownedW1.length === 1);
|
||||
const ownedW2 = await bdContract.getOwned(w2.address);
|
||||
assert(ownedW2.length === 1);
|
||||
});
|
||||
|
||||
it("should return the correct status if a token can be claimed", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
snapshot: [w1.address],
|
||||
},
|
||||
]);
|
||||
|
||||
await sdk.updateSignerOrProvider(w1);
|
||||
const canClaimW1 = await bdContract.claimConditions.canClaim("0", 1);
|
||||
assert.isTrue(canClaimW1, "w1 should be able to claim");
|
||||
|
||||
await sdk.updateSignerOrProvider(w2);
|
||||
const canClaimW2 = await bdContract.claimConditions.canClaim("0", 1);
|
||||
assert.isFalse(canClaimW2, "w2 should not be able to claim");
|
||||
});
|
||||
|
||||
it("Platform fees", async () => {
|
||||
const fees = await bdContract.platformFees.get();
|
||||
expect(fees.platform_fee_recipient).to.eq(adminWallet.address);
|
||||
expect(fees.platform_fee_basis_points).to.eq(10);
|
||||
|
||||
await bdContract.platformFees.set({
|
||||
platform_fee_recipient: samWallet.address,
|
||||
platform_fee_basis_points: 500,
|
||||
});
|
||||
const fees2 = await bdContract.platformFees.get();
|
||||
expect(fees2.platform_fee_recipient).to.eq(samWallet.address);
|
||||
expect(fees2.platform_fee_basis_points).to.eq(500);
|
||||
});
|
||||
|
||||
it("should allow custom overrides", async () => {
|
||||
bdContract.interceptor.overrideNextTransaction(() => ({
|
||||
nonce: 5000,
|
||||
}));
|
||||
try {
|
||||
await bdContract.createBatch([
|
||||
{
|
||||
name: "test",
|
||||
description: "test",
|
||||
},
|
||||
]);
|
||||
} catch (e) {
|
||||
expectError(e, "Expected nonce to be");
|
||||
}
|
||||
});
|
||||
|
||||
it("canClaim: 1 address", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
snapshot: [w1.address],
|
||||
},
|
||||
]);
|
||||
|
||||
assert.isTrue(
|
||||
await bdContract.claimConditions.canClaim("0", 1, w1.address),
|
||||
"can claim",
|
||||
);
|
||||
assert.isFalse(
|
||||
await bdContract.claimConditions.canClaim("0", 1, w2.address),
|
||||
"!can claim",
|
||||
);
|
||||
});
|
||||
|
||||
it("canClaim: 3 address", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
snapshot: [
|
||||
w1.address.toUpperCase().replace("0X", "0x"),
|
||||
w2.address.toLowerCase(),
|
||||
w3.address,
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
assert.isTrue(
|
||||
await bdContract.claimConditions.canClaim("0", 1, w1.address),
|
||||
"can claim",
|
||||
);
|
||||
assert.isTrue(
|
||||
await bdContract.claimConditions.canClaim("0", 1, w2.address),
|
||||
"can claim",
|
||||
);
|
||||
assert.isTrue(
|
||||
await bdContract.claimConditions.canClaim("0", 1, w3.address),
|
||||
"can claim",
|
||||
);
|
||||
assert.isFalse(
|
||||
await bdContract.claimConditions.canClaim("0", 1, bobWallet.address),
|
||||
"!can claim",
|
||||
);
|
||||
});
|
||||
|
||||
it("should work when the token has a price", async () => {
|
||||
await bdContract.createBatch([
|
||||
{
|
||||
name: "test",
|
||||
description: "test",
|
||||
},
|
||||
]);
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
price: 1,
|
||||
},
|
||||
]);
|
||||
await bdContract.claim("0", 1);
|
||||
});
|
||||
|
||||
it("should set multiple claim conditions at once", async () => {
|
||||
await bdContract.createBatch([
|
||||
{
|
||||
name: "test1",
|
||||
description: "test1",
|
||||
},
|
||||
{
|
||||
name: "test2",
|
||||
description: "test2",
|
||||
},
|
||||
]);
|
||||
await bdContract.claimConditions.setBatch([
|
||||
{
|
||||
tokenId: 0,
|
||||
claimConditions: [
|
||||
{
|
||||
price: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tokenId: 1,
|
||||
claimConditions: [
|
||||
{
|
||||
price: 0.1,
|
||||
maxClaimableSupply: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
await bdContract.claim("0", 2);
|
||||
try {
|
||||
await bdContract.claim("1", 2);
|
||||
} catch (e) {
|
||||
expectError(e, "exceed max mint supply");
|
||||
}
|
||||
});
|
||||
|
||||
describe("eligibility", () => {
|
||||
beforeEach(async () => {
|
||||
await bdContract.createBatch([
|
||||
{
|
||||
name: "test",
|
||||
description: "test",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should return false if there isn't an active claim condition", async () => {
|
||||
const reasons =
|
||||
await bdContract.claimConditions.getClaimIneligibilityReasons(
|
||||
"0",
|
||||
"0",
|
||||
bobWallet.address,
|
||||
);
|
||||
|
||||
expect(reasons).to.include(ClaimEligibility.NoClaimConditionSet);
|
||||
assert.lengthOf(reasons, 1);
|
||||
const canClaim = await bdContract.claimConditions.canClaim(
|
||||
"0",
|
||||
"1",
|
||||
w1.address,
|
||||
);
|
||||
assert.isFalse(canClaim);
|
||||
});
|
||||
|
||||
it("set claim condition in the future should not be claimable now", async () => {
|
||||
await bdContract.claimConditions.set(0, [
|
||||
{
|
||||
startTime: new Date(Date.now() + 60 * 60 * 24 * 1000),
|
||||
},
|
||||
]);
|
||||
const canClaim = await bdContract.claimConditions.canClaim(0, 1);
|
||||
expect(canClaim).to.eq(false);
|
||||
});
|
||||
|
||||
it("should check for the total supply", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxClaimableSupply: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
const reasons =
|
||||
await bdContract.claimConditions.getClaimIneligibilityReasons(
|
||||
"0",
|
||||
"2",
|
||||
w1.address,
|
||||
);
|
||||
expect(reasons).to.include(ClaimEligibility.NotEnoughSupply);
|
||||
const canClaim = await bdContract.claimConditions.canClaim(
|
||||
"0",
|
||||
"2",
|
||||
w1.address,
|
||||
);
|
||||
assert.isFalse(canClaim);
|
||||
});
|
||||
|
||||
it("should check if an address has valid merkle proofs", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxClaimableSupply: 1,
|
||||
snapshot: [w2.address, adminWallet.address],
|
||||
},
|
||||
]);
|
||||
|
||||
const reasons =
|
||||
await bdContract.claimConditions.getClaimIneligibilityReasons(
|
||||
"0",
|
||||
"1",
|
||||
w1.address,
|
||||
);
|
||||
expect(reasons).to.include(ClaimEligibility.AddressNotAllowed);
|
||||
const canClaim = await bdContract.claimConditions.canClaim(
|
||||
"0",
|
||||
"1",
|
||||
w1.address,
|
||||
);
|
||||
assert.isFalse(canClaim);
|
||||
});
|
||||
|
||||
it("should check if its been long enough since the last claim", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxClaimableSupply: 10,
|
||||
waitInSeconds: 24 * 60 * 60,
|
||||
},
|
||||
]);
|
||||
await sdk.updateSignerOrProvider(bobWallet);
|
||||
await bdContract.claim("0", 1);
|
||||
|
||||
const reasons =
|
||||
await bdContract.claimConditions.getClaimIneligibilityReasons(
|
||||
"0",
|
||||
"1",
|
||||
bobWallet.address,
|
||||
);
|
||||
|
||||
expect(reasons).to.include(
|
||||
ClaimEligibility.WaitBeforeNextClaimTransaction,
|
||||
);
|
||||
const canClaim = await bdContract.claimConditions.canClaim(
|
||||
"0",
|
||||
"1",
|
||||
bobWallet.address,
|
||||
);
|
||||
assert.isFalse(canClaim);
|
||||
});
|
||||
|
||||
it("should check if an address has enough native currency", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxClaimableSupply: 10,
|
||||
price: "1000000000000000",
|
||||
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
||||
},
|
||||
]);
|
||||
await sdk.updateSignerOrProvider(bobWallet);
|
||||
|
||||
const reasons =
|
||||
await bdContract.claimConditions.getClaimIneligibilityReasons(
|
||||
"0",
|
||||
"1",
|
||||
bobWallet.address,
|
||||
);
|
||||
|
||||
expect(reasons).to.include(ClaimEligibility.NotEnoughTokens);
|
||||
const canClaim = await bdContract.claimConditions.canClaim(
|
||||
"0",
|
||||
"1",
|
||||
w1.address,
|
||||
);
|
||||
assert.isFalse(canClaim);
|
||||
});
|
||||
|
||||
it("should check if an address has enough erc20 currency", async () => {
|
||||
const currency = await sdk.getToken(
|
||||
await sdk.deployer.deployBuiltInContract(
|
||||
TokenInitializer.contractType,
|
||||
{
|
||||
name: "test",
|
||||
symbol: "test",
|
||||
primary_sale_recipient: adminWallet.address,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxClaimableSupply: 10,
|
||||
price: "1000000000000000",
|
||||
currencyAddress: currency.getAddress(),
|
||||
},
|
||||
]);
|
||||
await sdk.updateSignerOrProvider(bobWallet);
|
||||
|
||||
const reasons =
|
||||
await bdContract.claimConditions.getClaimIneligibilityReasons(
|
||||
"0",
|
||||
"1",
|
||||
bobWallet.address,
|
||||
);
|
||||
|
||||
expect(reasons).to.include(ClaimEligibility.NotEnoughTokens);
|
||||
const canClaim = await bdContract.claimConditions.canClaim(
|
||||
"0",
|
||||
"1",
|
||||
w1.address,
|
||||
);
|
||||
assert.isFalse(canClaim);
|
||||
});
|
||||
|
||||
it("should return nothing if the claim is eligible", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxClaimableSupply: 10,
|
||||
price: "100",
|
||||
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
||||
snapshot: [w1.address, w2.address, w3.address],
|
||||
},
|
||||
]);
|
||||
|
||||
const reasons =
|
||||
await bdContract.claimConditions.getClaimIneligibilityReasons(
|
||||
"0",
|
||||
"1",
|
||||
w1.address,
|
||||
);
|
||||
assert.lengthOf(reasons, 0);
|
||||
|
||||
const canClaim = await bdContract.claimConditions.canClaim(
|
||||
"0",
|
||||
"1",
|
||||
w1.address,
|
||||
);
|
||||
assert.isTrue(canClaim);
|
||||
});
|
||||
});
|
||||
it("should allow you to update claim conditions", async () => {
|
||||
await bdContract.createBatch([
|
||||
{ name: "test", description: "test" },
|
||||
{ name: "test", description: "test" },
|
||||
]);
|
||||
|
||||
await bdContract.claimConditions.set(BigNumber.from("0"), [{}]);
|
||||
await bdContract.claimConditions.update(BigNumber.from("0"), 0, {});
|
||||
const conditions = await bdContract.claimConditions.getAll(0);
|
||||
assert.lengthOf(conditions, 1);
|
||||
});
|
||||
|
||||
it("should return snapshot data on claim conditions", async () => {
|
||||
await bdContract.createBatch([
|
||||
{ name: "test", description: "test" },
|
||||
{ name: "test", description: "test" },
|
||||
]);
|
||||
|
||||
await bdContract.claimConditions.set(0, [
|
||||
{
|
||||
snapshot: [samWallet.address],
|
||||
},
|
||||
]);
|
||||
const conditions = await bdContract.claimConditions.getAll(0, {
|
||||
withAllowList: true,
|
||||
});
|
||||
assert.lengthOf(conditions, 1);
|
||||
invariant(conditions[0].snapshot);
|
||||
expect(conditions[0].snapshot[0].address).to.eq(samWallet.address);
|
||||
});
|
||||
|
||||
it("should be able to use claim as function expected", async () => {
|
||||
await bdContract.createBatch([
|
||||
{
|
||||
name: "test",
|
||||
},
|
||||
]);
|
||||
await bdContract.claimConditions.set("0", [{}]);
|
||||
await bdContract.claim("0", 1);
|
||||
assert((await bdContract.getOwned()).length > 0);
|
||||
});
|
||||
|
||||
it("should be able to use claimTo function as expected", async () => {
|
||||
await bdContract.createBatch([
|
||||
{
|
||||
name: "test",
|
||||
},
|
||||
]);
|
||||
await bdContract.claimConditions.set("0", [{}]);
|
||||
await bdContract.claimTo(samWallet.address, "0", 3);
|
||||
assert((await bdContract.getOwned(samWallet.address)).length === 1);
|
||||
assert(
|
||||
(await bdContract.getOwned(samWallet.address))[0].owner ===
|
||||
samWallet.address,
|
||||
);
|
||||
assert(
|
||||
(await bdContract.getOwned(samWallet.address))[0].quantityOwned === 3,
|
||||
);
|
||||
});
|
||||
|
||||
describe("setting merkle claim conditions", () => {
|
||||
it("should not overwrite existing merkle keys in the metadata", async () => {
|
||||
await bdContract.createBatch([
|
||||
{ name: "test", description: "test" },
|
||||
{ name: "test", description: "test" },
|
||||
]);
|
||||
|
||||
await bdContract.claimConditions.set("1", [
|
||||
{
|
||||
snapshot: [w1.address, w2.address, bobWallet.address],
|
||||
},
|
||||
]);
|
||||
|
||||
await bdContract.claimConditions.set("2", [
|
||||
{
|
||||
snapshot: [w3.address, w1.address, w2.address, adminWallet.address],
|
||||
},
|
||||
]);
|
||||
|
||||
const metadata = await bdContract.metadata.get();
|
||||
const merkle: { [key: string]: string } = metadata.merkle;
|
||||
assert.lengthOf(Object.keys(merkle), 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
+108
-68
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
ClaimEligibility,
|
||||
EditionDrop,
|
||||
EditionDropInitializer,
|
||||
NATIVE_TOKEN_ADDRESS,
|
||||
TokenInitializer,
|
||||
} from "../../src/evm";
|
||||
@@ -17,7 +18,7 @@ const deepEqualInAnyOrder = require("deep-equal-in-any-order");
|
||||
|
||||
use(deepEqualInAnyOrder);
|
||||
|
||||
describe("Edition Drop Contract", async () => {
|
||||
describe("Edition Drop Contract (V4)", async () => {
|
||||
let bdContract: EditionDrop;
|
||||
let adminWallet: SignerWithAddress,
|
||||
samWallet: SignerWithAddress,
|
||||
@@ -34,24 +35,73 @@ describe("Edition Drop Contract", async () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
sdk.updateSignerOrProvider(adminWallet);
|
||||
const address = await sdk.deployer.deployEditionDrop({
|
||||
name: `Testing bundle drop from SDK`,
|
||||
description: "Test contract from tests",
|
||||
image:
|
||||
"https://pbs.twimg.com/profile_images/1433508973215367176/XBCfBn3g_400x400.jpg",
|
||||
primary_sale_recipient: adminWallet.address,
|
||||
seller_fee_basis_points: 500,
|
||||
fee_recipient: AddressZero,
|
||||
platform_fee_basis_points: 10,
|
||||
platform_fee_recipient: adminWallet.address,
|
||||
});
|
||||
const address = await sdk.deployer.deployBuiltInContract(
|
||||
EditionDropInitializer.contractType,
|
||||
{
|
||||
name: `Testing edition drop from SDK`,
|
||||
description: "Test contract from tests",
|
||||
image:
|
||||
"https://pbs.twimg.com/profile_images/1433508973215367176/XBCfBn3g_400x400.jpg",
|
||||
primary_sale_recipient: adminWallet.address,
|
||||
seller_fee_basis_points: 500,
|
||||
fee_recipient: AddressZero,
|
||||
platform_fee_basis_points: 10,
|
||||
platform_fee_recipient: adminWallet.address,
|
||||
},
|
||||
await sdk.deployer.getLatestBuiltInContractVersion(
|
||||
EditionDropInitializer.contractType,
|
||||
),
|
||||
);
|
||||
bdContract = await sdk.getEditionDrop(address);
|
||||
});
|
||||
|
||||
it("should allow a snapshot to be set", async () => {
|
||||
await bdContract.createBatch([
|
||||
{
|
||||
name: "Test1",
|
||||
},
|
||||
]);
|
||||
await bdContract.claimConditions.set(0, [
|
||||
{
|
||||
startTime: new Date(Date.now() / 2),
|
||||
snapshot: [bobWallet.address, samWallet.address, abbyWallet.address],
|
||||
price: 1,
|
||||
},
|
||||
{
|
||||
startTime: new Date(),
|
||||
snapshot: [bobWallet.address],
|
||||
},
|
||||
]);
|
||||
|
||||
let proof = await bdContract.claimConditions.getClaimerProofs(
|
||||
0,
|
||||
bobWallet.address,
|
||||
);
|
||||
expect(proof.address).to.eq(bobWallet.address);
|
||||
proof = await bdContract.claimConditions.getClaimerProofs(
|
||||
0,
|
||||
bobWallet.address,
|
||||
0,
|
||||
);
|
||||
expect(proof.address).to.eq(bobWallet.address);
|
||||
proof = await bdContract.claimConditions.getClaimerProofs(
|
||||
0,
|
||||
samWallet.address,
|
||||
);
|
||||
expect(proof).to.eq(null);
|
||||
proof = await bdContract.claimConditions.getClaimerProofs(
|
||||
0,
|
||||
samWallet.address,
|
||||
0,
|
||||
);
|
||||
expect(proof.address).to.eq(samWallet.address);
|
||||
});
|
||||
|
||||
it("should estimate gas cost", async () => {
|
||||
const cost = await bdContract.estimator.gasCostOf("lazyMint", [
|
||||
1000,
|
||||
"mock://12398172398172389/0",
|
||||
"0x00",
|
||||
]);
|
||||
expect(parseFloat(cost)).gt(0);
|
||||
});
|
||||
@@ -96,7 +146,7 @@ describe("Edition Drop Contract", async () => {
|
||||
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxQuantity: 1000,
|
||||
maxClaimableSupply: 1000,
|
||||
snapshot: members,
|
||||
},
|
||||
]);
|
||||
@@ -129,10 +179,14 @@ describe("Edition Drop Contract", async () => {
|
||||
w2,
|
||||
w3,
|
||||
];
|
||||
const members = testWallets.map((w) => w.address);
|
||||
const members = testWallets.map((w) => ({
|
||||
address: w.address,
|
||||
maxClaimable: 1,
|
||||
}));
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxQuantity: 1000,
|
||||
maxClaimablePerWallet: 0,
|
||||
maxClaimableSupply: 1000,
|
||||
snapshot: members,
|
||||
},
|
||||
]);
|
||||
@@ -215,7 +269,7 @@ describe("Edition Drop Contract", async () => {
|
||||
await sdk.updateSignerOrProvider(w2);
|
||||
await bdContract.claim(0, 2);
|
||||
} catch (e) {
|
||||
expectError(e, "invalid quantity proof");
|
||||
expectError(e, "!Qty");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -236,6 +290,20 @@ describe("Edition Drop Contract", async () => {
|
||||
await bdContract.claim("0", 1);
|
||||
});
|
||||
|
||||
it("should allow claiming with a price", async () => {
|
||||
await bdContract.createBatch([
|
||||
{ name: "name", description: "description" },
|
||||
]);
|
||||
await bdContract.claimConditions.set(0, [
|
||||
{
|
||||
price: 0.1,
|
||||
},
|
||||
]);
|
||||
await bdContract.claim(0, 2);
|
||||
const balance = await bdContract.balance(0);
|
||||
expect(balance).to.deep.equal(BigNumber.from(2));
|
||||
});
|
||||
|
||||
it("should return addresses of all the claimers", async () => {
|
||||
await bdContract.createBatch([
|
||||
{
|
||||
@@ -274,7 +342,8 @@ describe("Edition Drop Contract", async () => {
|
||||
it("should return the correct status if a token can be claimed", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
snapshot: [w1.address],
|
||||
maxClaimablePerWallet: 0,
|
||||
snapshot: [{ address: w1.address, maxClaimable: 1 }],
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -320,7 +389,8 @@ describe("Edition Drop Contract", async () => {
|
||||
it("canClaim: 1 address", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
snapshot: [w1.address],
|
||||
maxClaimablePerWallet: 0,
|
||||
snapshot: [{ address: w1.address, maxClaimable: 1 }],
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -335,13 +405,18 @@ describe("Edition Drop Contract", async () => {
|
||||
});
|
||||
|
||||
it("canClaim: 3 address", async () => {
|
||||
const members = [
|
||||
{
|
||||
address: w1.address.toUpperCase().replace("0X", "0x"),
|
||||
maxClaimable: 1,
|
||||
},
|
||||
{ address: w2.address.toLowerCase(), maxClaimable: 1 },
|
||||
{ address: w3.address.toLowerCase(), maxClaimable: 1 },
|
||||
];
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
snapshot: [
|
||||
w1.address.toUpperCase().replace("0X", "0x"),
|
||||
w2.address.toLowerCase(),
|
||||
w3.address,
|
||||
],
|
||||
maxClaimablePerWallet: 0,
|
||||
snapshot: members,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -403,7 +478,7 @@ describe("Edition Drop Contract", async () => {
|
||||
claimConditions: [
|
||||
{
|
||||
price: 0.1,
|
||||
maxQuantity: 1,
|
||||
maxClaimableSupply: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -412,7 +487,7 @@ describe("Edition Drop Contract", async () => {
|
||||
try {
|
||||
await bdContract.claim("1", 2);
|
||||
} catch (e) {
|
||||
expectError(e, "exceed max mint supply");
|
||||
expectError(e, "!MaxSupply");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -457,7 +532,7 @@ describe("Edition Drop Contract", async () => {
|
||||
it("should check for the total supply", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxQuantity: 1,
|
||||
maxClaimableSupply: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -476,61 +551,26 @@ describe("Edition Drop Contract", async () => {
|
||||
assert.isFalse(canClaim);
|
||||
});
|
||||
|
||||
it("should check if an address has valid merkle proofs", async () => {
|
||||
it("should get default values with default snapshot", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxQuantity: 1,
|
||||
snapshot: [w2.address, adminWallet.address],
|
||||
maxClaimableSupply: 1,
|
||||
snapshot: [w1.address],
|
||||
},
|
||||
]);
|
||||
|
||||
const reasons =
|
||||
await bdContract.claimConditions.getClaimIneligibilityReasons(
|
||||
"0",
|
||||
"1",
|
||||
w1.address,
|
||||
);
|
||||
expect(reasons).to.include(ClaimEligibility.AddressNotAllowed);
|
||||
const canClaim = await bdContract.claimConditions.canClaim(
|
||||
"0",
|
||||
"1",
|
||||
w1.address,
|
||||
);
|
||||
assert.isFalse(canClaim);
|
||||
});
|
||||
|
||||
it("should check if its been long enough since the last claim", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxQuantity: 10,
|
||||
waitInSeconds: 24 * 60 * 60,
|
||||
},
|
||||
]);
|
||||
await sdk.updateSignerOrProvider(bobWallet);
|
||||
await bdContract.claim("0", 1);
|
||||
|
||||
const reasons =
|
||||
await bdContract.claimConditions.getClaimIneligibilityReasons(
|
||||
"0",
|
||||
"1",
|
||||
bobWallet.address,
|
||||
);
|
||||
|
||||
expect(reasons).to.include(
|
||||
ClaimEligibility.WaitBeforeNextClaimTransaction,
|
||||
);
|
||||
const canClaim = await bdContract.claimConditions.canClaim(
|
||||
"0",
|
||||
"1",
|
||||
bobWallet.address,
|
||||
);
|
||||
assert.isFalse(canClaim);
|
||||
assert.isTrue(canClaim);
|
||||
});
|
||||
|
||||
it("should check if an address has enough native currency", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxQuantity: 10,
|
||||
maxClaimableSupply: 10,
|
||||
price: "1000000000000000",
|
||||
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
||||
},
|
||||
@@ -567,7 +607,7 @@ describe("Edition Drop Contract", async () => {
|
||||
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxQuantity: 10,
|
||||
maxClaimableSupply: 10,
|
||||
price: "1000000000000000",
|
||||
currencyAddress: currency.getAddress(),
|
||||
},
|
||||
@@ -593,7 +633,7 @@ describe("Edition Drop Contract", async () => {
|
||||
it("should return nothing if the claim is eligible", async () => {
|
||||
await bdContract.claimConditions.set("0", [
|
||||
{
|
||||
maxQuantity: 10,
|
||||
maxClaimableSupply: 10,
|
||||
price: "100",
|
||||
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
||||
snapshot: [w1.address, w2.address, w3.address],
|
||||
+171
-90
@@ -1,23 +1,21 @@
|
||||
import { NFTMetadataInput } from "../../src/core/schema/nft";
|
||||
import {
|
||||
ClaimEligibility,
|
||||
createSnapshot,
|
||||
NATIVE_TOKEN_ADDRESS,
|
||||
NFTDrop,
|
||||
NFTDropInitializer,
|
||||
TokenInitializer,
|
||||
} from "../../src/evm";
|
||||
import { ShardedMerkleTree } from "../../src/evm/common/sharded-merkle-tree";
|
||||
import { expectError, sdk, signers, storage } from "./before-setup";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
|
||||
import { assert, expect } from "chai";
|
||||
import { BigNumber, ethers } from "ethers";
|
||||
import { MerkleTree } from "merkletreejs";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
global.fetch = require("cross-fetch");
|
||||
|
||||
describe("NFT Drop Contract", async () => {
|
||||
describe("NFT Drop Contract (v3)", async () => {
|
||||
let dropContract: NFTDrop;
|
||||
let adminWallet: SignerWithAddress,
|
||||
samWallet: SignerWithAddress,
|
||||
@@ -30,18 +28,21 @@ describe("NFT Drop Contract", async () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
[adminWallet, samWallet, bobWallet, abbyWallet, w1, w2, w3, w4] = signers;
|
||||
sdk.updateSignerOrProvider(adminWallet);
|
||||
const address = await sdk.deployer.deployNFTDrop({
|
||||
name: `Testing drop from SDK`,
|
||||
description: "Test contract from tests",
|
||||
image:
|
||||
"https://pbs.twimg.com/profile_images/1433508973215367176/XBCfBn3g_400x400.jpg",
|
||||
primary_sale_recipient: adminWallet.address,
|
||||
seller_fee_basis_points: 500,
|
||||
fee_recipient: AddressZero,
|
||||
platform_fee_basis_points: 10,
|
||||
platform_fee_recipient: AddressZero,
|
||||
});
|
||||
const address = await sdk.deployer.deployBuiltInContract(
|
||||
NFTDropInitializer.contractType,
|
||||
{
|
||||
name: `Testing drop from SDK`,
|
||||
description: "Test contract from tests",
|
||||
image:
|
||||
"https://pbs.twimg.com/profile_images/1433508973215367176/XBCfBn3g_400x400.jpg",
|
||||
primary_sale_recipient: adminWallet.address,
|
||||
seller_fee_basis_points: 500,
|
||||
fee_recipient: AddressZero,
|
||||
platform_fee_basis_points: 10,
|
||||
platform_fee_recipient: AddressZero,
|
||||
},
|
||||
3,
|
||||
);
|
||||
dropContract = await sdk.getNFTDrop(address);
|
||||
});
|
||||
|
||||
@@ -55,6 +56,116 @@ describe("NFT Drop Contract", async () => {
|
||||
assert.equal(nft.metadata.name, "Test1");
|
||||
});
|
||||
|
||||
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, "exceed max claimable supply");
|
||||
}
|
||||
await dropContract.claim(1);
|
||||
// claiming with max per wallet
|
||||
await dropContract.claimConditions.set([
|
||||
{
|
||||
maxClaimablePerWallet: 1,
|
||||
},
|
||||
]);
|
||||
try {
|
||||
await dropContract.claim(2);
|
||||
expect.fail("should not be able to claim 2 - maxClaimablePerWallet");
|
||||
} catch (e) {
|
||||
expectError(e, "invalid quantity");
|
||||
}
|
||||
await dropContract.claim(1);
|
||||
expect((await dropContract.totalClaimedSupply()).toString()).eq("3");
|
||||
});
|
||||
|
||||
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([
|
||||
{
|
||||
snapshot: [adminWallet.address],
|
||||
},
|
||||
]);
|
||||
try {
|
||||
sdk.updateSignerOrProvider(bobWallet);
|
||||
await dropContract.claim(1);
|
||||
expect.fail("should not be able to claim - not in allowlist");
|
||||
} catch (e) {
|
||||
expectError(e, "No claim found");
|
||||
}
|
||||
sdk.updateSignerOrProvider(adminWallet);
|
||||
await dropContract.claim(1);
|
||||
|
||||
// claiming with max supply
|
||||
await dropContract.claimConditions.set([
|
||||
{
|
||||
snapshot: [adminWallet.address],
|
||||
maxClaimableSupply: 2,
|
||||
},
|
||||
]);
|
||||
try {
|
||||
await dropContract.claim(2);
|
||||
expect.fail("should not be able to claim - maxClaimableSupply");
|
||||
} catch (e) {
|
||||
expectError(e, "exceed max claimable supply");
|
||||
}
|
||||
await dropContract.claim(1);
|
||||
// claiming with max per wallet
|
||||
await dropContract.claimConditions.set([
|
||||
{
|
||||
snapshot: [adminWallet.address],
|
||||
maxClaimablePerWallet: 1,
|
||||
},
|
||||
]);
|
||||
try {
|
||||
await dropContract.claim(2);
|
||||
expect.fail("should not be able to claim - maxClaimablePerWallet");
|
||||
} catch (e) {
|
||||
expectError(e, "invalid quantity");
|
||||
}
|
||||
await dropContract.claim(1);
|
||||
// claiming with max per wallet in snapshot
|
||||
await dropContract.claimConditions.set([
|
||||
{
|
||||
snapshot: [{ address: adminWallet.address, maxClaimable: 1 }],
|
||||
maxClaimablePerWallet: 0,
|
||||
},
|
||||
]);
|
||||
try {
|
||||
await dropContract.claim(2);
|
||||
expect.fail("should not be able to claim - proof maxClaimable");
|
||||
} catch (e) {
|
||||
expectError(e, "invalid quantity proof");
|
||||
}
|
||||
await dropContract.claim(1);
|
||||
try {
|
||||
await dropContract.claim(1);
|
||||
expect.fail("should not be able to claim - proof used");
|
||||
} catch (e) {
|
||||
expectError(e, "proof claimed");
|
||||
}
|
||||
});
|
||||
|
||||
it("should get and execute transaction task", async () => {
|
||||
await dropContract.createBatch([
|
||||
{
|
||||
@@ -85,23 +196,36 @@ describe("NFT Drop Contract", async () => {
|
||||
const metadata = await dropContract.metadata.get();
|
||||
const merkles = metadata.merkle;
|
||||
|
||||
expect(merkles).have.property(
|
||||
"0xd89eb21bf7ee4dd07d88e8f90a513812d9d38ac390a58722762c9f3afc4e0feb",
|
||||
);
|
||||
|
||||
expect(merkles).have.property(
|
||||
"0xb1a60ad68b77609a455696695fbdd02b850d03ec285e7fe1f4c4093797457b24",
|
||||
);
|
||||
expect(merkles).have.property(
|
||||
"0xd89eb21bf7ee4dd07d88e8f90a513812d9d38ac390a58722762c9f3afc4e0feb",
|
||||
);
|
||||
|
||||
const roots = (await dropContract.claimConditions.getAll()).map(
|
||||
(c) => c.merkleRootHash,
|
||||
);
|
||||
expect(roots).length(2);
|
||||
|
||||
const proof = await dropContract.claimConditions.getClaimerProofs(
|
||||
let proof = await dropContract.claimConditions.getClaimerProofs(
|
||||
bobWallet.address,
|
||||
);
|
||||
expect(proof.address).to.eq(bobWallet.address);
|
||||
proof = await dropContract.claimConditions.getClaimerProofs(
|
||||
bobWallet.address,
|
||||
0,
|
||||
);
|
||||
expect(proof.address).to.eq(bobWallet.address);
|
||||
proof = await dropContract.claimConditions.getClaimerProofs(
|
||||
samWallet.address,
|
||||
);
|
||||
expect(proof).to.eq(null);
|
||||
proof = await dropContract.claimConditions.getClaimerProofs(
|
||||
samWallet.address,
|
||||
0,
|
||||
);
|
||||
expect(proof.address).to.eq(samWallet.address);
|
||||
});
|
||||
|
||||
it("should return snapshot data on claim conditions", async () => {
|
||||
@@ -139,13 +263,12 @@ describe("NFT Drop Contract", async () => {
|
||||
const metadata = await dropContract.metadata.get();
|
||||
const merkles = metadata.merkle;
|
||||
|
||||
expect(merkles).have.property(
|
||||
"0xd89eb21bf7ee4dd07d88e8f90a513812d9d38ac390a58722762c9f3afc4e0feb",
|
||||
);
|
||||
|
||||
expect(merkles).have.property(
|
||||
"0xb1a60ad68b77609a455696695fbdd02b850d03ec285e7fe1f4c4093797457b24",
|
||||
);
|
||||
expect(merkles).have.property(
|
||||
"0xd89eb21bf7ee4dd07d88e8f90a513812d9d38ac390a58722762c9f3afc4e0feb",
|
||||
);
|
||||
|
||||
const roots = (await dropContract.claimConditions.getAll()).map(
|
||||
(c) => c.merkleRootHash,
|
||||
@@ -268,14 +391,14 @@ describe("NFT Drop Contract", async () => {
|
||||
await dropContract.createBatch(metadatas);
|
||||
await dropContract.claimConditions.set([
|
||||
{
|
||||
maxQuantity: 10,
|
||||
maxClaimableSupply: 10,
|
||||
},
|
||||
]);
|
||||
await dropContract.claim(5);
|
||||
await dropContract.claimConditions.set(
|
||||
[
|
||||
{
|
||||
maxQuantity: 10,
|
||||
maxClaimableSupply: 10,
|
||||
},
|
||||
],
|
||||
true,
|
||||
@@ -365,52 +488,6 @@ describe("NFT Drop Contract", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("should generate valid proofs", async () => {
|
||||
const members = [
|
||||
bobWallet.address,
|
||||
samWallet.address,
|
||||
abbyWallet.address,
|
||||
w1.address,
|
||||
w2.address,
|
||||
w3.address,
|
||||
w4.address,
|
||||
];
|
||||
|
||||
const hashedLeafs = members.map((l) =>
|
||||
ethers.utils.solidityKeccak256(["address", "uint256"], [l, 0]),
|
||||
);
|
||||
const tree = new MerkleTree(hashedLeafs, ethers.utils.keccak256, {
|
||||
sort: true,
|
||||
sortLeaves: true,
|
||||
sortPairs: true,
|
||||
});
|
||||
const input = members.map((address) => ({
|
||||
address,
|
||||
maxClaimable: 0,
|
||||
}));
|
||||
const snapshot = await createSnapshot(input, 0, storage);
|
||||
for (const leaf of members) {
|
||||
const expectedProof = tree.getHexProof(
|
||||
ethers.utils.solidityKeccak256(["address", "uint256"], [leaf, 0]),
|
||||
);
|
||||
|
||||
const smt = await ShardedMerkleTree.fromUri(
|
||||
snapshot.snapshotUri,
|
||||
storage,
|
||||
);
|
||||
const actualProof = await smt?.getProof(leaf);
|
||||
assert.isDefined(actualProof);
|
||||
expect(actualProof?.proof).to.include.ordered.members(expectedProof);
|
||||
|
||||
const verified = tree.verify(
|
||||
actualProof?.proof as string[],
|
||||
ethers.utils.solidityKeccak256(["address", "uint256"], [leaf, 0]),
|
||||
tree.getHexRoot(),
|
||||
);
|
||||
expect(verified).to.eq(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("should return the newly claimed token", async () => {
|
||||
await dropContract.claimConditions.set([{}]);
|
||||
await dropContract.createBatch([
|
||||
@@ -472,7 +549,7 @@ describe("NFT Drop Contract", async () => {
|
||||
});
|
||||
|
||||
it("should check for the total supply", async () => {
|
||||
await dropContract.claimConditions.set([{ maxQuantity: 1 }]);
|
||||
await dropContract.claimConditions.set([{ maxClaimableSupply: 1 }]);
|
||||
|
||||
const reasons =
|
||||
await dropContract.claimConditions.getClaimIneligibilityReasons(
|
||||
@@ -490,7 +567,7 @@ describe("NFT Drop Contract", async () => {
|
||||
it("should check if an address has valid merkle proofs", async () => {
|
||||
await dropContract.claimConditions.set([
|
||||
{
|
||||
maxQuantity: 1,
|
||||
maxClaimableSupply: 1,
|
||||
snapshot: [w2.address, adminWallet.address],
|
||||
},
|
||||
]);
|
||||
@@ -511,7 +588,7 @@ describe("NFT Drop Contract", async () => {
|
||||
it("should check if its been long enough since the last claim", async () => {
|
||||
await dropContract.claimConditions.set([
|
||||
{
|
||||
maxQuantity: 10,
|
||||
maxClaimableSupply: 10,
|
||||
waitInSeconds: 24 * 60 * 60,
|
||||
},
|
||||
]);
|
||||
@@ -534,7 +611,7 @@ describe("NFT Drop Contract", async () => {
|
||||
it("should check if an address has enough native currency", async () => {
|
||||
await dropContract.claimConditions.set([
|
||||
{
|
||||
maxQuantity: 10,
|
||||
maxClaimableSupply: 10,
|
||||
price: "1000000000000000",
|
||||
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
||||
},
|
||||
@@ -564,7 +641,7 @@ describe("NFT Drop Contract", async () => {
|
||||
|
||||
await dropContract.claimConditions.set([
|
||||
{
|
||||
maxQuantity: 10,
|
||||
maxClaimableSupply: 10,
|
||||
price: "1000000000000000",
|
||||
currencyAddress,
|
||||
},
|
||||
@@ -585,7 +662,7 @@ describe("NFT Drop Contract", async () => {
|
||||
it("should return nothing if the claim is eligible", async () => {
|
||||
await dropContract.claimConditions.set([
|
||||
{
|
||||
maxQuantity: 10,
|
||||
maxClaimableSupply: 10,
|
||||
price: "100",
|
||||
currencyAddress: NATIVE_TOKEN_ADDRESS,
|
||||
snapshot: [w1.address, w2.address, w3.address],
|
||||
@@ -802,14 +879,18 @@ describe("NFT Drop Contract", async () => {
|
||||
|
||||
it("set claim condition and update claim condition", async () => {
|
||||
await dropContract.claimConditions.set([
|
||||
{ startTime: new Date(Date.now() / 2), maxQuantity: 1, price: 0.15 },
|
||||
{
|
||||
startTime: new Date(Date.now() / 2),
|
||||
maxClaimableSupply: 1,
|
||||
price: 0.15,
|
||||
},
|
||||
{ startTime: new Date(), waitInSeconds: 60 },
|
||||
]);
|
||||
expect((await dropContract.claimConditions.getAll()).length).to.be.equal(2);
|
||||
|
||||
await dropContract.claimConditions.update(0, { waitInSeconds: 10 });
|
||||
let updatedConditions = await dropContract.claimConditions.getAll();
|
||||
expect(updatedConditions[0].maxQuantity).to.be.deep.equal("1");
|
||||
expect(updatedConditions[0].maxClaimableSupply).to.be.deep.equal("1");
|
||||
expect(updatedConditions[0].price).to.be.deep.equal(
|
||||
ethers.utils.parseUnits("0.15"),
|
||||
);
|
||||
@@ -821,12 +902,12 @@ describe("NFT Drop Contract", async () => {
|
||||
);
|
||||
|
||||
await dropContract.claimConditions.update(1, {
|
||||
maxQuantity: 10,
|
||||
maxClaimableSupply: 10,
|
||||
waitInSeconds: 10,
|
||||
});
|
||||
updatedConditions = await dropContract.claimConditions.getAll();
|
||||
expect(updatedConditions[0].maxQuantity).to.be.deep.equal("1");
|
||||
expect(updatedConditions[1].maxQuantity).to.be.deep.equal("10");
|
||||
expect(updatedConditions[0].maxClaimableSupply).to.be.deep.equal("1");
|
||||
expect(updatedConditions[1].maxClaimableSupply).to.be.deep.equal("10");
|
||||
expect(updatedConditions[1].waitInSeconds).to.be.deep.equal(
|
||||
BigNumber.from(10),
|
||||
);
|
||||
@@ -834,8 +915,8 @@ describe("NFT Drop Contract", async () => {
|
||||
|
||||
it("set claim condition and update claim condition with diff timestamps should reorder", async () => {
|
||||
await dropContract.claimConditions.set([
|
||||
{ startTime: new Date(Date.now() / 2), maxQuantity: 1 },
|
||||
{ startTime: new Date(), maxQuantity: 2 },
|
||||
{ startTime: new Date(Date.now() / 2), maxClaimableSupply: 1 },
|
||||
{ startTime: new Date(), maxClaimableSupply: 2 },
|
||||
]);
|
||||
expect((await dropContract.claimConditions.getAll()).length).to.be.equal(2);
|
||||
|
||||
@@ -844,8 +925,8 @@ describe("NFT Drop Contract", async () => {
|
||||
});
|
||||
// max quantities should be inverted now
|
||||
const updatedConditions = await dropContract.claimConditions.getAll();
|
||||
expect(updatedConditions[0].maxQuantity).to.be.deep.equal("2");
|
||||
expect(updatedConditions[1].maxQuantity).to.be.deep.equal("1");
|
||||
expect(updatedConditions[0].maxClaimableSupply).to.be.deep.equal("2");
|
||||
expect(updatedConditions[1].maxClaimableSupply).to.be.deep.equal("1");
|
||||
});
|
||||
|
||||
it("set claim condition in the future should not be claimable now", async () => {
|
||||
@@ -1000,14 +1081,14 @@ describe("NFT Drop Contract", async () => {
|
||||
(await adminWallet.provider?.getBlockWithTransactions("latest"))
|
||||
?.transactions || [];
|
||||
|
||||
const { index, _key } = dropContract.encoder.decode(
|
||||
const [index, key] = dropContract.encoder.decode(
|
||||
"reveal",
|
||||
transactions[0].data,
|
||||
);
|
||||
|
||||
// re-using broadcasted _key to decode :)
|
||||
try {
|
||||
await dropContract.revealer.reveal(index.add(1), _key);
|
||||
await dropContract.revealer.reveal(index.add(1), key);
|
||||
assert.fail("should not be able to re-used published password");
|
||||
} catch (e) {
|
||||
expect((e as Error).message).to.be.equal("invalid password");
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user