diff --git a/packages/js/src/plugins/instrumentModule/methods.ts b/packages/js/src/plugins/instrumentModule/methods.ts index 7de7c2860..23e70991c 100644 --- a/packages/js/src/plugins/instrumentModule/methods.ts +++ b/packages/js/src/plugins/instrumentModule/methods.ts @@ -1,16 +1,18 @@ -import { Leg, QuoteAsset, legBeet } from '@convergence-rfq/rfq'; +import { QuoteAsset, legBeet } from '@convergence-rfq/rfq'; -import { AccountMeta } from '@solana/web3.js'; +import { AccountMeta, PublicKey } from '@solana/web3.js'; import { createSerializerFromFixableBeetArgsStruct } from '../../types'; -import { addDecimals } from '../../utils/conversions'; -import { toSolitaLegSide } from '../rfqModule/models/LegSide'; -import { PsyoptionsEuropeanInstrument } from '../psyoptionsEuropeanInstrumentModule'; -import { PsyoptionsAmericanInstrument } from '../psyoptionsAmericanInstrumentModule'; -import { SpotLegInstrument } from '../spotInstrumentModule'; +import { toSolitaLegSide } from '../rfqModule/models'; import { LegInstrument, QuoteInstrument } from './types'; +import { addDecimals } from '@/utils/conversions'; import { Convergence } from '@/Convergence'; -export function toLeg(legInstrument: LegInstrument): Leg { +export function serializeAsLeg(legInstrument: LegInstrument) { + const legSerializer = createSerializerFromFixableBeetArgsStruct(legBeet); + return legSerializer.serialize(toLeg(legInstrument)); +} + +export function toLeg(legInstrument: LegInstrument) { return { instrumentProgram: legInstrument.getProgramId(), baseAssetIndex: legInstrument.getBaseAssetIndex(), @@ -24,29 +26,45 @@ export function toLeg(legInstrument: LegInstrument): Leg { }; } -export function serializeAsLeg(legInstrument: LegInstrument) { - const legSerializer = createSerializerFromFixableBeetArgsStruct(legBeet); - return legSerializer.serialize(toLeg(legInstrument)); -} +export function getBaseAssetAccount( + legInstrument: LegInstrument, + cvg: Convergence +): AccountMeta { + const baseAsset = cvg + .protocol() + .pdas() + .baseAsset({ index: legInstrument.getBaseAssetIndex().value }); -export function getSerializedLegLength(legInstrument: LegInstrument) { - return serializeAsLeg(legInstrument).length; + const baseAssetAccount: AccountMeta = { + pubkey: baseAsset, + isSigner: false, + isWritable: false, + }; + + return baseAssetAccount; } -export function getProgramAccount(legInstrument: LegInstrument): AccountMeta { - return { - pubkey: legInstrument.getProgramId(), +export async function getOracleAccount( + baseAsset: PublicKey, + cvg: Convergence +): Promise { + const baseAssetModel = await cvg + .protocol() + .findBaseAssetByAddress({ address: baseAsset }); + + if (!baseAssetModel.priceOracle.address) { + throw Error('Base asset does not have a price oracle!'); + } + const oracleAccount = { + pubkey: baseAssetModel.priceOracle.address, isSigner: false, isWritable: false, }; + return oracleAccount; } -export function getValidationAccounts( - legInstrument: LegInstrument -): AccountMeta[] { - return [getProgramAccount(legInstrument)].concat( - legInstrument.getValidationAccounts() - ); +export function getSerializedLegLength(legInstrument: LegInstrument) { + return serializeAsLeg(legInstrument).length; } export function toQuote(legInstrument: QuoteInstrument): QuoteAsset { @@ -56,31 +74,3 @@ export function toQuote(legInstrument: QuoteInstrument): QuoteAsset { instrumentDecimals: legInstrument.getDecimals(), }; } - -//TODO: refactor this method to use instrument interface in the future -export const legToBaseAssetMint = async ( - convergence: Convergence, - leg: LegInstrument -) => { - if (leg instanceof PsyoptionsEuropeanInstrument) { - const euroMetaOptionMint = await convergence.tokens().findMintByAddress({ - address: leg.optionMint, - }); - - return euroMetaOptionMint; - } else if (leg instanceof PsyoptionsAmericanInstrument) { - const americanOptionMint = await convergence.tokens().findMintByAddress({ - address: leg.optionMint, - }); - - return americanOptionMint; - } else if (leg instanceof SpotLegInstrument) { - const mint = await convergence.tokens().findMintByAddress({ - address: leg.mintAddress, - }); - - return mint; - } - - throw Error('Unsupported instrument!'); -}; diff --git a/packages/js/src/plugins/instrumentModule/types.ts b/packages/js/src/plugins/instrumentModule/types.ts index e1ee536f5..2440ef21b 100644 --- a/packages/js/src/plugins/instrumentModule/types.ts +++ b/packages/js/src/plugins/instrumentModule/types.ts @@ -18,6 +18,7 @@ export interface LegInstrument { getDecimals: () => number; getSide: () => LegSide; serializeInstrumentData: () => Buffer; + getExchangeAssetMint(): PublicKey; getValidationAccounts(): AccountMeta[]; getPreparationsBeforeRfqCreation(): Promise; } diff --git a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/instrument.ts b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/instrument.ts index c458b57b2..8df0292a6 100644 --- a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/instrument.ts +++ b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/instrument.ts @@ -70,17 +70,16 @@ export class PsyoptionsAmericanInstrument implements LegInstrument { readonly stableAssetMint?: PublicKey ) {} - getBaseAssetIndex = () => this.baseAssetIndex; getAmount = () => this.amount; + getBaseAssetIndex = () => this.baseAssetIndex; getDecimals = () => PsyoptionsAmericanInstrument.decimals; getSide = () => this.side; + async getPreparationsBeforeRfqCreation(): Promise { - if (!this.underlyingAssetMint) { - throw new Error('Missing underlying asset mint'); - } - if (!this.stableAssetMint) { - throw new Error('Missing stable asset mint'); - } + if (!this.underlyingAssetMint) + throw Error('Underlying asset mint is not defined'); + + if (!this.stableAssetMint) throw Error('Stable asset mint is not defined'); const optionMarketIxs = await getPsyAmericanMarketIxs( this.convergence, @@ -95,6 +94,10 @@ export class PsyoptionsAmericanInstrument implements LegInstrument { return optionMarketIxs; } + getExchangeAssetMint(): PublicKey { + return this.optionMint; + } + static async create( convergence: Convergence, underlyingMint: Mint, @@ -171,12 +174,10 @@ export class PsyoptionsAmericanInstrument implements LegInstrument { } getValidationAccounts() { - if (!this.underlyingAssetMint) { - throw new Error('Missing underlying asset mint'); - } - if (!this.stableAssetMint) { - throw new Error('Missing stable asset mint'); - } + if (!this.underlyingAssetMint) + throw Error('Underlying asset mint is not defined'); + if (!this.stableAssetMint) throw Error('Stable asset mint is not defined'); + const mintInfoPda = this.convergence .rfqs() .pdas() @@ -186,6 +187,11 @@ export class PsyoptionsAmericanInstrument implements LegInstrument { .pdas() .mintInfo({ mint: this.stableAssetMint }); return [ + { + pubkey: this.getProgramId(), + isSigner: false, + isWritable: false, + }, { pubkey: this.optionMetaPubKey, isSigner: false, isWritable: false }, { pubkey: mintInfoPda, diff --git a/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/instrument.ts b/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/instrument.ts index 7e5a69cc1..b63d1a772 100644 --- a/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/instrument.ts +++ b/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/instrument.ts @@ -125,13 +125,13 @@ export class PsyoptionsEuropeanInstrument implements LegInstrument { getSide = () => this.side; async getPreparationsBeforeRfqCreation(): Promise { if (!this.underlyingAssetMint) { - throw new Error('Missing underlying asset mint'); + throw Error('Underlying asset mint is undefined'); } if (!this.stableAssetMint) { - throw new Error('Missing stable asset mint'); + throw Error('Stable asset mint is undefined'); } if (!this.oracleAddress) { - throw new Error('Missing oracle address'); + throw Error('Oracle address is undefined'); } const optionMarketIxs = await getPsyEuropeanMarketIxs( this.convergence, @@ -148,6 +148,10 @@ export class PsyoptionsEuropeanInstrument implements LegInstrument { return optionMarketIxs; } + getExchangeAssetMint(): PublicKey { + return this.optionMint; + } + static async create( convergence: Convergence, underlyingMint: Mint, @@ -213,10 +217,14 @@ export class PsyoptionsEuropeanInstrument implements LegInstrument { /** Helper method to get validation accounts for a Psyoptions European instrument. */ getValidationAccounts() { - if (!this.underlyingAssetMint) { - throw new Error('Missing underlying asset mint'); - } + if (!this.underlyingAssetMint) + throw Error('Underlying asset mint is undefined'); return [ + { + pubkey: this.getProgramId(), + isSigner: false, + isWritable: false, + }, { pubkey: this.optionMetaPubKey, isSigner: false, isWritable: false }, { pubkey: this.convergence diff --git a/packages/js/src/plugins/rfqModule/helpers.ts b/packages/js/src/plugins/rfqModule/helpers.ts index a0d89a1df..7f631349e 100644 --- a/packages/js/src/plugins/rfqModule/helpers.ts +++ b/packages/js/src/plugins/rfqModule/helpers.ts @@ -1,6 +1,5 @@ -import { AccountMeta } from '@solana/web3.js'; import { Sha256 } from '@aws-crypto/sha256-js'; -import { Leg } from '@convergence-rfq/rfq'; +import { AccountMeta } from '@solana/web3.js'; import { Confirmation, Quote, @@ -9,13 +8,10 @@ import { isQuoteStandard, } from '../rfqModule/models'; import { UnparsedAccount } from '../../types'; -import { Convergence } from '../../Convergence'; import { LegInstrument, getSerializedLegLength, - getValidationAccounts, serializeAsLeg, - toLeg, } from '../instrumentModule'; import { LEG_MULTIPLIER_DECIMALS } from './constants'; import { Rfq, Response, isFixedSizeOpen } from './models'; @@ -81,65 +77,6 @@ export const calculateExpectedLegsSize = ( ); }; -// TODO remove -export const instrumentsToLegsAndLegsSize = ( - instruments: LegInstrument[] -): [Leg[], number] => { - return [ - instrumentsToLegs(instruments), - calculateExpectedLegsSize(instruments), - ]; -}; - -export const instrumentsToLegs = (instruments: LegInstrument[]): Leg[] => { - return instruments.map((i) => toLeg(i)); -}; - -// TODO remove -export const instrumentsToLegsAndExpectedLegsHash = ( - instruments: LegInstrument[] -): [Leg[], Uint8Array] => { - return [ - instrumentsToLegs(instruments), - calculateExpectedLegsHash(instruments), - ]; -}; - -export const legsToBaseAssetAccounts = ( - convergence: Convergence, - legs: Leg[] -): AccountMeta[] => { - const baseAssetAccounts: AccountMeta[] = []; - - for (const leg of legs) { - const baseAsset = convergence - .protocol() - .pdas() - .baseAsset({ index: leg.baseAssetIndex.value }); - - const baseAssetAccount: AccountMeta = { - pubkey: baseAsset, - isSigner: false, - isWritable: false, - }; - - baseAssetAccounts.push(baseAssetAccount); - } - - return baseAssetAccounts; -}; - -// TODO remove async part after option instruments refactoring -export const instrumentsToLegAccounts = async ( - instruments: LegInstrument[] -): Promise => { - const accounts = await Promise.all( - instruments.map((i) => getValidationAccounts(i)) - ); - - return accounts.flat(); -}; - export const sortByActiveAndExpiry = (rfqs: Rfq[]) => { return rfqs .sort((a, b) => { @@ -188,3 +125,17 @@ export function extractLegsMultiplier( } throw new Error('Invalid fixed size'); } + +export const removeDuplicateAccountMeta = ( + accountMeta: AccountMeta[] +): AccountMeta[] => { + const uniqueAccountMeta: AccountMeta[] = []; + for (let i = 0; i < accountMeta.length; i++) { + if ( + !uniqueAccountMeta.find((x) => x.pubkey.equals(accountMeta[i].pubkey)) + ) { + uniqueAccountMeta.push(accountMeta[i]); + } + } + return uniqueAccountMeta; +}; diff --git a/packages/js/src/plugins/rfqModule/models/Rfq.ts b/packages/js/src/plugins/rfqModule/models/Rfq.ts index d53e04e06..92ba8aa03 100644 --- a/packages/js/src/plugins/rfqModule/models/Rfq.ts +++ b/packages/js/src/plugins/rfqModule/models/Rfq.ts @@ -98,12 +98,16 @@ export const toRfq = async ( convergence: Convergence, account: RfqAccount ): Promise => { - const quoteAsset = await SpotQuoteInstrument.parseFromQuote( + const quoteAsset = SpotQuoteInstrument.parseFromQuote( convergence, account.data.quoteAsset ); const collateralMint = await collateralMintCache.get(convergence); const collateralDecimals = collateralMint.decimals; + const legs = account.data.legs.map((leg) => + convergence.parseLegInstrument(leg) + ); + return { model: 'rfq', address: account.publicKey, @@ -130,6 +134,6 @@ export const toRfq = async ( totalResponses: account.data.totalResponses, clearedResponses: account.data.clearedResponses, confirmedResponses: account.data.confirmedResponses, - legs: account.data.legs.map((leg) => convergence.parseLegInstrument(leg)), + legs, }; }; diff --git a/packages/js/src/plugins/rfqModule/operations/addLegsToRfq.ts b/packages/js/src/plugins/rfqModule/operations/addLegsToRfq.ts index 78bf2d36e..b7bdeb6b5 100644 --- a/packages/js/src/plugins/rfqModule/operations/addLegsToRfq.ts +++ b/packages/js/src/plugins/rfqModule/operations/addLegsToRfq.ts @@ -1,8 +1,7 @@ import { createAddLegsToRfqInstruction } from '@convergence-rfq/rfq'; -import { PublicKey, AccountMeta } from '@solana/web3.js'; +import { PublicKey } from '@solana/web3.js'; import { SendAndConfirmTransactionResponse } from '../../rpcModule'; -import { instrumentsToLegAccounts, instrumentsToLegs } from '../helpers'; import { Convergence } from '../../../Convergence'; import { Operation, @@ -15,7 +14,11 @@ import { TransactionBuilder, TransactionBuilderOptions, } from '../../../utils/TransactionBuilder'; -import { LegInstrument } from '../../../plugins/instrumentModule'; +import { + LegInstrument, + getBaseAssetAccount, + toLeg, +} from '@/plugins/instrumentModule'; const Key = 'AddLegsToRfqOperation' as const; @@ -133,26 +136,14 @@ export const addLegsToRfqBuilder = async ( const protocol = protocolPdaClient.protocol(); const { taker = convergence.identity(), instruments, rfq } = params; - const legs = instrumentsToLegs(instruments); - const legAccounts = await instrumentsToLegAccounts(instruments); - - const baseAssetAccounts: AccountMeta[] = []; - const baseAssetIndexValues = []; - for (const leg of legs) { - baseAssetIndexValues.push(leg.baseAssetIndex.value); - } - - for (const value of baseAssetIndexValues) { - const baseAsset = convergence.protocol().pdas().baseAsset({ index: value }); - - const baseAssetAccount: AccountMeta = { - pubkey: baseAsset, - isSigner: false, - isWritable: false, - }; + const legs = instruments.map((ins) => toLeg(ins)); + const legAccounts = instruments + .map((ins) => ins.getValidationAccounts()) + .flat(); - baseAssetAccounts.push(baseAssetAccount); - } + const baseAssetAccounts = instruments.map((instrument) => + getBaseAssetAccount(instrument, convergence) + ); const rfqProgram = convergence.programs().getRfq(programs); diff --git a/packages/js/src/plugins/rfqModule/operations/cleanUpResponse.ts b/packages/js/src/plugins/rfqModule/operations/cleanUpResponse.ts index 37442530b..4b2a18b7b 100644 --- a/packages/js/src/plugins/rfqModule/operations/cleanUpResponse.ts +++ b/packages/js/src/plugins/rfqModule/operations/cleanUpResponse.ts @@ -11,7 +11,6 @@ import { } from '../../../types'; import { TransactionBuilder, TransactionBuilderOptions } from '../../../utils'; import { InstrumentPdasClient } from '../../instrumentModule/InstrumentPdasClient'; -import { legToBaseAssetMint } from '@/plugins/instrumentModule'; import { SendAndConfirmTransactionResponse } from '@/plugins'; const Key = 'cleanUpResponseOperation' as const; @@ -163,7 +162,7 @@ export const cleanUpResponseBuilder = async ( index: i, }); - const baseAssetMint = await legToBaseAssetMint(convergence, leg); + const exchangeAssetMint = leg.getExchangeAssetMint(); const legAccounts: AccountMeta[] = [ { pubkey: firstToPrepare, @@ -177,7 +176,7 @@ export const cleanUpResponseBuilder = async ( }, { pubkey: convergence.tokens().pdas().associatedTokenAccount({ - mint: baseAssetMint!.address, + mint: exchangeAssetMint, owner: dao, programs, }), diff --git a/packages/js/src/plugins/rfqModule/operations/cleanUpResponseLegs.ts b/packages/js/src/plugins/rfqModule/operations/cleanUpResponseLegs.ts index d5c4bd846..56e7b5ae9 100644 --- a/packages/js/src/plugins/rfqModule/operations/cleanUpResponseLegs.ts +++ b/packages/js/src/plugins/rfqModule/operations/cleanUpResponseLegs.ts @@ -17,7 +17,6 @@ import { import { getOrCreateATA } from '../../../utils/ata'; import { InstrumentPdasClient } from '../../instrumentModule/InstrumentPdasClient'; import { protocolCache } from '../../protocolModule/cache'; -import { legToBaseAssetMint } from '@/plugins/instrumentModule'; const Key = 'CleanUpResponseLegsOperation' as const; @@ -176,7 +175,7 @@ export const cleanUpResponseLegsBuilder = async ( }); const leg = rfqModel.legs[i]; - const baseAssetMint = await legToBaseAssetMint(convergence, leg); + const exchangeAssetMint = leg.getExchangeAssetMint(); const legAccounts: AccountMeta[] = [ { @@ -192,7 +191,7 @@ export const cleanUpResponseLegsBuilder = async ( { pubkey: await getOrCreateATA( convergence, - baseAssetMint!.address, + exchangeAssetMint, protocol.authority ), isSigner: false, diff --git a/packages/js/src/plugins/rfqModule/operations/confirmResponse.ts b/packages/js/src/plugins/rfqModule/operations/confirmResponse.ts index 81bb25098..1aaeefa15 100644 --- a/packages/js/src/plugins/rfqModule/operations/confirmResponse.ts +++ b/packages/js/src/plugins/rfqModule/operations/confirmResponse.ts @@ -1,5 +1,5 @@ import { createConfirmResponseInstruction } from '@convergence-rfq/rfq'; -import { PublicKey, AccountMeta, ComputeBudgetProgram } from '@solana/web3.js'; +import { PublicKey, ComputeBudgetProgram } from '@solana/web3.js'; import { SendAndConfirmTransactionResponse } from '../../rpcModule'; import { Convergence } from '../../../Convergence'; @@ -16,6 +16,11 @@ import { } from '../../../utils/TransactionBuilder'; import { ResponseSide, toSolitaQuoteSide } from '../models/ResponseSide'; import { toSolitaOverrideLegMultiplierBps } from '../models/Confirmation'; +import { removeDuplicateAccountMeta } from '../helpers'; +import { + getBaseAssetAccount, + getOracleAccount, +} from '@/plugins/instrumentModule'; const Key = 'ConfirmResponseOperation' as const; @@ -193,35 +198,18 @@ export const confirmResponseBuilder = async ( programs, }); - const baseAssetIndexValuesSet: Set = new Set(); const rfqModel = await convergence.rfqs().findRfqByAddress({ address: rfq }); - for (const leg of rfqModel.legs) { - baseAssetIndexValuesSet.add(leg.getBaseAssetIndex().value); - } - - const baseAssetAccounts: AccountMeta[] = []; - const oracleAccounts: AccountMeta[] = []; - const baseAssetIndexValues = Array.from(baseAssetIndexValuesSet); - for (const index of baseAssetIndexValues) { - const baseAsset = convergence.protocol().pdas().baseAsset({ index }); - baseAssetAccounts.push({ - pubkey: baseAsset, - isSigner: false, - isWritable: false, - }); - - const baseAssetModel = await convergence - .protocol() - .findBaseAssetByAddress({ address: baseAsset }); - if (baseAssetModel.priceOracle.address) { - oracleAccounts.push({ - pubkey: baseAssetModel.priceOracle.address, - isSigner: false, - isWritable: false, - }); - } - } + const baseAssetAccounts = removeDuplicateAccountMeta( + rfqModel.legs.map((leg) => getBaseAssetAccount(leg, convergence)) + ); + const oracleAccounts = removeDuplicateAccountMeta( + await Promise.all( + baseAssetAccounts.map((baseAsset) => + getOracleAccount(baseAsset.pubkey, convergence) + ) + ) + ); return TransactionBuilder.make() .setFeePayer(payer) diff --git a/packages/js/src/plugins/rfqModule/operations/createRfq.ts b/packages/js/src/plugins/rfqModule/operations/createRfq.ts index 77d97f576..edcd4ff09 100644 --- a/packages/js/src/plugins/rfqModule/operations/createRfq.ts +++ b/packages/js/src/plugins/rfqModule/operations/createRfq.ts @@ -7,9 +7,6 @@ import { SendAndConfirmTransactionResponse } from '../../rpcModule'; import { assertRfq, FixedSize, Rfq, toSolitaFixedSize } from '../models'; import { calculateExpectedLegsHash, - instrumentsToLegAccounts, - legsToBaseAssetAccounts, - instrumentsToLegs, calculateExpectedLegsSize, } from '../helpers'; import { @@ -26,9 +23,11 @@ import { } from '../../../types'; import { Convergence } from '../../../Convergence'; import { + getBaseAssetAccount, LegInstrument, // LegInstrumentInputData, QuoteInstrument, + toLeg, toQuote, } from '../../../plugins/instrumentModule'; import { OrderType, toSolitaOrderType } from '../models/OrderType'; @@ -308,8 +307,7 @@ export const createRfqBuilder = async ( } = params; let { expectedLegsSize } = params; - const legs = instrumentsToLegs(instruments); - + const legs = instruments.map((ins) => toLeg(ins)); const expectedLegsSizeValue = calculateExpectedLegsSize(instruments); expectedLegsSize = expectedLegsSize ?? expectedLegsSizeValue; @@ -335,8 +333,12 @@ export const createRfqBuilder = async ( }, ]; - let baseAssetAccounts = legsToBaseAssetAccounts(convergence, legs); - let legAccounts = await instrumentsToLegAccounts(instruments); + let baseAssetAccounts = instruments.map((ins) => + getBaseAssetAccount(ins, convergence) + ); + let legAccounts = instruments + .map((ins) => ins.getValidationAccounts()) + .flat(); let rfqBuilder = TransactionBuilder.make() .setFeePayer(payer) @@ -379,8 +381,12 @@ export const createRfqBuilder = async ( while (!rfqBuilder.checkTransactionFits()) { instrumentsToAdd = instrumentsToAdd.slice(0, instrumentsToAdd.length - 1); legsToAdd = legsToAdd.slice(0, instrumentsToAdd.length); - legAccounts = await instrumentsToLegAccounts(instrumentsToAdd); - baseAssetAccounts = legsToBaseAssetAccounts(convergence, legsToAdd); + legAccounts = instrumentsToAdd + .map((ins) => ins.getValidationAccounts()) + .flat(); + baseAssetAccounts = instrumentsToAdd.map((i) => + getBaseAssetAccount(i, convergence) + ); rfqBuilder = TransactionBuilder.make() .setFeePayer(payer) .setContext({ @@ -418,7 +424,6 @@ export const createRfqBuilder = async ( } const remainingLegsToAdd = instruments.slice(legsToAdd.length, legs.length); - return { createRfqTxBuilder: rfqBuilder, remainingLegsToAdd, diff --git a/packages/js/src/plugins/rfqModule/operations/finalizeRfqConstruction.ts b/packages/js/src/plugins/rfqModule/operations/finalizeRfqConstruction.ts index 5e43011c0..5d442ac05 100644 --- a/packages/js/src/plugins/rfqModule/operations/finalizeRfqConstruction.ts +++ b/packages/js/src/plugins/rfqModule/operations/finalizeRfqConstruction.ts @@ -16,7 +16,12 @@ import { Signer, } from '../../../types'; import { Convergence } from '../../../Convergence'; -import { LegInstrument } from '@/plugins/instrumentModule'; +import { removeDuplicateAccountMeta } from '../helpers'; +import { + getBaseAssetAccount, + getOracleAccount, + LegInstrument, +} from '@/plugins/instrumentModule'; const Key = 'FinalizeRfqConstructionOperation' as const; @@ -195,7 +200,6 @@ export const finalizeRfqConstructionBuilder = async ( collateralInfo = collateralInfo ?? collateralInfoPda; collateralToken = collateralToken ?? collateralTokenPda; - const anchorRemainingAccounts: AccountMeta[] = []; const protocol = convergence.protocol().pdas().protocol(); @@ -211,46 +215,22 @@ export const finalizeRfqConstructionBuilder = async ( isWritable: false, }; - const oracleAccounts: AccountMeta[] = []; - - const baseAssetAccounts: AccountMeta[] = []; - const baseAssetIndexValuesSet: Set = new Set(); - - for (const leg of legs) { - baseAssetIndexValuesSet.add(leg.getBaseAssetIndex().value); - } - - const baseAssetIndexValues = Array.from(baseAssetIndexValuesSet); - - for (const index of baseAssetIndexValues) { - const baseAsset = convergence.protocol().pdas().baseAsset({ index }); - const baseAssetAccount: AccountMeta = { - pubkey: baseAsset, - isSigner: false, - isWritable: false, - }; - - baseAssetAccounts.push(baseAssetAccount); - - const baseAssetModel = await convergence - .protocol() - .findBaseAssetByAddress({ address: baseAsset }); - - if (baseAssetModel.priceOracle.address) { - oracleAccounts.push({ - pubkey: baseAssetModel.priceOracle.address, - isSigner: false, - isWritable: false, - }); - } - } + const baseAssetAccounts = removeDuplicateAccountMeta( + legs.map((leg) => getBaseAssetAccount(leg, convergence)) + ); + const oracleAccounts = removeDuplicateAccountMeta( + await Promise.all( + baseAssetAccounts.map((baseAsset) => + getOracleAccount(baseAsset.pubkey, convergence) + ) + ) + ); anchorRemainingAccounts.push( configAccount, ...baseAssetAccounts, ...oracleAccounts ); - return TransactionBuilder.make() .setFeePayer(payer) .setContext({ diff --git a/packages/js/src/plugins/rfqModule/operations/partiallySettleLegs.ts b/packages/js/src/plugins/rfqModule/operations/partiallySettleLegs.ts index 3e8571e35..07a2d233f 100644 --- a/packages/js/src/plugins/rfqModule/operations/partiallySettleLegs.ts +++ b/packages/js/src/plugins/rfqModule/operations/partiallySettleLegs.ts @@ -16,7 +16,6 @@ import { TransactionBuilderOptions, } from '../../../utils/TransactionBuilder'; import { InstrumentPdasClient } from '../../instrumentModule'; -import { legToBaseAssetMint } from '@/plugins/instrumentModule'; const Key = 'PartiallySettleLegsOperation' as const; @@ -178,7 +177,7 @@ export const partiallySettleLegsBuilder = async ( rfqModel, }); - const baseAssetMint = await legToBaseAssetMint(convergence, leg); + const exchangeAssetMint = leg.getExchangeAssetMint(); const legAccounts: AccountMeta[] = [ //`escrow` @@ -193,7 +192,7 @@ export const partiallySettleLegsBuilder = async ( .tokens() .pdas() .associatedTokenAccount({ - mint: baseAssetMint!.address, + mint: exchangeAssetMint, owner: receiver === 'maker' ? maker : taker, programs, }), diff --git a/packages/js/src/plugins/rfqModule/operations/partlyRevertSettlementPreparation.ts b/packages/js/src/plugins/rfqModule/operations/partlyRevertSettlementPreparation.ts index 20eeaa70b..53612712a 100644 --- a/packages/js/src/plugins/rfqModule/operations/partlyRevertSettlementPreparation.ts +++ b/packages/js/src/plugins/rfqModule/operations/partlyRevertSettlementPreparation.ts @@ -17,7 +17,6 @@ import { } from '../../../utils/TransactionBuilder'; import { InstrumentPdasClient } from '../../instrumentModule'; import { AuthoritySide, toSolitaAuthoritySide } from '../models/AuthoritySide'; -import { legToBaseAssetMint } from '@/plugins/instrumentModule'; const Key = 'PartlyRevertSettlementPreparationOperation' as const; @@ -179,7 +178,7 @@ export const partlyRevertSettlementPreparationBuilder = async ( }; const leg = rfqModel.legs[i]; - const baseAssetMint = await legToBaseAssetMint(convergence, leg); + const exchangeAssetMint = leg.getExchangeAssetMint(); const legAccounts: AccountMeta[] = [ //`escrow` @@ -194,7 +193,7 @@ export const partlyRevertSettlementPreparationBuilder = async ( .tokens() .pdas() .associatedTokenAccount({ - mint: baseAssetMint!.address, + mint: exchangeAssetMint, owner: side === 'maker' ? responseModel.maker : rfqModel.taker, programs, }), diff --git a/packages/js/src/plugins/rfqModule/operations/prepareMoreLegsSettlement.ts b/packages/js/src/plugins/rfqModule/operations/prepareMoreLegsSettlement.ts index 14287a14c..73a2d8068 100644 --- a/packages/js/src/plugins/rfqModule/operations/prepareMoreLegsSettlement.ts +++ b/packages/js/src/plugins/rfqModule/operations/prepareMoreLegsSettlement.ts @@ -26,7 +26,6 @@ import { } from '../../../utils/TransactionBuilder'; import { getOrCreateATA } from '../../../utils/ata'; import { InstrumentPdasClient } from '../../instrumentModule'; -import { legToBaseAssetMint } from '@/plugins/instrumentModule'; const Key = 'PrepareMoreLegsSettlementOperation' as const; @@ -208,7 +207,7 @@ export const prepareMoreLegsSettlementBuilder = async ( }); const leg = rfqModel.legs[i]; - const baseAssetMint = await legToBaseAssetMint(convergence, leg); + const exchangeAssetMint = leg.getExchangeAssetMint(); const legAccounts: AccountMeta[] = [ // `caller @@ -219,14 +218,9 @@ export const prepareMoreLegsSettlementBuilder = async ( }, // `caller_token_account` { - // pubkey: convergence.tokens().pdas().associatedTokenAccount({ - // mint: baseAssetMint!.address, - // owner: caller.publicKey, - // programs, - // }), pubkey: await getOrCreateATA( convergence, - baseAssetMint!.address, + exchangeAssetMint, caller.publicKey, programs ), @@ -235,7 +229,7 @@ export const prepareMoreLegsSettlementBuilder = async ( }, // `mint` { - pubkey: baseAssetMint!.address, + pubkey: exchangeAssetMint, isSigner: false, isWritable: false, }, diff --git a/packages/js/src/plugins/rfqModule/operations/prepareSettlement.ts b/packages/js/src/plugins/rfqModule/operations/prepareSettlement.ts index 03272f574..2a7dc4845 100644 --- a/packages/js/src/plugins/rfqModule/operations/prepareSettlement.ts +++ b/packages/js/src/plugins/rfqModule/operations/prepareSettlement.ts @@ -26,9 +26,7 @@ import { } from '../../../utils/TransactionBuilder'; 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, @@ -37,6 +35,7 @@ import { prepareEuropeanOptions, psyoptionsEuropeanInstrumentProgram, } from '@/plugins/psyoptionsEuropeanInstrumentModule'; +import { Mint } from '@/plugins/tokenModule/models'; const Key = 'PrepareSettlementOperation' as const; @@ -205,10 +204,13 @@ export const prepareSettlementBuilder = async ( const spotInstrumentProgram = convergence.programs().getSpotInstrument(); - const baseAssetMints: Mint[] = []; - + const exchangeAssetMints: Mint[] = []; for (const leg of rfqModel.legs) { - baseAssetMints.push(await legToBaseAssetMint(convergence, leg)); + const exchangeAssetMintPubkey = leg.getExchangeAssetMint(); + const exchangeAssetMint = await convergence + .tokens() + .findMintByAddress({ address: exchangeAssetMintPubkey }); + exchangeAssetMints.push(exchangeAssetMint); } const anchorRemainingAccounts: AccountMeta[] = []; @@ -272,7 +274,7 @@ export const prepareSettlementBuilder = async ( const { ataPubKey, txBuilder } = await getOrCreateATAtxBuilder( convergence, - baseAssetMints[legIndex].address, + exchangeAssetMints[legIndex].address, caller.publicKey, programs ); @@ -295,7 +297,7 @@ export const prepareSettlementBuilder = async ( }, // `mint` { - pubkey: baseAssetMints[legIndex].address, + pubkey: exchangeAssetMints[legIndex].address, isSigner: false, isWritable: false, }, diff --git a/packages/js/src/plugins/rfqModule/operations/respondToRfq.ts b/packages/js/src/plugins/rfqModule/operations/respondToRfq.ts index 529505697..e0319f051 100644 --- a/packages/js/src/plugins/rfqModule/operations/respondToRfq.ts +++ b/packages/js/src/plugins/rfqModule/operations/respondToRfq.ts @@ -1,5 +1,5 @@ import { createRespondToRfqInstruction } from '@convergence-rfq/rfq'; -import { PublicKey, AccountMeta, ComputeBudgetProgram } from '@solana/web3.js'; +import { PublicKey, ComputeBudgetProgram } from '@solana/web3.js'; import { SendAndConfirmTransactionResponse } from '../../rpcModule'; import { assertResponse, Response } from '../models/Response'; @@ -17,6 +17,11 @@ import { } from '../../../utils/TransactionBuilder'; import { Quote, Rfq } from '../models'; import { toSolitaQuote } from '../models/Quote'; +import { removeDuplicateAccountMeta } from '../helpers'; +import { + getBaseAssetAccount, + getOracleAccount, +} from '@/plugins/instrumentModule'; const getNextResponsePdaAndDistinguisher = async ( cvg: Convergence, @@ -257,35 +262,16 @@ export const respondToRfqBuilder = async ( rfqModel ); - // TODO: DRY - const baseAssetIndexValuesSet: Set = new Set(); - for (const leg of rfqModel.legs) { - baseAssetIndexValuesSet.add(leg.getBaseAssetIndex().value); - } - const baseAssetAccounts: AccountMeta[] = []; - const baseAssetIndexValues = Array.from(baseAssetIndexValuesSet); - const oracleAccounts: AccountMeta[] = []; - for (const index of baseAssetIndexValues) { - const baseAsset = convergence.protocol().pdas().baseAsset({ index }); - const baseAssetAccount: AccountMeta = { - pubkey: baseAsset, - isSigner: false, - isWritable: false, - }; - - baseAssetAccounts.push(baseAssetAccount); - - const baseAssetModel = await convergence - .protocol() - .findBaseAssetByAddress({ address: baseAsset }); - if (baseAssetModel.priceOracle.address) { - oracleAccounts.push({ - pubkey: baseAssetModel.priceOracle.address, - isSigner: false, - isWritable: false, - }); - } - } + const baseAssetAccounts = removeDuplicateAccountMeta( + rfqModel.legs.map((leg) => getBaseAssetAccount(leg, convergence)) + ); + const oracleAccounts = removeDuplicateAccountMeta( + await Promise.all( + baseAssetAccounts.map((baseAsset) => + getOracleAccount(baseAsset.pubkey, convergence) + ) + ) + ); return TransactionBuilder.make() .setFeePayer(maker) diff --git a/packages/js/src/plugins/rfqModule/operations/revertSettlementPreparation.ts b/packages/js/src/plugins/rfqModule/operations/revertSettlementPreparation.ts index 68e6e51d3..c0fc784ac 100644 --- a/packages/js/src/plugins/rfqModule/operations/revertSettlementPreparation.ts +++ b/packages/js/src/plugins/rfqModule/operations/revertSettlementPreparation.ts @@ -17,7 +17,6 @@ import { } from '../../../utils/TransactionBuilder'; import { InstrumentPdasClient } from '../../instrumentModule'; import { AuthoritySide, toSolitaAuthoritySide } from '../models/AuthoritySide'; -import { legToBaseAssetMint } from '@/plugins/instrumentModule'; const Key = 'RevertSettlementPreparationOperation' as const; @@ -173,7 +172,7 @@ export const revertSettlementPreparationBuilder = async ( const leg = rfqModel.legs[i]; - const baseAssetMint = await legToBaseAssetMint(convergence, leg); + const exchangeAssetMint = leg.getExchangeAssetMint(); const legAccounts: AccountMeta[] = [ //`escrow` @@ -188,7 +187,7 @@ export const revertSettlementPreparationBuilder = async ( .tokens() .pdas() .associatedTokenAccount({ - mint: baseAssetMint!.address, + mint: exchangeAssetMint, owner: side === 'maker' ? responseModel.maker : rfqModel.taker, programs, }), diff --git a/packages/js/src/plugins/rfqModule/operations/settle.ts b/packages/js/src/plugins/rfqModule/operations/settle.ts index 831947092..ce3622bf3 100644 --- a/packages/js/src/plugins/rfqModule/operations/settle.ts +++ b/packages/js/src/plugins/rfqModule/operations/settle.ts @@ -16,7 +16,6 @@ import { TransactionBuilderOptions, } from '../../../utils/TransactionBuilder'; import { InstrumentPdasClient } from '../../instrumentModule'; -import { legToBaseAssetMint } from '@/plugins/instrumentModule'; const Key = 'SettleOperation' as const; @@ -162,7 +161,7 @@ export const settleBuilder = async ( const leg = rfqModel.legs[legIndex]; const { receiver } = legs[legIndex]; - const baseAssetMint = await legToBaseAssetMint(convergence, leg); + const exchangeAssetMint = leg.getExchangeAssetMint(); const instrumentProgramAccount: AccountMeta = { pubkey: rfqModel.legs[legIndex].getProgramId(), @@ -191,7 +190,7 @@ export const settleBuilder = async ( .tokens() .pdas() .associatedTokenAccount({ - mint: baseAssetMint!.address, + mint: exchangeAssetMint, owner: receiver === 'maker' ? maker : taker, programs, }), diff --git a/packages/js/src/plugins/spotInstrumentModule/instruments.ts b/packages/js/src/plugins/spotInstrumentModule/instruments.ts index 11ac793a3..e3dd54856 100644 --- a/packages/js/src/plugins/spotInstrumentModule/instruments.ts +++ b/packages/js/src/plugins/spotInstrumentModule/instruments.ts @@ -50,6 +50,10 @@ export class SpotLegInstrument implements LegInstrument { return []; } + getExchangeAssetMint(): PublicKey { + return this.mintAddress; + } + static async create( convergence: Convergence, mint: Mint, @@ -89,7 +93,14 @@ export class SpotLegInstrument implements LegInstrument { .rfqs() .pdas() .mintInfo({ mint: this.mintAddress }); - return [{ pubkey: mintInfo, isSigner: false, isWritable: false }]; + return [ + { + pubkey: this.getProgramId(), + isSigner: false, + isWritable: false, + }, + { pubkey: mintInfo, isSigner: false, isWritable: false }, + ]; } /** Helper method to serialize the instrument data for this instrument. */ @@ -132,10 +143,10 @@ export class SpotQuoteInstrument implements QuoteInstrument { getProgramId = () => this.convergence.programs().getSpotInstrument().address; getDecimals = () => this.decimals; - static async parseFromQuote( + static parseFromQuote( convergence: Convergence, quote: QuoteAsset - ): Promise { + ): QuoteInstrument { const { instrumentData, instrumentDecimals } = quote; const { mintAddress } = SpotLegInstrument.deserializeInstrumentData( Buffer.from(instrumentData) diff --git a/packages/js/tests/helpers.ts b/packages/js/tests/helpers.ts index b41d2de09..554cdb544 100644 --- a/packages/js/tests/helpers.ts +++ b/packages/js/tests/helpers.ts @@ -128,7 +128,6 @@ export const createAmericanCoveredCallRfq = async ( fixedSize: { type: 'fixed-base', amount: 1 }, quoteAsset: await SpotQuoteInstrument.create(cvg, quoteMint), }); - return { rfq, response }; }; diff --git a/packages/js/tests/integration/psyoptionsAmerican.spec.ts b/packages/js/tests/integration/psyoptionsAmerican.spec.ts index 2b9a21b7c..e441bfe25 100644 --- a/packages/js/tests/integration/psyoptionsAmerican.spec.ts +++ b/packages/js/tests/integration/psyoptionsAmerican.spec.ts @@ -66,7 +66,6 @@ describe('integration.psyoptionsAmerican', () => { const settlementResponse = await settleRfq(takerCvg, rfq, rfqResponse); expect(settlementResponse.response).toHaveProperty('signature'); - // TODO: Check balances });