import { PublicKey, AccountMeta } from '@solana/web3.js'; import { createPartlyRevertEscrowSettlementPreparationInstruction } from '@convergence-rfq/rfq'; import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { SendAndConfirmTransactionResponse } from '../../rpcModule'; import { Operation, OperationHandler, OperationScope, useOperation, makeConfirmOptionsFinalizedOnMainnet, } from '../../../types'; import { Convergence } from '../../../Convergence'; import { TransactionBuilder, TransactionBuilderOptions, } from '../../../utils/TransactionBuilder'; import { InstrumentPdasClient } from '../../instrumentModule'; import { AuthoritySide, toSolitaAuthoritySide } from '../models/AuthoritySide'; import { legToBaseAssetMint } from '@/plugins/instrumentModule'; import { addComputeBudgetIxsIfNeeded } from '@/utils/helpers'; const Key = 'PartlyRevertSettlementPreparationOperation' as const; /** * Partially reverts settlement preparations. * * ```ts * const rfq = await convergence * .rfqs() * .partlyRevertSettlementPreparation({ * rfq: rfq.address, * response: rfqResponse.address, * side: AuthoritySide.Maker, * legAmountToRevert: 3 * }); * ``` * * @group Operations * @category Constructors */ export const partlyRevertSettlementPreparationOperation = useOperation(Key); /** * @group Operations * @category Types */ export type PartlyRevertSettlementPreparationOperation = Operation< typeof Key, PartlyRevertSettlementPreparationInput, PartlyRevertSettlementPreparationOutput >; /** * @group Operations * @category Inputs */ export type PartlyRevertSettlementPreparationInput = { /** * The protocol address. * @defaultValue `convergence.protocol().pdas().protocol()` */ protocol?: PublicKey; /** The Rfq address. */ rfq: PublicKey; /** The response address. */ response: PublicKey; /* * Args */ /** * The side (Maker or Taker) that is partly reverting * settlement preparation. */ side: AuthoritySide; /** The number of legs to revert settlement preparation for. */ legAmountToRevert: number; }; /** * @group Operations * @category Outputs */ export type PartlyRevertSettlementPreparationOutput = { /** The blockchain response from sending and confirming the transaction. */ response: SendAndConfirmTransactionResponse; }; /** * @group Operations * @category Handlers */ export const partlyRevertSettlementPreparationOperationHandler: OperationHandler = { handle: async ( operation: PartlyRevertSettlementPreparationOperation, convergence: Convergence, scope: OperationScope ): Promise => { const builder = await partlyRevertSettlementPreparationBuilder( convergence, { ...operation.input, }, scope ); scope.throwIfCanceled(); const confirmOptions = makeConfirmOptionsFinalizedOnMainnet( convergence, scope.confirmOptions ); const output = await builder.sendAndConfirm(convergence, confirmOptions); scope.throwIfCanceled(); return output; }, }; export type PartlyRevertSettlementPreparationBuilderParams = PartlyRevertSettlementPreparationInput; /** * Partially reverts settlement preparations * * ```ts * const transactionBuilder = await convergence * .rfqs() * .builders() * .partlyRevertSettlementPreparation(); * ``` * * @group Transaction Builders * @category Constructors */ export const partlyRevertSettlementPreparationBuilder = async ( convergence: Convergence, params: PartlyRevertSettlementPreparationBuilderParams, options: TransactionBuilderOptions = {} ): Promise => { const { programs, payer = convergence.rpc().getDefaultFeePayer() } = options; const rfqProgram = convergence.programs().getRfq(programs); const { rfq, response, side, legAmountToRevert } = params; const anchorRemainingAccounts: AccountMeta[] = []; const rfqModel = await convergence.rfqs().findRfqByAddress({ address: rfq }); const responseModel = await convergence .rfqs() .findResponseByAddress({ address: response }); if ( responseModel.model !== 'escrowResponse' || rfqModel.model !== 'escrowRfq' ) { throw new Error('Response is not settled as an escrow!'); } const sidePreparedLegs: number = side === 'taker' ? parseInt(responseModel.takerPreparedLegs.toString()) : parseInt(responseModel.makerPreparedLegs.toString()); const startIndex = sidePreparedLegs - legAmountToRevert; for (let i = startIndex; i < sidePreparedLegs; i++) { const instrumentEscrowPda = new InstrumentPdasClient( convergence ).instrumentEscrow({ response, index: i, rfqModel, }); const instrumentProgramAccount: AccountMeta = { pubkey: rfqModel.legs[i].getProgramId(), isSigner: false, isWritable: false, }; const leg = rfqModel.legs[i]; const baseAssetMint = await legToBaseAssetMint(convergence, leg); const legAccounts: AccountMeta[] = [ //`escrow` { pubkey: instrumentEscrowPda, isSigner: false, isWritable: true, }, // `receiver_tokens` { pubkey: convergence .tokens() .pdas() .associatedTokenAccount({ mint: baseAssetMint!.address, owner: side === 'maker' ? responseModel.maker : rfqModel.taker, programs, }), isSigner: false, isWritable: true, }, { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, ]; anchorRemainingAccounts.push(instrumentProgramAccount, ...legAccounts); } const txBuilder = TransactionBuilder.make() .setFeePayer(payer) .add({ instruction: createPartlyRevertEscrowSettlementPreparationInstruction( { protocol: convergence.protocol().pdas().protocol(), rfq, response, anchorRemainingAccounts, }, { side: toSolitaAuthoritySide(side), legAmountToRevert, }, rfqProgram.address ), signers: [], key: 'partlyRevertSettlementPreparation', }); await addComputeBudgetIxsIfNeeded(txBuilder, convergence); return txBuilder; };