Compare commits

...
Author SHA1 Message Date
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
c2b6d977ed Version Packages (#383)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-11-16 14:02:50 -08:00
7cb8e59be4 [SDK] Add new Drop contract implementation addresses (#364)
Co-authored-by: Jonas Daniels <[email protected]>
Co-authored-by: Adam Majmudar <[email protected]>
2022-11-16 21:52:01 +00:00
Adam MajmudarandGitHub 04a47e8971 Add retroactive creator and royalty update option (#385) 2022-11-16 21:37:45 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8a6a97a3e9 Bump loader-utils from 2.0.3 to 2.0.4 (#384)
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-16 19:51:40 +00:00
Jonas DanielsandGitHub 612bcdd16f [REACT] - update gnosis connector (#386) 2022-11-16 19:34:27 +00:00
Jonas DanielsandGitHub 0ed77d2c9e [SDK] - add constructorParams to schema for better release -> deploy (#382) 2022-11-15 20:47:54 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
7129aef6e1 Version Packages (#370)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-11-11 08:51:19 -08:00
Joaquim VergesandGitHub f50b74819d [SDK] make isConnected sync, remove useless fn (#381) 2022-11-11 06:14:24 +00:00
Joaquim VergesandGitHub 3eba69afd7 [SDK] expose signer's chain id via sdk.wallet (#380) 2022-11-11 00:42:05 +00:00
Jarrod WattsandGitHub 98c2253e8f [SDK] Docs - Add await before sdk.getContract snippets (#379) 2022-11-10 16:08:31 -08:00
32d91ef709 [REACT] Fix Border Radius for Safari (#378)
Co-authored-by: Jarrod Watts <[email protected]>
2022-11-10 09:53:17 -08:00
Adam MajmudarandGitHub b79dc18f7a Add event filtering on topics (#377) 2022-11-09 11:51:13 -05:00
Adam MajmudarandGitHub 4af785c37c Add TokensClaimed event to tiered-drop (#376) 2022-11-08 20:34:45 +00:00
Adam MajmudarandGitHub 6f818f393e Updated tiered-drop contract (#373) 2022-11-03 17:03:00 -07:00
Adam MajmudarandGitHub 97ed4779a9 Update nft drop claimTo documentation (#372) 2022-11-03 22:42:42 +00:00
Nacho IacovinoandGitHub 2d3fca5237 [React] Add useClaimerProofs hook (#369) 2022-11-03 01:17:15 +01:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
7016019f2a Version Packages (#366)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-11-02 15:16:25 -07:00
Joaquim VergesandGitHub 4e7b293c54 [SDK] Fix handling of old allowlist proof format (#368) 2022-11-02 14:11:13 -07:00
Jonas DanielsandGitHub c9c0e4ff39 [SDK] - make EVM side react native compatible (#365) 2022-11-02 05:38:56 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
06c24c153a Version Packages (#361)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-11-01 16:13:47 -07:00
aa6d9ed75d [SDK] Support new Drop claim conditions mechanics (#333)
Co-authored-by: adam-maj <[email protected]>
Co-authored-by: Adam Majmudar <[email protected]>
2022-11-01 23:07:10 +00:00
Joaquim VergesandGitHub 42a606588d [SDK] Fix fetching NFTs with numbers as ids (#362) 2022-11-01 22:01:01 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
2820761003 Version Packages (#360)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-11-01 13:49:34 -07:00
Adam MajmudarandGitHub 5200d5d7e7 Add support for TieredDrop in extensions (#329) 2022-11-01 13:48:48 -07:00
Joaquim VergesandGitHub e731c1a7c3 [SDK] Allow fetching claimerProofs from any claim condition (#359) 2022-10-31 23:26:27 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
2e8d3fd6e7 Version Packages (#358)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-28 18:14:35 -07:00
Nacho IacovinoandGitHub cc4613b5fd [React] Add Pack and Multiwrap as NFTContract types (#356) 2022-10-29 01:28:40 +02:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
78566babaa Version Packages (#354)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-28 10:29:57 -07:00
Joaquim VergesandGitHub 65fc7ba6ff [SDK] Expose claimCondtions.getClaimerProofs(address) (#357) 2022-10-28 10:25:36 -07:00
Nacho IacovinoandGitHub 1c24c3c3c4 [Multi] Add useUpdateCreator hook, fix CreatorInput schema (#353) 2022-10-26 18:19:32 +00:00
github-actions[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
408c5a81fa Version Packages (#347)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-26 08:38:25 -07:00
Joaquim VergesandGitHub 352291791e [SDK, React] expose marketplace.auction.executeSale (#352) 2022-10-26 00:19:01 +00:00
Jonas DanielsandGitHub aabbb149e8 [React] - fix wallet connection during hot reload (#351) 2022-10-25 22:48:39 +00:00
Jonas DanielsandGitHub d59450f814 do not release e2e tests (#350) 2022-10-25 20:29:22 +00:00
7d9a4c6abc Update name of useRoyalty, and update (#348)
Co-authored-by: Jonas Daniels <[email protected]>
2022-10-25 19:56:09 +00:00
Jonas DanielsandGitHub 99806e595d [VARIOUS] - exclude sizes of test files etc (#349) 2022-10-25 18:39:30 +00:00
Nacho IacovinoandGitHub 21c21c1adf resolveReleasesFromAddress -> resolveContractUriFromAddress (#344) 2022-10-24 23:43:43 +00:00
136 changed files with 9479 additions and 1648 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"ignore": ["e2e-cra-5", "e2e-next-12", "e2e-vite-3"],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"updateInternalDependents": "always",
"onlyUpdatePeerDependentsWhenOutOfRange": true
+2
View File
@@ -15,3 +15,5 @@ jobs:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
strip-hash: "\\-(\\w{8})(?:\\.esm|\\.cjs)"
minimum-change-threshold: 100
# exclude source maps, node modules and any test related files
exclude: "{**/*.map,**/node_modules/**,**/test/**}"
+2 -1
View File
@@ -16,4 +16,5 @@ public/dist
.yalc/
yalc.lock
build/
playwright-report/
playwright-report/
.vscode/
+3 -3
View File
@@ -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": {
+16
View File
@@ -1,5 +1,21 @@
# @thirdweb-dev/auth
## 2.0.30
## 2.0.29
## 2.0.28
## 2.0.27
## 2.0.26
## 2.0.25
## 2.0.24
## 2.0.23
## 2.0.22
## 2.0.21
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/auth",
"version": "2.0.22",
"version": "2.0.30",
"exports": {
"./next": {
"module": "./next/dist/thirdweb-dev-auth-next.esm.js",
+56
View File
@@ -1,5 +1,61 @@
# thirdweb
## 0.8.16
### Patch Changes
- Updated dependencies [[`7cb8e59`](https://github.com/thirdweb-dev/js/commit/7cb8e59be48dc923316eb36ab43a1bef7364f6b1), [`0ed77d2`](https://github.com/thirdweb-dev/js/commit/0ed77d2c9e7526444ef660aab82a950865234aa6), [`04a47e8`](https://github.com/thirdweb-dev/js/commit/04a47e8971d28da2532ebd9db824eeb459c17bd4)]:
- @thirdweb-dev/sdk@3.6.0
## 0.8.15
### Patch Changes
- 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
- Updated dependencies []:
- @thirdweb-dev/sdk@3.4.4
## 0.8.10
### Patch Changes
- Updated dependencies [[`1c24c3c`](https://github.com/thirdweb-dev/js/commit/1c24c3c3c48476a824f817e09e7bf0fbe67c1db5), [`65fc7ba`](https://github.com/thirdweb-dev/js/commit/65fc7ba6fff093a971b53cb0979c39768e090ba1)]:
- @thirdweb-dev/sdk@3.4.3
## 0.8.9
### Patch Changes
- Updated dependencies [[`21c21c1`](https://github.com/thirdweb-dev/js/commit/21c21c1adfd09b60ad563bfd4c308597140de33c), [`3522917`](https://github.com/thirdweb-dev/js/commit/352291791ee900e1500f84f095290497934d2f60)]:
- @thirdweb-dev/sdk@3.4.2
## 0.8.8
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "thirdweb",
"main": "dist/cli/index.js",
"version": "0.8.8",
"version": "0.8.16",
"repository": "https://github.com/thirdweb-dev/js/tree/main/packages/cli",
"author": "thirdweb eng <[email protected]>",
"license": "Apache-2.0",
+14
View File
@@ -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"
}
+2 -2
View File
@@ -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",
+47
View File
@@ -1,5 +1,52 @@
# @thirdweb-dev/react
## 3.6.0
### Patch Changes
- [#386](https://github.com/thirdweb-dev/js/pull/386) [`612bcdd`](https://github.com/thirdweb-dev/js/commit/612bcdd16fc242d8b04bc70d609e44b0df47ed3f) Thanks [@jnsdls](https://github.com/jnsdls)! - [Gnosis Connector] - switch to safe.global hostname & add support for bsc and optimism chains
- [#364](https://github.com/thirdweb-dev/js/pull/364) [`7cb8e59`](https://github.com/thirdweb-dev/js/commit/7cb8e59be48dc923316eb36ab43a1bef7364f6b1) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Add experimental `useActiveClaimConditionForWallet()` hook
## 3.5.2
### Patch Changes
- [#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
- [#356](https://github.com/thirdweb-dev/js/pull/356) [`cc4613b`](https://github.com/thirdweb-dev/js/commit/cc4613b5fd69840f4f9cfd0ac1c4e6743e62fe52) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Add Pack and Multiwrap as NFTContract types
## 3.4.3
### Patch Changes
- [#353](https://github.com/thirdweb-dev/js/pull/353) [`1c24c3c`](https://github.com/thirdweb-dev/js/commit/1c24c3c3c48476a824f817e09e7bf0fbe67c1db5) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Add useUpdateCreators hook, fix CreatorInput schema
## 3.4.2
### Patch Changes
- [#348](https://github.com/thirdweb-dev/js/pull/348) [`7d9a4c6`](https://github.com/thirdweb-dev/js/commit/7d9a4c6abcad1b4f43e431ce7b0b38db9016ea9a) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - [SOL] - add `useUpdateRoyaltySettings()` hook
[SOL] - change `useRoyalty()` hook -> `useRoyaltySettings()` for consistency
- [#352](https://github.com/thirdweb-dev/js/pull/352) [`3522917`](https://github.com/thirdweb-dev/js/commit/352291791ee900e1500f84f095290497934d2f60) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - expose useExecuteAuctionSale and useAcceptDirectListingOffer
- [#351](https://github.com/thirdweb-dev/js/pull/351) [`aabbb14`](https://github.com/thirdweb-dev/js/commit/aabbb149e81d6824015a4797b2b6c929ca359545) Thanks [@jnsdls](https://github.com/jnsdls)! - [EVM] - fix wallet connection during dev (hot reload)
## 3.4.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/react",
"version": "3.4.1",
"version": "3.6.0",
"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;
}
`;
@@ -9,16 +9,21 @@ import {
useContext as useWagmiContext,
} from "wagmi";
// exerpt from https://docs.gnosis-safe.io/backend/available-services
const CHAIN_ID_TO_GNOSIS_SERVER_URL = {
// mainnet
1: "https://safe-transaction.mainnet.gnosis.io",
1: "https://safe-transaction-mainnet.safe.global",
// avalanche
43114: "https://safe-transaction.avalanche.gnosis.io",
43114: "https://safe-transaction-avalanche.safe.global",
// polygon
137: "https://safe-transaction.polygon.gnosis.io",
137: "https://safe-transaction-polygon.safe.global",
// goerli
5: "https://safe-transaction.goerli.gnosis.io",
};
5: "https://safe-transaction-goerli.safe.global",
// bsc
56: "https://safe-transaction-bsc.safe.global",
// optimism
10: "https://safe-transaction-optimism.safe.global",
} as const;
export interface GnosisConnectorArguments {
safeAddress: string;
@@ -28,6 +33,8 @@ export interface GnosisConnectorArguments {
const __IS_SERVER__ = typeof window === "undefined";
export class GnosisSafeConnector extends Connector {
static supportedChains = Object.keys(CHAIN_ID_TO_GNOSIS_SERVER_URL);
public supportedChains = GnosisSafeConnector.supportedChains;
id = "gnosis";
ready = __IS_SERVER__;
name = "Gnosis Safe";
@@ -60,6 +67,11 @@ export class GnosisSafeConnector extends Connector {
};
}
public isChainSupported(chainId: string | number) {
const id = normalizeChainId(chainId);
return !this.isChainUnsupported(id);
}
private async createSafeSigner() {
const signer = await this.previousConnector?.getSigner();
const safeAddress = this.config?.safeAddress;
@@ -33,7 +33,17 @@ export const ThirdwebConnectedWalletProvider: React.FC<
const { rpcUrlMap } = useThirdwebConfigContext();
const [contextValue, setContextValue] =
useState<ThirdwebConnectedWalletContext>(INITIAL_CONTEXT_VALUE);
useState<ThirdwebConnectedWalletContext>({
...INITIAL_CONTEXT_VALUE,
signer: signer ? signer : undefined,
});
useEffect(() => {
setContextValue((val) => ({
...val,
signer: signer ? signer : undefined,
}));
}, [signer]);
useEffect(() => {
let s = signer;
@@ -2,7 +2,7 @@ import {
RequiredParam,
requiredParamInvariant,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../../providers/base";
import { useSDK, useSDKChainId } from "../../providers/base";
import { getErcs, DropContract, WalletAddress } from "../../types";
import {
cacheKeys,
@@ -14,8 +14,12 @@ import {
ClaimCondition,
ClaimConditionFetchOptions,
ClaimConditionInput,
SnapshotEntryWithProof,
fetchCurrencyValue,
convertToReadableQuantity,
fetchCurrencyMetadata,
} from "@thirdweb-dev/sdk";
import { BigNumberish } from "ethers";
import { BigNumberish, utils } from "ethers";
import invariant from "tiny-invariant";
/**
@@ -45,7 +49,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 +64,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 +110,80 @@ 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 +198,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
@@ -242,6 +319,134 @@ export function useClaimIneligibilityReasons(
);
}
/**
* Use this to check the claim condition for a given wallet address (including checking for overrides that may exist for that given wallet address).
*
* @param contract - an instance of a contract that extends the ERC20, ERC721 or ERC1155 spec and implements the `claimConditions` extension.
* @param walletAddress - the wallet address to check the active claim condition for
* @param tokenId - the id of the token to fetch the claim conditions for (if the contract is an ERC1155 contract)
* @returns the active claim conditon for the wallet address or null if there is no active claim condition
*
* @beta
*/
export function useActiveClaimConditionForWallet(
contract: RequiredParam<DropContract>,
walletAddress: RequiredParam<WalletAddress>,
tokenId?: BigNumberish,
) {
const sdk = useSDK();
const contractAddress = contract?.getAddress();
const { erc1155, erc721, erc20 } = getErcs(contract);
return useQueryWithNetwork<ClaimCondition | null>(
cacheKeys.extensions.claimConditions.useActiveClaimConditionForWallet(
contractAddress,
walletAddress || "_NO_WALLET_",
tokenId,
),
async () => {
// if we do not have a walletAddress just do the same logic as basic useClaimCondition
if (!walletAddress) {
if (erc1155) {
requiredParamInvariant(
tokenId,
"tokenId is required for ERC1155 claim conditions",
);
return erc1155.claimConditions.getActive(tokenId);
}
if (erc721) {
return erc721.claimConditions.getActive();
}
if (erc20) {
return erc20.claimConditions.getActive();
}
throw new Error("Contract must be ERC721, ERC1155 or ERC20");
}
invariant(sdk, "sdk is required");
let activeGeneralClaimCondition: ClaimCondition | null = null;
let claimerProofForWallet: SnapshotEntryWithProof | null = null;
if (erc1155) {
requiredParamInvariant(tokenId, "tokenId is required for ERC1155");
const [cc, cp] = await Promise.all([
erc1155.claimConditions.getActive(tokenId),
erc1155.claimConditions.getClaimerProofs(tokenId, walletAddress),
]);
activeGeneralClaimCondition = cc;
claimerProofForWallet = cp;
}
if (erc721) {
const [cc, cp] = await Promise.all([
erc721.claimConditions.getActive(),
erc721.claimConditions.getClaimerProofs(walletAddress),
]);
activeGeneralClaimCondition = cc;
claimerProofForWallet = cp;
}
if (erc20) {
const [cc, cp] = await Promise.all([
erc20.claimConditions.getActive(),
erc20.claimConditions.getClaimerProofs(walletAddress),
]);
activeGeneralClaimCondition = cc;
claimerProofForWallet = cp;
}
// if there is no active claim condition nothing matters, return null
if (!activeGeneralClaimCondition) {
return null;
}
// if there is no claimer proof then just fall back to the active general claim condition
if (!claimerProofForWallet) {
return activeGeneralClaimCondition;
}
const { maxClaimable, currencyAddress, price } = claimerProofForWallet;
const currencyWithOverride =
currencyAddress || activeGeneralClaimCondition.currencyAddress;
const currencyMetadata = await fetchCurrencyMetadata(
sdk.getProvider(),
currencyWithOverride,
);
const normalizedPrize = price
? utils.parseUnits(price, currencyMetadata.decimals)
: null;
const priceWithOverride =
normalizedPrize || activeGeneralClaimCondition.price;
const maxClaimableWithOverride =
// have to transform this the same way that claim conditions do it in SDK
convertToReadableQuantity(maxClaimable, currencyMetadata.decimals) ||
activeGeneralClaimCondition.maxClaimablePerWallet;
const currencyValueWithOverride = await fetchCurrencyValue(
sdk.getProvider(),
currencyWithOverride,
priceWithOverride,
);
return {
// inherit the entire claim condition
...activeGeneralClaimCondition,
// overwrite all keys that could be changed based on overwrites
maxClaimablePerWallet: maxClaimableWithOverride,
price: priceWithOverride,
currency: currencyWithOverride,
currencyAddress: currencyWithOverride,
currencyMetadata: currencyValueWithOverride,
};
},
{
// Checks that happen here:
// 1. if the contract is based on ERC1155 contract => tokenId cannot be `undefined`
// 2. if the contract is NOT based on ERC1155 => we have to have either an ERC721 or ERC20 contract
enabled: erc1155 ? tokenId !== undefined : !!erc721 || !!erc20,
},
);
}
/** **********************/
/** WRITE HOOKS **/
/** **********************/
@@ -368,8 +573,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,
},
) {
@@ -3,7 +3,13 @@ import {
requiredParamInvariant,
} from "../../../core/query-utils/required-param";
import { useSDKChainId } from "../../providers/base";
import { BuyNowParams, MakeBidParams, MakeOfferParams } from "../../types";
import {
AcceptDirectOffer,
BuyNowParams,
ExecuteAuctionSale,
MakeBidParams,
MakeOfferParams,
} from "../../types";
import {
cacheKeys,
invalidateContractAndBalances,
@@ -572,6 +578,120 @@ export function useMakeOffer(contract: RequiredParam<Marketplace>) {
);
}
/**
* Accept an offer on a direct listing from an offeror, will accept the latest offer by the given offeror.
*
* @example
* ```jsx
* const Component = () => {
* const {
* mutate: acceptOffer,
* isLoading,
* error,
* } = useAcceptDirectListingOffer(">>YourMarketplaceContractInstance<<");
*
* if (error) {
* console.error("failed to accept offer", error);
* }
*
* return (
* <button
* disabled={isLoading}
* onClick={() => acceptOffer({ listingId: 1, addressOfOfferor: "0x..." })}
* >
* Accept offer
* </button>
* );
* };
* ```
*
* @param contract - an instance of a Marketplace contract
* @returns a mutation object that can be used to accept an offer on a direct listing
* @beta
*/
export function useAcceptDirectListingOffer(
contract: RequiredParam<Marketplace>,
) {
const activeChainId = useSDKChainId();
const contractAddress = contract?.getAddress();
const queryClient = useQueryClient();
const walletAddress = useAddress();
return useMutation(
async (data: AcceptDirectOffer) => {
invariant(walletAddress, "no wallet connected, cannot make bid");
requiredParamInvariant(contract?.direct, "No Direct instance provided");
return await contract.direct.acceptOffer(
data.listingId,
data.addressOfOfferor,
);
},
{
onSettled: () =>
invalidateContractAndBalances(
queryClient,
contractAddress,
activeChainId,
),
},
);
}
/**
* Execute an auction sale. Can only be executed once the auction has ended and the auction has a winning bid.
*
* @example
* ```jsx
* const Component = () => {
* const {
* mutate: executeAuctionSale,
* isLoading,
* error,
* } = useExecuteAuctionSale(">>YourMarketplaceContractInstance<<");
*
* if (error) {
* console.error("failed to execute sale", error);
* }
*
* return (
* <button
* disabled={isLoading}
* onClick={() => executeAuctionSale({ listingId: 1 })}
* >
* Execute sale
* </button>
* );
* };
* ```
*
* @param contract - an instance of a Marketplace contract
* @returns a mutation object that can be used to accept an offer on a direct listing
* @beta
*/
export function useExecuteAuctionSale(contract: RequiredParam<Marketplace>) {
const activeChainId = useSDKChainId();
const contractAddress = contract?.getAddress();
const queryClient = useQueryClient();
const walletAddress = useAddress();
return useMutation(
async (data: ExecuteAuctionSale) => {
invariant(walletAddress, "no wallet connected, cannot make bid");
requiredParamInvariant(
contract?.auction,
"No Auction marketplace instance provided",
);
return await contract.auction.executeSale(data.listingId);
},
{
onSettled: () =>
invalidateContractAndBalances(
queryClient,
contractAddress,
activeChainId,
),
},
);
}
/**
* Get all the offers for a listing
*
@@ -583,6 +703,7 @@ export function useMakeOffer(contract: RequiredParam<Marketplace>) {
*
* @param contract - an instance of a Marketplace contract
* @param listingId - the id of the listing to fetch offers for
* @beta
*/
export function useOffers(
contract: RequiredParam<Marketplace>,
+3 -5
View File
@@ -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
*
+21 -6
View File
@@ -21,7 +21,7 @@ import {
} from "@thirdweb-dev/sdk";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import { Signer } from "ethers";
import { createContext, useContext, useEffect, useMemo } from "react";
import { createContext, useContext, useEffect, useMemo, useState } from "react";
import invariant from "tiny-invariant";
interface TWSDKContext {
@@ -60,21 +60,36 @@ export const WrappedThirdwebSDKProvider: ComponentWithChildren<
}) => {
const { signer } = useThirdwebConnectedWalletContext();
const sdk = useMemo(() => {
const [sdk, setSDK] = useState<ThirdwebSDK>();
useEffect(() => {
if (!desiredChainId || typeof window === "undefined") {
return undefined;
}
const _sdk = new ThirdwebSDK(provider, sdkOptions, storageInterface);
// if we already have a signer from the wallet context, use it immediately
if (signer) {
_sdk.updateSignerOrProvider(signer);
}
(_sdk as any)._constructedAt = Date.now();
(_sdk as any)._chainId = desiredChainId;
return _sdk;
setSDK(_sdk);
// explicitly *not* passing the signer, if we have it we use it if we don't we don't
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [provider, sdkOptions, storageInterface, desiredChainId]);
useEffect(() => {
if (signer && sdk && (sdk as any)._chainId === desiredChainId) {
sdk.updateSignerOrProvider(signer);
if (sdk && (sdk as any)._chainId === desiredChainId) {
if (signer) {
sdk.updateSignerOrProvider(signer);
} else {
sdk.updateSignerOrProvider(provider);
}
}
}, [signer, sdk, desiredChainId]);
}, [signer, sdk, desiredChainId, provider]);
const ctxValue = useMemo(
() => ({
+11
View File
@@ -14,6 +14,8 @@ import type {
ClaimOptions,
NFTMetadataOrUri,
Token,
Multiwrap,
Pack,
} from "@thirdweb-dev/sdk";
import type { Edition } from "@thirdweb-dev/sdk/dist/declarations/src/evm/contracts/prebuilt-implementations/edition";
import type { EditionDrop } from "@thirdweb-dev/sdk/dist/declarations/src/evm/contracts/prebuilt-implementations/edition-drop";
@@ -68,6 +70,8 @@ export type TokenBurnParams = {
export type NFTContract =
| NFTCollection
| Edition
| Pack
| Multiwrap
| Exclude<DropContract, "TokenDrop">;
/**
@@ -217,6 +221,13 @@ export type MakeOfferParams = {
pricePerToken: Price;
quantity?: Amount;
};
export type AcceptDirectOffer = {
listingId: BigNumberish;
addressOfOfferor: string;
};
export type ExecuteAuctionSale = {
listingId: BigNumberish;
};
export type BuyNowParams<TListingType = ListingType> =
TListingType extends ListingType.Direct
@@ -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 },
@@ -303,6 +313,23 @@ export const cacheKeys = {
]
: ["claimConditions", "getIneligibilityReasons", params],
),
// combinations of queries cache keys
useActiveClaimConditionForWallet: (
contractAddress: RequiredParam<ContractAddress>,
walletAddress: WalletAddress,
tokenId?: BigNumberish,
) =>
createContractCacheKey(
contractAddress,
tokenId
? [
"claimConditions",
"useActiveClaimConditionForWallet",
{ tokenId, walletAddress },
,
]
: ["claimConditions", "getIneligibilityReasons", { walletAddress }],
),
},
// primary sale contracts
@@ -30,7 +30,7 @@ export function nftRoyaltyQuery(
*
* export default function Component() {
* const { program } = useProgram("{{program_address}}");
* const { data: royalty, isLoading } = useRoyalty(program);
* const { data: royalty, isLoading } = useRoyaltySettings(program);
*
* return (
* <p>{royalty}</p>
@@ -40,6 +40,6 @@ export function nftRoyaltyQuery(
*
* @public
*/
export function useRoyalty(program: RequiredParam<NFTCollection | NFTDrop>) {
export function useRoyaltySettings(program: RequiredParam<NFTCollection | NFTDrop>) {
return useQuery(nftRoyaltyQuery(program));
}
@@ -0,0 +1,47 @@
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
import {
requiredParamInvariant,
RequiredParam,
} from "../../../core/query-utils/required-param";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import type { CreatorInput, NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
/**
* Update the creators for an NFT program
* @param program - The NFT program instance to update the creators for
*
* @example
* ```jsx
* import { useProgram, useUpdateCreators } from "@thirdweb-dev/react/solana";
*
* export default function Component() {
* const { program } = useProgram("{{program_address}}");
* const { mutateAsync: updateCreators, isLoading, error } = useUpdateCreators(program);
*
* return (
* <button
* onClick={() => updateCreators([{ address: "0x...", share: 10 }])}
* >
* Update Creators
* </button>
* )
* }
* ```
*
* @public
*/
export function useUpdateCreators(
program: RequiredParam<NFTCollection | NFTDrop>,
) {
const queryClient = useQueryClient();
return useMutation(
async (creators: CreatorInput[]) => {
requiredParamInvariant(program, "program is required");
return await program.updateCreators(creators);
},
{
onSettled: () =>
queryClient.invalidateQueries(createSOLProgramQueryKey(program)),
},
);
}
@@ -0,0 +1,47 @@
import { createSOLProgramQueryKey } from "../../../core/query-utils/query-key";
import {
requiredParamInvariant,
RequiredParam,
} from "../../../core/query-utils/required-param";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import type { NFTCollection, NFTDrop } from "@thirdweb-dev/sdk/solana";
/**
* Update the royalty for an NFT program
* @param program - The NFT program instance to update the royalty for
*
* @example
* ```jsx
* import { useProgram, useUpdateRoyaltySettings } from "@thirdweb-dev/react/solana";
*
* export default function Component() {
* const { program } = useProgram("{{program_address}}");
* const { mutateAsync: updateRoyalties, isLoading, error } = useUpdateRoyaltySettings(program);
*
* return (
* <button
* onClick={() => updateRoyalties(300)}
* >
* Update Royalties
* </button>
* )
* }
* ```
*
* @public
*/
export function useUpdateRoyaltySettings(
program: RequiredParam<NFTCollection | NFTDrop>,
) {
const queryClient = useQueryClient();
return useMutation(
async (sellerFeeBasisPoints: number) => {
requiredParamInvariant(program, "program is required");
return await program.updateRoyalty(sellerFeeBasisPoints);
},
{
onSettled: () =>
queryClient.invalidateQueries(createSOLProgramQueryKey(program)),
},
);
}
@@ -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;
+4
View File
@@ -14,6 +14,10 @@ export * from "./hooks/program/useProgramAccountType";
export * from "./hooks/nft/useNFTs";
export * from "./hooks/nft/useTransferNFT";
export * from "./hooks/nft/useBurnNFT";
export * from "./hooks/nft/useRoyaltySettings";
export * from "./hooks/nft/useUpdateRoyaltySettings";
export * from "./hooks/nft/useCreators";
export * from "./hooks/nft/useUpdateCreators";
// NFT Collection
export * from "./hooks/nft/collection/useMintNFT";
+85
View File
@@ -1,5 +1,90 @@
# @thirdweb-dev/sdk
## 3.6.0
### Minor Changes
- [#364](https://github.com/thirdweb-dev/js/pull/364) [`7cb8e59`](https://github.com/thirdweb-dev/js/commit/7cb8e59be48dc923316eb36ab43a1bef7364f6b1) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Enable new Drop contracts
### Patch Changes
- [#382](https://github.com/thirdweb-dev/js/pull/382) [`0ed77d2`](https://github.com/thirdweb-dev/js/commit/0ed77d2c9e7526444ef660aab82a950865234aa6) Thanks [@jnsdls](https://github.com/jnsdls)! - Add `constructorParams` to `ExtraPublishMetadataSchemaInput` schema to support better deploys from releases.
- [#385](https://github.com/thirdweb-dev/js/pull/385) [`04a47e8`](https://github.com/thirdweb-dev/js/commit/04a47e8971d28da2532ebd9db824eeb459c17bd4) Thanks [@adam-maj](https://github.com/adam-maj)! - Add retroactive royalty and creator setting
## 3.5.2
### Patch Changes
- [#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
### Patch Changes
- [#353](https://github.com/thirdweb-dev/js/pull/353) [`1c24c3c`](https://github.com/thirdweb-dev/js/commit/1c24c3c3c48476a824f817e09e7bf0fbe67c1db5) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Add useUpdateCreators hook, fix CreatorInput schema
- [#357](https://github.com/thirdweb-dev/js/pull/357) [`65fc7ba`](https://github.com/thirdweb-dev/js/commit/65fc7ba6fff093a971b53cb0979c39768e090ba1) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Expose claimConditions.getClaimerProofs(address) to get allowlist information for a given address
## 3.4.2
### Patch Changes
- [#344](https://github.com/thirdweb-dev/js/pull/344) [`21c21c1`](https://github.com/thirdweb-dev/js/commit/21c21c1adfd09b60ad563bfd4c308597140de33c) Thanks [@nachoiacovino](https://github.com/nachoiacovino)! - Update internal function resolveContractUriFromAddress -> resolveContractUriFromAddress
- [#352](https://github.com/thirdweb-dev/js/pull/352) [`3522917`](https://github.com/thirdweb-dev/js/commit/352291791ee900e1500f84f095290497934d2f60) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Expose marketplace.auction.executeSale(listingId)
## 3.4.1
### Patch Changes
+1 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@thirdweb-dev/sdk",
"version": "3.4.1",
"version": "3.6.0",
"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",
+17 -4
View File
@@ -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
@@ -72,12 +78,12 @@ export const BigNumberTransformSchema = z
export const BasisPointsSchema = z
.number()
.max(MAX_BPS, "Cannot exeed 100%")
.max(MAX_BPS, "Cannot exceed 100%")
.min(0, "Cannot be below 0%");
export const PercentSchema = z
.number()
.max(100, "Cannot exeed 100%")
.max(100, "Cannot exceed 100%")
.min(0, "Cannot be below 0%");
export const HexColor = z.union([
@@ -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));
});
+244 -96
View File
@@ -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 { IClaimCondition } from "@thirdweb-dev/contracts-js/dist/declarations/src/Drop";
import { IDropClaimCondition_V2 } from "@thirdweb-dev/contracts-js/dist/declarations/src/IDropERC20_V2";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import {
BigNumber,
@@ -45,6 +50,7 @@ import {
* @returns - `overrides` and `proofs` as an object.
*/
export async function prepareClaim(
addressToClaim: string,
quantity: BigNumberish,
activeClaimCondition: ClaimCondition,
merkleMetadataFetcher: () => Promise<Record<string, string>>,
@@ -52,27 +58,57 @@ 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,11 @@ export async function prepareClaim(
return {
overrides,
proofs,
maxQuantityPerTransaction: maxClaimable,
price,
currencyAddress,
maxClaimable,
price: pricePerToken,
currencyAddress: currencyAddress,
priceInProof,
currencyAddressInProof,
};
}
@@ -139,6 +187,8 @@ export async function fetchSnapshot(
return snapshotData.claims.map((claim) => ({
address: claim.address,
maxClaimable: claim.maxClaimable,
price: claim.price,
currencyAddress: claim.currencyAddress,
}));
}
}
@@ -150,7 +200,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 +215,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);
@@ -231,53 +287,20 @@ export async function updateExistingClaimConditions(
}
/**
* Fetches the proof for the current signer for a particular wallet.
*
* @param addressToClaim
* @param merkleRoot - The merkle root of the condition to check.
* @param tokenDecimals
* @param merkleMetadata
* @param storage
* @returns - The proof for the current signer for the specified condition.
*/
export async function getClaimerProofs(
addressToClaim: string,
merkleRoot: string,
tokenDecimals: number,
merkleMetadata: Record<string, string>,
storage: ThirdwebStorage,
): Promise<{ maxClaimable: BigNumber; proof: string[] }> {
const claim = await fetchSnapshotEntryForAddress(
addressToClaim,
merkleRoot,
merkleMetadata,
storage,
);
if (!claim) {
return {
proof: [],
maxClaimable: BigNumber.from(0),
};
}
return {
proof: claim.proof,
maxClaimable: ethers.utils.parseUnits(claim.maxClaimable, tokenDecimals),
};
}
/**
* 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 +308,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 +324,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 +381,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 +491,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 +508,8 @@ export async function transformResultToClaimCondition(
pm.maxClaimableSupply,
tokenDecimals,
);
const quantityLimitPerTransaction = convertToReadableQuantity(
pm.quantityLimitPerTransaction,
const maxClaimablePerWallet = convertToReadableQuantity(
pm.maxClaimablePerWallet,
tokenDecimals,
);
const availableSupply = convertToReadableQuantity(
@@ -401,13 +520,17 @@ export async function transformResultToClaimCondition(
pm.supplyClaimed,
tokenDecimals,
);
let resolvedMetadata;
if (pm.metadata) {
resolvedMetadata = await storage.downloadJSON(pm.metadata);
}
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 +539,19 @@ export async function transformResultToClaimCondition(
snapshot: shouldDownloadSnapshot
? await fetchSnapshot(pm.merkleRoot, merkleMetadata, storage)
: undefined,
metadata: resolvedMetadata,
});
}
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 +559,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,
+24 -1
View File
@@ -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";
@@ -33,6 +33,14 @@ export function cleanCurrencyAddress(currencyAddress: string): string {
return currencyAddress;
}
/**
*
* @param provider
* @param inputPrice
* @param currencyAddress
* @returns
* @internal
*/
export async function normalizePriceValue(
provider: providers.Provider,
inputPrice: Price,
@@ -42,6 +50,13 @@ export async function normalizePriceValue(
return utils.parseUnits(AmountSchema.parse(inputPrice), metadata.decimals);
}
/**
*
* @param provider
* @param asset
* @returns
* @internal
*/
export async function fetchCurrencyMetadata(
provider: providers.Provider,
asset: string,
@@ -73,6 +88,14 @@ export async function fetchCurrencyMetadata(
}
}
/**
*
* @param providerOrSigner
* @param asset
* @param price
* @returns
* @internal
*/
export async function fetchCurrencyValue(
providerOrSigner: providers.Provider,
asset: string,
@@ -6,7 +6,9 @@ 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 {
Abi,
AbiEvent,
AbiFunction,
AbiSchema,
@@ -23,7 +25,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";
@@ -32,26 +33,57 @@ import { z } from "zod";
* @param abi
* @param feature
*/
function matchesAbiInterface(
abi: z.input<typeof AbiSchema>,
feature: Feature,
): boolean {
function matchesAbiInterface(abi: Abi, feature: Feature): boolean {
// returns true if all the functions in `interfaceToMatch` are found in `contract` (removing any duplicates)
const contractFn = [
...new Set(extractFunctionsFromAbi(abi).map((f) => f.name)),
];
const interfaceFn = [
...new Set(
feature.abis
.flatMap((i: any) => extractFunctionsFromAbi(i))
.map((f: AbiFunction) => f.name),
),
];
return hasMatchingAbi(abi, feature.abis);
}
return (
contractFn.filter((k) => interfaceFn.includes(k)).length ===
interfaceFn.length
/**
* @internal
* @param contractWrapper
* @param abi
* @returns
*/
export function matchesPrebuiltAbi<T extends BaseContract>(
contractWrapper: ContractWrapper<BaseContract>,
abi: Abi,
): contractWrapper is ContractWrapper<T> {
return hasMatchingAbi(contractWrapper.abi as Abi, [abi]);
}
/**
* @internal
* @param contractAbi
* @param featureAbis
* @returns
*/
export function hasMatchingAbi(contractAbi: Abi, featureAbis: readonly Abi[]) {
const contractFn = extractFunctionsFromAbi(contractAbi);
const interfaceFn = featureAbis.flatMap((i: any) =>
extractFunctionsFromAbi(i),
);
// match every function and their arguments
const intersection = contractFn.filter((fn) => {
const match = interfaceFn.find(
(iFn) =>
iFn.name === fn.name &&
iFn.inputs.length === fn.inputs.length &&
iFn.inputs.every((i, index) => {
if (i.type === "tuple" || i.type === "tuple[]") {
// check that all properties in the tuple are the same type
return (
i.type === fn.inputs[index].type &&
i.components?.every((c, cIndex) => {
return c.type === fn.inputs[index].components?.[cIndex]?.type;
})
);
}
return i.type === fn.inputs[index].type;
}),
);
return match !== undefined;
});
return intersection.length === interfaceFn.length;
}
/**
@@ -145,7 +177,7 @@ export function extractFunctionParamsFromAbi(
* @param metadata
*/
export function extractFunctionsFromAbi(
abi: z.input<typeof AbiSchema>,
abi: Abi,
metadata?: Record<string, any>,
): AbiFunction[] {
const functions = (abi || []).filter((el) => el.type === "function");
@@ -178,7 +210,7 @@ export function extractFunctionsFromAbi(
* @param metadata
*/
export function extractEventsFromAbi(
abi: z.input<typeof AbiSchema>,
abi: Abi,
metadata?: Record<string, any>,
): AbiEvent[] {
const events = (abi || []).filter((el) => el.type === "event");
@@ -405,7 +437,9 @@ export async function fetchSourceFilesFromMetadata(
Object.entries(publishedMetadata.metadata.sources).map(
async ([path, info]) => {
const urls = (info as any).urls as string[];
const ipfsLink = urls.find((url) => url.includes("ipfs"));
const ipfsLink = urls
? urls.find((url) => url.includes("ipfs"))
: undefined;
if (ipfsLink) {
const ipfsHash = ipfsLink.split("ipfs/")[1];
// 5 sec timeout for sources that haven't been uploaded to ipfs
@@ -423,7 +457,9 @@ export async function fetchSourceFilesFromMetadata(
} else {
return {
filename: path,
source: "Could not find source for this contract",
source:
(info as any).content ||
"Could not find source for this contract",
};
}
},
+7
View File
@@ -3,3 +3,10 @@ export * from "./snapshots";
export * from "./role";
export * from "./feature-detection";
export * from "./version-checker";
// export helpful util
export {
fetchCurrencyValue,
fetchCurrencyMetadata,
normalizePriceValue,
} from "./currency";
export { convertToReadableQuantity } from "./claim-conditions";
+27
View File
@@ -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 -1
View File
@@ -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,
+1 -1
View File
@@ -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;
}
}
+12 -19
View File
@@ -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)],
);
}
+113
View File
@@ -1,3 +1,4 @@
import { PrebuiltContractType } from "../core/types";
import { ChainId, SUPPORTED_CHAIN_ID } from "./chains";
import { constants } from "ethers";
@@ -142,6 +143,118 @@ export const CONTRACT_ADDRESSES: Record<
},
};
type DropContract = Extract<
PrebuiltContractType,
"nft-drop" | "token-drop" | "edition-drop" | "signature-drop"
>;
export const APPROVED_IMPLEMENTATIONS: Record<
SUPPORTED_CHAIN_ID,
Record<DropContract, string>
> = {
[ChainId.Mainnet]: {
"nft-drop": "0x60fF9952e0084A6DEac44203838cDC91ABeC8736",
"edition-drop": "0x74af262d0671F378F97a1EDC3d0970Dbe8A1C550",
"token-drop": "0xE1eE43D23f247b6A9aF81fcE2766E76709482728",
"signature-drop": "0x6fD690EB509BdE4C50028C5D9C0dE3750C2Fad6A",
},
[ChainId.Polygon]: {
"nft-drop": "0xB96508050Ba0925256184103560EBADA912Fcc69",
"edition-drop": "0x74af262d0671F378F97a1EDC3d0970Dbe8A1C550",
"token-drop": "0x5A8eA4Adad8289746D073947BA06D69A62499aaf",
"signature-drop": "0xBE2fDc35410E268e41Bec62DBb01AEb43245c7d5",
},
[ChainId.Fantom]: {
"nft-drop": "0x2A396b2D90BAcEF19cDa973586B2633d22710fC2",
"edition-drop": "0x06395FCF9AC6ED827f9dD6e776809cEF1Be0d21B",
"token-drop": "0x0148b28a38efaaC31b6aa0a6D9FEb70FE7C91FFa",
"signature-drop": "0xe135Ef65C2B2213C3fD56d0Bd6500A2cA147aC10",
},
[ChainId.Avalanche]: {
"nft-drop": "0x9cF91118C8ee2913F0588e0F10e36B3d63F68bF6",
"edition-drop": "0x135fC9D26E5eC51260ece1DF4ED424E2f55c7766",
"token-drop": "0xca0B071899E575BA86495D46c5066971b6f3A901",
"signature-drop": "0x1d47526C3292B0130ef0afD5F02c1DA052A017B3",
},
[ChainId.Optimism]: {
"nft-drop": "0xFBd7D24d80ee005671E731a7287DEB6073264dD1",
"edition-drop": "0xe135Ef65C2B2213C3fD56d0Bd6500A2cA147aC10",
"token-drop": "0x902Dd246e66d8C3CE652375a723F2a52b43b9AAE",
"signature-drop": "0x8a4cd3549e548bbEEb38C16E041FFf040a5acabD",
},
[ChainId.Arbitrum]: {
"nft-drop": "0xC4903c1Ff5367b9ac2c349B63DC2409421AaEE2a",
"edition-drop": "0xCcddcec1831646Beff2753249f1B9C580327E89F",
"token-drop": "0x1b5947e1a2d5a29D0df20931DeAB0B87818209B9",
"signature-drop": "0x2dF9851af45dd41C8584ac55D983C604da985Bc7",
},
[ChainId.BinanceSmartChainMainnet]: {
"nft-drop": "0x902Dd246e66d8C3CE652375a723F2a52b43b9AAE",
"edition-drop": "0x2A396b2D90BAcEF19cDa973586B2633d22710fC2",
"token-drop": "0xe135Ef65C2B2213C3fD56d0Bd6500A2cA147aC10",
"signature-drop": "0xFBd7D24d80ee005671E731a7287DEB6073264dD1",
},
[ChainId.Goerli]: {
"nft-drop": "0xD11c97DD5F5546B5bBd630D7D1d7327481B0b92C",
"edition-drop": "0x5A8eA4Adad8289746D073947BA06D69A62499aaf",
"token-drop": "0x5680933221B752EB443654a014f88B101F868d50",
"signature-drop": "0x1b5947e1a2d5a29D0df20931DeAB0B87818209B9",
},
[ChainId.Mumbai]: {
"nft-drop": "0xC4903c1Ff5367b9ac2c349B63DC2409421AaEE2a",
"edition-drop": "0xCcddcec1831646Beff2753249f1B9C580327E89F",
"token-drop": "0x1b5947e1a2d5a29D0df20931DeAB0B87818209B9",
"signature-drop": "0x2dF9851af45dd41C8584ac55D983C604da985Bc7",
},
[ChainId.FantomTestnet]: {
"nft-drop": "0x8a4cd3549e548bbEEb38C16E041FFf040a5acabD",
"edition-drop": "0x902Dd246e66d8C3CE652375a723F2a52b43b9AAE",
"token-drop": "0xFBd7D24d80ee005671E731a7287DEB6073264dD1",
"signature-drop": "0x5A8eA4Adad8289746D073947BA06D69A62499aaf",
},
[ChainId.AvalancheFujiTestnet]: {
"nft-drop": "0xD11c97DD5F5546B5bBd630D7D1d7327481B0b92C",
"edition-drop": "0xE1eE43D23f247b6A9aF81fcE2766E76709482728",
"token-drop": "0x6fD690EB509BdE4C50028C5D9C0dE3750C2Fad6A",
"signature-drop": "0xCcddcec1831646Beff2753249f1B9C580327E89F",
},
[ChainId.OptimismGoerli]: {
"nft-drop": "0xCcddcec1831646Beff2753249f1B9C580327E89F",
"edition-drop": "0x6fD690EB509BdE4C50028C5D9C0dE3750C2Fad6A",
"token-drop": "0xD11c97DD5F5546B5bBd630D7D1d7327481B0b92C",
"signature-drop": "0x1b5947e1a2d5a29D0df20931DeAB0B87818209B9",
},
[ChainId.ArbitrumGoerli]: {
"nft-drop": "0x9CfE807a5b124b962064Fa8F7FD823Cc701255b6",
"edition-drop": "0x9cF91118C8ee2913F0588e0F10e36B3d63F68bF6",
"token-drop": "0x1d47526C3292B0130ef0afD5F02c1DA052A017B3",
"signature-drop": "0xE1eE43D23f247b6A9aF81fcE2766E76709482728",
},
[ChainId.BinanceSmartChainTestnet]: {
"nft-drop": "",
"edition-drop": "",
"token-drop": "",
"signature-drop": "", // TODO
},
};
/**
* @internal
* @param chainId
* @param contractType
*/
export function getApprovedImplementation(
chainId: SUPPORTED_CHAIN_ID, // TODO use SupportedChainId once we deploy to all chains
contractType: PrebuiltContractType,
): string | null {
if (chainId in APPROVED_IMPLEMENTATIONS) {
const approvedImpls = APPROVED_IMPLEMENTATIONS[chainId];
if (contractType in approvedImpls) {
return approvedImpls[contractType as keyof typeof approvedImpls];
}
}
return null;
}
/**
* @internal
*/
-1
View File
@@ -6,7 +6,6 @@ export enum ChainId {
Goerli = 5,
Polygon = 137,
Mumbai = 80001,
Harmony = 1666600000,
Localhost = 1337,
Hardhat = 31337,
Fantom = 250,
@@ -3,33 +3,45 @@ import {
FEATURE_TOKEN_BATCH_MINTABLE,
FEATURE_TOKEN_BURNABLE,
FEATURE_TOKEN_MINTABLE,
FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS,
FEATURE_TOKEN_SIGNATURE_MINTABLE,
FEATURE_TOKEN_CLAIM_CONDITIONS_V1,
FEATURE_TOKEN_CLAIM_CONDITIONS_V2,
FEATURE_TOKEN_CLAIM_PHASES_V2,
FEATURE_TOKEN_CLAIM_PHASES_V1,
} from "./erc20-features";
import {
FEATURE_NFT,
FEATURE_NFT_BATCH_MINTABLE,
FEATURE_NFT_BURNABLE,
FEATURE_NFT_CLAIMABLE,
FEATURE_NFT_CLAIM_CUSTOM,
FEATURE_NFT_LAZY_MINTABLE,
FEATURE_NFT_ENUMERABLE,
FEATURE_NFT_MINTABLE,
FEATURE_NFT_REVEALABLE,
FEATURE_NFT_SIGNATURE_MINTABLE,
FEATURE_NFT_SUPPLY,
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS,
FEATURE_NFT_CLAIM_CONDITIONS_V1,
FEATURE_NFT_CLAIM_CONDITIONS_V2,
FEATURE_NFT_CLAIM_PHASES_V2,
FEATURE_NFT_TIERED_DROP,
FEATURE_NFT_CLAIM_PHASES_V1,
FEATURE_NFT_SIGNATURE_MINTABLE_V1,
FEATURE_NFT_SIGNATURE_MINTABLE_V2,
} from "./erc721-features";
import {
FEATURE_EDITION,
FEATURE_EDITION_BATCH_MINTABLE,
FEATURE_EDITION_BURNABLE,
FEATURE_EDITION_CLAIMABLE,
FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS,
FEATURE_EDITION_CLAIM_CUSTOM,
FEATURE_EDITION_CLAIM_CONDITIONS_V1,
FEATURE_EDITION_CLAIM_CONDITIONS_V2,
FEATURE_EDITION_CLAIM_PHASES_V2,
FEATURE_EDITION_ENUMERABLE,
FEATURE_EDITION_LAZY_MINTABLE,
FEATURE_EDITION_LAZY_MINTABLE_V1,
FEATURE_EDITION_LAZY_MINTABLE_V2,
FEATURE_EDITION_MINTABLE,
FEATURE_EDITION_REVEALABLE,
FEATURE_EDITION_SIGNATURE_MINTABLE,
FEATURE_EDITION_CLAIM_PHASES_V1,
} from "./erc1155-features";
import {
FEATURE_METADATA,
@@ -50,7 +62,10 @@ export type Feature =
| typeof FEATURE_TOKEN_MINTABLE
| typeof FEATURE_TOKEN_BATCH_MINTABLE
| typeof FEATURE_TOKEN_BURNABLE
| typeof FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS
| typeof FEATURE_TOKEN_CLAIM_CONDITIONS_V1
| typeof FEATURE_TOKEN_CLAIM_CONDITIONS_V2
| typeof FEATURE_TOKEN_CLAIM_PHASES_V1
| typeof FEATURE_TOKEN_CLAIM_PHASES_V2
| typeof FEATURE_TOKEN_SIGNATURE_MINTABLE
| typeof FEATURE_NFT
| typeof FEATURE_NFT_SUPPLY
@@ -58,17 +73,26 @@ export type Feature =
| typeof FEATURE_NFT_MINTABLE
| typeof FEATURE_NFT_BATCH_MINTABLE
| typeof FEATURE_NFT_LAZY_MINTABLE
| typeof FEATURE_NFT_CLAIMABLE
| typeof FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS
| typeof FEATURE_NFT_CLAIM_CUSTOM
| typeof FEATURE_NFT_CLAIM_CONDITIONS_V1
| typeof FEATURE_NFT_CLAIM_CONDITIONS_V2
| typeof FEATURE_NFT_CLAIM_PHASES_V1
| typeof FEATURE_NFT_CLAIM_PHASES_V2
| typeof FEATURE_NFT_REVEALABLE
| typeof FEATURE_NFT_SIGNATURE_MINTABLE
| typeof FEATURE_NFT_SIGNATURE_MINTABLE_V1
| typeof FEATURE_NFT_SIGNATURE_MINTABLE_V2
| typeof FEATURE_NFT_BURNABLE
| typeof FEATURE_NFT_TIERED_DROP
| typeof FEATURE_EDITION
| typeof FEATURE_EDITION_ENUMERABLE
| typeof FEATURE_EDITION_MINTABLE
| typeof FEATURE_EDITION_CLAIMABLE
| typeof FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS
| typeof FEATURE_EDITION_LAZY_MINTABLE
| typeof FEATURE_EDITION_CLAIM_CUSTOM
| typeof FEATURE_EDITION_CLAIM_CONDITIONS_V1
| typeof FEATURE_EDITION_CLAIM_CONDITIONS_V2
| typeof FEATURE_EDITION_CLAIM_PHASES_V1
| typeof FEATURE_EDITION_CLAIM_PHASES_V2
| typeof FEATURE_EDITION_LAZY_MINTABLE_V1
| typeof FEATURE_EDITION_LAZY_MINTABLE_V2
| typeof FEATURE_EDITION_REVEALABLE
| typeof FEATURE_EDITION_BATCH_MINTABLE
| typeof FEATURE_EDITION_BURNABLE
@@ -1,7 +1,10 @@
import DelayedRevealAbi from "@thirdweb-dev/contracts-js/dist/abis/DelayedReveal.json";
import DropSinglePhase1155 from "@thirdweb-dev/contracts-js/dist/abis/DropSinglePhase1155.json";
import DropERC1155_V2Abi from "@thirdweb-dev/contracts-js/dist/abis/DropERC1155_V2.json";
import IBurnableERC1155Abi from "@thirdweb-dev/contracts-js/dist/abis/IBurnableERC1155.json";
import IClaimableERC1155 from "@thirdweb-dev/contracts-js/dist/abis/IClaimableERC1155.json";
import DelayedRevealAbi from "@thirdweb-dev/contracts-js/dist/abis/IDelayedReveal.json";
import IDropMultiPhase1155 from "@thirdweb-dev/contracts-js/dist/abis/IDrop1155.json";
import IDropSinglePhase1155 from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase1155.json";
import IDropSinglePhase1155_V1 from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase1155_V1.json";
import Erc1155Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC1155.json";
import Erc1155EnumerableAbi from "@thirdweb-dev/contracts-js/dist/abis/IERC1155Enumerable.json";
import ILazyMintAbi from "@thirdweb-dev/contracts-js/dist/abis/ILazyMint.json";
@@ -20,19 +23,52 @@ export const FEATURE_EDITION_BURNABLE = {
features: {},
} as const;
export const FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS = {
name: "ERC1155ClaimableWithConditions",
export const FEATURE_EDITION_CLAIM_CONDITIONS_V1 = {
name: "ERC1155ClaimConditionsV1",
namespace: "edition.drop.claim",
docLinks: {
sdk: "sdk.erc1155claimable",
contracts: "DropSinglePhase1155",
},
abis: [Erc1155Abi, ILazyMintAbi, DropSinglePhase1155],
abis: [Erc1155Abi, ILazyMintAbi, IDropSinglePhase1155_V1],
features: {},
} as const;
export const FEATURE_EDITION_CLAIMABLE = {
name: "ERC1155Claimable",
export const FEATURE_EDITION_CLAIM_CONDITIONS_V2 = {
name: "ERC1155ClaimConditionsV2",
namespace: "edition.drop.claim",
docLinks: {
sdk: "sdk.erc1155claimable",
contracts: "DropSinglePhase1155",
},
abis: [Erc1155Abi, ILazyMintAbi, IDropSinglePhase1155],
features: {},
} as const;
export const FEATURE_EDITION_CLAIM_PHASES_V2 = {
name: "ERC1155ClaimPhasesV2",
namespace: "edition.drop.claim",
docLinks: {
sdk: "sdk.erc1155claimable",
contracts: "Drop1155",
},
abis: [Erc1155Abi, ILazyMintAbi, IDropMultiPhase1155],
features: {},
} as const;
export const FEATURE_EDITION_CLAIM_PHASES_V1 = {
name: "ERC1155ClaimPhasesV1",
namespace: "edition.drop.claim",
docLinks: {
sdk: "sdk.erc1155claimable",
contracts: "Drop1155",
},
abis: [DropERC1155_V2Abi],
features: {},
} as const;
export const FEATURE_EDITION_CLAIM_CUSTOM = {
name: "ERC1155ClaimCustom",
namespace: "edition.drop.claim",
docLinks: {
sdk: "sdk.erc1155claimable",
@@ -42,21 +78,6 @@ export const FEATURE_EDITION_CLAIMABLE = {
features: {},
} as const;
export const FEATURE_EDITION_LAZY_MINTABLE = {
name: "ERC1155LazyMintable",
namespace: "edition.drop",
docLinks: {
sdk: "sdk.erc1155droppable",
contracts: "LazyMint",
},
abis: [Erc1155Abi, ILazyMintAbi],
features: {
[FEATURE_EDITION_CLAIMABLE.name]: FEATURE_EDITION_CLAIMABLE,
[FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS.name]:
FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS,
},
} as const;
export const FEATURE_EDITION_REVEALABLE = {
name: "ERC1155Revealable",
namespace: "edition.drop.revealer",
@@ -68,6 +89,38 @@ export const FEATURE_EDITION_REVEALABLE = {
features: {},
} as const;
export const FEATURE_EDITION_LAZY_MINTABLE_V2 = {
name: "ERC1155LazyMintableV2",
namespace: "edition.drop",
docLinks: {
sdk: "sdk.erc1155droppable",
contracts: "LazyMint",
},
abis: [Erc1155Abi, ILazyMintAbi],
features: {
[FEATURE_EDITION_REVEALABLE.name]: FEATURE_EDITION_REVEALABLE,
[FEATURE_EDITION_CLAIM_CUSTOM.name]: FEATURE_EDITION_CLAIM_CUSTOM,
[FEATURE_EDITION_CLAIM_CONDITIONS_V1.name]:
FEATURE_EDITION_CLAIM_CONDITIONS_V1,
[FEATURE_EDITION_CLAIM_CONDITIONS_V2.name]:
FEATURE_EDITION_CLAIM_CONDITIONS_V2,
[FEATURE_EDITION_CLAIM_PHASES_V2.name]: FEATURE_EDITION_CLAIM_PHASES_V2,
},
} as const;
export const FEATURE_EDITION_LAZY_MINTABLE_V1 = {
name: "ERC1155LazyMintableV1",
namespace: "edition.drop",
docLinks: {
sdk: "sdk.erc1155droppable",
contracts: "LazyMint",
},
abis: [DropERC1155_V2Abi],
features: {
[FEATURE_EDITION_CLAIM_PHASES_V1.name]: FEATURE_EDITION_CLAIM_PHASES_V1,
},
} as const;
export const FEATURE_EDITION_SIGNATURE_MINTABLE = {
name: "ERC1155SignatureMintable",
namespace: "edition.signature",
@@ -126,7 +179,8 @@ export const FEATURE_EDITION = {
[FEATURE_EDITION_BURNABLE.name]: FEATURE_EDITION_BURNABLE,
[FEATURE_EDITION_ENUMERABLE.name]: FEATURE_EDITION_ENUMERABLE,
[FEATURE_EDITION_MINTABLE.name]: FEATURE_EDITION_MINTABLE,
[FEATURE_EDITION_LAZY_MINTABLE.name]: FEATURE_EDITION_LAZY_MINTABLE,
[FEATURE_EDITION_LAZY_MINTABLE_V1.name]: FEATURE_EDITION_LAZY_MINTABLE_V1,
[FEATURE_EDITION_LAZY_MINTABLE_V2.name]: FEATURE_EDITION_LAZY_MINTABLE_V2,
[FEATURE_EDITION_REVEALABLE.name]: FEATURE_EDITION_REVEALABLE,
[FEATURE_EDITION_SIGNATURE_MINTABLE.name]:
FEATURE_EDITION_SIGNATURE_MINTABLE,
@@ -1,12 +1,26 @@
import DropERC20_V2Abi from "@thirdweb-dev/contracts-js/dist/abis/DropERC20_V2.json";
import IBurnableERC20Abi from "@thirdweb-dev/contracts-js/dist/abis/IBurnableERC20.json";
import IDrop from "@thirdweb-dev/contracts-js/dist/abis/IDrop.json";
import IDropSinglePhase from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase.json";
import IDropSinglePhaseV1 from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase_V1.json";
import ERC20Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC20.json";
import IMintableERC20Abi from "@thirdweb-dev/contracts-js/dist/abis/IMintableERC20.json";
import MulticallAbi from "@thirdweb-dev/contracts-js/dist/abis/IMulticall.json";
import ISignatureMintERC20Abi from "@thirdweb-dev/contracts-js/dist/abis/ISignatureMintERC20.json";
export const FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS = {
name: "ERC20ClaimableWithConditions",
export const FEATURE_TOKEN_CLAIM_CONDITIONS_V1 = {
name: "ERC20ClaimConditionsV1",
namespace: "token.drop.claim",
docLinks: {
sdk: "sdk.erc20dclaimable",
contracts: "DropSinglePhase_V1",
},
abis: [ERC20Abi, IDropSinglePhaseV1],
features: {},
} as const;
export const FEATURE_TOKEN_CLAIM_CONDITIONS_V2 = {
name: "ERC20ClaimConditionsV2",
namespace: "token.drop.claim",
docLinks: {
sdk: "sdk.erc20dclaimable",
@@ -16,6 +30,28 @@ export const FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS = {
features: {},
} as const;
export const FEATURE_TOKEN_CLAIM_PHASES_V2 = {
name: "ERC20ClaimPhasesV2",
namespace: "token.drop.claim",
docLinks: {
sdk: "sdk.erc20dclaimable",
contracts: "Drop",
},
abis: [ERC20Abi, IDrop],
features: {},
} as const;
export const FEATURE_TOKEN_CLAIM_PHASES_V1 = {
name: "ERC20ClaimPhasesV1",
namespace: "token.drop.claim",
docLinks: {
sdk: "sdk.erc20dclaimable",
contracts: "Drop",
},
abis: [DropERC20_V2Abi],
features: {},
} as const;
export const FEATURE_TOKEN_BURNABLE = {
name: "ERC20Burnable",
namespace: "token.burn",
@@ -73,8 +109,10 @@ export const FEATURE_TOKEN = {
features: {
[FEATURE_TOKEN_BURNABLE.name]: FEATURE_TOKEN_BURNABLE,
[FEATURE_TOKEN_MINTABLE.name]: FEATURE_TOKEN_MINTABLE,
[FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS.name]:
FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS,
[FEATURE_TOKEN_CLAIM_CONDITIONS_V1.name]: FEATURE_TOKEN_CLAIM_CONDITIONS_V1,
[FEATURE_TOKEN_CLAIM_CONDITIONS_V2.name]: FEATURE_TOKEN_CLAIM_CONDITIONS_V2,
[FEATURE_TOKEN_CLAIM_PHASES_V1.name]: FEATURE_TOKEN_CLAIM_PHASES_V1,
[FEATURE_TOKEN_CLAIM_PHASES_V2.name]: FEATURE_TOKEN_CLAIM_PHASES_V2,
[FEATURE_TOKEN_SIGNATURE_MINTABLE.name]: FEATURE_TOKEN_SIGNATURE_MINTABLE,
},
} as const;
@@ -1,14 +1,19 @@
import DropERC721_V3Abi from "@thirdweb-dev/contracts-js/dist/abis/DropERC721_V3.json";
import IBurnableERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/IBurnableERC721.json";
import IClaimableERC721 from "@thirdweb-dev/contracts-js/dist/abis/IClaimableERC721.json";
import DelayedRevealAbi from "@thirdweb-dev/contracts-js/dist/abis/IDelayedReveal.json";
import IDrop from "@thirdweb-dev/contracts-js/dist/abis/IDrop.json";
import IDropSinglePhase from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase.json";
import IDropSinglePhaseV1 from "@thirdweb-dev/contracts-js/dist/abis/IDropSinglePhase_V1.json";
import Erc721Abi from "@thirdweb-dev/contracts-js/dist/abis/IERC721.json";
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 SignatureMintERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/SignatureMintERC721.json";
import SignatureMintERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/ISignatureMintERC721.json";
import TieredDropAbi from "@thirdweb-dev/contracts-js/dist/abis/LazyMintWithTier.json";
import TokenERC721Abi from "@thirdweb-dev/contracts-js/dist/abis/TokenERC721.json";
export const FEATURE_NFT_BURNABLE = {
name: "ERC721Burnable",
@@ -25,15 +30,37 @@ 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_CLAIM_CONDITIONS_V1 = {
name: "ERC721ClaimConditionsV1",
namespace: "nft.drop.claim",
docLinks: {
sdk: "sdk.erc721claimable",
contracts: "DropSinglePhase_V1",
},
abis: [Erc721Abi, ILazyMintAbi, IDropSinglePhaseV1],
features: {},
} as const;
export const FEATURE_NFT_CLAIM_CONDITIONS_V2 = {
name: "ERC721ClaimConditionsV2",
namespace: "nft.drop.claim",
docLinks: {
sdk: "sdk.erc721claimable",
@@ -43,8 +70,30 @@ export const FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS = {
features: {},
} as const;
export const FEATURE_NFT_CLAIMABLE = {
name: "ERC721Claimable",
export const FEATURE_NFT_CLAIM_PHASES_V1 = {
name: "ERC721ClaimPhasesV1",
namespace: "nft.drop.claim",
docLinks: {
sdk: "sdk.erc721claimable",
contracts: "Drop",
},
abis: [DropERC721_V3Abi],
features: {},
} as const;
export const FEATURE_NFT_CLAIM_PHASES_V2 = {
name: "ERC721ClaimPhasesV2",
namespace: "nft.drop.claim",
docLinks: {
sdk: "sdk.erc721claimable",
contracts: "Drop",
},
abis: [Erc721Abi, ILazyMintAbi, IDrop],
features: {},
} as const;
export const FEATURE_NFT_CLAIM_CUSTOM = {
name: "ERC721ClaimCustom",
namespace: "nft.drop.claim",
docLinks: {
sdk: "sdk.erc721claimable",
@@ -64,9 +113,11 @@ export const FEATURE_NFT_LAZY_MINTABLE = {
abis: [Erc721Abi, ILazyMintAbi],
features: {
[FEATURE_NFT_REVEALABLE.name]: FEATURE_NFT_REVEALABLE,
[FEATURE_NFT_CLAIMABLE.name]: FEATURE_NFT_CLAIMABLE,
[FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS.name]:
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS,
[FEATURE_NFT_CLAIM_CUSTOM.name]: FEATURE_NFT_CLAIM_CUSTOM,
[FEATURE_NFT_CLAIM_CONDITIONS_V1.name]: FEATURE_NFT_CLAIM_CONDITIONS_V1,
[FEATURE_NFT_CLAIM_CONDITIONS_V2.name]: FEATURE_NFT_CLAIM_CONDITIONS_V2,
[FEATURE_NFT_CLAIM_PHASES_V1.name]: FEATURE_NFT_CLAIM_PHASES_V1,
[FEATURE_NFT_CLAIM_PHASES_V2.name]: FEATURE_NFT_CLAIM_PHASES_V2,
},
} as const;
@@ -94,8 +145,8 @@ export const FEATURE_NFT_MINTABLE = {
},
} as const;
export const FEATURE_NFT_SIGNATURE_MINTABLE = {
name: "ERC721SignatureMint",
export const FEATURE_NFT_SIGNATURE_MINTABLE_V2 = {
name: "ERC721SignatureMintV2",
namespace: "nft.signature",
docLinks: {
sdk: "sdk.erc721signaturemint",
@@ -105,6 +156,17 @@ export const FEATURE_NFT_SIGNATURE_MINTABLE = {
features: {},
} as const;
export const FEATURE_NFT_SIGNATURE_MINTABLE_V1 = {
name: "ERC721SignatureMintV1",
namespace: "nft.signature",
docLinks: {
sdk: "sdk.erc721signaturemint",
contracts: "ISignatureMintERC721",
},
abis: [TokenERC721Abi],
features: {},
} as const;
export const FEATURE_NFT_ENUMERABLE = {
name: "ERC721Enumerable",
namespace: "nft.query.owned",
@@ -142,6 +204,8 @@ export const FEATURE_NFT = {
[FEATURE_NFT_SUPPLY.name]: FEATURE_NFT_SUPPLY,
[FEATURE_NFT_MINTABLE.name]: FEATURE_NFT_MINTABLE,
[FEATURE_NFT_LAZY_MINTABLE.name]: FEATURE_NFT_LAZY_MINTABLE,
[FEATURE_NFT_SIGNATURE_MINTABLE.name]: FEATURE_NFT_SIGNATURE_MINTABLE,
[FEATURE_NFT_SIGNATURE_MINTABLE_V1.name]: FEATURE_NFT_SIGNATURE_MINTABLE_V1,
[FEATURE_NFT_SIGNATURE_MINTABLE_V2.name]: FEATURE_NFT_SIGNATURE_MINTABLE_V2,
[FEATURE_NFT_TIERED_DROP.name]: FEATURE_NFT_TIERED_DROP,
},
} as const;
+143 -47
View File
@@ -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,
@@ -35,7 +41,7 @@ export const EditionDropInitializer = {
) => {
const [, provider] = getSignerAndProvider(network, options);
const [abi, contract, _network] = await Promise.all([
EditionDropInitializer.getAbi(),
await EditionDropInitializer.getAbi(address, provider),
import("./prebuilt-implementations/edition-drop"),
provider.getNetwork(),
]);
@@ -49,9 +55,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 +82,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 +96,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 +114,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 +128,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 +146,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 +160,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 +178,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 +192,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 = {
@@ -181,7 +210,7 @@ export const NFTDropInitializer = {
) => {
const [, provider] = getSignerAndProvider(network, options);
const [abi, contract, _network] = await Promise.all([
NFTDropInitializer.getAbi(),
NFTDropInitializer.getAbi(address, provider),
import("./prebuilt-implementations/nft-drop"),
provider.getNetwork(),
]);
@@ -195,9 +224,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 +252,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 +266,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 +284,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 +298,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 +329,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 +343,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 = {
@@ -299,7 +361,7 @@ export const TokenDropInitializer = {
) => {
const [, provider] = getSignerAndProvider(network, options);
const [abi, contract, _network] = await Promise.all([
TokenDropInitializer.getAbi(),
TokenDropInitializer.getAbi(address, provider),
import("./prebuilt-implementations/token-drop"),
provider.getNetwork(),
]);
@@ -313,9 +375,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 +399,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 +413,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 +432,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 +446,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 +505,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
@@ -326,9 +323,15 @@ export class Marketplace implements UpdateableNetwork {
*/
public async getOffers(listingId: BigNumberish): Promise<Offer[]> {
// get all new offer events from this contract
const events = await this.events.getEvents<NewOfferEventObject>("NewOffer");
// get only the events for this listing id
const listingEvents = events.filter((e) => e.data.listingId.eq(listingId));
const listingEvents = await this.events.getEvents<NewOfferEventObject>(
"NewOffer",
{
order: "desc",
filters: {
listingId,
},
},
);
// derive the offers from the events
return await Promise.all(
listingEvents.map(async (e): Promise<Offer> => {
@@ -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(
@@ -4,10 +4,12 @@ import {
fetchExtendedReleaseMetadata,
fetchPreDeployMetadata,
} from "../../common/index";
import { ChainId } from "../../constants";
import { getContractAddressByChainId } from "../../constants/addresses";
import {
EditionDropInitializer,
EditionInitializer,
getContractName,
MarketplaceInitializer,
MultiwrapInitializer,
NFTCollectionInitializer,
@@ -387,6 +389,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 +399,72 @@ export class ContractDeployer extends RPCConnectionHandler {
contractMetadata: z.input<
DeploySchemaForPrebuiltContractType<TContractType>
>,
version: string = "latest",
): Promise<string> {
const parsed =
const activeChainId = (await this.getProvider().getNetwork()).chainId;
const parsedMetadata =
PREBUILT_CONTRACTS_MAP[contractType].schema.deploy.parse(
contractMetadata,
);
const factory = await this.getFactory();
return await factory.deploy(contractType, parsed);
if (
activeChainId === ChainId.Hardhat ||
activeChainId === ChainId.Localhost
) {
//
// old behavior for hardhat and localhost chains
//
// parse version into the first number of the version string (or undefined if unparseable)
let parsedVersion: number | undefined = undefined;
try {
parsedVersion = parseInt(version);
if (isNaN(parsedVersion)) {
parsedVersion = undefined;
}
} catch (e) {
parsedVersion = undefined;
}
return await factory.deploy(contractType, parsedMetadata, parsedVersion);
}
//
// new behavior for all other chains
//
// resolve contract name from type
const contractName = getContractName(contractType);
invariant(contractName, "contract name not found");
// get deploy arugments for the contractType
// first upload the contractmetadata
const contractURI = await this.storage.upload(parsedMetadata);
// the get the deploy arguments
const constructorParams = await factory.getDeployArguments(
contractType,
parsedMetadata,
contractURI,
);
return this.deployReleasedContract(
// 0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024 === deployer.thirdweb.eth
"0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024",
contractName,
constructorParams,
version,
);
}
/**
* @internal
* @param contractType
*/
public async getLatestBuiltInContractVersion<
TContractType extends PrebuiltContractType,
>(contractType: TContractType) {
const factory = await this.getFactory();
return await factory.getLatestVersion(contractType);
}
/**
@@ -415,13 +477,14 @@ export class ContractDeployer extends RPCConnectionHandler {
releaserAddress: string,
contractName: string,
constructorParams: any[],
version = "latest",
): Promise<string> {
const release = await new ThirdwebSDK("polygon")
.getPublisher()
.getLatest(releaserAddress, contractName);
.getVersion(releaserAddress, contractName, version);
if (!release) {
throw new Error(
`No release found for '${contractName}' by ${releaserAddress}`,
`No release found for '${contractName}' at version '${version}' by '${releaserAddress}'`,
);
}
return await this.deployContractFromUri(
@@ -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) => {
@@ -195,13 +195,13 @@ export class ContractPublisher extends RPCConnectionHandler {
* @internal
* TODO clean this up (see method above, too)
*/
public async resolveReleasesFromAddress(address: string) {
public async resolveContractUriFromAddress(address: string) {
const contractUri = await resolveContractUriFromAddress(
address,
this.getProvider(),
);
invariant(contractUri, "Could not resolve contract URI from address");
return await this.resolvePublishMetadataFromCompilerMetadata(contractUri);
return contractUri;
}
/**
@@ -293,6 +293,30 @@ export class ContractPublisher extends RPCConnectionHandler {
return contractStructs.map((d) => this.toPublishedContract(d));
}
public async getVersion(
publisherAddress: string,
contractId: string,
version = "latest",
): Promise<PublishedContract | undefined> {
if (version === "latest") {
return this.getLatest(publisherAddress, contractId);
}
const allVersions = await this.getAllVersions(publisherAddress, contractId);
// get the metadata for each version
const versionMetadata = await Promise.all(
allVersions.map((contract) => this.fetchPublishedContractInfo(contract)),
);
// find the version that matches the version string
const versionMatch = versionMetadata.find(
(metadata) => metadata.publishedMetadata.version === version,
);
invariant(versionMatch, "Contract version not found");
// match the version back to the contract based on the release timestamp
return allVersions.find(
(contract) => contract.timestamp === versionMatch.publishedTimestamp,
);
}
public async getLatest(
publisherAddress: string,
contractId: string,
@@ -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,6 +1,12 @@
import { AmountSchema } from "../../../core/schema/shared";
import { includesErrorMessage } from "../../common";
import {
getClaimerProofs,
abstractContractModelToLegacy,
abstractContractModelToNew,
convertQuantityToBigNumber,
fetchSnapshotEntryForAddress,
legacyContractModelToAbstract,
newContractModelToAbstract,
prepareClaim,
processClaimConditionInputs,
transformResultToClaimCondition,
@@ -11,32 +17,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 } 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";
@@ -47,11 +67,10 @@ import deepEqual from "fast-deep-equal";
*/
export class DropClaimConditions<
TContract extends
| DropERC721
| DropERC20
| PrebuiltNFTDrop
| PrebuiltTokenDrop
| BaseClaimConditionERC721
| BaseDropERC20
| SignatureDrop,
| BaseDropERC20,
> {
private contractWrapper;
private metadata;
@@ -91,13 +110,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");
}
@@ -111,7 +154,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;
@@ -119,11 +165,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();
@@ -233,82 +277,176 @@ export class DropClaimConditions<
const merkleRootArray = ethers.utils.stripZeros(
claimCondition.merkleRootHash,
);
if (merkleRootArray.length > 0) {
const merkleLower = claimCondition.merkleRootHash.toString();
const metadata = await this.metadata.get();
const proofs = await getClaimerProofs(
addressToCheck,
merkleLower,
await this.getTokenDecimals(),
metadata.merkle,
this.storage,
);
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) {
const hasAllowList = merkleRootArray.length > 0;
let allowListEntry: SnapshotEntryWithProof | null = null;
if (hasAllowList) {
allowListEntry = await this.getClaimerProofs(addressToCheck);
if (
!allowListEntry &&
(this.isLegacySinglePhaseDrop(this.contractWrapper) ||
this.isLegacyMultiPhaseDrop(this.contractWrapper))
) {
// exclusive allowlist behavior
reasons.push(ClaimEligibility.AddressNotAllowed);
return reasons;
}
if (allowListEntry) {
try {
const claimVerification = await this.prepareClaim(
quantity,
false,
decimals,
addressToCheck,
);
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,
claimVerification.proofs,
claimVerification.maxClaimable,
);
if (!validMerkleProof) {
reasons.push(ClaimEligibility.AddressNotAllowed);
return reasons;
}
} else if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
[validMerkleProof] =
await this.contractWrapper.readContract.verifyClaimMerkleProof(
addressToCheck,
quantity,
{
proof: claimVerification.proofs,
maxQuantityInAllowlist: claimVerification.maxClaimable,
},
);
if (!validMerkleProof) {
reasons.push(ClaimEligibility.AddressNotAllowed);
return reasons;
}
} else if (this.isNewSinglePhaseDrop(this.contractWrapper)) {
await this.contractWrapper.readContract.verifyClaim(
addressToCheck,
quantity,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: claimVerification.proofs,
quantityLimitPerWallet: claimVerification.maxClaimable,
currency: claimVerification.currencyAddressInProof,
pricePerToken: claimVerification.priceInProof,
} as IDropSinglePhase.AllowlistProofStruct,
);
if (
(convertQuantityToBigNumber(
claimCondition.maxClaimablePerWallet,
decimals,
).eq(0) &&
claimVerification.maxClaimable ===
ethers.constants.MaxUint256) ||
claimVerification.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,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: claimVerification.proofs,
quantityLimitPerWallet: claimVerification.maxClaimable,
currency: claimVerification.currencyAddressInProof,
pricePerToken: claimVerification.priceInProof,
} as IDropSinglePhase.AllowlistProofStruct,
);
if (
(convertQuantityToBigNumber(
claimCondition.maxClaimablePerWallet,
decimals,
).eq(0) &&
claimVerification.maxClaimable ===
ethers.constants.MaxUint256) ||
claimVerification.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);
}
}
}
@@ -339,6 +477,33 @@ export class DropClaimConditions<
return reasons;
}
/**
* Returns allow list information and merkle proofs for the given address.
* @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.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,
merkleRoot.toString(),
metadata.merkle,
this.contractWrapper.getProvider(),
this.storage,
this.getSnapshotFormatVersion(),
);
} else {
return null;
}
}
/** ***************************************
* WRITE FUNCTIONS
*****************************************/
@@ -383,16 +548,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: [],
@@ -404,6 +572,48 @@ export class DropClaimConditions<
);
}
}
// if using new snapshot format, make sure that maxClaimablePerWallet is set if allowlist is set as well
if (
this.isNewSinglePhaseDrop(this.contractWrapper) ||
this.isNewMultiphaseDrop(this.contractWrapper)
) {
claimConditionsProcessed.forEach((cc) => {
if (
cc.snapshot &&
cc.snapshot.length > 0 &&
(cc.maxClaimablePerWallet === undefined ||
cc.maxClaimablePerWallet === "unlimited")
) {
throw new Error(
"maxClaimablePerWallet must be set to a specific value when an allowlist is set.\n" +
"Example: Set it to 0 to only allow addresses in the allowlist to claim the amount specified in the allowlist.\n" +
"contract.claimConditions.set([{ snapshot: [{ address: '0x...', maxClaimable: 1 }], maxClaimablePerWallet: 0 }])",
);
}
if (
cc.snapshot &&
cc.snapshot.length > 0 &&
cc.maxClaimablePerWallet?.toString() === "0" &&
cc.snapshot
.map((s) => {
if (typeof s === "string") {
return 0;
} else {
return Number(s.maxClaimable?.toString() || 0);
}
})
.reduce((acc, current) => {
return acc + current;
}, 0) === 0
) {
throw new Error(
"maxClaimablePerWallet is set to 0, and all addresses in the allowlist have max claimable 0. This means that no one can claim.",
);
}
});
}
// process inputs
const { snapshotInfos, sortedConditions } =
await processClaimConditionInputs(
@@ -411,6 +621,7 @@ export class DropClaimConditions<
await this.getTokenDecimals(),
this.contractWrapper.getProvider(),
this.storage,
this.getSnapshotFormatVersion(),
);
const merkleInfo: { [key: string]: string } = {};
@@ -431,6 +642,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",
@@ -451,17 +664,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,
]),
);
@@ -515,8 +742,13 @@ export class DropClaimConditions<
quantity: BigNumberish,
checkERC20Allowance: boolean,
decimals = 0,
address?: string,
): Promise<ClaimVerification> {
const addressToClaim = address
? address
: await this.contractWrapper.getSignerAddress();
return prepareClaim(
addressToClaim,
quantity,
await this.getActive(),
async () => (await this.metadata.get()).merkle,
@@ -524,21 +756,147 @@ export class DropClaimConditions<
this.contractWrapper,
this.storage,
checkERC20Allowance,
this.getSnapshotFormatVersion(),
);
}
private isSinglePhaseDropContract(
public async getClaimArguments(
destinationAddress: string,
quantity: BigNumberish,
claimVerification: ClaimVerification,
): Promise<any[]> {
if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
return [
destinationAddress,
quantity,
claimVerification.currencyAddress,
claimVerification.price,
claimVerification.proofs,
claimVerification.maxClaimable,
];
} else if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
return [
destinationAddress,
quantity,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: claimVerification.proofs,
maxQuantityInAllowlist: claimVerification.maxClaimable,
} as IDropSinglePhase_V1.AllowlistProofStruct,
ethers.utils.toUtf8Bytes(""),
];
}
return [
destinationAddress,
quantity,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: claimVerification.proofs,
quantityLimitPerWallet: claimVerification.maxClaimable,
pricePerToken: claimVerification.priceInProof,
currency: claimVerification.currencyAddressInProof,
} 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 (
detectContractFeature<DropSinglePhase>(
contractWrapper,
"ERC721ClaimConditionsV2",
) ||
detectContractFeature<DropSinglePhase>(
contractWrapper,
"ERC20ClaimConditionsV2",
)
);
}
private isMultiPhaseDropContract(
isNewMultiphaseDrop(
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<DropERC721 | DropERC20> {
return hasFunction<DropERC721>("getClaimConditionById", contractWrapper);
): contractWrapper is ContractWrapper<Drop> {
return (
detectContractFeature<Drop>(contractWrapper, "ERC721ClaimPhasesV2") ||
detectContractFeature<Drop>(contractWrapper, "ERC20ClaimPhasesV2")
);
}
isLegacySinglePhaseDrop(
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<DropSinglePhase_V1> {
return (
detectContractFeature<DropSinglePhase_V1>(
contractWrapper,
"ERC721ClaimConditionsV1",
) ||
detectContractFeature<DropSinglePhase_V1>(
contractWrapper,
"ERC20ClaimConditionsV1",
)
);
}
isLegacyMultiPhaseDrop(
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<DropERC721_V3 | DropERC20_V2> {
return (
detectContractFeature<DropERC721_V3 | DropERC20_V2>(
contractWrapper,
"ERC721ClaimPhasesV1",
) ||
detectContractFeature<DropERC721_V3 | DropERC20_V2>(
contractWrapper,
"ERC20ClaimPhasesV1",
)
);
}
private getSnapshotFormatVersion(): SnapshotFormatVersion {
return this.isLegacyMultiPhaseDrop(this.contractWrapper) ||
this.isLegacySinglePhaseDrop(this.contractWrapper)
? SnapshotFormatVersion.V1
: SnapshotFormatVersion.V2;
}
}
@@ -1,35 +1,55 @@
import { includesErrorMessage } from "../../common";
import {
getClaimerProofs,
abstractContractModelToLegacy,
abstractContractModelToNew,
fetchSnapshotEntryForAddress,
legacyContractModelToAbstract,
newContractModelToAbstract,
prepareClaim,
processClaimConditionInputs,
transformResultToClaimCondition,
updateExistingClaimConditions,
} from "../../common/claim-conditions";
import { isNativeToken } from "../../common/currency";
import { hasFunction } from "../../common/feature-detection";
import {
detectContractFeature,
hasFunction,
} from "../../common/feature-detection";
import { SnapshotFormatVersion } from "../../common/sharded-merkle-tree";
import { isNode } from "../../common/utils";
import { NATIVE_TOKEN_ADDRESS } from "../../constants/index";
import { ClaimEligibility } from "../../enums";
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";
@@ -39,7 +59,7 @@ import deepEqual from "fast-deep-equal";
* @public
*/
export class DropErc1155ClaimConditions<
TContract extends DropERC1155 | BaseClaimConditionERC1155,
TContract extends PrebuiltEditionDrop | BaseClaimConditionERC1155,
> {
private contractWrapper;
private metadata;
@@ -82,20 +102,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");
}
@@ -110,7 +155,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;
@@ -118,14 +166,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(
@@ -141,7 +184,7 @@ export class DropErc1155ClaimConditions<
),
);
} else {
return [await this.getActive(tokenId)];
return [await this.getActive(tokenId, options)];
}
}
@@ -230,52 +273,140 @@ export class DropErc1155ClaimConditions<
const merkleRootArray = ethers.utils.stripZeros(
claimCondition.merkleRootHash,
);
if (merkleRootArray.length > 0) {
const merkleLower = claimCondition.merkleRootHash.toString();
const metadata = await this.metadata.get();
const proofs = await getClaimerProofs(
addressToCheck,
merkleLower,
0,
metadata.merkle,
this.storage,
);
try {
let validMerkleProof;
if (this.isMultiPhaseDropContract(this.contractWrapper)) {
activeConditionIndex =
await this.contractWrapper.readContract.getActiveClaimConditionId(
const hasAllowList = merkleRootArray.length > 0;
let allowListEntry: SnapshotEntryWithProof | null = null;
if (hasAllowList) {
allowListEntry = await this.getClaimerProofs(tokenId, addressToCheck);
if (
!allowListEntry &&
(this.isLegacySinglePhaseDrop(this.contractWrapper) ||
this.isLegacyMultiPhaseDrop(this.contractWrapper))
) {
// exclusive allowlist behavior
reasons.push(ClaimEligibility.AddressNotAllowed);
return reasons;
}
if (allowListEntry) {
try {
const claimVerification = await this.prepareClaim(
tokenId,
quantity,
false,
addressToCheck,
);
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,
claimVerification.proofs,
claimVerification.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: claimVerification.proofs,
maxQuantityInAllowlist: claimVerification.maxClaimable,
},
);
if (!validMerkleProof) {
reasons.push(ClaimEligibility.AddressNotAllowed);
return reasons;
}
} else if (this.isNewSinglePhaseDrop(this.contractWrapper)) {
await this.contractWrapper.readContract.verifyClaim(
tokenId,
addressToCheck,
quantity,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: claimVerification.proofs,
quantityLimitPerWallet: claimVerification.maxClaimable,
currency: claimVerification.currencyAddressInProof,
pricePerToken: claimVerification.priceInProof,
} 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" &&
claimVerification.maxClaimable ===
ethers.constants.MaxUint256) ||
claimVerification.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,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: proofs.proof,
maxQuantityInAllowlist: proofs.maxClaimable,
},
proof: claimVerification.proofs,
quantityLimitPerWallet: claimVerification.maxClaimable,
currency: claimVerification.currencyAddressInProof,
pricePerToken: claimVerification.priceInProof,
} as IDropSinglePhase.AllowlistProofStruct,
);
}
if (!validMerkleProof) {
if (
(claimCondition.maxClaimablePerWallet === "0" &&
claimVerification.maxClaimable ===
ethers.constants.MaxUint256) ||
claimVerification.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;
}
}
@@ -284,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,
@@ -295,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,
@@ -342,6 +473,35 @@ export class DropErc1155ClaimConditions<
return reasons;
}
/**
* Returns allow list information and merkle proofs for the given address.
* @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.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,
merkleRoot.toString(),
metadata.merkle,
this.contractWrapper.getProvider(),
this.storage,
this.getSnapshotFormatVersion(),
);
} else {
return null;
}
}
/** ***************************************
* WRITE FUNCTIONS
*****************************************/
@@ -440,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: [],
@@ -461,6 +621,47 @@ export class DropErc1155ClaimConditions<
);
}
}
// if using new snapshot format, make sure that maxClaimablePerWallet is set if allowlist is set as well
if (
this.isNewSinglePhaseDrop(this.contractWrapper) ||
this.isNewMultiphaseDrop(this.contractWrapper)
) {
claimConditionsProcessed.forEach((cc) => {
if (
cc.snapshot &&
cc.snapshot.length > 0 &&
(cc.maxClaimablePerWallet === undefined ||
cc.maxClaimablePerWallet === "unlimited")
) {
throw new Error(
"maxClaimablePerWallet must be set to a specific value when an allowlist is set.\n" +
"Set it to 0 to only allow addresses in the allowlist to claim the amount specified in the allowlist." +
"\n\nex:\n" +
"contract.claimConditions.set(tokenId, [{ snapshot: [{ address: '0x...', maxClaimable: 1 }], maxClaimablePerWallet: 0 }])",
);
}
if (
cc.snapshot &&
cc.snapshot.length > 0 &&
cc.maxClaimablePerWallet?.toString() === "0" &&
cc.snapshot
.map((s) => {
if (typeof s === "string") {
return 0;
} else {
return Number(s.maxClaimable?.toString() || 0);
}
})
.reduce((acc, current) => {
return acc + current;
}, 0) === 0
) {
throw new Error(
"maxClaimablePerWallet is set to 0, and all addresses in the allowlist have max claimable 0. This means that no one can claim.",
);
}
});
}
// process inputs
const { snapshotInfos, sortedConditions } =
await processClaimConditionInputs(
@@ -468,6 +669,7 @@ export class DropErc1155ClaimConditions<
0,
this.contractWrapper.getProvider(),
this.storage,
this.getSnapshotFormatVersion(),
);
snapshotInfos.forEach((s) => {
@@ -519,18 +721,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 {
@@ -572,8 +804,13 @@ export class DropErc1155ClaimConditions<
tokenId: BigNumberish,
quantity: BigNumberish,
checkERC20Allowance: boolean,
address?: string,
): Promise<ClaimVerification> {
const addressToClaim = address
? address
: await this.contractWrapper.getSignerAddress();
return prepareClaim(
addressToClaim,
quantity,
await this.getActive(tokenId),
async () => (await this.metadata.get()).merkle,
@@ -581,21 +818,132 @@ 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[]> {
if (this.isLegacyMultiPhaseDrop(this.contractWrapper)) {
return [
destinationAddress,
tokenId,
quantity,
claimVerification.currencyAddress,
claimVerification.price,
claimVerification.proofs,
claimVerification.maxClaimable,
];
} else if (this.isLegacySinglePhaseDrop(this.contractWrapper)) {
return [
destinationAddress,
tokenId,
quantity,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: claimVerification.proofs,
maxQuantityInAllowlist: claimVerification.maxClaimable,
} as IDropSinglePhase_V1.AllowlistProofStruct,
ethers.utils.toUtf8Bytes(""),
];
}
return [
destinationAddress,
tokenId,
quantity,
claimVerification.currencyAddress,
claimVerification.price,
{
proof: claimVerification.proofs,
quantityLimitPerWallet: claimVerification.maxClaimable,
pricePerToken: claimVerification.priceInProof,
currency: claimVerification.currencyAddressInProof,
} 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",
return detectContractFeature<DropSinglePhase1155>(
contractWrapper,
"ERC1155ClaimConditionsV2",
);
}
private isMultiPhaseDropContract(
isNewMultiphaseDrop(
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<DropERC1155> {
return hasFunction<DropERC1155>("getClaimConditionById", contractWrapper);
): contractWrapper is ContractWrapper<Drop1155> {
return detectContractFeature<Drop1155>(
contractWrapper,
"ERC1155ClaimPhasesV2",
);
}
isLegacySinglePhaseDrop(
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<DropSinglePhase1155_V1> {
return detectContractFeature<DropSinglePhase1155_V1>(
contractWrapper,
"ERC1155ClaimConditionsV1",
);
}
isLegacyMultiPhaseDrop(
contractWrapper: ContractWrapper<any>,
): contractWrapper is ContractWrapper<DropERC1155_V2> {
return detectContractFeature<DropERC1155_V2>(
contractWrapper,
"ERC1155ClaimPhasesV1",
);
}
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 { FEATURE_EDITION_CLAIM_CONDITIONS_V2 } 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
@@ -27,7 +21,7 @@ import { BigNumberish, ethers } from "ethers";
* ```
*/
export class Erc1155ClaimableWithConditions implements DetectableFeature {
featureName = FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS.name;
featureName = FEATURE_EDITION_CLAIM_CONDITIONS_V2.name;
public conditions: DropErc1155ClaimConditions<BaseClaimConditionERC1155>;
private contractWrapper: ContractWrapper<BaseClaimConditionERC1155>;
@@ -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;
}
}
}
@@ -1,5 +1,5 @@
import { calculateClaimCost } from "../../common/claim-conditions";
import { FEATURE_EDITION_CLAIMABLE } from "../../constants/erc1155-features";
import { FEATURE_EDITION_CLAIM_CUSTOM } from "../../constants/erc1155-features";
import { ClaimOptions } from "../../types";
import { DetectableFeature } from "../interfaces/DetectableFeature";
import { TransactionResult } from "../types";
@@ -18,7 +18,7 @@ import { BigNumberish, CallOverrides } from "ethers";
* ```
*/
export class ERC1155Claimable implements DetectableFeature {
featureName = FEATURE_EDITION_CLAIMABLE.name;
featureName = FEATURE_EDITION_CLAIM_CUSTOM.name;
private contractWrapper: ContractWrapper<IClaimableERC1155>;
@@ -1,11 +1,9 @@
import { NFTMetadata, NFTMetadataOrUri } from "../../../core/schema/nft";
import {
detectContractFeature,
hasFunction,
} from "../../common/feature-detection";
import { detectContractFeature } from "../../common/feature-detection";
import { getPrebuiltInfo } from "../../common/legacy";
import { uploadOrExtractURIs } from "../../common/nft";
import {
FEATURE_EDITION_LAZY_MINTABLE,
FEATURE_EDITION_LAZY_MINTABLE_V2,
FEATURE_EDITION_REVEALABLE,
} from "../../constants/erc1155-features";
import {
@@ -21,16 +19,13 @@ 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";
export class Erc1155LazyMintable implements DetectableFeature {
featureName = FEATURE_EDITION_LAZY_MINTABLE.name;
featureName = FEATURE_EDITION_LAZY_MINTABLE_V2.name;
/**
* Delayed reveal
@@ -93,7 +88,7 @@ export class Erc1155LazyMintable implements DetectableFeature {
this.storage = storage;
this.claim = this.detectErc1155Claimable();
this.claimWithConditions = this.detectErc1155ClaimableWithConditions();
this.revealer = this.detectErc721Revealable();
this.revealer = this.detectErc1155Revealable();
}
/**
@@ -186,9 +181,8 @@ export class Erc1155LazyMintable implements DetectableFeature {
if (
detectContractFeature<IClaimableERC1155>(
this.contractWrapper,
"ERC1155Claimable",
) &&
!hasFunction("setClaimConditions", this.contractWrapper)
"ERC1155ClaimCustom",
)
) {
return new ERC1155Claimable(this.contractWrapper);
}
@@ -201,7 +195,19 @@ export class Erc1155LazyMintable implements DetectableFeature {
if (
detectContractFeature<BaseClaimConditionERC1155>(
this.contractWrapper,
"ERC1155ClaimableWithConditions",
"ERC1155ClaimConditionsV1",
) ||
detectContractFeature<BaseClaimConditionERC1155>(
this.contractWrapper,
"ERC1155ClaimConditionsV2",
) ||
detectContractFeature<BaseClaimConditionERC1155>(
this.contractWrapper,
"ERC1155ClaimPhasesV1",
) ||
detectContractFeature<BaseClaimConditionERC1155>(
this.contractWrapper,
"ERC1155ClaimPhasesV2",
)
) {
return new Erc1155ClaimableWithConditions(
@@ -212,7 +218,7 @@ export class Erc1155LazyMintable implements DetectableFeature {
return undefined;
}
private detectErc721Revealable():
private detectErc1155Revealable():
| DelayedReveal<BaseDelayedRevealERC1155>
| undefined {
if (
@@ -232,17 +238,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
+13 -9
View File
@@ -12,13 +12,13 @@ import {
FEATURE_EDITION,
FEATURE_EDITION_BATCH_MINTABLE,
FEATURE_EDITION_BURNABLE,
FEATURE_EDITION_LAZY_MINTABLE,
FEATURE_EDITION_ENUMERABLE,
FEATURE_EDITION_MINTABLE,
FEATURE_EDITION_REVEALABLE,
FEATURE_EDITION_SIGNATURE_MINTABLE,
FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS,
FEATURE_EDITION_CLAIMABLE,
FEATURE_EDITION_CLAIM_CUSTOM,
FEATURE_EDITION_CLAIM_CONDITIONS_V2,
FEATURE_EDITION_LAZY_MINTABLE_V2,
} from "../../constants/erc1155-features";
import { AirdropInputSchema } from "../../schema/contracts/common/airdrop";
import { EditionMetadataOrUri } from "../../schema/tokens/edition";
@@ -770,7 +770,7 @@ export class Erc1155<
): Promise<TransactionResultWithId<NFTMetadata>[]> {
return assertEnabled(
this.lazyMintable,
FEATURE_EDITION_LAZY_MINTABLE,
FEATURE_EDITION_LAZY_MINTABLE_V2,
).lazyMint(metadatas, options);
}
@@ -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,
@@ -808,7 +808,7 @@ export class Erc1155<
options,
);
}
throw new ExtensionNotImplementedError(FEATURE_EDITION_CLAIMABLE);
throw new ExtensionNotImplementedError(FEATURE_EDITION_CLAIM_CUSTOM);
}
/**
@@ -887,7 +887,7 @@ export class Erc1155<
if (claim) {
return claim.to(destinationAddress, tokenId, quantity, options);
}
throw new ExtensionNotImplementedError(FEATURE_EDITION_CLAIMABLE);
throw new ExtensionNotImplementedError(FEATURE_EDITION_CLAIM_CUSTOM);
}
/**
@@ -916,7 +916,7 @@ export class Erc1155<
get claimConditions() {
return assertEnabled(
this.lazyMintable?.claimWithConditions,
FEATURE_EDITION_CLAIMABLE_WITH_CONDITIONS,
FEATURE_EDITION_CLAIM_CONDITIONS_V2,
).conditions;
}
@@ -1041,7 +1041,11 @@ export class Erc1155<
if (
detectContractFeature<BaseDropERC1155>(
this.contractWrapper,
"ERC1155LazyMintable",
"ERC1155LazyMintableV1",
) ||
detectContractFeature<BaseDropERC1155>(
this.contractWrapper,
"ERC1155LazyMintableV2",
)
) {
return new Erc1155LazyMintable(this, this.contractWrapper, this.storage);
@@ -1,6 +1,6 @@
import { FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS } from "../../constants/erc20-features";
import { FEATURE_TOKEN_CLAIM_CONDITIONS_V2 } from "../../constants/erc20-features";
import { CustomContractSchema } from "../../schema/contracts/custom";
import { ClaimVerification } from "../../types";
import { ClaimOptions } from "../../types";
import { Amount } from "../../types/currency";
import { BaseDropERC20 } from "../../types/eips";
import { DetectableFeature } from "../interfaces/DetectableFeature";
@@ -20,8 +20,8 @@ import { ThirdwebStorage } from "@thirdweb-dev/storage";
* await contract.token.drop.claim.to("0x...", quantity);
* ```
*/
export class Erc20Claimable implements DetectableFeature {
featureName = FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS.name;
export class Erc20ClaimableWithConditions implements DetectableFeature {
featureName = FEATURE_TOKEN_CLAIM_CONDITIONS_V2.name;
/**
* Configure claim conditions
* @remarks Define who can claim NFTs in the collection, when and how many.
@@ -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
+23 -18
View File
@@ -1,4 +1,5 @@
import { NetworkOrSignerOrProvider, TransactionResult } from "..";
import { AmountSchema } from "../../../core/schema/shared";
import { assertEnabled, detectContractFeature } from "../../common";
import {
fetchCurrencyMetadata,
@@ -10,15 +11,10 @@ import {
FEATURE_TOKEN_BATCH_MINTABLE,
FEATURE_TOKEN_BURNABLE,
FEATURE_TOKEN_SIGNATURE_MINTABLE,
FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS,
FEATURE_TOKEN_CLAIM_CONDITIONS_V2,
} from "../../constants/erc20-features";
import { TokenMintInput, 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);
FEATURE_TOKEN_CLAIM_CONDITIONS_V2,
).to(destinationAddress, amount, options);
}
/**
@@ -553,7 +546,7 @@ export class Erc20<
get claimConditions() {
return assertEnabled(
this.droppable?.claim,
FEATURE_TOKEN_CLAIMABLE_WITH_CONDITIONS,
FEATURE_TOKEN_CLAIM_CONDITIONS_V2,
).conditions;
}
@@ -627,7 +620,19 @@ export class Erc20<
if (
detectContractFeature<BaseDropERC20>(
this.contractWrapper,
"ERC20ClaimableWithConditions",
"ERC20ClaimConditionsV1",
) ||
detectContractFeature<BaseDropERC20>(
this.contractWrapper,
"ERC20ClaimConditionsV2",
) ||
detectContractFeature<BaseDropERC20>(
this.contractWrapper,
"ERC20ClaimPhasesV1",
) ||
detectContractFeature<BaseDropERC20>(
this.contractWrapper,
"ERC20ClaimPhasesV2",
)
) {
return new Erc20Droppable(this, this.contractWrapper, this.storage);
@@ -1,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_CLAIM_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_CLAIM_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;
}
}
}
@@ -1,6 +1,6 @@
import { NFT } from "../../../core/schema/nft";
import { calculateClaimCost } from "../../common/claim-conditions";
import { FEATURE_NFT_CLAIMABLE } from "../../constants/erc721-features";
import { FEATURE_NFT_CLAIM_CUSTOM } from "../../constants/erc721-features";
import { ClaimOptions } from "../../types";
import { DetectableFeature } from "../interfaces/DetectableFeature";
import { TransactionResultWithId } from "../types";
@@ -21,7 +21,7 @@ import { BigNumber, BigNumberish, CallOverrides } from "ethers";
* ```
*/
export class Erc721Claimable implements DetectableFeature {
featureName = FEATURE_NFT_CLAIMABLE.name;
featureName = FEATURE_NFT_CLAIM_CUSTOM.name;
private erc721: Erc721;
private contractWrapper: ContractWrapper<IClaimableERC721>;
@@ -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
@@ -1,8 +1,5 @@
import { NFTMetadata, NFTMetadataOrUri } from "../../../core/schema/nft";
import {
detectContractFeature,
hasFunction,
} from "../../common/feature-detection";
import { detectContractFeature } from "../../common/feature-detection";
import { getBaseUriFromBatch, uploadOrExtractURIs } from "../../common/nft";
import {
FEATURE_NFT_LAZY_MINTABLE,
@@ -194,7 +191,19 @@ export class Erc721LazyMintable implements DetectableFeature {
if (
detectContractFeature<BaseClaimConditionERC721>(
this.contractWrapper,
"ERC721ClaimableWithConditions",
"ERC721ClaimConditionsV1",
) ||
detectContractFeature<BaseClaimConditionERC721>(
this.contractWrapper,
"ERC721ClaimConditionsV2",
) ||
detectContractFeature<BaseClaimConditionERC721>(
this.contractWrapper,
"ERC721ClaimPhasesV1",
) ||
detectContractFeature<BaseClaimConditionERC721>(
this.contractWrapper,
"ERC721ClaimPhasesV2",
)
) {
return new Erc721ClaimableWithConditions(
@@ -210,9 +219,8 @@ export class Erc721LazyMintable implements DetectableFeature {
if (
detectContractFeature<IClaimableERC721>(
this.contractWrapper,
"ERC721Claimable",
) &&
!hasFunction("setClaimConditions", this.contractWrapper)
"ERC721ClaimCustom",
)
) {
return new Erc721Claimable(this.erc721, this.contractWrapper);
}
@@ -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;
}
}
@@ -1,7 +1,7 @@
import { hasFunction } from "../../common";
import { normalizePriceValue, setErc20Allowance } from "../../common/currency";
import { uploadOrExtractURIs } from "../../common/nft";
import { FEATURE_NFT_SIGNATURE_MINTABLE } from "../../constants/erc721-features";
import { FEATURE_NFT_SIGNATURE_MINTABLE_V2 } from "../../constants/erc721-features";
import {
MintRequest721,
MintRequest721withQuantity,
@@ -30,7 +30,7 @@ import invariant from "tiny-invariant";
* @public
*/
export class Erc721WithQuantitySignatureMintable implements DetectableFeature {
featureName = FEATURE_NFT_SIGNATURE_MINTABLE.name;
featureName = FEATURE_NFT_SIGNATURE_MINTABLE_V2.name;
private contractWrapper: ContractWrapper<SignatureMintERC721 | TokenERC721>;
@@ -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
+40 -9
View File
@@ -12,13 +12,14 @@ import {
FEATURE_NFT,
FEATURE_NFT_BATCH_MINTABLE,
FEATURE_NFT_BURNABLE,
FEATURE_NFT_CLAIMABLE,
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS,
FEATURE_NFT_CLAIM_CUSTOM,
FEATURE_NFT_CLAIM_CONDITIONS_V2,
FEATURE_NFT_LAZY_MINTABLE,
FEATURE_NFT_MINTABLE,
FEATURE_NFT_REVEALABLE,
FEATURE_NFT_SIGNATURE_MINTABLE,
FEATURE_NFT_SUPPLY,
FEATURE_NFT_TIERED_DROP,
FEATURE_NFT_SIGNATURE_MINTABLE_V2,
} from "../../constants/erc721-features";
import { BaseDropERC721, BaseERC721 } from "../../types/eips";
import { ClaimOptions, UploadProgressEvent } from "../../types/index";
@@ -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;
}
@@ -595,7 +600,7 @@ export class Erc721<
if (claim) {
return claim.to(destinationAddress, quantity, options);
}
throw new ExtensionNotImplementedError(FEATURE_NFT_CLAIMABLE);
throw new ExtensionNotImplementedError(FEATURE_NFT_CLAIM_CUSTOM);
}
/**
@@ -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,
@@ -622,7 +627,7 @@ export class Erc721<
if (claim) {
return claim.getClaimTransaction(destinationAddress, quantity, options);
}
throw new ExtensionNotImplementedError(FEATURE_NFT_CLAIMABLE);
throw new ExtensionNotImplementedError(FEATURE_NFT_CLAIM_CUSTOM);
}
public async totalClaimedSupply(): Promise<BigNumber> {
@@ -682,10 +687,20 @@ export class Erc721<
get claimConditions() {
return assertEnabled(
this.lazyMintable?.claimWithConditions,
FEATURE_NFT_CLAIMABLE_WITH_CONDITIONS,
FEATURE_NFT_CLAIM_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 //////
/**
@@ -706,7 +721,7 @@ export class Erc721<
get signature() {
return assertEnabled(
this.signatureMintable,
FEATURE_NFT_SIGNATURE_MINTABLE,
FEATURE_NFT_SIGNATURE_MINTABLE_V2,
);
}
@@ -828,13 +843,29 @@ 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 {
if (
detectContractFeature<ISignatureMintERC721>(
this.contractWrapper,
"ERC721SignatureMint",
"ERC721SignatureMintV1",
) ||
detectContractFeature<ISignatureMintERC721>(
this.contractWrapper,
"ERC721SignatureMintV2",
)
) {
return new Erc721WithQuantitySignatureMintable(
+90 -33
View File
@@ -1,8 +1,12 @@
import { TransactionError } from "../../common";
import { CONTRACT_ADDRESSES, SUPPORTED_CHAIN_IDS } from "../../constants";
import {
CONTRACT_ADDRESSES,
getApprovedImplementation,
SUPPORTED_CHAIN_IDS,
} from "../../constants";
import {
EditionDropInitializer,
EditionInitializer,
getContractName,
MarketplaceInitializer,
MultiwrapInitializer,
NFTCollectionInitializer,
@@ -41,6 +45,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 +76,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 +84,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",
@@ -140,7 +154,15 @@ export class ContractFactory extends ContractWrapper<TWFactory> {
return events[0].args.proxy;
}
private async getDeployArguments<TContractType extends PrebuiltContractType>(
/**
*
* @param contractType
* @param metadata
* @param contractURI
* @returns
* @internal
*/
public async getDeployArguments<TContractType extends PrebuiltContractType>(
contractType: TContractType,
metadata: z.input<DeploySchemaForPrebuiltContractType<TContractType>>,
contractURI: string,
@@ -285,4 +307,39 @@ 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);
const chainId = await this.getChainID();
const approvedImplementation = getApprovedImplementation(
chainId,
contract.contractType,
);
// return approved implementation if it exists and we're not overriding the version
if (
approvedImplementation &&
approvedImplementation.length > 0 &&
version === undefined
) {
return approvedImplementation;
}
return this.readContract.getImplementation(
encodedType,
version !== undefined
? 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);
}
}
@@ -26,9 +26,13 @@ import {
Offer,
} from "../../types/marketplace";
import { TransactionResult, TransactionResultWithId } from "../types";
import { ContractEncoder } from "./contract-encoder";
import { ContractWrapper } from "./contract-wrapper";
import type { IMarketplace, Marketplace } from "@thirdweb-dev/contracts-js";
import { ListingAddedEvent } from "@thirdweb-dev/contracts-js/dist/declarations/src/Marketplace";
import {
ListingAddedEvent,
Marketplace as MarketplaceContract,
} from "@thirdweb-dev/contracts-js/dist/declarations/src/Marketplace";
import { ThirdwebStorage } from "@thirdweb-dev/storage";
import { BigNumber, BigNumberish, ethers, constants } from "ethers";
import invariant from "tiny-invariant";
@@ -40,6 +44,7 @@ import invariant from "tiny-invariant";
export class MarketplaceAuction {
private contractWrapper: ContractWrapper<Marketplace>;
private storage: ThirdwebStorage;
public encoder: ContractEncoder<MarketplaceContract>;
constructor(
contractWrapper: ContractWrapper<Marketplace>,
@@ -47,6 +52,7 @@ export class MarketplaceAuction {
) {
this.contractWrapper = contractWrapper;
this.storage = storage;
this.encoder = new ContractEncoder<Marketplace>(contractWrapper);
}
getAddress(): string {
@@ -401,9 +407,9 @@ export class MarketplaceAuction {
}
/**
* Close the Auction
* Close the Auction for the buyer or the seller
*
* @remarks Closes the Auction and executes the sale.
* @remarks Closes the Auction and executes the sale for the buyer or the seller.
*
* @example
* ```javascript
@@ -442,6 +448,49 @@ export class MarketplaceAuction {
}
}
/**
* Execute the Auction Sale
*
* @remarks Closes the Auction and executes the sale for both parties.
*
* @example
* ```javascript
* // The listing ID of the auction listing you want to close
* const listingId = "0";
* await contract.auction.executeSale(listingId);
* ```
*
* @param listingId - the auction listing ud to close
*/
public async executeSale(listingId: BigNumberish) {
const listing = await this.validateListing(BigNumber.from(listingId));
try {
const winningBid = await this.getWinningBid(listingId);
invariant(winningBid, "No winning bid found");
const closeForSeller = this.encoder.encode("closeAuction", [
listingId,
listing.sellerAddress,
]);
const closeForBuyer = this.encoder.encode("closeAuction", [
listingId,
winningBid.buyerAddress,
]);
return await this.contractWrapper.multiCall([
closeForSeller,
closeForBuyer,
]);
} catch (err: any) {
if (err.message.includes("cannot close auction before it has ended")) {
throw new AuctionHasNotEndedError(
listingId.toString(),
listing.endTimeInEpochSeconds.toString(),
);
} else {
throw err;
}
}
}
/**
* Update an Auction listing with new metadata
* @param listing - the listing id to update
+1 -1
View File
@@ -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);
}
}
+24 -3
View File
@@ -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();
+1 -1
View File
@@ -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";
@@ -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,17 +1,28 @@
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";
/**
* @internal
*/
export const ClaimConditionMetadataSchema = z
.object({
name: z.string().optional(),
})
.catchall(z.unknown());
/**
* @internal
*/
@@ -19,11 +30,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: ClaimConditionMetadataSchema.optional(),
});
/**
@@ -55,3 +67,15 @@ export const ClaimConditionOutputSchema = ClaimConditionInputSchema.extend({
startTime: BigNumberSchema.transform((n) => new Date(n.toNumber() * 1000)),
snapshot: SnapshotInputSchema.optional().nullable(),
});
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,
@@ -96,6 +96,7 @@ export const AbiObjectSchema = z
* @internal
*/
export const AbiSchema = z.array(AbiObjectSchema);
export type Abi = z.input<typeof AbiSchema>;
/**
* @internal
@@ -154,6 +155,17 @@ export const ExtraPublishMetadataSchemaInput = z
isDeployableViaFactory: z.boolean().optional(),
isDeployableViaProxy: z.boolean().optional(),
factoryDeploymentData: FactoryDeploymentSchema.optional(),
constructorParams: z
.record(
z.string(),
z
.object({
displayName: z.string().optional(),
description: z.string().optional(),
})
.catchall(z.any()),
)
.optional(),
})
.catchall(z.any());
@@ -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,
-45
View File
@@ -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 exeed 100%")
.min(0, "Cannot be below 0%");
export const PercentSchema = z
.number()
.max(100, "Cannot exeed 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));
});

Some files were not shown because too many files have changed in this diff Show More