import { BN } from "@project-serum/anchor"; import { PublicKey } from "@solana/web3.js"; import { ActionVertexInfo, ActionVertexInfoIsolated, BNIsh, errors, EstimateOutFN, IsolatedAction, IsolatedActionWithNonSpecific, MintAndTokenAccount, MintWithAmount, NonSpecificConstruction, NonSpecificConstructionFilledUnserializable, } from "../index"; import { splitTokensByFractions } from "../utils/tokens"; const updateEstimatedOutsForAction = ( currEstimatesOut: BN[], actionOutEstimate: MintWithAmount[], action: IsolatedAction ): BN[] => { const orderIdx = actionOutEstimate.map((a) => { return action.tokenMintOuts.findIndex( (mint) => mint.toBase58() === a.mint.toBase58() ); }); return orderIdx.map((outIdx, i) => { if (outIdx === -1) { throw `Expected the action's out estimate to have an estimate for all mints which the action will return`; } return currEstimatesOut[outIdx].add(new BN(actionOutEstimate[i].amount)); }); }; const updateEstimatedIns = ( currEstimatesIn: BN[][], action: ActionVertexInfoIsolated, actionOutEstimate: MintWithAmount[], inMintsPerAction: MintAndTokenAccount[][] ): { newIn: BN[][] } => { if (action.nextNodes.length === 0) return { newIn: currEstimatesIn }; if (action.nextNodes.length !== actionOutEstimate.length) { throw `Expected the number of estimates out to equal or be greater than the number of next mints if there is a continuation from the action`; } // Clone the arrays const newEstimatesIn = [...currEstimatesIn.map((est) => [...est])]; actionOutEstimate.forEach((estimate, i) => { const splits = action.nextNodes[i].map((nextNode) => nextNode.fraction); // You cannot simply use the next input mint idx of next node here as // there is no guarantee on the ordering of estimateOuts const nextMintIdx = action.nextNodes[i].map((nextNode, i) => { const nextMintIdx = inMintsPerAction[nextNode.actionIdx].findIndex( (m) => m.mint.toBase58() === estimate.mint.toBase58() ); if (nextMintIdx === -1) throw `Expected the action's out estimate to have an estimate for all mints which feed into next actions`; return nextMintIdx; }); const nextActionIdxs = action.nextNodes[i].map( (nextNode) => nextNode.actionIdx ); const nextEstimates = splitTokensByFractions( new BN(estimate.amount), splits ); nextActionIdxs.forEach((actionIdx, i) => { const nextMintIdxForNextAction = nextMintIdx[i]; newEstimatesIn[actionIdx][nextMintIdxForNextAction] = newEstimatesIn[ actionIdx ][nextMintIdxForNextAction].add(nextEstimates[i]); }); }); return { newIn: newEstimatesIn }; }; const getInitEstimatedAmountsIn = ( numbActions: number, construction: NonSpecificConstruction, initActionIdxs: number[], initMints: PublicKey[], vertexInfos: ActionVertexInfoIsolated[], inMintsPerAction: MintAndTokenAccount[][] ): BN[][] => { // The outer index corresponds to the action index, the inner to the different values on // the input mints let estimatedAmountsOnAction: BN[][] = Array(numbActions) .fill(0) .map((_, i) => Array(vertexInfos[i].numberInputMints).fill(new BN(0))); const amounts = Object.values(construction.source.mints); console.log(amounts); const initialSplitVals = amounts.map((amountForToken) => splitTokensByFractions( new BN(amountForToken), Object.values(construction.source.nextActions) ) ); initialSplitVals.forEach((splitValByToken, mintIdx) => { const mint = initMints[mintIdx]; initActionIdxs.forEach((actionIdx, i) => { const mintIdxForAction = inMintsPerAction[actionIdx].findIndex( (m) => m.mint.toBase58() === mint.toBase58() ); if (mintIdxForAction === -1) { throw `Unexpected error: expected to find the initial mint indices in the init mints for an action`; } estimatedAmountsOnAction[actionIdx][mintIdxForAction] = estimatedAmountsOnAction[actionIdx][mintIdxForAction].add( splitValByToken[i] ); }); }); return estimatedAmountsOnAction; }; const getInitEstimatedAmountsOut = (isolatedActions: IsolatedAction[]) => { let estimatedAmountsOnAction: BN[][] = Array(isolatedActions.length) .fill(0) .map((_, i) => Array(isolatedActions[i].tokenMintOuts.length).fill(new BN(0)) ); return estimatedAmountsOnAction; }; export type EstimateRet = { estimateIns: MintWithAmount[][]; estimateOuts: MintWithAmount[][]; }; export const getConstructionEstimates = async ( order: number[][], actionsIsolated: IsolatedActionWithNonSpecific[], construction: | NonSpecificConstruction | NonSpecificConstructionFilledUnserializable, initActionIdxs: number[], initMints: PublicKey[], actionVertexInfo: ActionVertexInfoIsolated[], estimateFns: EstimateOutFN[], inMintsPerAction: MintAndTokenAccount[][] ): Promise => { let estimatedAmountOuts = getInitEstimatedAmountsOut(actionsIsolated); let estimatedAmountIn = getInitEstimatedAmountsIn( actionsIsolated.length, construction, initActionIdxs, initMints, actionVertexInfo, inMintsPerAction ); const estimateArrInToMintWithAmount = ( estimates: BN[], actionIdx: number ): MintWithAmount[] => { return estimates.map((e, i) => { return { mint: inMintsPerAction[actionIdx][i].mint, amount: e, }; }); }; const estimateArrOutToMintWithAmount = ( estimates: BN[], actionIdx: number ): MintWithAmount[] => { return estimates.map((e, i) => { return { mint: actionsIsolated[actionIdx].tokenMintOuts[i], amount: e, }; }); }; for (let o = 0; o < order.length; o++) { const orderGroup = order[o]; for (let q = 0; q < orderGroup.length; q++) { const actionIdx = orderGroup[q]; const action = actionsIsolated[actionIdx]; try { const out = await estimateFns[actionIdx]( estimateArrInToMintWithAmount(estimatedAmountIn[actionIdx], actionIdx) ); const { newIn } = updateEstimatedIns( estimatedAmountIn, actionVertexInfo[actionIdx], out, inMintsPerAction ); estimatedAmountIn = newIn; estimatedAmountOuts[actionIdx] = updateEstimatedOutsForAction( estimatedAmountOuts[actionIdx], out, actionsIsolated[actionIdx] ); } catch (e) { console.error(e); throw errors.newActionBuilderError( action.nonSpecificAction.actionTypeUID, action.actionName, actionIdx, e ); } } } return { estimateOuts: estimatedAmountOuts.map((est, i) => estimateArrOutToMintWithAmount(est, i) ), estimateIns: estimatedAmountIn.map((est, i) => estimateArrInToMintWithAmount(est, i) ), }; };