UNPKG

@silvana-one/mina-utils

Version:
161 lines 8.26 kB
import { checkZkappTransaction } from "o1js"; import { sleep } from "../utils/utils.js"; import { fetchMinaAccount } from "../utils/fetch.js"; import { getCurrentNetwork } from "../utils/mina.js"; /** * The function `sendTx` sends a transaction, checks account updates, and waits for * confirmation on the blockchain. * @param params The parameters object * @param params.tx The transaction to send * @param params.description A description of the transaction * @param params.verbose Whether to log verbose information * @param params.wait Whether to wait for the transaction to be included in a block * @param params.chain The blockchain to send the transaction on * @returns The `sendTx` function returns a `Mina.IncludedTransaction`, `Mina.PendingTransaction`, * `Mina.RejectedTransaction`, or `undefined` if there was an error during the process. */ export async function sendTx(params) { const { tx, description = "", verbose = true, wait = true, chain = getCurrentNetwork().network.chainId, delay = chain === "zeko:testnet" || chain === "zeko:alphanet" || chain === "mina:lightnet" ? 5000 : 60000, retry = 30, } = params; // flatten accountUpdates const accountUpdates = JSON.parse(tx.toJSON()).accountUpdates; const auCount = []; let proofAuthorizationCount = 0; // Calculate the number of account updates for each { publicKey, tokenId } for (const au of accountUpdates) { const { publicKey, tokenId, authorizationKind } = au.body; if (au.authorization.proof) { proofAuthorizationCount++; if (authorizationKind.isProved === false) console.error("Proof authorization exists but isProved is false"); } else if (authorizationKind.isProved === true) console.error("isProved is true but no proof authorization"); const index = auCount.findIndex((item) => item.publicKey === publicKey && item.tokenId === tokenId); if (index === -1) auCount.push({ publicKey, tokenId, count: 1 }); else auCount[index].count++; } if (verbose) console.log(`Account updates for ${description ?? "tx"}: ${auCount.length}, proof authorizations: ${proofAuthorizationCount}`); for (const au of auCount) { if (au.count > 1) if (verbose) console.log(`DUPLICATE AU ${description ?? ""}: ${au.publicKey} ${au.tokenId !== "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf" ? "tokenId: " + au.tokenId : ""} count: ${au.count}`); } try { let txSent; let sent = false; let attempt = 1; while (!sent && attempt <= retry) { txSent = await tx.safeSend(); if (txSent.status === "pending") { sent = true; if (verbose) console.log(`${description ?? ""} tx sent: hash: ${txSent.hash} status: ${txSent.status}`); } else if (chain !== "mina:local") { attempt++; console.error(`${description} tx NOT sent: hash: ${txSent?.hash} status: ${txSent?.status}, errors: ${String(txSent.errors ?? "")}`); if (verbose) console.log(`Retrying ${chain} tx, retry:`, attempt); await sleep(10000 * attempt); } else attempt = retry + 1; // local chain - do not retry } if (txSent === undefined) throw new Error("txSent is undefined"); if ((txSent.errors?.length ?? 0) > 0) { console.error(`${description ?? ""} tx error: hash: ${txSent.hash} status: ${txSent.status} errors: ${String(txSent.errors ?? "")}`); } if (txSent.status === "pending" && wait !== false && chain !== "zeko:testnet" && chain !== "zeko:alphanet") { if (verbose) console.log(`Waiting for tx inclusion...`); let txIncluded = await txSent.safeWait(); if (txIncluded.status !== "included") { console.error(`${description ?? ""} tx NOT included into block: hash: ${txIncluded.hash} status: ${txIncluded.status}, errors: ${String(txIncluded.errors ?? "")}`); } if (chain !== "mina:local") { // we still wait for the tx to be included in the block by checking the nonce // even in the case of tx NOT included // because the tx might still be included in the block in the case of devnet instability const { publicKey, nonce } = tx.transaction.feePayer.body; const started = Date.now(); while (Date.now() - started < 1000 * 60 * 10) { const newNonce = (await fetchMinaAccount({ publicKey, force: true, })).account?.nonce; if (newNonce && Number(newNonce.toBigint()) > Number(nonce.toBigint())) { let txIncluded = await txSent.safeWait(); if (txIncluded.status === "included") { if (verbose) console.log(`${description ?? ""} tx included into block: hash: ${txIncluded.hash} status: ${txIncluded.status}`); if (delay > 0) await sleep(delay); return txIncluded; } else if (txIncluded.status === "rejected") { // We never should see this error as the nonce is updated (except when tx failed due to a preconditions), // so we retry to check if the tx is included console.error(`tx rejected: ${chain}: hash: ${txIncluded.hash} status: ${txIncluded.status} errors: ${txIncluded.errors}`); await sleep(30000); txIncluded = await txSent.safeWait(); if (txIncluded.status === "included") { if (verbose) console.log(`${description ?? ""} tx included into block: hash: ${txIncluded.hash} status: ${txIncluded.status}`); if (delay > 0) await sleep(delay); return txIncluded; } console.error(`tx failed: ${chain}: hash: ${txIncluded.hash} status: ${txIncluded.status} errors: ${txIncluded.errors}`); return txIncluded; } } if (verbose) console.log(`Waiting for ${chain} to update state for ${Math.floor((Date.now() - started) / 1000)} sec...`); await sleep(30000); } // finally, if the tx is still not included, show an error console.error(`${chain} do not reflect nonce update for tx ${txIncluded.hash} with status ${txIncluded.status}`); } if (verbose) console.log(`${description ?? ""} tx included into block: hash: ${txIncluded.hash} status: ${txIncluded.status}`); return txIncluded; } else return txSent; } catch (error) { if (chain !== "zeko:testnet") console.error("Error sending tx", error); } } export async function getTxStatusFast(params) { const { hash, chain = getCurrentNetwork().network.chainId } = params; if (chain === "mina:local" || chain === "zeko:testnet") return { success: true, result: true }; try { const txStatus = await checkZkappTransaction(hash); return { success: true, result: txStatus?.success ?? false, }; } catch (error) { console.error("getTxStatusFast error while getting tx status - catch", hash, error); return { success: false, error: error?.message ?? "Cannot get tx status" }; } } //# sourceMappingURL=send.js.map