diff --git a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/helpers.ts b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/helpers.ts index 3e6499227..1bf7aecd0 100644 --- a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/helpers.ts +++ b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/helpers.ts @@ -1,100 +1,17 @@ import * as psyoptionsAmerican from '@mithraic-labs/psy-american'; import { BN } from 'bn.js'; -import { PublicKey } from '@solana/web3.js'; +import { PublicKey, Transaction } from '@solana/web3.js'; import { Convergence } from '../../Convergence'; -import { ATAExistence, getOrCreateATA } from '../../utils/ata'; +import { getOrCreateATAtxBuilder } from '../../utils/ata'; import { Mint } from '../tokenModule/models'; +import { TransactionBuilder } from '../../utils/TransactionBuilder'; import { CvgWallet } from '../../utils/Wallets'; -import { - InstructionWithSigners, - TransactionBuilder, -} from '../../utils/TransactionBuilder'; +import { InstructionUniquenessTracker } from '../../utils/classes'; import { PsyoptionsAmericanInstrument } from './types'; import { createAmericanProgram } from './instrument'; -export const mintAmericanOptions = async ( - convergence: Convergence, - responseAddress: PublicKey, - caller: PublicKey, - americanProgram: any -) => { - const response = await convergence - .rfqs() - .findResponseByAddress({ address: responseAddress }); - const rfq = await convergence - .rfqs() - .findRfqByAddress({ address: response.rfq }); - - const callerSide = caller.equals(rfq.taker) ? 'taker' : 'maker'; - const instructionWithSigners: InstructionWithSigners[] = []; - const { legs } = await convergence.rfqs().getSettlementResult({ - response, - rfq, - }); - for (const [index, leg] of rfq.legs.entries()) { - if (leg instanceof PsyoptionsAmericanInstrument) { - const { receiver } = legs[index]; - if (receiver !== callerSide) { - const { amount } = legs[index]; - - const optionMarket = await psyoptionsAmerican.getOptionByKey( - americanProgram, - leg.optionMetaPubKey - ); - if (optionMarket) { - const optionToken = await getOrCreateATA( - convergence, - optionMarket.optionMint, - caller - ); - - const writerToken = await getOrCreateATA( - convergence, - optionMarket!.writerTokenMint, - caller - ); - - const underlyingToken = await getOrCreateATA( - convergence, - optionMarket!.underlyingAssetMint, - caller - ); - - const ixWithSigners = - await psyoptionsAmerican.instructions.mintOptionV2Instruction( - americanProgram, - optionToken, - writerToken, - underlyingToken, - new BN(amount!), - optionMarket as psyoptionsAmerican.OptionMarketWithKey - ); - ixWithSigners.ix.keys[0] = { - pubkey: caller, - isSigner: true, - isWritable: false, - }; - instructionWithSigners.push({ - instruction: ixWithSigners.ix, - signers: ixWithSigners.signers, - }); - } - } - } - } - if (instructionWithSigners.length > 0) { - const payer = convergence.rpc().getDefaultFeePayer(); - const txBuilder = TransactionBuilder.make().setFeePayer(payer); - - txBuilder.add(...instructionWithSigners); - const sig = await txBuilder.sendAndConfirm(convergence); - return sig; - } - return null; -}; - export const initializeNewAmericanOption = async ( convergence: Convergence, underlyingMint: Mint, @@ -112,10 +29,8 @@ export const initializeNewAmericanOption = async ( Number(underlyingAmountPerContract) * Math.pow(10, underlyingMint.decimals) ); - const americanProgram = createAmericanProgram( - convergence, - new CvgWallet(convergence) - ); + const cvgWallet = new CvgWallet(convergence); + const americanProgram = createAmericanProgram(convergence, cvgWallet); const { optionMarketKey, optionMintKey, writerMintKey } = await psyoptionsAmerican.instructions.initializeMarket(americanProgram, { @@ -143,15 +58,15 @@ export const initializeNewAmericanOption = async ( optionMint, }; }; - -// used in UI -export const getOrCreateAmericanOptionATAs = async ( +//create American Options ATAs and mint Options +export const prepareAmericanOptions = async ( convergence: Convergence, responseAddress: PublicKey, - caller: PublicKey, - americanProgram: any -): Promise => { - let flag = false; + caller: PublicKey +) => { + const ixTracker = new InstructionUniquenessTracker([]); + const cvgWallet = new CvgWallet(convergence); + const americanProgram = createAmericanProgram(convergence, cvgWallet); const response = await convergence .rfqs() .findResponseByAddress({ address: responseAddress }); @@ -160,28 +75,104 @@ export const getOrCreateAmericanOptionATAs = async ( .findRfqByAddress({ address: response.rfq }); const callerSide = caller.equals(rfq.taker) ? 'taker' : 'maker'; - const { legs } = await convergence.rfqs().getSettlementResult({ + + const { legs } = convergence.rfqs().getSettlementResult({ response, rfq, }); + + const ataTxBuilderArray: TransactionBuilder[] = []; + const mintTxBuilderArray: TransactionBuilder[] = []; for (const [index, leg] of rfq.legs.entries()) { - if (leg instanceof PsyoptionsAmericanInstrument) { - const { receiver } = legs[index]; - if (receiver !== callerSide) { - flag = true; + const { receiver, amount } = legs[index]; + if ( + !(leg instanceof PsyoptionsAmericanInstrument) || + receiver === callerSide + ) { + continue; + } - const optionMarket = await psyoptionsAmerican.getOptionByKey( - americanProgram, - leg.optionMetaPubKey - ); - if (optionMarket) { - await getOrCreateATA(convergence, optionMarket.optionMint, caller); - } - } + const optionMarket = await leg.getOptionMeta(); + const optionToken = await getOrCreateATAtxBuilder( + convergence, + optionMarket.optionMint, + caller + ); + if (optionToken.txBuilder && ixTracker.checkedAdd(optionToken.txBuilder)) { + ataTxBuilderArray.push(optionToken.txBuilder); + } + const writerToken = await getOrCreateATAtxBuilder( + convergence, + optionMarket!.writerTokenMint, + caller + ); + if (writerToken.txBuilder && ixTracker.checkedAdd(writerToken.txBuilder)) { + ataTxBuilderArray.push(writerToken.txBuilder); + } + const underlyingToken = await getOrCreateATAtxBuilder( + convergence, + optionMarket!.underlyingAssetMint, + caller + ); + if ( + underlyingToken.txBuilder && + ixTracker.checkedAdd(underlyingToken.txBuilder) + ) { + ataTxBuilderArray.push(underlyingToken.txBuilder); } + const ixWithSigners = + await psyoptionsAmerican.instructions.mintOptionInstruction( + americanProgram, + optionToken.ataPubKey, + writerToken.ataPubKey, + underlyingToken.ataPubKey, + new BN(amount!), + optionMarket as psyoptionsAmerican.OptionMarketWithKey + ); + ixWithSigners.ix.keys[0] = { + pubkey: caller, + isSigner: true, + isWritable: false, + }; + const mintTxBuilder = TransactionBuilder.make().setFeePayer( + convergence.rpc().getDefaultFeePayer() + ); + mintTxBuilder.add({ + instruction: ixWithSigners.ix, + signers: [convergence.identity()], + }); + mintTxBuilderArray.push(mintTxBuilder); + } + let signedTxs: Transaction[] = []; + const lastValidBlockHeight = await convergence.rpc().getLatestBlockhash(); + if (ataTxBuilderArray.length > 0 || mintTxBuilderArray.length > 0) { + const mergedTxBuilderArray = ataTxBuilderArray.concat(mintTxBuilderArray); + signedTxs = await convergence + .identity() + .signAllTransactions( + mergedTxBuilderArray.map((b) => b.toTransaction(lastValidBlockHeight)) + ); + } + + const ataSignedTx = signedTxs.slice(0, ataTxBuilderArray.length); + const mintSignedTx = signedTxs.slice(ataTxBuilderArray.length); + + if (ataSignedTx.length > 0) { + await Promise.all( + ataSignedTx.map((signedTx) => + convergence + .rpc() + .serializeAndSendTransaction(signedTx, lastValidBlockHeight) + ) + ); } - if (flag === true) { - return ATAExistence.EXISTS; + if (mintSignedTx.length > 0) { + await Promise.all( + mintSignedTx.map((signedTx) => + convergence + .rpc() + .serializeAndSendTransaction(signedTx, lastValidBlockHeight) + ) + ); } - return ATAExistence.NOTEXISTS; }; diff --git a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/instrument.ts b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/instrument.ts index ad226fc03..ec4882198 100644 --- a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/instrument.ts +++ b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/instrument.ts @@ -113,7 +113,8 @@ export class PsyoptionsAmericanInstrument implements LegInstrument { convergence: Convergence, metaKey: PublicKey ): Promise { - const americanProgram = createAmericanProgram(convergence); + const cvgWallet = new CvgWallet(convergence); + const americanProgram = createAmericanProgram(convergence, cvgWallet); const optionMarket = (await psyoptionsAmerican.getOptionByKey( americanProgram, metaKey diff --git a/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/helpers.ts b/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/helpers.ts index d3c021a2a..0c3b95b2e 100644 --- a/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/helpers.ts +++ b/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/helpers.ts @@ -1,19 +1,20 @@ import * as psyoptionsEuropean from '@mithraic-labs/tokenized-euros'; import * as anchor from '@project-serum/anchor'; -import { Keypair, PublicKey } from '@solana/web3.js'; +import { PublicKey, Transaction } from '@solana/web3.js'; import { BN } from 'bn.js'; import { Mint } from '../tokenModule'; -import { ATAExistence, getOrCreateATA } from '../../utils/ata'; +import { getOrCreateATAtxBuilder } from '../../utils/ata'; import { addDecimals } from '../../utils/conversions'; import { TransactionBuilder } from '../../utils/TransactionBuilder'; import { Convergence } from '../../Convergence'; +import { InstructionUniquenessTracker } from '../../utils/classes'; +import { CvgWallet } from '../../utils/Wallets'; import { PsyoptionsEuropeanInstrument } from './instrument'; -import { Pda } from '@/types/Pda'; -import { makeConfirmOptionsFinalizedOnMainnet } from '@/types/Operation'; import { toBigNumber } from '@/types/BigNumber'; export const initializeNewEuropeanOption = async ( convergence: Convergence, + ixTracker: InstructionUniquenessTracker, oracle: PublicKey, europeanProgram: anchor.Program, underlyingMint: Mint, @@ -25,7 +26,7 @@ export const initializeNewEuropeanOption = async ( ) => { const expirationTimestamp = new BN(Date.now() / 1_000 + expiration); - let { instructions: initializeIxs } = + const { instructions: initializeIxs } = await psyoptionsEuropean.instructions.initializeAllAccountsInstructions( europeanProgram, underlyingMint.address, @@ -36,41 +37,20 @@ export const initializeNewEuropeanOption = async ( oracleProviderId ); - const tx = TransactionBuilder.make(); - - const underlyingPoolKey = Pda.find(europeanProgram.programId, [ - underlyingMint.address.toBuffer(), - Buffer.from('underlyingPool', 'utf-8'), - ]); - // TODO: Use retry method - const underlyingPoolAccount = await convergence.connection.getAccountInfo( - underlyingPoolKey - ); - if (underlyingPoolAccount && initializeIxs.length === 3) { - initializeIxs = initializeIxs.slice(1); - } - const stablePoolKey = Pda.find(europeanProgram.programId, [ - stableMint.address.toBuffer(), - Buffer.from('stablePool', 'utf-8'), - ]); - // TODO: Use retry method - const stablePoolAccount = await convergence.connection.getAccountInfo( - stablePoolKey + const inititalizeTxBuilder = TransactionBuilder.make().setFeePayer( + convergence.rpc().getDefaultFeePayer() ); - if (stablePoolAccount && initializeIxs.length === 2) { - initializeIxs = initializeIxs.slice(1); - } else if (stablePoolAccount && initializeIxs.length === 3) { - initializeIxs.splice(1, 1); - } initializeIxs.forEach((ix) => { - tx.add({ instruction: ix, signers: [] }); + if (ixTracker.checkedAdd(ix)) + inititalizeTxBuilder.add({ + instruction: ix, + signers: [convergence.identity()], + }); }); - const confirmOptions = makeConfirmOptionsFinalizedOnMainnet(convergence); - - if (initializeIxs.length > 0) { - await tx.sendAndConfirm(convergence, confirmOptions); + if (inititalizeTxBuilder.getInstructions().length > 0) { + await inititalizeTxBuilder.sendAndConfirm(convergence); } const strikePriceSize = addDecimals(strikePrice, stableMint.decimals); @@ -98,9 +78,16 @@ export const initializeNewEuropeanOption = async ( oracleProviderId ); - await TransactionBuilder.make() - .add({ instruction: createIx, signers: [] }) - .sendAndConfirm(convergence); + if (ixTracker.checkedAdd(createIx)) { + const createTxBuilder = TransactionBuilder.make().setFeePayer( + convergence.rpc().getDefaultFeePayer() + ); + createTxBuilder.add({ + instruction: createIx, + signers: [convergence.identity()], + }); + await createTxBuilder.sendAndConfirm(convergence); + } return { euroMeta, @@ -110,19 +97,25 @@ export const initializeNewEuropeanOption = async ( }; export const createEuropeanProgram = async (convergence: Convergence) => { - return psyoptionsEuropean.createProgram( - convergence.rpc().getDefaultFeePayer() as Keypair, - convergence.connection.rpcEndpoint, + const cvgWallet = new CvgWallet(convergence); + return psyoptionsEuropean.createProgramFromProvider( + new anchor.AnchorProvider( + convergence.connection, + cvgWallet, + anchor.AnchorProvider.defaultOptions() + ), new PublicKey(psyoptionsEuropean.programId) ); }; -export const mintEuropeanOptions = async ( +// create European Option ATAs and mint options +export const prepareEuropeanOptions = async ( convergence: Convergence, responseAddress: PublicKey, - caller: PublicKey, - europeanProgram: any + caller: PublicKey ) => { + const ixTracker = new InstructionUniquenessTracker([]); + const europeanProgram = await createEuropeanProgram(convergence); const response = await convergence .rfqs() .findResponseByAddress({ address: responseAddress }); @@ -130,137 +123,124 @@ export const mintEuropeanOptions = async ( .rfqs() .findRfqByAddress({ address: response.rfq }); - const callerIsTaker = caller.toBase58() === rfq.taker.toBase58(); - const callerSide = callerIsTaker ? 'taker' : 'maker'; - const instructions: anchor.web3.TransactionInstruction[] = []; - const { legs } = await convergence.rfqs().getSettlementResult({ + const callerSide = caller.equals(rfq.taker) ? 'taker' : 'maker'; + + const { legs } = convergence.rfqs().getSettlementResult({ response, rfq, }); + const mintTxBuilderArray: TransactionBuilder[] = []; + const ataTxBuilderArray: TransactionBuilder[] = []; for (const [index, leg] of rfq.legs.entries()) { - if (leg instanceof PsyoptionsEuropeanInstrument) { - const { receiver } = legs[index]; - - if (receiver !== callerSide) { - const { amount } = legs[index]; - - const euroMeta = await leg.getOptionMeta(); - const { stableMint } = euroMeta; - const { underlyingMint } = euroMeta; - const stableMintToken = convergence - .tokens() - .pdas() - .associatedTokenAccount({ - mint: stableMint, - owner: caller, - }); - const underlyingMintToken = convergence - .tokens() - .pdas() - .associatedTokenAccount({ - mint: underlyingMint, - owner: caller, - }); - const minterCollateralKey = - leg.optionType == psyoptionsEuropean.OptionType.PUT - ? stableMintToken - : underlyingMintToken; - - const optionDestination = await getOrCreateATA( - convergence, - leg.optionType == psyoptionsEuropean.OptionType.PUT - ? euroMeta.putOptionMint - : euroMeta.callOptionMint, - caller - ); - const writerDestination = await getOrCreateATA( - convergence, - leg.optionType == psyoptionsEuropean.OptionType.PUT - ? euroMeta.putWriterMint - : euroMeta.callWriterMint, - caller - ); - const { instruction: ix } = psyoptionsEuropean.instructions.mintOptions( - europeanProgram, - leg.optionMetaPubKey, - euroMeta as psyoptionsEuropean.EuroMeta, - minterCollateralKey, - optionDestination, - writerDestination, - new BN(addDecimals(amount, PsyoptionsEuropeanInstrument.decimals)), - leg.optionType - ); - - ix.keys[0] = { - pubkey: caller, - isSigner: true, - isWritable: false, - }; + const { receiver, amount } = legs[index]; + if ( + !(leg instanceof PsyoptionsEuropeanInstrument) || + receiver === callerSide + ) { + continue; + } + const euroMeta = await leg.getOptionMeta(); + const { stableMint, underlyingMint } = euroMeta; + const stableMintToken = convergence.tokens().pdas().associatedTokenAccount({ + mint: stableMint, + owner: caller, + }); + const underlyingMintToken = convergence + .tokens() + .pdas() + .associatedTokenAccount({ + mint: underlyingMint, + owner: caller, + }); + const minterCollateralKey = + leg.optionType == psyoptionsEuropean.OptionType.PUT + ? stableMintToken + : underlyingMintToken; + + const optionDestination = await getOrCreateATAtxBuilder( + convergence, + leg.optionType == psyoptionsEuropean.OptionType.PUT + ? euroMeta.putOptionMint + : euroMeta.callOptionMint, + caller + ); - instructions.push(ix); - } + if ( + optionDestination.txBuilder && + ixTracker.checkedAdd(optionDestination.txBuilder) + ) { + ataTxBuilderArray.push(optionDestination.txBuilder); } - } - if (instructions.length > 0) { - const txBuilder = TransactionBuilder.make().setFeePayer( - convergence.rpc().getDefaultFeePayer() + const writerDestination = await getOrCreateATAtxBuilder( + convergence, + leg.optionType == psyoptionsEuropean.OptionType.PUT + ? euroMeta.putWriterMint + : euroMeta.callWriterMint, + caller + ); + if ( + writerDestination.txBuilder && + ixTracker.checkedAdd(writerDestination.txBuilder) + ) { + ataTxBuilderArray.push(writerDestination.txBuilder); + } + const { instruction: ix } = psyoptionsEuropean.instructions.mintOptions( + europeanProgram, + leg.optionMetaPubKey, + euroMeta as psyoptionsEuropean.EuroMeta, + minterCollateralKey, + optionDestination.ataPubKey, + writerDestination.ataPubKey, + addDecimals(amount, PsyoptionsEuropeanInstrument.decimals), + leg.optionType ); - instructions.forEach((ins) => { - txBuilder.add({ - instruction: ins, - signers: [convergence.identity()], - }); - }); + ix.keys[0] = { + pubkey: caller, + isSigner: true, + isWritable: false, + }; - const confirmOptions = makeConfirmOptionsFinalizedOnMainnet(convergence, { - skipPreflight: true, + const mintTxBuilder = TransactionBuilder.make().setFeePayer( + convergence.rpc().getDefaultFeePayer() + ); + mintTxBuilder.add({ + instruction: ix, + signers: [convergence.identity()], }); + mintTxBuilderArray.push(mintTxBuilder); + } - const sig = await txBuilder.sendAndConfirm(convergence, confirmOptions); - return sig; + let signedTxs: Transaction[] = []; + const lastValidBlockHeight = await convergence.rpc().getLatestBlockhash(); + if (ataTxBuilderArray.length > 0 || mintTxBuilderArray.length > 0) { + const mergedTxBuilderArray = ataTxBuilderArray.concat(mintTxBuilderArray); + signedTxs = await convergence + .identity() + .signAllTransactions( + mergedTxBuilderArray.map((b) => b.toTransaction(lastValidBlockHeight)) + ); } - return null; -}; -export const getOrCreateEuropeanOptionATAs = async ( - convergence: Convergence, - responseAddress: PublicKey, - caller: PublicKey -): Promise => { - let flag = false; - const response = await convergence - .rfqs() - .findResponseByAddress({ address: responseAddress }); - const rfq = await convergence - .rfqs() - .findRfqByAddress({ address: response.rfq }); + const ataSignedTx = signedTxs.slice(0, ataTxBuilderArray.length); + const mintSignedTx = signedTxs.slice(ataTxBuilderArray.length); - const callerIsTaker = caller.toBase58() === rfq.taker.toBase58(); - const callerSide = callerIsTaker ? 'taker' : 'maker'; - const { legs } = await convergence.rfqs().getSettlementResult({ - response, - rfq, - }); - for (const [index, leg] of rfq.legs.entries()) { - if (leg instanceof PsyoptionsEuropeanInstrument) { - const { receiver } = legs[index]; - if (receiver !== callerSide) { - flag = true; - const euroMeta = await leg.getOptionMeta(); - const { optionType } = leg; - await getOrCreateATA( - convergence, - optionType === psyoptionsEuropean.OptionType.PUT - ? euroMeta.putOptionMint - : euroMeta.callOptionMint, - caller - ); - } - } + if (ataSignedTx.length > 0) { + await Promise.all( + ataSignedTx.map((signedTx) => + convergence + .rpc() + .serializeAndSendTransaction(signedTx, lastValidBlockHeight) + ) + ); } - if (flag === true) { - return ATAExistence.EXISTS; + if (mintSignedTx.length > 0) { + await Promise.all( + mintSignedTx.map((signedTx) => + convergence + .rpc() + .serializeAndSendTransaction(signedTx, lastValidBlockHeight) + ) + ); } - return ATAExistence.NOTEXISTS; }; diff --git a/packages/js/src/plugins/rfqModule/operations/prepareSettlement.ts b/packages/js/src/plugins/rfqModule/operations/prepareSettlement.ts index d016c5989..03272f574 100644 --- a/packages/js/src/plugins/rfqModule/operations/prepareSettlement.ts +++ b/packages/js/src/plugins/rfqModule/operations/prepareSettlement.ts @@ -24,10 +24,19 @@ import { TransactionBuilder, TransactionBuilderOptions, } from '../../../utils/TransactionBuilder'; -import { getOrCreateATA } from '../../../utils/ata'; +import { Rfq } from '../../rfqModule'; +import { getOrCreateATAtxBuilder } from '../../../utils/ata'; import { Mint } from '../../tokenModule'; import { InstrumentPdasClient } from '../../instrumentModule'; import { legToBaseAssetMint } from '@/plugins/instrumentModule'; +import { + prepareAmericanOptions, + psyoptionsAmericanInstrumentProgram, +} from '@/plugins/psyoptionsAmericanInstrumentModule'; +import { + prepareEuropeanOptions, + psyoptionsEuropeanInstrumentProgram, +} from '@/plugins/psyoptionsEuropeanInstrumentModule'; const Key = 'PrepareSettlementOperation' as const; @@ -112,8 +121,25 @@ export const prepareSettlementOperationHandler: OperationHandler => { + const { + response, + rfq, + caller = convergence.identity(), + } = operation.input; + + const rfqModel = await convergence + .rfqs() + .findRfqByAddress({ address: rfq }); + + if (doesRfqLegContainsPsyoptionsAmerican(rfqModel)) { + await prepareAmericanOptions(convergence, response, caller?.publicKey); + } + if (doesRfqLegContainsPsyoptionsEuropean(rfqModel)) { + await prepareEuropeanOptions(convergence, response, caller?.publicKey); + } const builder = await prepareSettlementBuilder( convergence, + rfqModel, { ...operation.input, }, @@ -153,6 +179,7 @@ export type PrepareSettlementBuilderParams = PrepareSettlementInput; */ export const prepareSettlementBuilder = async ( convergence: Convergence, + rfqModel: Rfq, params: PrepareSettlementBuilderParams, options: TransactionBuilderOptions = {} ): Promise => { @@ -166,7 +193,7 @@ export const prepareSettlementBuilder = async ( const rfqProgram = convergence.programs().getRfq(programs); - const rfqModel = await convergence.rfqs().findRfqByAddress({ address: rfq }); + // const rfqModel = await convergence.rfqs().findRfqByAddress({ address: rfq }); const responseModel = await convergence .rfqs() .findResponseByAddress({ address: response }); @@ -227,6 +254,7 @@ export const prepareSettlementBuilder = async ( anchorRemainingAccounts.push(spotInstrumentProgramAccount, ...quoteAccounts); + const ataTxBuilderArray: TransactionBuilder[] = []; for (let legIndex = 0; legIndex < legAmountToPrepare; legIndex++) { const instrumentProgramAccount: AccountMeta = { pubkey: rfqModel.legs[legIndex].getProgramId(), @@ -242,6 +270,16 @@ export const prepareSettlementBuilder = async ( rfqModel, }); + const { ataPubKey, txBuilder } = await getOrCreateATAtxBuilder( + convergence, + baseAssetMints[legIndex].address, + caller.publicKey, + programs + ); + if (txBuilder) { + ataTxBuilderArray.push(txBuilder); + } + const legAccounts: AccountMeta[] = [ // `caller` { @@ -251,12 +289,7 @@ export const prepareSettlementBuilder = async ( }, // `caller_token_account` { - pubkey: await getOrCreateATA( - convergence, - baseAssetMints[legIndex].address, - caller.publicKey, - programs - ), + pubkey: ataPubKey, isSigner: false, isWritable: true, }, @@ -279,6 +312,22 @@ export const prepareSettlementBuilder = async ( anchorRemainingAccounts.push(instrumentProgramAccount, ...legAccounts); } + if (ataTxBuilderArray.length > 0) { + const lastValidBlockHeight = await convergence.rpc().getLatestBlockhash(); + const signedTxs = await convergence + .identity() + .signAllTransactions( + ataTxBuilderArray.map((b) => b.toTransaction(lastValidBlockHeight)) + ); + await Promise.all( + signedTxs.map((signedTx) => + convergence + .rpc() + .serializeAndSendTransaction(signedTx, lastValidBlockHeight) + ) + ); + } + return TransactionBuilder.make() .setFeePayer(payer) .add( @@ -308,3 +357,15 @@ export const prepareSettlementBuilder = async ( } ); }; + +const doesRfqLegContainsPsyoptionsAmerican = (rfq: Rfq) => { + return rfq.legs.some((leg) => + leg.getProgramId().equals(psyoptionsAmericanInstrumentProgram.address) + ); +}; + +const doesRfqLegContainsPsyoptionsEuropean = (rfq: Rfq) => { + return rfq.legs.some((leg) => + leg.getProgramId().equals(psyoptionsEuropeanInstrumentProgram.address) + ); +}; diff --git a/packages/js/src/plugins/rfqModule/operations/prepareSettlementAndPrepareMoreLegs.ts b/packages/js/src/plugins/rfqModule/operations/prepareSettlementAndPrepareMoreLegs.ts index e3a3c89fd..0fe7ffe36 100644 --- a/packages/js/src/plugins/rfqModule/operations/prepareSettlementAndPrepareMoreLegs.ts +++ b/packages/js/src/plugins/rfqModule/operations/prepareSettlementAndPrepareMoreLegs.ts @@ -96,13 +96,20 @@ export const prepareSettlementAndPrepareMoreLegsOperationHandler: OperationHandl convergence: Convergence, scope: OperationScope ): Promise => { - const { caller = convergence.identity(), legAmountToPrepare } = - operation.input; + const { + caller = convergence.identity(), + legAmountToPrepare, + rfq, + } = operation.input; const MAX_TX_SIZE = 1232; + const rfqModel = await convergence.rfqs().findRfqByAddress({ + address: rfq, + }); let prepareBuilder = await prepareSettlementBuilder( convergence, + rfqModel, { ...operation.input, }, @@ -124,6 +131,7 @@ export const prepareSettlementAndPrepareMoreLegsOperationHandler: OperationHandl prepareBuilder = await prepareSettlementBuilder( convergence, + rfqModel, { ...operation.input, legAmountToPrepare: halvedLegAmount, diff --git a/packages/js/src/plugins/rpcModule/RpcClient.ts b/packages/js/src/plugins/rpcModule/RpcClient.ts index a89551491..9f3501632 100644 --- a/packages/js/src/plugins/rpcModule/RpcClient.ts +++ b/packages/js/src/plugins/rpcModule/RpcClient.ts @@ -283,8 +283,8 @@ export class RpcClient { async sendAndConfirmTransaction( transaction: Transaction | TransactionBuilder, - confirmOptions?: ConfirmOptions, - signers: Signer[] = [] + signers: Signer[] = [], + confirmOptions?: ConfirmOptions ): Promise { const prepared = await this.prepareTransaction(transaction, signers); const { blockhashWithExpiryBlockHeight } = prepared; diff --git a/packages/js/src/utils/TransactionBuilder.ts b/packages/js/src/utils/TransactionBuilder.ts index 04be4161e..54d51f00a 100644 --- a/packages/js/src/utils/TransactionBuilder.ts +++ b/packages/js/src/utils/TransactionBuilder.ts @@ -196,7 +196,7 @@ export class TransactionBuilder { ): Promise<{ response: SendAndConfirmTransactionResponse } & C> { const response = await convergence .rpc() - .sendAndConfirmTransaction(this, confirmOptions); + .sendAndConfirmTransaction(this, [], confirmOptions); return { response, diff --git a/packages/js/src/utils/Wallets.ts b/packages/js/src/utils/Wallets.ts index 23bfe096b..965c5d73a 100644 --- a/packages/js/src/utils/Wallets.ts +++ b/packages/js/src/utils/Wallets.ts @@ -15,17 +15,19 @@ export class CvgWallet implements Wallet { constructor(convergence: Convergence) { this.convergence = convergence; this.payer = convergence.rpc().getDefaultFeePayer() as Keypair; - this.publicKey = convergence.rpc().getDefaultFeePayer().publicKey; + this.publicKey = convergence.identity().publicKey; } signTransaction = (tx: Transaction): Promise => { - return this.convergence.rpc().signTransaction(tx, [this.payer as Signer]); + return this.convergence + .rpc() + .signTransaction(tx, [this.convergence.identity() as Signer]); }; signAllTransactions = (txs: Transaction[]): Promise => { return this.convergence .rpc() - .signAllTransactions(txs, [this.payer as Signer]); + .signAllTransactions(txs, [this.convergence.identity() as Signer]); }; } diff --git a/packages/js/src/utils/ata.ts b/packages/js/src/utils/ata.ts index 7478b4fce..e0851f235 100644 --- a/packages/js/src/utils/ata.ts +++ b/packages/js/src/utils/ata.ts @@ -1,14 +1,10 @@ -import * as Spl from '@solana/spl-token'; -import { Keypair, PublicKey, TransactionInstruction } from '@solana/web3.js'; +import { Keypair, PublicKey } from '@solana/web3.js'; import { Convergence } from '../Convergence'; import { token } from '../types/Amount'; import { Program } from '../types'; +import { TransactionBuilder } from '../utils/TransactionBuilder'; import { collateralMintCache } from '@/plugins/collateralModule/cache'; - -export enum ATAExistence { - EXISTS, - NOTEXISTS, -} +import { createTokenBuilder } from '@/plugins/tokenModule/operations/createToken'; export const getOrCreateATA = async ( convergence: Convergence, @@ -35,31 +31,31 @@ export const getOrCreateATA = async ( return ata; }; -export const getOrCreateATAInx = async ( +interface GetOrCreateATAtxBuilderReturnType { + ataPubKey: PublicKey; + txBuilder?: TransactionBuilder; +} + +export const getOrCreateATAtxBuilder = async ( convergence: Convergence, mint: PublicKey, owner: PublicKey, programs?: Program[] -): Promise => { +): Promise => { const pda = convergence.tokens().pdas().associatedTokenAccount({ mint, owner, programs, }); const account = await convergence.rpc().getAccount(pda); - let ix: TransactionInstruction; if (!account.exists) { - ix = Spl.createAssociatedTokenAccountInstruction( - owner, - pda, - owner, + const txBuilder = await createTokenBuilder(convergence, { mint, - Spl.TOKEN_PROGRAM_ID, - Spl.ASSOCIATED_TOKEN_PROGRAM_ID - ); - return ix; + owner, + }); + return { ataPubKey: pda, txBuilder }; } - return pda; + return { ataPubKey: pda }; }; export const devnetAirdrops = async ( diff --git a/packages/js/src/utils/classes.ts b/packages/js/src/utils/classes.ts index 0c8af3042..198f3c407 100644 --- a/packages/js/src/utils/classes.ts +++ b/packages/js/src/utils/classes.ts @@ -1,5 +1,58 @@ +import { AccountMeta, TransactionInstruction } from '@solana/web3.js'; +import { TransactionBuilder } from './TransactionBuilder'; import { Rfq } from '@/plugins/rfqModule'; +export class InstructionUniquenessTracker { + constructor(public readonly IxArray: TransactionInstruction[]) { + this.IxArray = IxArray; + } + + private matchKeys = ( + keys: AccountMeta[], + keysToMatch: AccountMeta[] + ): boolean => { + if (keys.length !== keysToMatch.length) { + return false; + } + return keys.every( + (key, index) => + key.isSigner === keysToMatch[index].isSigner && + key.isWritable === keysToMatch[index].isWritable && + key.pubkey.equals(keysToMatch[index].pubkey) + ); + }; + private matchInstruction = (ixToBeAdded: TransactionInstruction): boolean => { + return !this.IxArray.every( + (ix) => + !( + this.matchKeys(ix.keys, ixToBeAdded.keys) && + ix.programId.equals(ixToBeAdded.programId) && + ix.data.equals(ixToBeAdded.data) + ) + ); + }; + checkedAdd(ix: TransactionInstruction | TransactionBuilder): boolean { + if (ix instanceof TransactionBuilder) { + const instructions = ix.getInstructions(); + const areAllUnique = instructions.every( + (ix) => !this.matchInstruction(ix) + ); + if (areAllUnique) { + this.IxArray.push(...instructions); + return true; + } + return false; + } else if (ix instanceof TransactionInstruction) { + if (!this.matchInstruction(ix)) { + this.IxArray.push(ix); + return true; + } + return false; + } + throw new Error('Invalid Instruction type'); + } +} + export class RfqTimers { public timestampExpiry: Date; public timestampStart: Date; diff --git a/packages/js/src/utils/index.ts b/packages/js/src/utils/index.ts index c6caca0ba..47ca604ba 100644 --- a/packages/js/src/utils/index.ts +++ b/packages/js/src/utils/index.ts @@ -12,3 +12,4 @@ export * from './Wallets'; export * from './cache'; export * from './conversions'; export * from './ata'; +export * from './classes'; diff --git a/packages/js/tests/helpers.ts b/packages/js/tests/helpers.ts index 2800eed64..7429cf3f3 100644 --- a/packages/js/tests/helpers.ts +++ b/packages/js/tests/helpers.ts @@ -3,6 +3,7 @@ import { PROGRAM_ID } from '@convergence-rfq/rfq'; import { v4 as uuidv4 } from 'uuid'; import { Program, web3 } from '@project-serum/anchor'; import * as anchor from '@project-serum/anchor'; +import { EuroPrimitive } from '@mithraic-labs/tokenized-euros'; import { Convergence, OrderType, @@ -10,23 +11,20 @@ import { PsyoptionsAmericanInstrument, initializeNewAmericanOption, toBigNumber, - createAmericanProgram, Rfq, Response, - getOrCreateAmericanOptionATAs, - mintAmericanOptions, + prepareAmericanOptions, SpotQuoteInstrument, SpotLegInstrument, keypairIdentity, PublicKey, removeDecimals, useCache, - createEuropeanProgram, CvgWallet, PsyoptionsEuropeanInstrument, initializeNewEuropeanOption, - getOrCreateEuropeanOptionATAs, - mintEuropeanOptions, + prepareEuropeanOptions, + InstructionUniquenessTracker, } from '../src'; import { getUserKp, RPC_ENDPOINT } from '../../validator'; import { IDL as PseudoPythIdl } from '../../validator/fixtures/programs/pseudo_pyth_idl'; @@ -120,9 +118,9 @@ export const createAmericanCoveredCallRfq = async ( cvg, baseMint, quoteMint, - 27_000, + 44_000 + Math.random() * 100, 1, - 3_600 + Math.random() + 3_600 + Math.random() * 100 ); const { rfq, response } = await cvg.rfqs().createAndFinalize({ @@ -151,9 +149,10 @@ export const createEuropeanCoveredCallRfq = async ( cvg: Convergence, orderType: OrderType, baseMint: any, - quoteMint: any + quoteMint: any, + ixTracker: InstructionUniquenessTracker, + europeanProgram: Program ) => { - const europeanProgram = await createEuropeanProgram(cvg); const oracle = await createPythPriceFeed( new anchor.Program( PseudoPythIdl, @@ -164,10 +163,10 @@ export const createEuropeanCoveredCallRfq = async ( quoteMint.decimals * -1 ); const min = 3_600; - const randomExpiry = min + Math.random(); - + const randomExpiry = min + Math.random() * 1000; const { euroMeta, euroMetaKey } = await initializeNewEuropeanOption( cvg, + ixTracker, oracle, europeanProgram, baseMint, @@ -202,9 +201,10 @@ export const createEuropeanOpenSizeCallSpdOptionRfq = async ( cvg: Convergence, orderType: OrderType, baseMint: any, - quoteMint: any + quoteMint: any, + ixTracker: InstructionUniquenessTracker, + europeanProgram: Program ) => { - const europeanProgram = await createEuropeanProgram(cvg); const oracle = await createPythPriceFeed( new anchor.Program( PseudoPythIdl, @@ -215,15 +215,16 @@ export const createEuropeanOpenSizeCallSpdOptionRfq = async ( quoteMint.decimals * -1 ); const min = 3_600; - const randomExpiry = min + Math.random(); + const randomExpiry = min + Math.random() * 1000; const { euroMeta: euroMeta1, euroMetaKey: euroMetaKey1 } = await initializeNewEuropeanOption( cvg, + ixTracker, oracle, europeanProgram, baseMint, quoteMint, - 23_354, + 23_822, 1, randomExpiry, 0 @@ -231,11 +232,12 @@ export const createEuropeanOpenSizeCallSpdOptionRfq = async ( const { euroMeta: euroMeta2, euroMetaKey: euroMetaKey2 } = await initializeNewEuropeanOption( cvg, + ixTracker, oracle, europeanProgram, baseMint, quoteMint, - 25_354, + 28_822, 1, randomExpiry, 0 @@ -275,12 +277,12 @@ export const createAmericanFixedBaseStraddle = async ( baseMint: any, quoteMint: any ) => { - const expiration = 3_600 + Math.random(); + const expiration = 3_600 + Math.random() * 1000; const { optionMarketKey, optionMarket } = await initializeNewAmericanOption( cvg, baseMint, quoteMint, - 27_000, + 29_210, 1, expiration ); @@ -305,7 +307,7 @@ export const createAmericanFixedBaseStraddle = async ( optionMarket, optionMarketKey, 1, - 'long' + 'short' ), ], orderType, @@ -320,9 +322,10 @@ export const createEuropeanFixedBaseStraddle = async ( cvg: Convergence, orderType: OrderType, baseMint: any, - quoteMint: any + quoteMint: any, + ixTracker: InstructionUniquenessTracker, + europeanProgram: Program ) => { - const europeanProgram = await createEuropeanProgram(cvg); const oracle = await createPythPriceFeed( new anchor.Program( PseudoPythIdl, @@ -333,10 +336,12 @@ export const createEuropeanFixedBaseStraddle = async ( quoteMint.decimals * -1 ); const min = 3_600; - const randomExpiry = min + Math.random(); + const randomExpiry = min + Math.random() * 1000; + const { euroMeta: euroMeta, euroMetaKey: euroMetaKey } = await initializeNewEuropeanOption( cvg, + ixTracker, oracle, europeanProgram, baseMint, @@ -365,7 +370,7 @@ export const createEuropeanFixedBaseStraddle = async ( euroMeta, euroMetaKey, 1, - 'long' + 'short' ), ], orderType, @@ -382,7 +387,7 @@ export const createAmericanOpenSizeCallSpdOptionRfq = async ( baseMint: any, quoteMint: any ) => { - const expiration = 3_600 + Math.random(); + const expiration = 3_600 + Math.random() * 1000; const { optionMarketKey: optionMarketKey1, optionMarket: optionMarket1 } = await initializeNewAmericanOption( cvg, @@ -593,7 +598,7 @@ export const prepareRfqSettlement = async ( response: Response ) => { return await cvg.rfqs().prepareSettlement({ - caller: cvg.rpc().getDefaultFeePayer(), + caller: cvg.identity(), rfq: rfq.address, response: response.address, legAmountToPrepare: rfq.legs.length, @@ -652,33 +657,13 @@ export const createPythPriceFeed = async ( }; export const setupAmerican = async (cvg: Convergence, response: Response) => { - const americanProgram = createAmericanProgram(cvg); - await getOrCreateAmericanOptionATAs( - cvg, - response.address, - cvg.identity().publicKey, - americanProgram - ); - await mintAmericanOptions( - cvg, - response.address, - cvg.identity().publicKey, - americanProgram - ); + await prepareAmericanOptions(cvg, response.address, cvg.identity().publicKey); }; export const setupEuropean = async (cvg: Convergence, response: Response) => { - const europeanProgram = await createEuropeanProgram(cvg); - - await getOrCreateEuropeanOptionATAs( + await prepareEuropeanOptions( cvg, response.address, cvg.rpc().getDefaultFeePayer().publicKey ); - await mintEuropeanOptions( - cvg, - response.address, - cvg.rpc().getDefaultFeePayer().publicKey, - europeanProgram - ); }; diff --git a/packages/js/tests/integration/psyoptionsAmerican.spec.ts b/packages/js/tests/integration/psyoptionsAmerican.spec.ts index 0e32836a9..96bf368f8 100644 --- a/packages/js/tests/integration/psyoptionsAmerican.spec.ts +++ b/packages/js/tests/integration/psyoptionsAmerican.spec.ts @@ -7,7 +7,6 @@ import { prepareRfqSettlement, settleRfq, createUserCvg, - setupAmerican, createAmericanOpenSizeCallSpdOptionRfq, createAmericanFixedBaseStraddle, } from '../helpers'; @@ -28,7 +27,7 @@ describe('integration.psyoptionsAmerican', () => { .findMintByAddress({ address: QUOTE_MINT_PK }); }); - it('covered call [sell]', async () => { + it('american covered call [sell]', async () => { const { rfq } = await createAmericanCoveredCallRfq( takerCvg, 'sell', @@ -47,8 +46,8 @@ describe('integration.psyoptionsAmerican', () => { response: rfqResponse.address, side: 'bid', }); + expect(confirmResponse).toHaveProperty('signature'); - await setupAmerican(takerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, @@ -93,15 +92,13 @@ describe('integration.psyoptionsAmerican', () => { side: 'ask', }); expect(confirmResponse).toHaveProperty('signature'); - await setupAmerican(takerCvg, rfqResponse); - await setupAmerican(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, rfqResponse ); - expect(takerResponse.response).toHaveProperty('signature'); + expect(takerResponse.response).toHaveProperty('signature'); const makerResponse = await prepareRfqSettlement( makerCvg, rfq, @@ -113,10 +110,10 @@ describe('integration.psyoptionsAmerican', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('fixed-size american straddle [sell]', async () => { - const { rfq } = await createAmericanFixedBaseStraddle( + it('open size american call Spread [buy]', async () => { + const { rfq } = await createAmericanOpenSizeCallSpdOptionRfq( takerCvg, - 'sell', + 'buy', baseMint, quoteMint ); @@ -124,20 +121,21 @@ describe('integration.psyoptionsAmerican', () => { const { rfqResponse } = await respondToRfq( makerCvg, rfq, - 55_133, - undefined + undefined, + 150_123, + 5 ); expect(rfqResponse).toHaveProperty('address'); + const { response: confirmResponse } = await takerCvg .rfqs() .confirmResponse({ rfq: rfq.address, response: rfqResponse.address, - side: 'bid', + side: 'ask', + overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupAmerican(takerCvg, rfqResponse); - await setupAmerican(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -156,33 +154,36 @@ describe('integration.psyoptionsAmerican', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('fixed-size american straddle [2-way]', async () => { + it('fixed-size american straddle [sell]', async () => { const { rfq } = await createAmericanFixedBaseStraddle( takerCvg, - 'two-way', + 'sell', baseMint, quoteMint ); expect(rfq).toHaveProperty('address'); - const { rfqResponse } = await respondToRfq(makerCvg, rfq, 61_222, 60_123); + const { rfqResponse } = await respondToRfq( + makerCvg, + rfq, + 55_133, + undefined + ); expect(rfqResponse).toHaveProperty('address'); const { response: confirmResponse } = await takerCvg .rfqs() .confirmResponse({ rfq: rfq.address, response: rfqResponse.address, - side: 'ask', + side: 'bid', }); expect(confirmResponse).toHaveProperty('signature'); - await setupAmerican(takerCvg, rfqResponse); - await setupAmerican(makerCvg, rfqResponse); + const takerResponse = await prepareRfqSettlement( takerCvg, rfq, rfqResponse ); expect(takerResponse.response).toHaveProperty('signature'); - const makerResponse = await prepareRfqSettlement( makerCvg, rfq, @@ -194,10 +195,10 @@ describe('integration.psyoptionsAmerican', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('open size american call Spread [buy]', async () => { + it('open size american call Spread [2-way]', async () => { const { rfq } = await createAmericanOpenSizeCallSpdOptionRfq( takerCvg, - 'buy', + 'two-way', baseMint, quoteMint ); @@ -205,7 +206,7 @@ describe('integration.psyoptionsAmerican', () => { const { rfqResponse } = await respondToRfq( makerCvg, rfq, - undefined, + 220_111, 150_123, 5 ); @@ -220,8 +221,6 @@ describe('integration.psyoptionsAmerican', () => { overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupAmerican(takerCvg, rfqResponse); - await setupAmerican(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -240,34 +239,24 @@ describe('integration.psyoptionsAmerican', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('open size american call Spread [2-way]', async () => { - const { rfq } = await createAmericanOpenSizeCallSpdOptionRfq( + it('fixed-size american straddle [2-way]', async () => { + const { rfq } = await createAmericanFixedBaseStraddle( takerCvg, 'two-way', baseMint, quoteMint ); expect(rfq).toHaveProperty('address'); - const { rfqResponse } = await respondToRfq( - makerCvg, - rfq, - 220_111, - 150_123, - 5 - ); + const { rfqResponse } = await respondToRfq(makerCvg, rfq, 61_222, 60_123); expect(rfqResponse).toHaveProperty('address'); - const { response: confirmResponse } = await takerCvg .rfqs() .confirmResponse({ rfq: rfq.address, response: rfqResponse.address, side: 'ask', - overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupAmerican(takerCvg, rfqResponse); - await setupAmerican(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, diff --git a/packages/js/tests/integration/psyoptionsEuropean.spec.ts b/packages/js/tests/integration/psyoptionsEuropean.spec.ts index 54ee6ea70..878fa3697 100644 --- a/packages/js/tests/integration/psyoptionsEuropean.spec.ts +++ b/packages/js/tests/integration/psyoptionsEuropean.spec.ts @@ -1,6 +1,8 @@ import { expect } from 'expect'; import { Mint } from '@solana/spl-token'; +import { Program } from '@project-serum/anchor'; +import { EuroPrimitive } from '@mithraic-labs/tokenized-euros'; import { prepareRfqSettlement, respondToRfq, @@ -8,17 +10,19 @@ import { createUserCvg, createEuropeanCoveredCallRfq, createEuropeanOpenSizeCallSpdOptionRfq, - setupEuropean, createEuropeanFixedBaseStraddle, } from '../helpers'; import { BASE_MINT_BTC_PK, QUOTE_MINT_PK } from '../constants'; +import { InstructionUniquenessTracker } from '../../src/utils/'; +import { createEuropeanProgram } from '../../src'; describe('integration.psyoptionsEuropean', () => { const takerCvg = createUserCvg('taker'); const makerCvg = createUserCvg('maker'); let baseMint: Mint; let quoteMint: Mint; - + let europeanProgram: Program; + const ixTracker = new InstructionUniquenessTracker([]); before(async () => { baseMint = await takerCvg .tokens() @@ -26,14 +30,17 @@ describe('integration.psyoptionsEuropean', () => { quoteMint = await takerCvg .tokens() .findMintByAddress({ address: QUOTE_MINT_PK }); + europeanProgram = await createEuropeanProgram(takerCvg); }); - it('covered call [sell]', async () => { + it('european covered call [sell]', async () => { const { rfq, response } = await createEuropeanCoveredCallRfq( takerCvg, 'sell', baseMint, - quoteMint + quoteMint, + ixTracker, + europeanProgram ); expect(rfq).toHaveProperty('address'); @@ -45,20 +52,20 @@ describe('integration.psyoptionsEuropean', () => { response: rfqResponse.address, side: 'bid', }); - - await setupEuropean(takerCvg, rfqResponse); - await prepareRfqSettlement(makerCvg, rfq, rfqResponse); await prepareRfqSettlement(takerCvg, rfq, rfqResponse); await settleRfq(takerCvg, rfq, rfqResponse); }); + it('fixed-size european straddle [buy]', async () => { const { rfq } = await createEuropeanFixedBaseStraddle( takerCvg, 'buy', baseMint, - quoteMint + quoteMint, + ixTracker, + europeanProgram ); expect(rfq).toHaveProperty('address'); const { rfqResponse } = await respondToRfq( @@ -76,8 +83,6 @@ describe('integration.psyoptionsEuropean', () => { side: 'ask', }); expect(confirmResponse).toHaveProperty('signature'); - await setupEuropean(takerCvg, rfqResponse); - await setupEuropean(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -96,31 +101,34 @@ describe('integration.psyoptionsEuropean', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('fixed-size european straddle [sell]', async () => { - const { rfq } = await createEuropeanFixedBaseStraddle( + it('open size european call Spread [buy]', async () => { + const { rfq } = await createEuropeanOpenSizeCallSpdOptionRfq( takerCvg, - 'sell', + 'buy', baseMint, - quoteMint + quoteMint, + ixTracker, + europeanProgram ); expect(rfq).toHaveProperty('address'); const { rfqResponse } = await respondToRfq( makerCvg, rfq, - 55_133, - undefined + undefined, + 150_123, + 5 ); expect(rfqResponse).toHaveProperty('address'); + const { response: confirmResponse } = await takerCvg .rfqs() .confirmResponse({ rfq: rfq.address, response: rfqResponse.address, - side: 'bid', + side: 'ask', + overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupEuropean(takerCvg, rfqResponse); - await setupEuropean(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -139,26 +147,31 @@ describe('integration.psyoptionsEuropean', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('fixed-size european straddle [2-way]', async () => { + it('fixed-size european straddle [sell]', async () => { const { rfq } = await createEuropeanFixedBaseStraddle( takerCvg, - 'two-way', + 'sell', baseMint, - quoteMint + quoteMint, + ixTracker, + europeanProgram ); expect(rfq).toHaveProperty('address'); - const { rfqResponse } = await respondToRfq(makerCvg, rfq, 61_222, 60_123); + const { rfqResponse } = await respondToRfq( + makerCvg, + rfq, + 55_133, + undefined + ); expect(rfqResponse).toHaveProperty('address'); const { response: confirmResponse } = await takerCvg .rfqs() .confirmResponse({ rfq: rfq.address, response: rfqResponse.address, - side: 'ask', + side: 'bid', }); expect(confirmResponse).toHaveProperty('signature'); - await setupEuropean(takerCvg, rfqResponse); - await setupEuropean(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -177,18 +190,20 @@ describe('integration.psyoptionsEuropean', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('open size european call Spread [buy]', async () => { + it('open size european call Spread [2-way]', async () => { const { rfq } = await createEuropeanOpenSizeCallSpdOptionRfq( takerCvg, - 'buy', + 'two-way', baseMint, - quoteMint + quoteMint, + ixTracker, + europeanProgram ); expect(rfq).toHaveProperty('address'); const { rfqResponse } = await respondToRfq( makerCvg, rfq, - undefined, + 220_111, 150_123, 5 ); @@ -203,8 +218,6 @@ describe('integration.psyoptionsEuropean', () => { overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupEuropean(takerCvg, rfqResponse); - await setupEuropean(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -223,34 +236,26 @@ describe('integration.psyoptionsEuropean', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('open size european call Spread [2-way]', async () => { - const { rfq } = await createEuropeanOpenSizeCallSpdOptionRfq( + it('fixed-size european straddle [2-way]', async () => { + const { rfq } = await createEuropeanFixedBaseStraddle( takerCvg, 'two-way', baseMint, - quoteMint + quoteMint, + ixTracker, + europeanProgram ); expect(rfq).toHaveProperty('address'); - const { rfqResponse } = await respondToRfq( - makerCvg, - rfq, - 220_111, - 150_123, - 5 - ); + const { rfqResponse } = await respondToRfq(makerCvg, rfq, 61_222, 60_123); expect(rfqResponse).toHaveProperty('address'); - const { response: confirmResponse } = await takerCvg .rfqs() .confirmResponse({ rfq: rfq.address, response: rfqResponse.address, side: 'ask', - overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupEuropean(takerCvg, rfqResponse); - await setupEuropean(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -274,7 +279,9 @@ describe('integration.psyoptionsEuropean', () => { takerCvg, 'sell', baseMint, - quoteMint + quoteMint, + ixTracker, + europeanProgram ); expect(rfq).toHaveProperty('address'); const { rfqResponse } = await respondToRfq( @@ -295,8 +302,6 @@ describe('integration.psyoptionsEuropean', () => { overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupEuropean(takerCvg, rfqResponse); - await setupEuropean(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, diff --git a/packages/js/tests/unit/settlementResult.spec.ts b/packages/js/tests/unit/settlementResult.spec.ts index d784c36bc..4d9d37b1c 100644 --- a/packages/js/tests/unit/settlementResult.spec.ts +++ b/packages/js/tests/unit/settlementResult.spec.ts @@ -1,5 +1,7 @@ import expect from 'expect'; import { Mint } from '@solana/spl-token'; +import { EuroPrimitive } from '@mithraic-labs/tokenized-euros'; +import { Program } from '@project-serum/anchor'; import { createAmericanCoveredCallRfq, createEuropeanCoveredCallRfq, @@ -12,13 +14,15 @@ import { QUOTE_MINT_DECIMALS, QUOTE_MINT_PK, } from '../constants'; +import { InstructionUniquenessTracker, createEuropeanProgram } from '../../src'; describe('unit.settlementResult', () => { const takerCvg = createUserCvg('taker'); const makerCvg = createUserCvg('maker'); let baseMint: Mint; let quoteMint: Mint; - + let europeanProgram: Program; + const ixTracker = new InstructionUniquenessTracker([]); before(async () => { baseMint = await takerCvg .tokens() @@ -26,6 +30,7 @@ describe('unit.settlementResult', () => { quoteMint = await takerCvg .tokens() .findMintByAddress({ address: QUOTE_MINT_PK }); + europeanProgram = await createEuropeanProgram(takerCvg); }); it('fixed-base buy', async () => { @@ -371,7 +376,9 @@ describe('unit.settlementResult', () => { takerCvg, 'sell', baseMint, - quoteMint + quoteMint, + ixTracker, + europeanProgram ); expect(rfq).toHaveProperty('address'); expect(response.signature).toBeDefined();