@silvana-one/abi
Version:
Silvana ABI Experimental Library
805 lines • 34.5 kB
JavaScript
import { Whitelist } from "@silvana-one/storage";
import { fetchMinaAccount } from "../fetch.js";
import { FungibleToken, AdvancedFungibleToken, BondingCurveFungibleToken, FungibleTokenAdmin, FungibleTokenAdvancedAdmin, FungibleTokenBidContract, FungibleTokenOfferContract, FungibleTokenBondingCurveAdmin, } from "@silvana-one/token";
import { tokenVerificationKeys } from "../vk/index.js";
import { PublicKey, Mina, AccountUpdate, UInt64, UInt8, Bool, Struct, Field, TokenId, UInt32, } from "o1js";
export async function buildTokenLaunchTransaction(params) {
const { chain, args } = params;
const ACCOUNT_CREATION_FEE = chain === "zeko:testnet" ? 100000000n : 1000000000n;
const { uri, symbol, memo, nonce, adminContract: adminType } = args;
if (memo && typeof memo !== "string")
throw new Error("Memo must be a string");
if (memo && memo.length > 30)
throw new Error("Memo must be less than 30 characters");
if (!symbol || typeof symbol !== "string")
throw new Error("Symbol must be a string");
if (symbol.length >= 7)
throw new Error("Symbol must be less than 7 characters");
const sender = PublicKey.fromBase58(args.sender);
if (nonce === undefined)
throw new Error("Nonce is required");
if (typeof nonce !== "number")
throw new Error("Nonce must be a number");
const fee = args.fee ?? 200_000_000;
if (uri && typeof uri !== "string")
throw new Error("Uri must be a string");
if (!args.tokenAddress || typeof args.tokenAddress !== "string")
throw new Error("tokenAddress is required");
if (!args.adminContractAddress ||
typeof args.adminContractAddress !== "string")
throw new Error("adminContractAddress is required");
const adminContract = adminType === "advanced"
? FungibleTokenAdvancedAdmin
: adminType === "bondingCurve"
? FungibleTokenBondingCurveAdmin
: FungibleTokenAdmin;
const tokenContract = adminType === "advanced"
? AdvancedFungibleToken
: adminType === "bondingCurve"
? BondingCurveFungibleToken
: FungibleToken;
const vk = tokenVerificationKeys[chain === "mina:mainnet" ? "mainnet" : "devnet"].vk;
if (!vk ||
!vk.FungibleTokenOfferContract ||
!vk.FungibleTokenOfferContract.hash ||
!vk.FungibleTokenOfferContract.data ||
!vk.FungibleTokenBidContract ||
!vk.FungibleTokenBidContract.hash ||
!vk.FungibleTokenBidContract.data ||
!vk.FungibleTokenAdvancedAdmin ||
!vk.FungibleTokenAdvancedAdmin.hash ||
!vk.FungibleTokenAdvancedAdmin.data ||
!vk.FungibleTokenAdmin ||
!vk.FungibleTokenAdmin.hash ||
!vk.FungibleTokenAdmin.data ||
!vk.FungibleTokenBondingCurveAdmin ||
!vk.FungibleTokenBondingCurveAdmin.hash ||
!vk.FungibleTokenBondingCurveAdmin.data ||
!vk.AdvancedFungibleToken ||
!vk.AdvancedFungibleToken.hash ||
!vk.AdvancedFungibleToken.data ||
!vk.FungibleToken ||
!vk.FungibleToken.hash ||
!vk.FungibleToken.data)
throw new Error("Cannot get verification key from vk");
const adminVerificationKey = adminType === "advanced"
? vk.FungibleTokenAdvancedAdmin
: adminType === "bondingCurve"
? vk.FungibleTokenBondingCurveAdmin
: vk.FungibleTokenAdmin;
const tokenVerificationKey = adminType === "advanced"
? vk.AdvancedFungibleToken
: adminType === "bondingCurve"
? vk.BondingCurveFungibleToken
: vk.FungibleToken;
if (!adminVerificationKey || !tokenVerificationKey)
throw new Error("Cannot get verification keys");
await fetchMinaAccount({
publicKey: sender,
force: true,
});
if (!Mina.hasAccount(sender)) {
throw new Error("Sender does not have account");
}
const whitelist = "whitelist" in args && args.whitelist
? typeof args.whitelist === "string"
? Whitelist.fromString(args.whitelist)
: (await Whitelist.create({ list: args.whitelist, name: symbol }))
.whitelist
: Whitelist.empty();
const tokenAddress = PublicKey.fromBase58(args.tokenAddress);
const adminContractAddress = PublicKey.fromBase58(args.adminContractAddress);
const zkToken = new tokenContract(tokenAddress);
const zkAdmin = new adminContract(adminContractAddress);
const provingKey = params.provingKey
? PublicKey.fromBase58(params.provingKey)
: sender;
const provingFee = params.provingFee
? UInt64.from(Math.round(params.provingFee))
: undefined;
const developerFee = args.developerFee
? UInt64.from(Math.round(args.developerFee))
: undefined;
const developerAddress = params.developerAddress
? PublicKey.fromBase58(params.developerAddress)
: undefined;
const totalSupply = "totalSupply" in args && args.totalSupply
? UInt64.from(Math.round(args.totalSupply))
: UInt64.MAXINT();
const requireAdminSignatureForMint = "requireAdminSignatureForMint" in args && args.requireAdminSignatureForMint
? Bool(args.requireAdminSignatureForMint)
: Bool(false);
const anyoneCanMint = "canMint" in args && args.canMint
? Bool(args.canMint === "anyone")
: Bool(false);
const decimals = UInt8.from(args.decimals ? Math.round(args.decimals) : 9);
const tx = await Mina.transaction({ sender, fee, memo: memo ?? `launch ${symbol}`, nonce }, async () => {
if (zkAdmin instanceof FungibleTokenBondingCurveAdmin) {
await zkAdmin.deploy({
verificationKey: adminVerificationKey,
});
zkAdmin.account.tokenSymbol.set("BC");
zkAdmin.account.zkappUri.set(uri);
await zkAdmin.initialize({
tokenAddress,
startPrice: UInt64.from(10_000),
curveK: UInt64.from(10_000),
feeMaster: provingKey,
fee: UInt32.from(1000), // 1000 = 1%
launchFee: UInt64.from(5_000_000_000),
numberOfNewAccounts: UInt64.from(ACCOUNT_CREATION_FEE < 1000000000n ? 0 : 4), // patch Zeko Account Creation Fee
});
if (ACCOUNT_CREATION_FEE < 1000000000n) {
// patch Zeko Account Creation Fee
const feeAccountUpdate = AccountUpdate.createSigned(sender);
feeAccountUpdate.balance.subInPlace(ACCOUNT_CREATION_FEE * 4n);
}
}
else {
const feeAccountUpdate = AccountUpdate.createSigned(sender);
feeAccountUpdate.balance.subInPlace(ACCOUNT_CREATION_FEE * 3n +
(adminType === "advanced" ? ACCOUNT_CREATION_FEE : 0n));
if (provingFee && provingKey)
feeAccountUpdate.send({
to: provingKey,
amount: provingFee,
});
if (developerAddress && developerFee) {
feeAccountUpdate.send({
to: developerAddress,
amount: developerFee,
});
}
await zkAdmin.deploy({
adminPublicKey: sender,
tokenContract: tokenAddress,
verificationKey: adminVerificationKey,
whitelist,
totalSupply,
requireAdminSignatureForMint,
anyoneCanMint,
});
if (adminType === "advanced") {
const adminUpdate = AccountUpdate.create(adminContractAddress, TokenId.derive(adminContractAddress));
zkAdmin.approve(adminUpdate);
}
zkAdmin.account.zkappUri.set(uri);
}
await zkToken.deploy({
symbol,
src: uri,
verificationKey: tokenVerificationKey,
allowUpdates: true,
});
await zkToken.initialize(adminContractAddress, decimals,
// We can set `startPaused` to `Bool(false)` here, because we are doing an atomic deployment
// If you are not deploying the admin and token contracts in the same transaction,
// it is safer to start the tokens paused, and resume them only after verifying that
// the admin contract has been deployed
Bool(false));
});
return {
request: adminType === "advanced"
? {
...args,
whitelist: whitelist.toString(),
}
: args,
tx,
adminType,
verificationKeyHashes: [
adminVerificationKey.hash,
tokenVerificationKey.hash,
],
};
}
export async function buildTokenTransaction(params) {
const { chain, args } = params;
const ACCOUNT_CREATION_FEE = chain === "zeko:testnet" ? 100000000n : 1000000000n;
const { nonce, txType } = args;
if (nonce === undefined)
throw new Error("Nonce is required");
if (typeof nonce !== "number")
throw new Error("Nonce must be a number");
if (txType === undefined)
throw new Error("Tx type is required");
if (typeof txType !== "string")
throw new Error("Tx type must be a string");
const sender = PublicKey.fromBase58(args.sender);
const tokenAddress = PublicKey.fromBase58(args.tokenAddress);
const offerAddress = "offerAddress" in args && args.offerAddress
? PublicKey.fromBase58(args.offerAddress)
: undefined;
if (!offerAddress &&
(txType === "token:offer:create" ||
txType === "token:offer:buy" ||
txType === "token:offer:withdraw"))
throw new Error("Offer address is required");
const bidAddress = "bidAddress" in args && args.bidAddress
? PublicKey.fromBase58(args.bidAddress)
: undefined;
if (!bidAddress &&
(txType === "token:bid:create" ||
txType === "token:bid:sell" ||
txType === "token:bid:withdraw"))
throw new Error("Bid address is required");
const to = "to" in args && args.to ? PublicKey.fromBase58(args.to) : undefined;
if (!to &&
(txType === "token:transfer" ||
txType === "token:airdrop" ||
txType === "token:mint"))
throw new Error("To address is required");
const from = "from" in args && args.from ? PublicKey.fromBase58(args.from) : undefined;
if (!from && txType === "token:burn")
throw new Error("From address is required for token:burn");
const amount = "amount" in args ? UInt64.from(Math.round(args.amount)) : undefined;
const price = "price" in args && args.price
? UInt64.from(Math.round(args.price))
: undefined;
const slippage = UInt32.from(Math.round("slippage" in args && args.slippage !== undefined ? args.slippage : 50));
await fetchMinaAccount({
publicKey: sender,
force: true,
});
if (!Mina.hasAccount(sender)) {
throw new Error("Sender does not have account");
}
const { symbol, adminContractAddress, adminAddress, adminType, isToNewAccount, verificationKeyHashes, } = await getTokenSymbolAndAdmin({
txType,
tokenAddress,
chain,
to,
offerAddress,
bidAddress,
});
const memo = args.memo ?? `${txType} ${symbol}`;
const fee = args.fee ?? 200_000_000;
const provingKey = params.provingKey
? PublicKey.fromBase58(params.provingKey)
: sender;
const provingFee = params.provingFee
? UInt64.from(Math.round(params.provingFee))
: undefined;
const developerFee = args.developerFee
? UInt64.from(Math.round(args.developerFee))
: undefined;
const developerAddress = params.developerAddress
? PublicKey.fromBase58(params.developerAddress)
: undefined;
//const adminContract = new FungibleTokenAdmin(adminContractAddress);
const advancedAdminContract = new FungibleTokenAdvancedAdmin(adminContractAddress);
const bondingCurveAdminContract = new FungibleTokenBondingCurveAdmin(adminContractAddress);
const tokenContract = adminType === "advanced" && txType === "token:mint"
? AdvancedFungibleToken
: adminType === "bondingCurve" &&
(txType === "token:mint" || txType === "token:redeem")
? BondingCurveFungibleToken
: FungibleToken;
if ((txType === "token:admin:whitelist" ||
txType === "token:bid:whitelist" ||
txType === "token:offer:whitelist") &&
!args.whitelist) {
throw new Error("Whitelist is required");
}
const whitelist = "whitelist" in args && args.whitelist
? typeof args.whitelist === "string"
? Whitelist.fromString(args.whitelist)
: (await Whitelist.create({ list: args.whitelist, name: symbol }))
.whitelist
: Whitelist.empty();
const zkToken = new tokenContract(tokenAddress);
const tokenId = zkToken.deriveTokenId();
if (txType === "token:mint" &&
adminType === "standard" &&
adminAddress.toBase58() !== sender.toBase58())
throw new Error("Invalid sender for FungibleToken mint with standard admin");
await fetchMinaAccount({
publicKey: sender,
tokenId,
force: [
"token:transfer",
"token:airdrop",
].includes(txType),
});
if (to) {
await fetchMinaAccount({
publicKey: to,
tokenId,
force: false,
});
}
if (from) {
await fetchMinaAccount({
publicKey: from,
tokenId,
force: false,
});
}
if (offerAddress)
await fetchMinaAccount({
publicKey: offerAddress,
tokenId,
force: [
"token:offer:whitelist",
"token:offer:buy",
"token:offer:withdraw",
].includes(txType),
});
if (bidAddress)
await fetchMinaAccount({
publicKey: bidAddress,
force: [
"token:bid:whitelist",
"token:bid:sell",
"token:bid:withdraw",
].includes(txType),
});
const offerContract = offerAddress
? new FungibleTokenOfferContract(offerAddress, tokenId)
: undefined;
const bidContract = bidAddress
? new FungibleTokenBidContract(bidAddress)
: undefined;
const offerContractDeployment = offerAddress
? new FungibleTokenOfferContract(offerAddress, tokenId)
: undefined;
const bidContractDeployment = bidAddress
? new FungibleTokenBidContract(bidAddress)
: undefined;
const vk = tokenVerificationKeys[chain === "mina:mainnet" ? "mainnet" : "devnet"].vk;
if (!vk ||
!vk.FungibleTokenOfferContract ||
!vk.FungibleTokenOfferContract.hash ||
!vk.FungibleTokenOfferContract.data ||
!vk.FungibleTokenBidContract ||
!vk.FungibleTokenBidContract.hash ||
!vk.FungibleTokenBidContract.data ||
!vk.FungibleTokenAdvancedAdmin ||
!vk.FungibleTokenAdvancedAdmin.hash ||
!vk.FungibleTokenAdvancedAdmin.data ||
!vk.FungibleTokenBondingCurveAdmin ||
!vk.FungibleTokenBondingCurveAdmin.hash ||
!vk.FungibleTokenBondingCurveAdmin.data ||
!vk.FungibleTokenAdmin ||
!vk.FungibleTokenAdmin.hash ||
!vk.FungibleTokenAdmin.data ||
!vk.AdvancedFungibleToken ||
!vk.AdvancedFungibleToken.hash ||
!vk.AdvancedFungibleToken.data ||
!vk.FungibleToken ||
!vk.FungibleToken.hash ||
!vk.FungibleToken.data)
throw new Error("Cannot get verification key from vk");
const offerVerificationKey = FungibleTokenOfferContract._verificationKey ?? {
hash: Field(vk.FungibleTokenOfferContract.hash),
data: vk.FungibleTokenOfferContract.data,
};
const bidVerificationKey = FungibleTokenBidContract._verificationKey ?? {
hash: Field(vk.FungibleTokenBidContract.hash),
data: vk.FungibleTokenBidContract.data,
};
const isNewBidOfferAccount = txType === "token:offer:create" && offerAddress
? !Mina.hasAccount(offerAddress, tokenId)
: txType === "token:bid:create" && bidAddress
? !Mina.hasAccount(bidAddress)
: false;
const isNewBuyAccount = txType === "token:offer:buy" ? !Mina.hasAccount(sender, tokenId) : false;
let isNewSellAccount = false;
if (txType === "token:bid:sell") {
if (!bidAddress || !bidContract)
throw new Error("Bid address is required");
await fetchMinaAccount({
publicKey: bidAddress,
force: true,
});
const buyer = bidContract.buyer.get();
await fetchMinaAccount({
publicKey: buyer,
tokenId,
force: false,
});
isNewSellAccount = !Mina.hasAccount(buyer, tokenId);
}
if (txType === "token:burn") {
await fetchMinaAccount({
publicKey: sender,
force: true,
});
await fetchMinaAccount({
publicKey: sender,
tokenId,
force: false,
});
if (!Mina.hasAccount(sender, tokenId))
throw new Error("Sender does not have tokens to burn");
}
const isNewTransferMintAccount = (txType === "token:transfer" ||
txType === "token:airdrop" ||
txType === "token:mint") &&
to
? !Mina.hasAccount(to, tokenId)
: false;
const accountCreationFee = (isNewBidOfferAccount ? ACCOUNT_CREATION_FEE : 0n) +
(isNewBuyAccount ? ACCOUNT_CREATION_FEE : 0n) +
(isNewSellAccount ? ACCOUNT_CREATION_FEE : 0n) +
(isNewTransferMintAccount ? ACCOUNT_CREATION_FEE : 0n) +
(isToNewAccount &&
txType === "token:mint" &&
adminType === "advanced" &&
advancedAdminContract.whitelist.get().isSome().toBoolean()
? ACCOUNT_CREATION_FEE
: 0n);
console.log("accountCreationFee", accountCreationFee / 1000000000n);
let isNewMintAccountBondingCurve = false;
if (txType === "token:mint" && adminType === "bondingCurve" && to) {
await fetchMinaAccount({
publicKey: to,
tokenId,
force: false,
});
isNewMintAccountBondingCurve = !Mina.hasAccount(to, tokenId);
}
switch (txType) {
case "token:offer:buy":
case "token:offer:withdraw":
case "token:offer:whitelist":
if (offerContract === undefined)
throw new Error("Offer contract is required");
if (Mina.getAccount(offerContract.address, tokenId).zkapp?.verificationKey?.hash.toJSON() !==
vk.FungibleTokenOfferContract.hash)
throw new Error("Invalid offer verification key, offer contract has to be upgraded");
break;
}
switch (txType) {
case "token:bid:sell":
case "token:bid:withdraw":
case "token:bid:whitelist":
if (bidContract === undefined)
throw new Error("Bid contract is required");
if (Mina.getAccount(bidContract.address).zkapp?.verificationKey?.hash.toJSON() !==
vk.FungibleTokenBidContract.hash)
throw new Error("Invalid bid verification key, bid contract has to be upgraded");
break;
}
switch (txType) {
case "token:mint":
case "token:burn":
case "token:redeem":
case "token:transfer":
case "token:airdrop":
case "token:offer:create":
case "token:bid:create":
case "token:offer:buy":
case "token:offer:withdraw":
case "token:bid:sell":
if (Mina.getAccount(zkToken.address).zkapp?.verificationKey?.hash.toJSON() !== vk.FungibleToken.hash)
throw new Error("Invalid token verification key, token contract has to be upgraded");
break;
}
const tx = await Mina.transaction({ sender, fee, memo, nonce }, async () => {
if (adminType !== "bondingCurve" ||
(txType !== "token:mint" && txType !== "token:redeem")) {
const feeAccountUpdate = AccountUpdate.createSigned(sender);
if (accountCreationFee > 0) {
feeAccountUpdate.balance.subInPlace(accountCreationFee);
}
if (provingKey && provingFee)
feeAccountUpdate.send({
to: provingKey,
amount: provingFee,
});
if (developerAddress && developerFee) {
feeAccountUpdate.send({
to: developerAddress,
amount: developerFee,
});
}
}
switch (txType) {
case "token:mint":
if (amount === undefined)
throw new Error("Error: Amount is required");
if (to === undefined)
throw new Error("Error: To address is required");
if (adminType === "bondingCurve") {
if (isNewMintAccountBondingCurve &&
ACCOUNT_CREATION_FEE < 1000000000n) {
const feeAccountUpdate = AccountUpdate.createSigned(sender);
feeAccountUpdate.balance.addInPlace(1000000000n - ACCOUNT_CREATION_FEE);
}
if (price === undefined)
throw new Error("Error: Price is required for bonding curve mint");
await bondingCurveAdminContract.mint(to, amount, price);
}
else {
await zkToken.mint(to, amount);
}
break;
case "token:redeem":
if (adminType !== "bondingCurve")
throw new Error("Error: Invalid admin type for redeem");
if (amount === undefined)
throw new Error("Error: Amount is required");
if (price === undefined)
throw new Error("Error: Price is required");
if (slippage === undefined)
throw new Error("Error: Slippage is required");
await bondingCurveAdminContract.redeem(amount, price, slippage);
break;
case "token:transfer":
if (amount === undefined)
throw new Error("Error: Amount is required");
if (to === undefined)
throw new Error("Error: From address is required");
await zkToken.transfer(sender, to, amount);
break;
case "token:burn":
if (amount === undefined)
throw new Error("Error: Amount is required");
if (from === undefined)
throw new Error("Error: From address is required");
await zkToken.burn(from, amount);
break;
case "token:offer:create":
if (price === undefined)
throw new Error("Error: Price is required");
if (amount === undefined)
throw new Error("Error: Amount is required");
if (offerContract === undefined)
throw new Error("Error: Offer address is required");
if (offerContractDeployment === undefined)
throw new Error("Error: Offer address is required");
if (isNewBidOfferAccount) {
await offerContractDeployment.deploy({
verificationKey: offerVerificationKey,
whitelist: whitelist ?? Whitelist.empty(),
});
offerContract.account.zkappUri.set(`Offer for ${symbol}`);
await offerContract.initialize(sender, tokenAddress, amount, price);
await zkToken.approveAccountUpdates([
offerContractDeployment.self,
offerContract.self,
]);
}
else {
await offerContract.offer(amount, price);
await zkToken.approveAccountUpdate(offerContract.self);
}
break;
case "token:offer:buy":
if (amount === undefined)
throw new Error("Error: Amount is required");
if (offerContract === undefined)
throw new Error("Error: Offer address is required");
await offerContract.buy(amount);
await zkToken.approveAccountUpdate(offerContract.self);
break;
case "token:offer:withdraw":
if (amount === undefined)
throw new Error("Error: Amount is required");
if (offerContract === undefined)
throw new Error("Error: Offer address is required");
await offerContract.withdraw(amount);
await zkToken.approveAccountUpdate(offerContract.self);
break;
case "token:bid:create":
if (price === undefined)
throw new Error("Error: Price is required");
if (amount === undefined)
throw new Error("Error: Amount is required");
if (bidContract === undefined)
throw new Error("Error: Bid address is required");
if (bidContractDeployment === undefined)
throw new Error("Error: Bid address is required");
if (isNewBidOfferAccount) {
await bidContractDeployment.deploy({
verificationKey: bidVerificationKey,
whitelist: whitelist ?? Whitelist.empty(),
});
bidContract.account.zkappUri.set(`Bid for ${symbol}`);
await bidContract.initialize(tokenAddress, amount, price);
await zkToken.approveAccountUpdates([
bidContractDeployment.self,
bidContract.self,
]);
}
else {
await bidContract.bid(amount, price);
await zkToken.approveAccountUpdate(bidContract.self);
}
break;
case "token:bid:sell":
if (amount === undefined)
throw new Error("Error: Amount is required");
if (bidContract === undefined)
throw new Error("Error: Bid address is required");
await bidContract.sell(amount);
await zkToken.approveAccountUpdate(bidContract.self);
break;
case "token:bid:withdraw":
if (amount === undefined)
throw new Error("Error: Amount is required");
if (bidContract === undefined)
throw new Error("Error: Bid address is required");
await bidContract.withdraw(amount);
//await zkToken.approveAccountUpdate(bidContract.self);
break;
case "token:admin:whitelist":
if (adminType !== "advanced")
throw new Error("Invalid admin type for updateAdminWhitelist");
await advancedAdminContract.updateWhitelist(whitelist);
break;
case "token:bid:whitelist":
if (bidContract === undefined)
throw new Error("Error: Bid address is required");
await bidContract.updateWhitelist(whitelist);
break;
case "token:offer:whitelist":
if (offerContract === undefined)
throw new Error("Error: Offer address is required");
await offerContract.updateWhitelist(whitelist);
break;
default:
throw new Error(`Unknown transaction type: ${txType}`);
}
});
return {
request: txType === "token:offer:create" ||
txType === "token:bid:create" ||
txType === "token:offer:whitelist" ||
txType === "token:bid:whitelist" ||
txType === "token:admin:whitelist"
? {
...args,
whitelist: whitelist?.toString(),
}
: args,
tx,
adminType,
adminContractAddress,
adminAddress,
symbol,
verificationKeyHashes,
};
}
export async function getTokenSymbolAndAdmin(params) {
const { txType, tokenAddress, chain, to, offerAddress, bidAddress } = params;
const vk = tokenVerificationKeys[chain === "mina:mainnet" ? "mainnet" : "devnet"].vk;
let verificationKeyHashes = [];
if (bidAddress) {
verificationKeyHashes.push(vk.FungibleTokenBidContract.hash);
}
if (offerAddress) {
verificationKeyHashes.push(vk.FungibleTokenOfferContract.hash);
}
class FungibleTokenState extends Struct({
decimals: UInt8,
admin: PublicKey,
paused: Bool,
}) {
}
const FungibleTokenStateSize = FungibleTokenState.sizeInFields();
class FungibleTokenAdminState extends Struct({
adminPublicKey: PublicKey,
}) {
}
const FungibleTokenAdminStateSize = FungibleTokenAdminState.sizeInFields();
await fetchMinaAccount({ publicKey: tokenAddress, force: true });
if (!Mina.hasAccount(tokenAddress)) {
throw new Error("Token contract account not found");
}
const tokenId = TokenId.derive(tokenAddress);
await fetchMinaAccount({ publicKey: tokenAddress, tokenId, force: true });
if (!Mina.hasAccount(tokenAddress, tokenId)) {
throw new Error("Token contract totalSupply account not found");
}
const account = Mina.getAccount(tokenAddress);
const verificationKey = account.zkapp?.verificationKey;
if (!verificationKey) {
throw new Error("Token contract verification key not found");
}
if (!verificationKeyHashes.includes(verificationKey.hash.toJSON())) {
verificationKeyHashes.push(verificationKey.hash.toJSON());
}
if (account.zkapp?.appState === undefined) {
throw new Error("Token contract state not found");
}
const state = FungibleTokenState.fromFields(account.zkapp?.appState.slice(0, FungibleTokenStateSize));
const symbol = account.tokenSymbol;
const adminContractPublicKey = state.admin;
await fetchMinaAccount({
publicKey: adminContractPublicKey,
force: true,
});
if (!Mina.hasAccount(adminContractPublicKey)) {
throw new Error("Admin contract account not found");
}
const adminContract = Mina.getAccount(adminContractPublicKey);
const adminVerificationKey = adminContract.zkapp?.verificationKey;
if (!adminVerificationKey) {
throw new Error("Admin verification key not found");
}
if (!verificationKeyHashes.includes(adminVerificationKey.hash.toJSON())) {
verificationKeyHashes.push(adminVerificationKey.hash.toJSON());
}
let adminType = "unknown";
if (vk.FungibleTokenAdvancedAdmin.hash === adminVerificationKey.hash.toJSON() &&
vk.FungibleTokenAdvancedAdmin.data === adminVerificationKey.data) {
adminType = "advanced";
}
else if (vk.FungibleTokenAdmin.hash === adminVerificationKey.hash.toJSON() &&
vk.FungibleTokenAdmin.data === adminVerificationKey.data) {
adminType = "standard";
}
else if (vk.FungibleTokenBondingCurveAdmin.hash ===
adminVerificationKey.hash.toJSON() &&
vk.FungibleTokenBondingCurveAdmin.data === adminVerificationKey.data) {
adminType = "bondingCurve";
}
else {
console.error("Unknown admin verification key", {
hash: adminVerificationKey.hash.toJSON(),
symbol,
address: adminContractPublicKey.toBase58(),
});
}
let isToNewAccount = undefined;
if (to) {
if (adminType === "advanced") {
const adminTokenId = TokenId.derive(adminContractPublicKey);
await fetchMinaAccount({
publicKey: to,
tokenId: adminTokenId,
force: false,
});
isToNewAccount = !Mina.hasAccount(to, adminTokenId);
}
if (adminType === "bondingCurve") {
const adminTokenId = TokenId.derive(adminContractPublicKey);
await fetchMinaAccount({
publicKey: adminContractPublicKey,
tokenId: adminTokenId,
force: true,
});
}
}
const adminAddress0 = adminContract.zkapp?.appState[0];
const adminAddress1 = adminContract.zkapp?.appState[1];
if (adminAddress0 === undefined || adminAddress1 === undefined) {
throw new Error("Cannot fetch admin address from admin contract");
}
const adminAddress = PublicKey.fromFields([adminAddress0, adminAddress1]);
for (const hash of verificationKeyHashes) {
const found = Object.values(vk).some((key) => key.hash === hash);
if (!found) {
console.error(`Final check: unknown verification key hash: ${hash}`);
verificationKeyHashes = verificationKeyHashes.filter((h) => h !== hash);
}
}
// Sort verification key hashes by type: upgrade -> admin -> token -> user
verificationKeyHashes.sort((a, b) => {
const typeA = Object.values(vk).find((key) => key.hash === a)?.type;
const typeB = Object.values(vk).find((key) => key.hash === b)?.type;
if (typeA === undefined || typeB === undefined) {
throw new Error("Unknown verification key hash");
}
const typeOrder = {
upgrade: 0,
nft: 1,
admin: 2,
collection: 3,
token: 4,
user: 5,
};
return typeOrder[typeA] - typeOrder[typeB];
});
return {
adminContractAddress: adminContractPublicKey,
adminAddress: adminAddress,
symbol,
adminType,
isToNewAccount,
verificationKeyHashes,
};
}
//# sourceMappingURL=build.js.map