UNPKG

@quartz-labs/sdk

Version:

SDK for interacting with the Quartz Protocol

812 lines 41.6 kB
import { DEPOSIT_ADDRESS_DATA_SIZE, DRIFT_PROGRAM_ID, MARKET_INDEX_SOL, MARKET_INDEX_USDC, MESSAGE_TRANSMITTER_PROGRAM_ID, QUARTZ_PROGRAM_ID, SPEND_FEE_DESTINATION, TOKEN_MESSAGE_MINTER_PROGRAM_ID, ZERO, } from "./config/constants.js"; import { getDriftSpotMarketVaultPublicKey, getDriftStatePublicKey, getPythOracle, getDriftSignerPublicKey, getVaultPublicKey, getCollateralRepayLedgerPublicKey, getBridgeRentPayerPublicKey, getLocalToken, getTokenMinter, getRemoteTokenMessenger, getTokenMessenger, getSenderAuthority, getMessageTransmitter, getEventAuthority, getInitRentPayerPublicKey, getSpendMulePublicKey, getTimeLockRentPayerPublicKey, getWithdrawMulePublicKey, getDepositAddressPublicKey, getDepositMulePublicKey, getCollateralRepayMulePublicKey, getDepositAddressAtaPublicKey, } from "./utils/accounts.js"; import { calculateWithdrawOrderBalances, getTokenAccountBalance, getTokenProgram, } from "./utils/helpers.js"; import { getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, } from "@solana/spl-token"; import { SystemProgram, SYSVAR_INSTRUCTIONS_PUBKEY } from "@solana/web3.js"; import { ASSOCIATED_TOKEN_PROGRAM_ID } from "@solana/spl-token"; import BN from "bn.js"; import { getMarketIndicesRecord, MarketIndex, TOKENS, } from "./config/tokens.js"; import { Keypair } from "@solana/web3.js"; import { DriftUser } from "./types/classes/DriftUser.class.js"; export class QuartzUser { pubkey; vaultPubkey; depositAddress; spendLimitPerTransaction; spendLimitPerTimeframe; remainingSpendLimitPerTimeframe; nextTimeframeResetTimestamp; timeframeInSeconds; connection; program; quartzLookupTable; client; driftUser; driftSigner; constructor(pubkey, connection, client, program, quartzLookupTable, driftClient, driftUserAccount, spendLimitPerTransaction, spendLimitPerTimeframe, remainingSpendLimitPerTimeframe, nextTimeframeResetTimeframe, timeframeInSeconds) { this.pubkey = pubkey; this.connection = connection; this.client = client; this.program = program; this.quartzLookupTable = quartzLookupTable; this.vaultPubkey = getVaultPublicKey(pubkey); this.depositAddress = getDepositAddressPublicKey(pubkey); this.driftSigner = getDriftSignerPublicKey(); this.spendLimitPerTransaction = spendLimitPerTransaction; this.spendLimitPerTimeframe = spendLimitPerTimeframe; this.remainingSpendLimitPerTimeframe = remainingSpendLimitPerTimeframe; this.nextTimeframeResetTimestamp = nextTimeframeResetTimeframe; this.timeframeInSeconds = timeframeInSeconds; this.driftUser = new DriftUser(this.vaultPubkey, driftClient, driftUserAccount); } getHealth() { return this.driftUser.getHealth(); } async getRepayUsdcValueForTargetHealth(targetHealth, repayAssetWeight, repayLiabilityWeight) { // New Quartz health after repayment is given as: // // marginRequirement - (repayValue * repayLiabilityWeight) // targetHealth = 1 - ----------------------------------------------------------- // currentWeightedCollateral - (repayValue * repayAssetWeight) // // Where repayAssetWeight and repayLiabilityWeight are between 0 and 1. // The following is an algebraicly simplified expression of the above formula, in terms of repayValue. if (targetHealth < 0 || targetHealth > 100) throw Error("Target health must be between 0 and 100 inclusive"); if (!Number.isInteger(targetHealth)) throw new Error("Target health must be a whole number"); if (repayAssetWeight < 0 || repayAssetWeight > 100) throw Error("Repay collateral weight must be between 0 and 100 inclusive"); if (!Number.isInteger(repayAssetWeight)) throw new Error("Repay collateral weight must be a whole number"); if (repayLiabilityWeight < 100) throw Error("Repay liability weight must be greater or equal to 100"); if (!Number.isInteger(repayLiabilityWeight)) throw new Error("Repay liability weight must be a whole number"); if (targetHealth <= this.getHealth()) throw Error("Target health must be greater than current health"); const openOrders = []; // Ignore orders for liquidation const currentWeightedCollateral = await this.getTotalWeightedCollateralValue(openOrders); const marginRequirement = await this.getMarginRequirement(openOrders); const targetHealthDecimal = targetHealth / 100; const repayAssetWeightDecimal = repayAssetWeight / 100; const repayLiabilityWeightDecimal = repayLiabilityWeight / 100; const repayValueUsdcBaseUnits = Math.round((currentWeightedCollateral * (targetHealthDecimal - 1) + marginRequirement) / (repayAssetWeightDecimal * (targetHealthDecimal - 1) + repayLiabilityWeightDecimal)); return repayValueUsdcBaseUnits; } async getTotalCollateralValue(openWithdrawOrders) { openWithdrawOrders = await this.validateOpenWithdrawOrders(openWithdrawOrders); const openOrderBalances = calculateWithdrawOrderBalances(openWithdrawOrders); return this.driftUser .getTotalCollateralValue(undefined, false, true, openOrderBalances) .toNumber(); } async getTotalSpotLiabilityValue(openWithdrawOrders) { openWithdrawOrders = await this.validateOpenWithdrawOrders(openWithdrawOrders); const openOrderBalances = calculateWithdrawOrderBalances(openWithdrawOrders); return this.driftUser .getTotalSpotLiabilityValue(undefined, false, true, openOrderBalances) .toNumber(); } async getTotalWeightedCollateralValue(openWithdrawOrders) { openWithdrawOrders = await this.validateOpenWithdrawOrders(openWithdrawOrders); const openOrderBalances = calculateWithdrawOrderBalances(openWithdrawOrders); return this.driftUser .getTotalCollateralValue("Initial", false, true, openOrderBalances) .toNumber(); } async getMarginRequirement(openWithdrawOrders) { openWithdrawOrders = await this.validateOpenWithdrawOrders(openWithdrawOrders); const openOrderBalances = calculateWithdrawOrderBalances(openWithdrawOrders); return this.driftUser .getInitialMarginRequirement(openOrderBalances) .toNumber(); } async getAvailableCreditUsdcBaseUnits(openWithdrawOrders) { return await this.getWithdrawalLimit(MARKET_INDEX_USDC, false, openWithdrawOrders); } async validateOpenWithdrawOrders(openWithdrawOrders) { if (openWithdrawOrders === undefined) { const accounts = await this.client.getOpenWithdrawOrders(this.pubkey); return accounts.map((account) => account.account); } return openWithdrawOrders.filter((order) => order.timeLock.owner.equals(this.pubkey)); } async getDepositAddressBalance(marketIndex) { if (marketIndex === MARKET_INDEX_SOL) { const rent = await this.connection.getMinimumBalanceForRentExemption(DEPOSIT_ADDRESS_DATA_SIZE); const balance = await this.connection.getBalance(this.depositAddress); const availableBalance = balance - rent; return new BN(Math.max(availableBalance, 0)); } const depositAddressAta = await getDepositAddressAtaPublicKey(this.connection, this.pubkey, marketIndex); const depositAddressAtaBalance = await getTokenAccountBalance(this.connection, depositAddressAta); return new BN(depositAddressAtaBalance); } async getAllDepositAddressBalances() { const depositBalances = getMarketIndicesRecord(ZERO); depositBalances[MARKET_INDEX_SOL] = await this.getDepositAddressBalance(MARKET_INDEX_SOL); const splIndices = MarketIndex.filter((index) => index !== MARKET_INDEX_SOL); const [standardTokenAccounts, token2022Accounts] = await Promise.all([ this.connection.getParsedTokenAccountsByOwner(this.depositAddress, { programId: TOKEN_PROGRAM_ID, }), this.connection.getParsedTokenAccountsByOwner(this.depositAddress, { programId: TOKEN_2022_PROGRAM_ID, }), ]); const mintToMarketIndex = {}; for (const index of splIndices) { mintToMarketIndex[TOKENS[index].mint.toBase58()] = index; } const allTokenAccounts = [ ...standardTokenAccounts.value, ...token2022Accounts.value, ]; for (const account of allTokenAccounts) { const parsedInfo = account.account.data.parsed.info; const mintAddress = parsedInfo.mint; const amount = parsedInfo.tokenAmount.amount; const marketIndex = mintToMarketIndex[mintAddress]; if (marketIndex !== undefined) { depositBalances[marketIndex] = new BN(amount); } } return depositBalances; } async getTokenBalance(marketIndex, openWithdrawOrders) { openWithdrawOrders = await this.validateOpenWithdrawOrders(openWithdrawOrders); const openOrderBalances = calculateWithdrawOrderBalances(openWithdrawOrders); const driftDeposit = this.driftUser.getTokenAmount(marketIndex, openOrderBalances); const depositAddressBalance = await this.getDepositAddressBalance(marketIndex); return driftDeposit.add(depositAddressBalance); } async getMultipleTokenBalances(marketIndices, openWithdrawOrders) { openWithdrawOrders = await this.validateOpenWithdrawOrders(openWithdrawOrders); const openOrderBalances = calculateWithdrawOrderBalances(openWithdrawOrders); const driftBalances = marketIndices.reduce((acc, index) => { acc[index] = this.driftUser.getTokenAmount(index, openOrderBalances); return acc; }, {}); const depositBalances = await this.getAllDepositAddressBalances(); return marketIndices.reduce((acc, index) => { const driftDeposit = driftBalances[index] || ZERO; const addressDeposit = depositBalances[index] || ZERO; acc[index] = driftDeposit.add(addressDeposit); return acc; }, {}); } async getWithdrawalLimit(marketIndex, reduceOnly = false, openWithdrawOrders) { openWithdrawOrders = await this.validateOpenWithdrawOrders(openWithdrawOrders); const openOrderBalances = calculateWithdrawOrderBalances(openWithdrawOrders); const driftLimit = this.driftUser.getWithdrawalLimit(marketIndex, reduceOnly, openOrderBalances); const depositAddressBalance = await this.getDepositAddressBalance(marketIndex); return driftLimit.add(depositAddressBalance); } async getMultipleWithdrawalLimits(marketIndices, reduceOnly = false, openWithdrawOrders) { openWithdrawOrders = await this.validateOpenWithdrawOrders(openWithdrawOrders); const openOrderBalances = calculateWithdrawOrderBalances(openWithdrawOrders); const driftLimits = marketIndices.reduce((acc, index) => { acc[index] = this.driftUser.getWithdrawalLimit(index, reduceOnly, openOrderBalances); return acc; }, {}); const depositAddressBalances = await this.getAllDepositAddressBalances(); return marketIndices.reduce((acc, index) => { const driftLimit = driftLimits[index] || ZERO; const addressBalance = depositAddressBalances[index] || ZERO; acc[index] = driftLimit.add(addressBalance); return acc; }, {}); } // --- Instructions --- getLookupTables() { return [this.quartzLookupTable]; } /** * Creates instructions to upgrade a Quartz user account. * @param spendLimitPerTransaction - The card spend limit per transaction. * @param spendLimitPerTimeframe - The card spend limit per timeframe. * @param timeframeInSlots - The timeframe in slots (eg: 216,000 for ~1 day). * @returns {Promise<{ * ixs: TransactionInstruction[], * lookupTables: AddressLookupTableAccount[], * signers: Keypair[] * }>} Object containing: * - ixs: Array of instructions to upgrade the Quartz user account. * - lookupTables: Array of lookup tables for building VersionedTransaction. * - signers: Array of signer keypairs that must sign the transaction the instructions are added to. * @throw Error if the RPC connection fails. The transaction will fail if the account does not require an upgrade. */ async makeUpgradeAccountIxs(spendLimitPerTransaction, spendLimitPerTimeframe, timeframeInSeconds, nextTimeframeResetTimestamp) { const ix_upgradeAccount = await this.program.methods .upgradeVault(spendLimitPerTransaction, spendLimitPerTimeframe, timeframeInSeconds, nextTimeframeResetTimestamp) .accounts({ vault: this.vaultPubkey, owner: this.pubkey, initRentPayer: getInitRentPayerPublicKey(), systemProgram: SystemProgram.programId, }) .instruction(); return { ixs: [ix_upgradeAccount], lookupTables: this.getLookupTables(), signers: [], }; } async makeFulfilDepositIxs(marketIndex, caller) { const mint = TOKENS[marketIndex].mint; const tokenProgram = await getTokenProgram(this.connection, mint); const depositAddress = getDepositAddressPublicKey(this.pubkey); let depositAddressAta = null; if (marketIndex !== MARKET_INDEX_SOL) { depositAddressAta = getAssociatedTokenAddressSync(mint, depositAddress, true, tokenProgram); } const ix = await this.program.methods .fulfilDeposit(marketIndex) .accounts({ vault: this.vaultPubkey, depositAddress: depositAddress, depositAddressSpl: depositAddressAta, owner: this.pubkey, mule: getDepositMulePublicKey(this.pubkey, mint), caller: caller, mint: mint, driftUser: this.driftUser.pubkey, driftUserStats: this.driftUser.statsPubkey, driftState: getDriftStatePublicKey(), spotMarketVault: getDriftSpotMarketVaultPublicKey(marketIndex), tokenProgram: tokenProgram, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, driftProgram: DRIFT_PROGRAM_ID, systemProgram: SystemProgram.programId, }) .remainingAccounts(this.driftUser.getRemainingAccounts(marketIndex)) .instruction(); return { ixs: [ix], lookupTables: this.getLookupTables(), signers: [], }; } /** * Creates instructions to rescue unsupported tokens from a Quartz rescue token. * @param mint - The mint of the token to rescue. * @returns {Promise<{ * ixs: TransactionInstruction[], * lookupTables: AddressLookupTableAccount[], * signers: Keypair[] * }>} Object containing: * - ixs: Array of instructions to initiate a withdraw order. * - lookupTables: Array of lookup tables for building VersionedTransaction. * - signers: Array of signer keypairs that must sign the transaction the instructions are added to. * @throw Error if the RPC connection fails, if the mint's ATA does not exist, or has 0 balance. */ async makeRescueDepositIxs(destination, mint) { const tokenProgram = await getTokenProgram(this.connection, mint); const depositAddress = getDepositAddressPublicKey(this.pubkey); const depositAddressAta = getAssociatedTokenAddressSync(mint, depositAddress, true, tokenProgram); const ix = await this.program.methods .rescueDeposit() .accounts({ vault: this.vaultPubkey, owner: this.pubkey, destination: destination, destinationSpl: getAssociatedTokenAddressSync(mint, destination, true, tokenProgram), depositAddress: depositAddress, depositAddressSpl: depositAddressAta, mint: mint, tokenProgram: tokenProgram, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, }) .instruction(); return { ixs: [ix], lookupTables: this.getLookupTables(), signers: [], }; } /** * Creates instructions to iniaite a withdraw order from the Quartz user account, which will be fulfilled after the time lock. * @param amountBaseUnits - The amount of tokens to withdraw. * @param marketIndex - The market index of the token to withdraw. * @param reduceOnly - True means amount will be capped so a positive balance (collateral) cannot become a negative balance (loan). * @returns {Promise<{ * ixs: TransactionInstruction[], * lookupTables: AddressLookupTableAccount[], * signers: Keypair[] * }>} Object containing: * - ixs: Array of instructions to initiate a withdraw order. * - lookupTables: Array of lookup tables for building VersionedTransaction. * - signers: Array of signer keypairs that must sign the transaction the instructions are added to. * @throw Error if the RPC connection fails. */ async makeInitiateWithdrawIxs(amountBaseUnits, marketIndex, reduceOnly, paidByUser = false, destinationAddress = this.pubkey) { const orderAccount = Keypair.generate(); const timeLockRentPayer = paidByUser ? this.pubkey : getTimeLockRentPayerPublicKey(); const ix = await this.program.methods .initiateWithdraw(new BN(amountBaseUnits), marketIndex, reduceOnly) .accounts({ vault: this.vaultPubkey, owner: this.pubkey, withdrawOrder: orderAccount.publicKey, timeLockRentPayer: timeLockRentPayer, systemProgram: SystemProgram.programId, destination: destinationAddress, }) .instruction(); return { ixs: [ix], lookupTables: this.getLookupTables(), signers: [orderAccount], }; } /** * Creates instructions to withdraw a token from the Quartz user account. * @param orderAccount - The public key of the withdraw order, which must be created with the initiateWithdraw instruction. * @returns {Promise<{ * ixs: TransactionInstruction[], * lookupTables: AddressLookupTableAccount[], * signers: Keypair[] * }>} Object containing: * - ixs: Array of instructions to withdraw the token from the Quartz user account. * - lookupTables: Array of lookup tables for building VersionedTransaction. * - signers: Array of signer keypairs that must sign the transaction the instructions are added to. * @throw Error if the RPC connection fails. The transaction will fail if the account does not have enough tokens or, (when taking out a loan) the account health is not high enough for a loan. */ async makeFulfilWithdrawIxs(orderAccount, caller) { const order = await this.program.account.withdrawOrder.fetch(orderAccount); const marketIndex = order.driftMarketIndex; const timeLockRentPayer = order.timeLock.isOwnerPayer ? this.pubkey : getTimeLockRentPayerPublicKey(); const mint = TOKENS[marketIndex].mint; const tokenProgram = await getTokenProgram(this.connection, mint); const destination = order.destination; const destinationSpl = getAssociatedTokenAddressSync(mint, destination, true, tokenProgram); const depositAddress = getDepositAddressPublicKey(this.pubkey); const depositAddressAta = getAssociatedTokenAddressSync(mint, depositAddress, true, tokenProgram); const destinationSplValue = marketIndex === MARKET_INDEX_SOL // TODO: Remove once validation fixed in program ? QUARTZ_PROGRAM_ID : destinationSpl; const ix_fulfilWithdraw = await this.program.methods .fulfilWithdraw() .accounts({ withdrawOrder: orderAccount, timeLockRentPayer: timeLockRentPayer, caller: caller, vault: this.vaultPubkey, mule: getWithdrawMulePublicKey(this.pubkey, mint), owner: this.pubkey, mint: mint, driftUser: this.driftUser.pubkey, driftUserStats: this.driftUser.statsPubkey, driftState: getDriftStatePublicKey(), spotMarketVault: getDriftSpotMarketVaultPublicKey(marketIndex), driftSigner: this.driftSigner, tokenProgram: tokenProgram, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, driftProgram: DRIFT_PROGRAM_ID, systemProgram: SystemProgram.programId, destination: destination, destinationSpl: destinationSplValue, depositAddress: depositAddress, depositAddressSpl: depositAddressAta, admin: this.program.programId, }) .remainingAccounts(this.driftUser.getRemainingAccounts(marketIndex)) .instruction(); return { ixs: [ix_fulfilWithdraw], lookupTables: this.getLookupTables(), signers: [], }; } async makeFeePaymentIxs(amountBaseUnits, marketIndex, admin, reduceOnly) { const timeLockRentPayer = getTimeLockRentPayerPublicKey(); const orderAccount = Keypair.generate(); const ix_initiateWithdraw = await this.program.methods .initiateWithdraw(new BN(amountBaseUnits), marketIndex, reduceOnly) .accounts({ vault: this.vaultPubkey, owner: this.pubkey, withdrawOrder: orderAccount.publicKey, timeLockRentPayer: timeLockRentPayer, systemProgram: SystemProgram.programId, destination: admin.publicKey, }) .instruction(); // Get destination const mint = TOKENS[marketIndex].mint; const tokenProgram = await getTokenProgram(this.connection, mint); const destinationSpl = getAssociatedTokenAddressSync(mint, admin.publicKey, true, tokenProgram); // Get deposit address (in case any funds are there) const depositAddress = getDepositAddressPublicKey(this.pubkey); const depositAddressAta = getAssociatedTokenAddressSync(mint, depositAddress, true, tokenProgram); const destinationSplValue = marketIndex === MARKET_INDEX_SOL // TODO: Remove once validation fixed in program ? QUARTZ_PROGRAM_ID : destinationSpl; const ix_fulfilWithdraw = await this.program.methods .fulfilWithdraw() .accounts({ withdrawOrder: orderAccount.publicKey, timeLockRentPayer: timeLockRentPayer, caller: admin.publicKey, vault: this.vaultPubkey, mule: getWithdrawMulePublicKey(this.pubkey, mint), owner: this.pubkey, mint: mint, driftUser: this.driftUser.pubkey, driftUserStats: this.driftUser.statsPubkey, driftState: getDriftStatePublicKey(), spotMarketVault: getDriftSpotMarketVaultPublicKey(marketIndex), driftSigner: this.driftSigner, tokenProgram: tokenProgram, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, driftProgram: DRIFT_PROGRAM_ID, systemProgram: SystemProgram.programId, destination: admin.publicKey, destinationSpl: destinationSplValue, depositAddress: depositAddress, depositAddressSpl: depositAddressAta, admin: admin.publicKey, }) .remainingAccounts(this.driftUser.getRemainingAccounts(marketIndex)) .instruction(); return { ixs: [ix_initiateWithdraw, ix_fulfilWithdraw], lookupTables: this.getLookupTables(), signers: [admin, orderAccount], }; } /** * Creates instructions to withdraw a token from the Quartz user account. * @param orderAccount - The public key of the withdraw order, which must be created with the initiateWithdraw instruction. * @returns {Promise<{ * ixs: TransactionInstruction[], * lookupTables: AddressLookupTableAccount[], * signers: Keypair[] * }>} Object containing: * - ixs: Array of instructions to withdraw the token from the Quartz user account. * - lookupTables: Array of lookup tables for building VersionedTransaction. * - signers: Array of signer keypairs that must sign the transaction the instructions are added to. * @throw Error if the RPC connection fails. The transaction will fail if the account does not have enough tokens or, (when taking out a loan) the account health is not high enough for a loan. */ async makeCancelWithdrawIxs(orderAccount) { const order = await this.program.account.withdrawOrder.fetch(orderAccount); const timeLockRentPayer = order.timeLock.isOwnerPayer ? this.pubkey : getTimeLockRentPayerPublicKey(); const ix_fulfilWithdraw = await this.program.methods .cancelWithdraw() .accounts({ withdrawOrder: orderAccount, owner: this.pubkey, timeLockRentPayer: timeLockRentPayer, systemProgram: SystemProgram.programId, }) .instruction(); return { ixs: [ix_fulfilWithdraw], lookupTables: this.getLookupTables(), signers: [], }; } /** * Creates instructions to iniate and order to adjust the spend limits of a Quartz user account. * @param spendLimitPerTransaction - The new spend limit per transaction. * @param spendLimitPerTimeframe - The new spend limit per timeframe. * @param timeframeInSeconds - The new timeframe in seconds. * @param nextTimeframeResetTimestamp - The new next timeframe reset timestamp. * @returns {Promise<{ * ixs: TransactionInstruction[], * lookupTables: AddressLookupTableAccount[], * signers: Keypair[] * }>} Object containing: * - ixs: Array of instructions to adjust the spend limits. * - lookupTables: Array of lookup tables for building VersionedTransaction. * - signers: Array of signer keypairs that must sign the transaction the instructions are added to. * @throw Error if the RPC connection fails. Or if the spend limits are invalid. */ async makeInitiateSpendLimitsIxs(spendLimitPerTransaction, spendLimitPerTimeframe, timeframeInSeconds, nextTimeframeResetTimestamp, paidByUser = false) { const orderAccount = Keypair.generate(); const timeLockRentPayer = paidByUser ? this.pubkey : getTimeLockRentPayerPublicKey(); const ix = await this.program.methods .initiateSpendLimits(spendLimitPerTransaction, spendLimitPerTimeframe, timeframeInSeconds, nextTimeframeResetTimestamp) .accounts({ vault: this.vaultPubkey, owner: this.pubkey, spendLimitsOrder: orderAccount.publicKey, timeLockRentPayer: timeLockRentPayer, systemProgram: SystemProgram.programId, }) .instruction(); return { ixs: [ix], lookupTables: this.getLookupTables(), signers: [orderAccount], }; } /** * Creates instructions to update the card spend limits of a Quartz user account. * @param orderAccount - The public key of the spend limits order, which must be created with the initiateSpendLimits instruction. * @returns {Promise<{ * ixs: TransactionInstruction[], * lookupTables: AddressLookupTableAccount[], * signers: Keypair[] * }>} Object containing: * - ixs: Array of instructions to fulfil the spend limits order. * - lookupTables: Array of lookup tables for building VersionedTransaction. * - signers: Array of signer keypairs that must sign the transaction the instructions are added to. * @throw Error if the RPC connection fails. */ async makeFulfilSpendLimitsIxs(orderAccount, caller) { const order = await this.program.account.spendLimitsOrder.fetch(orderAccount); const timeLockRentPayer = order.timeLock.isOwnerPayer ? this.pubkey : getTimeLockRentPayerPublicKey(); const ix_fulfilSpendLimits = await this.program.methods .fulfilSpendLimits() .accounts({ spendLimitsOrder: orderAccount, timeLockRentPayer: timeLockRentPayer, caller: caller, vault: this.vaultPubkey, owner: this.pubkey, systemProgram: SystemProgram.programId, }) .instruction(); return { ixs: [ix_fulfilSpendLimits], lookupTables: this.getLookupTables(), signers: [], }; } /** * Creates instructions to instantly increase the spend limits of a Quartz user account, skipping the time lock. * @param spendLimitPerTransaction - The new spend limit per transaction. * @param spendLimitPerTimeframe - The new spend limit per timeframe. * @param timeframeInSeconds - The new timeframe in seconds. * @param nextTimeframeResetTimestamp - The new next timeframe reset timestamp. * @returns {Promise<{ * ixs: TransactionInstruction[], * lookupTables: AddressLookupTableAccount[], * signers: Keypair[] * }>} Object containing: * - ixs: Array of instructions to adjust the spend limits. * - lookupTables: Array of lookup tables for building VersionedTransaction. * - signers: Array of signer keypairs that must sign the transaction the instructions are added to. * @throw Error if the RPC connection fails, if the spend limits are invalid, or if the adjustment to spend limits results in a lower spend limit. Lowering spend limits must be done through adjustSpendLimits. */ async makeIncreaseSpendLimitsIxs(spendLimitPerTransaction, spendLimitPerTimeframe, timeframeInSeconds, nextTimeframeResetTimestamp) { const ix = await this.program.methods .increaseSpendLimits(spendLimitPerTransaction, spendLimitPerTimeframe, timeframeInSeconds, nextTimeframeResetTimestamp) .accounts({ vault: this.vaultPubkey, owner: this.pubkey, }) .instruction(); return { ixs: [ix], lookupTables: this.getLookupTables(), signers: [], }; } /** * Creates instructions to spend using the Quartz card. * @param amountSpendBaseUnits - The amount of tokens to spend. * @param spendCaller - The public key of the Quartz spend caller. * @param spendFee - True means a percentage of the spend will be sent to the spend fee address. * @returns {Promise<{ * ixs: TransactionInstruction[], * lookupTables: AddressLookupTableAccount[], * signers: Keypair[] * }>} Object containing: * - ixs: Array of instructions to repay the loan using collateral. * - lookupTables: Array of lookup tables for building VersionedTransaction. * - signers: Array of signer keypairs that must sign the transaction the instructions are added to. * @throw Error if the RPC connection fails. The transaction will fail if: * - the user does not have enough available tokens. * - the user's spend limit is exceeded. */ async makeSpendIxs(amountSpendBaseUnits, amountFeeBaseUnits, spendCaller) { const messageSentEventData = Keypair.generate(); const mint = TOKENS[MARKET_INDEX_USDC].mint; const tokenProgram = await getTokenProgram(this.connection, mint); const depositAddress = getDepositAddressPublicKey(this.pubkey); const depositAddressUsdc = getAssociatedTokenAddressSync(mint, depositAddress, true, tokenProgram); const ix_startSpend = await this.program.methods .startSpend(new BN(amountSpendBaseUnits), new BN(amountFeeBaseUnits)) .accounts({ vault: this.vaultPubkey, owner: this.pubkey, spendCaller: spendCaller.publicKey, spendFeeDestination: SPEND_FEE_DESTINATION, mule: getSpendMulePublicKey(this.pubkey), usdcMint: TOKENS[MARKET_INDEX_USDC].mint, driftUser: this.driftUser.pubkey, driftUserStats: this.driftUser.statsPubkey, driftState: getDriftStatePublicKey(), spotMarketVault: getDriftSpotMarketVaultPublicKey(MARKET_INDEX_USDC), driftSigner: this.driftSigner, tokenProgram: TOKEN_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, driftProgram: DRIFT_PROGRAM_ID, instructions: SYSVAR_INSTRUCTIONS_PUBKEY, systemProgram: SystemProgram.programId, depositAddress: depositAddress, depositAddressUsdc: depositAddressUsdc, }) .remainingAccounts(this.driftUser.getRemainingAccounts(MARKET_INDEX_USDC)) .instruction(); const ix_completeSpend = await this.program.methods .completeSpend() .accounts({ vault: this.vaultPubkey, owner: this.pubkey, spendCaller: spendCaller.publicKey, mule: getSpendMulePublicKey(this.pubkey), usdcMint: TOKENS[MARKET_INDEX_USDC].mint, bridgeRentPayer: getBridgeRentPayerPublicKey(), senderAuthorityPda: getSenderAuthority(), messageTransmitter: getMessageTransmitter(), tokenMessenger: getTokenMessenger(), remoteTokenMessenger: getRemoteTokenMessenger(), tokenMinter: getTokenMinter(), localToken: getLocalToken(), messageSentEventData: messageSentEventData.publicKey, eventAuthority: getEventAuthority(), messageTransmitterProgram: MESSAGE_TRANSMITTER_PROGRAM_ID, tokenMessengerMinterProgram: TOKEN_MESSAGE_MINTER_PROGRAM_ID, tokenProgram: TOKEN_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, instructions: SYSVAR_INSTRUCTIONS_PUBKEY, systemProgram: SystemProgram.programId, }) .instruction(); return { ixs: [ix_startSpend, ix_completeSpend], lookupTables: this.getLookupTables(), signers: [spendCaller, messageSentEventData], }; } /** * Creates instructions to repay a loan using collateral. * @param caller - The public key of the caller, this can be the owner or someone else if account health is 0%. * @param depositMarketIndex - The market index of the loan token to deposit. * @param withdrawMarketIndex - The market index of the collateral token to withdraw. * @param swapInstruction - The swap instruction to use. Deposit and withdrawn amounts are calculated by the balance change after this instruction. * @param requireOwnerSignature - True means the owner must sign the transaction. This is for manually marking the account info when two signers are required. * @returns {Promise<{ * ixs: TransactionInstruction[], * lookupTables: AddressLookupTableAccount[], * signers: Keypair[] * }>} Object containing: * - ixs: Array of instructions to repay the loan using collateral. * - lookupTables: Array of lookup tables for building VersionedTransaction. * - signers: Array of signer keypairs that must sign the transaction the instructions are added to. * @throw Error if the RPC connection fails. The transaction will fail if: * - the caller does not have enough tokens. * - the account health is above 0% and the caller is not the owner. * - the account health is 0% and the caller is not the owner, but the health has not increased above 0% after the repay. */ async makeCollateralRepayIxs(caller, depositMarketIndex, withdrawMarketIndex, swapInstruction, requireOwnerSignature = false) { const depositMint = TOKENS[depositMarketIndex].mint; const withdrawMint = TOKENS[withdrawMarketIndex].mint; const [depositTokenProgram, withdrawTokenProgram] = await Promise.all([ getTokenProgram(this.connection, depositMint), getTokenProgram(this.connection, withdrawMint), ]); const callerDepositSpl = getAssociatedTokenAddressSync(depositMint, caller, false, depositTokenProgram); const callerWithdrawSpl = getAssociatedTokenAddressSync(withdrawMint, caller, false, withdrawTokenProgram); const driftState = getDriftStatePublicKey(); const collateralRepayLedger = getCollateralRepayLedgerPublicKey(this.pubkey); const startCollateralRepayPromise = this.program.methods .startCollateralRepay() .accounts({ caller: caller, callerDepositSpl: callerDepositSpl, callerWithdrawSpl: callerWithdrawSpl, owner: this.pubkey, vault: this.vaultPubkey, mintDeposit: depositMint, mintWithdraw: withdrawMint, tokenProgramDeposit: depositTokenProgram, tokenProgramWithdraw: withdrawTokenProgram, systemProgram: SystemProgram.programId, instructions: SYSVAR_INSTRUCTIONS_PUBKEY, ledger: collateralRepayLedger, }) .instruction(); const depositCollateralRepayPromise = this.program.methods .depositCollateralRepay(depositMarketIndex) .accounts({ caller: caller, callerSpl: callerDepositSpl, owner: this.pubkey, vault: this.vaultPubkey, mule: getCollateralRepayMulePublicKey(this.pubkey, depositMint), mint: depositMint, driftUser: this.driftUser.pubkey, driftUserStats: this.driftUser.statsPubkey, driftState: driftState, spotMarketVault: getDriftSpotMarketVaultPublicKey(depositMarketIndex), tokenProgram: depositTokenProgram, driftProgram: DRIFT_PROGRAM_ID, systemProgram: SystemProgram.programId, instructions: SYSVAR_INSTRUCTIONS_PUBKEY, ledger: collateralRepayLedger, }) .remainingAccounts(this.driftUser.getRemainingAccounts(depositMarketIndex)) .instruction(); const withdrawCollateralRepayPromise = this.program.methods .withdrawCollateralRepay(withdrawMarketIndex) .accounts({ caller: caller, callerSpl: callerWithdrawSpl, owner: this.pubkey, vault: this.vaultPubkey, mule: getCollateralRepayMulePublicKey(this.pubkey, withdrawMint), mint: withdrawMint, driftUser: this.driftUser.pubkey, driftUserStats: this.driftUser.statsPubkey, driftState: driftState, spotMarketVault: getDriftSpotMarketVaultPublicKey(withdrawMarketIndex), driftSigner: this.driftSigner, tokenProgram: withdrawTokenProgram, driftProgram: DRIFT_PROGRAM_ID, systemProgram: SystemProgram.programId, depositPriceUpdate: getPythOracle(depositMarketIndex), withdrawPriceUpdate: getPythOracle(withdrawMarketIndex), instructions: SYSVAR_INSTRUCTIONS_PUBKEY, ledger: collateralRepayLedger, }) .remainingAccounts(this.driftUser.getRemainingAccounts(withdrawMarketIndex)) .instruction(); const [ix_startCollateralRepay, ix_depositCollateralRepay, ix_withdrawCollateralRepay,] = await Promise.all([ startCollateralRepayPromise, depositCollateralRepayPromise, withdrawCollateralRepayPromise, ]); // Mark the owner as a signer if the caller is not the owner if (requireOwnerSignature) { for (const ix of [ ix_startCollateralRepay, ix_depositCollateralRepay, ix_withdrawCollateralRepay, ]) { const ownerAccountMeta = ix.keys.find((key) => key.pubkey.equals(this.pubkey)); if (ownerAccountMeta) { ownerAccountMeta.isSigner = true; } } } return { ixs: [ ix_startCollateralRepay, swapInstruction, ix_depositCollateralRepay, ix_withdrawCollateralRepay, ], lookupTables: this.getLookupTables(), signers: [], }; } } //# sourceMappingURL=QuartzUser.class.js.map