UNPKG

@silvana-one/abi

Version:

Silvana ABI Experimental Library

531 lines 19 kB
import { Mina, PublicKey, TokenId, Field } from "o1js"; import { fetchMinaAccount } from "../fetch.js"; import { checkAddress } from "../info/address.js"; import { FungibleToken, } from "@silvana-one/token"; import { tokenVerificationKeys } from "../vk/vk.js"; export async function getContractInfo(params) { const { address, chain, parentTokenId, decimals } = params; const vk = tokenVerificationKeys[chain].vk; const info = []; if (typeof address === "string" && !checkAddress(address)) { throw new Error("Invalid address"); } const publicKey = typeof address === "string" ? PublicKey.fromBase58(address) : address; const tokenId = params.tokenId ? typeof params.tokenId === "string" ? TokenId.fromBase58(params.tokenId) : params.tokenId : undefined; await fetchMinaAccount({ publicKey, tokenId, force: false }); if (!Mina.hasAccount(publicKey, tokenId)) { throw new Error("Account does not exist"); } const account = Mina.getAccount(publicKey, tokenId); if (account.zkapp?.appState === undefined) { throw new Error("The account is not a zkApp"); } const tokenSymbol = account.tokenSymbol; const uri = account.zkapp?.zkappUri; const verificationKey = account.zkapp?.verificationKey?.data; const verificationKeyHash = account.zkapp?.verificationKey?.hash.toJSON(); const versionData = account.zkapp?.zkappVersion; const version = Number(versionData.toBigint()); const name = Object.keys(vk).find((key) => vk[key].hash === verificationKeyHash); const derivedTokenId = TokenId.derive(publicKey, tokenId); const info0 = { name: { type: "name", value: name ?? "unknown" }, address: { type: "address", value: publicKey.toBase58() }, tokenId: { type: "tokenId", value: TokenId.toBase58(tokenId ?? Field.from(1)), }, derivedTokenId: { type: "tokenId", value: TokenId.toBase58(derivedTokenId), }, symbol: { type: "symbol", value: tokenSymbol }, uri: { type: "uri", value: uri }, verificationKey: { type: "verificationKey", value: verificationKey ?? "" }, verificationKeyHash: { type: "verificationKeyHash", value: verificationKeyHash ?? "", }, zkappVersion: { type: "zkappVersion", value: version.toString() }, }; switch (name) { case "FungibleToken": case "AdvancedFungibleToken": { const zkApp = new FungibleToken(publicKey, tokenId); const admin = zkApp.admin.get(); const decimals = zkApp.decimals.get(); const paused = zkApp.paused.get(); const zkAppTokenId = zkApp.deriveTokenId(); if (TokenId.toBase58(zkAppTokenId) !== info0.derivedTokenId.value) { throw new Error("Derived tokenId does not match"); } await fetchMinaAccount({ publicKey, tokenId: zkAppTokenId, force: false, }); const totalSupply = Mina.getBalance(publicKey, zkAppTokenId).toBigInt(); info0.admin = { type: "address", value: admin.toBase58() }; info0.decimals = { type: "number", value: decimals.toNumber().toString(), }; info0.paused = { type: "boolean", value: paused.toBoolean().toString(), }; info0.totalSupply = { type: "bigint", value: totalSupply.toString(), presentation: formatBalanceInternal(Number(totalSupply / BigInt(10 ** decimals.toNumber()))), }; const info1 = await getContractInfo({ address: admin, parentTokenId: zkAppTokenId, decimals: decimals.toNumber(), chain, }); info.push(...info1); } break; case "FungibleTokenAdmin": { const adminAddress0 = account.zkapp?.appState[0]; const adminAddress1 = account.zkapp?.appState[1]; if (adminAddress0 === undefined || adminAddress1 === undefined) { throw new Error("Cannot fetch admin address from admin contract"); } const adminAddress = PublicKey.fromFields([ adminAddress0, adminAddress1, ]); let adminTokenBalance = 0n; if (parentTokenId) { try { await fetchMinaAccount({ publicKey: adminAddress, tokenId: parentTokenId, force: false, }); adminTokenBalance = Mina.getBalance(adminAddress, parentTokenId).toBigInt(); } catch (error) { } } info0.admin = { type: "address", value: adminAddress.toBase58() }; info0.adminTokenBalance = { type: "bigint", value: adminTokenBalance.toString(), presentation: formatBalanceInternal(Number(adminTokenBalance / BigInt(1 << (decimals ?? 9)))), }; } break; // TODO: add NFT and Bonding Curve } info.push(info0); return info; } function formatBalanceInternal(num) { const fixed = num.toFixed(2); return fixed.endsWith(".00") ? fixed.slice(0, -3) : fixed; } export async function tokenBalance(params) { const { tokenAddress, address } = params; if (!address || !checkAddress(address)) { throw new Error("Invalid address"); } if (tokenAddress && !checkAddress(tokenAddress)) { throw new Error("Invalid token address"); } const tokenContractPublicKey = tokenAddress ? PublicKey.fromBase58(tokenAddress) : undefined; const publicKey = PublicKey.fromBase58(address); const tokenIdDerived = tokenContractPublicKey ? TokenId.derive(tokenContractPublicKey) : undefined; if (tokenIdDerived && params.tokenId && TokenId.toBase58(tokenIdDerived) !== params.tokenId) { throw new Error("TokenId does not match tokenAddress"); } const tokenId = tokenIdDerived ?? (params.tokenId ? TokenId.fromBase58(params.tokenId) : undefined); try { await fetchMinaAccount({ publicKey, tokenId, force: false, }); return { tokenAddress, address, tokenId: tokenId ? TokenId.toBase58(tokenId) : undefined, hasAccount: Mina.hasAccount(publicKey, tokenId), balance: Mina.hasAccount(publicKey, tokenId) ? Number(Mina.getAccount(publicKey, tokenId).balance.toBigInt()) : undefined, }; } catch (error) { console.error("Cannot fetch account balance", params, error); return { tokenAddress, address, tokenId: tokenId ? TokenId.toBase58(tokenId) : undefined, hasAccount: undefined, balance: undefined, }; } } // export async function offerInfo( // params: OfferInfoRequest, // apiKeyAddress: string // ): Promise<ApiResponse<OfferInfo>> { // const { tokenAddress, offerAddress } = params; // try { // await initBlockchain(); // if (!offerAddress || !checkAddress(offerAddress)) { // return { // status: 400, // json: { error: "Invalid offer address" }, // }; // } // if (!tokenAddress || !checkAddress(tokenAddress)) { // return { // status: 400, // json: { error: "Invalid token address" }, // }; // } // const tokenContractPublicKey = PublicKey.fromBase58(tokenAddress); // const offerPublicKey = PublicKey.fromBase58(offerAddress); // const tokenId = TokenId.derive(tokenContractPublicKey); // await fetchMinaAccount({ // publicKey: offerPublicKey, // tokenId, // force: false, // }); // if (!Mina.hasAccount(offerPublicKey, tokenId)) { // return { // status: 400, // json: { error: "Offer account not found" }, // }; // } // const offer = new FungibleTokenOfferContract(offerPublicKey, tokenId); // const price = Number(offer.price.get().toBigInt()); // const amount = Number( // Mina.getAccount(offerPublicKey, tokenId).balance.toBigInt() // ); // const ownerAddress = offer.seller.get().toBase58(); // const offerInfo = await getOffer({ tokenAddress, offerAddress }); // if ( // offerInfo === null || // amount !== Number(offerInfo?.amount) || // price !== Number(offerInfo?.price) || // ownerAddress !== offerInfo?.ownerAddress // ) { // writeOffer({ // tokenAddress, // offerAddress, // amount, // price, // ownerAddress, // }); // } // return { // status: 200, // json: { // tokenAddress, // offerAddress, // ownerAddress, // amount, // price, // }, // }; // } catch (error) { // console.error("Cannot fetch offer info", params, error); // return { // status: 500, // json: { error: "Failed to get offer info" }, // }; // } // } // export async function bidInfo( // params: BidInfoRequest, // apiKeyAddress: string // ): Promise<ApiResponse<BidInfo>> { // const { tokenAddress, bidAddress } = params; // try { // await initBlockchain(); // if (!bidAddress || !checkAddress(bidAddress)) { // return { // status: 400, // json: { error: "Invalid bid address" }, // }; // } // if (!tokenAddress || !checkAddress(tokenAddress)) { // return { // status: 400, // json: { error: "Invalid token address" }, // }; // } // const tokenContractPublicKey = PublicKey.fromBase58(tokenAddress); // const bidPublicKey = PublicKey.fromBase58(bidAddress); // const tokenId = TokenId.derive(tokenContractPublicKey); // await fetchMinaAccount({ // publicKey: bidPublicKey, // force: false, // }); // if (!Mina.hasAccount(bidPublicKey)) { // return { // status: 400, // json: { error: "Bid account not found" }, // }; // } // const bid = new FungibleTokenBidContract(bidPublicKey); // const price = Number(bid.price.get().toBigInt()); // const amount = Number(Mina.getAccount(bidPublicKey).balance.toBigInt()); // const ownerAddress = bid.buyer.get().toBase58(); // const bidInfo = await getBid({ tokenAddress, bidAddress }); // if ( // bidInfo === null || // amount !== Number(bidInfo?.amount) || // price !== Number(bidInfo?.price) || // ownerAddress !== bidInfo?.ownerAddress // ) { // writeBid({ // tokenAddress, // bidAddress, // amount, // price, // ownerAddress, // }); // } // return { // status: 200, // json: { // tokenAddress, // bidAddress, // ownerAddress, // amount, // price, // }, // }; // } catch (error) { // console.error("Cannot fetch bid info", params, error); // return { // status: 500, // json: { error: "Failed to get bid info" }, // }; // } // } // class FungibleTokenState extends Struct({ // decimals: UInt8, // admin: PublicKey, // paused: Bool, // }) {} // const FungibleTokenStateSize = FungibleTokenState.sizeInFields(); // class FungibleTokenAdminState extends Struct({ // adminPublicKey: PublicKey, // }) {} // const FungibleTokenAdminStateSize = FungibleTokenAdminState.sizeInFields(); // export async function getTokenState(props: { // params: TokenInfoRequestParams; // name: ApiName; // apiKeyAddress: string; // }): Promise<ApiResponse<TokenState>> { // const { params, name, apiKeyAddress } = props; // const { tokenAddress } = params; // try { // await initBlockchain(); // if (!checkAddress(tokenAddress)) { // return { // status: 400, // json: { error: "Invalid token address" }, // }; // } // const tokenContractPublicKey = PublicKey.fromBase58(tokenAddress); // await fetchMinaAccount({ publicKey: tokenContractPublicKey, force: false }); // if (!Mina.hasAccount(tokenContractPublicKey)) { // return { // status: 400, // json: { error: "Token contract account not found" }, // }; // } // const tokenId = TokenId.derive(tokenContractPublicKey); // await fetchMinaAccount({ // publicKey: tokenContractPublicKey, // tokenId, // force: false, // }); // if (!Mina.hasAccount(tokenContractPublicKey, tokenId)) { // console.error( // "getTokenState: Token contract totalSupply account not found", // { // tokenAddress, // } // ); // return { // status: 400, // json: { error: "Token contract totalSupply account not found" }, // }; // } // const account = Mina.getAccount(tokenContractPublicKey); // if (account.zkapp?.appState === undefined) { // console.error("getTokenState: Token contract state not found", { // tokenAddress, // }); // return { // status: 400, // json: { error: "Token contract state not found" }, // }; // } // const state = FungibleTokenState.fromFields( // account.zkapp?.appState.slice(0, FungibleTokenStateSize) // ); // const adminContractPublicKey = state.admin; // const decimals = state.decimals.toNumber(); // const isPaused = state.paused.toBoolean(); // const totalSupply = // Number(Mina.getBalance(tokenContractPublicKey, tokenId).toBigInt()) / // 1_000_000_000; // const tokenSymbol = account.tokenSymbol; // const uri = account.zkapp?.zkappUri; // if (uri === undefined) { // console.error("getTokenState: Token uri not found", { // tokenAddress, // }); // return { // status: 400, // json: { error: "Token uri not found" }, // }; // } // const verificationKeyHash = account.zkapp?.verificationKey?.hash.toJSON(); // if (verificationKeyHash === undefined) { // console.error("getTokenState: Token verification key hash not found", { // tokenAddress, // }); // return { // status: 400, // json: { error: "Token verification key hash not found" }, // }; // } // const versionData = account.zkapp?.zkappVersion; // if (versionData === undefined) { // console.error("getTokenState: Token contract version not found", { // tokenAddress, // }); // return { // status: 400, // json: { error: "Token contract version not found" }, // }; // } // const version = Number(versionData.toBigint()); // await fetchMinaAccount({ publicKey: adminContractPublicKey, force: false }); // if (!Mina.hasAccount(adminContractPublicKey)) { // console.error("getTokenState: Admin contract account not found", { // tokenAddress, // }); // return { // status: 400, // json: { error: "Admin contract account not found" }, // }; // } // const adminContract = Mina.getAccount(adminContractPublicKey); // const adminTokenSymbol = adminContract.tokenSymbol; // const adminUri = adminContract.zkapp?.zkappUri; // const adminVerificationKeyHash = // adminContract.zkapp?.verificationKey?.hash.toJSON(); // if (adminVerificationKeyHash === undefined) { // console.error( // "getTokenState: Admin contract verification key hash not found", // { // adminContractPublicKey: adminContractPublicKey.toBase58(), // } // ); // return { // status: 400, // json: { error: "Admin contract verification key hash not found" }, // }; // } // const adminVersionData = adminContract.zkapp?.zkappVersion; // if (adminVersionData === undefined) { // console.error("getTokenState: Admin contract version not found", { // adminContractPublicKey: adminContractPublicKey.toBase58(), // }); // return { // status: 400, // json: { error: "Admin contract version not found" }, // }; // } // const adminVersion = Number(adminVersionData.toBigint()); // const adminAddress0 = adminContract.zkapp?.appState[0]; // const adminAddress1 = adminContract.zkapp?.appState[1]; // if (adminAddress0 === undefined || adminAddress1 === undefined) { // console.error("Cannot fetch admin address from admin contract"); // return { // status: 400, // json: { error: "Cannot fetch admin address from admin contract" }, // }; // } // const adminAddress = PublicKey.fromFields([adminAddress0, adminAddress1]); // let adminTokenBalance = 0; // try { // await fetchMinaAccount({ // publicKey: adminAddress, // tokenId, // force: false, // }); // adminTokenBalance = Number( // Mina.getBalance(adminAddress, tokenId).toBigInt() // ); // } catch (error) {} // const tokenState: TokenState = { // tokenAddress: tokenContractPublicKey.toBase58(), // tokenId: TokenId.toBase58(tokenId), // adminContractAddress: adminContractPublicKey.toBase58(), // adminAddress: adminAddress.toBase58(), // adminTokenBalance, // totalSupply, // isPaused, // decimals, // tokenSymbol, // verificationKeyHash, // uri, // version, // adminTokenSymbol, // adminUri: adminUri ?? "", // adminVerificationKeyHash, // adminVersion, // }; // const updated = await updateTokenInfo({ // tokenAddress, // tokenState, // }); // if (updated) { // console.log("getTokenState: Updated token info", { // tokenAddress, // symbol: tokenState.tokenSymbol, // }); // } // return { // status: 200, // json: tokenState, // }; // } catch (error: any) { // console.error("getTokenState catch", error); // return { // status: 503, // json: { // error: // "getTokenState error:" + // (error?.message ?? (error ? String(error) : "unknown error")), // }, // }; // } // } //# sourceMappingURL=info.js.map