@zoralabs/coins-sdk 0.2.3 → 0.2.5
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.
- package/CHANGELOG.md +13 -0
- package/dist/actions/createCoin.d.ts +11 -4
- package/dist/actions/createCoin.d.ts.map +1 -1
- package/dist/actions/updateCoinURI.d.ts +1 -1
- package/dist/actions/updateCoinURI.d.ts.map +1 -1
- package/dist/actions/updatePayoutRecipient.d.ts +1 -1
- package/dist/actions/updatePayoutRecipient.d.ts.map +1 -1
- package/dist/api/api-key.d.ts +2 -1
- package/dist/api/api-key.d.ts.map +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/internal.d.ts +2 -2
- package/dist/api/internal.d.ts.map +1 -1
- package/dist/index.cjs +301 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +320 -122
- package/dist/index.js.map +1 -1
- package/dist/metadata/cleanAndValidateMetadataURI.d.ts +1 -1
- package/dist/metadata/cleanAndValidateMetadataURI.d.ts.map +1 -1
- package/dist/metadata/index.d.ts +1 -1
- package/dist/metadata/index.d.ts.map +1 -1
- package/dist/metadata/validateMetadataURIContent.d.ts +1 -1
- package/dist/metadata/validateMetadataURIContent.d.ts.map +1 -1
- package/dist/uploader/index.d.ts +10 -0
- package/dist/uploader/index.d.ts.map +1 -0
- package/dist/uploader/metadata.d.ts +44 -0
- package/dist/uploader/metadata.d.ts.map +1 -0
- package/dist/uploader/providers/zora.d.ts +18 -0
- package/dist/uploader/providers/zora.d.ts.map +1 -0
- package/dist/uploader/types.d.ts +21 -0
- package/dist/uploader/types.d.ts.map +1 -0
- package/dist/utils/getPrepurchaseHook.d.ts +16 -0
- package/dist/utils/getPrepurchaseHook.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/actions/createCoin.ts +33 -7
- package/src/actions/updateCoinURI.ts +1 -1
- package/src/actions/updatePayoutRecipient.ts +1 -1
- package/src/api/api-key.ts +5 -1
- package/src/api/index.ts +1 -0
- package/src/api/internal.ts +3 -3
- package/src/index.ts +9 -9
- package/src/metadata/cleanAndValidateMetadataURI.ts +1 -5
- package/src/metadata/index.ts +1 -4
- package/src/metadata/validateMetadataURIContent.ts +2 -4
- package/src/uploader/index.ts +16 -0
- package/src/uploader/metadata.ts +214 -0
- package/src/uploader/providers/zora.ts +86 -0
- package/src/uploader/tests/metadata.test.ts +331 -0
- package/src/uploader/tests/providers.test.ts +131 -0
- package/src/uploader/types.ts +27 -0
- package/src/utils/getPrepurchaseHook.ts +59 -0
- package/dist/actions/tradeCoin.d.ts +0 -75
- package/dist/actions/tradeCoin.d.ts.map +0 -1
- package/src/actions/tradeCoin.ts +0 -182
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
CoinMetadataBuilder,
|
|
4
|
+
validateImageMimeType,
|
|
5
|
+
getURLFromUploadResult,
|
|
6
|
+
} from "../metadata";
|
|
7
|
+
import { Uploader, UploadResult } from "../types";
|
|
8
|
+
|
|
9
|
+
enum UploadResultType {
|
|
10
|
+
Image = "image",
|
|
11
|
+
Media = "media",
|
|
12
|
+
Metadata = "metadata",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Create a mock implementation of the Uploader interface
|
|
16
|
+
const createMockUploader = (resultList: UploadResultType[]) => {
|
|
17
|
+
// Pre-defined results to return from the upload method
|
|
18
|
+
const mockImageResult: UploadResult = {
|
|
19
|
+
url: "ipfs://image-cid",
|
|
20
|
+
size: 100,
|
|
21
|
+
mimeType: "image/png",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const mockMediaResult: UploadResult = {
|
|
25
|
+
url: "ipfs://media-cid",
|
|
26
|
+
size: 200,
|
|
27
|
+
mimeType: "video/mp4",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const mockMetadataResult: UploadResult = {
|
|
31
|
+
url: "ipfs://metadata-cid",
|
|
32
|
+
size: 300,
|
|
33
|
+
mimeType: "application/json",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const uploadedFiles: File[] = [];
|
|
37
|
+
let callCount = 0;
|
|
38
|
+
|
|
39
|
+
const mockUploader: Uploader = {
|
|
40
|
+
upload: vi.fn(async (file: File): Promise<UploadResult> => {
|
|
41
|
+
uploadedFiles.push(file);
|
|
42
|
+
callCount++;
|
|
43
|
+
|
|
44
|
+
switch (resultList[callCount - 1]) {
|
|
45
|
+
case UploadResultType.Image:
|
|
46
|
+
return mockImageResult;
|
|
47
|
+
case UploadResultType.Media:
|
|
48
|
+
return mockMediaResult;
|
|
49
|
+
case UploadResultType.Metadata:
|
|
50
|
+
return mockMetadataResult;
|
|
51
|
+
default:
|
|
52
|
+
throw new Error(`Invalid result type: ${resultList[callCount - 1]}`);
|
|
53
|
+
}
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
uploader: mockUploader,
|
|
59
|
+
uploadedFiles,
|
|
60
|
+
mockImageResult,
|
|
61
|
+
mockMediaResult,
|
|
62
|
+
mockMetadataResult,
|
|
63
|
+
reset: () => {
|
|
64
|
+
callCount = 0;
|
|
65
|
+
uploadedFiles.length = 0;
|
|
66
|
+
vi.clearAllMocks();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
describe("validateImageMimeType", () => {
|
|
72
|
+
it("should not throw for valid image types", () => {
|
|
73
|
+
const validTypes = [
|
|
74
|
+
"image/png",
|
|
75
|
+
"image/jpeg",
|
|
76
|
+
"image/jpg",
|
|
77
|
+
"image/gif",
|
|
78
|
+
"image/svg+xml",
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
validTypes.forEach((type) => {
|
|
82
|
+
expect(() => validateImageMimeType(type)).not.toThrow();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should throw for invalid image types", () => {
|
|
87
|
+
const invalidTypes = ["image/webp", "application/pdf", "text/plain"];
|
|
88
|
+
|
|
89
|
+
invalidTypes.forEach((type) => {
|
|
90
|
+
expect(() => validateImageMimeType(type)).toThrow(
|
|
91
|
+
"Image must be a PNG, JPEG, JPG, GIF or SVG",
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("getURLFromUploadResult", () => {
|
|
98
|
+
it("should convert an upload result URL to a URL object", () => {
|
|
99
|
+
const result: UploadResult = {
|
|
100
|
+
url: "ipfs://bafybeiguslukdujd22p7ix53rcszgbg4ine464g33zk2st3lnjpx4uvmri",
|
|
101
|
+
size: 100,
|
|
102
|
+
mimeType: "image/png",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const url = getURLFromUploadResult(result);
|
|
106
|
+
expect(url).toBeInstanceOf(URL);
|
|
107
|
+
expect(url.toString()).toBe(
|
|
108
|
+
"ipfs://bafybeiguslukdujd22p7ix53rcszgbg4ine464g33zk2st3lnjpx4uvmri",
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should throw for invalid URLs", () => {
|
|
113
|
+
const result: UploadResult = {
|
|
114
|
+
url: "invalid-url",
|
|
115
|
+
size: 100,
|
|
116
|
+
mimeType: "image/png",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
expect(() => getURLFromUploadResult(result)).toThrow();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("CoinMetadataBuilder", () => {
|
|
124
|
+
let mockUploaderData: ReturnType<typeof createMockUploader>;
|
|
125
|
+
let builder: CoinMetadataBuilder;
|
|
126
|
+
|
|
127
|
+
beforeEach(() => {
|
|
128
|
+
mockUploaderData = createMockUploader([
|
|
129
|
+
UploadResultType.Image,
|
|
130
|
+
UploadResultType.Media,
|
|
131
|
+
UploadResultType.Metadata,
|
|
132
|
+
]);
|
|
133
|
+
builder = new CoinMetadataBuilder();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("builder methods", () => {
|
|
137
|
+
it("should set name and return self", () => {
|
|
138
|
+
const result = builder.withName("Test Coin");
|
|
139
|
+
expect(result).toBe(builder);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should set symbol and return self", () => {
|
|
143
|
+
const result = builder.withSymbol("TEST");
|
|
144
|
+
expect(result).toBe(builder);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should set description and return self", () => {
|
|
148
|
+
const result = builder.withDescription("Test Description");
|
|
149
|
+
expect(result).toBe(builder);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should set image file and return self", () => {
|
|
153
|
+
const imageFile = new File(["test"], "test.png", { type: "image/png" });
|
|
154
|
+
const result = builder.withImage(imageFile);
|
|
155
|
+
expect(result).toBe(builder);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should throw if image URL is already set when setting image file", () => {
|
|
159
|
+
builder.withImageURI("ipfs://test");
|
|
160
|
+
const imageFile = new File(["test"], "test.png", { type: "image/png" });
|
|
161
|
+
expect(() => builder.withImage(imageFile)).toThrow(
|
|
162
|
+
"Image URL already set",
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("should throw if image file is already set when setting image URI", () => {
|
|
167
|
+
const imageFile = new File(["test"], "test.png", { type: "image/png" });
|
|
168
|
+
builder.withImage(imageFile);
|
|
169
|
+
expect(() => builder.withImageURI("ipfs://test")).toThrow(
|
|
170
|
+
"Image file already set",
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should set media file and return self", () => {
|
|
175
|
+
const mediaFile = new File(["test"], "test.mp4", { type: "video/mp4" });
|
|
176
|
+
const result = builder.withMedia(mediaFile);
|
|
177
|
+
expect(result).toBe(builder);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should throw if media URL is already set when setting media file", () => {
|
|
181
|
+
builder.withMediaURI("ipfs://test", "video/mp4");
|
|
182
|
+
const mediaFile = new File(["test"], "test.mp4", { type: "video/mp4" });
|
|
183
|
+
expect(() => builder.withMedia(mediaFile)).toThrow(
|
|
184
|
+
"Media URL already set",
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should throw if media file is already set when setting media URI", () => {
|
|
189
|
+
const mediaFile = new File(["test"], "test.mp4", { type: "video/mp4" });
|
|
190
|
+
builder.withMedia(mediaFile);
|
|
191
|
+
expect(() => builder.withMediaURI("ipfs://test", "video/mp4")).toThrow(
|
|
192
|
+
"Media file already set",
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should set properties and return self", () => {
|
|
197
|
+
const properties = { key1: "value1", key2: "value2" };
|
|
198
|
+
const result = builder.withProperties(properties);
|
|
199
|
+
expect(result).toBe(builder);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("should throw if property value is not a string", () => {
|
|
203
|
+
const properties = { key1: "value1", key2: 123 as any };
|
|
204
|
+
expect(() => builder.withProperties(properties)).toThrow(
|
|
205
|
+
"Property value must be a string",
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe("validate", () => {
|
|
211
|
+
it("should throw if name is not set", () => {
|
|
212
|
+
builder
|
|
213
|
+
.withSymbol("TEST")
|
|
214
|
+
.withImage(new File(["test"], "test.png", { type: "image/png" }));
|
|
215
|
+
expect(() => builder.validate()).toThrow("Name is required");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should throw if symbol is not set", () => {
|
|
219
|
+
builder
|
|
220
|
+
.withName("Test Coin")
|
|
221
|
+
.withImage(new File(["test"], "test.png", { type: "image/png" }));
|
|
222
|
+
expect(() => builder.validate()).toThrow("Symbol is required");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("should throw if image is not set", () => {
|
|
226
|
+
builder.withName("Test Coin").withSymbol("TEST");
|
|
227
|
+
expect(() => builder.validate()).toThrow("Image is required");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should not throw if all required fields are set", () => {
|
|
231
|
+
builder
|
|
232
|
+
.withName("Test Coin")
|
|
233
|
+
.withSymbol("TEST")
|
|
234
|
+
.withImage(new File(["test"], "test.png", { type: "image/png" }));
|
|
235
|
+
|
|
236
|
+
expect(() => builder.validate()).not.toThrow();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("generateMetadata", () => {
|
|
241
|
+
it("should generate correct metadata", () => {
|
|
242
|
+
builder
|
|
243
|
+
.withName("Test Coin")
|
|
244
|
+
.withSymbol("TEST")
|
|
245
|
+
.withDescription("Test Description")
|
|
246
|
+
.withImageURI("ipfs://image-cid")
|
|
247
|
+
.withMediaURI("ipfs://media-cid", "video/mp4")
|
|
248
|
+
.withProperties({ key1: "value1", key2: "value2" });
|
|
249
|
+
|
|
250
|
+
const metadata = builder.generateMetadata();
|
|
251
|
+
|
|
252
|
+
expect(metadata).toEqual({
|
|
253
|
+
name: "Test Coin",
|
|
254
|
+
symbol: "TEST",
|
|
255
|
+
description: "Test Description",
|
|
256
|
+
image: "ipfs://image-cid",
|
|
257
|
+
animation_url: "ipfs://media-cid",
|
|
258
|
+
content: {
|
|
259
|
+
uri: "ipfs://media-cid",
|
|
260
|
+
mime: "video/mp4",
|
|
261
|
+
},
|
|
262
|
+
properties: { key1: "value1", key2: "value2" },
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe("upload", () => {
|
|
268
|
+
it("should upload image, media and metadata", async () => {
|
|
269
|
+
const imageFile = new File(["test-image"], "test.png", {
|
|
270
|
+
type: "image/png",
|
|
271
|
+
});
|
|
272
|
+
const mediaFile = new File(["test-media"], "test.mp4", {
|
|
273
|
+
type: "video/mp4",
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
builder
|
|
277
|
+
.withName("Test Coin")
|
|
278
|
+
.withSymbol("TEST")
|
|
279
|
+
.withDescription("Test Description")
|
|
280
|
+
.withImage(imageFile)
|
|
281
|
+
.withMedia(mediaFile)
|
|
282
|
+
.withProperties({ key1: "value1", key2: "value2" });
|
|
283
|
+
|
|
284
|
+
const result = await builder.upload(mockUploaderData.uploader);
|
|
285
|
+
|
|
286
|
+
// Verify uploader was called 3 times
|
|
287
|
+
expect(mockUploaderData.uploader.upload).toHaveBeenCalledTimes(3);
|
|
288
|
+
|
|
289
|
+
// Verify files were uploaded in the right order
|
|
290
|
+
expect(mockUploaderData.uploadedFiles[0]).toBe(imageFile);
|
|
291
|
+
expect(mockUploaderData.uploadedFiles[1]).toBe(mediaFile);
|
|
292
|
+
expect(mockUploaderData.uploadedFiles[2]?.type).toBe("application/json");
|
|
293
|
+
|
|
294
|
+
// Verify result
|
|
295
|
+
expect(result.url.toString()).toBe("ipfs://metadata-cid");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("should use existing image and media URLs if provided", async () => {
|
|
299
|
+
mockUploaderData = createMockUploader([UploadResultType.Metadata]);
|
|
300
|
+
builder = new CoinMetadataBuilder();
|
|
301
|
+
|
|
302
|
+
builder
|
|
303
|
+
.withName("Test Coin")
|
|
304
|
+
.withSymbol("TEST")
|
|
305
|
+
.withDescription("Test Description")
|
|
306
|
+
.withImageURI("ipfs://existing-image")
|
|
307
|
+
.withMediaURI("ipfs://existing-media", "video/mp4")
|
|
308
|
+
.withProperties({ key1: "value1", key2: "value2" });
|
|
309
|
+
|
|
310
|
+
const result = await builder.upload(mockUploaderData.uploader);
|
|
311
|
+
|
|
312
|
+
// Should only upload metadata (1 upload)
|
|
313
|
+
expect(mockUploaderData.uploader.upload).toHaveBeenCalledTimes(1);
|
|
314
|
+
|
|
315
|
+
// The upload should be the metadata
|
|
316
|
+
expect(mockUploaderData.uploadedFiles[0]?.type).toBe("application/json");
|
|
317
|
+
|
|
318
|
+
// Verify result
|
|
319
|
+
expect(result.url.toString()).toBe("ipfs://metadata-cid");
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("should validate before uploading", async () => {
|
|
323
|
+
// Missing required fields
|
|
324
|
+
builder.withName("Test Coin");
|
|
325
|
+
|
|
326
|
+
await expect(builder.upload(mockUploaderData.uploader)).rejects.toThrow(
|
|
327
|
+
"Symbol is required",
|
|
328
|
+
);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
2
|
+
import { ZoraUploader, createZoraUploaderForCreator } from "../providers/zora";
|
|
3
|
+
import { setApiKey } from "../../api/api-key";
|
|
4
|
+
|
|
5
|
+
// Mock the fetch function
|
|
6
|
+
const mockFetch = vi.fn();
|
|
7
|
+
global.fetch = mockFetch;
|
|
8
|
+
|
|
9
|
+
// Mock console methods
|
|
10
|
+
console.log = vi.fn();
|
|
11
|
+
console.error = vi.fn();
|
|
12
|
+
|
|
13
|
+
describe("ZoraUploader", () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.clearAllMocks();
|
|
16
|
+
mockFetch.mockReset();
|
|
17
|
+
setApiKey(undefined);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("constructor", () => {
|
|
21
|
+
it("should initialize with provided API key", () => {
|
|
22
|
+
setApiKey("test-api-key");
|
|
23
|
+
const uploader = new ZoraUploader("0x123");
|
|
24
|
+
expect(uploader).toBeDefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should throw if no API key is available", () => {
|
|
28
|
+
expect(() => new ZoraUploader("0x123")).toThrow("API key is required");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("upload", () => {
|
|
33
|
+
it("should successfully upload a file with the zora uploader", async () => {
|
|
34
|
+
// Mock JWT token creation response
|
|
35
|
+
mockFetch
|
|
36
|
+
.mockResolvedValueOnce({
|
|
37
|
+
ok: true,
|
|
38
|
+
headers: new Headers({
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
"Content-Length": "1000",
|
|
41
|
+
}),
|
|
42
|
+
json: async () => ({
|
|
43
|
+
createUploadJwtFromApiKey: "test-jwt-token",
|
|
44
|
+
}),
|
|
45
|
+
})
|
|
46
|
+
.mockResolvedValueOnce({
|
|
47
|
+
ok: true,
|
|
48
|
+
headers: new Headers({
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
}),
|
|
51
|
+
json: async () => ({
|
|
52
|
+
cid: "bafybeiguslukdujd22p7ix53rcszgbg4ine464g33zk2st3lnjpx4uvmri",
|
|
53
|
+
size: 100,
|
|
54
|
+
mimeType: "image/png",
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
setApiKey("test-api-key");
|
|
59
|
+
const uploader = new ZoraUploader("0x123");
|
|
60
|
+
const file = new File(["test"], "test.png", { type: "image/png" });
|
|
61
|
+
const result = await uploader.upload(file);
|
|
62
|
+
|
|
63
|
+
// Verify fetch was called with correct parameters
|
|
64
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
65
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
66
|
+
"https://ipfs-uploader.zora.co/api/v0/add?cid-version=1",
|
|
67
|
+
expect.objectContaining({
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: expect.objectContaining({
|
|
70
|
+
Authorization: "Bearer test-jwt-token",
|
|
71
|
+
}),
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Verify result
|
|
76
|
+
expect(result).toEqual({
|
|
77
|
+
url: "ipfs://bafybeiguslukdujd22p7ix53rcszgbg4ine464g33zk2st3lnjpx4uvmri",
|
|
78
|
+
size: 100,
|
|
79
|
+
mimeType: "image/png",
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should throw an error if upload fails", async () => {
|
|
84
|
+
// Mock failed response
|
|
85
|
+
mockFetch
|
|
86
|
+
.mockResolvedValueOnce({
|
|
87
|
+
ok: true,
|
|
88
|
+
headers: new Headers({
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
"Content-Length": "1000",
|
|
91
|
+
}),
|
|
92
|
+
json: async () => ({
|
|
93
|
+
createUploadJwtFromApiKey: "test-jwt-token",
|
|
94
|
+
}),
|
|
95
|
+
})
|
|
96
|
+
.mockResolvedValueOnce({
|
|
97
|
+
ok: false,
|
|
98
|
+
statusText: "Bad Request",
|
|
99
|
+
text: async () => "Invalid file",
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
setApiKey("test-api-key");
|
|
103
|
+
const uploader = new ZoraUploader("0x123");
|
|
104
|
+
const file = new File(["test"], "test.png", { type: "image/png" });
|
|
105
|
+
|
|
106
|
+
await expect(uploader.upload(file)).rejects.toThrow(
|
|
107
|
+
"Failed to upload file",
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Verify fetch was called with correct parameters
|
|
111
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
112
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
113
|
+
"https://ipfs-uploader.zora.co/api/v0/add?cid-version=1",
|
|
114
|
+
expect.objectContaining({
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: expect.objectContaining({
|
|
117
|
+
Authorization: "Bearer test-jwt-token",
|
|
118
|
+
}),
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("factory function", () => {
|
|
125
|
+
it("should create a ZoraUploader instance", () => {
|
|
126
|
+
setApiKey("test-api-key");
|
|
127
|
+
const uploader = createZoraUploaderForCreator("0x123");
|
|
128
|
+
expect(uploader).toBeInstanceOf(ZoraUploader);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type ValidMetadataURI =
|
|
2
|
+
| `ipfs://${string}`
|
|
3
|
+
| `ar://${string}`
|
|
4
|
+
| `data:${string}`
|
|
5
|
+
| `https://${string}`;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Result from uploading a file to a storage provider
|
|
9
|
+
*/
|
|
10
|
+
export type UploadResult = {
|
|
11
|
+
url: ValidMetadataURI;
|
|
12
|
+
size: number | undefined;
|
|
13
|
+
mimeType: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Interface for file uploaders (IPFS, Arweave, etc.)
|
|
18
|
+
*/
|
|
19
|
+
export interface Uploader {
|
|
20
|
+
upload(file: File): Promise<UploadResult>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type CreateMetadataParameters = {
|
|
24
|
+
name: string;
|
|
25
|
+
symbol: string;
|
|
26
|
+
uri: `ipfs://${string}`;
|
|
27
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
encodeBuySupplyWithMultiHopSwapRouterHookCall,
|
|
3
|
+
wethAddress,
|
|
4
|
+
} from "@zoralabs/protocol-deployments";
|
|
5
|
+
import { InitialPurchaseCurrency } from "../actions/createCoin";
|
|
6
|
+
import { Address, concat, Hex, pad, toHex } from "viem";
|
|
7
|
+
import { ZORA_ADDRESS } from "./poolConfigUtils";
|
|
8
|
+
import { base } from "viem/chains";
|
|
9
|
+
|
|
10
|
+
const BASE_UDSC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
11
|
+
|
|
12
|
+
const USDC_ZORA_FEE = 3000;
|
|
13
|
+
const WETH_BASE_FEE = 3000;
|
|
14
|
+
|
|
15
|
+
const encodeFee = (fee: number) => pad(toHex(fee), { size: 3 });
|
|
16
|
+
|
|
17
|
+
export const getPrepurchaseHook = async ({
|
|
18
|
+
payoutRecipient,
|
|
19
|
+
initialPurchase,
|
|
20
|
+
chainId,
|
|
21
|
+
}: {
|
|
22
|
+
initialPurchase: {
|
|
23
|
+
currency: InitialPurchaseCurrency;
|
|
24
|
+
amount: bigint;
|
|
25
|
+
amountOutMinimum?: bigint;
|
|
26
|
+
};
|
|
27
|
+
payoutRecipient: Address;
|
|
28
|
+
chainId: number;
|
|
29
|
+
}) => {
|
|
30
|
+
if (
|
|
31
|
+
initialPurchase.currency !== InitialPurchaseCurrency.ETH &&
|
|
32
|
+
chainId !== base.id
|
|
33
|
+
) {
|
|
34
|
+
throw new Error("Initial purchase currency and/or chain not supported");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const path = concat([
|
|
38
|
+
wethAddress[base.id],
|
|
39
|
+
encodeFee(WETH_BASE_FEE),
|
|
40
|
+
BASE_UDSC_ADDRESS,
|
|
41
|
+
encodeFee(USDC_ZORA_FEE),
|
|
42
|
+
ZORA_ADDRESS,
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
return encodeBuySupplyWithMultiHopSwapRouterHookCall({
|
|
46
|
+
ethValue: initialPurchase.amount,
|
|
47
|
+
buyRecipient: payoutRecipient,
|
|
48
|
+
exactInputParams: {
|
|
49
|
+
path,
|
|
50
|
+
amountIn: initialPurchase.amount,
|
|
51
|
+
amountOutMinimum: initialPurchase.amountOutMinimum || 0n,
|
|
52
|
+
},
|
|
53
|
+
chainId: base.id,
|
|
54
|
+
}) as {
|
|
55
|
+
hook: Address;
|
|
56
|
+
hookData: Hex;
|
|
57
|
+
value: bigint;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { coinABI } from "@zoralabs/protocol-deployments";
|
|
2
|
-
import { Address, TransactionReceipt, WalletClient, SimulateContractParameters, ContractEventArgsFromTopics } from "viem";
|
|
3
|
-
import { GenericPublicClient } from "src/utils/genericPublicClient";
|
|
4
|
-
export type SellEventArgs = ContractEventArgsFromTopics<typeof coinABI, "CoinSell">;
|
|
5
|
-
export type BuyEventArgs = ContractEventArgsFromTopics<typeof coinABI, "CoinBuy">;
|
|
6
|
-
export type TradeEventArgs = SellEventArgs | BuyEventArgs;
|
|
7
|
-
/**
|
|
8
|
-
* Simulates a buy order to get the expected output amount
|
|
9
|
-
* @param {Object} params - The simulation parameters
|
|
10
|
-
* @param {Address} params.target - The target coin contract address
|
|
11
|
-
* @param {bigint} params.requestedOrderSize - The desired input amount for the buy
|
|
12
|
-
* @param {PublicClient} params.publicClient - The viem public client instance
|
|
13
|
-
* @returns {Promise<{orderSize: bigint, amountOut: bigint}>} The simulated order size and output amount
|
|
14
|
-
*/
|
|
15
|
-
export declare function simulateBuy({ target, requestedOrderSize, publicClient, }: {
|
|
16
|
-
target: Address;
|
|
17
|
-
requestedOrderSize: bigint;
|
|
18
|
-
publicClient: GenericPublicClient;
|
|
19
|
-
}): Promise<{
|
|
20
|
-
orderSize: bigint;
|
|
21
|
-
amountOut: bigint;
|
|
22
|
-
}>;
|
|
23
|
-
/**
|
|
24
|
-
* Parameters for creating a trade call
|
|
25
|
-
* @typedef {Object} TradeParams
|
|
26
|
-
* @property {'sell' | 'buy'} direction - The trade direction
|
|
27
|
-
* @property {Address} target - The target coin contract address
|
|
28
|
-
* @property {Object} args - The trade arguments
|
|
29
|
-
* @property {Address} args.recipient - The recipient of the trade output
|
|
30
|
-
* @property {bigint} args.orderSize - The size of the order
|
|
31
|
-
* @property {bigint} [args.minAmountOut] - The minimum amount to receive
|
|
32
|
-
* @property {bigint} [args.sqrtPriceLimitX96] - The price limit for the trade
|
|
33
|
-
* @property {Address} [args.tradeReferrer] - The referrer address for the trade
|
|
34
|
-
*/
|
|
35
|
-
export type TradeParams = {
|
|
36
|
-
direction: "sell" | "buy";
|
|
37
|
-
target: Address;
|
|
38
|
-
args: {
|
|
39
|
-
recipient: Address;
|
|
40
|
-
orderSize: bigint;
|
|
41
|
-
minAmountOut?: bigint;
|
|
42
|
-
sqrtPriceLimitX96?: bigint;
|
|
43
|
-
tradeReferrer?: Address;
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* Creates a trade call parameters object for buy or sell
|
|
48
|
-
* @param {TradeParams} params - The trade parameters
|
|
49
|
-
* @returns {SimulateContractParameters} The contract call parameters
|
|
50
|
-
*/
|
|
51
|
-
export declare function tradeCoinCall({ target, direction, args: { recipient, orderSize, minAmountOut, sqrtPriceLimitX96, tradeReferrer, }, }: TradeParams): SimulateContractParameters;
|
|
52
|
-
/**
|
|
53
|
-
* Gets the trade event from transaction receipt logs
|
|
54
|
-
* @param {TransactionReceipt} receipt - The transaction receipt containing the logs
|
|
55
|
-
* @param {'buy' | 'sell'} direction - The direction of the trade
|
|
56
|
-
* @returns {TradeEventArgs | undefined} The decoded trade event args if found
|
|
57
|
-
*/
|
|
58
|
-
export declare function getTradeFromLogs(receipt: TransactionReceipt, direction: "buy" | "sell"): TradeEventArgs | undefined;
|
|
59
|
-
/**
|
|
60
|
-
* Executes a trade transaction
|
|
61
|
-
* @param {TradeParams} params - The trade parameters
|
|
62
|
-
* @param {PublicClient} publicClient - The viem public client instance
|
|
63
|
-
* @param {WalletClient} walletClient - The viem wallet client instance
|
|
64
|
-
* @returns {Promise<{
|
|
65
|
-
* hash: `0x${string}`,
|
|
66
|
-
* receipt: TransactionReceipt,
|
|
67
|
-
* trade: TradeEventArgs | undefined
|
|
68
|
-
* }>} The transaction result with trade details
|
|
69
|
-
*/
|
|
70
|
-
export declare function tradeCoin(params: TradeParams, walletClient: WalletClient, publicClient: GenericPublicClient): Promise<{
|
|
71
|
-
hash: `0x${string}`;
|
|
72
|
-
receipt: any;
|
|
73
|
-
trade: TradeEventArgs | undefined;
|
|
74
|
-
}>;
|
|
75
|
-
//# sourceMappingURL=tradeCoin.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tradeCoin.d.ts","sourceRoot":"","sources":["../../src/actions/tradeCoin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAEzD,OAAO,EACL,OAAO,EACP,kBAAkB,EAClB,YAAY,EACZ,0BAA0B,EAG1B,2BAA2B,EAE5B,MAAM,MAAM,CAAC;AAEd,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAIpE,MAAM,MAAM,aAAa,GAAG,2BAA2B,CACrD,OAAO,OAAO,EACd,UAAU,CACX,CAAC;AACF,MAAM,MAAM,YAAY,GAAG,2BAA2B,CACpD,OAAO,OAAO,EACd,SAAS,CACV,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,YAAY,CAAC;AAO1D;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,EAChC,MAAM,EACN,kBAAkB,EAClB,YAAY,GACb,EAAE;IACD,MAAM,EAAE,OAAO,CAAC;IAChB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,mBAAmB,CAAC;CACnC,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAwBpD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE;QACJ,SAAS,EAAE,OAAO,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;KACzB,CAAC;CACH,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,SAAS,EACT,IAAI,EAAE,EACJ,SAAS,EACT,SAAS,EACT,YAAiB,EACjB,iBAAsB,EACtB,aAA2B,GAC5B,GACF,EAAE,WAAW,GAAG,0BAA0B,CAc1C;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,kBAAkB,EAC3B,SAAS,EAAE,KAAK,GAAG,MAAM,GACxB,cAAc,GAAG,SAAS,CAU5B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,WAAW,EACnB,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,mBAAmB;;;;GAgBlC"}
|