From 8abd1898405d3d94d3c33d11ec0500de709a3208 Mon Sep 17 00:00:00 2001 From: 0xodia <0xodia@solend.fi> Date: Mon, 9 Sep 2024 11:26:43 -0400 Subject: [PATCH] update sdk and lite --- solend-lite/package.json | 1 + solend-lite/public/og_image.png | Bin 0 -> 8757 bytes .../AccountMetrics/AccountMetrics.tsx | 12 +- .../TransactionTakeover.tsx | 12 +- .../TransactionTakeover/configs.tsx | 36 +- solend-lite/src/pages/index.tsx | 5 +- solend-lite/src/stores/pools.ts | 4 +- solend-sdk/.gitignore | 1 + solend-sdk/package.json | 5 +- solend-sdk/src/core/actions.ts | 696 ++++++++++-------- solend-sdk/src/core/constants.ts | 26 - solend-sdk/src/core/margin.ts | 10 +- solend-sdk/src/core/types.ts | 47 +- solend-sdk/src/core/utils/obligations.ts | 12 +- solend-sdk/src/core/utils/pools.ts | 23 +- solend-sdk/src/core/utils/utils.ts | 6 +- solend-sdk/src/core/utils/wallet.ts | 26 +- solend-sdk/src/instructions/initReserve.ts | 10 +- ...ateObligationAndRedeemReserveCollateral.ts | 1 + .../src/instructions/updateReserveConfig.ts | 7 +- solend-sdk/src/state/lendingMarket.ts | 2 + solend-sdk/src/state/obligation.ts | 20 +- yarn.lock | 126 +++- 23 files changed, 644 insertions(+), 444 deletions(-) create mode 100644 solend-lite/public/og_image.png diff --git a/solend-lite/package.json b/solend-lite/package.json index 65516ad9..ff788690 100644 --- a/solend-lite/package.json +++ b/solend-lite/package.json @@ -15,6 +15,7 @@ "@chakra-ui/react": "^2.4.9", "@chakra-ui/styled-system": "^2.5.1", "@chakra-ui/theme-tools": "^2.0.16", + "@coral-xyz/anchor": "^0.30.1", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "@next/font": "13.0.7", diff --git a/solend-lite/public/og_image.png b/solend-lite/public/og_image.png new file mode 100644 index 0000000000000000000000000000000000000000..fb1c61f807092c2270576a31be9f24929c466e52 GIT binary patch literal 8757 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 diff --git a/solend-lite/src/components/TransactionTakeover/TransactionTakeover.tsx b/solend-lite/src/components/TransactionTakeover/TransactionTakeover.tsx index fd6d678b..42f14ba8 100644 --- a/solend-lite/src/components/TransactionTakeover/TransactionTakeover.tsx +++ b/solend-lite/src/components/TransactionTakeover/TransactionTakeover.tsx @@ -28,13 +28,15 @@ import Result, { ResultConfigType } from 'components/Result/Result'; import BigNumber from 'bignumber.js'; import { connectionAtom, refreshPageAtom } from 'stores/settings'; import { rateLimiterAtom, selectedPoolAtom } from 'stores/pools'; -import { useWallet } from '@solana/wallet-adapter-react'; +import { useAnchorWallet, useWallet } from '@solana/wallet-adapter-react'; import { U64_MAX } from '@solendprotocol/solend-sdk'; import { SKIP_PREFLIGHT } from 'common/config'; import { selectedModalTabAtom, selectedReserveAtom } from 'stores/modal'; +import { Wallet } from '@coral-xyz/anchor'; export default function TransactionTakeover() { const { sendTransaction, signAllTransactions } = useWallet(); + const anchorWallet = useAnchorWallet(); const [publicKey] = useAtom(publicKeyAtom); const [connection] = useAtom(connectionAtom); const [rateLimiter] = useAtom(rateLimiterAtom); @@ -143,7 +145,7 @@ export default function TransactionTakeover() { new BigNumber(value) .shiftedBy(selectedReserve.decimals) .toFixed(0), - publicKey, + anchorWallet as Wallet, selectedPool, selectedReserve, connection, @@ -185,7 +187,7 @@ export default function TransactionTakeover() { : new BigNumber(value) .shiftedBy(selectedReserve.decimals) .toFixed(0), - publicKey, + anchorWallet as Wallet, selectedPool, selectedReserve, connection, @@ -229,7 +231,7 @@ export default function TransactionTakeover() { : new BigNumber(value) .shiftedBy(selectedReserve.decimals) .toFixed(0), - publicKey, + anchorWallet as Wallet, selectedPool, selectedReserve, connection, @@ -273,7 +275,7 @@ export default function TransactionTakeover() { : new BigNumber(value) .shiftedBy(selectedReserve.decimals) .toFixed(0), - publicKey, + anchorWallet as Wallet, selectedPool, selectedReserve, connection, diff --git a/solend-lite/src/components/TransactionTakeover/configs.tsx b/solend-lite/src/components/TransactionTakeover/configs.tsx index df443e68..d0782788 100644 --- a/solend-lite/src/components/TransactionTakeover/configs.tsx +++ b/solend-lite/src/components/TransactionTakeover/configs.tsx @@ -14,6 +14,7 @@ import { getAssociatedTokenAddress, NATIVE_MINT } from '@solana/spl-token'; import { ENVIRONMENT, HOST_ATA } from 'common/config'; import { sendAndConfirmStrategy } from 'components/TransactionTakeover/util'; import { WalletContextState } from '@solana/wallet-adapter-react'; +import { Wallet } from '@coral-xyz/anchor'; const SOL_PADDING_FOR_RENT_AND_FEE = 0.02; @@ -28,7 +29,7 @@ export function sufficientSOLForTransaction(wallet: WalletType) { export const supplyConfigs = { action: async ( value: string, - publicKey: string, + wallet: Wallet, pool: PoolType, selectedReserve: SelectedReserveType, connection: Connection, @@ -45,8 +46,10 @@ export const supplyConfigs = { selectedReserve, connection, value, - new PublicKey(publicKey), - ENVIRONMENT, + wallet, + { + environment: ENVIRONMENT, + } ); return sendAndConfirmStrategy( @@ -158,7 +161,7 @@ export const supplyConfigs = { export const borrowConfigs = { action: async ( value: string, - publicKey: string, + wallet: Wallet, pool: PoolType, selectedReserve: SelectedReserveType, connection: Connection, @@ -191,10 +194,11 @@ export const borrowConfigs = { selectedReserve, connection, value, - new PublicKey(publicKey), - ENVIRONMENT, - undefined, - hostAta, + wallet, + { + environment: ENVIRONMENT, + hostAta, + } ); return sendAndConfirmStrategy( @@ -359,7 +363,7 @@ export const borrowConfigs = { export const withdrawConfigs = { action: async ( value: string, - publicKey: string, + wallet: Wallet, pool: PoolType, selectedReserve: SelectedReserveType, connection: Connection, @@ -376,8 +380,10 @@ export const withdrawConfigs = { selectedReserve, connection, value, - new PublicKey(publicKey), - ENVIRONMENT, + wallet, + { + environment: ENVIRONMENT, + } ); return sendAndConfirmStrategy( @@ -552,7 +558,7 @@ export const withdrawConfigs = { export const repayConfigs = { action: async ( value: string, - publicKey: string, + wallet: Wallet, pool: PoolType, selectedReserve: SelectedReserveType, connection: Connection, @@ -569,8 +575,10 @@ export const repayConfigs = { selectedReserve, connection, value, - new PublicKey(publicKey), - ENVIRONMENT, + wallet, + { + environment: ENVIRONMENT, + }, ); return sendAndConfirmStrategy( diff --git a/solend-lite/src/pages/index.tsx b/solend-lite/src/pages/index.tsx index 4485f361..6c344633 100644 --- a/solend-lite/src/pages/index.tsx +++ b/solend-lite/src/pages/index.tsx @@ -25,10 +25,7 @@ export default function Index() { property='og:title' content='Solend | Lend and borrow on Solana' /> - + diff --git a/solend-lite/src/stores/pools.ts b/solend-lite/src/stores/pools.ts index dd08a5ab..45cf6841 100644 --- a/solend-lite/src/stores/pools.ts +++ b/solend-lite/src/stores/pools.ts @@ -26,12 +26,12 @@ import { atomWithRefresh } from './shared'; export type ReserveWithMetadataType = ReserveType & { symbol: string; - logo: string | null; + logo: string | undefined; }; export type SelectedReserveType = ReserveType & { symbol: string; - logo: string | null; + logo: string | undefined; }; export type SelectedPoolType = { diff --git a/solend-sdk/.gitignore b/solend-sdk/.gitignore index 1eae0cf6..8e01851b 100644 --- a/solend-sdk/.gitignore +++ b/solend-sdk/.gitignore @@ -1,2 +1,3 @@ +__tests__/scripts.test.ts dist/ node_modules/ diff --git a/solend-sdk/package.json b/solend-sdk/package.json index 34fdaeda..6bef58da 100644 --- a/solend-sdk/package.json +++ b/solend-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@solendprotocol/solend-sdk", - "version": "0.12.11", + "version": "0.13.5", "private": true, "main": "src/index.ts", "module": "src/index.ts", @@ -27,8 +27,9 @@ "@solana/buffer-layout": "=4.0.1", "@solana/spl-token": "^0.3.7", "@solana/web3.js": "=1.92.3", + "@solendprotocol/token2022-wrapper-sdk": "^1.0.1", "@solflare-wallet/utl-sdk": "^1.4.0", - "@switchboard-xyz/on-demand": "^1.2.20", + "@switchboard-xyz/on-demand": "1.2.27", "@switchboard-xyz/sbv2-lite": "^0.2.4", "axios": "^0.24.0", "bignumber.js": "^9.0.2", diff --git a/solend-sdk/src/core/actions.ts b/solend-sdk/src/core/actions.ts index 38603561..6f061a21 100644 --- a/solend-sdk/src/core/actions.ts +++ b/solend-sdk/src/core/actions.ts @@ -13,10 +13,12 @@ import { import { NATIVE_MINT, getAssociatedTokenAddress, + getAssociatedTokenAddressSync, createAssociatedTokenAccountInstruction, getMinimumBalanceForRentExemptAccount, createCloseAccountInstruction, TOKEN_2022_PROGRAM_ID, + createAssociatedTokenAccountIdempotentInstruction, } from "@solana/spl-token"; import BN from "bn.js"; import BigNumber from "bignumber.js"; @@ -47,7 +49,7 @@ import { liquidateObligationAndRedeemReserveCollateral, } from "../instructions"; import { NULL_ORACLE, POSITION_LIMIT } from "./constants"; -import { EnvironmentType, PoolType, ReserveType } from "./types"; +import { EnvironmentType, PoolType } from "./types"; import { getProgramId, U64_MAX, WAD } from "./constants"; import { PriceServiceConnection } from "@pythnetwork/price-service-client"; import { AnchorProvider, Program } from "@coral-xyz/anchor-30"; @@ -55,39 +57,70 @@ import { CrossbarClient, loadLookupTables, PullFeed, - SB_ON_DEMAND_PID, + ON_DEMAND_MAINNET_PID, } from "@switchboard-xyz/on-demand"; import { Wallet } from "@coral-xyz/anchor"; +import { + createDepositAndMintWrapperTokensInstruction, + createWithdrawAndBurnWrapperTokensInstruction, +} from "@solendprotocol/token2022-wrapper-sdk"; +import { ReserveType } from "./utils"; const SOL_PADDING_FOR_INTEREST = "1000000"; -const MAPPING_2022 = { - '123': '321', +type ActionConfigType = { + environment?: EnvironmentType, + customObligationAddress?: PublicKey, + hostAta?: PublicKey, + customObligationSeed?: string, + lookupTableAddress?: PublicKey, + tipAmount?: number, + repayReserve?: ReserveType, + token2022Mint?: string, + repayToken2022Mint?: string + debug?: boolean } -type SupportType = - 'wrap' - | 'unwrap' - | 'refreshReserves' - | 'refreshObligation' - | 'createObligation' - | 'cAta' - | 'ata' - | 'wrapUnwrapLiquidate' - | 'wsol'; - -const ACTION_SUPPORT_REQUIREMENTS: {[Property in ActionType]: Array} = { - deposit: ['wsol', 'wrap', 'createObligation', 'cAta'], - borrow: ['wsol', 'ata', 'refreshReserves', 'refreshObligation', 'unwrap'], - withdraw: ['wsol', 'ata', 'cAta', 'refreshReserves', 'refreshObligation', 'unwrap'], - repay: ['wsol', 'wrap'], - mint: ['wsol', 'wrap', 'cAta'], - redeem: ['wsol', 'ata', 'refreshReserves', 'refreshObligation', 'unwrap'], - depositCollateral: ['createObligation'], - withdrawCollateral: ['cAta', 'refreshReserves', 'refreshObligation'], - forgive: [], - liquidate: ['wsol', 'ata', 'cAta', 'wrapUnwrapLiquidate', 'refreshReserves', 'refreshObligation'], -} +type SupportType = + | "wrap" + | "unwrap" + | "refreshReserves" + | "refreshObligation" + | "createObligation" + | "cAta" + | "ata" + | "wrapRepay" + | "wsol"; + +const ACTION_SUPPORT_REQUIREMENTS: { + [Property in ActionType]: Array; +} = { + deposit: ["wsol", "wrap", "createObligation", "cAta"], + borrow: ["wsol", "ata", "refreshReserves", "refreshObligation", "unwrap"], + withdraw: [ + "wsol", + "ata", + "cAta", + "refreshReserves", + "refreshObligation", + "unwrap", + ], + repay: ["wsol", "wrap"], + mint: ["wsol", "wrap", "cAta"], + redeem: ["wsol", "ata", "refreshReserves", "unwrap"], + depositCollateral: ["createObligation"], + withdrawCollateral: ["cAta", "refreshReserves", "refreshObligation"], + forgive: ["refreshReserves", "refreshObligation"], + liquidate: [ + "wsol", + "ata", + "cAta", + "wrapRepay", + "unwrap", + "refreshReserves", + "refreshObligation", + ], +}; export const toHexString = (byteArray: number[]) => { return ( @@ -160,11 +193,20 @@ export class SolendActionCore { wallet: Wallet; - userRepayTokenAccountAddress?: PublicKey; + debug: boolean; - userRepayCollateralAccountAddress?: PublicKey; - - is2022?: boolean; + repayInfo?: { + userRepayTokenAccountAddress: PublicKey; + userRepayCollateralAccountAddress: PublicKey; + repayToken2022Mint?: PublicKey; + repayWrappedAta?: PublicKey; + repayMint: PublicKey; + reserveAddress: PublicKey; + }; + + token2022Mint?: PublicKey; + + wrappedAta?: PublicKey; private constructor( programId: PublicKey, @@ -181,19 +223,29 @@ export class SolendActionCore { amount: BN, depositReserves: Array, borrowReserves: Array, - hostAta?: PublicKey, - lookupTableAccount?: AddressLookupTableAccount, - tipAmount?: number, - userRepayTokenAccountAddress?: PublicKey, - userRepayCollateralAccountAddress?: PublicKey, - is2022?: boolean + config?: { + hostAta?: PublicKey, + lookupTableAccount?: AddressLookupTableAccount, + tipAmount?: number, + repayInfo?: { + userRepayTokenAccountAddress: PublicKey; + userRepayCollateralAccountAddress: PublicKey; + repayToken2022Mint?: PublicKey; + repayWrappedAta?: PublicKey; + repayMint: PublicKey; + reserveAddress: PublicKey; + }, + token2022Mint?: PublicKey, + wrappedAta?: PublicKey, + debug?: boolean; + } ) { this.programId = programId; this.connection = connection; this.publicKey = wallet.publicKey; this.amount = new BN(amount); this.positions = positions; - this.hostAta = hostAta; + this.hostAta = config?.hostAta; this.obligationAccountInfo = obligationAccountInfo; this.pool = pool; this.seed = seed; @@ -209,13 +261,14 @@ export class SolendActionCore { this.postTxnIxs = []; this.depositReserves = depositReserves; this.borrowReserves = borrowReserves; - this.lookupTableAccount = lookupTableAccount; - this.jitoTipAmount = tipAmount ?? 1000; + this.lookupTableAccount = config?.lookupTableAccount; + this.jitoTipAmount = config?.tipAmount ?? 9000; this.wallet = wallet; - this.userRepayTokenAccountAddress = userRepayTokenAccountAddress; - this.userRepayCollateralAccountAddress = - userRepayCollateralAccountAddress; - this.is2022 = is2022; + this.repayInfo = config?.repayInfo; + this.token2022Mint = config?.token2022Mint; + this.wrappedAta = config?.wrappedAta; + // temporarily default to true + this.debug = config?.debug ?? true; } static async initialize( @@ -225,19 +278,13 @@ export class SolendActionCore { amount: BN, wallet: Wallet, connection: Connection, - environment: EnvironmentType = "production", - customObligationAddress?: PublicKey, - hostAta?: PublicKey, - customObligationSeed?: string, - lookupTableAddress?: PublicKey, - tipAmount?: number, - repayReserve?: ReserveType + config: ActionConfigType ) { - const seed = customObligationSeed ?? pool.address.slice(0, 32); - const programId = getProgramId(environment); + const seed = config.customObligationSeed ?? pool.address.slice(0, 32); + const programId = getProgramId(config.environment ?? 'production'); const obligationAddress = - customObligationAddress ?? + config.customObligationAddress ?? (await PublicKey.createWithSeed(wallet.publicKey, seed, programId)); const obligationAccountInfo = await connection.getAccountInfo( @@ -264,8 +311,6 @@ export class SolendActionCore { }); } - const supports = ACTION_SUPPORT_REQUIREMENTS[action]; - // Union of addresses const distinctReserveCount = Array.from( @@ -287,16 +332,6 @@ export class SolendActionCore { ); } - let is2022 = false; - if (supports.includes('wrap') || supports.includes('unwrap')) { - const mintAccount = await connection.getAccountInfo( - new PublicKey(reserve.mintAddress) - ); - if (mintAccount?.owner.equals(TOKEN_2022_PROGRAM_ID)) { - is2022 = true; - } - } - const userTokenAccountAddress = await getAssociatedTokenAddress( new PublicKey(reserve.mintAddress), wallet.publicKey, @@ -308,23 +343,25 @@ export class SolendActionCore { true ); - const lookupTableAccount = lookupTableAddress - ? (await connection.getAddressLookupTable(lookupTableAddress)).value + const lookupTableAccount = config.lookupTableAddress + ? (await connection.getAddressLookupTable(config.lookupTableAddress)).value : undefined; - const userRepayTokenAccountAddress = repayReserve + const userRepayTokenAccountAddress = config.repayReserve ? await getAssociatedTokenAddress( - new PublicKey(repayReserve.mintAddress), + new PublicKey(config.repayReserve.mintAddress), wallet.publicKey, true ) : undefined; - const userRepayCollateralAccountAddress = repayReserve ? await getAssociatedTokenAddress( - new PublicKey(repayReserve.cTokenMint), - wallet.publicKey, - true - ) : undefined; + const userRepayCollateralAccountAddress = config.repayReserve + ? await getAssociatedTokenAddress( + new PublicKey(config.repayReserve.cTokenMint), + wallet.publicKey, + true + ) + : undefined; return new SolendActionCore( programId, @@ -341,13 +378,41 @@ export class SolendActionCore { amount, depositReserves, borrowReserves, - hostAta, - lookupTableAccount ?? undefined, - tipAmount, - userRepayTokenAccountAddress, - userRepayCollateralAccountAddress, - is2022 - ); + +{ + hostAta: config.hostAta, + lookupTableAccount: lookupTableAccount ?? undefined, + tipAmount: config.tipAmount, + repayInfo: config.repayReserve + ? { + userRepayTokenAccountAddress: userRepayTokenAccountAddress!, + userRepayCollateralAccountAddress: + userRepayCollateralAccountAddress!, + repayToken2022Mint: config.repayToken2022Mint + ? new PublicKey(config.repayToken2022Mint) + : undefined, + repayWrappedAta: config.repayToken2022Mint + ? getAssociatedTokenAddressSync( + new PublicKey(config.repayToken2022Mint), + wallet.publicKey, + true, + TOKEN_2022_PROGRAM_ID + ) + : undefined, + repayMint: new PublicKey(config.repayReserve.mintAddress), + reserveAddress: new PublicKey(config.repayReserve.address), + } + : undefined, + token2022Mint: config.token2022Mint ? new PublicKey(config.token2022Mint) : undefined, + wrappedAta: config.token2022Mint + ? getAssociatedTokenAddressSync( + new PublicKey(config.token2022Mint), + wallet.publicKey, + true, + TOKEN_2022_PROGRAM_ID + ) + : undefined + }) } static async buildForgiveTxns( @@ -357,8 +422,7 @@ export class SolendActionCore { amount: string, wallet: Wallet, obligationAddress: PublicKey, - environment: EnvironmentType = "production", - lookupTableAddress?: PublicKey + config: ActionConfigType ) { const axn = await SolendActionCore.initialize( pool, @@ -367,11 +431,10 @@ export class SolendActionCore { new BN(amount), wallet, connection, - environment, - obligationAddress, - undefined, - undefined, - lookupTableAddress + { + ...config, + customObligationAddress: obligationAddress + } ); await axn.addSupportIxs("forgive"); @@ -386,10 +449,7 @@ export class SolendActionCore { connection: Connection, amount: string, wallet: Wallet, - environment: EnvironmentType = "production", - obligationAddress?: PublicKey, - obligationSeed?: string, - lookupTableAddress?: PublicKey + config: ActionConfigType ) { const axn = await SolendActionCore.initialize( pool, @@ -398,11 +458,7 @@ export class SolendActionCore { new BN(amount), wallet, connection, - environment, - obligationAddress, - undefined, - obligationSeed, - lookupTableAddress + config ); await axn.addSupportIxs("deposit"); @@ -417,11 +473,7 @@ export class SolendActionCore { connection: Connection, amount: string, wallet: Wallet, - environment: EnvironmentType = "production", - customObligationAddress?: PublicKey, - hostAta?: PublicKey, - lookupTableAddress?: PublicKey, - tipAmount?: number + config: ActionConfigType ) { const axn = await SolendActionCore.initialize( pool, @@ -430,12 +482,7 @@ export class SolendActionCore { new BN(amount), wallet, connection, - environment, - customObligationAddress, - hostAta, - undefined, - lookupTableAddress, - tipAmount + config ); await axn.addSupportIxs("borrow"); @@ -449,8 +496,7 @@ export class SolendActionCore { connection: Connection, amount: string, wallet: Wallet, - environment: EnvironmentType = "production", - lookupTableAddress?: PublicKey + config: ActionConfigType ) { const axn = await SolendActionCore.initialize( pool, @@ -459,11 +505,7 @@ export class SolendActionCore { new BN(amount), wallet, connection, - environment, - undefined, - undefined, - undefined, - lookupTableAddress + config ); await axn.addSupportIxs("mint"); await axn.addDepositReserveLiquidityIx(); @@ -476,8 +518,7 @@ export class SolendActionCore { connection: Connection, amount: string, wallet: Wallet, - environment: EnvironmentType = "production", - lookupTableAddress?: PublicKey + config: ActionConfigType ) { const axn = await SolendActionCore.initialize( pool, @@ -486,11 +527,7 @@ export class SolendActionCore { new BN(amount), wallet, connection, - environment, - undefined, - undefined, - undefined, - lookupTableAddress + config ); await axn.addSupportIxs("redeem"); await axn.addRedeemReserveCollateralIx(); @@ -503,9 +540,7 @@ export class SolendActionCore { connection: Connection, amount: string, wallet: Wallet, - environment: EnvironmentType = "production", - lookupTableAddress?: PublicKey, - customObligationAddress?: PublicKey + config: ActionConfigType ) { const axn = await SolendActionCore.initialize( pool, @@ -514,11 +549,7 @@ export class SolendActionCore { new BN(amount), wallet, connection, - environment, - customObligationAddress, - undefined, - undefined, - lookupTableAddress + config ); await axn.addSupportIxs("depositCollateral"); await axn.addDepositObligationCollateralIx(); @@ -531,9 +562,7 @@ export class SolendActionCore { connection: Connection, amount: string, wallet: Wallet, - environment: EnvironmentType = "production", - lookupTableAddress?: PublicKey, - customObligationAddress?: PublicKey + config: ActionConfigType ) { const axn = await SolendActionCore.initialize( pool, @@ -542,11 +571,7 @@ export class SolendActionCore { new BN(amount), wallet, connection, - environment, - customObligationAddress, - undefined, - undefined, - lookupTableAddress + config ); await axn.addSupportIxs("withdrawCollateral"); @@ -561,11 +586,7 @@ export class SolendActionCore { connection: Connection, amount: string, wallet: Wallet, - environment: EnvironmentType = "production", - obligationAddress?: PublicKey, - obligationSeed?: string, - lookupTableAddress?: PublicKey, - tipAmount?: number + config: ActionConfigType ) { const axn = await SolendActionCore.initialize( pool, @@ -574,12 +595,7 @@ export class SolendActionCore { new BN(amount), wallet, connection, - environment, - obligationAddress, - undefined, - obligationSeed, - lookupTableAddress, - tipAmount + config ); await axn.addSupportIxs("withdraw"); @@ -594,9 +610,7 @@ export class SolendActionCore { connection: Connection, amount: string, wallet: Wallet, - environment: EnvironmentType = "production", - customObligationAddress?: PublicKey, - lookupTableAddress?: PublicKey + config: ActionConfigType ) { const axn = await SolendActionCore.initialize( pool, @@ -605,11 +619,7 @@ export class SolendActionCore { new BN(amount), wallet, connection, - environment, - customObligationAddress, - undefined, - undefined, - lookupTableAddress + config ); await axn.addSupportIxs("repay"); @@ -625,10 +635,7 @@ export class SolendActionCore { connection: Connection, amount: string, wallet: Wallet, - environment: EnvironmentType = "production", - customObligationAddress?: PublicKey, - lookupTableAddress?: PublicKey, - tipAmount?: number + config: ActionConfigType ) { const axn = await SolendActionCore.initialize( pool, @@ -637,13 +644,7 @@ export class SolendActionCore { new BN(amount), wallet, connection, - environment, - customObligationAddress, - undefined, - undefined, - lookupTableAddress, - tipAmount, - repayReserve, + config ); await axn.addSupportIxs("liquidate"); @@ -758,6 +759,7 @@ export class SolendActionCore { const tip = this.getTipIx(); if (tip && this.pullPriceTxns.length >= 5) { + if (this.debug) console.log('adding tip ix to lending txn'); instructions.push(tip); } @@ -796,6 +798,7 @@ export class SolendActionCore { } addForgiveIx() { + if (this.debug) console.log('adding forgive ix to lending txn'); this.lendingIxs.push( forgiveDebtInstruction( this.obligationAddress, @@ -809,6 +812,7 @@ export class SolendActionCore { } addDepositIx() { + if (this.debug) console.log('adding deposit ix to lending txn'); this.lendingIxs.push( this.amount.toString() === U64_MAX ? depositMaxReserveLiquidityAndObligationCollateralInstruction( @@ -848,6 +852,7 @@ export class SolendActionCore { } addDepositReserveLiquidityIx() { + if (this.debug) console.log('adding mint ix to lending txn'); this.lendingIxs.push( depositReserveLiquidityInstruction( this.amount, @@ -865,6 +870,7 @@ export class SolendActionCore { } addRedeemReserveCollateralIx() { + if (this.debug) console.log('adding redeem ix to lending txn'); this.lendingIxs.push( redeemReserveCollateralInstruction( this.amount, @@ -882,6 +888,7 @@ export class SolendActionCore { } async addWithdrawObligationCollateralIx() { + if (this.debug) console.log('adding withdrawCollateral ix to lending txn'); this.lendingIxs.push( withdrawObligationCollateralInstruction( this.amount, @@ -899,6 +906,7 @@ export class SolendActionCore { } addDepositObligationCollateralIx() { + if (this.debug) console.log('adding depositCollateral ix to lending txn'); this.lendingIxs.push( depositObligationCollateralInstruction( this.amount, @@ -915,6 +923,7 @@ export class SolendActionCore { } addBorrowIx() { + if (this.debug) console.log('adding borrow ix to lending txn'); this.lendingIxs.push( borrowObligationLiquidityInstruction( this.amount, @@ -934,6 +943,7 @@ export class SolendActionCore { } async addWithdrawIx() { + if (this.debug) console.log('adding withdraw ix to lending txn'); this.lendingIxs.push( this.amount.eq(new BN(U64_MAX)) ? withdrawObligationCollateralAndRedeemReserveLiquidity( @@ -972,6 +982,7 @@ export class SolendActionCore { } async addRepayIx() { + if (this.debug) console.log('adding repay ix to lending txn'); this.lendingIxs.push( this.amount.toString() === U64_MAX ? repayMaxObligationLiquidityInstruction( @@ -997,16 +1008,17 @@ export class SolendActionCore { } async addLiquidateIx(repayReserve: ReserveType) { + if (this.debug) console.log('adding liquidate ix to lending txn'); if ( - !this.userRepayCollateralAccountAddress || - !this.userRepayTokenAccountAddress + !this.repayInfo?.userRepayCollateralAccountAddress || + !this.repayInfo?.userRepayTokenAccountAddress ) { throw Error("Not correctly initialized with a withdraw reserve."); } this.lendingIxs.push( liquidateObligationAndRedeemReserveCollateral( this.amount, - this.userRepayTokenAccountAddress, + this.repayInfo.userRepayTokenAccountAddress, this.userCollateralAccountAddress, this.userTokenAccountAddress, new PublicKey(repayReserve.address), @@ -1025,110 +1037,116 @@ export class SolendActionCore { ); } -// if ( -// action === "liquidate" && -// this.userWithdrawCollateralAccountAddress && -// this.userWithdrawTokenAccountAddress -// ) { -// const userWithdrawTokenAccountInfo = await this.connection.getAccountInfo( -// this.userWithdrawTokenAccountAddress -// ); -// if (!userWithdrawTokenAccountInfo) { -// const createUserWithdrawTokenAccountIx = -// createAssociatedTokenAccountInstruction( -// this.publicKey, -// this.userWithdrawTokenAccountAddress, -// this.publicKey, -// new PublicKey(this.reserve.mintAddress) -// ); - -// if ( -// this.positions === POSITION_LIMIT && -// this.hostAta && -// !this.lookupTableAccount -// ) { -// this.preTxnIxs.push(createUserWithdrawTokenAccountIx); -// } else { -// this.setupIxs.push(createUserWithdrawTokenAccountIx); -// } -// } - -// const userWithdrawCollateralAccountInfo = -// await this.connection.getAccountInfo( -// this.userWithdrawCollateralAccountAddress -// ); -// if (!userWithdrawCollateralAccountInfo) { -// const createUserWithdrawCollateralAccountIx = -// createAssociatedTokenAccountInstruction( -// this.publicKey, -// this.userWithdrawCollateralAccountAddress, -// this.publicKey, -// new PublicKey(this.reserve.cTokenMint) -// ); - -// if ( -// this.positions === POSITION_LIMIT && -// this.hostAta && -// !this.lookupTableAccount -// ) { -// this.preTxnIxs.push(createUserWithdrawCollateralAccountIx); -// } else { -// this.setupIxs.push(createUserWithdrawCollateralAccountIx); -// } -// } -// } -// } - async addSupportIxs(action: ActionType) { const supports = ACTION_SUPPORT_REQUIREMENTS[action]; for (const support of supports) { - switch(support) { - case 'createObligation': + switch (support) { + case "createObligation": await this.addObligationIxs(); break; - case 'wrap': - if (this.is2022) { + case "wrap": + if (this.wrappedAta) { await this.addWrapIx(); } break; - case 'unwrap': - if (this.is2022) { + case "unwrap": + if (this.wrappedAta) { await this.addUnwrapIx(); } break; - case 'refreshReserves': + case "refreshReserves": await this.addRefreshReservesIxs(); break; - case 'refreshObligation': + case "refreshObligation": await this.addRefreshObligationIxs(); break; - case 'cAta': + case "cAta": await this.addCAtaIxs(); break; - case 'ata': + case "ata": await this.addAtaIxs(); break; - case 'wrapUnwrapLiquidate': - await this.addWrapUnwrapLiquidateIxs(); + case "wrapRepay": + if (this.repayInfo?.repayWrappedAta) { + await this.addWrapRepayIx(); + } break; - case 'wsol': + case "wsol": await this.updateWSOLAccount(action); break; - } + } } } private async addWrapIx() { + if (!this.wrappedAta || !this.token2022Mint) + throw new Error("Wrapped ATA not initialized"); + if (this.debug) console.log('adding wrap ix to preTxnIxs'); + this.preTxnIxs.push( + createAssociatedTokenAccountIdempotentInstruction( + this.publicKey, + this.userTokenAccountAddress, + this.publicKey, + new PublicKey(this.reserve.mintAddress) + ) + ); + this.preTxnIxs.push( + await createDepositAndMintWrapperTokensInstruction( + this.publicKey, + this.wrappedAta, + this.token2022Mint, + this.amount + ) + ); } private async addUnwrapIx() { - + if (!this.wrappedAta || !this.token2022Mint) + throw new Error("Wrapped ATA not initialized"); + if (this.debug) console.log('adding wrap ix to preTxnIxs'); + this.preTxnIxs.push( + createAssociatedTokenAccountIdempotentInstruction( + this.publicKey, + this.wrappedAta, + this.publicKey, + this.token2022Mint, + TOKEN_2022_PROGRAM_ID + ) + ); + + if (this.debug) console.log('adding wrap ix to postTxnIxs'); + this.postTxnIxs.push( + await createWithdrawAndBurnWrapperTokensInstruction( + this.publicKey, + this.wrappedAta, + this.token2022Mint, + new BN(U64_MAX) + ) + ); } - private async addWrapUnwrapLiquidateIxs() { + private async addWrapRepayIx() { + if (!this.repayInfo?.repayWrappedAta || !this.repayInfo?.repayToken2022Mint) + throw new Error("Wrapped ATA not initialized"); + this.preTxnIxs.push( + createAssociatedTokenAccountIdempotentInstruction( + this.publicKey, + this.repayInfo.userRepayTokenAccountAddress, + this.publicKey, + new PublicKey(this.repayInfo.repayMint) + ) + ); + this.preTxnIxs.push( + await createDepositAndMintWrapperTokensInstruction( + this.publicKey, + this.repayInfo.repayWrappedAta, + this.repayInfo.repayToken2022Mint, + this.amount + ) + ); } private async buildPullPriceTxns(oracleKeys: Array) { @@ -1148,39 +1166,52 @@ export class SolendActionCore { }); const provider = new AnchorProvider(this.connection, this.wallet, {}); - const idl = (await Program.fetchIdl(SB_ON_DEMAND_PID, provider))!; + const idl = (await Program.fetchIdl(ON_DEMAND_MAINNET_PID, provider))!; const sbod = new Program(idl, provider); const sbPulledOracles = oracleKeys.filter( (_o, index) => oracleAccounts[index]?.owner.toBase58() === sbod.programId.toBase58() ); - const feedAccounts = sbPulledOracles.map( - (oracleKey) => new PullFeed(sbod as any, oracleKey) - ); - const crossbar = new CrossbarClient( - "https://crossbar.switchboard.xyz/" + if (sbPulledOracles.length) { + const feedAccounts = await Promise.all( + sbPulledOracles.map((oracleKey) => new PullFeed(sbod as any, oracleKey)) + ); + if (this.debug) console.log("Feed accounts", sbPulledOracles); + const loadedFeedAccounts = await Promise.all( + feedAccounts.map((acc) => acc.loadData()) ); - // Responses is Array<[pullIx, responses, success]> - const responses = await Promise.all( - feedAccounts.map((feedAccount) => - feedAccount.fetchUpdateIx({ - crossbarClient: crossbar, - }) - ) + const numSignatures = Math.max( + ...loadedFeedAccounts.map( + (f) => f.minSampleSize + Math.ceil(f.minSampleSize / 3) + ), + 1 ); - const oracles = responses.flatMap((x) => x[1].map((y) => y.oracle)); - const lookupTables = await loadLookupTables([...oracles, ...feedAccounts]); + const crossbar = new CrossbarClient("https://crossbar.switchboard.xyz/"); + + const [ix, accountLookups] = await PullFeed.fetchUpdateManyIx(sbod, { + feeds: sbPulledOracles.map((p) => new PublicKey(p)), + numSignatures, + crossbarClient: crossbar, + }); + + const lookupTables = (await loadLookupTables(feedAccounts)).concat( + accountLookups + ); const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1_000_000, }); + const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: 1_000_000, + }); - const instructions = [priorityFeeIx, ...responses.map((r) => r[0]!)]; + const instructions = [priorityFeeIx, modifyComputeUnits, ix]; + if (this.debug) console.log('adding tip ix to pullPriceTxns for sbod'); instructions.push(this.getTipIx()); // Get the latest context @@ -1198,8 +1229,9 @@ export class SolendActionCore { // Get Versioned Transaction const vtx = new VersionedTransaction(message); + if (this.debug) console.log('adding sbod ix to pullPriceTxns'); this.pullPriceTxns.push(vtx); - + } const pythPulledOracles = oracleAccounts.filter( (o) => o?.owner.toBase58() === pythSolanaReceiver.receiver.programId.toBase58() @@ -1208,27 +1240,27 @@ export class SolendActionCore { const shuffledPriceIds = ( pythPulledOracles .map((pythOracleData, index) => { - if (!pythOracleData) { - throw new Error(`Could not find oracle data at index ${index}`); - } - const priceUpdate = - pythSolanaReceiver.receiver.account.priceUpdateV2.coder.accounts.decode( - "priceUpdateV2", - pythOracleData.data - ); - - const needUpdate = - Date.now() / 1000 - - Number(priceUpdate.priceMessage.publishTime.toString()) > - 30; - - return needUpdate - ? { - key: Math.random(), - priceFeedId: toHexString(priceUpdate.priceMessage.feedId), - } - : undefined; - }) + if (!pythOracleData) { + throw new Error(`Could not find oracle data at index ${index}`); + } + const priceUpdate = + pythSolanaReceiver.receiver.account.priceUpdateV2.coder.accounts.decode( + "priceUpdateV2", + pythOracleData.data + ); + + const needUpdate = + Date.now() / 1000 - + Number(priceUpdate.priceMessage.publishTime.toString()) > + 80; + + return needUpdate + ? { + key: Math.random(), + priceFeedId: toHexString(priceUpdate.priceMessage.feedId), + } + : undefined; + }) .filter(Boolean) as Array<{ key: number; priceFeedId: string; @@ -1238,6 +1270,7 @@ export class SolendActionCore { .map((x) => x.priceFeedId); if (shuffledPriceIds.length) { + if (this.debug) console.log("Feed accounts", shuffledPriceIds); const priceFeedUpdateData = await priceServiceConnection.getLatestVaas( shuffledPriceIds ); @@ -1263,6 +1296,7 @@ export class SolendActionCore { } this.pullPriceTxns.push(tx); } + console.log(`adding ${transactionsWithSigners.length} txns to pullPriceTxns`); } } @@ -1281,6 +1315,8 @@ export class SolendActionCore { ]) ); + console.log(allReserveAddresses); + await this.buildPullPriceTxns([ ...allReserveAddresses.map((address) => reserveMap[address].pythOracle), ...allReserveAddresses.map( @@ -1323,12 +1359,14 @@ export class SolendActionCore { } private async addObligationIxs() { + if (this.debug) console.log('addObligationIxs'); if (!this.obligationAccountInfo) { const obligationAccountInfoRentExempt = await this.connection.getMinimumBalanceForRentExemption( OBLIGATION_SIZE ); + if (this.debug) console.log('adding createAccount and initObligation ix to setup txn'); this.setupIxs.push( SystemProgram.createAccountWithSeed({ fromPubkey: this.publicKey, @@ -1369,73 +1407,93 @@ export class SolendActionCore { this.hostAta && !this.lookupTableAccount ) { + if (this.debug) console.log('adding createAta ix to pre txn'); this.preTxnIxs.push(createUserTokenAccountIx); } else { + if (this.debug) console.log('adding createAta ix to setup txn'); this.setupIxs.push(createUserTokenAccountIx); } } } } - private async addCAtaIxs() { - const userCollateralAccountInfo = await this.connection.getAccountInfo( - this.userCollateralAccountAddress - ); + private async addCAtaIxs() { + const userCollateralAccountInfo = await this.connection.getAccountInfo( + this.userCollateralAccountAddress + ); - if (!userCollateralAccountInfo) { - const createUserCollateralAccountIx = - createAssociatedTokenAccountInstruction( - this.publicKey, - this.userCollateralAccountAddress, - this.publicKey, - new PublicKey(this.reserve.cTokenMint) - ); + if (!userCollateralAccountInfo) { + const createUserCollateralAccountIx = + createAssociatedTokenAccountInstruction( + this.publicKey, + this.userCollateralAccountAddress, + this.publicKey, + new PublicKey(this.reserve.cTokenMint) + ); - if ( - this.positions === POSITION_LIMIT && - this.hostAta && - !this.lookupTableAccount - ) { - this.preTxnIxs.push(createUserCollateralAccountIx); - } else { - this.setupIxs.push(createUserCollateralAccountIx); - } + if ( + this.positions === POSITION_LIMIT && + this.hostAta && + !this.lookupTableAccount + ) { + if (this.debug) console.log('adding createCAta ix to pre txn'); + this.preTxnIxs.push(createUserCollateralAccountIx); + } else { + if (this.debug) console.log('adding createCAta ix to setup txn'); + this.setupIxs.push(createUserCollateralAccountIx); } } + } private async updateWSOLAccount(action: ActionType) { - if (this.reserve.mintAddress !== NATIVE_MINT.toBase58()) return; + if ( + ![ + this.reserve.mintAddress, + this.repayInfo?.repayMint.toString(), + ].includes(NATIVE_MINT.toBase58()) + ) + return; const preIxs: Array = []; const postIxs: Array = []; let safeRepay = new BN(this.amount); + const liquidateWithSol = + action === "liquidate" && + this.repayInfo?.repayMint.toString() === NATIVE_MINT.toBase58(); + + const solAccountAddress = liquidateWithSol + ? this.repayInfo!.userRepayTokenAccountAddress + : this.userTokenAccountAddress; + const borrowReserveAddress = liquidateWithSol + ? this.repayInfo!.reserveAddress.toBase58() + : this.reserve.address; if ( this.obligationAccountInfo && - action === "repay" && + (action === "repay" || liquidateWithSol) && this.amount.eq(new BN(U64_MAX)) ) { const buffer = await this.connection.getAccountInfo( - new PublicKey(this.reserve.address), + new PublicKey(borrowReserveAddress), "processed" ); if (!buffer) { - throw Error(`Unable to fetch reserve data for ${this.reserve.address}`); + throw Error(`Unable to fetch reserve data for ${borrowReserveAddress}`); } const parsedData = parseReserve( - new PublicKey(this.reserve.address), + new PublicKey(borrowReserveAddress), buffer )?.info; if (!parsedData) { - throw Error(`Unable to parse data of reserve ${this.reserve.address}`); + throw Error(`Unable to parse data of reserve ${borrowReserveAddress}`); } const borrow = this.obligationAccountInfo.borrows.find( - (borrow) => borrow.borrowReserve.toBase58() === this.reserve.address + (borrow) => borrow.borrowReserve.toBase58() === borrowReserveAddress ); if (!borrow) { @@ -1459,7 +1517,7 @@ export class SolendActionCore { } const userWSOLAccountInfo = await this.connection.getAccountInfo( - this.userTokenAccountAddress + solAccountAddress ); const rentExempt = await getMinimumBalanceForRentExemptAccount( @@ -1467,38 +1525,46 @@ export class SolendActionCore { ); const sendAction = - action === "deposit" || action === "repay" || action === "mint"; + action === "deposit" || + action === "repay" || + action === "mint" || + liquidateWithSol; const transferLamportsIx = SystemProgram.transfer({ fromPubkey: this.publicKey, - toPubkey: this.userTokenAccountAddress, + toPubkey: solAccountAddress, lamports: (userWSOLAccountInfo ? 0 : rentExempt) + (sendAction ? parseInt(safeRepay.toString(), 10) : 0), }); + if (this.debug) console.log('adding transferLamports ix'); preIxs.push(transferLamportsIx); const closeWSOLAccountIx = createCloseAccountInstruction( - this.userTokenAccountAddress, + solAccountAddress, this.publicKey, this.publicKey, [] ); if (userWSOLAccountInfo) { - const syncIx = syncNative(this.userTokenAccountAddress); + const syncIx = syncNative(solAccountAddress); if (sendAction) { + if (this.debug) console.log('adding syncIx ix'); preIxs.push(syncIx); } else { + if (this.debug) console.log('adding closeWSOLAccountIx ix'); postIxs.push(closeWSOLAccountIx); } } else { const createUserWSOLAccountIx = createAssociatedTokenAccountInstruction( this.publicKey, - this.userTokenAccountAddress, + solAccountAddress, this.publicKey, NATIVE_MINT ); + if (this.debug) console.log('adding createUserWSOLAccountIx ix'); preIxs.push(createUserWSOLAccountIx); + if (this.debug) console.log('adding closeWSOLAccountIx ix'); postIxs.push(closeWSOLAccountIx); } @@ -1507,9 +1573,11 @@ export class SolendActionCore { this.hostAta && !this.lookupTableAccount ) { + if (this.debug) console.log('adding above ixs to pre and post txn'); this.preTxnIxs.push(...preIxs); this.postTxnIxs.push(...postIxs); } else { + if (this.debug) console.log('adding above ixs to lending txn'); this.setupIxs.push(...preIxs); this.cleanupIxs.push(...postIxs); } diff --git a/solend-sdk/src/core/constants.ts b/solend-sdk/src/core/constants.ts index ecc80683..ea9a0630 100644 --- a/solend-sdk/src/core/constants.ts +++ b/solend-sdk/src/core/constants.ts @@ -1,6 +1,5 @@ import { Cluster, PublicKey } from "@solana/web3.js"; import BigNumber from "bignumber.js"; -import { ObligationType } from "./types"; export const WAD = "1".concat(Array(18 + 1).join("0")); export const POSITION_LIMIT = 6; export const SOL_PADDING_FOR_INTEREST = "1000000"; @@ -46,28 +45,3 @@ export function getProgramId(environment?: Cluster | "beta" | "production") { } export const BigZero = new BigNumber(0); - -export const EmptyObligation: ObligationType = { - address: "empty", - positions: 0, - deposits: [], - borrows: [], - poolAddress: "", - totalSupplyValue: BigZero, - totalBorrowValue: BigZero, - borrowLimit: BigZero, - liquidationThreshold: BigZero, - netAccountValue: BigZero, - liquidationThresholdFactor: BigZero, - borrowLimitFactor: BigZero, - borrowUtilization: BigZero, - weightedConservativeBorrowUtilization: BigZero, - weightedBorrowUtilization: BigZero, - isBorrowLimitReached: false, - borrowOverSupply: BigZero, - weightedTotalBorrowValue: BigZero, - minPriceUserTotalSupply: BigZero, - minPriceBorrowLimit: BigZero, - maxPriceUserTotalWeightedBorrow: BigZero, - netApy: BigZero, -}; diff --git a/solend-sdk/src/core/margin.ts b/solend-sdk/src/core/margin.ts index 0a5bbbfe..71a23d03 100644 --- a/solend-sdk/src/core/margin.ts +++ b/solend-sdk/src/core/margin.ts @@ -411,10 +411,12 @@ export class Margin { this.connection, depositCollateralConfig.amount, this.wallet, - "production", - this.obligationAddress, - this.obligationSeed, - lookupTableAccount?.key + { + environment: "production", + customObligationAddress: this.obligationAddress, + customObligationSeed: this.obligationSeed, + lookupTableAddress: lookupTableAccount?.key + } ); const { preLendingTxn, lendingTxn } = await solendAction.getLegacyTransactions(); diff --git a/solend-sdk/src/core/types.ts b/solend-sdk/src/core/types.ts index 0f1d74ed..d43f24b8 100644 --- a/solend-sdk/src/core/types.ts +++ b/solend-sdk/src/core/types.ts @@ -1,7 +1,7 @@ -import { Cluster } from "@solana/web3.js"; +import { Cluster, PublicKey } from "@solana/web3.js"; import BigNumber from "bignumber.js"; -import { formatReserve } from "./utils/pools"; -import { formatObligation } from "./utils"; +import { ReserveType } from "./utils"; +import BN from "bn.js"; export type PoolMetadataCoreType = { name: string | null; @@ -21,7 +21,7 @@ export type EnvironmentType = Cluster | "production" | "beta"; export type TokenMetadata = { [mintAddress: string]: { symbol: string; - logoUri: string | null; + logoUri: string | undefined; decimals: number; }; }; @@ -34,15 +34,6 @@ export type PoolType = { reserves: Array; }; -export type ReserveType = Omit< - Awaited>, - "symbol" -> & { - symbol: string | undefined; -}; - -export type ObligationType = Awaited>; - export type WalletAssetType = { amount: BigNumber; mintAddress: string; @@ -53,3 +44,33 @@ export type WalletAssetType = { }; export type WalletType = Array; + +export type InputReserveConfigParams = { + optimalUtilizationRate: number; + maxUtilizationRate: number; + loanToValueRatio: number; + liquidationBonus: number; + liquidationThreshold: number; + minBorrowRate: number; + optimalBorrowRate: number; + maxBorrowRate: number; + superMaxBorrowRate: BN; + fees: { + borrowFeeWad: BN; + flashLoanFeeWad: BN; + hostFeePercentage: number; + }; + depositLimit: BN; + borrowLimit: BN; + feeReceiver: PublicKey; + protocolLiquidationFee: number; + protocolTakeRate: number; + addedBorrowWeightBPS: BN; + reserveType: number; + maxLiquidationBonus: number; + maxLiquidationThreshold: number; + scaledPriceOffsetBPS: BN; + extraOracle?: PublicKey; + attributedBorrowLimitOpen: BN; + attributedBorrowLimitClose: BN; +} diff --git a/solend-sdk/src/core/utils/obligations.ts b/solend-sdk/src/core/utils/obligations.ts index aa923701..6cc63ec6 100644 --- a/solend-sdk/src/core/utils/obligations.ts +++ b/solend-sdk/src/core/utils/obligations.ts @@ -1,12 +1,12 @@ import { Connection, PublicKey } from "@solana/web3.js"; import BigNumber from "bignumber.js"; import { Obligation, OBLIGATION_SIZE, parseObligation } from "../../state"; -import { ReserveType } from "../types"; import { sha256 as sha256$1 } from "js-sha256"; import { getBatchMultipleAccountsInfo } from "./utils"; import { U64_MAX } from "../constants"; +import { ReserveType } from "./pools"; -export type FormattedObligation = ReturnType; +export type ObligationType = ReturnType; export function formatObligation( obligation: { pubkey: PublicKey; info: Obligation }, @@ -39,6 +39,7 @@ export function formatObligation( return { liquidationThreshold: reserve.liquidationThreshold, + maxLiquidationThreshold: reserve.maxLiquidationThreshold, loanToValueRatio: reserve.loanToValueRatio, symbol: reserve.symbol, price: reserve.price, @@ -112,6 +113,10 @@ export function formatObligation( (acc, d) => d.amountUsd.times(d.liquidationThreshold).plus(acc), BigNumber(0) ); + const superUnhealthyBorrowValue = deposits.reduce( + (acc, d) => d.amountUsd.times(d.maxLiquidationThreshold).plus(acc), + BigNumber(0) + ); const netAccountValue = totalSupplyValue.minus(totalBorrowValue); const liquidationThresholdFactor = totalSupplyValue.isZero() ? new BigNumber(0) @@ -154,6 +159,8 @@ export function formatObligation( .div(netAccountValue.toString()); return { address: obligation.pubkey.toBase58(), + owner: obligation.info.owner.toBase58(), + closeable: obligation.info.closeable, positions, deposits, borrows, @@ -164,6 +171,7 @@ export function formatObligation( liquidationThreshold, netAccountValue, liquidationThresholdFactor, + superUnhealthyBorrowValue, borrowLimitFactor, borrowUtilization, weightedConservativeBorrowUtilization, diff --git a/solend-sdk/src/core/utils/pools.ts b/solend-sdk/src/core/utils/pools.ts index 2a5018c7..3906e1bf 100644 --- a/solend-sdk/src/core/utils/pools.ts +++ b/solend-sdk/src/core/utils/pools.ts @@ -3,7 +3,7 @@ import BigNumber from "bignumber.js"; import SwitchboardProgram from "@switchboard-xyz/sbv2-lite"; import { fetchPrices } from "./prices"; import { calculateBorrowInterest, calculateSupplyInterest } from "./rates"; -import { PoolType, ReserveType } from "../types"; +import { PoolType } from "../types"; import { parseReserve, Reserve } from "../../state"; import { parseRateLimiter } from "./utils"; @@ -49,18 +49,22 @@ export async function fetchPools( return pools; } +export type ReserveType = ReturnType; + export function formatReserve( reserve: { pubkey: PublicKey; info: Reserve; }, - price: - | { - spotPrice: number; - emaPrice: number; - } - | undefined, - currentSlot: number + price?: { + spotPrice: number; + emaPrice: number; + }, + currentSlot?: number, + metadata?: { + symbol: string; + logo: string; + } ) { const decimals = reserve.info.liquidity.mintDecimals; const availableAmount = new BigNumber( @@ -147,7 +151,8 @@ export function formatReserve( address, mintAddress: reserve.info.liquidity.mintPubkey.toBase58(), decimals, - symbol: undefined, + symbol: metadata?.symbol, + logo: metadata?.logo, price: priceResolved, poolAddress: reserve.info.lendingMarket.toBase58(), pythOracle: reserve.info.liquidity.pythOracle.toBase58(), diff --git a/solend-sdk/src/core/utils/utils.ts b/solend-sdk/src/core/utils/utils.ts index 6c0a925b..0082b1fc 100644 --- a/solend-sdk/src/core/utils/utils.ts +++ b/solend-sdk/src/core/utils/utils.ts @@ -8,7 +8,7 @@ export const OUTFLOW_BUFFER = 0.985; export const parseRateLimiter = ( rateLimiter: RateLimiter, - currentSlot: number + currentSlot?: number ) => { const convertedRateLimiter = { config: { @@ -27,7 +27,9 @@ export const parseRateLimiter = ( }; return { ...convertedRateLimiter, - remainingOutflow: remainingOutflow(currentSlot, convertedRateLimiter), + remainingOutflow: currentSlot + ? remainingOutflow(currentSlot, convertedRateLimiter) + : null, }; }; diff --git a/solend-sdk/src/core/utils/wallet.ts b/solend-sdk/src/core/utils/wallet.ts index e66a1fcb..a42d4bc5 100644 --- a/solend-sdk/src/core/utils/wallet.ts +++ b/solend-sdk/src/core/utils/wallet.ts @@ -1,6 +1,7 @@ import { getAssociatedTokenAddress, NATIVE_MINT, + TOKEN_2022_PROGRAM_ID, unpackAccount, } from "@solana/spl-token"; import { Connection, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js"; @@ -42,7 +43,7 @@ export function formatWalletAssets( address: wSolAddress, amount: nativeSolBalance, mintAddress: NATIVE_MINT.toBase58(), - logo: 'https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png', + logo: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png", }, ]); } @@ -54,12 +55,21 @@ export async function fetchWalletAssets( debug?: boolean ) { if (debug) console.log("fetchWalletAssets", uniqueAssets.length); + + const uniqueAssetAccounts = await getBatchMultipleAccountsInfo( + uniqueAssets.map((asset) => new PublicKey(asset)), + connection + ); + const userTokenAssociatedAddresses = await Promise.all( - uniqueAssets.map(async (asset) => { + uniqueAssetAccounts.map(async (asset, index) => { const userTokenAccount = await getAssociatedTokenAddress( - new PublicKey(asset), + new PublicKey(uniqueAssets[index]), new PublicKey(publicKey), - true + true, + asset?.owner.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58() + ? TOKEN_2022_PROGRAM_ID + : undefined ); return userTokenAccount; }) @@ -82,7 +92,13 @@ export async function fetchWalletAssets( userAssociatedTokenAccounts: userAssociatedTokenAccounts.map( (account, index) => account - ? unpackAccount(userTokenAssociatedAddresses[index], account) + ? unpackAccount( + userTokenAssociatedAddresses[index], + account, + account?.owner.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58() + ? TOKEN_2022_PROGRAM_ID + : undefined + ) : null ), nativeSolBalance: new BigNumber(nativeSolBalance).dividedBy( diff --git a/solend-sdk/src/instructions/initReserve.ts b/solend-sdk/src/instructions/initReserve.ts index 329bf3e9..7ea4bbcc 100644 --- a/solend-sdk/src/instructions/initReserve.ts +++ b/solend-sdk/src/instructions/initReserve.ts @@ -6,14 +6,14 @@ import { } from "@solana/web3.js"; import BN from "bn.js"; import * as Layout from "../layout"; -import { ReserveConfig } from "../state"; import { LendingInstruction } from "./instruction"; +import { InputReserveConfigParams, NULL_ORACLE } from "../core"; const BufferLayout = require("buffer-layout"); export const initReserveInstruction = ( liquidityAmount: number | BN, - config: ReserveConfig, + config: InputReserveConfigParams, sourceLiquidity: PublicKey, destinationCollateral: PublicKey, reserve: PublicKey, @@ -22,7 +22,6 @@ export const initReserveInstruction = ( liquidityFeeReceiver: PublicKey, collateralMint: PublicKey, collateralSupply: PublicKey, - pythProduct: PublicKey, pythPrice: PublicKey, switchboardFeed: PublicKey, lendingMarket: PublicKey, @@ -72,7 +71,7 @@ export const initReserveInstruction = ( instruction: LendingInstruction.InitReserve, liquidityAmount: new BN(liquidityAmount), optimalUtilizationRate: config.optimalUtilizationRate, - maxUtilizationRate: config.maxUtilizationRate, + maxUtilizationRate: config.maxUtilizationRate, loanToValueRatio: config.loanToValueRatio, liquidationBonus: config.liquidationBonus, liquidationThreshold: config.liquidationThreshold, @@ -110,7 +109,8 @@ export const initReserveInstruction = ( { pubkey: liquidityFeeReceiver, isSigner: false, isWritable: true }, { pubkey: collateralMint, isSigner: false, isWritable: true }, { pubkey: collateralSupply, isSigner: false, isWritable: true }, - { pubkey: pythProduct, isSigner: false, isWritable: false }, + // Doesn't matter what we pass in as long as it's not null + { pubkey: pythPrice, isSigner: false, isWritable: false }, { pubkey: pythPrice, isSigner: false, isWritable: false }, { pubkey: switchboardFeed, isSigner: false, isWritable: false }, { pubkey: lendingMarket, isSigner: false, isWritable: true }, diff --git a/solend-sdk/src/instructions/liquidateObligationAndRedeemReserveCollateral.ts b/solend-sdk/src/instructions/liquidateObligationAndRedeemReserveCollateral.ts index b7fb89e4..d0121f08 100644 --- a/solend-sdk/src/instructions/liquidateObligationAndRedeemReserveCollateral.ts +++ b/solend-sdk/src/instructions/liquidateObligationAndRedeemReserveCollateral.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; import { PublicKey, TransactionInstruction } from "@solana/web3.js"; import BN from "bn.js"; diff --git a/solend-sdk/src/instructions/updateReserveConfig.ts b/solend-sdk/src/instructions/updateReserveConfig.ts index df7a515c..a7f0fd13 100644 --- a/solend-sdk/src/instructions/updateReserveConfig.ts +++ b/solend-sdk/src/instructions/updateReserveConfig.ts @@ -1,8 +1,8 @@ import { PublicKey, TransactionInstruction } from "@solana/web3.js"; import { RateLimiterConfig } from "../state/rateLimiter"; -import { ReserveConfig } from "../state/reserve"; import * as Layout from "../layout"; import { LendingInstruction } from "./instruction"; +import { NULL_ORACLE, InputReserveConfigParams } from "../core"; const BufferLayout = require("buffer-layout"); @@ -22,10 +22,9 @@ export const updateReserveConfig = ( lendingMarket: PublicKey, lendingMarketAuthority: PublicKey, lendingMarketOwner: PublicKey, - pythProduct: PublicKey, pythPrice: PublicKey, switchboardOracle: PublicKey, - reserveConfig: ReserveConfig, + reserveConfig: InputReserveConfigParams, rateLimiterConfig: RateLimiterConfig, solendProgramAddress: PublicKey ): TransactionInstruction => { @@ -107,7 +106,7 @@ export const updateReserveConfig = ( { pubkey: lendingMarket, isSigner: false, isWritable: false }, { pubkey: lendingMarketAuthority, isSigner: false, isWritable: false }, { pubkey: lendingMarketOwner, isSigner: true, isWritable: false }, - { pubkey: pythProduct, isSigner: false, isWritable: false }, + { pubkey: NULL_ORACLE, isSigner: false, isWritable: false }, { pubkey: pythPrice, isSigner: false, isWritable: false }, { pubkey: switchboardOracle, isSigner: false, isWritable: false }, ]; diff --git a/solend-sdk/src/state/lendingMarket.ts b/solend-sdk/src/state/lendingMarket.ts index 8fe4f0ce..6733d882 100644 --- a/solend-sdk/src/state/lendingMarket.ts +++ b/solend-sdk/src/state/lendingMarket.ts @@ -18,6 +18,8 @@ export interface LendingMarket { riskAuthority: PublicKey; } +export type ParsedLendingMarket = ReturnType; + export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.struct([ BufferLayout.u8("version"), diff --git a/solend-sdk/src/state/obligation.ts b/solend-sdk/src/state/obligation.ts index fa60574b..4ddfcb82 100644 --- a/solend-sdk/src/state/obligation.ts +++ b/solend-sdk/src/state/obligation.ts @@ -20,8 +20,10 @@ export interface Obligation { borrowedValueUpperBound: BN; // decimals allowedBorrowValue: BN; // decimals unhealthyBorrowValue: BN; // decimals - superUnhealthyBorrowValue: BN; // decimals borrowingIsolatedAsset: boolean; + unweightedBorrowValue: BN; // decimals + superUnhealthyBorrowValue: BN; // decimals + closeable: boolean; pubkey: PublicKey; // add on } @@ -79,7 +81,9 @@ export const ObligationLayout: typeof BufferLayout.Structure = Layout.uint128("borrowedValueUpperBound"), BufferLayout.u8("borrowingIsolatedAsset"), Layout.uint128("superUnhealthyBorrowValue"), - BufferLayout.blob(31, "_padding"), + Layout.uint128("unweightedBorrowValue"), + BufferLayout.u8("closeable"), + BufferLayout.blob(14, "_padding"), BufferLayout.u8("depositsLen"), BufferLayout.u8("borrowsLen"), @@ -118,6 +122,10 @@ export interface ProtoObligation { allowedBorrowValue: BN; // decimals unhealthyBorrowValue: BN; // decimals borrowedValueUpperBound: BN; // decimals + borrowingIsolatedAsset: boolean; + unweightedBorrowValue: BN; // decimals + superUnhealthyBorrowValue: BN; // decimals + closeable: boolean; depositsLen: number; borrowsLen: number; dataFlat: Buffer; @@ -143,6 +151,10 @@ export const parseObligation = ( allowedBorrowValue, unhealthyBorrowValue, borrowedValueUpperBound, + borrowingIsolatedAsset, + unweightedBorrowValue, + superUnhealthyBorrowValue, + closeable, depositsLen, borrowsLen, dataFlat, @@ -181,6 +193,10 @@ export const parseObligation = ( allowedBorrowValue, unhealthyBorrowValue, borrowedValueUpperBound, + borrowingIsolatedAsset, + unweightedBorrowValue, + superUnhealthyBorrowValue, + closeable, deposits, borrows, pubkey, diff --git a/yarn.lock b/yarn.lock index 859e9d9c..ab5f78fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1015,6 +1015,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@brokerloop/ttlcache@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@brokerloop/ttlcache/-/ttlcache-3.2.3.tgz#bc3c79bb381f7b43f83745eb96e86673f75d3d11" + integrity sha512-kZWoyJGBYTv1cL5oHBYEixlJysJBf2RVnub3gbclD+dwaW9aKubbHzbZ9q1q6bONosxaOqMsoBorOrZKzBDiqg== + dependencies: + "@soncodi/signal" "~2.0.7" + "@bundlr-network/client@^0.7.14": version "0.7.17" resolved "https://registry.yarnpkg.com/@bundlr-network/client/-/client-0.7.17.tgz#0a4c41534640f9038e5d7d1d8cf1cc2571b185b7" @@ -1861,7 +1868,7 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@coral-xyz/anchor-30@npm:@coral-xyz/anchor@0.30.1": +"@coral-xyz/anchor-30@npm:@coral-xyz/anchor@0.30.1", "@coral-xyz/anchor@^0.30.1": version "0.30.1" resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.1.tgz#17f3e9134c28cd0ea83574c6bab4e410bcecec5d" integrity sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ== @@ -3525,6 +3532,32 @@ bn.js "^5.2.0" debug "^4.3.4" +"@metaplex-foundation/rustbin@^0.3.0", "@metaplex-foundation/rustbin@^0.3.1": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/rustbin/-/rustbin-0.3.5.tgz#56d028afd96c2b56ad3bbea22ff454adde900e8c" + integrity sha512-m0wkRBEQB/8krwMwKBvFugufZtYwMXiGHud2cTDAv+aGXK4M90y0Hx67/wpu+AqqoQfdV8VM9YezUOHKD+Z5kA== + dependencies: + debug "^4.3.3" + semver "^7.3.7" + text-table "^0.2.0" + toml "^3.0.0" + +"@metaplex-foundation/solita@^0.12.2": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/solita/-/solita-0.12.2.tgz#13ef213ac183c986f6d01c5d981c44e59a900834" + integrity sha512-oczMfE43NNHWweSqhXPTkQBUbap/aAiwjDQw8zLKNnd/J8sXr/0+rKcN5yJIEgcHeKRkp90eTqkmt2WepQc8yw== + dependencies: + "@metaplex-foundation/beet" "^0.4.0" + "@metaplex-foundation/beet-solana" "^0.3.0" + "@metaplex-foundation/rustbin" "^0.3.0" + "@solana/web3.js" "^1.36.0" + camelcase "^6.2.1" + debug "^4.3.3" + js-sha256 "^0.9.0" + prettier "^2.5.1" + snake-case "^3.0.4" + spok "^1.4.3" + "@mithraic-labs/psy-american@^0.2.1": version "0.2.3" resolved "https://registry.yarnpkg.com/@mithraic-labs/psy-american/-/psy-american-0.2.3.tgz#a6f3a62f96ec01f94463a7b12f5bf71a003c9942" @@ -5666,6 +5699,21 @@ jsbi "^4.3.0" typedoc-plugin-cname "^1.0.1" +"@solendprotocol/token2022-wrapper-sdk@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@solendprotocol/token2022-wrapper-sdk/-/token2022-wrapper-sdk-1.0.1.tgz#47158a0c4c6fa3c7f9b2740c7d07101e0191abd0" + integrity sha512-+1fLbH5XSJo2/+vyPqVhEjuQzUbG+JQ54M3lSYuSe4h83q6udI0JdUxzW/hPxm6BvNlp+2acmy/gRuKrSEk4NA== + dependencies: + "@metaplex-foundation/beet" "^0.7.1" + "@metaplex-foundation/rustbin" "^0.3.1" + "@metaplex-foundation/solita" "^0.12.2" + "@solana/spl-token" "^0.3.7" + bn.js "^5.2.1" + borsh "^0.7.0" + bs58 "^5.0.0" + buffer-layout "^1.2.2" + zstddec "^0.0.2" + "@solflare-wallet/metamask-sdk@^1.0.2": version "1.0.3" resolved "https://registry.yarnpkg.com/@solflare-wallet/metamask-sdk/-/metamask-sdk-1.0.3.tgz#3baaa22de2c86515e6ba6025285cd1f5d74b04e5" @@ -5711,6 +5759,11 @@ decimal.js "^10.4.0" typescript "^4.8.2" +"@soncodi/signal@~2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@soncodi/signal/-/signal-2.0.7.tgz#0a2c361b02dbfdbcf4e66b78e5f711e0a13d6e83" + integrity sha512-zA2oZluZmVvgZEDjF243KWD1S2J+1SH1MVynI0O1KRgDt1lU8nqk7AK3oQfW/WpwT51L5waGSU0xKF/9BTP5Cw== + "@stablelib/aead@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@stablelib/aead/-/aead-1.0.1.tgz#c4b1106df9c23d1b867eb9b276d8f42d5fc4c0c3" @@ -5881,20 +5934,20 @@ protobufjs "^7.2.6" yaml "^2.2.1" -"@switchboard-xyz/on-demand@^1.2.20": - version "1.2.20" - resolved "https://registry.yarnpkg.com/@switchboard-xyz/on-demand/-/on-demand-1.2.20.tgz#11dcd876cbf8bb1893f22f7895baaf63fe118d76" - integrity sha512-RC52eNZVGaRbPdb8GYc1H4nm4YRCToX2LMI8flWjV9cf7jOIkcjtPyLH/Spg8LJPe2Pa30wJlqaqSXfqYB47Xw== +"@switchboard-xyz/on-demand@1.2.27": + version "1.2.27" + resolved "https://registry.yarnpkg.com/@switchboard-xyz/on-demand/-/on-demand-1.2.27.tgz#8591d2a424a7a9a9256a996df35f408c0167fe40" + integrity sha512-acrIngoQRv6M6PiNooEFQP+0FExseKkCMecGDP1nLRz7C1hLMTgWWeUqmmfrxJh6uCafM4Cl4lpKk3pGd01XBg== dependencies: + "@brokerloop/ttlcache" "^3.2.3" "@coral-xyz/anchor-30" "npm:@coral-xyz/anchor@0.30.1" "@solana/web3.js" "^1.95.0" "@solworks/soltoolkit-sdk" "^0.0.23" "@switchboard-xyz/common" "^2.4.2" - axios "^1.2.0" + axios "^1.7.4" big.js "^6.2.1" bs58 "^5.0.0" js-yaml "^4.1.0" - node-cache "^5.1.2" protobufjs "^7.2.6" "@switchboard-xyz/sbv2-lite@^0.1.0": @@ -7297,7 +7350,7 @@ ansi-styles@^6.1.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== -ansicolors@^0.3.2: +ansicolors@^0.3.2, ansicolors@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== @@ -7661,7 +7714,7 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" -axios@^1.2.0, axios@^1.5.1, axios@^1.7.2: +axios@^1.5.1, axios@^1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== @@ -7670,6 +7723,15 @@ axios@^1.2.0, axios@^1.5.1, axios@^1.7.2: form-data "^4.0.0" proxy-from-env "^1.1.0" +axios@^1.7.4: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" @@ -8290,7 +8352,7 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0, camelcase@^6.2.0, camelcase@^6.3.0: +camelcase@^6.0.0, camelcase@^6.2.0, camelcase@^6.2.1, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -8525,11 +8587,6 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" -clone@2.x: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== - clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" @@ -8604,6 +8661,11 @@ commander@^2.20.3: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + commander@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" @@ -10385,6 +10447,15 @@ filter-obj@^1.1.0: resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== +find-process@^1.4.7: + version "1.4.7" + resolved "https://registry.yarnpkg.com/find-process/-/find-process-1.4.7.tgz#8c76962259216c381ef1099371465b5b439ea121" + integrity sha512-/U4CYp1214Xrp3u3Fqr9yNynUrr5Le4y0SsJh2lMDDSbpwYSz3M2SMWQC+wqcx79cN8PQtHQIL8KnuY9M66fdg== + dependencies: + chalk "^4.0.0" + commander "^5.1.0" + debug "^4.1.1" + find-root@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" @@ -13540,13 +13611,6 @@ node-addon-api@^8.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.0.0.tgz#5453b7ad59dd040d12e0f1a97a6fa1c765c5c9d2" integrity sha512-ipO7rsHEBqa9STO5C5T10fj732ml+5kLN1cAG8/jdHd56ldQeGj3Q7+scUS+VHK/qy1zLEwC4wMK5+yM0btPvw== -node-cache@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d" - integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg== - dependencies: - clone "2.x" - node-fetch-native@^1.6.1, node-fetch-native@^1.6.2, node-fetch-native@^1.6.3: version "1.6.4" resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.4.tgz#679fc8fd8111266d47d7e72c379f1bed9acff06e" @@ -15191,6 +15255,11 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.3.7: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -15514,6 +15583,14 @@ split@0.3: dependencies: through "2" +spok@^1.4.3: + version "1.5.5" + resolved "https://registry.yarnpkg.com/spok/-/spok-1.5.5.tgz#a51f7f290a53131d7b7a922dfedc461dda0aed72" + integrity sha512-IrJIXY54sCNFASyHPOY+jEirkiJ26JDqsGiI0Dvhwcnkl0PEWi1PSsrkYql0rzDw8LFVTcA7rdUCAJdE2HE+2Q== + dependencies: + ansicolors "~0.3.2" + find-process "^1.4.7" + sprintf-js@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" @@ -17133,3 +17210,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zstddec@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.0.2.tgz#57e2f28dd1ff56b750e07d158a43f0611ad9eeb4" + integrity sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==