diff --git a/src/client.ts b/src/client.ts index ddf9379..dd9294a 100644 --- a/src/client.ts +++ b/src/client.ts @@ -40,9 +40,11 @@ import { SRM_DECIMALS } from '@project-serum/serum/lib/token-instructions'; import { Order } from '@project-serum/serum/lib/market'; import Wallet from '@project-serum/sol-wallet-adapter'; import { + makeBorrowInstruction, makeCancelOrderInstruction, makeForceCancelOrdersInstruction, makePartialLiquidateInstruction, makeSettleFundsInstruction, + makeWithdrawInstruction, } from './instruction'; import { Aggregator } from './schema'; import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; @@ -574,22 +576,18 @@ export class MangoClient { const tokenIndex = mangoGroup.getTokenIndex(token) const nativeQuantity = uiToNative(quantity, mangoGroup.mintDecimals[tokenIndex]) - const keys = [ - { isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey}, - { isSigner: false, isWritable: true, pubkey: marginAccount.publicKey }, - { isSigner: true, isWritable: false, pubkey: owner.publicKey }, - { isSigner: false, isWritable: true, pubkey: tokenAcc }, - { isSigner: false, isWritable: true, pubkey: mangoGroup.vaults[tokenIndex] }, - { isSigner: false, isWritable: false, pubkey: mangoGroup.signerKey }, - { isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID }, - { isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY }, - ...marginAccount.openOrders.map( (pubkey) => ( { isSigner: false, isWritable: false, pubkey })), - ...mangoGroup.oracles.map( (pubkey) => ( { isSigner: false, isWritable: false, pubkey })) - ] - const data = encodeMangoInstruction({Withdraw: {quantity: nativeQuantity}}) - - - const instruction = new TransactionInstruction( { keys, data, programId }) + const instruction = makeWithdrawInstruction( + programId, + mangoGroup.publicKey, + marginAccount.publicKey, + owner.publicKey, + mangoGroup.signerKey, + tokenAcc, + mangoGroup.vaults[tokenIndex], + marginAccount.openOrders, + mangoGroup.oracles, + nativeQuantity, + ); const transaction = new Transaction() transaction.add(instruction) @@ -611,18 +609,16 @@ export class MangoClient { const tokenIndex = mangoGroup.getTokenIndex(token) const nativeQuantity = uiToNative(quantity, mangoGroup.mintDecimals[tokenIndex]) - const keys = [ - { isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey}, - { isSigner: false, isWritable: true, pubkey: marginAccount.publicKey }, - { isSigner: true, isWritable: false, pubkey: owner.publicKey }, - { isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY }, - ...marginAccount.openOrders.map( (pubkey) => ( { isSigner: false, isWritable: false, pubkey })), - ...mangoGroup.oracles.map( (pubkey) => ( { isSigner: false, isWritable: false, pubkey })), - ] - const data = encodeMangoInstruction({Borrow: {tokenIndex: new BN(tokenIndex), quantity: nativeQuantity}}) - - - const instruction = new TransactionInstruction( { keys, data, programId }) + const instruction = makeBorrowInstruction( + programId, + mangoGroup.publicKey, + marginAccount.publicKey, + owner.publicKey, + tokenIndex, + marginAccount.openOrders, + mangoGroup.oracles, + nativeQuantity, + ); const transaction = new Transaction() transaction.add(instruction) @@ -631,6 +627,65 @@ export class MangoClient { return await this.sendTransaction(connection, transaction, owner, additionalSigners) } + async borrowAndWithdraw( + connection: Connection, + programId: PublicKey, + mangoGroup: MangoGroup, + marginAccount: MarginAccount, + wallet: Wallet | Account, + token: PublicKey, + tokenAcc: PublicKey, + + withdrawQuantity: number + ): Promise { + const transaction = new Transaction() + const tokenIndex = mangoGroup.getTokenIndex(token) + const tokenBalance = marginAccount.getUiDeposit(mangoGroup, tokenIndex) + const borrowQuantity = withdrawQuantity - tokenBalance + + const nativeBorrowQuantity = uiToNative( + borrowQuantity, + mangoGroup.mintDecimals[tokenIndex] + ) + + const borrowInstruction = makeBorrowInstruction( + programId, + mangoGroup.publicKey, + marginAccount.publicKey, + wallet.publicKey, + tokenIndex, + marginAccount.openOrders, + mangoGroup.oracles, + nativeBorrowQuantity, + ); + transaction.add(borrowInstruction); + + // uiToNative() uses Math.round resulting in rounding + // errors, so we use Math.floor here instead + const nativeWithdrawQuantity = new BN( + Math.floor( + withdrawQuantity * Math.pow(10, mangoGroup.mintDecimals[tokenIndex]), + ) * 0.98, + ); + + const withdrawInstruction = makeWithdrawInstruction( + programId, + mangoGroup.publicKey, + marginAccount.publicKey, + wallet.publicKey, + mangoGroup.signerKey, + tokenAcc, + mangoGroup.vaults[tokenIndex], + marginAccount.openOrders, + mangoGroup.oracles, + nativeWithdrawQuantity, + ); + transaction.add(withdrawInstruction); + + const signers = [] + return await this.sendTransaction(connection, transaction, wallet, signers) + } + async settleBorrow( connection: Connection, programId: PublicKey, diff --git a/src/instruction.ts b/src/instruction.ts index 8fd29b8..a515492 100644 --- a/src/instruction.ts +++ b/src/instruction.ts @@ -115,6 +115,85 @@ export function makeSettleFundsInstruction( return new TransactionInstruction({ keys, data, programId }); } +export function makeBorrowInstruction( + programId: PublicKey, + mangoGroupPk: PublicKey, + marginAccountPk: PublicKey, + walletPk: PublicKey, + tokenIndex: number, + openOrders: PublicKey[], + oracles: PublicKey[], + nativeQuantity: BN, +): TransactionInstruction { + const borrowKeys = [ + { isSigner: false, isWritable: true, pubkey: mangoGroupPk }, + { isSigner: false, isWritable: true, pubkey: marginAccountPk }, + { isSigner: true, isWritable: false, pubkey: walletPk }, + { isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY }, + ...openOrders.map((pubkey) => ({ + isSigner: false, + isWritable: false, + pubkey, + })), + ...oracles.map((pubkey) => ({ + isSigner: false, + isWritable: false, + pubkey, + })), + ]; + const borrowData = encodeMangoInstruction({ + Borrow: { tokenIndex: new BN(tokenIndex), quantity: nativeQuantity }, + }); + + return new TransactionInstruction({ + keys: borrowKeys, + data: borrowData, + programId, + }); +} + +export function makeWithdrawInstruction( + programId: PublicKey, + mangoGroupPk: PublicKey, + marginAccountPk: PublicKey, + walletPk: PublicKey, + signerKey: PublicKey, + tokenAccPk: PublicKey, + vaultPk: PublicKey, + openOrders: PublicKey[], + oracles: PublicKey[], + nativeQuantity: BN, +): TransactionInstruction { + const withdrawKeys = [ + { isSigner: false, isWritable: true, pubkey: mangoGroupPk }, + { isSigner: false, isWritable: true, pubkey: marginAccountPk }, + { isSigner: true, isWritable: false, pubkey: walletPk }, + { isSigner: false, isWritable: true, pubkey: tokenAccPk }, + { isSigner: false, isWritable: true, pubkey: vaultPk }, + { isSigner: false, isWritable: false, pubkey: signerKey }, + { isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID }, + { isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY }, + ...openOrders.map((pubkey) => ({ + isSigner: false, + isWritable: false, + pubkey, + })), + ...oracles.map((pubkey) => ({ + isSigner: false, + isWritable: false, + pubkey, + })), + ]; + const withdrawData = encodeMangoInstruction({ + Withdraw: { quantity: nativeQuantity }, + }); + return new TransactionInstruction({ + keys: withdrawKeys, + data: withdrawData, + programId, + }); +} + export function makeSettleBorrowInstruction( programId: PublicKey, mangoGroupPk: PublicKey, @@ -153,32 +232,39 @@ export function makeForceCancelOrdersInstruction( dexProgramId: PublicKey, openOrders: PublicKey[], oracles: PublicKey[], - limit: BN + limit: BN, ): TransactionInstruction { - const keys = [ - { isSigner: false, isWritable: true, pubkey: mangoGroup}, + { isSigner: false, isWritable: true, pubkey: mangoGroup }, { isSigner: true, isWritable: false, pubkey: liqor }, - { isSigner: false, isWritable: true, pubkey: liqeeMarginAccount }, - { isSigner: false, isWritable: true, pubkey: baseVault }, - { isSigner: false, isWritable: true, pubkey: quoteVault }, - { isSigner: false, isWritable: true, pubkey: spotMarket }, - { isSigner: false, isWritable: true, pubkey: bids }, - { isSigner: false, isWritable: true, pubkey: asks }, - { isSigner: false, isWritable: false, pubkey: signerKey }, - { isSigner: false, isWritable: true, pubkey: dexEventQueue }, + { isSigner: false, isWritable: true, pubkey: liqeeMarginAccount }, + { isSigner: false, isWritable: true, pubkey: baseVault }, + { isSigner: false, isWritable: true, pubkey: quoteVault }, + { isSigner: false, isWritable: true, pubkey: spotMarket }, + { isSigner: false, isWritable: true, pubkey: bids }, + { isSigner: false, isWritable: true, pubkey: asks }, + { isSigner: false, isWritable: false, pubkey: signerKey }, + { isSigner: false, isWritable: true, pubkey: dexEventQueue }, { isSigner: false, isWritable: true, pubkey: dexBaseVault }, { isSigner: false, isWritable: true, pubkey: dexQuoteVault }, { isSigner: false, isWritable: false, pubkey: dexSigner }, { isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID }, { isSigner: false, isWritable: false, pubkey: dexProgramId }, { isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY }, - ...openOrders.map( (pubkey) => ( { isSigner: false, isWritable: true, pubkey })), - ...oracles.map( (pubkey) => ( { isSigner: false, isWritable: false, pubkey })), - ] + ...openOrders.map((pubkey) => ({ + isSigner: false, + isWritable: true, + pubkey, + })), + ...oracles.map((pubkey) => ({ + isSigner: false, + isWritable: false, + pubkey, + })), + ]; - const data = encodeMangoInstruction({ForceCancelOrders: { limit }}) - return new TransactionInstruction( { keys, data, programId }) + const data = encodeMangoInstruction({ ForceCancelOrders: { limit } }); + return new TransactionInstruction({ keys, data, programId }); } export function makePartialLiquidateInstruction( @@ -193,23 +279,31 @@ export function makePartialLiquidateInstruction( signerKey: PublicKey, openOrders: PublicKey[], oracles: PublicKey[], - maxDeposit: BN + maxDeposit: BN, ): TransactionInstruction { const keys = [ { isSigner: false, isWritable: true, pubkey: mangoGroup }, { isSigner: true, isWritable: false, pubkey: liqor }, { isSigner: false, isWritable: true, pubkey: liqorInTokenWallet }, { isSigner: false, isWritable: true, pubkey: liqorOutTokenWallet }, - { isSigner: false, isWritable: true, pubkey: liqeeMarginAccount }, + { isSigner: false, isWritable: true, pubkey: liqeeMarginAccount }, { isSigner: false, isWritable: true, pubkey: inTokenVault }, { isSigner: false, isWritable: true, pubkey: outTokenVault }, { isSigner: false, isWritable: false, pubkey: signerKey }, { isSigner: false, isWritable: false, pubkey: TOKEN_PROGRAM_ID }, { isSigner: false, isWritable: false, pubkey: SYSVAR_CLOCK_PUBKEY }, - ...openOrders.map( (pubkey) => ( { isSigner: false, isWritable: false, pubkey })), - ...oracles.map( (pubkey) => ( { isSigner: false, isWritable: false, pubkey })), - ] - const data = encodeMangoInstruction({PartialLiquidate: { maxDeposit }}) + ...openOrders.map((pubkey) => ({ + isSigner: false, + isWritable: false, + pubkey, + })), + ...oracles.map((pubkey) => ({ + isSigner: false, + isWritable: false, + pubkey, + })), + ]; + const data = encodeMangoInstruction({ PartialLiquidate: { maxDeposit } }); - return new TransactionInstruction( { keys, data, programId }) -} \ No newline at end of file + return new TransactionInstruction({ keys, data, programId }); +}