@zoralabs/protocol-sdk 0.6.0 → 0.7.1

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 (86) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/CHANGELOG.md +12 -0
  3. package/dist/constants.d.ts +0 -1
  4. package/dist/constants.d.ts.map +1 -1
  5. package/dist/index.cjs +2023 -431
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2001 -412
  10. package/dist/index.js.map +1 -1
  11. package/dist/ipfs/arweave.d.ts +3 -0
  12. package/dist/ipfs/arweave.d.ts.map +1 -0
  13. package/dist/ipfs/gateway.d.ts +4 -0
  14. package/dist/ipfs/gateway.d.ts.map +1 -0
  15. package/dist/ipfs/index.d.ts +4 -0
  16. package/dist/ipfs/index.d.ts.map +1 -0
  17. package/dist/ipfs/ipfs.d.ts +5 -0
  18. package/dist/ipfs/ipfs.d.ts.map +1 -0
  19. package/dist/ipfs/mimeTypes.d.ts +25 -0
  20. package/dist/ipfs/mimeTypes.d.ts.map +1 -0
  21. package/dist/ipfs/text-metadata.d.ts +5 -0
  22. package/dist/ipfs/text-metadata.d.ts.map +1 -0
  23. package/dist/ipfs/token-metadata.d.ts +14 -0
  24. package/dist/ipfs/token-metadata.d.ts.map +1 -0
  25. package/dist/ipfs/types.d.ts +49 -0
  26. package/dist/ipfs/types.d.ts.map +1 -0
  27. package/dist/mint/mint-client.d.ts +28 -4071
  28. package/dist/mint/mint-client.d.ts.map +1 -1
  29. package/dist/mint/mint-queries.d.ts +38 -0
  30. package/dist/mint/mint-queries.d.ts.map +1 -0
  31. package/dist/mint/mint-transactions.d.ts +20 -0
  32. package/dist/mint/mint-transactions.d.ts.map +1 -0
  33. package/dist/mint/subgraph-mint-getter.d.ts +14 -7
  34. package/dist/mint/subgraph-mint-getter.d.ts.map +1 -1
  35. package/dist/mint/subgraph-queries.d.ts +55 -0
  36. package/dist/mint/subgraph-queries.d.ts.map +1 -0
  37. package/dist/mint/types.d.ts +117 -19
  38. package/dist/mint/types.d.ts.map +1 -1
  39. package/dist/mint/utils.d.ts +2 -0
  40. package/dist/mint/utils.d.ts.map +1 -0
  41. package/dist/mints/mints-contracts.d.ts +3 -4494
  42. package/dist/mints/mints-contracts.d.ts.map +1 -1
  43. package/dist/premint/conversions.d.ts +12 -15
  44. package/dist/premint/conversions.d.ts.map +1 -1
  45. package/dist/premint/premint-api-client.d.ts +14 -11
  46. package/dist/premint/premint-api-client.d.ts.map +1 -1
  47. package/dist/premint/premint-client.d.ts +17 -6
  48. package/dist/premint/premint-client.d.ts.map +1 -1
  49. package/dist/premint/preminter.d.ts +1 -1
  50. package/dist/premint/preminter.d.ts.map +1 -1
  51. package/dist/sdk.d.ts +4 -2
  52. package/dist/sdk.d.ts.map +1 -1
  53. package/dist/types.d.ts +3 -0
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/utils.d.ts +10 -2
  56. package/dist/utils.d.ts.map +1 -1
  57. package/package.json +2 -1
  58. package/src/constants.ts +0 -36
  59. package/src/create/1155-create-helper.test.ts +9 -7
  60. package/src/index.ts +2 -0
  61. package/src/ipfs/arweave.ts +5 -0
  62. package/src/ipfs/gateway.ts +48 -0
  63. package/src/ipfs/index.ts +7 -0
  64. package/src/ipfs/ipfs.ts +82 -0
  65. package/src/ipfs/mimeTypes.ts +141 -0
  66. package/src/ipfs/text-metadata.ts +128 -0
  67. package/src/ipfs/token-metadata.ts +99 -0
  68. package/src/ipfs/types.ts +54 -0
  69. package/src/mint/mint-client.test.ts +96 -47
  70. package/src/mint/mint-client.ts +75 -343
  71. package/src/mint/mint-queries.ts +320 -0
  72. package/src/mint/mint-transactions.ts +253 -0
  73. package/src/mint/subgraph-mint-getter.ts +216 -123
  74. package/src/mint/subgraph-queries.ts +170 -0
  75. package/src/mint/types.ts +140 -23
  76. package/src/mint/utils.ts +14 -0
  77. package/src/premint/conversions.ts +26 -2
  78. package/src/premint/premint-api-client.ts +48 -16
  79. package/src/premint/premint-client.test.ts +29 -23
  80. package/src/premint/premint-client.ts +73 -37
  81. package/src/premint/preminter.ts +2 -3
  82. package/src/sdk.ts +7 -4
  83. package/src/types.ts +18 -0
  84. package/src/utils.ts +29 -28
  85. package/test-integration/setup-test-contracts.ts +96 -0
  86. package/test-integration/premint-client.test.ts +0 -148
@@ -0,0 +1,141 @@
1
+ // text
2
+ const HTML = "text/html";
3
+ const MARKDOWN = "text/markdown";
4
+ const MARKDOWN_UTF8 = "text/markdown; charset=utf-8";
5
+ const TEXT_PLAIN_UTF8 = "text/plain; charset=utf-8";
6
+ export const TEXT_PLAIN = "text/plain";
7
+ const CSV = "text/csv";
8
+ const NUMBERS = ".numbers";
9
+ const EXCEL = ".xlsx";
10
+ const PDF = "application/pdf";
11
+
12
+ // image
13
+ const JPG = "image/jpg";
14
+ const JPEG = "image/jpeg";
15
+ const PNG = "image/png";
16
+ const WEBP = "image/webp";
17
+ const SVG = "image/svg+xml";
18
+ const TIFF = "image/tiff";
19
+ const GIF = "image/gif";
20
+
21
+ export const isImage = (mimeType: string | null | undefined) => {
22
+ if (!mimeType) return false;
23
+ return [JPG, JPEG, PNG, WEBP, SVG, TIFF, GIF].includes(mimeType);
24
+ };
25
+
26
+ export enum MediaType {
27
+ CSV = "CSV",
28
+ NUMBERS = "NUMBERS",
29
+ EXCEL = "EXCEL",
30
+ IMAGE = "IMAGE",
31
+ VIDEO = "VIDEO",
32
+ AUDIO = "AUDIO",
33
+ TIFF = "TIFF",
34
+ TEXT = "TEXT",
35
+ PDF = "PDF",
36
+ MODEL = "MODEL",
37
+ HTML = "HTML",
38
+ ZIP = "ZIP",
39
+ UNKNOWN = "UNKNOWN",
40
+ }
41
+
42
+ export const DEFAULT_THUMBNAIL_CID_HASHES: { [key: string]: string } = {
43
+ [MediaType.AUDIO]:
44
+ "bafkreidir5laqi26ta6ivnpe2zpekgrfcyi4tb5x6vhwmwnledmzxshfb4",
45
+ [MediaType.VIDEO]:
46
+ "bafkreifm4edadl3j5luoyvw4p6elxeqd77la7bulee6vhq5gq4chfk32mu",
47
+ [MediaType.HTML]:
48
+ "bafkreifgvi6xfwqy2l6g45csyokejpaib52ee7zrw6etrxl2tas4xkkclq",
49
+ [MediaType.ZIP]:
50
+ "bafkreihe5rr5jbkwzegisjlhxbb7jw22xw5oilfmgd2re6tz6buo4pasdq", // assuming all zip files are html directories
51
+ [MediaType.TEXT]:
52
+ "bafkreiaez25nfgggzrnza2loxf6xueb2esm44pnyjyulwoslnipowrf56q",
53
+ default: "bafkreihcoahllisbpb4eeypdwtm7go5uh275wxd7wf2tantpxlpjhviok4",
54
+ };
55
+
56
+ // video
57
+ const MP4 = "video/mp4";
58
+ const QUICKTIME = "video/quicktime";
59
+ const M4V = "video/x-m4v";
60
+ const WEBM = "video/webm";
61
+
62
+ // audio
63
+ const M4A = "audio/x-m4a";
64
+ const MPEG = "audio/mpeg";
65
+ const MP3 = "audio/mp3";
66
+ const WAV = "audio/wav";
67
+ const VND_WAV = "audio/vnd.wav";
68
+ const VND_WAVE = "audio/vnd.wave";
69
+ const WAVE = "audio/wave";
70
+ const X_WAV = "audio/x-wav";
71
+ const AIFF = "audio/aiff";
72
+
73
+ // 3D
74
+ const GLTF = "model/gltf+json";
75
+ const GLB = "model/gltf-binary";
76
+ // File extensions, as some files return '' as the mimetype
77
+ const GLTF_EXT = ".gltf";
78
+ const GLB_EXT = ".glb";
79
+
80
+ // application
81
+ export const JSON_MIME_TYPE = "application/json";
82
+ const ZIP = "application/zip";
83
+
84
+ const mimeToMediaType = {
85
+ [HTML]: MediaType.HTML,
86
+ [JPG]: MediaType.IMAGE,
87
+ [JPEG]: MediaType.IMAGE,
88
+ [PNG]: MediaType.IMAGE,
89
+ [WEBP]: MediaType.IMAGE,
90
+ [SVG]: MediaType.IMAGE,
91
+ [TIFF]: MediaType.TIFF,
92
+ [GIF]: MediaType.IMAGE,
93
+ [MP4]: MediaType.VIDEO,
94
+ [WEBM]: MediaType.VIDEO,
95
+ [QUICKTIME]: MediaType.VIDEO,
96
+ [M4V]: MediaType.VIDEO,
97
+ [MPEG]: MediaType.AUDIO,
98
+ [MP3]: MediaType.AUDIO,
99
+ [M4A]: MediaType.AUDIO,
100
+ [VND_WAV]: MediaType.AUDIO,
101
+ [VND_WAVE]: MediaType.AUDIO,
102
+ [WAV]: MediaType.AUDIO,
103
+ [WAVE]: MediaType.AUDIO,
104
+ [X_WAV]: MediaType.AUDIO,
105
+ [AIFF]: MediaType.AUDIO,
106
+ [TEXT_PLAIN]: MediaType.TEXT,
107
+ [TEXT_PLAIN_UTF8]: MediaType.TEXT,
108
+ [MARKDOWN]: MediaType.TEXT,
109
+ [MARKDOWN_UTF8]: MediaType.TEXT,
110
+ [CSV]: MediaType.CSV,
111
+ [NUMBERS]: MediaType.NUMBERS,
112
+ [EXCEL]: MediaType.EXCEL,
113
+ [PDF]: MediaType.PDF,
114
+ [ZIP]: MediaType.ZIP,
115
+ [GLTF]: MediaType.MODEL,
116
+ [GLTF_EXT]: MediaType.MODEL,
117
+ [GLB]: MediaType.MODEL,
118
+ // GLTF returns 'application/json' as the mimetype,
119
+ // and as the only JSON-encoded media we currently support,
120
+ // we assume that if the mimetype is JSON, it's a GLTF
121
+ [JSON_MIME_TYPE]: MediaType.MODEL,
122
+ [GLB_EXT]: MediaType.MODEL,
123
+ } as const;
124
+
125
+ /** Return a MediaType for the given mime type. If mime type is unknown you can provide a filename as a fallback, where the type will be guessed based on extension. */
126
+ export function mimeTypeToMedia(mimeType?: string | null) {
127
+ if (!mimeType) return MediaType.UNKNOWN;
128
+
129
+ return (
130
+ mimeToMediaType[mimeType as keyof typeof mimeToMediaType] ||
131
+ MediaType.UNKNOWN
132
+ );
133
+ }
134
+
135
+ export async function getMimeType(uri?: string) {
136
+ if (!uri) return uri;
137
+
138
+ const res = await fetch(uri, { method: "HEAD" });
139
+ let mimeType = res.headers.get("content-type");
140
+ return mimeType;
141
+ }
@@ -0,0 +1,128 @@
1
+ import { TextMetadataFiles } from "./types";
2
+
3
+ const CHAR_LIMIT = 1111;
4
+
5
+ const wrapText = ({
6
+ ctx,
7
+ text,
8
+ x,
9
+ y,
10
+ maxWidth,
11
+ lineHeight,
12
+ }: {
13
+ ctx: CanvasRenderingContext2D;
14
+ text: string;
15
+ x: number;
16
+ y: number;
17
+ maxWidth: number;
18
+ lineHeight: number;
19
+ }) => {
20
+ // Split text into words
21
+ let words = text.replaceAll("\n", " \n ").split(/ +/);
22
+ let line = ""; // This will store the text of the current line
23
+ let testLine = ""; // This will store the text when we add a word, to test if it's too long
24
+ let lineArray = []; // This is an array of lines, which the function will return
25
+
26
+ for (var n = 0; n < words.length; n++) {
27
+ // Measure text sizing
28
+ testLine += `${words[n]} `;
29
+ let metrics = ctx.measureText(testLine);
30
+ let testWidth = metrics.width;
31
+ // If the width of this test line is more than the max width
32
+ if (words[n]?.includes("\n") || (testWidth > maxWidth && n > 0)) {
33
+ // Then the line is finished, push the current line into "lineArray"
34
+ lineArray.push({ text: line, x, y });
35
+ // Start a new line
36
+ y += lineHeight;
37
+ // Update line and test line to use this word as the first word on the next line
38
+ // If it's a newline, then don't add a space
39
+ if (words[n]?.includes("\n")) {
40
+ line = ``;
41
+ testLine = ``;
42
+ } else {
43
+ line = `${words[n]} `;
44
+ testLine = `${words[n]} `;
45
+ }
46
+ } else {
47
+ // Test line is less than the max width, add the word to the current line
48
+ line += `${words[n]} `;
49
+ }
50
+ // Handle a single line...
51
+ if (n === words.length - 1) {
52
+ lineArray.push({ text: line, x, y });
53
+ }
54
+ }
55
+ return lineArray;
56
+ };
57
+
58
+ async function generateTextPreview(text: string): Promise<File> {
59
+ // Trim the text to a reasonable max length. Prevent crashes if the user pastes a gigantic string
60
+ const trimmedText = text.trim().slice(0, CHAR_LIMIT);
61
+
62
+ const [width, height] = [500, 500];
63
+ const padding = 20;
64
+ const dpr = 2;
65
+
66
+ const fontFamily = "Inter";
67
+ const [fontSize, lineHeight] = [16, 24];
68
+ const [textColor, backgroundColor] = ["black", "white"];
69
+
70
+ return new Promise((resolve, reject) => {
71
+ const canvas = document.createElement("canvas");
72
+ canvas.width = width * dpr;
73
+ canvas.height = height * dpr;
74
+ const ctx = canvas.getContext("2d");
75
+ if (!ctx) {
76
+ return reject(new Error("Could not create canvas context"));
77
+ }
78
+ ctx.fillStyle = backgroundColor;
79
+ ctx.fillRect(0, 0, width * dpr, width * dpr);
80
+ ctx.fillStyle = textColor;
81
+ ctx.font = `${fontSize * dpr}px ${fontFamily}`;
82
+ const wrapped = wrapText({
83
+ ctx,
84
+ text: trimmedText,
85
+ x: padding * dpr,
86
+ y: fontSize * dpr + padding * dpr,
87
+ maxWidth: width * dpr - padding * 2 * dpr,
88
+ lineHeight: lineHeight * dpr,
89
+ });
90
+ wrapped.forEach((line) => ctx.fillText(line.text, line.x, line.y));
91
+ canvas.toBlob((blob) => {
92
+ if (!blob) {
93
+ return reject(new Error("Could not create blob"));
94
+ }
95
+ resolve(new File([blob], "thumbnail.png", { type: "image/png" }));
96
+ });
97
+ });
98
+ }
99
+
100
+ function generateTextTitle(text: string) {
101
+ const firstLine = text.split("\n")[0]!;
102
+ const firstSentence = firstLine?.split(". ")[0]!;
103
+
104
+ if (firstSentence.length > 50) {
105
+ return firstSentence.slice(0, 50) + "...";
106
+ }
107
+
108
+ return firstSentence;
109
+ }
110
+
111
+ const toTextFile = (text: string) =>
112
+ new File([text], "Untitled.txt", { type: "text/plain" });
113
+
114
+ /** For text nfts, this will generate files that are needed for the metadata json, including the txt.file containing the text, and a thumbnail image containing a preview of the text
115
+ */
116
+ export async function generateTextNftMetadataFiles(
117
+ text: string,
118
+ ): Promise<TextMetadataFiles> {
119
+ const name = generateTextTitle(text);
120
+ const textFile = toTextFile(text);
121
+ const thumbnailFile = await generateTextPreview(text);
122
+
123
+ return {
124
+ name,
125
+ mediaUrlFile: textFile,
126
+ thumbnailFile,
127
+ };
128
+ }
@@ -0,0 +1,99 @@
1
+ import { getFetchableUrl } from "./gateway";
2
+ import {
3
+ DEFAULT_THUMBNAIL_CID_HASHES,
4
+ TEXT_PLAIN,
5
+ getMimeType,
6
+ isImage,
7
+ mimeTypeToMedia,
8
+ } from "./mimeTypes";
9
+ import {
10
+ MakeMediaMetadataParams,
11
+ MakeTextMetadataParams,
12
+ TokenMetadataJson,
13
+ } from "./types";
14
+
15
+ /**
16
+ * Takes properties for a text based nft and formats it as proper json metadata
17
+ * for the token, which should be uploaded to IPFS.
18
+ * @param parameters - The parameters to format into metadata {@link MakeTextMetadataParams}
19
+ */
20
+ export const makeTextTokenMetadata = (
21
+ parameters: MakeTextMetadataParams,
22
+ ): TokenMetadataJson => {
23
+ const { name, textFileUrl, thumbnailUrl, attributes = [] } = parameters;
24
+
25
+ const content = textFileUrl
26
+ ? {
27
+ mime: TEXT_PLAIN,
28
+ uri: textFileUrl,
29
+ }
30
+ : null;
31
+
32
+ const image = thumbnailUrl;
33
+ const animation_url = textFileUrl;
34
+
35
+ return {
36
+ name,
37
+ image,
38
+ animation_url,
39
+ content,
40
+ attributes,
41
+ };
42
+ };
43
+
44
+ /**
45
+ * Takes properties for a media based nft (video, image, etc) and formats it as proper json metadata
46
+ * for the token, which should be uploaded to IPFS.
47
+ * @param parameters - The parameters to format into metadata {@link MakeMediaMetadataParams}
48
+ */
49
+ export const makeMediaTokenMetadata = async ({
50
+ name,
51
+ description,
52
+ attributes = [],
53
+ mediaUrl,
54
+ thumbnailUrl,
55
+ }: MakeMediaMetadataParams): Promise<TokenMetadataJson> => {
56
+ const contentUrl = mediaUrl;
57
+ const fetchableContentUrl = getFetchableUrl(contentUrl);
58
+
59
+ if (!fetchableContentUrl)
60
+ throw new Error(`Content url (${contentUrl}) is not fetchable`);
61
+
62
+ const mimeType = await getMimeType(fetchableContentUrl);
63
+ const mediaType = mimeTypeToMedia(mimeType);
64
+
65
+ let image: string | undefined = undefined;
66
+ let animation_url: string | null = null;
67
+
68
+ // If the media is an image, just set the image field
69
+ // Otherwise we require a thumbnail, set image and animation_url
70
+ if (isImage(mimeType)) {
71
+ image = contentUrl;
72
+ } else {
73
+ image = thumbnailUrl;
74
+ animation_url = mediaUrl;
75
+ }
76
+
77
+ // If no image determined, use a fallback placeholder
78
+ if (!image)
79
+ image = `ipfs://${
80
+ DEFAULT_THUMBNAIL_CID_HASHES[mediaType] ||
81
+ DEFAULT_THUMBNAIL_CID_HASHES.default
82
+ }`;
83
+
84
+ const content = contentUrl
85
+ ? {
86
+ mime: mimeType || "application/octet-stream",
87
+ uri: contentUrl,
88
+ }
89
+ : null;
90
+
91
+ return {
92
+ name,
93
+ description,
94
+ image,
95
+ animation_url,
96
+ content,
97
+ attributes,
98
+ };
99
+ };
@@ -0,0 +1,54 @@
1
+ export type CreateERC1155TokenAttributes = {
2
+ trait_type: string;
3
+ value: string;
4
+ };
5
+
6
+ export type ContractMetadataJson = {
7
+ name?: string;
8
+ description?: string;
9
+ image?: string;
10
+ };
11
+
12
+ export type TokenMetadataJson = {
13
+ name: string;
14
+ description?: string;
15
+ /** Primary image file */
16
+ image?: string;
17
+ animation_url?: string | null;
18
+ content?: {
19
+ mime: string;
20
+ uri: string;
21
+ } | null;
22
+ attributes: Array<CreateERC1155TokenAttributes>;
23
+ };
24
+
25
+ export type BaseMetadataParams = {
26
+ /** Token name */
27
+ name: string;
28
+ /** Optional description */
29
+ description?: string;
30
+ /** Optional attributes to tag the token with */
31
+ attributes?: CreateERC1155TokenAttributes[];
32
+ };
33
+
34
+ export type MakeTextMetadataParams = BaseMetadataParams & {
35
+ /** Ipfs url where media is hosted */
36
+ textFileUrl: string;
37
+ /** If thumbnail was generate for text file, thumbnail image url */
38
+ thumbnailUrl?: string;
39
+ };
40
+
41
+ export type TextMetadataFiles = {
42
+ name: string;
43
+ /** File that holds the text, and is the primary media */
44
+ mediaUrlFile: File;
45
+ /** Thumbnail image preview of the text */
46
+ thumbnailFile: File;
47
+ };
48
+
49
+ export type MakeMediaMetadataParams = BaseMetadataParams & {
50
+ /** Ipfs url where media is hosted */
51
+ mediaUrl: string;
52
+ /** Ipfs url where thumbnail of media is hosted */
53
+ thumbnailUrl?: string;
54
+ };
@@ -1,20 +1,20 @@
1
1
  import { describe, expect } from "vitest";
2
2
  import { Address, erc20Abi, parseAbi, parseEther } from "viem";
3
- import { zora } from "viem/chains";
4
- import {
5
- zoraCreator1155ImplABI,
6
- erc20MinterAddress as erc20MinterAddresses,
7
- } from "@zoralabs/protocol-deployments";
8
- import { anvilTest, forkUrls, makeAnvilTest } from "src/anvil";
3
+ import { zora, zoraSepolia } from "viem/chains";
4
+ import { zoraCreator1155ImplABI } from "@zoralabs/protocol-deployments";
5
+ import { forkUrls, makeAnvilTest } from "src/anvil";
9
6
  import { createCollectorClient } from "src/sdk";
10
- import { requestErc20ApprovalForMint } from "./mint-client";
11
7
 
12
8
  const erc721ABI = parseAbi([
13
9
  "function balanceOf(address owner) public view returns (uint256)",
14
10
  ] as const);
15
11
 
16
12
  describe("mint-helper", () => {
17
- anvilTest(
13
+ makeAnvilTest({
14
+ forkBlockNumber: 16028671,
15
+ forkUrl: forkUrls.zoraMainnet,
16
+ anvilChainId: zora.id,
17
+ })(
18
18
  "mints a new 1155 token",
19
19
  async ({ viemClients }) => {
20
20
  const { testClient, walletClient, publicClient } = viemClients;
@@ -31,15 +31,24 @@ describe("mint-helper", () => {
31
31
  publicClient,
32
32
  });
33
33
 
34
- const { parameters } = await collectorClient.mint({
35
- minterAccount: creatorAccount,
36
- tokenId: targetTokenId,
34
+ const { token: mintable, prepareMint } = await collectorClient.getToken({
37
35
  tokenContract: targetContract,
38
36
  mintType: "1155",
39
- mintRecipient: creatorAccount,
37
+ tokenId: targetTokenId,
38
+ });
39
+
40
+ mintable.maxSupply;
41
+ mintable.totalMinted;
42
+ mintable.tokenURI;
43
+ mintable;
44
+
45
+ const { parameters, costs } = prepareMint({
46
+ minterAccount: creatorAccount,
40
47
  quantityToMint: 1,
41
48
  });
42
49
 
50
+ expect(costs.totalCostEth).toBe(1n * parseEther("0.000777"));
51
+
43
52
  const oldBalance = await publicClient.readContract({
44
53
  abi: zoraCreator1155ImplABI,
45
54
  address: targetContract,
@@ -85,13 +94,24 @@ describe("mint-helper", () => {
85
94
  publicClient,
86
95
  });
87
96
 
88
- const { parameters } = await collectorClient.mint({
97
+ const { prepareMint } = await collectorClient.getToken({
89
98
  tokenContract: targetContract,
99
+ mintType: "721",
100
+ });
101
+
102
+ const quantityToMint = 3n;
103
+
104
+ const { parameters, costs } = prepareMint({
90
105
  minterAccount: creatorAccount,
91
106
  mintRecipient: creatorAccount,
92
- quantityToMint: 1,
93
- mintType: "721",
107
+ quantityToMint,
94
108
  });
109
+
110
+ expect(costs.totalPurchaseCost).toBe(quantityToMint * parseEther(".08"));
111
+ expect(costs.totalCostEth).toBe(
112
+ quantityToMint * (parseEther("0.08") + parseEther("0.000777")),
113
+ );
114
+
95
115
  const oldBalance = await publicClient.readContract({
96
116
  abi: erc721ABI,
97
117
  address: targetContract,
@@ -114,7 +134,7 @@ describe("mint-helper", () => {
114
134
  });
115
135
 
116
136
  expect(oldBalance).to.be.equal(0n);
117
- expect(newBalance).to.be.equal(1n);
137
+ expect(newBalance).to.be.equal(quantityToMint);
118
138
  },
119
139
  12 * 1000,
120
140
  );
@@ -139,42 +159,47 @@ describe("mint-helper", () => {
139
159
  address: mockCollector,
140
160
  });
141
161
 
142
- const erc20Currency = "0xa6b280b42cb0b7c4a4f789ec6ccc3a7609a1bc39";
143
- const erc20PricePerToken = 1000000000000000000n;
162
+ const { prepareMint } = await minter.getToken({
163
+ mintType: "1155",
164
+ tokenContract: targetContract,
165
+ tokenId: targetTokenId,
166
+ });
167
+
168
+ const quantityToMint = 1n;
169
+
170
+ const { parameters, erc20Approval, costs } = prepareMint({
171
+ minterAccount: mockCollector,
172
+ quantityToMint,
173
+ });
174
+
175
+ expect(erc20Approval).toBeDefined();
176
+ expect(costs.totalCostEth).toBe(0n);
177
+ expect(costs.totalPurchaseCost).toBe(
178
+ quantityToMint * 1000000000000000000n,
179
+ );
180
+ expect(costs.totalPurchaseCostCurrency).toBe(
181
+ "0xa6b280b42cb0b7c4a4f789ec6ccc3a7609a1bc39",
182
+ );
144
183
 
145
184
  const beforeERC20Balance = await publicClient.readContract({
146
185
  abi: erc20Abi,
147
- address: erc20Currency,
186
+ address: erc20Approval!.erc20,
148
187
  functionName: "balanceOf",
149
188
  args: [mockCollector],
150
189
  });
151
190
 
152
- const quantityToMint = 3n;
153
-
154
- const quantityErc20 = erc20PricePerToken * BigInt(quantityToMint);
155
-
156
- const { request } = await publicClient.simulateContract(
157
- requestErc20ApprovalForMint({
158
- erc20MinterAddress:
159
- erc20MinterAddresses[chain.id as keyof typeof erc20MinterAddresses],
160
- tokenAddress: erc20Currency,
161
- account: mockCollector,
162
- quantityErc20,
163
- }),
164
- );
165
- const approveHash = await walletClient.writeContract(request);
166
- const approveTxReciept = await publicClient.waitForTransactionReceipt({
167
- hash: approveHash,
191
+ // execute the erc20 approval
192
+ const { request: erc20Request } = await publicClient.simulateContract({
193
+ abi: erc20Abi,
194
+ address: erc20Approval!.erc20,
195
+ functionName: "approve",
196
+ args: [erc20Approval!.approveTo, erc20Approval!.quantity],
197
+ account: mockCollector,
168
198
  });
169
- expect(approveTxReciept).to.not.be.null;
170
199
 
171
- const { parameters } = await minter.mint({
172
- minterAccount: mockCollector,
173
- tokenId: targetTokenId,
174
- tokenContract: targetContract,
175
- mintRecipient: mockCollector,
176
- quantityToMint,
177
- mintType: "1155",
200
+ const approveHash = await walletClient.writeContract(erc20Request);
201
+ await publicClient.waitForTransactionReceipt({
202
+ hash: approveHash,
178
203
  });
179
204
 
180
205
  const beforeCollector1155Balance = await publicClient.readContract({
@@ -185,20 +210,20 @@ describe("mint-helper", () => {
185
210
  });
186
211
  expect(beforeCollector1155Balance).to.be.equal(0n);
187
212
 
213
+ // execute the mint
188
214
  const simulationResult = await publicClient.simulateContract(parameters);
189
215
  const hash = await walletClient.writeContract(simulationResult.request);
190
- const receipt = await publicClient.waitForTransactionReceipt({ hash });
191
- expect(receipt).to.not.be.null;
216
+ await publicClient.waitForTransactionReceipt({ hash });
192
217
 
193
218
  const afterERC20Balance = await publicClient.readContract({
194
219
  abi: erc20Abi,
195
- address: erc20Currency,
220
+ address: erc20Approval!.erc20,
196
221
  functionName: "balanceOf",
197
222
  args: [mockCollector],
198
223
  });
199
224
 
200
225
  expect(beforeERC20Balance - afterERC20Balance).to.be.equal(
201
- erc20PricePerToken * quantityToMint,
226
+ erc20Approval!.quantity,
202
227
  );
203
228
 
204
229
  const afterCollector1155Balance = await publicClient.readContract({
@@ -211,4 +236,28 @@ describe("mint-helper", () => {
211
236
  },
212
237
  12 * 1000,
213
238
  );
239
+
240
+ makeAnvilTest({
241
+ forkUrl: forkUrls.zoraSepolia,
242
+ forkBlockNumber: 10294670,
243
+ anvilChainId: zoraSepolia.id,
244
+ })(
245
+ "gets onchain and premint mintables",
246
+ async ({ viemClients }) => {
247
+ const { publicClient, chain } = viemClients;
248
+
249
+ const targetContract: Address =
250
+ "0xa33e4228843092bb0f2fcbb2eb237bcefc1046b3";
251
+
252
+ const minter = createCollectorClient({ chainId: chain.id, publicClient });
253
+
254
+ const { tokens: mintables, contract } = await minter.getTokensOfContract({
255
+ tokenContract: targetContract,
256
+ });
257
+
258
+ expect(mintables.length).toBe(4);
259
+ expect(contract).toBeDefined();
260
+ },
261
+ 12 * 1000,
262
+ );
214
263
  });