@silvana-one/abi 0.1.1 → 0.1.3

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 (66) hide show
  1. package/dist/node/{token/contracts.d.ts → compile.d.ts} +1 -2
  2. package/dist/node/compile.js +2 -0
  3. package/dist/node/compile.js.map +1 -0
  4. package/dist/node/contracts.d.ts +1 -16
  5. package/dist/node/contracts.js +2 -2
  6. package/dist/node/contracts.js.map +1 -1
  7. package/dist/node/index.cjs +534 -152
  8. package/dist/node/index.d.ts +2 -0
  9. package/dist/node/index.js +2 -0
  10. package/dist/node/index.js.map +1 -1
  11. package/dist/node/nft/build.d.ts +52 -0
  12. package/dist/node/nft/build.js +726 -0
  13. package/dist/node/nft/build.js.map +1 -0
  14. package/dist/node/nft/index.d.ts +1 -0
  15. package/dist/node/nft/index.js +2 -0
  16. package/dist/node/nft/index.js.map +1 -0
  17. package/dist/node/token/index.d.ts +0 -1
  18. package/dist/node/token/index.js +0 -1
  19. package/dist/node/token/index.js.map +1 -1
  20. package/dist/node/token/info.js +4 -2
  21. package/dist/node/token/info.js.map +1 -1
  22. package/dist/node/vk/devnet.js +83 -83
  23. package/dist/node/vk/devnet.js.map +1 -1
  24. package/dist/node/vk/mainnet.js +83 -83
  25. package/dist/node/vk/mainnet.js.map +1 -1
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/dist/tsconfig.web.tsbuildinfo +1 -1
  28. package/dist/web/{token/contracts.d.ts → compile.d.ts} +1 -2
  29. package/dist/web/compile.js +2 -0
  30. package/dist/web/compile.js.map +1 -0
  31. package/dist/web/contracts.d.ts +1 -16
  32. package/dist/web/contracts.js +2 -2
  33. package/dist/web/contracts.js.map +1 -1
  34. package/dist/web/index.d.ts +2 -0
  35. package/dist/web/index.js +2 -0
  36. package/dist/web/index.js.map +1 -1
  37. package/dist/web/nft/build.d.ts +52 -0
  38. package/dist/web/nft/build.js +726 -0
  39. package/dist/web/nft/build.js.map +1 -0
  40. package/dist/web/nft/index.d.ts +1 -0
  41. package/dist/web/nft/index.js +2 -0
  42. package/dist/web/nft/index.js.map +1 -0
  43. package/dist/web/token/index.d.ts +0 -1
  44. package/dist/web/token/index.js +0 -1
  45. package/dist/web/token/index.js.map +1 -1
  46. package/dist/web/token/info.js +4 -2
  47. package/dist/web/token/info.js.map +1 -1
  48. package/dist/web/vk/devnet.js +83 -83
  49. package/dist/web/vk/devnet.js.map +1 -1
  50. package/dist/web/vk/mainnet.js +83 -83
  51. package/dist/web/vk/mainnet.js.map +1 -1
  52. package/package.json +10 -9
  53. package/src/compile.ts +17 -0
  54. package/src/contracts.ts +3 -19
  55. package/src/index.ts +2 -0
  56. package/src/nft/build.ts +929 -0
  57. package/src/nft/index.ts +1 -0
  58. package/src/token/index.ts +0 -1
  59. package/src/token/info.ts +4 -2
  60. package/src/vk/devnet.ts +85 -84
  61. package/src/vk/mainnet.ts +85 -84
  62. package/dist/node/token/contracts.js +0 -13
  63. package/dist/node/token/contracts.js.map +0 -1
  64. package/dist/web/token/contracts.js +0 -13
  65. package/dist/web/token/contracts.js.map +0 -1
  66. package/src/token/contracts.ts +0 -40
@@ -0,0 +1,929 @@
1
+ import {
2
+ Whitelist,
3
+ WhitelistedAddress,
4
+ Storage,
5
+ pinJSON,
6
+ serializeIndexedMap,
7
+ IndexedMapSerialized,
8
+ } from "@silvana-one/storage";
9
+ import {
10
+ NftTransactionType,
11
+ NftTransactionParams,
12
+ LaunchNftCollectionStandardAdminParams,
13
+ LaunchNftCollectionAdvancedAdminParams,
14
+ NftMintTransactionParams,
15
+ } from "@silvana-one/api";
16
+ import { blockchain } from "../types.js";
17
+ import { fetchMinaAccount } from "../fetch.js";
18
+ import {
19
+ Collection,
20
+ Admin,
21
+ NFT,
22
+ AdvancedCollection,
23
+ NFTAdmin,
24
+ NFTAdvancedAdmin,
25
+ Metadata,
26
+ pinMetadata,
27
+ fieldFromString,
28
+ CollectionData,
29
+ MintParams,
30
+ NFTData,
31
+ } from "@silvana-one/nft";
32
+ import { tokenVerificationKeys } from "../vk/index.js";
33
+ import {
34
+ PublicKey,
35
+ Mina,
36
+ AccountUpdate,
37
+ UInt64,
38
+ UInt8,
39
+ Bool,
40
+ Transaction,
41
+ Struct,
42
+ Field,
43
+ TokenId,
44
+ UInt32,
45
+ fetchLastBlock,
46
+ } from "o1js";
47
+
48
+ export type NftAdminType = "standard" | "advanced" | "unknown";
49
+
50
+ export async function buildNftCollectionLaunchTransaction(params: {
51
+ chain: blockchain;
52
+ args:
53
+ | LaunchNftCollectionStandardAdminParams
54
+ | LaunchNftCollectionAdvancedAdminParams;
55
+ developerAddress?: string;
56
+ provingKey?: string;
57
+ provingFee?: number;
58
+ }): Promise<{
59
+ request:
60
+ | LaunchNftCollectionStandardAdminParams
61
+ | LaunchNftCollectionAdvancedAdminParams;
62
+ tx: Transaction<false, false>;
63
+ adminType: NftAdminType;
64
+ name: string;
65
+ verificationKeyHashes: string[];
66
+ metadataRoot: string;
67
+ storage: string;
68
+ privateMetadata?: string;
69
+ map?: IndexedMapSerialized;
70
+ }> {
71
+ const { chain, args } = params;
72
+ const {
73
+ url = chain === "mainnet"
74
+ ? "https://minanft.io"
75
+ : `https://${chain}.minanft.io`,
76
+ symbol = "NFT",
77
+ memo,
78
+ nonce,
79
+ adminContract: adminType,
80
+ masterNFT,
81
+ } = args;
82
+
83
+ if (memo && typeof memo !== "string")
84
+ throw new Error("Memo must be a string");
85
+ if (memo && memo.length > 30)
86
+ throw new Error("Memo must be less than 30 characters");
87
+ if (!symbol || typeof symbol !== "string")
88
+ throw new Error("Symbol must be a string");
89
+ if (symbol.length >= 7)
90
+ throw new Error("Symbol must be less than 7 characters");
91
+ if (masterNFT === undefined) {
92
+ throw new Error("masterNFT is required");
93
+ }
94
+ if (masterNFT.metadata === undefined) {
95
+ throw new Error("masterNFT.metadata is required");
96
+ }
97
+ if (
98
+ typeof masterNFT.metadata === "string" &&
99
+ masterNFT.storage === undefined
100
+ ) {
101
+ throw new Error("masterNFT.storage is required if metadata is a string");
102
+ }
103
+ const sender = PublicKey.fromBase58(args.sender);
104
+ if (nonce === undefined) throw new Error("Nonce is required");
105
+ if (typeof nonce !== "number") throw new Error("Nonce must be a number");
106
+ const fee = 100_000_000;
107
+ if (url && typeof url !== "string") throw new Error("Url must be a string");
108
+ if (!args.collectionAddress || typeof args.collectionAddress !== "string")
109
+ throw new Error("collectionAddress is required");
110
+ if (
111
+ !args.adminContractAddress ||
112
+ typeof args.adminContractAddress !== "string"
113
+ )
114
+ throw new Error("adminContractAddress is required");
115
+
116
+ const adminContract = adminType === "advanced" ? NFTAdvancedAdmin : NFTAdmin;
117
+ const collectionContract =
118
+ adminType === "advanced" ? AdvancedCollection : Collection;
119
+ const vk =
120
+ tokenVerificationKeys[chain === "mainnet" ? "mainnet" : "devnet"].vk;
121
+ if (
122
+ !vk ||
123
+ !vk.Collection ||
124
+ !vk.Collection.hash ||
125
+ !vk.Collection.data ||
126
+ !vk.AdvancedCollection ||
127
+ !vk.AdvancedCollection.hash ||
128
+ !vk.AdvancedCollection.data ||
129
+ !vk.NFT ||
130
+ !vk.NFT.hash ||
131
+ !vk.NFT.data ||
132
+ !vk.NFTAdmin ||
133
+ !vk.NFTAdmin.hash ||
134
+ !vk.NFTAdmin.data ||
135
+ !vk.NFTAdvancedAdmin ||
136
+ !vk.NFTAdvancedAdmin.hash ||
137
+ !vk.NFTAdvancedAdmin.data
138
+ )
139
+ throw new Error("Cannot get verification key from vk");
140
+
141
+ const adminVerificationKey =
142
+ adminType === "advanced" ? vk.NFTAdvancedAdmin : vk.NFTAdmin;
143
+ const collectionVerificationKey =
144
+ adminType === "advanced" ? vk.AdvancedCollection : vk.Collection;
145
+
146
+ if (!adminVerificationKey || !collectionVerificationKey)
147
+ throw new Error("Cannot get verification keys");
148
+ await fetchMinaAccount({
149
+ publicKey: sender,
150
+ force: true,
151
+ });
152
+
153
+ if (!Mina.hasAccount(sender)) {
154
+ throw new Error("Sender does not have account");
155
+ }
156
+
157
+ const whitelist =
158
+ "whitelist" in args && args.whitelist
159
+ ? typeof args.whitelist === "string"
160
+ ? Whitelist.fromString(args.whitelist)
161
+ : (await Whitelist.create({ list: args.whitelist, name: symbol }))
162
+ .whitelist
163
+ : Whitelist.empty();
164
+
165
+ const collectionAddress = PublicKey.fromBase58(args.collectionAddress);
166
+ const adminContractAddress = PublicKey.fromBase58(args.adminContractAddress);
167
+ const creator = args.creator ? PublicKey.fromBase58(args.creator) : sender;
168
+ const zkCollection = new collectionContract(collectionAddress);
169
+ const zkAdmin = new adminContract(adminContractAddress);
170
+ const metadataVerificationKeyHash = masterNFT.metadataVerificationKeyHash
171
+ ? Field.fromJSON(masterNFT.metadataVerificationKeyHash)
172
+ : Field(0);
173
+
174
+ const provingKey = params.provingKey
175
+ ? PublicKey.fromBase58(params.provingKey)
176
+ : sender;
177
+ const provingFee = params.provingFee
178
+ ? UInt64.from(Math.round(params.provingFee))
179
+ : undefined;
180
+ const developerFee = args.developerFee
181
+ ? UInt64.from(Math.round(args.developerFee))
182
+ : undefined;
183
+ const developerAddress = params.developerAddress
184
+ ? PublicKey.fromBase58(params.developerAddress)
185
+ : undefined;
186
+
187
+ const { name, ipfsHash, metadataRoot, privateMetadata, serializedMap } =
188
+ typeof masterNFT.metadata === "string"
189
+ ? {
190
+ name: masterNFT.name,
191
+ ipfsHash: masterNFT.storage,
192
+ metadataRoot: Field.fromJSON(masterNFT.metadata),
193
+ privateMetadata: undefined,
194
+ serializedMap: undefined,
195
+ }
196
+ : await pinMetadata(
197
+ Metadata.fromOpenApiJSON({ json: masterNFT.metadata })
198
+ );
199
+
200
+ if (ipfsHash === undefined)
201
+ throw new Error("storage is required, but not provided");
202
+ if (metadataRoot === undefined) throw new Error("metadataRoot is required");
203
+
204
+ const slot =
205
+ chain === "local"
206
+ ? Mina.currentSlot()
207
+ : chain === "zeko"
208
+ ? UInt32.zero
209
+ : (await fetchLastBlock()).globalSlotSinceGenesis;
210
+ const expiry = slot.add(
211
+ UInt32.from(chain === "mainnet" || chain === "devnet" ? 20 : 100000)
212
+ );
213
+
214
+ const nftData = NFTData.new({
215
+ owner: creator,
216
+ approved: undefined,
217
+ version: undefined,
218
+ id: masterNFT.data.id ? BigInt(masterNFT.data.id) : undefined,
219
+ canChangeOwnerByProof: masterNFT.data.canChangeOwnerByProof,
220
+ canTransfer: masterNFT.data.canTransfer,
221
+ canApprove: masterNFT.data.canApprove,
222
+ canChangeMetadata: masterNFT.data.canChangeMetadata,
223
+ canChangeStorage: masterNFT.data.canChangeStorage,
224
+ canChangeName: masterNFT.data.canChangeName,
225
+ canChangeMetadataVerificationKeyHash:
226
+ masterNFT.data.canChangeMetadataVerificationKeyHash,
227
+ canPause: masterNFT.data.canPause,
228
+ isPaused: masterNFT.data.isPaused,
229
+ requireOwnerAuthorizationToUpgrade:
230
+ masterNFT.data.requireOwnerAuthorizationToUpgrade,
231
+ });
232
+
233
+ const mintParams = new MintParams({
234
+ name: fieldFromString(name),
235
+ address: collectionAddress,
236
+ tokenId: TokenId.derive(collectionAddress),
237
+ data: nftData,
238
+ fee: args.masterNFT.fee
239
+ ? UInt64.from(Math.round(args.masterNFT.fee * 1_000_000_000))
240
+ : UInt64.zero,
241
+ metadata: metadataRoot,
242
+ storage: Storage.fromString(ipfsHash),
243
+ metadataVerificationKeyHash,
244
+ expiry: masterNFT.expiry
245
+ ? UInt32.from(Math.round(masterNFT.expiry))
246
+ : expiry,
247
+ });
248
+
249
+ const collectionData = CollectionData.new(args.collectionData ?? {});
250
+
251
+ const tx = await Mina.transaction(
252
+ {
253
+ sender,
254
+ fee,
255
+ memo:
256
+ memo ?? `${args.collectionName} NFT collection launch`.substring(0, 30),
257
+ nonce,
258
+ },
259
+ async () => {
260
+ const feeAccountUpdate = AccountUpdate.createSigned(sender);
261
+ feeAccountUpdate.balance.subInPlace(3_000_000_000);
262
+
263
+ if (provingFee && provingKey)
264
+ feeAccountUpdate.send({
265
+ to: provingKey,
266
+ amount: provingFee,
267
+ });
268
+ if (developerAddress && developerFee) {
269
+ feeAccountUpdate.send({
270
+ to: developerAddress,
271
+ amount: developerFee,
272
+ });
273
+ }
274
+
275
+ if (zkAdmin instanceof NFTAdvancedAdmin) {
276
+ throw new Error("Advanced admin is not supported");
277
+ } else if (zkAdmin instanceof NFTAdmin) {
278
+ await zkAdmin.deploy({
279
+ admin: sender,
280
+ uri: `NFT Admin`,
281
+ verificationKey: adminVerificationKey,
282
+ });
283
+ // deploy() and initialize() create 2 account updates for the same publicKey, it is intended
284
+ await zkCollection.deploy({
285
+ creator,
286
+ collectionName: fieldFromString(args.collectionName),
287
+ baseURL: fieldFromString(args.baseURL ?? "ipfs"),
288
+ admin: adminContractAddress,
289
+ symbol,
290
+ url,
291
+ verificationKey: collectionVerificationKey,
292
+ });
293
+ await zkCollection.initialize(mintParams, collectionData);
294
+ }
295
+ }
296
+ );
297
+ return {
298
+ request:
299
+ adminType === "advanced"
300
+ ? {
301
+ ...args,
302
+ whitelist: whitelist.toString(),
303
+ }
304
+ : args,
305
+ tx,
306
+ adminType,
307
+ name: args.collectionName,
308
+ verificationKeyHashes: [
309
+ adminVerificationKey.hash,
310
+ collectionVerificationKey.hash,
311
+ ],
312
+ metadataRoot: metadataRoot.toJSON(),
313
+ storage: ipfsHash,
314
+ privateMetadata,
315
+ map: serializedMap,
316
+ };
317
+ }
318
+
319
+ export async function buildNftTransaction(params: {
320
+ chain: blockchain;
321
+ args: Exclude<
322
+ NftTransactionParams,
323
+ | LaunchNftCollectionStandardAdminParams
324
+ | LaunchNftCollectionAdvancedAdminParams
325
+ >;
326
+ developerAddress?: string;
327
+ provingKey?: string;
328
+ provingFee?: number;
329
+ }): Promise<{
330
+ request: Exclude<
331
+ NftTransactionParams,
332
+ | LaunchNftCollectionStandardAdminParams
333
+ | LaunchNftCollectionAdvancedAdminParams
334
+ >;
335
+ tx: Transaction<false, false>;
336
+ adminType: NftAdminType;
337
+ adminContractAddress: PublicKey;
338
+ symbol: string;
339
+ name: string;
340
+ verificationKeyHashes: string[];
341
+ metadataRoot: string;
342
+ storage: string;
343
+ privateMetadata?: string;
344
+ map?: IndexedMapSerialized;
345
+ }> {
346
+ const { chain, args } = params;
347
+ const { nonce, txType } = args;
348
+ if (nonce === undefined) throw new Error("Nonce is required");
349
+ if (typeof nonce !== "number") throw new Error("Nonce must be a number");
350
+ if (txType === undefined) throw new Error("Tx type is required");
351
+ if (typeof txType !== "string") throw new Error("Tx type must be a string");
352
+ const sender = PublicKey.fromBase58(args.sender);
353
+ const collectionAddress = PublicKey.fromBase58(args.collectionAddress);
354
+ if (args.nftMintParams.address === undefined) {
355
+ throw new Error("NFT address is required");
356
+ }
357
+ const nftAddress = PublicKey.fromBase58(args.nftMintParams.address);
358
+
359
+ // const offerAddress =
360
+ // "offerAddress" in args && args.offerAddress
361
+ // ? PublicKey.fromBase58(args.offerAddress)
362
+ // : undefined;
363
+ // if (
364
+ // !offerAddress &&
365
+ // (txType === "token:offer:create" ||
366
+ // txType === "token:offer:buy" ||
367
+ // txType === "token:offer:withdraw")
368
+ // )
369
+ // throw new Error("Offer address is required");
370
+
371
+ // const bidAddress =
372
+ // "bidAddress" in args && args.bidAddress
373
+ // ? PublicKey.fromBase58(args.bidAddress)
374
+ // : undefined;
375
+ // if (
376
+ // !bidAddress &&
377
+ // (txType === "token:bid:create" ||
378
+ // txType === "token:bid:sell" ||
379
+ // txType === "token:bid:withdraw")
380
+ // )
381
+ // throw new Error("Bid address is required");
382
+
383
+ // const to =
384
+ // "to" in args && args.to ? PublicKey.fromBase58(args.to) : undefined;
385
+ // if (
386
+ // !to &&
387
+ // (txType === "token:transfer" ||
388
+ // txType === "token:airdrop" ||
389
+ // txType === "token:mint")
390
+ // )
391
+ // throw new Error("To address is required");
392
+
393
+ // const from =
394
+ // "from" in args && args.from ? PublicKey.fromBase58(args.from) : undefined;
395
+ // if (!from && txType === "token:burn")
396
+ // throw new Error("From address is required for token:burn");
397
+
398
+ // const amount =
399
+ // "amount" in args ? UInt64.from(Math.round(args.amount)) : undefined;
400
+ // const price =
401
+ // "price" in args && args.price
402
+ // ? UInt64.from(Math.round(args.price))
403
+ // : undefined;
404
+ // const slippage = UInt32.from(
405
+ // Math.round(
406
+ // "slippage" in args && args.slippage !== undefined ? args.slippage : 50
407
+ // )
408
+ // );
409
+
410
+ await fetchMinaAccount({
411
+ publicKey: sender,
412
+ force: true,
413
+ });
414
+
415
+ if (!Mina.hasAccount(sender)) {
416
+ throw new Error("Sender does not have account");
417
+ }
418
+
419
+ const { symbol, adminContractAddress, adminType, verificationKeyHashes } =
420
+ await getNftSymbolAndAdmin({
421
+ txType,
422
+ collectionAddress,
423
+ chain,
424
+ nftAddress: undefined, // TODO: add nft address
425
+ });
426
+ const memo = args.memo ?? `${txType} ${symbol} ${args.nftMintParams.name}`;
427
+ const fee = 100_000_000;
428
+ const provingKey = params.provingKey
429
+ ? PublicKey.fromBase58(params.provingKey)
430
+ : sender;
431
+ const provingFee = params.provingFee
432
+ ? UInt64.from(Math.round(params.provingFee))
433
+ : undefined;
434
+ const developerFee = args.developerFee
435
+ ? UInt64.from(Math.round(args.developerFee))
436
+ : undefined;
437
+ const developerAddress = params.developerAddress
438
+ ? PublicKey.fromBase58(params.developerAddress)
439
+ : undefined;
440
+
441
+ //const adminContract = new FungibleTokenAdmin(adminContractAddress);
442
+ const advancedAdminContract = new NFTAdvancedAdmin(adminContractAddress);
443
+ const adminContract = new NFTAdmin(adminContractAddress);
444
+ const collectionContract =
445
+ adminType === "advanced" ? AdvancedCollection : Collection;
446
+
447
+ // if (
448
+ // (txType === "token:admin:whitelist" ||
449
+ // txType === "token:bid:whitelist" ||
450
+ // txType === "token:offer:whitelist") &&
451
+ // !args.whitelist
452
+ // ) {
453
+ // throw new Error("Whitelist is required");
454
+ // }
455
+
456
+ // const whitelist =
457
+ // "whitelist" in args && args.whitelist
458
+ // ? typeof args.whitelist === "string"
459
+ // ? Whitelist.fromString(args.whitelist)
460
+ // : (await Whitelist.create({ list: args.whitelist, name: symbol }))
461
+ // .whitelist
462
+ // : Whitelist.empty();
463
+
464
+ const zkCollection = new collectionContract(collectionAddress);
465
+ const tokenId = zkCollection.deriveTokenId();
466
+
467
+ // if (
468
+ // txType === "nft:mint" &&
469
+ // adminType === "standard" &&
470
+ // adminAddress.toBase58() !== sender.toBase58()
471
+ // )
472
+ // throw new Error(
473
+ // "Invalid sender for FungibleToken mint with standard admin"
474
+ // );
475
+
476
+ await fetchMinaAccount({
477
+ publicKey: nftAddress,
478
+ tokenId,
479
+ force: (
480
+ ["nft:transfer"] satisfies NftTransactionType[] as NftTransactionType[]
481
+ ).includes(txType),
482
+ });
483
+
484
+ // if (to) {
485
+ // await fetchMinaAccount({
486
+ // publicKey: to,
487
+ // tokenId,
488
+ // force: false,
489
+ // });
490
+ // }
491
+
492
+ // if (from) {
493
+ // await fetchMinaAccount({
494
+ // publicKey: from,
495
+ // tokenId,
496
+ // force: false,
497
+ // });
498
+ // }
499
+
500
+ // if (offerAddress)
501
+ // await fetchMinaAccount({
502
+ // publicKey: offerAddress,
503
+ // tokenId,
504
+ // force: (
505
+ // [
506
+ // "token:offer:whitelist",
507
+ // "token:offer:buy",
508
+ // "token:offer:withdraw",
509
+ // ] satisfies TokenTransactionType[] as TokenTransactionType[]
510
+ // ).includes(txType),
511
+ // });
512
+ // if (bidAddress)
513
+ // await fetchMinaAccount({
514
+ // publicKey: bidAddress,
515
+ // force: (
516
+ // [
517
+ // "token:bid:whitelist",
518
+ // "token:bid:sell",
519
+ // "token:bid:withdraw",
520
+ // ] satisfies TokenTransactionType[] as TokenTransactionType[]
521
+ // ).includes(txType),
522
+ // });
523
+
524
+ // const offerContract = offerAddress
525
+ // ? new FungibleTokenOfferContract(offerAddress, tokenId)
526
+ // : undefined;
527
+
528
+ // const bidContract = bidAddress
529
+ // ? new FungibleTokenBidContract(bidAddress)
530
+ // : undefined;
531
+ // const offerContractDeployment = offerAddress
532
+ // ? new FungibleTokenOfferContract(offerAddress, tokenId)
533
+ // : undefined;
534
+ // const bidContractDeployment = bidAddress
535
+ // ? new FungibleTokenBidContract(bidAddress)
536
+ // : undefined;
537
+ const vk =
538
+ tokenVerificationKeys[chain === "mainnet" ? "mainnet" : "devnet"].vk;
539
+ if (
540
+ !vk ||
541
+ !vk.Collection ||
542
+ !vk.Collection.hash ||
543
+ !vk.Collection.data ||
544
+ !vk.AdvancedCollection ||
545
+ !vk.AdvancedCollection.hash ||
546
+ !vk.AdvancedCollection.data ||
547
+ !vk.NFT ||
548
+ !vk.NFT.hash ||
549
+ !vk.NFT.data ||
550
+ !vk.NFTAdmin ||
551
+ !vk.NFTAdmin.hash ||
552
+ !vk.NFTAdmin.data ||
553
+ !vk.NFTAdvancedAdmin ||
554
+ !vk.NFTAdvancedAdmin.hash ||
555
+ !vk.NFTAdvancedAdmin.data
556
+ )
557
+ throw new Error("Cannot get verification key from vk");
558
+
559
+ // const offerVerificationKey = FungibleTokenOfferContract._verificationKey ?? {
560
+ // hash: Field(vk.FungibleTokenOfferContract.hash),
561
+ // data: vk.FungibleTokenOfferContract.data,
562
+ // };
563
+ // const bidVerificationKey = FungibleTokenBidContract._verificationKey ?? {
564
+ // hash: Field(vk.FungibleTokenBidContract.hash),
565
+ // data: vk.FungibleTokenBidContract.data,
566
+ // };
567
+
568
+ // const isNewBidOfferAccount =
569
+ // txType === "token:offer:create" && offerAddress
570
+ // ? !Mina.hasAccount(offerAddress, tokenId)
571
+ // : txType === "token:bid:create" && bidAddress
572
+ // ? !Mina.hasAccount(bidAddress)
573
+ // : false;
574
+
575
+ // const isNewBuyAccount =
576
+ // txType === "token:offer:buy" ? !Mina.hasAccount(sender, tokenId) : false;
577
+ // let isNewSellAccount: boolean = false;
578
+ // if (txType === "token:bid:sell") {
579
+ // if (!bidAddress || !bidContract) throw new Error("Bid address is required");
580
+ // await fetchMinaAccount({
581
+ // publicKey: bidAddress,
582
+ // force: true,
583
+ // });
584
+ // const buyer = bidContract.buyer.get();
585
+ // await fetchMinaAccount({
586
+ // publicKey: buyer,
587
+ // tokenId,
588
+ // force: false,
589
+ // });
590
+ // isNewSellAccount = !Mina.hasAccount(buyer, tokenId);
591
+ // }
592
+
593
+ // if (txType === "token:burn") {
594
+ // await fetchMinaAccount({
595
+ // publicKey: sender,
596
+ // force: true,
597
+ // });
598
+ // await fetchMinaAccount({
599
+ // publicKey: sender,
600
+ // tokenId,
601
+ // force: false,
602
+ // });
603
+ // if (!Mina.hasAccount(sender, tokenId))
604
+ // throw new Error("Sender does not have tokens to burn");
605
+ // }
606
+
607
+ // const isNewTransferMintAccount =
608
+ // (txType === "token:transfer" ||
609
+ // txType === "token:airdrop" ||
610
+ // txType === "token:mint") &&
611
+ // to
612
+ // ? !Mina.hasAccount(to, tokenId)
613
+ // : false;
614
+
615
+ // const accountCreationFee =
616
+ // (isNewBidOfferAccount ? 1_000_000_000 : 0) +
617
+ // (isNewBuyAccount ? 1_000_000_000 : 0) +
618
+ // (isNewSellAccount ? 1_000_000_000 : 0) +
619
+ // (isNewTransferMintAccount ? 1_000_000_000 : 0) +
620
+ // (isToNewAccount &&
621
+ // txType === "token:mint" &&
622
+ // adminType === "advanced" &&
623
+ // advancedAdminContract.whitelist.get().isSome().toBoolean()
624
+ // ? 1_000_000_000
625
+ // : 0);
626
+ // console.log("accountCreationFee", accountCreationFee / 1_000_000_000);
627
+
628
+ // switch (txType) {
629
+ // case "token:offer:buy":
630
+ // case "token:offer:withdraw":
631
+ // case "token:offer:whitelist":
632
+ // if (offerContract === undefined)
633
+ // throw new Error("Offer contract is required");
634
+ // if (
635
+ // Mina.getAccount(
636
+ // offerContract.address,
637
+ // tokenId
638
+ // ).zkapp?.verificationKey?.hash.toJSON() !==
639
+ // vk.FungibleTokenOfferContract.hash
640
+ // )
641
+ // throw new Error(
642
+ // "Invalid offer verification key, offer contract has to be upgraded"
643
+ // );
644
+ // break;
645
+ // }
646
+ // switch (txType) {
647
+ // case "token:bid:sell":
648
+ // case "token:bid:withdraw":
649
+ // case "token:bid:whitelist":
650
+ // if (bidContract === undefined)
651
+ // throw new Error("Bid contract is required");
652
+ // if (
653
+ // Mina.getAccount(
654
+ // bidContract.address
655
+ // ).zkapp?.verificationKey?.hash.toJSON() !==
656
+ // vk.FungibleTokenBidContract.hash
657
+ // )
658
+ // throw new Error(
659
+ // "Invalid bid verification key, bid contract has to be upgraded"
660
+ // );
661
+ // break;
662
+ // }
663
+
664
+ // switch (txType) {
665
+ // case "token:mint":
666
+ // case "token:burn":
667
+ // case "token:redeem":
668
+ // case "token:transfer":
669
+ // case "token:airdrop":
670
+ // case "token:offer:create":
671
+ // case "token:bid:create":
672
+ // case "token:offer:buy":
673
+ // case "token:offer:withdraw":
674
+ // case "token:bid:sell":
675
+ // if (
676
+ // Mina.getAccount(
677
+ // zkToken.address
678
+ // ).zkapp?.verificationKey?.hash.toJSON() !== vk.FungibleToken.hash
679
+ // )
680
+ // throw new Error(
681
+ // "Invalid token verification key, token contract has to be upgraded"
682
+ // );
683
+ // break;
684
+ // }
685
+
686
+ const { name, ipfsHash, metadataRoot, privateMetadata, serializedMap } =
687
+ typeof args.nftMintParams.metadata === "string"
688
+ ? {
689
+ name: args.nftMintParams.name,
690
+ ipfsHash: args.nftMintParams.storage,
691
+ metadataRoot: Field.fromJSON(args.nftMintParams.metadata),
692
+ privateMetadata: undefined,
693
+ serializedMap: undefined,
694
+ }
695
+ : await pinMetadata(
696
+ Metadata.fromOpenApiJSON({ json: args.nftMintParams.metadata })
697
+ );
698
+
699
+ if (ipfsHash === undefined)
700
+ throw new Error("storage is required, but not provided");
701
+ if (metadataRoot === undefined) throw new Error("metadataRoot is required");
702
+
703
+ const slot =
704
+ chain === "local"
705
+ ? Mina.currentSlot()
706
+ : chain === "zeko"
707
+ ? UInt32.zero
708
+ : (await fetchLastBlock()).globalSlotSinceGenesis;
709
+ const expiry = slot.add(
710
+ UInt32.from(chain === "mainnet" || chain === "devnet" ? 20 : 100000)
711
+ );
712
+
713
+ const nftData = NFTData.new(args.nftMintParams.data);
714
+
715
+ if (!args.nftMintParams.address) throw new Error("NFT address is required");
716
+
717
+ const mintParams = new MintParams({
718
+ name: fieldFromString(name),
719
+ address: PublicKey.fromBase58(args.nftMintParams.address),
720
+ tokenId: TokenId.derive(collectionAddress),
721
+ data: nftData,
722
+ fee: args.nftMintParams.fee
723
+ ? UInt64.from(Math.round(args.nftMintParams.fee * 1_000_000_000))
724
+ : UInt64.zero,
725
+ metadata: metadataRoot,
726
+ storage: Storage.fromString(ipfsHash),
727
+ metadataVerificationKeyHash: args.nftMintParams.metadataVerificationKeyHash
728
+ ? Field.fromJSON(args.nftMintParams.metadataVerificationKeyHash)
729
+ : Field(0),
730
+ expiry: args.nftMintParams.expiry
731
+ ? UInt32.from(Math.round(args.nftMintParams.expiry))
732
+ : expiry,
733
+ });
734
+
735
+ const accountCreationFee = 0;
736
+
737
+ const tx = await Mina.transaction({ sender, fee, memo, nonce }, async () => {
738
+ const feeAccountUpdate = AccountUpdate.createSigned(sender);
739
+ if (accountCreationFee > 0) {
740
+ feeAccountUpdate.balance.subInPlace(accountCreationFee);
741
+ }
742
+ if (provingKey && provingFee)
743
+ feeAccountUpdate.send({
744
+ to: provingKey,
745
+ amount: provingFee,
746
+ });
747
+ if (developerAddress && developerFee) {
748
+ feeAccountUpdate.send({
749
+ to: developerAddress,
750
+ amount: developerFee,
751
+ });
752
+ }
753
+
754
+ switch (txType) {
755
+ case "nft:mint":
756
+ await zkCollection.mintByCreator(mintParams);
757
+ break;
758
+
759
+ default:
760
+ throw new Error(`Unknown transaction type: ${txType}`);
761
+ }
762
+ });
763
+ return {
764
+ request: args,
765
+ tx,
766
+ adminType,
767
+ adminContractAddress,
768
+ symbol,
769
+ name,
770
+ verificationKeyHashes,
771
+ metadataRoot: metadataRoot.toJSON(),
772
+ privateMetadata,
773
+ storage: ipfsHash,
774
+ map: serializedMap,
775
+ };
776
+ }
777
+
778
+ export async function getNftSymbolAndAdmin(params: {
779
+ txType: NftTransactionType;
780
+ // to?: PublicKey;
781
+ // offerAddress?: PublicKey;
782
+ // bidAddress?: PublicKey;
783
+ nftAddress?: PublicKey;
784
+ collectionAddress: PublicKey;
785
+ chain: blockchain;
786
+ }): Promise<{
787
+ adminContractAddress: PublicKey;
788
+ symbol: string;
789
+ adminType: NftAdminType;
790
+ verificationKeyHashes: string[];
791
+ }> {
792
+ const { txType, collectionAddress, chain, nftAddress } = params;
793
+ const vk =
794
+ tokenVerificationKeys[chain === "mainnet" ? "mainnet" : "devnet"].vk;
795
+ let verificationKeyHashes: string[] = [];
796
+ // if (bidAddress) {
797
+ // verificationKeyHashes.push(vk.FungibleTokenBidContract.hash);
798
+ // }
799
+ // if (offerAddress) {
800
+ // verificationKeyHashes.push(vk.FungibleTokenOfferContract.hash);
801
+ // }
802
+
803
+ await fetchMinaAccount({ publicKey: collectionAddress, force: true });
804
+ if (!Mina.hasAccount(collectionAddress)) {
805
+ throw new Error("Collection contract account not found");
806
+ }
807
+ const tokenId = TokenId.derive(collectionAddress);
808
+ if (nftAddress) {
809
+ await fetchMinaAccount({
810
+ publicKey: nftAddress,
811
+ tokenId,
812
+ force: true,
813
+ });
814
+ if (!Mina.hasAccount(nftAddress, tokenId)) {
815
+ throw new Error("NFT account not found");
816
+ }
817
+ }
818
+
819
+ const account = Mina.getAccount(collectionAddress);
820
+ const verificationKey = account.zkapp?.verificationKey;
821
+ if (!verificationKey) {
822
+ throw new Error("Collection contract verification key not found");
823
+ }
824
+ if (!verificationKeyHashes.includes(verificationKey.hash.toJSON())) {
825
+ verificationKeyHashes.push(verificationKey.hash.toJSON());
826
+ }
827
+ if (account.zkapp?.appState === undefined) {
828
+ throw new Error("Collection contract state not found");
829
+ }
830
+
831
+ const collection = new Collection(collectionAddress);
832
+ const symbol = account.tokenSymbol;
833
+ const adminContractPublicKey = collection.admin.get();
834
+ await fetchMinaAccount({
835
+ publicKey: adminContractPublicKey,
836
+ force: true,
837
+ });
838
+ if (!Mina.hasAccount(adminContractPublicKey)) {
839
+ throw new Error("Admin contract account not found");
840
+ }
841
+
842
+ const adminContract = Mina.getAccount(adminContractPublicKey);
843
+ const adminVerificationKey = adminContract.zkapp?.verificationKey;
844
+
845
+ if (!adminVerificationKey) {
846
+ throw new Error("Admin verification key not found");
847
+ }
848
+ if (!verificationKeyHashes.includes(adminVerificationKey.hash.toJSON())) {
849
+ verificationKeyHashes.push(adminVerificationKey.hash.toJSON());
850
+ }
851
+ let adminType: NftAdminType = "unknown";
852
+ if (
853
+ vk.NFTAdvancedAdmin.hash === adminVerificationKey.hash.toJSON() &&
854
+ vk.NFTAdvancedAdmin.data === adminVerificationKey.data
855
+ ) {
856
+ adminType = "advanced";
857
+ } else if (
858
+ vk.NFTAdmin.hash === adminVerificationKey.hash.toJSON() &&
859
+ vk.NFTAdmin.data === adminVerificationKey.data
860
+ ) {
861
+ adminType = "standard";
862
+ } else {
863
+ console.error("Unknown admin verification key", {
864
+ hash: adminVerificationKey.hash.toJSON(),
865
+ symbol,
866
+ address: adminContractPublicKey.toBase58(),
867
+ });
868
+ }
869
+ // let isToNewAccount: boolean | undefined = undefined;
870
+ // if (to) {
871
+ // if (adminType === "advanced") {
872
+ // const adminTokenId = TokenId.derive(adminContractPublicKey);
873
+ // await fetchMinaAccount({
874
+ // publicKey: to,
875
+ // tokenId: adminTokenId,
876
+ // force: false,
877
+ // });
878
+ // isToNewAccount = !Mina.hasAccount(to, adminTokenId);
879
+ // }
880
+ // if (adminType === "bondingCurve") {
881
+ // const adminTokenId = TokenId.derive(adminContractPublicKey);
882
+ // await fetchMinaAccount({
883
+ // publicKey: adminContractPublicKey,
884
+ // tokenId: adminTokenId,
885
+ // force: true,
886
+ // });
887
+ // }
888
+ // }
889
+ // const adminAddress0 = adminContract.zkapp?.appState[0];
890
+ // const adminAddress1 = adminContract.zkapp?.appState[1];
891
+ // if (adminAddress0 === undefined || adminAddress1 === undefined) {
892
+ // throw new Error("Cannot fetch admin address from admin contract");
893
+ // }
894
+ // const adminAddress = PublicKey.fromFields([adminAddress0, adminAddress1]);
895
+
896
+ for (const hash of verificationKeyHashes) {
897
+ const found = Object.values(vk).some((key) => key.hash === hash);
898
+ if (!found) {
899
+ console.error(`Final check: unknown verification key hash: ${hash}`);
900
+ verificationKeyHashes = verificationKeyHashes.filter((h) => h !== hash);
901
+ }
902
+ }
903
+
904
+ // Sort verification key hashes by type
905
+ verificationKeyHashes.sort((a, b) => {
906
+ const typeA = Object.values(vk).find((key) => key.hash === a)?.type;
907
+ const typeB = Object.values(vk).find((key) => key.hash === b)?.type;
908
+ if (typeA === undefined || typeB === undefined) {
909
+ throw new Error("Unknown verification key hash");
910
+ }
911
+ const typeOrder = {
912
+ upgrade: 0,
913
+ nft: 1,
914
+ admin: 2,
915
+ collection: 3,
916
+ token: 4,
917
+ user: 5,
918
+ };
919
+
920
+ return typeOrder[typeA] - typeOrder[typeB];
921
+ });
922
+
923
+ return {
924
+ adminContractAddress: adminContractPublicKey,
925
+ symbol,
926
+ adminType,
927
+ verificationKeyHashes,
928
+ };
929
+ }