Skip to content

Commit

Permalink
Merge pull request #1055 from mrgnlabs/chore/tools
Browse files Browse the repository at this point in the history
feat: tools package
  • Loading branch information
k0beLeenders authored Feb 7, 2025
2 parents 84fb824 + 93100fd commit cb56b18
Show file tree
Hide file tree
Showing 70 changed files with 5,853 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,9 @@ export const PositionActionButtons = ({
depositSwapProps={{
connected: connected,
requestedDepositBank: depositBanks[0],
banks: extendedBankInfos,
requestedSwapBank: arenaPool.status === GroupStatus.LONG ? borrowBank ?? undefined : undefined,
showAvailableCollateral: false,
walletTokens: walletTokens,
walletTokens: null,
captureEvent: () => {
capture("position_add_btn_click", {
group: arenaPool.groupPk?.toBase58(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import {
} from "@mrgnlabs/marginfi-client-v2";
import {
ActionMessageType,
CalculateLoopingProps,
DYNAMIC_SIMULATION_ERRORS,
extractErrorString,
TradeActionTxns,
STATIC_SIMULATION_ERRORS,
usePrevious,
CalculateTradingProps,
} from "@mrgnlabs/mrgn-utils";

import { SimulationStatus } from "~/components/action-box-v2/utils";
Expand Down Expand Up @@ -110,8 +110,6 @@ export function useTradeSimulation({
if (props.txns.length > 0) {
const simulationResult = await getSimulationResult(props);

console.log("simulationResult", simulationResult);

if (simulationResult.actionMethod) {
return { simulationResult: null, actionMessage: simulationResult.actionMethod };
} else if (simulationResult.simulationResult) {
Expand All @@ -126,16 +124,14 @@ export function useTradeSimulation({
};

const fetchTradeTxnsAction = async (
props: CalculateLoopingProps
props: CalculateTradingProps
): Promise<{ actionTxns: TradeActionTxns | null; actionMessage: ActionMessageType | null }> => {
try {
console.log("fetching trade txns, props", props);
const tradingResult = await generateTradeTx({
...props,
});

console.log("tradingResult", tradingResult);

if (tradingResult && "actionQuote" in tradingResult) {
return { actionTxns: tradingResult, actionMessage: null };
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,16 @@ export const TradeBoxV2 = ({ activePool, side = "long" }: TradeBoxV2Props) => {
leverage,
}),

[amount, connected, actionTxns, tradeState, borrowBank, depositBank, leverage]
[
amount,
connected,
depositBank,
borrowBank,
actionTxns.actionQuote,
tradeState,
activePoolExtended.status,
leverage,
]
);

const actionMethods = React.useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
deserializeInstruction,
getAdressLookupTableAccounts,
MultiStepToastHandle,
CalculateTradingProps,
} from "@mrgnlabs/mrgn-utils";

import { ExecuteActionsCallbackProps } from "~/components/action-box-v2/types";
Expand Down Expand Up @@ -113,29 +114,30 @@ const handleExecuteTradeAction = async ({
}
};

export async function generateTradeTx(props: CalculateLoopingProps): Promise<TradeActionTxns | ActionMessageType> {
export async function generateTradeTx(props: CalculateTradingProps): Promise<TradeActionTxns | ActionMessageType> {
let swapTx: { quote?: QuoteResponse; tx?: SolanaTransaction; error?: ActionMessageType } | undefined;

const swapNeeded = props.tradeState === "long";
if (swapNeeded) {
console.log("Creating Quote swap transaction...");
try {
swapTx = await createSwapTx(
props,

props.marginfiClient.wallet.publicKey,
props.marginfiClient.provider.connection
);
if (swapTx.error) {
console.error("Quote swap transaction error:", swapTx.error);
console.error("Swap transaction error:", swapTx.error);
return swapTx.error;
} else {
if (!swapTx.tx || !swapTx.quote) {
// TODO: improve error message
console.error("Swap transaction error: no tx or quote");
return STATIC_SIMULATION_ERRORS.FL_FAILED;
}
}
} catch (error) {
console.error("Error creating Quote swap transaction:", error);
// TODO: improve error message
console.error("Swap transaction error:", error);
return STATIC_SIMULATION_ERRORS.FL_FAILED;
}
}
Expand Down Expand Up @@ -175,25 +177,37 @@ export async function generateTradeTx(props: CalculateLoopingProps): Promise<Tra
if (result && "actionQuote" in result) {
return {
...result,
transactions: [...(swapTx?.tx ? [swapTx.tx] : []), ...accountCreationTx, ...(result.transactions ?? [])],
transactions: [
...(swapTx?.tx ? [swapTx.tx] : []),
...accountCreationTx,
...addArenaTxTypes(result.transactions, props.tradeState),
],
marginfiAccount: finalAccount ?? undefined,
};
}

return result;
}

function addArenaTxTypes(txs: SolanaTransaction[], tradeState: "long" | "short") {
return txs.map((tx) =>
tx.type === TransactionType.LOOP
? addTransactionMetadata(tx, {
...tx,
type: tradeState === "long" ? TransactionType.LONG : TransactionType.SHORT,
})
: tx
);
}

async function createMarginfiAccountTx(
props: CalculateLoopingProps
): Promise<{ account: MarginfiAccountWrapper; tx: SolanaTransaction }> {
// if no marginfi account, we need to create one
console.log("Creating new marginfi account transaction...");
const authority = props.marginfiAccount?.authority ?? props.marginfiClient.provider.publicKey;

const marginfiAccountKeypair = Keypair.generate();

// create a dummy account with 15 empty balances to be used in other transactions
const dummyWrappedI80F48 = bigNumberToWrappedI80F48(new BigNumber(0));

const dummyBalances: BalanceRaw[] = Array(15).fill({
active: false,
bankPk: new PublicKey("11111111111111111111111111111111"),
Expand All @@ -202,7 +216,6 @@ async function createMarginfiAccountTx(
emissionsOutstanding: dummyWrappedI80F48,
lastUpdate: new BN(0),
});

const rawAccount: MarginfiAccountRaw = {
group: props.marginfiClient.group.address,
authority: authority,
Expand Down
3 changes: 2 additions & 1 deletion apps/marginfi-v2-trading/src/hooks/useMarginfiClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ export function useMarginfiClient({
bankMetadataByBankPk,
clientOptions?.bundleSimRpcEndpoint,
clientOptions?.processTransactionStrategy,
lookupTables
lookupTables,
true // Add arena tag to transactions
);

return client;
Expand Down
11 changes: 6 additions & 5 deletions apps/marginfi-v2-ui/src/pages/deposit-swap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function DepositSwapPage() {
initialized,
extendedBankInfosWithoutStakedAssets,
fetchWalletTokens,
extendedbankInfos,
extendedBankInfos,
marginfiClient,
updateWalletTokens,
updateWalletToken,
Expand All @@ -38,13 +38,13 @@ export default function DepositSwapPage() {
React.useEffect(() => {
if (
wallet &&
extendedbankInfos &&
extendedbankInfos.length > 0 &&
extendedBankInfos &&
extendedBankInfos.length > 0 &&
(walletTokens === null || walletTokens.length === 0)
) {
fetchWalletTokens(wallet, extendedbankInfos);
fetchWalletTokens(wallet, extendedBankInfos);
}
}, [fetchWalletTokens, wallet, walletTokens, extendedbankInfos]);
}, [fetchWalletTokens, wallet, walletTokens, extendedBankInfos]);

const fetchAndUpdateTokens = React.useCallback(() => {
if (!wallet || !connection) {
Expand Down Expand Up @@ -79,6 +79,7 @@ export default function DepositSwapPage() {
useProvider={true}
depositSwapProps={{
banks: extendedBankInfosWithoutStakedAssets,
allBanks: extendedBankInfos,
connected: connected,
requestedDepositBank: undefined,
requestedSwapBank: undefined,
Expand Down
4 changes: 3 additions & 1 deletion apps/marginfi-v2-ui/src/pages/looper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import { Loader } from "~/components/ui/loader";
import { useWallet } from "~/components/wallet-v2";

export default function LooperPage() {
const [initialized, extendedBankInfosWithoutStakedAssets] = useMrgnlendStore((state) => [
const [initialized, extendedBankInfosWithoutStakedAssets, extendedBankInfos] = useMrgnlendStore((state) => [
state.initialized,
state.extendedBankInfosWithoutStakedAssets,
state.extendedBankInfos,
]);
const { connected, walletContextState } = useWallet();

Expand All @@ -27,6 +28,7 @@ export default function LooperPage() {
loopProps={{
connected: connected,
banks: extendedBankInfosWithoutStakedAssets,
allBanks: extendedBankInfos,
walletContextState: walletContextState,
captureEvent: (event, properties) => {
capture(event, properties);
Expand Down
2 changes: 1 addition & 1 deletion apps/marginfi-v2-ui/src/utils/actionBoxUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function checkActionAvailable({
if (borrowChecks.length) checks.push(...borrowChecks);
break;
case ActionType.Loop:
const loopChecks = canBeLooped(selectedBank, selectedRepayBank, repayCollatQuote);
const loopChecks = canBeLooped(selectedBank, selectedRepayBank, repayCollatQuote, extendedBankInfos);
if (loopChecks.length) checks.push(...loopChecks);
break;
case ActionType.Repay:
Expand Down
7 changes: 6 additions & 1 deletion packages/marginfi-client-v2/src/clients/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class MarginfiClient {
public processTransactionStrategy?: ProcessTransactionStrategy;
private preloadedBankAddresses?: PublicKey[];
private bundleSimRpcEndpoint: string;
private addArenaTxTag: boolean;

// --------------------------------------------------------------------------
// Factories
Expand All @@ -139,7 +140,8 @@ class MarginfiClient {
readonly bankMetadataMap?: BankMetadataMap,
bundleSimRpcEndpoint?: string,
processTransactionStrategy?: ProcessTransactionStrategy,
lookupTablesAddresses?: PublicKey[]
lookupTablesAddresses?: PublicKey[],
addArenaTxTag?: boolean
) {
this.group = group;
this.banks = banks;
Expand All @@ -151,6 +153,7 @@ class MarginfiClient {
this.bundleSimRpcEndpoint = bundleSimRpcEndpoint ?? program.provider.connection.rpcEndpoint;
this.processTransactionStrategy = processTransactionStrategy;
this.lookupTablesAddresses = lookupTablesAddresses ?? [];
this.addArenaTxTag = addArenaTxTag ?? false;
}

/**
Expand Down Expand Up @@ -938,6 +941,7 @@ class MarginfiClient {
programId: this.program.programId,
bundleSimRpcEndpoint: this.bundleSimRpcEndpoint,
dynamicStrategy: processOpts?.dynamicStrategy ?? this.processTransactionStrategy,
addArenaTxTag: this.addArenaTxTag,
};

console.log("processOpts", processOpts);
Expand Down Expand Up @@ -976,6 +980,7 @@ class MarginfiClient {
programId: this.program.programId,
bundleSimRpcEndpoint: this.bundleSimRpcEndpoint,
dynamicStrategy: processOpts?.dynamicStrategy ?? this.processTransactionStrategy,
addArenaTxTag: this.addArenaTxTag,
};

const [signature] = await processTransactions({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import {
getComputeBudgetUnits,
microLamportsToUi,
uiToMicroLamports,
MARGINFI_PROGRAM,
addTransactionMetadata,
TransactionArenaKeyMap,
} from "@mrgnlabs/mrgn-common";

import { MARGINFI_IDL, MarginfiIdlType } from "../../../idl";
Expand All @@ -40,17 +43,6 @@ export function isFlashloan(tx: SolanaTransaction): boolean {
return false;
}

export function hasBundleTip(tx: SolanaTransaction): boolean {
if (isV0Tx(tx)) {
const addressLookupTableAccounts = tx.addressLookupTables ?? [];
const message = decompileV0Transaction(tx, addressLookupTableAccounts);
const idl = { ...MARGINFI_IDL, address: new PublicKey(0) } as unknown as MarginfiIdlType;
const decoded = message.instructions.map((ix) => decodeInstruction(idl, ix.data));
return decoded.some((ix) => ix?.name.toLowerCase().includes("flashloan"));
}
return false;
}

function getFlashloanIndex(transactions: SolanaTransaction[]): number | null {
for (const [index, transaction] of transactions.entries()) {
if (isFlashloan(transaction)) {
Expand All @@ -61,24 +53,27 @@ function getFlashloanIndex(transactions: SolanaTransaction[]): number | null {
}

export function formatTransactions(
transactions: SolanaTransaction[],
transactionsArg: SolanaTransaction[],
broadcastType: TransactionBroadcastType,
priorityFeeMicro: number,
bundleTipUi: number,
feePayer: PublicKey,
blockhash: string,
maxCapUi?: number
maxCapUi?: number,
addArenaTxTag?: boolean
): VersionedTransaction[] {
let formattedTransactions: VersionedTransaction[] = [];

const flashloanIndex = getFlashloanIndex(transactions);
transactions.forEach((tx) => {
const flashloanIndex = getFlashloanIndex(transactionsArg);
transactionsArg.forEach((tx) => {
if (!isV0Tx(tx)) {
tx.recentBlockhash = blockhash;
tx.feePayer = feePayer;
}
});

let transactions = addArenaTxTag ? addArenaTxTags(transactionsArg) : transactionsArg;

const txSizes: number[] = transactions.map((tx) => getTxSize(tx));
const dummyPriorityFeeIx = makePriorityFeeMicroIx(1);

Expand Down Expand Up @@ -173,3 +168,45 @@ export function formatTransactions(

return formattedTransactions;
}

function addArenaTxTags(transactions: SolanaTransaction[]): SolanaTransaction[] {
const txWithTags: SolanaTransaction[] = [];

for (const [index, tx] of transactions.entries()) {
let solanaTx: SolanaTransaction = tx;
const arenaKey = TransactionArenaKeyMap[tx.type];
console.log("arenaKey", arenaKey);
console.log("tx.type", tx.type);

if (arenaKey) {
if (isV0Tx(solanaTx)) {
console.log("tx", solanaTx);
const addressLookupTableAccounts = solanaTx.addressLookupTables ?? [];
const message = decompileV0Transaction(solanaTx, addressLookupTableAccounts);

message.instructions[0].keys.push({
pubkey: arenaKey,
isSigner: false,
isWritable: false,
});
solanaTx = addTransactionMetadata(
new VersionedTransaction(message.compileToV0Message(tx.addressLookupTables)),
{
signers: solanaTx.signers,
addressLookupTables: solanaTx.addressLookupTables,
type: solanaTx.type,
unitsConsumed: solanaTx.unitsConsumed,
}
);
} else {
solanaTx.instructions[0].keys.push({
pubkey: arenaKey,
isSigner: false,
isWritable: false,
});
}
}
txWithTags.push(solanaTx);
}
return txWithTags;
}
Loading

0 comments on commit cb56b18

Please sign in to comment.