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