@zoralabs/protocol-sdk 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.turbo/turbo-build.log +15 -0
  2. package/CHANGELOG.md +47 -0
  3. package/README.md +163 -0
  4. package/dist/anvil.d.ts +16 -0
  5. package/dist/anvil.d.ts.map +1 -0
  6. package/dist/apis/chain-constants.d.ts +21 -0
  7. package/dist/apis/chain-constants.d.ts.map +1 -0
  8. package/dist/apis/client-base.d.ts +15 -0
  9. package/dist/apis/client-base.d.ts.map +1 -0
  10. package/dist/apis/generated/discover-api-types.d.ts +2131 -0
  11. package/dist/apis/generated/discover-api-types.d.ts.map +1 -0
  12. package/dist/apis/generated/premint-api-types.d.ts +356 -0
  13. package/dist/apis/generated/premint-api-types.d.ts.map +1 -0
  14. package/dist/apis/http-api-base.d.ts +26 -0
  15. package/dist/apis/http-api-base.d.ts.map +1 -0
  16. package/dist/constants.d.ts +4 -0
  17. package/dist/constants.d.ts.map +1 -0
  18. package/dist/create/1155-create-helper.d.ts +63 -0
  19. package/dist/create/1155-create-helper.d.ts.map +1 -0
  20. package/dist/create/1155-create-helper.test.d.ts +2 -0
  21. package/dist/create/1155-create-helper.test.d.ts.map +1 -0
  22. package/dist/index.cjs +1006 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +984 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/mint/mint-api-client.d.ts +20 -0
  29. package/dist/mint/mint-api-client.d.ts.map +1 -0
  30. package/dist/mint/mint-client.d.ts +237 -0
  31. package/dist/mint/mint-client.d.ts.map +1 -0
  32. package/dist/mint/mint-client.test.d.ts +2 -0
  33. package/dist/mint/mint-client.test.d.ts.map +1 -0
  34. package/dist/premint/premint-api-client.d.ts +19 -0
  35. package/dist/premint/premint-api-client.d.ts.map +1 -0
  36. package/dist/premint/premint-client.d.ts +320 -0
  37. package/dist/premint/premint-client.d.ts.map +1 -0
  38. package/dist/premint/premint-client.test.d.ts +2 -0
  39. package/dist/premint/premint-client.test.d.ts.map +1 -0
  40. package/dist/premint/preminter.d.ts +25 -0
  41. package/dist/premint/preminter.d.ts.map +1 -0
  42. package/dist/premint/preminter.test.d.ts +2 -0
  43. package/dist/premint/preminter.test.d.ts.map +1 -0
  44. package/dist/preminter.d.ts +25 -0
  45. package/dist/preminter.d.ts.map +1 -0
  46. package/dist/preminter.test.d.ts +451 -0
  47. package/dist/preminter.test.d.ts.map +1 -0
  48. package/package.json +28 -0
  49. package/src/anvil.ts +84 -0
  50. package/src/apis/chain-constants.ts +101 -0
  51. package/src/apis/client-base.ts +29 -0
  52. package/src/apis/generated/discover-api-types.ts +2138 -0
  53. package/src/apis/generated/premint-api-types.ts +363 -0
  54. package/src/apis/http-api-base.ts +93 -0
  55. package/src/constants.ts +10 -0
  56. package/src/create/1155-create-helper.test.ts +90 -0
  57. package/src/create/1155-create-helper.ts +342 -0
  58. package/src/index.ts +9 -0
  59. package/src/mint/mint-api-client.ts +52 -0
  60. package/src/mint/mint-client.test.ts +117 -0
  61. package/src/mint/mint-client.ts +218 -0
  62. package/src/premint/premint-api-client.ts +57 -0
  63. package/src/premint/premint-client.test.ts +196 -0
  64. package/src/premint/premint-client.ts +619 -0
  65. package/src/premint/preminter.test.ts +502 -0
  66. package/src/premint/preminter.ts +72 -0
  67. package/src/preminter.test.ts +510 -0
  68. package/src/preminter.ts +72 -0
  69. package/tsconfig.json +25 -0
  70. package/tsup.config.js +10 -0
@@ -0,0 +1,342 @@
1
+ import {
2
+ zoraCreator1155FactoryImplABI,
3
+ zoraCreator1155FactoryImplAddress,
4
+ zoraCreator1155ImplABI,
5
+ zoraCreatorFixedPriceSaleStrategyABI,
6
+ } from "@zoralabs/protocol-deployments";
7
+ import type {
8
+ Address,
9
+ Hex,
10
+ PublicClient,
11
+ TransactionReceipt,
12
+ WalletClient,
13
+ } from "viem";
14
+ import { decodeEventLog, encodeFunctionData, zeroAddress } from "viem";
15
+ import { OPEN_EDITION_MINT_SIZE } from "../constants";
16
+
17
+ // Sales end forever amount (uint64 max)
18
+ const SALE_END_FOREVER = 18446744073709551615n;
19
+
20
+ // Default royalty bps
21
+ const ROYALTY_BPS_DEFAULT = 1000;
22
+
23
+ type SalesConfigParamsType = {
24
+ // defaults to 0
25
+ pricePerToken?: bigint;
26
+ // defaults to 0, in seconds
27
+ saleStart?: bigint;
28
+ // defaults to forever, in seconds
29
+ saleEnd?: bigint;
30
+ // max tokens that can be minted per address
31
+ maxTokensPerAddress?: bigint;
32
+ fundsRecipient?: Address;
33
+ };
34
+
35
+ export const DEFAULT_SALE_SETTINGS = {
36
+ fundsRecipient: zeroAddress,
37
+ // Free Mint
38
+ pricePerToken: 0n,
39
+ // Sale start time – defaults to beginning of unix time
40
+ saleStart: 0n,
41
+ // This is the end of uint64, plenty of time
42
+ saleEnd: SALE_END_FOREVER,
43
+ // 0 Here means no limit
44
+ maxTokensPerAddress: 0n,
45
+ };
46
+
47
+ // Hardcode the permission bit for the minter
48
+ const PERMISSION_BIT_MINTER = 2n ** 2n;
49
+
50
+ type ContractType =
51
+ | {
52
+ name: string;
53
+ uri: string;
54
+ defaultAdmin?: Address;
55
+ }
56
+ | Address;
57
+
58
+ type RoyaltySettingsType = {
59
+ royaltyBPS: number;
60
+ royaltyRecipient: Address;
61
+ };
62
+
63
+ export function create1155TokenSetupArgs({
64
+ nextTokenId,
65
+ // How many NFTs upon initialization to mint to the creator
66
+ mintToCreatorCount,
67
+ tokenMetadataURI,
68
+ // Fixed price minter address – required minter
69
+ fixedPriceMinterAddress,
70
+ // Address to use as the create referral, optional.
71
+ createReferral,
72
+ // Optional max supply of the token. Default unlimited
73
+ maxSupply,
74
+ // wallet sending the transaction
75
+ account,
76
+ salesConfig,
77
+ royaltySettings,
78
+ }: {
79
+ maxSupply?: bigint | number;
80
+ createReferral?: Address;
81
+ nextTokenId: bigint;
82
+ mintToCreatorCount: bigint | number;
83
+ // wallet sending the transaction
84
+ account: Address;
85
+ tokenMetadataURI: string;
86
+ fixedPriceMinterAddress: Address;
87
+ salesConfig: SalesConfigParamsType;
88
+ royaltySettings?: RoyaltySettingsType;
89
+ }) {
90
+ if (!maxSupply) {
91
+ maxSupply = OPEN_EDITION_MINT_SIZE;
92
+ }
93
+ maxSupply = BigInt(maxSupply);
94
+ mintToCreatorCount = BigInt(mintToCreatorCount);
95
+
96
+ const salesConfigWithDefaults = {
97
+ // Set static sales default.
98
+ ...DEFAULT_SALE_SETTINGS,
99
+ // Override with user settings.
100
+ ...salesConfig,
101
+ };
102
+
103
+ const setupActions = [
104
+ encodeFunctionData({
105
+ abi: zoraCreator1155ImplABI,
106
+ functionName: "addPermission",
107
+ args: [0n, fixedPriceMinterAddress, PERMISSION_BIT_MINTER],
108
+ }),
109
+ encodeFunctionData({
110
+ abi: zoraCreator1155ImplABI,
111
+ functionName: "assumeLastTokenIdMatches",
112
+ args: [nextTokenId - 1n],
113
+ }),
114
+ createReferral
115
+ ? encodeFunctionData({
116
+ abi: zoraCreator1155ImplABI,
117
+ functionName: "setupNewTokenWithCreateReferral",
118
+ args: [tokenMetadataURI, maxSupply, createReferral],
119
+ })
120
+ : encodeFunctionData({
121
+ abi: zoraCreator1155ImplABI,
122
+ functionName: "setupNewToken",
123
+ args: [tokenMetadataURI, maxSupply],
124
+ }),
125
+ encodeFunctionData({
126
+ abi: zoraCreator1155ImplABI,
127
+ functionName: "callSale",
128
+ args: [
129
+ nextTokenId,
130
+ fixedPriceMinterAddress,
131
+ encodeFunctionData({
132
+ abi: zoraCreatorFixedPriceSaleStrategyABI,
133
+ functionName: "setSale",
134
+ args: [nextTokenId, salesConfigWithDefaults],
135
+ }),
136
+ ],
137
+ }),
138
+ ];
139
+
140
+ if (mintToCreatorCount) {
141
+ setupActions.push(
142
+ encodeFunctionData({
143
+ abi: zoraCreator1155ImplABI,
144
+ functionName: "adminMint",
145
+ args: [account, nextTokenId, mintToCreatorCount, "0x"],
146
+ }),
147
+ );
148
+ }
149
+
150
+ if (royaltySettings) {
151
+ setupActions.push(
152
+ encodeFunctionData({
153
+ abi: zoraCreator1155ImplABI,
154
+ functionName: "updateRoyaltiesForToken",
155
+ args: [
156
+ nextTokenId,
157
+ {
158
+ royaltyMintSchedule: 0,
159
+ royaltyBPS: royaltySettings?.royaltyBPS || ROYALTY_BPS_DEFAULT,
160
+ royaltyRecipient: royaltySettings?.royaltyRecipient || account,
161
+ },
162
+ ],
163
+ }),
164
+ );
165
+ }
166
+
167
+ return setupActions;
168
+ }
169
+
170
+ export const getTokenIdFromCreateReceipt = (
171
+ receipt: TransactionReceipt,
172
+ ): bigint | undefined => {
173
+ for (const data of receipt.logs) {
174
+ try {
175
+ const decodedLog = decodeEventLog({
176
+ abi: zoraCreator1155ImplABI,
177
+ eventName: "SetupNewToken",
178
+ ...data,
179
+ });
180
+ if (decodedLog && decodedLog.eventName === "SetupNewToken") {
181
+ return decodedLog.args.tokenId;
182
+ }
183
+ } catch (err: any) {}
184
+ }
185
+ };
186
+
187
+ async function getContractExists(
188
+ publicClient: PublicClient,
189
+ contract: ContractType,
190
+ // Account that is the creator of the contract
191
+ account: Address,
192
+ ) {
193
+ let contractAddress;
194
+ let contractExists = false;
195
+ if (typeof contract !== "string") {
196
+ contractAddress = await publicClient.readContract({
197
+ abi: zoraCreator1155FactoryImplABI,
198
+ // Since this address is deterministic we can hardcode a chain id safely here.
199
+ address: zoraCreator1155FactoryImplAddress[999],
200
+ functionName: "deterministicContractAddress",
201
+ args: [
202
+ account,
203
+ contract.uri,
204
+ contract.name,
205
+ contract.defaultAdmin || account,
206
+ ],
207
+ });
208
+
209
+ try {
210
+ await publicClient.readContract({
211
+ abi: zoraCreator1155ImplABI,
212
+ address: contractAddress,
213
+ functionName: "contractVersion",
214
+ });
215
+ contractExists = true;
216
+ } catch (e: any) {
217
+ // This logic branch is hit if the contract doesn't exist
218
+ // falling back to contractExists to false.
219
+ }
220
+ return { contractAddress, contractExists };
221
+ }
222
+
223
+ return {
224
+ contractExists: true,
225
+ contractAddress: contract,
226
+ };
227
+ }
228
+
229
+ // Create new 1155 token
230
+ export async function createNew1155Token({
231
+ publicClient,
232
+ contract,
233
+ tokenMetadataURI,
234
+ mintToCreatorCount = 1,
235
+ salesConfig = {},
236
+ maxSupply,
237
+ account,
238
+ royaltySettings,
239
+ getAdditionalSetupActions,
240
+ }: {
241
+ publicClient: PublicClient;
242
+ account: Address;
243
+ maxSupply?: bigint | number;
244
+ royaltySettings?: RoyaltySettingsType;
245
+ royaltyBPS?: number;
246
+ contract: ContractType;
247
+ tokenMetadataURI: string;
248
+ mintToCreatorCount?: bigint | number;
249
+ salesConfig?: SalesConfigParamsType;
250
+ getAdditionalSetupActions?: (args: {
251
+ tokenId: bigint;
252
+ contractAddress: Address;
253
+ }) => Hex[];
254
+ }) {
255
+ // Check if contract exists either from metadata or the static address passed in.
256
+ // If a static address is passed in, this fails if that contract does not exist.
257
+ const { contractExists, contractAddress } = await getContractExists(
258
+ publicClient,
259
+ contract,
260
+ account,
261
+ );
262
+
263
+ // Assume the next token id is the first token available for a new contract.
264
+ let nextTokenId = 1n;
265
+
266
+ if (contractExists) {
267
+ nextTokenId = await publicClient.readContract({
268
+ abi: zoraCreator1155ImplABI,
269
+ functionName: "nextTokenId",
270
+ address: contractAddress,
271
+ });
272
+ }
273
+
274
+ // Get the fixed price minter to use within the new token to set the sales configuration.
275
+ const fixedPriceMinterAddress = await publicClient.readContract({
276
+ abi: zoraCreator1155FactoryImplABI,
277
+ address: zoraCreator1155FactoryImplAddress[999],
278
+ functionName: "fixedPriceMinter",
279
+ });
280
+
281
+ let tokenSetupActions = create1155TokenSetupArgs({
282
+ tokenMetadataURI,
283
+ nextTokenId,
284
+ salesConfig,
285
+ maxSupply,
286
+ fixedPriceMinterAddress,
287
+ account,
288
+ mintToCreatorCount,
289
+ royaltySettings,
290
+ });
291
+ if (getAdditionalSetupActions) {
292
+ tokenSetupActions = [
293
+ ...getAdditionalSetupActions({ tokenId: nextTokenId, contractAddress }),
294
+ ...tokenSetupActions,
295
+ ];
296
+ }
297
+
298
+ if (!contractAddress && typeof contract === "string") {
299
+ throw new Error("Invariant: contract cannot be missing and an address");
300
+ }
301
+ if (!contractExists && typeof contract !== "string") {
302
+ const { request } = await publicClient.simulateContract({
303
+ abi: zoraCreator1155FactoryImplABI,
304
+ functionName: "createContractDeterministic",
305
+ account,
306
+ address: zoraCreator1155FactoryImplAddress[999],
307
+ args: [
308
+ contract.uri,
309
+ contract.name,
310
+ {
311
+ // deprecated
312
+ royaltyMintSchedule: 0,
313
+ royaltyBPS: royaltySettings?.royaltyBPS || ROYALTY_BPS_DEFAULT,
314
+ royaltyRecipient: royaltySettings?.royaltyRecipient || account,
315
+ },
316
+ contract.defaultAdmin || account,
317
+ tokenSetupActions,
318
+ ],
319
+ });
320
+ return {
321
+ send: (walletClient: WalletClient) => walletClient.writeContract(request),
322
+ tokenSetupActions,
323
+ contractAddress,
324
+ contractExists,
325
+ };
326
+ } else if (contractExists) {
327
+ const { request } = await publicClient.simulateContract({
328
+ abi: zoraCreator1155ImplABI,
329
+ functionName: "multicall",
330
+ account,
331
+ address: contractAddress,
332
+ args: [tokenSetupActions],
333
+ });
334
+ return {
335
+ send: (walletClient: WalletClient) => walletClient.writeContract(request),
336
+ tokenSetupActions,
337
+ contractAddress,
338
+ contractExists,
339
+ };
340
+ }
341
+ throw new Error("Unsupported contract argument type");
342
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export * from "./premint/premint-client";
2
+
3
+ export * from "./premint/preminter";
4
+
5
+ export * from "./premint/premint-api-client";
6
+
7
+ export * from "./mint/mint-api-client";
8
+
9
+ export * from "./create/1155-create-helper";
@@ -0,0 +1,52 @@
1
+ import { retries, get, post } from "../apis/http-api-base";
2
+ import { paths } from "../apis/generated/discover-api-types";
3
+ import { ZORA_API_BASE } from "../constants";
4
+
5
+ export type MintableGetToken =
6
+ paths["/mintables/{chain_name}/{collection_address}"];
7
+ type MintableGetTokenPathParameters =
8
+ MintableGetToken["get"]["parameters"]["path"];
9
+ type MintableGetTokenGetQueryParameters =
10
+ MintableGetToken["get"]["parameters"]["query"];
11
+ export type MintableGetTokenResponse =
12
+ MintableGetToken["get"]["responses"][200]["content"]["application/json"];
13
+
14
+ function encodeQueryParameters(params: Record<string, string>) {
15
+ return new URLSearchParams(params).toString();
16
+ }
17
+
18
+ const getMintable = async (
19
+ path: MintableGetTokenPathParameters,
20
+ query: MintableGetTokenGetQueryParameters,
21
+ ): Promise<MintableGetTokenResponse> =>
22
+ retries(() => {
23
+ return get<MintableGetTokenResponse>(
24
+ `${ZORA_API_BASE}discover/mintables/${path.chain_name}/${
25
+ path.collection_address
26
+ }${query?.token_id ? `?${encodeQueryParameters(query)}` : ""}`,
27
+ );
28
+ });
29
+
30
+ export const getSalesConfigFixedPrice = async ({
31
+ contractAddress,
32
+ tokenId,
33
+ subgraphUrl,
34
+ }: {
35
+ contractAddress: string;
36
+ tokenId: string;
37
+ subgraphUrl: string;
38
+ }): Promise<undefined | string> =>
39
+ retries(async () => {
40
+ const response = await post<any>(subgraphUrl, {
41
+ query:
42
+ "query($id: ID!) {\n zoraCreateToken(id: $id) {\n id\n salesStrategies{\n fixedPrice {\n address\n }\n }\n }\n}",
43
+ variables: { id: `${contractAddress.toLowerCase()}-${tokenId}` },
44
+ });
45
+ return response.zoraCreateToken?.salesStrategies?.find(() => true)
46
+ ?.fixedPriceMinterAddress;
47
+ });
48
+
49
+ export const MintAPIClient = {
50
+ getMintable,
51
+ getSalesConfigFixedPrice,
52
+ };
@@ -0,0 +1,117 @@
1
+ import { parseAbi, parseEther } from "viem";
2
+ import { zora } from "viem/chains";
3
+ import { describe, expect } from "vitest";
4
+ import { MintClient } from "./mint-client";
5
+ import { zoraCreator1155ImplABI } from "@zoralabs/protocol-deployments";
6
+ import { anvilTest } from "src/anvil";
7
+
8
+ const erc721ABI = parseAbi([
9
+ "function balanceOf(address owner) public view returns (uint256)",
10
+ ] as const);
11
+
12
+ describe("mint-helper", () => {
13
+ anvilTest(
14
+ "mints a new 1155 token",
15
+ async ({ viemClients }) => {
16
+ const { testClient, walletClient, publicClient } = viemClients;
17
+ const creatorAccount = (await walletClient.getAddresses())[0]!;
18
+ await testClient.setBalance({
19
+ address: creatorAccount,
20
+ value: parseEther("2000"),
21
+ });
22
+ const targetContract = "0xa2fea3537915dc6c7c7a97a82d1236041e6feb2e";
23
+ const targetTokenId = 1n;
24
+ const minter = new MintClient(zora);
25
+
26
+ const { simulateContractParameters: params } =
27
+ await minter.makePrepareMintTokenParams({
28
+ publicClient,
29
+ minterAccount: creatorAccount,
30
+ mintable: await minter.getMintable({
31
+ tokenId: targetTokenId,
32
+ tokenContract: targetContract,
33
+ }),
34
+ mintArguments: {
35
+ mintToAddress: creatorAccount,
36
+ quantityToMint: 1,
37
+ },
38
+ });
39
+
40
+ const oldBalance = await publicClient.readContract({
41
+ abi: zoraCreator1155ImplABI,
42
+ address: targetContract,
43
+ functionName: "balanceOf",
44
+ args: [creatorAccount, targetTokenId],
45
+ });
46
+
47
+ const simulationResult = await publicClient.simulateContract(params);
48
+
49
+ const hash = await walletClient.writeContract(simulationResult.request);
50
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
51
+ const newBalance = await publicClient.readContract({
52
+ abi: zoraCreator1155ImplABI,
53
+ address: targetContract,
54
+ functionName: "balanceOf",
55
+ args: [creatorAccount, targetTokenId],
56
+ });
57
+ expect(receipt).to.not.be.null;
58
+ expect(oldBalance).to.be.equal(0n);
59
+ expect(newBalance).to.be.equal(1n);
60
+ },
61
+ 12 * 1000,
62
+ );
63
+
64
+ anvilTest(
65
+ "mints a new 721 token",
66
+ async ({ viemClients }) => {
67
+ const { testClient, walletClient, publicClient } = viemClients;
68
+ const creatorAccount = (await walletClient.getAddresses())[0]!;
69
+ await testClient.setBalance({
70
+ address: creatorAccount,
71
+ value: parseEther("2000"),
72
+ });
73
+
74
+ const targetContract = "0x7aae7e67515A2CbB8585C707Ca6db37BDd3EA839";
75
+ const targetTokenId = undefined;
76
+ const minter = new MintClient(zora);
77
+
78
+ const { simulateContractParameters: prepared } =
79
+ await minter.makePrepareMintTokenParams({
80
+ mintable: await minter.getMintable({
81
+ tokenContract: targetContract,
82
+ tokenId: targetTokenId,
83
+ }),
84
+ publicClient,
85
+ minterAccount: creatorAccount,
86
+ mintArguments: {
87
+ mintToAddress: creatorAccount,
88
+ quantityToMint: 1,
89
+ },
90
+ });
91
+ const oldBalance = await publicClient.readContract({
92
+ abi: erc721ABI,
93
+ address: targetContract,
94
+ functionName: "balanceOf",
95
+ args: [creatorAccount],
96
+ });
97
+
98
+ const simulated = await publicClient.simulateContract(prepared);
99
+
100
+ const hash = await walletClient.writeContract(simulated.request);
101
+
102
+ const receipt = await publicClient.getTransactionReceipt({ hash });
103
+ expect(receipt).not.to.be.null;
104
+
105
+ const newBalance = await publicClient.readContract({
106
+ abi: erc721ABI,
107
+ address: targetContract,
108
+ functionName: "balanceOf",
109
+ args: [creatorAccount],
110
+ });
111
+
112
+ expect(oldBalance).to.be.equal(0n);
113
+ expect(newBalance).to.be.equal(1n);
114
+ },
115
+ 12 * 1000,
116
+ );
117
+ });