From ac2f5619eb4ffcf6f9a194a047467316400b6abc Mon Sep 17 00:00:00 2001 From: lowkeynicc <85139158+lowkeynicc@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:52:53 -0500 Subject: [PATCH] sdk: add option to get signed settlepnl tix from a market order (#813) * sdk: add option to get signed settlepnl tix from a market order * fix lint --- CHANGELOG.md | 1 + sdk/src/driftClient.ts | 165 +++++++++++++++++++++++++++++++++-------- sdk/src/tx/utils.ts | 32 ++++++++ 3 files changed, 169 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a20c81e1a..0412246fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - sdk: move bracket orders into single instruction - sdk: add ability to do placeAndTake order with bracket orders attached - sdk: add option to cancel existing orders in market for place and take order +- sdk: add option to get signed settlePnl tx back from a market order - program: increase full perp liquidation threshold ([#807](https://github.com/drift-labs/protocol-v2/pull/807)) - program: remove spot fee pool transfer ([#800](https://github.com/drift-labs/protocol-v2/pull/800)) diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index f086d7a76..adcc374c0 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -88,7 +88,7 @@ import { DataAndSlot, } from './accounts/types'; import { TxSender, TxSigAndSlot } from './tx/types'; -import { wrapInTx } from './tx/utils'; +import { getSignedTransactionMap, wrapInTx } from './tx/utils'; import { BASE_PRECISION, PRICE_PRECISION, @@ -2565,11 +2565,13 @@ export class DriftClient { txParams?: TxParams, bracketOrdersParams = new Array(), referrerInfo?: ReferrerInfo, - cancelExistingOrders?: boolean + cancelExistingOrders?: boolean, + settlePnl?: boolean ): Promise<{ txSig: TransactionSignature; signedFillTx?: Transaction; signedCancelExistingOrdersTx?: Transaction; + signedSettlePnlTx?: Transaction; }> { const marketIndex = orderParams.marketIndex; const orderId = userAccount.nextOrderId; @@ -2579,10 +2581,10 @@ export class DriftClient { userAccount.subAccountId ); - let cancelOrdersIx: TransactionInstruction; - let cancelExistingOrdersTx: Transaction; + /* Cancel open orders in market if requested */ + let cancelExistingOrdersTx; if (cancelExistingOrders && isVariant(orderParams.marketType, 'perp')) { - cancelOrdersIx = await this.getCancelOrdersIx( + const cancelOrdersIx = await this.getCancelOrdersIx( orderParams.marketType, orderParams.marketIndex, null, @@ -2597,6 +2599,23 @@ export class DriftClient { ); } + /* Settle PnL after fill if requested */ + let settlePnlTx; + if (settlePnl && isVariant(orderParams.marketType, 'perp')) { + const settlePnlIx = await this.settlePNLIx( + userAccountPublicKey, + userAccount, + marketIndex + ); + + //@ts-ignore + settlePnlTx = await this.buildTransaction( + [settlePnlIx], + txParams, + this.txVersion + ); + } + // use versioned transactions if there is a lookup table account and wallet is compatible if (this.txVersion === 0) { const versionedMarketOrderTx = await this.buildTransaction( @@ -2623,17 +2642,31 @@ export class DriftClient { 0 ); - const [ + const allPossibleTxs = [ + versionedMarketOrderTx, + versionedFillTx, + cancelExistingOrdersTx, + settlePnlTx, + ]; + const txKeys = [ + 'signedVersionedMarketOrderTx', + 'signedVersionedFillTx', + 'signedCancelExistingOrdersTx', + 'signedSettlePnlTx', + ]; + + const { signedVersionedMarketOrderTx, signedVersionedFillTx, signedCancelExistingOrdersTx, - ] = await this.provider.wallet.signAllTransactions( - [ - versionedMarketOrderTx, - versionedFillTx, - cancelExistingOrdersTx, - ].filter((tx) => tx !== undefined) + signedSettlePnlTx, + } = await getSignedTransactionMap( + //@ts-ignore + this.provider.wallet, + allPossibleTxs, + txKeys ); + const { txSig, slot } = await this.txSender.sendRawTransaction( signedVersionedMarketOrderTx.serialize(), this.opts @@ -2646,6 +2679,8 @@ export class DriftClient { signedFillTx: signedVersionedFillTx, // @ts-ignore signedCancelExistingOrdersTx, + // @ts-ignore + signedSettlePnlTx, }; } else { const marketOrderTx = wrapInTx( @@ -2667,12 +2702,33 @@ export class DriftClient { cancelExistingOrdersTx.feePayer = userAccount.authority; } - const [signedMarketOrderTx, signedCancelExistingOrdersTx] = - await this.provider.wallet.signAllTransactions( - [marketOrderTx, cancelExistingOrdersTx].filter( - (tx) => tx !== undefined - ) - ); + if (settlePnlTx) { + settlePnlTx.recentBlockhash = currentBlockHash; + settlePnlTx.feePayer = userAccount.authority; + } + + const allPossibleTxs = [ + marketOrderTx, + cancelExistingOrdersTx, + settlePnlTx, + ]; + const txKeys = [ + 'signedMarketOrderTx', + 'signedCancelExistingOrdersTx', + 'signedSettlePnlTx', + ]; + + const { + signedMarketOrderTx, + signedCancelExistingOrdersTx, + signedSettlePnlTx, + } = await getSignedTransactionMap( + //@ts-ignore + this.provider.wallet, + allPossibleTxs, + txKeys + ); + const { txSig, slot } = await this.sendTransaction( signedMarketOrderTx, [], @@ -2681,7 +2737,14 @@ export class DriftClient { ); this.perpMarketLastSlotCache.set(orderParams.marketIndex, slot); - return { txSig, signedFillTx: undefined, signedCancelExistingOrdersTx }; + return { + txSig, + signedFillTx: undefined, + //@ts-ignore + signedCancelExistingOrdersTx, + //@ts-ignore + signedSettlePnlTx, + }; } } @@ -4330,13 +4393,14 @@ export class DriftClient { bracketOrdersParams = new Array(), txParams?: TxParams, subAccountId?: number, - cancelExistingOrders?: boolean + cancelExistingOrders?: boolean, + settlePnl?: boolean ): Promise<{ txSig: TransactionSignature; signedCancelExistingOrdersTx?: Transaction; + signedSettlePnlTx?: Transaction; }> { - let signedCancelExistingOrdersTx: Transaction; - + let cancelExistingOrdersTx: Transaction; if (cancelExistingOrders && isVariant(orderParams.marketType, 'perp')) { const cancelOrdersIx = await this.getCancelOrdersIx( orderParams.marketType, @@ -4345,15 +4409,32 @@ export class DriftClient { subAccountId ); - const cancelExistingOrdersTx = await this.buildTransaction( + //@ts-ignore + cancelExistingOrdersTx = await this.buildTransaction( [cancelOrdersIx], txParams, this.txVersion ); + } - // @ts-ignore - signedCancelExistingOrdersTx = await this.provider.wallet.signTransaction( - cancelExistingOrdersTx + /* Settle PnL after fill if requested */ + let settlePnlTx: Transaction; + if (settlePnl && isVariant(orderParams.marketType, 'perp')) { + const userAccountPublicKey = await this.getUserAccountPublicKey( + subAccountId + ); + + const settlePnlIx = await this.settlePNLIx( + userAccountPublicKey, + this.getUserAccount(subAccountId), + orderParams.marketIndex + ); + + //@ts-ignore + settlePnlTx = await this.buildTransaction( + [settlePnlIx], + txParams, + this.txVersion ); } @@ -4376,14 +4457,40 @@ export class DriftClient { ixs.push(bracketOrdersIx); } + const placeAndTakeTx = await this.buildTransaction(ixs, txParams); + + const allPossibleTxs = [ + placeAndTakeTx, + cancelExistingOrdersTx, + settlePnlTx, + ]; + const txKeys = [ + 'signedPlaceAndTakeTx', + 'signedCancelExistingOrdersTx', + 'signedSettlePnlTx', + ]; + + const { + signedPlaceAndTakeTx, + signedCancelExistingOrdersTx, + signedSettlePnlTx, + } = await getSignedTransactionMap( + //@ts-ignore + this.provider.wallet, + allPossibleTxs, + txKeys + ); + const { txSig, slot } = await this.sendTransaction( - await this.buildTransaction(ixs, txParams), + signedPlaceAndTakeTx, [], - this.opts + this.opts, + true ); this.perpMarketLastSlotCache.set(orderParams.marketIndex, slot); - return { txSig, signedCancelExistingOrdersTx }; + //@ts-ignore + return { txSig, signedCancelExistingOrdersTx, signedSettlePnlTx }; } public async getPlaceAndTakePerpOrderIx( diff --git a/sdk/src/tx/utils.ts b/sdk/src/tx/utils.ts index ae3437f69..cae58e362 100644 --- a/sdk/src/tx/utils.ts +++ b/sdk/src/tx/utils.ts @@ -1,7 +1,9 @@ +import { Wallet } from '@coral-xyz/anchor'; import { Transaction, TransactionInstruction, ComputeBudgetProgram, + VersionedTransaction, } from '@solana/web3.js'; const COMPUTE_UNITS_DEFAULT = 200_000; @@ -30,3 +32,33 @@ export function wrapInTx( return tx.add(instruction); } + +/* Helper function for signing multiple transactions where some may be undefined and mapping the output */ +export async function getSignedTransactionMap( + wallet: Wallet, + txsToSign: (Transaction | VersionedTransaction | undefined)[], + keys: string[] +): Promise<{ [key: string]: Transaction | VersionedTransaction | undefined }> { + const signedTxMap: { + [key: string]: Transaction | VersionedTransaction | undefined; + } = {}; + + const keysWithTx = []; + txsToSign.forEach((tx, index) => { + if (tx == undefined) { + signedTxMap[keys[index]] = undefined; + } else { + keysWithTx.push(keys[index]); + } + }); + + const signedTxs = await wallet.signAllTransactions( + txsToSign.filter((tx) => tx !== undefined) + ); + + signedTxs.forEach((signedTx, index) => { + signedTxMap[keysWithTx[index]] = signedTx; + }); + + return signedTxMap; +}