import MALLOC_IDL from "./idls/malloc.json"; import * as BufferLayout from "buffer-layout"; import { BN, Program, Provider, Wallet, web3 } from "@project-serum/anchor"; import { NodeWallet, SendTxRequest } from "@project-serum/anchor/dist/provider"; import { TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token"; import { Signer, PublicKey, Cluster } from "@solana/web3.js"; import { BuildActionMap, Construction } from "./interfaces"; import { BNIsh, BuildEphemeralOpts, CleanupOpts, ConstructionCompiled, ConstructionInstr, ConstructionOnChainSerialized, errors, MintWithAmount, NonSpecificConstruction, NonSpecificConstructionFilled, RunConstructionOpts, } from "."; import { compileConstruction } from "./tx-compiler"; import { cleanup } from "./tx-compiler/cleanup"; import { runConstruction } from "./runner"; import { buildConstructionFromNonSpecific, buildConstructionFromNonSpecificWithFilled, nspFilledUnserialToSerial, } from "./builder"; import { MallocRunnerError } from "./interfaces/errors"; import { newCleanupError } from "./error"; export class MallocSdk { private readonly mallocProgram: Program; private readonly MALLOC_PROGRAM_ID: PublicKey; constructor( _MALLOC_PROGRAM_ID: PublicKey | string, private readonly provider: Provider, private readonly cluster: Cluster ) { this.MALLOC_PROGRAM_ID = new PublicKey(_MALLOC_PROGRAM_ID); this.mallocProgram = new Program( MALLOC_IDL as any, this.MALLOC_PROGRAM_ID, provider ); } public async buildWithFill( nonspecific: NonSpecificConstruction, buildActionMap: BuildActionMap, buildCompileOpts?: BuildEphemeralOpts ): Promise<{ constructionBuilt: Construction; filledNonSpecific: NonSpecificConstructionFilled; }> { const ret = await buildConstructionFromNonSpecificWithFilled( nonspecific, buildActionMap, this.provider, { cluster: this.cluster }, { authority: buildCompileOpts?.authority?.publicKey, preferredTokenAccounts: buildCompileOpts?.preferredTokenAccounts, } ); return { constructionBuilt: ret.constructionBuilt, filledNonSpecific: nspFilledUnserialToSerial(ret.filledNonSpecific), }; } public async build( nonspecific: NonSpecificConstruction, buildActionMap: BuildActionMap, buildCompileOpts?: BuildEphemeralOpts ): Promise { return await buildConstructionFromNonSpecific( nonspecific, buildActionMap, this.provider, { cluster: this.cluster }, { authority: buildCompileOpts?.authority?.publicKey, preferredTokenAccounts: buildCompileOpts?.preferredTokenAccounts, } ); } public async buildCompileRun( nonspecific: NonSpecificConstruction, inputTokenAccounts: PublicKey[], buildActionMap: BuildActionMap, buildCompileOpts?: BuildEphemeralOpts, runOpts?: RunConstructionOpts ): Promise { const builtConstruction = await buildConstructionFromNonSpecific( nonspecific, buildActionMap, this.provider, { cluster: this.cluster }, { authority: buildCompileOpts?.authority?.publicKey, preferredTokenAccounts: buildCompileOpts?.preferredTokenAccounts, } ); console.log("construction built"); const compiled = await this.compileConstruction({ construction: builtConstruction, actionMap: buildActionMap, amountInAccounts: inputTokenAccounts, opts: buildCompileOpts, }); return await this.runConstruction(compiled, runOpts); } public getErrorInfo( construction: Construction, error: MallocRunnerError ): { successful: string[]; errored?: string; notRan: string[]; } { if (!error.actionIdx) { return { successful: [], notRan: construction.actions.map((a) => a.actionName), }; } const idx = error.actionIdx; return { successful: construction.actions.slice(0, idx).map((a) => a.actionName), errored: construction.actions[idx].actionName, notRan: idx < construction.actions.length - 1 ? construction.actions.slice(idx + 1).map((s) => s.actionName) : [], }; } public async compileConstruction( inst: ConstructionInstr ): Promise { return await compileConstruction(this.provider, this.mallocProgram, inst); } async getConstructionState( data: ConstructionCompiled ): Promise { const constructionState: any = (await this.mallocProgram.account.construction.fetch( data.constructionSigner.publicKey )) as any; const actionSerialize = (a: any) => { return { inputTokenAccount: a.inputTokenAccount ? new PublicKey(a.inputTokenAccount).toBase58() : undefined, inDegree: a.inDegree, nextNodes: a.nextNodes.map((o) => o.map((nextNode) => { return { actionIdx: nextNode.actionIdx, fraction: new BN(nextNode.fraction).toString(), nextInputMintIdx: nextNode.nextInputMintIdx, }; }) ), }; }; const actionCallSerialize = (a: any) => { return { numbTimesHit: a.numbTimesHit, callAmounts: a.callAmounts.map((callAmount) => new BN(callAmount).toString() ), status: Object.keys(a.status)[0] }; }; return { authority: new PublicKey(constructionState.authority).toBase58(), actions: constructionState.actions.map(actionSerialize), actionCalls: constructionState.actionCalls.map(actionCallSerialize), }; } /** * Close the ephemeral accounts such that the caller reclaims more rent then * is spent on the transaction of closing the account. * * As a note: this may mean that result accounts are not closed as they have next to * no rent */ async cleanup(constructionData: ConstructionCompiled, options?: CleanupOpts) { const txs = await cleanup( this.provider, this.mallocProgram, constructionData.construction, constructionData.constructionSigner, constructionData.authority, options ); try { await this.provider.sendAll(txs, options?.commitment); } catch (e) { throw newCleanupError(e, options?.commitment?.skipPreflight ?? false); } } async runConstruction( construction: ConstructionCompiled, options?: RunConstructionOpts ): Promise { return await runConstruction(this.provider, construction, options); } /** * Run a construction starting with the transaction for a specific action */ async runConstructionFromAction( construction: ConstructionCompiled, actionName: string, options?: RunConstructionOpts ): Promise { return await runConstruction(this.provider, construction, options, actionName); } getEstimates( builtConstruction: Construction, actionName: string ): { estimateIns: MintWithAmount[]; estimateOuts: MintWithAmount[]; } { const action = builtConstruction.actions.find( (act) => act.actionName === actionName ); if (!action) throw `Could not find the action ${actionName}`; const mapToBN = (e: MintWithAmount) => { return { mint: e.mint, amount: new BN(e.amount) } as MintWithAmount< BN, PublicKey >; }; return { estimateIns: action.estimateIns.map(mapToBN), estimateOuts: action.estimateOuts.map(mapToBN), }; } }