import { Provider, web3 } from "@project-serum/anchor"; import { SendTxRequest } from "@project-serum/anchor/dist/provider"; import { newRunError } from "../error"; import { Construction, ConstructionCompiled, RunConstructionOpts, TransactionType, TxRequestsTyped, } from "../interfaces"; import { getMainActionTxIndex } from "../tx-compiler/actions"; import { SendAllError, sendAllTypedWithError } from "../utils/custom-send-all"; const onlyTxsAfterActionInclusive = ( txs: TxRequestsTyped[], actionName: string, construction: Construction ) => { const actionCallIdxs = txs.findIndex((t) => t.type === "action calls"); if (actionCallIdxs === -1) throw `Expected action call transactions in the construction`; const actionsAndAfter = txs.slice(actionCallIdxs); // Action transactions are double arrays where the outer corresponds to the action/ action group // and the inner to all the txs associated with the action/group const actionTxs = actionsAndAfter[0].txs as SendTxRequest[][]; const actionIdx = construction.actions.findIndex( (a) => a.actionName === actionName ); if (actionIdx === -1) throw `Could not find action ${actionName} in the construction`; const actionTxIdx = construction.actionOrders.findIndex((orderSet) => orderSet.includes(actionIdx) ); if (actionTxIdx === -1) throw `Could not find ${actionName} in the construction's hit order`; const actionTxsShorted = actionTxs.slice(actionTxIdx); actionsAndAfter[0] = { txs: actionTxsShorted, type: "action calls" }; return { actionsAndAfter, actionOrderOffset: actionTxIdx }; }; export const runConstruction = async ( provider: Provider, construction: ConstructionCompiled, options?: RunConstructionOpts, fromActionName?: string ) => { let txsToRun: TxRequestsTyped[] = construction.txs; let actionOrderOffsetIdx = 0; if (fromActionName) { const { actionsAndAfter, actionOrderOffset } = onlyTxsAfterActionInclusive( construction.txs, fromActionName, construction.construction ); txsToRun = actionsAndAfter; actionOrderOffsetIdx = actionOrderOffset; } const commitment: web3.ConfirmOptions = { preflightCommitment: "confirmed", commitment: "confirmed", skipPreflight: false, ...(options?.commitment || {}), }; try { return await sendAllTypedWithError( provider, txsToRun, construction.construction, { ...options, commitment, }, actionOrderOffsetIdx ); } catch (e) { const eCast = e as SendAllError; if (eCast.txGroup === undefined) throw newRunError( e, construction.actionMap, commitment.skipPreflight, construction.construction.actions, "unknown" ); // Only count an action call as failed if the failure comes from the calling the action itself let actionIdx: number | undefined; let isMainActionCall = false; if (txsToRun[eCast.txGroup].type === "action calls") { const actionIdxInOrder = actionOrderOffsetIdx + eCast.txFailed.txIdx; const numbPrependedInstsPerActions = construction.construction.actionOrders[actionIdxInOrder].map( (actionIdx) => construction.construction.actions[actionIdx].opts?.instructions ?.length ?? 0 ); const sumOfPrependedInst = numbPrependedInstsPerActions.reduce( (p, v) => p + v, 0 ); // The transaction failed at a prepended instruction // TODO: test if (sumOfPrependedInst > eCast.txFailed.instrIdx) { isMainActionCall = false; let runningSum = 0; for (let i = 0; i < numbPrependedInstsPerActions.length; i++) { runningSum += numbPrependedInstsPerActions[i]; if (eCast.txFailed.instrIdx < runningSum) { actionIdx = construction.construction.actionOrders[actionIdxInOrder][i]; break; } } } else { actionIdx = construction.construction.actionOrders[actionIdxInOrder][ eCast.txFailed.instrIdx - sumOfPrependedInst ]; if ( getMainActionTxIndex(construction.construction.actions[actionIdx]) === eCast.txInner ) { isMainActionCall = true; } } } throw newRunError( eCast.err, construction.actionMap, commitment.skipPreflight, construction.construction.actions, txsToRun[eCast.txGroup].type, actionIdx, isMainActionCall ); } };