From 6c2c3d0fc1f035b2cbed08feddc66bf23b8351ca Mon Sep 17 00:00:00 2001 From: Yaroslav Khodakovskij Date: Mon, 18 Sep 2023 19:08:58 +0300 Subject: [PATCH] Add support for print trade settlement --- .../printTrade.ts | 3 + .../src/plugins/printTradeModule/methods.ts | 33 +--- .../js/src/plugins/printTradeModule/types.ts | 4 + .../operations/createPrintTradeRfq.ts | 7 +- .../partiallySettleLegsAndSettle.ts | 24 ++- .../operations/preparePrintTradeSettlement.ts | 14 +- .../plugins/rfqModule/operations/settle.ts | 181 +++++++++++++----- packages/js/tests/unit/hxro.spec.ts | 4 + 8 files changed, 177 insertions(+), 93 deletions(-) diff --git a/packages/js/src/plugins/hxroPrintTradeProviderModule/printTrade.ts b/packages/js/src/plugins/hxroPrintTradeProviderModule/printTrade.ts index 75eab9cc6..f820b3d86 100644 --- a/packages/js/src/plugins/hxroPrintTradeProviderModule/printTrade.ts +++ b/packages/js/src/plugins/hxroPrintTradeProviderModule/printTrade.ts @@ -238,6 +238,9 @@ export class HxroPrintTrade implements PrintTrade { }, ]; }; + getSettlementAccounts = async () => { + return []; + }; // after an rfq is parsed from an on-chain data, as much data is possible is parsed from there // but some product info is missing in the rfq on-chain data diff --git a/packages/js/src/plugins/printTradeModule/methods.ts b/packages/js/src/plugins/printTradeModule/methods.ts index 683f40897..89712ba05 100644 --- a/packages/js/src/plugins/printTradeModule/methods.ts +++ b/packages/js/src/plugins/printTradeModule/methods.ts @@ -1,11 +1,6 @@ import { Leg, QuoteAsset, legBeet } from '@convergence-rfq/rfq'; import { AccountMeta } from '@solana/web3.js'; -import { - AuthoritySide, - toSolitaLegSide, - PrintTradeResponse, - PrintTradeRfq, -} from '../rfqModule/models'; +import { toSolitaLegSide } from '../rfqModule/models'; import { toNumberInstrumentType } from '../riskEngineModule/models'; import { PrintTrade, PrintTradeLeg, PrintTradeQuote } from './types'; import { addDecimals } from '@/utils'; @@ -53,27 +48,9 @@ export function getPrintTradeProgramAccount( }; } -export async function getPrintTradeValidationAccounts( - printTrade: PrintTrade -): Promise { - return [getPrintTradeProgramAccount(printTrade)].concat( - await printTrade.getValidationAccounts() - ); -} - -export async function getSettlementPreparationAccounts( +export function prependWithProviderProgram( printTrade: PrintTrade, - rfq: PrintTradeRfq, - response: PrintTradeResponse, - side: AuthoritySide, - additionalInfo: any -): Promise { - return [getPrintTradeProgramAccount(printTrade)].concat( - await printTrade.getSettlementPreparationAccounts( - rfq, - response, - side, - additionalInfo - ) - ); + accounts: AccountMeta[] +): AccountMeta[] { + return [getPrintTradeProgramAccount(printTrade)].concat(accounts); } diff --git a/packages/js/src/plugins/printTradeModule/types.ts b/packages/js/src/plugins/printTradeModule/types.ts index a5cb9c314..ffbbc2509 100644 --- a/packages/js/src/plugins/printTradeModule/types.ts +++ b/packages/js/src/plugins/printTradeModule/types.ts @@ -24,6 +24,10 @@ export interface PrintTrade { side: AuthoritySide, additionalParams: any ) => Promise; + getSettlementAccounts: ( + rfq: PrintTradeRfq, + response: PrintTradeResponse + ) => Promise; } export interface PrintTradeLeg { diff --git a/packages/js/src/plugins/rfqModule/operations/createPrintTradeRfq.ts b/packages/js/src/plugins/rfqModule/operations/createPrintTradeRfq.ts index 30164e894..72c69ca00 100644 --- a/packages/js/src/plugins/rfqModule/operations/createPrintTradeRfq.ts +++ b/packages/js/src/plugins/rfqModule/operations/createPrintTradeRfq.ts @@ -38,7 +38,7 @@ import { serializePrintTradeAsSolitaLeg, printTradeToSolitaLeg, printTradetoSolitaQuote, - getPrintTradeValidationAccounts, + prependWithProviderProgram, } from '@/plugins/printTradeModule'; const Key = 'CreatePrintTradeRfqOperation' as const; @@ -307,7 +307,10 @@ export const validateRfqByPrintTradeProviderBuilder = async ( const rfqProgram = convergence.programs().getRfq(programs); - const validationAccounts = await getPrintTradeValidationAccounts(printTrade); + const validationAccounts = prependWithProviderProgram( + printTrade, + await printTrade.getValidationAccounts() + ); return TransactionBuilder.make() .setFeePayer(payer) diff --git a/packages/js/src/plugins/rfqModule/operations/partiallySettleLegsAndSettle.ts b/packages/js/src/plugins/rfqModule/operations/partiallySettleLegsAndSettle.ts index e0359e486..f69a51a8c 100644 --- a/packages/js/src/plugins/rfqModule/operations/partiallySettleLegsAndSettle.ts +++ b/packages/js/src/plugins/rfqModule/operations/partiallySettleLegsAndSettle.ts @@ -9,7 +9,7 @@ import { makeConfirmOptionsFinalizedOnMainnet, } from '../../../types'; import { SendAndConfirmTransactionResponse } from '../../rpcModule'; -import { settleBuilder } from './settle'; +import { settleEscrowBuilder } from './settle'; import { partiallySettleLegsBuilder } from './partiallySettleLegs'; const Key = 'PartiallySettleLegsAndSettleOperation' as const; @@ -107,10 +107,19 @@ export const partiallySettleLegsAndSettleOperationHandler: OperationHandler MAX_TX_SIZE) { @@ -131,10 +136,11 @@ export const partiallySettleLegsAndSettleOperationHandler: OperationHandler; * @category Inputs */ export type SettleInput = { - /** - * The protocol address. - * - * @defaultValue `convergence.protocol().pdas().protocol()` - */ - protocol?: PublicKey; - - /** The address of the RFQ account. */ - rfq: PublicKey; - /** The address of the response account. */ response: PublicKey; - - /** The maker public key address. */ - maker: PublicKey; - - /** The taker public key address. */ - taker: PublicKey; - - /** - * Optional start index to corresponding to the first leg to settle. Used internally by - * Convergence SDK and does not need to be passed manually. - * - * @defaultValue `0` - * */ - startIndex?: number; }; /** @@ -120,15 +106,6 @@ export const settleOperationHandler: OperationHandler = { export type SettleBuilderParams = SettleInput; /** - * Settles - * - * ```ts - * const transactionBuilder = convergence - * .rfqs() - * .builders() - * .settle({ address }); - * ``` - * * @group Transaction Builders * @category Constructors */ @@ -137,13 +114,63 @@ export const settleBuilder = async ( params: SettleBuilderParams, options: TransactionBuilderOptions = {} ): Promise => { - const { programs, payer = convergence.rpc().getDefaultFeePayer() } = options; - const { rfq, response, maker, taker } = params; - - const rfqModel = await convergence.rfqs().findRfqByAddress({ address: rfq }); const responseModel = await convergence .rfqs() - .findResponseByAddress({ address: response }); + .findResponseByAddress({ address: params.response }); + const rfqModel = await convergence + .rfqs() + .findRfqByAddress({ address: responseModel.rfq }); + + if ( + responseModel.model === 'escrowResponse' && + rfqModel.model === 'escrowRfq' + ) { + return settleEscrowBuilder( + convergence, + { + response: responseModel, + rfq: rfqModel, + }, + options + ); + } else if ( + responseModel.model === 'printTradeResponse' && + rfqModel.model === 'printTradeRfq' + ) { + return settlePrintTradeBuilder( + convergence, + { + response: responseModel, + rfq: rfqModel, + }, + options + ); + } + + throw new Error('Rfq type does not match with response type!'); +}; + +export type SettleEscrowBuilderParams = { + response: PublicKey | EscrowResponse; + rfq?: EscrowRfq; + startIndex?: number; +}; + +export const settleEscrowBuilder = async ( + convergence: Convergence, + params: SettleEscrowBuilderParams, + options: TransactionBuilderOptions = {} +): Promise => { + const { programs, payer = convergence.rpc().getDefaultFeePayer() } = options; + const { response, rfq, startIndex = 0 } = params; + + const responseModel = + response instanceof PublicKey + ? await convergence.rfqs().findResponseByAddress({ address: response }) + : response; + const rfqModel = + rfq ?? + (await convergence.rfqs().findRfqByAddress({ address: responseModel.rfq })); if ( responseModel.model !== 'escrowResponse' || @@ -152,9 +179,6 @@ export const settleBuilder = async ( throw new Error('Response is not settled as an escrow!'); } - const { startIndex = parseInt(responseModel.settledLegs.toString()) } = - params; - const rfqProgram = convergence.programs().getRfq(programs); const anchorRemainingAccounts: AccountMeta[] = []; @@ -180,7 +204,7 @@ export const settleBuilder = async ( const instrumentEscrowPda = new InstrumentPdasClient( convergence ).instrumentEscrow({ - response, + response: responseModel.address, index: legIndex, rfqModel, }); @@ -199,7 +223,7 @@ export const settleBuilder = async ( .pdas() .associatedTokenAccount({ mint: baseAssetMint!.address, - owner: receiver === 'maker' ? maker : taker, + owner: receiver === 'maker' ? responseModel.maker : rfqModel.taker, programs, }), isSigner: false, @@ -218,7 +242,7 @@ export const settleBuilder = async ( }; const quoteEscrowPda = new InstrumentPdasClient(convergence).quoteEscrow({ - response, + response: responseModel.address, program: spotInstrumentProgram.address, }); @@ -236,7 +260,8 @@ export const settleBuilder = async ( .pdas() .associatedTokenAccount({ mint: rfqModel.quoteMint, - owner: quote.receiver === 'maker' ? maker : taker, + owner: + quote.receiver === 'maker' ? responseModel.maker : rfqModel.taker, programs, }), isSigner: false, @@ -260,8 +285,8 @@ export const settleBuilder = async ( instruction: createSettleEscrowInstruction( { protocol: convergence.protocol().pdas().protocol(), - rfq, - response, + rfq: rfqModel.address, + response: responseModel.address, anchorRemainingAccounts, }, rfqProgram.address @@ -271,3 +296,63 @@ export const settleBuilder = async ( } ); }; + +export type SettlePrintTradeBuilderParams = { + response: PublicKey | PrintTradeResponse; + rfq?: PrintTradeRfq; +}; + +export const settlePrintTradeBuilder = async ( + convergence: Convergence, + params: SettlePrintTradeBuilderParams, + options: TransactionBuilderOptions = {} +): Promise => { + const { programs, payer = convergence.rpc().getDefaultFeePayer() } = options; + const { response, rfq } = params; + + const responseModel = + response instanceof PublicKey + ? await convergence.rfqs().findResponseByAddress({ address: response }) + : response; + const rfqModel = + rfq ?? + (await convergence.rfqs().findRfqByAddress({ address: responseModel.rfq })); + + if ( + responseModel.model !== 'printTradeResponse' || + rfqModel.model !== 'printTradeRfq' + ) { + throw new Error('Response is not settled as an escrow!'); + } + + const rfqProgram = convergence.programs().getRfq(programs); + + const remainingAccounts = prependWithProviderProgram( + rfqModel.printTrade, + await rfqModel.printTrade.getSettlementAccounts(rfqModel, responseModel) + ); + + return TransactionBuilder.make() + .setFeePayer(payer) + .add( + { + instruction: ComputeBudgetProgram.setComputeUnitLimit({ + units: 1_400_000, + }), + signers: [], + }, + { + instruction: createSettlePrintTradeInstruction( + { + protocol: convergence.protocol().pdas().protocol(), + rfq: rfqModel.address, + response: responseModel.address, + anchorRemainingAccounts: remainingAccounts, + }, + rfqProgram.address + ), + signers: [], + key: 'settle', + } + ); +}; diff --git a/packages/js/tests/unit/hxro.spec.ts b/packages/js/tests/unit/hxro.spec.ts index 748c1d67d..8741472fb 100644 --- a/packages/js/tests/unit/hxro.spec.ts +++ b/packages/js/tests/unit/hxro.spec.ts @@ -136,5 +136,9 @@ describe('unit.hxro', () => { new PublicKey(CTX.hxroMakerTrg) ), }); + + await cvgTaker.rfqs().settle({ + response: rfqResponse.address, + }); }); });