@mrgnlabs/marginfi-client-v2
Advanced tools
Comparing version 2.8.2 to 2.9.0
@@ -11,2 +11,10 @@ /// <reference types="node" /> | ||
export type OraclePriceMap = Map<string, OraclePrice>; | ||
export type MarginfiClientOptions = { | ||
confirmOpts?: ConfirmOptions; | ||
readOnly?: boolean; | ||
sendEndpoint?: string; | ||
spamSendTx?: boolean; | ||
skipPreflightInSpam?: boolean; | ||
preloadedBankAddresses?: PublicKey[]; | ||
}; | ||
/** | ||
@@ -26,3 +34,6 @@ * Entrypoint to interact with the marginfi contract. | ||
private preloadedBankAddresses?; | ||
constructor(config: MarginfiConfig, program: MarginfiProgram, wallet: Wallet, isReadOnly: boolean, group: MarginfiGroup, banks: BankMap, priceInfos: OraclePriceMap, addressLookupTables?: AddressLookupTableAccount[], preloadedBankAddresses?: PublicKey[], bankMetadataMap?: BankMetadataMap | undefined); | ||
private sendEndpoint?; | ||
private spamSendTx; | ||
private skipPreflightInSpam; | ||
constructor(config: MarginfiConfig, program: MarginfiProgram, wallet: Wallet, isReadOnly: boolean, group: MarginfiGroup, banks: BankMap, priceInfos: OraclePriceMap, addressLookupTables?: AddressLookupTableAccount[], preloadedBankAddresses?: PublicKey[], bankMetadataMap?: BankMetadataMap | undefined, sendEndpoint?: string, spamSendTx?: boolean, skipPreflightInSpam?: boolean); | ||
/** | ||
@@ -36,10 +47,5 @@ * MarginfiClient factory | ||
* @param connection Solana web.js Connection object | ||
* @param opts Solana web.js ConfirmOptions object | ||
* @param readOnly Whether the client should be read-only or not | ||
* @param { preloadedBankAddresses } Optional list of bank addresses to skip the gpa call | ||
* @returns MarginfiClient instance | ||
*/ | ||
static fetch(config: MarginfiConfig, wallet: Wallet, connection: Connection, opts?: ConfirmOptions, readOnly?: boolean, { preloadedBankAddresses }?: { | ||
preloadedBankAddresses?: PublicKey[]; | ||
}): Promise<MarginfiClient>; | ||
static fetch(config: MarginfiConfig, wallet: Wallet, connection: Connection, clientOptions?: MarginfiClientOptions): Promise<MarginfiClient>; | ||
static fromEnv(overrides?: Partial<{ | ||
@@ -46,0 +52,0 @@ env: Environment; |
@@ -26,3 +26,3 @@ "use strict"; | ||
// -------------------------------------------------------------------------- | ||
constructor(config, program, wallet, isReadOnly, group, banks, priceInfos, addressLookupTables, preloadedBankAddresses, bankMetadataMap) { | ||
constructor(config, program, wallet, isReadOnly, group, banks, priceInfos, addressLookupTables, preloadedBankAddresses, bankMetadataMap, sendEndpoint, spamSendTx = false, skipPreflightInSpam = false) { | ||
this.config = config; | ||
@@ -38,2 +38,5 @@ this.program = program; | ||
this.preloadedBankAddresses = preloadedBankAddresses; | ||
this.sendEndpoint = sendEndpoint; | ||
this.spamSendTx = spamSendTx; | ||
this.skipPreflightInSpam = skipPreflightInSpam; | ||
} | ||
@@ -48,14 +51,17 @@ /** | ||
* @param connection Solana web.js Connection object | ||
* @param opts Solana web.js ConfirmOptions object | ||
* @param readOnly Whether the client should be read-only or not | ||
* @param { preloadedBankAddresses } Optional list of bank addresses to skip the gpa call | ||
* @returns MarginfiClient instance | ||
*/ | ||
static async fetch(config, wallet, connection, opts, readOnly = false, { preloadedBankAddresses } = {}) { | ||
static async fetch(config, wallet, connection, clientOptions) { | ||
const debug = require("debug")("mfi:client"); | ||
debug("Loading Marginfi Client\n\tprogram: %s\n\tenv: %s\n\tgroup: %s\n\turl: %s", config.programId, config.environment, config.groupPk, connection.rpcEndpoint); | ||
const confirmOpts = clientOptions?.confirmOpts ?? {}; | ||
const readOnly = clientOptions?.readOnly ?? false; | ||
const sendEndpoint = clientOptions?.sendEndpoint; | ||
const preloadedBankAddresses = clientOptions?.preloadedBankAddresses; | ||
const spamSendTx = clientOptions?.spamSendTx ?? false; | ||
const skipPreflightInSpam = clientOptions?.skipPreflightInSpam ?? false; | ||
const provider = new anchor_1.AnchorProvider(connection, wallet, { | ||
...anchor_1.AnchorProvider.defaultOptions(), | ||
commitment: connection.commitment ?? anchor_1.AnchorProvider.defaultOptions().commitment, | ||
...opts, | ||
...confirmOpts, | ||
}); | ||
@@ -76,3 +82,3 @@ const program = new anchor_1.Program(idl_1.MARGINFI_IDL, config.programId, provider); | ||
.filter((table) => table !== null); | ||
return new MarginfiClient(config, program, wallet, readOnly, marginfiGroup, banks, priceInfos, addressLookupTables, preloadedBankAddresses, bankMetadataMap); | ||
return new MarginfiClient(config, program, wallet, readOnly, marginfiGroup, banks, priceInfos, addressLookupTables, preloadedBankAddresses, bankMetadataMap, sendEndpoint, spamSendTx, skipPreflightInSpam); | ||
} | ||
@@ -95,3 +101,3 @@ static async fromEnv(overrides) { | ||
debug("Env: %s\nProgram: %s\nGroup: %s\nSigner: %s", env, programId, groupPk, wallet.publicKey); | ||
const config = await (0, config_1.getConfig)(env, { | ||
const config = (0, config_1.getConfig)(env, { | ||
groupPk: (0, anchor_1.translateAddress)(groupPk), | ||
@@ -101,3 +107,5 @@ programId: (0, anchor_1.translateAddress)(programId), | ||
return MarginfiClient.fetch(config, wallet, connection, { | ||
commitment: connection.commitment, | ||
confirmOpts: { | ||
commitment: connection.commitment, | ||
}, | ||
}); | ||
@@ -340,3 +348,3 @@ } | ||
dbg("Created Marginfi account %s", sig); | ||
return (opts?.dryRun || createOpts?.newAccountKey) | ||
return opts?.dryRun || createOpts?.newAccountKey | ||
? Promise.resolve(undefined) | ||
@@ -357,2 +365,3 @@ : wrapper_1.MarginfiAccountWrapper.fetch(newAccountKey, this, opts?.commitment); | ||
const connection = new web3_js_1.Connection(this.provider.connection.rpcEndpoint, this.provider.opts); | ||
const sendConnection = this.sendEndpoint ? new web3_js_1.Connection(this.sendEndpoint, this.provider.opts) : connection; | ||
let minContextSlot; | ||
@@ -412,13 +421,48 @@ let blockhash; | ||
}; | ||
signature = await connection.sendTransaction(versionedTransaction, { | ||
// minContextSlot: mergedOpts.minContextSlot, | ||
skipPreflight: mergedOpts.skipPreflight, | ||
preflightCommitment: mergedOpts.preflightCommitment, | ||
maxRetries: mergedOpts.maxRetries, | ||
}); | ||
await connection.confirmTransaction({ | ||
blockhash, | ||
lastValidBlockHeight, | ||
signature, | ||
}, mergedOpts.commitment); | ||
if (this.spamSendTx) { | ||
let status = "pending"; | ||
if (this.skipPreflightInSpam) { | ||
const response = await connection.simulateTransaction(versionedTransaction, opts ?? { minContextSlot, sigVerify: false }); | ||
if (response.value.err) | ||
throw new web3_js_1.SendTransactionError(JSON.stringify(response.value.err), response.value.logs ?? []); | ||
} | ||
while (true) { | ||
signature = await sendConnection.sendTransaction(versionedTransaction, { | ||
// minContextSlot: mergedOpts.minContextSlot, | ||
skipPreflight: this.skipPreflightInSpam || mergedOpts.skipPreflight, | ||
preflightCommitment: mergedOpts.preflightCommitment, | ||
maxRetries: mergedOpts.maxRetries, | ||
}); | ||
for (let i = 0; i < 5; i++) { | ||
const signatureStatus = await connection.getSignatureStatus(signature, { | ||
searchTransactionHistory: false, | ||
}); | ||
if (signatureStatus.value?.confirmationStatus === "confirmed") { | ||
status = "confirmed"; | ||
break; | ||
} | ||
await (0, mrgn_common_1.sleep)(200); | ||
} | ||
let blockHeight = await connection.getBlockHeight(); | ||
if (blockHeight > lastValidBlockHeight) { | ||
throw new errors_1.ProcessTransactionError("Transaction was not confirmed within †he alloted time", errors_1.ProcessTransactionErrorType.TimeoutError); | ||
} | ||
if (status === "confirmed") { | ||
break; | ||
} | ||
} | ||
} | ||
else { | ||
signature = await connection.sendTransaction(versionedTransaction, { | ||
// minContextSlot: mergedOpts.minContextSlot, | ||
skipPreflight: mergedOpts.skipPreflight, | ||
preflightCommitment: mergedOpts.preflightCommitment, | ||
maxRetries: mergedOpts.maxRetries, | ||
}); | ||
await connection.confirmTransaction({ | ||
blockhash, | ||
lastValidBlockHeight, | ||
signature, | ||
}, mergedOpts.commitment); | ||
} | ||
return signature; | ||
@@ -437,4 +481,4 @@ } | ||
} | ||
console.log(error); | ||
throw new errors_1.ProcessTransactionError(error.message, errors_1.ProcessTransactionErrorType.FallthroughError); | ||
console.log("fallthrough error", error); | ||
throw new errors_1.ProcessTransactionError("Something went wrong", errors_1.ProcessTransactionErrorType.FallthroughError); | ||
} | ||
@@ -441,0 +485,0 @@ } |
@@ -5,3 +5,4 @@ import { PublicKey } from "@solana/web3.js"; | ||
SimulationError = 1, | ||
FallthroughError = 2 | ||
FallthroughError = 2, | ||
TimeoutError = 3 | ||
} | ||
@@ -8,0 +9,0 @@ export declare class ProcessTransactionError extends Error { |
@@ -12,2 +12,3 @@ "use strict"; | ||
ProcessTransactionErrorType[ProcessTransactionErrorType["FallthroughError"] = 2] = "FallthroughError"; | ||
ProcessTransactionErrorType[ProcessTransactionErrorType["TimeoutError"] = 3] = "TimeoutError"; | ||
})(ProcessTransactionErrorType = exports.ProcessTransactionErrorType || (exports.ProcessTransactionErrorType = {})); | ||
@@ -14,0 +15,0 @@ class ProcessTransactionError extends Error { |
@@ -1,2 +0,1 @@ | ||
/// <reference types="@coral-xyz/anchor/node_modules/@solana/web3.js" /> | ||
import { AccountMeta, PublicKey } from "@solana/web3.js"; | ||
@@ -3,0 +2,0 @@ import BN from "bn.js"; |
@@ -435,7 +435,2 @@ "use strict"; | ||
let ixs = []; | ||
// Add additional CU request if necessary | ||
const activeBalances = this.balances.filter((b) => b.active); | ||
if (activeBalances.length >= 4) { | ||
ixs.push(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 600000 })); | ||
} | ||
// Add emissions-related instructions if necessary | ||
@@ -469,7 +464,2 @@ if (repayAll && !bank.emissionsMint.equals(web3_js_1.PublicKey.default)) { | ||
let ixs = []; | ||
// Add additional CU request if necessary | ||
const activeBalances = this.balances.filter((b) => b.active); | ||
if (activeBalances.length >= 4) { | ||
ixs.push(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 1000000 })); | ||
} | ||
// Add emissions-related instructions if necessary | ||
@@ -515,7 +505,2 @@ if (withdrawAll && !bank.emissionsMint.equals(web3_js_1.PublicKey.default)) { | ||
const userAta = (0, mrgn_common_1.getAssociatedTokenAddressSync)(bank.mint, this.authority, true); // We allow off curve addresses here to support Fuse. | ||
// Add additional CU request if necessary | ||
const activeBalances = this.balances.filter((b) => b.active); | ||
if (activeBalances.length >= 4) { | ||
ixs.push(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 1000000 })); | ||
} | ||
// Add borrow-related instructions | ||
@@ -623,3 +608,3 @@ const createAtaIdempotentIx = (0, mrgn_common_1.createAssociatedTokenAccountIdempotentInstruction)(this.authority, userAta, this.authority, bank.mint); | ||
projectActiveBalancesNoCpi(program, instructions) { | ||
let projectedBalances = [...this.balances]; | ||
let projectedBalances = [...this.balances.map((b) => ({ active: b.active, bankPk: b.bankPk }))]; | ||
for (let index = 0; index < instructions.length; index++) { | ||
@@ -626,0 +611,0 @@ const ix = instructions[index]; |
@@ -67,5 +67,8 @@ /// <reference types="node" /> | ||
makePriorityFeeIx(priorityFeeUi?: number): TransactionInstruction[]; | ||
makeComputeBudgetIx(): TransactionInstruction[]; | ||
makeDepositIx(amount: Amount, bankAddress: PublicKey): Promise<InstructionsWrapper>; | ||
deposit(amount: Amount, bankAddress: PublicKey, priorityFeeUi?: number): Promise<string>; | ||
simulateDeposit(amount: Amount, bankAddress: PublicKey): Promise<SimulationResult>; | ||
repayWithCollat(amount: Amount, repayAmount: Amount, bankAddress: PublicKey, repayBankAddress: PublicKey, withdrawAll: boolean | undefined, repayAll: boolean | undefined, swapIxs: TransactionInstruction[], addressLookupTableAccounts: AddressLookupTableAccount[], priorityFeeUi?: number): Promise<string>; | ||
simulateRepayWithCollat(amount: Amount, repayAmount: Amount, bankAddress: PublicKey, repayBankAddress: PublicKey, repayAll: boolean | undefined, swapIxs: TransactionInstruction[], addressLookupTableAccounts: AddressLookupTableAccount[]): Promise<SimulationResult>; | ||
makeRepayIx(amount: Amount, bankAddress: PublicKey, repayAll?: boolean): Promise<InstructionsWrapper>; | ||
@@ -90,4 +93,6 @@ repay(amount: Amount, bankAddress: PublicKey, repayAll?: boolean, priorityFeeUi?: number): Promise<string>; | ||
makeEndFlashLoanIx(projectedActiveBalances: PublicKey[]): Promise<InstructionsWrapper>; | ||
buildFlashLoanTx(args: FlashLoanArgs): Promise<VersionedTransaction>; | ||
flashLoan(args: FlashLoanArgs): Promise<string>; | ||
buildFlashLoanTx(args: FlashLoanArgs, lookupTables?: AddressLookupTableAccount[]): Promise<VersionedTransaction>; | ||
makeTransferAccountAuthorityIx(newAccountAuthority: PublicKey): Promise<InstructionsWrapper>; | ||
transferAccountAuthority(newAccountAuthority: PublicKey): Promise<string>; | ||
getHealthCheckAccounts(mandatoryBanks?: Bank[], excludedBanks?: Bank[]): AccountMeta[]; | ||
@@ -94,0 +99,0 @@ private static _fetchAccountData; |
@@ -129,2 +129,11 @@ "use strict"; | ||
} | ||
makeComputeBudgetIx() { | ||
// Add additional CU request if necessary | ||
let cuRequestIxs = []; | ||
const activeBalances = this.balances.filter((b) => b.active); | ||
if (activeBalances.length >= 4) { | ||
cuRequestIxs.push(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 1000000 })); | ||
} | ||
return cuRequestIxs; | ||
} | ||
// -------------------------------------------------------------------------- | ||
@@ -161,2 +170,40 @@ // User actions | ||
} | ||
async repayWithCollat(amount, repayAmount, bankAddress, repayBankAddress, withdrawAll = false, repayAll = false, swapIxs, addressLookupTableAccounts, priorityFeeUi) { | ||
const debug = require("debug")(`mfi:margin-account:${this.address.toString()}:repay`); | ||
debug("Repaying %s into marginfi account (bank: %s), repay all: %s", amount, bankAddress, repayAll); | ||
const cuRequestIxs = this.makeComputeBudgetIx(); | ||
const priorityFeeIx = this.makePriorityFeeIx(priorityFeeUi); | ||
const withdrawIxs = await this.makeWithdrawIx(repayAmount, repayBankAddress, withdrawAll); | ||
const depositIxs = await this.makeRepayIx(amount, bankAddress, repayAll); | ||
const lookupTables = this.client.addressLookupTables; | ||
const flashloanTx = await this.buildFlashLoanTx({ | ||
ixs: [...priorityFeeIx, ...cuRequestIxs, ...withdrawIxs.instructions, ...swapIxs, ...depositIxs.instructions], | ||
addressLookupTableAccounts: [...lookupTables, ...addressLookupTableAccounts], | ||
}); | ||
console.log({ flashloanTx }); | ||
const sig = await this.client.processTransaction(flashloanTx, []); | ||
debug("Repay with collateral successful %s", sig); | ||
return sig; | ||
} | ||
async simulateRepayWithCollat(amount, repayAmount, bankAddress, repayBankAddress, repayAll = false, swapIxs, addressLookupTableAccounts) { | ||
const cuRequestIxs = this.makeComputeBudgetIx(); | ||
const withdrawIxs = await this.makeWithdrawIx(repayAmount, repayBankAddress); | ||
const depositIxs = await this.makeRepayIx(amount, bankAddress, repayAll); | ||
const lookupTables = this.client.addressLookupTables; | ||
const tx = await this.buildFlashLoanTx({ | ||
ixs: [...cuRequestIxs, ...withdrawIxs.instructions, ...swapIxs, ...depositIxs.instructions], | ||
addressLookupTableAccounts: [...lookupTables, ...addressLookupTableAccounts], | ||
}); | ||
const [mfiAccountData, bankData] = await this.client.simulateTransaction(tx, [this.address, bankAddress]); | ||
if (!mfiAccountData || !bankData) | ||
throw new Error("Failed to simulate repay w/ collat"); | ||
const previewBanks = this.client.banks; | ||
previewBanks.set(bankAddress.toBase58(), bank_1.Bank.fromBuffer(bankAddress, bankData)); | ||
const previewClient = new __1.MarginfiClient(this._config, this.client.program, {}, true, this.client.group, this.client.banks, this.client.oraclePrices); | ||
const previewMarginfiAccount = MarginfiAccountWrapper.fromAccountDataRaw(this.address, previewClient, mfiAccountData); | ||
return { | ||
banks: previewBanks, | ||
marginfiAccount: previewMarginfiAccount, | ||
}; | ||
} | ||
async makeRepayIx(amount, bankAddress, repayAll = false) { | ||
@@ -197,4 +244,5 @@ return this._marginfiAccount.makeRepayIx(this._program, this.client.banks, amount, bankAddress, repayAll); | ||
const priorityFeeIx = this.makePriorityFeeIx(priorityFeeUi); | ||
const cuRequestIxs = this.makeComputeBudgetIx(); | ||
const ixs = await this.makeWithdrawIx(amount, bankAddress, withdrawAll); | ||
const tx = new web3_js_1.Transaction().add(...priorityFeeIx, ...ixs.instructions); | ||
const tx = new web3_js_1.Transaction().add(...priorityFeeIx, ...cuRequestIxs, ...ixs.instructions); | ||
const sig = await this.client.processTransaction(tx, []); | ||
@@ -205,4 +253,5 @@ debug("Withdrawing successful %s", sig); | ||
async simulateWithdraw(amount, bankAddress, withdrawAll = false) { | ||
const cuRequestIxs = this.makeComputeBudgetIx(); | ||
const ixs = await this.makeWithdrawIx(amount, bankAddress, withdrawAll); | ||
const tx = new web3_js_1.Transaction().add(...ixs.instructions); | ||
const tx = new web3_js_1.Transaction().add(...cuRequestIxs, ...ixs.instructions); | ||
const [mfiAccountData, bankData] = await this.client.simulateTransaction(tx, [this.address, bankAddress]); | ||
@@ -227,4 +276,5 @@ if (!mfiAccountData || !bankData) | ||
const priorityFeeIx = this.makePriorityFeeIx(priorityFeeUi); | ||
const cuRequestIxs = this.makeComputeBudgetIx(); | ||
const ixs = await this.makeBorrowIx(amount, bankAddress); | ||
const tx = new web3_js_1.Transaction().add(...priorityFeeIx, ...ixs.instructions); | ||
const tx = new web3_js_1.Transaction().add(...priorityFeeIx, ...cuRequestIxs, ...ixs.instructions); | ||
const sig = await this.client.processTransaction(tx, []); | ||
@@ -235,4 +285,5 @@ debug("Borrowing successful %s", sig); | ||
async simulateBorrow(amount, bankAddress) { | ||
const cuRequestIxs = this.makeComputeBudgetIx(); | ||
const ixs = await this.makeBorrowIx(amount, bankAddress); | ||
const tx = new web3_js_1.Transaction().add(...ixs.instructions); | ||
const tx = new web3_js_1.Transaction().add(...cuRequestIxs, ...ixs.instructions); | ||
const [mfiAccountData, bankData] = await this.client.simulateTransaction(tx, [this.address, bankAddress]); | ||
@@ -280,3 +331,12 @@ if (!mfiAccountData || !bankData) | ||
} | ||
async buildFlashLoanTx(args) { | ||
async flashLoan(args) { | ||
const debug = require("debug")(`mfi:margin-account:${this.address.toString()}:flashLoan`); | ||
debug("Executing flashloan from marginfi account"); | ||
const lookupTables = this.client.addressLookupTables; | ||
const tx = await this.buildFlashLoanTx(args, lookupTables); | ||
const sig = await this.client.processTransaction(tx, []); | ||
debug("Flashloan successful %s", sig); | ||
return sig; | ||
} | ||
async buildFlashLoanTx(args, lookupTables) { | ||
const endIndex = args.ixs.length + 1; | ||
@@ -292,3 +352,3 @@ const projectedActiveBalances = this._marginfiAccount.projectActiveBalancesNoCpi(this._program, args.ixs); | ||
instructions: ixs, | ||
}).compileToV0Message(args.addressLookupTableAccounts); | ||
}).compileToV0Message([...(lookupTables ?? []), ...(args.addressLookupTableAccounts ?? [])]); | ||
const tx = new web3_js_1.VersionedTransaction(message); | ||
@@ -303,2 +363,11 @@ if (args.signers) { | ||
} | ||
async transferAccountAuthority(newAccountAuthority) { | ||
const debug = require("debug")(`mfi:margin-account:${this.address.toString()}:transfer-authority`); | ||
debug("Transferring account %s to %s", this.address.toBase58(), newAccountAuthority.toBase58()); | ||
const ixs = await this.makeTransferAccountAuthorityIx(newAccountAuthority); | ||
const tx = new web3_js_1.Transaction().add(...ixs.instructions); | ||
const sig = await this.client.processTransaction(tx, []); | ||
debug("Transfer successful %s", sig); | ||
return sig; | ||
} | ||
// -------------------------------------------------------------------------- | ||
@@ -305,0 +374,0 @@ // Helpers |
@@ -179,4 +179,4 @@ "use strict"; | ||
const { insuranceFeeFixedApr, insuranceIrFee, protocolFixedFeeApr, protocolIrFee } = this.config.interestRateConfig; | ||
const rateFee = insuranceFeeFixedApr.plus(protocolFixedFeeApr); | ||
const fixedFee = insuranceIrFee.plus(protocolIrFee); | ||
const fixedFee = insuranceFeeFixedApr.plus(protocolFixedFeeApr); | ||
const rateFee = insuranceIrFee.plus(protocolIrFee); | ||
const baseInterestRate = this.computeBaseInterestRate(); | ||
@@ -183,0 +183,0 @@ const utilizationRate = this.computeUtilizationRate(); |
/// <reference types="node" /> | ||
import { Buffer } from "buffer"; | ||
export declare function readBigInt64LE(buffer: Buffer, offset?: number): bigint; | ||
export declare function readBigUInt64LE(buffer: Buffer, offset?: number): bigint; | ||
//# sourceMappingURL=readBig.d.ts.map |
@@ -1,2 +0,1 @@ | ||
/// <reference types="@coral-xyz/anchor/node_modules/@solana/web3.js" /> | ||
import { PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js"; | ||
@@ -3,0 +2,0 @@ /** |
{ | ||
"name": "@mrgnlabs/marginfi-client-v2", | ||
"version": "2.8.2", | ||
"version": "2.9.0", | ||
"main": "dist/index.js", | ||
@@ -38,3 +38,4 @@ "types": "dist/index.d.ts", | ||
"typescript": "^4.5.2", | ||
"zod": "^3.22.4" | ||
"zod": "^3.22.4", | ||
"big.js": "^6.2.1" | ||
}, | ||
@@ -41,0 +42,0 @@ "publishConfig": { |
@@ -30,2 +30,3 @@ import { Address, AnchorProvider, BorshAccountsCoder, Program, translateAddress } from "@coral-xyz/anchor"; | ||
NodeWallet, | ||
sleep, | ||
TransactionOptions, | ||
@@ -50,2 +51,11 @@ Wallet, | ||
export type MarginfiClientOptions = { | ||
confirmOpts?: ConfirmOptions; | ||
readOnly?: boolean; | ||
sendEndpoint?: string; | ||
spamSendTx?: boolean; | ||
skipPreflightInSpam?: boolean; | ||
preloadedBankAddresses?: PublicKey[]; | ||
}; | ||
/** | ||
@@ -60,2 +70,5 @@ * Entrypoint to interact with the marginfi contract. | ||
private preloadedBankAddresses?: PublicKey[]; | ||
private sendEndpoint?: string; | ||
private spamSendTx: boolean; | ||
private skipPreflightInSpam: boolean; | ||
@@ -76,3 +89,6 @@ // -------------------------------------------------------------------------- | ||
preloadedBankAddresses?: PublicKey[], | ||
readonly bankMetadataMap?: BankMetadataMap | ||
readonly bankMetadataMap?: BankMetadataMap, | ||
sendEndpoint?: string, | ||
spamSendTx: boolean = false, | ||
skipPreflightInSpam: boolean = false | ||
) { | ||
@@ -84,2 +100,5 @@ this.group = group; | ||
this.preloadedBankAddresses = preloadedBankAddresses; | ||
this.sendEndpoint = sendEndpoint; | ||
this.spamSendTx = spamSendTx; | ||
this.skipPreflightInSpam = skipPreflightInSpam; | ||
} | ||
@@ -95,5 +114,2 @@ | ||
* @param connection Solana web.js Connection object | ||
* @param opts Solana web.js ConfirmOptions object | ||
* @param readOnly Whether the client should be read-only or not | ||
* @param { preloadedBankAddresses } Optional list of bank addresses to skip the gpa call | ||
* @returns MarginfiClient instance | ||
@@ -105,5 +121,3 @@ */ | ||
connection: Connection, | ||
opts?: ConfirmOptions, | ||
readOnly: boolean = false, | ||
{ preloadedBankAddresses }: { preloadedBankAddresses?: PublicKey[] } = {} | ||
clientOptions?: MarginfiClientOptions | ||
) { | ||
@@ -118,6 +132,14 @@ const debug = require("debug")("mfi:client"); | ||
); | ||
const confirmOpts = clientOptions?.confirmOpts ?? {}; | ||
const readOnly = clientOptions?.readOnly ?? false; | ||
const sendEndpoint = clientOptions?.sendEndpoint; | ||
const preloadedBankAddresses = clientOptions?.preloadedBankAddresses; | ||
const spamSendTx = clientOptions?.spamSendTx ?? false; | ||
const skipPreflightInSpam = clientOptions?.skipPreflightInSpam ?? false; | ||
const provider = new AnchorProvider(connection, wallet, { | ||
...AnchorProvider.defaultOptions(), | ||
commitment: connection.commitment ?? AnchorProvider.defaultOptions().commitment, | ||
...opts, | ||
...confirmOpts, | ||
}); | ||
@@ -159,3 +181,6 @@ const program = new Program(MARGINFI_IDL, config.programId, provider) as any as MarginfiProgram; | ||
preloadedBankAddresses, | ||
bankMetadataMap | ||
bankMetadataMap, | ||
sendEndpoint, | ||
spamSendTx, | ||
skipPreflightInSpam | ||
); | ||
@@ -195,3 +220,3 @@ } | ||
const config = await getConfig(env, { | ||
const config = getConfig(env, { | ||
groupPk: translateAddress(groupPk), | ||
@@ -202,3 +227,5 @@ programId: translateAddress(programId), | ||
return MarginfiClient.fetch(config, wallet, connection, { | ||
commitment: connection.commitment, | ||
confirmOpts: { | ||
commitment: connection.commitment, | ||
}, | ||
}); | ||
@@ -501,3 +528,3 @@ } | ||
return (opts?.dryRun || createOpts?.newAccountKey) | ||
return opts?.dryRun || createOpts?.newAccountKey | ||
? Promise.resolve(undefined as unknown as MarginfiAccountWrapper) | ||
@@ -525,2 +552,3 @@ : MarginfiAccountWrapper.fetch(newAccountKey, this, opts?.commitment); | ||
const connection = new Connection(this.provider.connection.rpcEndpoint, this.provider.opts); | ||
const sendConnection = this.sendEndpoint ? new Connection(this.sendEndpoint, this.provider.opts) : connection; | ||
let minContextSlot: number; | ||
@@ -597,16 +625,60 @@ let blockhash: string; | ||
signature = await connection.sendTransaction(versionedTransaction, { | ||
// minContextSlot: mergedOpts.minContextSlot, | ||
skipPreflight: mergedOpts.skipPreflight, | ||
preflightCommitment: mergedOpts.preflightCommitment, | ||
maxRetries: mergedOpts.maxRetries, | ||
}); | ||
await connection.confirmTransaction( | ||
{ | ||
blockhash, | ||
lastValidBlockHeight, | ||
signature, | ||
}, | ||
mergedOpts.commitment | ||
); | ||
if (this.spamSendTx) { | ||
let status = "pending"; | ||
if (this.skipPreflightInSpam) { | ||
const response = await connection.simulateTransaction( | ||
versionedTransaction, | ||
opts ?? { minContextSlot, sigVerify: false } | ||
); | ||
if (response.value.err) | ||
throw new SendTransactionError(JSON.stringify(response.value.err), response.value.logs ?? []); | ||
} | ||
while (true) { | ||
signature = await sendConnection.sendTransaction(versionedTransaction, { | ||
// minContextSlot: mergedOpts.minContextSlot, | ||
skipPreflight: this.skipPreflightInSpam || mergedOpts.skipPreflight, | ||
preflightCommitment: mergedOpts.preflightCommitment, | ||
maxRetries: mergedOpts.maxRetries, | ||
}); | ||
for (let i = 0; i < 5; i++) { | ||
const signatureStatus = await connection.getSignatureStatus(signature, { | ||
searchTransactionHistory: false, | ||
}); | ||
if (signatureStatus.value?.confirmationStatus === "confirmed") { | ||
status = "confirmed"; | ||
break; | ||
} | ||
await sleep(200); | ||
} | ||
let blockHeight = await connection.getBlockHeight(); | ||
if (blockHeight > lastValidBlockHeight) { | ||
throw new ProcessTransactionError( | ||
"Transaction was not confirmed within †he alloted time", | ||
ProcessTransactionErrorType.TimeoutError | ||
); | ||
} | ||
if (status === "confirmed") { | ||
break; | ||
} | ||
} | ||
} else { | ||
signature = await connection.sendTransaction(versionedTransaction, { | ||
// minContextSlot: mergedOpts.minContextSlot, | ||
skipPreflight: mergedOpts.skipPreflight, | ||
preflightCommitment: mergedOpts.preflightCommitment, | ||
maxRetries: mergedOpts.maxRetries, | ||
}); | ||
await connection.confirmTransaction( | ||
{ | ||
blockhash, | ||
lastValidBlockHeight, | ||
signature, | ||
}, | ||
mergedOpts.commitment | ||
); | ||
} | ||
return signature; | ||
@@ -628,4 +700,4 @@ } | ||
} | ||
console.log(error); | ||
throw new ProcessTransactionError(error.message, ProcessTransactionErrorType.FallthroughError); | ||
console.log("fallthrough error", error); | ||
throw new ProcessTransactionError("Something went wrong", ProcessTransactionErrorType.FallthroughError); | ||
} | ||
@@ -632,0 +704,0 @@ } |
@@ -10,2 +10,3 @@ import { LangErrorMessage } from "@coral-xyz/anchor"; | ||
FallthroughError, | ||
TimeoutError, | ||
} | ||
@@ -12,0 +13,0 @@ |
@@ -667,8 +667,2 @@ import { | ||
// Add additional CU request if necessary | ||
const activeBalances = this.balances.filter((b) => b.active); | ||
if (activeBalances.length >= 4) { | ||
ixs.push(ComputeBudgetProgram.setComputeUnitLimit({ units: 600_000 })); | ||
} | ||
// Add emissions-related instructions if necessary | ||
@@ -725,8 +719,2 @@ if (repayAll && !bank.emissionsMint.equals(PublicKey.default)) { | ||
// Add additional CU request if necessary | ||
const activeBalances = this.balances.filter((b) => b.active); | ||
if (activeBalances.length >= 4) { | ||
ixs.push(ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 })); | ||
} | ||
// Add emissions-related instructions if necessary | ||
@@ -800,8 +788,2 @@ if (withdrawAll && !bank.emissionsMint.equals(PublicKey.default)) { | ||
// Add additional CU request if necessary | ||
const activeBalances = this.balances.filter((b) => b.active); | ||
if (activeBalances.length >= 4) { | ||
ixs.push(ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 })); | ||
} | ||
// Add borrow-related instructions | ||
@@ -953,13 +935,13 @@ const createAtaIdempotentIx = createAssociatedTokenAccountIdempotentInstruction( | ||
async makeAccountAuthorityTransferIx(program: MarginfiProgram, newAccountAuthority: PublicKey) : Promise<InstructionsWrapper> { | ||
const accountAuthorityTransferIx = await instructions.makeAccountAuthorityTransferIx( | ||
program, | ||
{ | ||
marginfiAccountPk: this.address, | ||
marginfiGroupPk: this.group, | ||
signerPk: this.authority, | ||
newAuthorityPk: newAccountAuthority, | ||
feePayerPk: this.authority, | ||
} | ||
) | ||
async makeAccountAuthorityTransferIx( | ||
program: MarginfiProgram, | ||
newAccountAuthority: PublicKey | ||
): Promise<InstructionsWrapper> { | ||
const accountAuthorityTransferIx = await instructions.makeAccountAuthorityTransferIx(program, { | ||
marginfiAccountPk: this.address, | ||
marginfiGroupPk: this.group, | ||
signerPk: this.authority, | ||
newAuthorityPk: newAccountAuthority, | ||
feePayerPk: this.authority, | ||
}); | ||
return { instructions: [accountAuthorityTransferIx], keys: [] }; | ||
@@ -969,3 +951,3 @@ } | ||
projectActiveBalancesNoCpi(program: MarginfiProgram, instructions: TransactionInstruction[]): PublicKey[] { | ||
let projectedBalances = [...this.balances]; | ||
let projectedBalances = [...this.balances.map((b) => ({ active: b.active, bankPk: b.bankPk }))]; | ||
@@ -972,0 +954,0 @@ for (let index = 0; index < instructions.length; index++) { |
@@ -257,2 +257,13 @@ import { Amount, DEFAULT_COMMITMENT, InstructionsWrapper, Wallet, shortenAddress } from "@mrgnlabs/mrgn-common"; | ||
makeComputeBudgetIx(): TransactionInstruction[] { | ||
// Add additional CU request if necessary | ||
let cuRequestIxs: TransactionInstruction[] = []; | ||
const activeBalances = this.balances.filter((b) => b.active); | ||
if (activeBalances.length >= 4) { | ||
cuRequestIxs.push(ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 })); | ||
} | ||
return cuRequestIxs; | ||
} | ||
// -------------------------------------------------------------------------- | ||
@@ -304,2 +315,73 @@ // User actions | ||
async repayWithCollat( | ||
amount: Amount, | ||
repayAmount: Amount, | ||
bankAddress: PublicKey, | ||
repayBankAddress: PublicKey, | ||
withdrawAll: boolean = false, | ||
repayAll: boolean = false, | ||
swapIxs: TransactionInstruction[], | ||
addressLookupTableAccounts: AddressLookupTableAccount[], | ||
priorityFeeUi?: number | ||
): Promise<string> { | ||
const debug = require("debug")(`mfi:margin-account:${this.address.toString()}:repay`); | ||
debug("Repaying %s into marginfi account (bank: %s), repay all: %s", amount, bankAddress, repayAll); | ||
const cuRequestIxs = this.makeComputeBudgetIx(); | ||
const priorityFeeIx = this.makePriorityFeeIx(priorityFeeUi); | ||
const withdrawIxs = await this.makeWithdrawIx(repayAmount, repayBankAddress, withdrawAll); | ||
const depositIxs = await this.makeRepayIx(amount, bankAddress, repayAll); | ||
const lookupTables = this.client.addressLookupTables; | ||
const flashloanTx = await this.buildFlashLoanTx({ | ||
ixs: [...priorityFeeIx, ...cuRequestIxs, ...withdrawIxs.instructions, ...swapIxs, ...depositIxs.instructions], | ||
addressLookupTableAccounts: [...lookupTables, ...addressLookupTableAccounts], | ||
}); | ||
console.log({ flashloanTx }); | ||
const sig = await this.client.processTransaction(flashloanTx, []); | ||
debug("Repay with collateral successful %s", sig); | ||
return sig; | ||
} | ||
async simulateRepayWithCollat( | ||
amount: Amount, | ||
repayAmount: Amount, | ||
bankAddress: PublicKey, | ||
repayBankAddress: PublicKey, | ||
repayAll: boolean = false, | ||
swapIxs: TransactionInstruction[], | ||
addressLookupTableAccounts: AddressLookupTableAccount[] | ||
): Promise<SimulationResult> { | ||
const cuRequestIxs = this.makeComputeBudgetIx(); | ||
const withdrawIxs = await this.makeWithdrawIx(repayAmount, repayBankAddress); | ||
const depositIxs = await this.makeRepayIx(amount, bankAddress, repayAll); | ||
const lookupTables = this.client.addressLookupTables; | ||
const tx = await this.buildFlashLoanTx({ | ||
ixs: [...cuRequestIxs, ...withdrawIxs.instructions, ...swapIxs, ...depositIxs.instructions], | ||
addressLookupTableAccounts: [...lookupTables, ...addressLookupTableAccounts], | ||
}); | ||
const [mfiAccountData, bankData] = await this.client.simulateTransaction(tx, [this.address, bankAddress]); | ||
if (!mfiAccountData || !bankData) throw new Error("Failed to simulate repay w/ collat"); | ||
const previewBanks = this.client.banks; | ||
previewBanks.set(bankAddress.toBase58(), Bank.fromBuffer(bankAddress, bankData)); | ||
const previewClient = new MarginfiClient( | ||
this._config, | ||
this.client.program, | ||
{} as Wallet, | ||
true, | ||
this.client.group, | ||
this.client.banks, | ||
this.client.oraclePrices | ||
); | ||
const previewMarginfiAccount = MarginfiAccountWrapper.fromAccountDataRaw( | ||
this.address, | ||
previewClient, | ||
mfiAccountData | ||
); | ||
return { | ||
banks: previewBanks, | ||
marginfiAccount: previewMarginfiAccount, | ||
}; | ||
} | ||
async makeRepayIx(amount: Amount, bankAddress: PublicKey, repayAll: boolean = false): Promise<InstructionsWrapper> { | ||
@@ -378,4 +460,5 @@ return this._marginfiAccount.makeRepayIx(this._program, this.client.banks, amount, bankAddress, repayAll); | ||
const priorityFeeIx = this.makePriorityFeeIx(priorityFeeUi); | ||
const cuRequestIxs = this.makeComputeBudgetIx(); | ||
const ixs = await this.makeWithdrawIx(amount, bankAddress, withdrawAll); | ||
const tx = new Transaction().add(...priorityFeeIx, ...ixs.instructions); | ||
const tx = new Transaction().add(...priorityFeeIx, ...cuRequestIxs, ...ixs.instructions); | ||
const sig = await this.client.processTransaction(tx, []); | ||
@@ -391,4 +474,5 @@ debug("Withdrawing successful %s", sig); | ||
): Promise<SimulationResult> { | ||
const cuRequestIxs = this.makeComputeBudgetIx(); | ||
const ixs = await this.makeWithdrawIx(amount, bankAddress, withdrawAll); | ||
const tx = new Transaction().add(...ixs.instructions); | ||
const tx = new Transaction().add(...cuRequestIxs, ...ixs.instructions); | ||
const [mfiAccountData, bankData] = await this.client.simulateTransaction(tx, [this.address, bankAddress]); | ||
@@ -430,4 +514,5 @@ if (!mfiAccountData || !bankData) throw new Error("Failed to simulate withdraw"); | ||
const priorityFeeIx = this.makePriorityFeeIx(priorityFeeUi); | ||
const cuRequestIxs = this.makeComputeBudgetIx(); | ||
const ixs = await this.makeBorrowIx(amount, bankAddress); | ||
const tx = new Transaction().add(...priorityFeeIx, ...ixs.instructions); | ||
const tx = new Transaction().add(...priorityFeeIx, ...cuRequestIxs, ...ixs.instructions); | ||
const sig = await this.client.processTransaction(tx, []); | ||
@@ -439,4 +524,5 @@ debug("Borrowing successful %s", sig); | ||
async simulateBorrow(amount: Amount, bankAddress: PublicKey): Promise<SimulationResult> { | ||
const cuRequestIxs = this.makeComputeBudgetIx(); | ||
const ixs = await this.makeBorrowIx(amount, bankAddress); | ||
const tx = new Transaction().add(...ixs.instructions); | ||
const tx = new Transaction().add(...cuRequestIxs, ...ixs.instructions); | ||
const [mfiAccountData, bankData] = await this.client.simulateTransaction(tx, [this.address, bankAddress]); | ||
@@ -524,3 +610,16 @@ if (!mfiAccountData || !bankData) throw new Error("Failed to simulate borrow"); | ||
public async buildFlashLoanTx(args: FlashLoanArgs): Promise<VersionedTransaction> { | ||
public async flashLoan(args: FlashLoanArgs): Promise<string> { | ||
const debug = require("debug")(`mfi:margin-account:${this.address.toString()}:flashLoan`); | ||
debug("Executing flashloan from marginfi account"); | ||
const lookupTables = this.client.addressLookupTables; | ||
const tx = await this.buildFlashLoanTx(args, lookupTables); | ||
const sig = await this.client.processTransaction(tx, []); | ||
debug("Flashloan successful %s", sig); | ||
return sig; | ||
} | ||
public async buildFlashLoanTx( | ||
args: FlashLoanArgs, | ||
lookupTables?: AddressLookupTableAccount[] | ||
): Promise<VersionedTransaction> { | ||
const endIndex = args.ixs.length + 1; | ||
@@ -543,3 +642,3 @@ | ||
instructions: ixs, | ||
}).compileToV0Message(args.addressLookupTableAccounts); | ||
}).compileToV0Message([...(lookupTables ?? []), ...(args.addressLookupTableAccounts ?? [])]); | ||
@@ -559,2 +658,12 @@ const tx = new VersionedTransaction(message); | ||
async transferAccountAuthority(newAccountAuthority: PublicKey): Promise<string> { | ||
const debug = require("debug")(`mfi:margin-account:${this.address.toString()}:transfer-authority`); | ||
debug("Transferring account %s to %s", this.address.toBase58(), newAccountAuthority.toBase58()); | ||
const ixs = await this.makeTransferAccountAuthorityIx(newAccountAuthority); | ||
const tx = new Transaction().add(...ixs.instructions); | ||
const sig = await this.client.processTransaction(tx, []); | ||
debug("Transfer successful %s", sig); | ||
return sig; | ||
} | ||
// -------------------------------------------------------------------------- | ||
@@ -561,0 +670,0 @@ // Helpers |
@@ -434,4 +434,4 @@ import { BankMetadata, WrappedI80F48, nativeToUi, wrappedI80F48toBigNumber } from "@mrgnlabs/mrgn-common"; | ||
const rateFee = insuranceFeeFixedApr.plus(protocolFixedFeeApr); | ||
const fixedFee = insuranceIrFee.plus(protocolIrFee); | ||
const fixedFee = insuranceFeeFixedApr.plus(protocolFixedFeeApr); | ||
const rateFee = insuranceIrFee.plus(protocolIrFee); | ||
@@ -442,3 +442,3 @@ const baseInterestRate = this.computeBaseInterestRate(); | ||
const lendingRate = baseInterestRate.times(utilizationRate); | ||
const borrowingRate = baseInterestRate.times(new BigNumber(1).plus(rateFee)).plus(fixedFee); | ||
const borrowingRate = baseInterestRate.times(new BigNumber(1).plus(rateFee)).plus(fixedFee) | ||
@@ -445,0 +445,0 @@ return { lendingRate, borrowingRate }; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1679459
44316
12