Skip to content

Commit 9142a4e

Browse files
committed
Revert root pnpm fix side effect
1 parent 4412727 commit 9142a4e

File tree

7 files changed

+1082
-0
lines changed

7 files changed

+1082
-0
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { encodePacked } from "viem/utils";
2+
import { upload } from "../../../../storage/upload.js";
3+
import type { BaseTransactionOptions } from "../../../../transaction/types.js";
4+
import { encodeAbiParameters } from "../../../../utils/abi/encodeAbiParameters.js";
5+
import { toHex } from "../../../../utils/encoding/hex.js";
6+
import { keccak256 } from "../../../../utils/hashing/keccak256.js";
7+
import { getBaseUriFromBatch } from "../../../../utils/ipfs.js";
8+
import type { NFTInput } from "../../../../utils/nft/parseNft.js";
9+
import {
10+
getBaseURICount,
11+
isGetBaseURICountSupported,
12+
} from "../../__generated__/IBatchMintMetadata/read/getBaseURICount.js";
13+
import {
14+
encryptDecrypt,
15+
isEncryptDecryptSupported,
16+
} from "../../__generated__/IDelayedReveal/read/encryptDecrypt.js";
17+
import { nextTokenIdToMint } from "../../__generated__/IERC721Enumerable/read/nextTokenIdToMint.js";
18+
import {
19+
lazyMint as generatedLazyMint,
20+
isLazyMintSupported,
21+
} from "../../__generated__/ILazyMint/write/lazyMint.js";
22+
import { hashDelayedRevealPassword } from "../helpers/hashDelayedRevealBatch.js";
23+
24+
/**
25+
* @extension ERC721
26+
*/
27+
export type CreateDelayedRevealBatchParams = {
28+
placeholderMetadata: NFTInput;
29+
metadata: NFTInput[];
30+
password: string;
31+
};
32+
33+
/**
34+
* Creates a batch of encrypted NFTs that can be revealed at a later time.
35+
* This method is only available on the `DropERC721` contract.
36+
*
37+
* @param options {CreateDelayedRevealBatchParams} - The delayed reveal options.
38+
* @param options.placeholderMetadata {@link NFTInput} - The placeholder metadata for the batch.
39+
* @param options.metadata {@link NFTInput} - An array of NFT metadata to be revealed at a later time.
40+
* @param options.password {string} - The password for the reveal.
41+
* @param options.contract {@link ThirdwebContract} - The NFT contract instance.
42+
*
43+
* @returns The prepared transaction to send.
44+
*
45+
* @extension ERC721
46+
* @example
47+
* ```ts
48+
* import { createDelayedRevealBatch } from "thirdweb/extensions/erc721";
49+
*
50+
* const placeholderNFT = {
51+
* name: "Hidden NFT",
52+
* description: "Will be revealed next week!"
53+
* };
54+
*
55+
* const realNFTs = [{
56+
* name: "Common NFT #1",
57+
* description: "Common NFT, one of many.",
58+
* image: ipfs://...,
59+
* }, {
60+
* name: "Super Rare NFT #2",
61+
* description: "You got a Super Rare NFT!",
62+
* image: ipfs://...,
63+
* }];
64+
*
65+
* const transaction = createDelayedRevealBatch({
66+
* contract,
67+
* placeholderMetadata: placeholderNFT,
68+
* metadata: realNFTs,
69+
* password: "password123",
70+
* });
71+
*
72+
* const { transactionHash } = await sendTransaction({ transaction, account });
73+
* ```
74+
*/
75+
export function createDelayedRevealBatch(
76+
options: BaseTransactionOptions<CreateDelayedRevealBatchParams>,
77+
) {
78+
if (!options.password) {
79+
throw new Error("Password is required");
80+
}
81+
82+
return generatedLazyMint({
83+
asyncParams: async () => {
84+
const [placeholderUris, startFileNumber] = await Promise.all([
85+
upload({
86+
client: options.contract.client,
87+
files: Array(options.metadata.length).fill(
88+
options.placeholderMetadata,
89+
),
90+
}),
91+
nextTokenIdToMint({
92+
contract: options.contract,
93+
}),
94+
]);
95+
const placeholderUri = getBaseUriFromBatch(placeholderUris);
96+
97+
const uris = await upload({
98+
client: options.contract.client,
99+
files: options.metadata,
100+
// IMPORTANT: File number has to be calculated properly otherwise the whole batch will break
101+
// e.g: If you are uploading a second batch, the file name should never start from `0`
102+
rewriteFileNames: {
103+
fileStartNumber: Number(startFileNumber),
104+
},
105+
});
106+
107+
const baseUri = getBaseUriFromBatch(uris);
108+
const baseUriId = await getBaseURICount({
109+
contract: options.contract,
110+
});
111+
112+
const hashedPassword = await hashDelayedRevealPassword(
113+
baseUriId,
114+
options.password,
115+
options.contract,
116+
);
117+
const encryptedBaseURI = await encryptDecrypt({
118+
contract: options.contract,
119+
data: toHex(baseUri),
120+
key: hashedPassword,
121+
});
122+
123+
const chainId = BigInt(options.contract.chain.id);
124+
const provenanceHash = keccak256(
125+
encodePacked(
126+
["bytes", "bytes", "uint256"],
127+
[toHex(baseUri), hashedPassword, chainId],
128+
),
129+
);
130+
const data = encodeAbiParameters(
131+
[
132+
{ name: "baseUri", type: "bytes" },
133+
{ name: "provenanceHash", type: "bytes32" },
134+
],
135+
[encryptedBaseURI, provenanceHash],
136+
);
137+
138+
return {
139+
amount: BigInt(options.metadata.length),
140+
baseURIForTokens:
141+
placeholderUri.slice(-1) === "/"
142+
? placeholderUri
143+
: `${placeholderUri}/`,
144+
extraData: data,
145+
} as const;
146+
},
147+
contract: options.contract,
148+
});
149+
}
150+
151+
/**
152+
* Checks if the `createDelayedRevealBatch` method is supported by the given contract.
153+
* @param availableSelectors An array of 4byte function selectors of the contract. You can get this in various ways, such as using "whatsabi" or if you have the ABI of the contract available you can use it to generate the selectors.
154+
* @returns A boolean indicating if the `createDelayedRevealBatch` method is supported.
155+
* @extension ERC721
156+
* @example
157+
* ```ts
158+
* import { isCreateDelayedRevealBatchSupported } from "thirdweb/extensions/erc721";
159+
* const supported = isCreateDelayedRevealBatchSupported(["0x..."]);
160+
* ```
161+
*/
162+
export function isCreateDelayedRevealBatchSupported(
163+
availableSelectors: string[],
164+
) {
165+
return [
166+
isGetBaseURICountSupported(availableSelectors),
167+
isEncryptDecryptSupported(availableSelectors),
168+
isLazyMintSupported(availableSelectors),
169+
].every(Boolean);
170+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { beforeAll, describe, expect, it } from "vitest";
2+
import { ANVIL_CHAIN } from "../../../../../test/src/chains.js";
3+
import { TEST_CLIENT } from "../../../../../test/src/test-clients.js";
4+
import {
5+
getContract,
6+
type ThirdwebContract,
7+
} from "../../../../contract/contract.js";
8+
import { reveal } from "./reveal.js";
9+
10+
describe("reveal", () => {
11+
let contract: ThirdwebContract;
12+
beforeAll(() => {
13+
contract = getContract({
14+
address: "0x708781BAE850faA490cB5b5b16b4687Ec0A8D65D",
15+
chain: ANVIL_CHAIN,
16+
client: TEST_CLIENT,
17+
});
18+
});
19+
20+
it("should throw an error if password is missing", async () => {
21+
const options = {
22+
batchId: 1n,
23+
contract,
24+
password: "",
25+
};
26+
27+
expect(() => reveal(options)).toThrowError("Password is required");
28+
});
29+
});

0 commit comments

Comments
 (0)