Merge branch 'deploy-v0.21.0' into dev
This commit is contained in:
commit
0e8fc5e10e
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@blockworks-foundation/mango-v4",
|
||||
"version": "0.19.43",
|
||||
"version": "0.20.9",
|
||||
"description": "Typescript Client for mango-v4 program.",
|
||||
"repository": "https://github.com/blockworks-foundation/mango-v4",
|
||||
"author": {
|
||||
|
@ -27,6 +27,7 @@
|
|||
"lint": "eslint ./ts/client/src --ext ts --ext tsx --ext js --quiet",
|
||||
"typecheck": "tsc --noEmit --pretty",
|
||||
"prepublishOnly": "yarn validate && yarn build",
|
||||
"deduplicate": "npx yarn-deduplicate --list --fail",
|
||||
"validate": "yarn lint && yarn format"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -3,10 +3,10 @@ import { getAccount } from '@solana/spl-token';
|
|||
import { Cluster, Connection, Keypair } from '@solana/web3.js';
|
||||
import * as dotenv from 'dotenv';
|
||||
import fs from 'fs';
|
||||
import { MangoClient } from '../../src/client';
|
||||
import { MANGO_V4_ID } from '../../src/constants';
|
||||
import { I80F48, ZERO_I80F48 } from '../../src/numbers/I80F48';
|
||||
import { toUiDecimals } from '../../src/utils';
|
||||
import { MangoClient } from '../src/client';
|
||||
import { MANGO_V4_ID } from '../src/constants';
|
||||
import { I80F48, ZERO_I80F48 } from '../src/numbers/I80F48';
|
||||
import { toUiDecimals } from '../src/utils';
|
||||
dotenv.config();
|
||||
|
||||
const CLUSTER_URL =
|
||||
|
@ -42,13 +42,14 @@ async function main(): Promise<void> {
|
|||
banks.map((bank) => {
|
||||
(bank as any).indexedDepositsByMangoAccounts = ZERO_I80F48();
|
||||
(bank as any).indexedBorrowsByMangoAccounts = ZERO_I80F48();
|
||||
(bank as any).serum3Total = ZERO_I80F48();
|
||||
return [bank.tokenIndex, bank];
|
||||
}),
|
||||
);
|
||||
|
||||
const mangoAccounts = await client.getAllMangoAccounts(group);
|
||||
const mangoAccounts = await client.getAllMangoAccounts(group, true);
|
||||
|
||||
mangoAccounts.map((mangoAccount) =>
|
||||
mangoAccounts.map((mangoAccount) => {
|
||||
mangoAccount.tokensActive().forEach((token) => {
|
||||
const bank = banksMapUsingTokenIndex.get(token.tokenIndex);
|
||||
if (token.indexedPosition.isPos()) {
|
||||
|
@ -69,8 +70,21 @@ async function main(): Promise<void> {
|
|||
.mul(banksMapUsingTokenIndex.get(token.tokenIndex)!.borrowIndex),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
mangoAccount.serum3Active().map((s3a) => {
|
||||
const baseBank = group.getFirstBankByTokenIndex(s3a.baseTokenIndex);
|
||||
const quoteBank = group.getFirstBankByTokenIndex(s3a.quoteTokenIndex);
|
||||
|
||||
const oo = mangoAccount.serum3OosMapByMarketIndex.get(s3a.marketIndex);
|
||||
(baseBank as any).serum3Total = (baseBank as any).serum3Total.add(
|
||||
I80F48.fromU64(oo!.baseTokenTotal),
|
||||
);
|
||||
(quoteBank as any).serum3Total = (quoteBank as any).serum3Total.add(
|
||||
I80F48.fromU64(oo!.quoteTokenTotal),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
for (const bank of await Array.from(banksMapUsingTokenIndex.values()).sort(
|
||||
(a, b) => a.tokenIndex - b.tokenIndex,
|
||||
|
@ -81,67 +95,60 @@ async function main(): Promise<void> {
|
|||
);
|
||||
const vault = I80F48.fromNumber(Number(account.amount));
|
||||
|
||||
const error = vault.sub(
|
||||
(bank as any).indexedDepositsByMangoAccounts
|
||||
.sub((bank as any).indexedBorrowsByMangoAccounts)
|
||||
.add(bank.collectedFeesNative)
|
||||
.add(bank.dust),
|
||||
);
|
||||
|
||||
const error = vault
|
||||
.sub(
|
||||
bank.indexedDeposits
|
||||
.mul(bank.depositIndex)
|
||||
.sub(bank.indexedBorrows.mul(bank.borrowIndex)),
|
||||
)
|
||||
.sub(bank.collectedFeesNative)
|
||||
.sub(bank.dust)
|
||||
.add(I80F48.fromU64(bank.feesWithdrawn));
|
||||
let res = `${bank.name}`;
|
||||
res =
|
||||
res +
|
||||
`\n ${'tokenIndex'.padEnd(40)} ${bank.tokenIndex}` +
|
||||
`\n ${'bank'.padEnd(40)} ${bank.publicKey}` +
|
||||
`\n ${'vault'.padEnd(40)} ${bank.vault}` +
|
||||
`\n ${'oracle'.padEnd(40)} ${bank.oracle}` +
|
||||
`\n ${'mint'.padEnd(40)} ${bank.mint}` +
|
||||
`\n ${'price'.padEnd(40)} ${bank.price?.toNumber()}` +
|
||||
`\n ${'uiPrice'.padEnd(40)} ${bank.uiPrice}` +
|
||||
`\n ${'error'.padEnd(40)} ${error}` +
|
||||
`\n ${'collectedFeesNative'.padEnd(40)} ${bank.collectedFeesNative}` +
|
||||
`\n ${'dust'.padEnd(40)} ${bank.dust}` +
|
||||
`\n ${'vault balance'.padEnd(40)} ${toUiDecimals(
|
||||
`\n ${'error'.padEnd(40)} ${toUiDecimals(
|
||||
error,
|
||||
bank.mintDecimals,
|
||||
).toLocaleString()}` +
|
||||
`\n ${'vault'.padEnd(40)} ${toUiDecimals(
|
||||
vault,
|
||||
bank.mintDecimals,
|
||||
)}, ${vault} native` +
|
||||
`\n ${'deposits'.padEnd(40)} ${bank.indexedDeposits.mul(
|
||||
bank.depositIndex,
|
||||
)}` +
|
||||
`\n ${'deposits (sum over all mango accounts)'.padEnd(40)} ${
|
||||
(bank as any).indexedDepositsByMangoAccounts
|
||||
}` +
|
||||
`\n ${'borrows'.padEnd(40)} ${bank.indexedBorrows.mul(
|
||||
bank.borrowIndex,
|
||||
)}` +
|
||||
`\n ${'borrows (sum over all mango accounts)'.padEnd(40)} ${
|
||||
(bank as any).indexedBorrowsByMangoAccounts
|
||||
}` +
|
||||
`\n ${'avgUtilization since last rate update'.padEnd(40)} ${(
|
||||
100 * bank.avgUtilization.toNumber()
|
||||
).toFixed(1)}%` +
|
||||
`\n ${'rate parameters'.padEnd(40)} ${(
|
||||
100 * bank.rate0.toNumber()
|
||||
).toFixed()}% @ ${(100 * bank.util0.toNumber()).toFixed()}% util, ${(
|
||||
100 * bank.rate1.toNumber()
|
||||
).toFixed()}% @${(100 * bank.util1.toNumber()).toFixed()}% util, ${(
|
||||
100 * bank.maxRate.toNumber()
|
||||
).toFixed()}% @ 100% util` +
|
||||
`\n ${'depositRate'.padEnd(40)} ${(
|
||||
100 * bank.getDepositRate().toNumber()
|
||||
).toFixed(2)}%` +
|
||||
`\n ${'borrowRate'.padEnd(40)} ${(
|
||||
100 * bank.getBorrowRate().toNumber()
|
||||
).toFixed(2)}%` +
|
||||
`\n ${'last index update'.padEnd(40)} ${new Date(
|
||||
1000 * bank.indexLastUpdated.toNumber(),
|
||||
)}` +
|
||||
`\n ${'last rates update'.padEnd(40)} ${new Date(
|
||||
1000 * bank.bankRateLastUpdated.toNumber(),
|
||||
)}` +
|
||||
`\n ${'net borrows in window'.padEnd(
|
||||
40,
|
||||
)} ${bank.netBorrowsInWindow.toNumber()} / ${bank.netBorrowLimitPerWindowQuote.toNumber()}`;
|
||||
).toLocaleString()}` +
|
||||
`\n ${'collected fees'.padEnd(40)} ${toUiDecimals(
|
||||
bank.collectedFeesNative,
|
||||
bank.mintDecimals,
|
||||
).toLocaleString()}` +
|
||||
`\n ${'fees withdrawn'.padEnd(40)} ${toUiDecimals(
|
||||
bank.feesWithdrawn,
|
||||
bank.mintDecimals,
|
||||
).toLocaleString()}` +
|
||||
`\n ${'deposits'.padEnd(40)} ${toUiDecimals(
|
||||
bank.indexedDeposits.mul(bank.depositIndex),
|
||||
bank.mintDecimals,
|
||||
).toLocaleString()}` +
|
||||
`\n ${'deposits (sum over all mango accounts)'.padEnd(40)} ${toUiDecimals(
|
||||
(bank as any).indexedDepositsByMangoAccounts,
|
||||
bank.mintDecimals,
|
||||
).toLocaleString()}` +
|
||||
`\n ${'borrows'.padEnd(40)} ${toUiDecimals(
|
||||
bank.indexedBorrows.mul(bank.borrowIndex),
|
||||
bank.mintDecimals,
|
||||
).toLocaleString()}` +
|
||||
`\n ${'borrows (sum over all mango accounts)'.padEnd(40)} ${toUiDecimals(
|
||||
(bank as any).indexedBorrowsByMangoAccounts,
|
||||
bank.mintDecimals,
|
||||
).toLocaleString()}` +
|
||||
`\n ${'deposits - borrows'.padEnd(40)} ${toUiDecimals(
|
||||
bank.indexedDeposits
|
||||
.mul(bank.depositIndex)
|
||||
.sub(bank.indexedBorrows.mul(bank.borrowIndex)),
|
||||
bank.mintDecimals,
|
||||
).toLocaleString()}` +
|
||||
`\n ${`serum3 total`.padEnd(40)} ${toUiDecimals(
|
||||
(bank as any).serum3Total,
|
||||
bank.mintDecimals,
|
||||
).toLocaleString()}`;
|
||||
|
||||
console.log(`${res}`);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import { PublicKey } from '@solana/web3.js';
|
||||
import { MangoClient } from '../src/client';
|
||||
import { toUiDecimals } from '../src/utils';
|
||||
|
||||
async function run() {
|
||||
const client = await MangoClient.connectDefault(process.env.MB_CLUSTER_URL!);
|
||||
let group = await client.getGroup(
|
||||
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
|
||||
);
|
||||
|
||||
let accounts = await client.getAllMangoAccounts(group, true);
|
||||
const jitoBank = group.getFirstBankByMint(
|
||||
new PublicKey('J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn'),
|
||||
);
|
||||
const jitoSpotMarket = group.getSerum3MarketByName('JitoSOL/USDC');
|
||||
|
||||
accounts = accounts.filter(
|
||||
(a) =>
|
||||
a.getTokenBalanceUi(jitoBank) +
|
||||
(a.getSerum3Account(jitoSpotMarket.marketIndex)
|
||||
? a
|
||||
.getSerum3OoAccount(jitoSpotMarket.marketIndex)
|
||||
.baseTokenTotal.toNumber()
|
||||
: 0) >
|
||||
0.1,
|
||||
);
|
||||
|
||||
accounts.sort((a, b) =>
|
||||
a.publicKey.toBase58().localeCompare(b.publicKey.toBase58()),
|
||||
);
|
||||
|
||||
console.log(`wallet,mango_account,jito_sol_balance_ui`);
|
||||
accounts.forEach((a) =>
|
||||
console.log(
|
||||
`${a.owner},${a.publicKey},${
|
||||
a.getTokenBalanceUi(jitoBank) +
|
||||
(a.getSerum3Account(jitoSpotMarket.marketIndex)
|
||||
? toUiDecimals(
|
||||
a
|
||||
.getSerum3OoAccount(jitoSpotMarket.marketIndex)
|
||||
.baseTokenTotal.toNumber(),
|
||||
jitoBank.mintDecimals,
|
||||
)
|
||||
: 0)
|
||||
}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
run();
|
|
@ -52,7 +52,7 @@ async function updateBanks(client: MangoClient, group: Group): Promise<void> {
|
|||
);
|
||||
|
||||
console.log(
|
||||
` - Token update index and rate success, tokenIndices - ${tokenIndices}, sig https://explorer.solana.com/tx/${sig}`,
|
||||
` - Token update index and rate success, tokenIndices - ${tokenIndices}, sig https://explorer.solana.com/tx/${sig.signature}`,
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(
|
||||
|
@ -104,7 +104,7 @@ async function consumeEvents(client: MangoClient, group: Group): Promise<void> {
|
|||
);
|
||||
|
||||
console.log(
|
||||
` - Consume events success, perpMarketIndex - ${perpMarketIndex}, sig https://explorer.solana.com/tx/${sig}`,
|
||||
` - Consume events success, perpMarketIndex - ${perpMarketIndex}, sig https://explorer.solana.com/tx/${sig.signature}`,
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(
|
||||
|
@ -126,7 +126,7 @@ async function updateFunding(client: MangoClient, group: Group): Promise<void> {
|
|||
);
|
||||
for (const perpMarketIndex of perpMarketIndices) {
|
||||
try {
|
||||
const sig = await sendTransaction(
|
||||
const status = await sendTransaction(
|
||||
client.program.provider as AnchorProvider,
|
||||
[
|
||||
await client.perpUpdateFundingIx(
|
||||
|
@ -139,7 +139,7 @@ async function updateFunding(client: MangoClient, group: Group): Promise<void> {
|
|||
);
|
||||
|
||||
console.log(
|
||||
` - Update funding success, perpMarketIndex - ${perpMarketIndex}, sig https://explorer.solana.com/tx/${sig}`,
|
||||
` - Update funding success, perpMarketIndex - ${perpMarketIndex}, sig https://explorer.solana.com/tx/${status.signature}`,
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
import { AnchorProvider, Wallet } from '@coral-xyz/anchor';
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
NATIVE_MINT,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from '@solana/spl-token';
|
||||
import {
|
||||
AddressLookupTableProgram,
|
||||
Cluster,
|
||||
ComputeBudgetProgram,
|
||||
Connection,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||
SystemProgram,
|
||||
} from '@solana/web3.js';
|
||||
import fs from 'fs';
|
||||
import chunk from 'lodash/chunk';
|
||||
import { Group } from '../src/accounts/group';
|
||||
import { MangoClient } from '../src/client';
|
||||
import {
|
||||
MANGO_V4_ID,
|
||||
MANGO_V4_MAIN_GROUP,
|
||||
OPENBOOK_PROGRAM_ID,
|
||||
} from '../src/constants';
|
||||
import { buildVersionedTx } from '../src/utils';
|
||||
|
||||
const { MB_CLUSTER_URL, MB_PAYER3_KEYPAIR, DRY_RUN } = process.env;
|
||||
const CLUSTER: Cluster = (process.env.CLUSTER as Cluster) || 'mainnet-beta';
|
||||
|
||||
// eslint-disable-next-line no-inner-declarations
|
||||
async function extendTable(
|
||||
client: MangoClient,
|
||||
group: Group,
|
||||
payer: Keypair,
|
||||
nick: string,
|
||||
altAddress: PublicKey,
|
||||
addresses: PublicKey[],
|
||||
): Promise<void> {
|
||||
await group.reloadAll(client);
|
||||
const alt = await client.program.provider.connection.getAddressLookupTable(
|
||||
altAddress,
|
||||
);
|
||||
|
||||
addresses = addresses.filter(
|
||||
(newAddress) =>
|
||||
alt.value?.state.addresses &&
|
||||
alt.value?.state.addresses.findIndex((addressInAlt) =>
|
||||
addressInAlt.equals(newAddress),
|
||||
) === -1,
|
||||
);
|
||||
if (addresses.length === 0) {
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
`Extending ${altAddress} with ${nick} ${
|
||||
addresses.length
|
||||
} addresses - ${addresses.join(', ')}`,
|
||||
);
|
||||
|
||||
if (DRY_RUN) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const chunk_ of chunk(addresses, 20)) {
|
||||
const extendIx = AddressLookupTableProgram.extendLookupTable({
|
||||
lookupTable: alt.value!.key,
|
||||
payer: payer.publicKey,
|
||||
authority: payer.publicKey,
|
||||
addresses: chunk_,
|
||||
});
|
||||
const extendTx = await buildVersionedTx(
|
||||
client.program.provider as AnchorProvider,
|
||||
[extendIx],
|
||||
);
|
||||
const sig = await client.program.provider.connection.sendTransaction(
|
||||
extendTx,
|
||||
);
|
||||
console.log(`https://explorer.solana.com/tx/${sig}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
const options = AnchorProvider.defaultOptions();
|
||||
const connection = new Connection(MB_CLUSTER_URL!, options);
|
||||
const payer = Keypair.fromSecretKey(
|
||||
Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER3_KEYPAIR!, 'utf-8'))),
|
||||
);
|
||||
const payerWallet = new Wallet(payer);
|
||||
const userProvider = new AnchorProvider(connection, payerWallet, options);
|
||||
const client = await MangoClient.connect(
|
||||
userProvider,
|
||||
CLUSTER,
|
||||
MANGO_V4_ID[CLUSTER],
|
||||
{
|
||||
idsSource: 'get-program-accounts',
|
||||
},
|
||||
);
|
||||
const group = await client.getGroup(MANGO_V4_MAIN_GROUP);
|
||||
|
||||
//
|
||||
// Table 0 - liquidation relevant accounts
|
||||
//
|
||||
// const altAddress0 = group.addressLookupTablesList[0].key;
|
||||
const altAddress0 = new PublicKey(
|
||||
'AgCBUZ6UMWqPLftTxeAqpQxtrfiCyL2HgRfmmM6QTfCj',
|
||||
);
|
||||
// group and insurance vault
|
||||
await extendTable(client, group, payer, 'group', altAddress0, [
|
||||
group.publicKey,
|
||||
group.insuranceVault,
|
||||
]);
|
||||
// Banks + vaults + oracles
|
||||
// Split into 3 ixs since we end up with RangeError: encoding overruns Uint8Array otherwise
|
||||
await extendTable(
|
||||
client,
|
||||
group,
|
||||
payer,
|
||||
'token banks',
|
||||
altAddress0,
|
||||
Array.from(group.banksMapByMint.values())
|
||||
.flat()
|
||||
.map((bank) => bank.publicKey),
|
||||
);
|
||||
await extendTable(
|
||||
client,
|
||||
group,
|
||||
payer,
|
||||
'token bank oracles',
|
||||
altAddress0,
|
||||
Array.from(group.banksMapByMint.values())
|
||||
.flat()
|
||||
.map((bank) => bank.oracle),
|
||||
);
|
||||
await extendTable(
|
||||
client,
|
||||
group,
|
||||
payer,
|
||||
'token bank vaults',
|
||||
altAddress0,
|
||||
Array.from(group.banksMapByMint.values())
|
||||
.flat()
|
||||
.map((bank) => bank.vault),
|
||||
);
|
||||
// Perps + oracles
|
||||
await extendTable(
|
||||
client,
|
||||
group,
|
||||
payer,
|
||||
'perp markets and perp oracles',
|
||||
altAddress0,
|
||||
Array.from(group.perpMarketsMapByMarketIndex.values())
|
||||
.flat()
|
||||
.map((perpMarket) => [perpMarket.publicKey, perpMarket.oracle])
|
||||
.flat(),
|
||||
);
|
||||
// Well known addressess
|
||||
await extendTable(
|
||||
client,
|
||||
group,
|
||||
payer,
|
||||
'well known addresses',
|
||||
altAddress0,
|
||||
[
|
||||
// Solana specific
|
||||
SystemProgram.programId,
|
||||
TOKEN_PROGRAM_ID,
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
NATIVE_MINT,
|
||||
SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||
ComputeBudgetProgram.programId,
|
||||
// Misc.
|
||||
OPENBOOK_PROGRAM_ID['mainnet-beta'],
|
||||
],
|
||||
);
|
||||
|
||||
//
|
||||
// Table 1 - everything else
|
||||
//
|
||||
// const altAddress1 = group.addressLookupTablesList[1].key;
|
||||
const altAddress1 = new PublicKey(
|
||||
'FGZCgVhVGqzfWnmJFP9Hx4BvGvnFApEp1dM2whzXvg1Z',
|
||||
);
|
||||
// bank mints
|
||||
await extendTable(
|
||||
client,
|
||||
group,
|
||||
payer,
|
||||
'token mints',
|
||||
altAddress1,
|
||||
Array.from(group.banksMapByMint.values())
|
||||
.flat()
|
||||
.map((bank) => [bank.mint])
|
||||
.flat(),
|
||||
);
|
||||
// bank mint infos
|
||||
await extendTable(
|
||||
client,
|
||||
group,
|
||||
payer,
|
||||
'mint infos',
|
||||
altAddress1,
|
||||
Array.from(group.mintInfosMapByMint.values())
|
||||
.flat()
|
||||
.map((mintInto) => [mintInto.publicKey])
|
||||
.flat(),
|
||||
);
|
||||
// serum3
|
||||
// Split into 4 ixs since we end up with RangeError: encoding overruns Uint8Array otherwise
|
||||
await extendTable(
|
||||
client,
|
||||
group,
|
||||
payer,
|
||||
'serum3 markets',
|
||||
altAddress1,
|
||||
Array.from(group.serum3MarketsMapByMarketIndex.values())
|
||||
.flat()
|
||||
.map((serum3Market) => serum3Market.publicKey),
|
||||
);
|
||||
await extendTable(
|
||||
client,
|
||||
group,
|
||||
payer,
|
||||
'serum3 external markets',
|
||||
altAddress1,
|
||||
Array.from(group.serum3ExternalMarketsMap.values())
|
||||
.flat()
|
||||
.map((serum3ExternalMarket) => serum3ExternalMarket.publicKey),
|
||||
);
|
||||
await extendTable(
|
||||
client,
|
||||
group,
|
||||
payer,
|
||||
'serum3 external markets bids',
|
||||
altAddress1,
|
||||
Array.from(group.serum3ExternalMarketsMap.values())
|
||||
.flat()
|
||||
.map((serum3ExternalMarket) => serum3ExternalMarket.bidsAddress),
|
||||
);
|
||||
await extendTable(
|
||||
client,
|
||||
group,
|
||||
payer,
|
||||
'serum3 external markets asks',
|
||||
altAddress1,
|
||||
Array.from(group.serum3ExternalMarketsMap.values())
|
||||
.flat()
|
||||
.map((serum3ExternalMarket) => serum3ExternalMarket.asksAddress),
|
||||
);
|
||||
await extendTable(
|
||||
client,
|
||||
group,
|
||||
payer,
|
||||
'perp market event queues, bids, and asks',
|
||||
altAddress1,
|
||||
Array.from(group.perpMarketsMapByMarketIndex.values())
|
||||
.flat()
|
||||
.map((perpMarket) => [
|
||||
perpMarket.eventQueue,
|
||||
perpMarket.bids,
|
||||
perpMarket.asks,
|
||||
])
|
||||
.flat(),
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
|
@ -1,9 +1,6 @@
|
|||
import {
|
||||
LISTING_PRESETS,
|
||||
LISTING_PRESETS_PYTH,
|
||||
MidPriceImpact,
|
||||
getMidPriceImpacts,
|
||||
getProposedTier,
|
||||
} from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools';
|
||||
import { AnchorProvider, Wallet } from '@coral-xyz/anchor';
|
||||
import { BN } from '@project-serum/anchor';
|
||||
|
@ -21,12 +18,14 @@ import {
|
|||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
import fs from 'fs';
|
||||
import { OracleProvider } from '../src/accounts/oracle';
|
||||
import { Bank } from '../src/accounts/bank';
|
||||
import { Group } from '../src/accounts/group';
|
||||
import { Builder } from '../src/builder';
|
||||
import { MangoClient } from '../src/client';
|
||||
import { NullTokenEditParams } from '../src/clientIxParamBuilder';
|
||||
import { MANGO_V4_MAIN_GROUP as MANGO_V4_PRIMARY_GROUP } from '../src/constants';
|
||||
import { toUiDecimalsForQuote } from '../src/utils';
|
||||
import { getEquityForMangoAccounts } from '../src/risk';
|
||||
import { buildFetch } from '../src/utils';
|
||||
import {
|
||||
MANGO_DAO_WALLET_GOVERNANCE,
|
||||
MANGO_GOVERNANCE_PROGRAM,
|
||||
|
@ -38,6 +37,7 @@ import {
|
|||
DEFAULT_VSR_ID,
|
||||
VsrClient,
|
||||
} from './governanceInstructions/voteStakeRegistryClient';
|
||||
import { MangoAccount } from '../src/accounts/mangoAccount';
|
||||
|
||||
const {
|
||||
MB_CLUSTER_URL,
|
||||
|
@ -77,6 +77,52 @@ async function setupVsr(
|
|||
return vsrClient;
|
||||
}
|
||||
|
||||
async function getTotalLiqorEquity(
|
||||
client: MangoClient,
|
||||
group: Group,
|
||||
mangoAccounts: MangoAccount[],
|
||||
): Promise<number> {
|
||||
const liqors = (
|
||||
await (
|
||||
await (
|
||||
await buildFetch()
|
||||
)(
|
||||
`https://api.mngo.cloud/data/v4/stats/liqors-over_period?over_period=1MONTH`,
|
||||
{
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
},
|
||||
)
|
||||
).json()
|
||||
).map((data) => new PublicKey(data['liqor']));
|
||||
const ttlLiqorEquity = (
|
||||
await getEquityForMangoAccounts(client, group, liqors, mangoAccounts)
|
||||
).reduce((partialSum, ae) => partialSum + ae.Equity.val, 0);
|
||||
return ttlLiqorEquity;
|
||||
}
|
||||
|
||||
function getPriceImpactForBank(
|
||||
midPriceImpacts: MidPriceImpact[],
|
||||
bank: Bank,
|
||||
): MidPriceImpact {
|
||||
const tokenToPriceImpact = midPriceImpacts
|
||||
.filter((x) => x.avg_price_impact_percent < 1)
|
||||
.reduce((acc: { [key: string]: MidPriceImpact }, val: MidPriceImpact) => {
|
||||
if (
|
||||
!acc[val.symbol] ||
|
||||
val.target_amount > acc[val.symbol].target_amount
|
||||
) {
|
||||
acc[val.symbol] = val;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
const priceImpact = tokenToPriceImpact[getApiTokenName(bank.name)];
|
||||
return priceImpact;
|
||||
}
|
||||
|
||||
async function updateTokenParams(): Promise<void> {
|
||||
const [client, wallet] = await Promise.all([buildClient(), setupWallet()]);
|
||||
const vsrClient = await setupVsr(client.connection, wallet);
|
||||
|
@ -84,87 +130,65 @@ async function updateTokenParams(): Promise<void> {
|
|||
const group = await client.getGroup(MANGO_V4_PRIMARY_GROUP);
|
||||
|
||||
const instructions: TransactionInstruction[] = [];
|
||||
|
||||
const mangoAccounts = await client.getAllMangoAccounts(group, true);
|
||||
const ttlLiqorEquityUi = await getTotalLiqorEquity(
|
||||
client,
|
||||
group,
|
||||
mangoAccounts,
|
||||
);
|
||||
|
||||
const midPriceImpacts = getMidPriceImpacts(group.pis);
|
||||
|
||||
Array.from(group.banksMapByTokenIndex.values())
|
||||
.map((banks) => banks[0])
|
||||
.filter(
|
||||
(bank) =>
|
||||
bank.mint.toBase58() == 'So11111111111111111111111111111111111111112' ||
|
||||
bank.name.toLocaleLowerCase().indexOf('usdc') > -1 ||
|
||||
bank.name.toLocaleLowerCase().indexOf('stsol') > -1,
|
||||
// // low low liquidity
|
||||
// bank.name.includes('DUAL') ||
|
||||
// bank.name.includes('MNGO') ||
|
||||
// // low liquidity
|
||||
// bank.name.includes('ALL') ||
|
||||
// bank.name.includes('CROWN') ||
|
||||
// bank.name.includes('GUAC') ||
|
||||
// bank.name.includes('HNT') ||
|
||||
// bank.name.includes('KIN') ||
|
||||
// bank.name.includes('OPOS') ||
|
||||
// bank.name.includes('RLB') ||
|
||||
// bank.name.includes('USDH') ||
|
||||
// better liquidity
|
||||
bank.name.includes('BONK') ||
|
||||
bank.name.includes('bSOL') ||
|
||||
bank.name.includes('CHAI') ||
|
||||
bank.name.includes('DAI') ||
|
||||
bank.name.includes('ETH (Portal)') ||
|
||||
bank.name.includes('JitoSOL') ||
|
||||
bank.name.includes('LDO') ||
|
||||
bank.name.includes('MSOL') ||
|
||||
bank.name.includes('ORCA') ||
|
||||
bank.name.includes('RAY') ||
|
||||
bank.name.includes('SOL') ||
|
||||
bank.name.includes('stSOL') ||
|
||||
bank.name.includes('TBTC') ||
|
||||
bank.name.includes('USDC') ||
|
||||
bank.name.includes('wBTC (Portal)') ||
|
||||
bank.name.includes('USDT'),
|
||||
)
|
||||
.forEach(async (bank) => {
|
||||
// Limit borrows to 1/3rd of deposit, rounded to 1000, only update if more than 10% different
|
||||
const depositsInUsd = bank.nativeDeposits().mul(bank.price);
|
||||
let newNetBorrowLimitPerWindowQuote: number | null =
|
||||
depositsInUsd.toNumber() / 3;
|
||||
newNetBorrowLimitPerWindowQuote =
|
||||
Math.round(newNetBorrowLimitPerWindowQuote / 1_000_000_000) *
|
||||
1_000_000_000;
|
||||
newNetBorrowLimitPerWindowQuote =
|
||||
Math.abs(
|
||||
(newNetBorrowLimitPerWindowQuote -
|
||||
bank.netBorrowLimitPerWindowQuote.toNumber()) /
|
||||
bank.netBorrowLimitPerWindowQuote.toNumber(),
|
||||
) > 0.1
|
||||
? newNetBorrowLimitPerWindowQuote
|
||||
: null;
|
||||
const priceImpact = getPriceImpactForBank(midPriceImpacts, bank);
|
||||
|
||||
// Kick in weight scaling as late as possible until liquidation fee remains reasonable
|
||||
// Only update if more than 10% different
|
||||
let newWeightScaleQuote: number | null = null;
|
||||
if (
|
||||
bank.tokenIndex != 0 && // USDC
|
||||
bank.mint.toBase58() != 'So11111111111111111111111111111111111111112' // SOL
|
||||
) {
|
||||
const PRESETS =
|
||||
bank?.oracleProvider === OracleProvider.Pyth
|
||||
? LISTING_PRESETS_PYTH
|
||||
: LISTING_PRESETS;
|
||||
const scaleStartQuoteUi = Math.min(
|
||||
50 * ttlLiqorEquityUi,
|
||||
4 * priceImpact.target_amount,
|
||||
);
|
||||
|
||||
const tokenToPriceImpact = midPriceImpacts
|
||||
.filter((x) => x.avg_price_impact_percent < 1)
|
||||
.reduce(
|
||||
(acc: { [key: string]: MidPriceImpact }, val: MidPriceImpact) => {
|
||||
if (
|
||||
!acc[val.symbol] ||
|
||||
val.target_amount > acc[val.symbol].target_amount
|
||||
) {
|
||||
acc[val.symbol] = val;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
const priceImpact = tokenToPriceImpact[getApiTokenName(bank.name)];
|
||||
const suggestedTier = getProposedTier(
|
||||
PRESETS,
|
||||
priceImpact?.target_amount,
|
||||
bank.oracleProvider === OracleProvider.Pyth,
|
||||
);
|
||||
newWeightScaleQuote =
|
||||
PRESETS[suggestedTier].borrowWeightScaleStartQuote;
|
||||
const newNetDepositsUi = Math.max(
|
||||
10_000,
|
||||
Math.min(bank.uiDeposits(), 300_000) / 3 +
|
||||
Math.max(0, bank.uiDeposits() - 300_000) / 5,
|
||||
);
|
||||
|
||||
newWeightScaleQuote =
|
||||
bank.depositWeightScaleStartQuote !== newWeightScaleQuote ||
|
||||
bank.borrowWeightScaleStartQuote !== newWeightScaleQuote
|
||||
? newWeightScaleQuote
|
||||
: null;
|
||||
}
|
||||
|
||||
if (
|
||||
newNetBorrowLimitPerWindowQuote == null &&
|
||||
newWeightScaleQuote == null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params = Builder(NullTokenEditParams)
|
||||
.netBorrowLimitPerWindowQuote(newNetBorrowLimitPerWindowQuote)
|
||||
.borrowWeightScaleStartQuote(newWeightScaleQuote)
|
||||
.depositWeightScaleStartQuote(newWeightScaleQuote)
|
||||
.build();
|
||||
const params = Builder(NullTokenEditParams).build();
|
||||
|
||||
const ix = await client.program.methods
|
||||
.tokenEdit(
|
||||
|
@ -198,7 +222,7 @@ async function updateTokenParams(): Promise<void> {
|
|||
params.forceClose,
|
||||
params.tokenConditionalSwapTakerFeeRate,
|
||||
params.tokenConditionalSwapMakerFeeRate,
|
||||
params.flashLoanDepositFeeRate,
|
||||
params.flashLoanSwapFeeRate,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
@ -224,21 +248,6 @@ async function updateTokenParams(): Promise<void> {
|
|||
throw simulated.value.logs;
|
||||
}
|
||||
|
||||
console.log(`Bank ${bank.name}`);
|
||||
console.log(
|
||||
`- netBorrowLimitPerWindowQuote UI old ${toUiDecimalsForQuote(
|
||||
bank.netBorrowLimitPerWindowQuote.toNumber(),
|
||||
).toLocaleString()} new ${toUiDecimalsForQuote(
|
||||
newNetBorrowLimitPerWindowQuote!,
|
||||
).toLocaleString()}`,
|
||||
);
|
||||
console.log(
|
||||
`- WeightScaleQuote UI old ${toUiDecimalsForQuote(
|
||||
bank.depositWeightScaleStartQuote,
|
||||
).toLocaleString()} new ${toUiDecimalsForQuote(
|
||||
newWeightScaleQuote!,
|
||||
).toLocaleString()}`,
|
||||
);
|
||||
instructions.push(ix);
|
||||
});
|
||||
|
||||
|
|
|
@ -47,6 +47,9 @@ export class Group {
|
|||
ixGate: BN;
|
||||
buybackFeesSwapMangoAccount: PublicKey;
|
||||
buybackFeesExpiryInterval: BN;
|
||||
fastListingIntervalStart: BN;
|
||||
fastListingsInInterval: number;
|
||||
allowedFastListingsPerInterval: number;
|
||||
},
|
||||
): Group {
|
||||
return new Group(
|
||||
|
@ -68,6 +71,9 @@ export class Group {
|
|||
obj.ixGate,
|
||||
obj.buybackFeesSwapMangoAccount,
|
||||
obj.buybackFeesExpiryInterval,
|
||||
obj.fastListingIntervalStart,
|
||||
obj.fastListingsInInterval,
|
||||
obj.allowedFastListingsPerInterval,
|
||||
[], // addressLookupTablesList
|
||||
new Map(), // banksMapByName
|
||||
new Map(), // banksMapByMint
|
||||
|
@ -104,6 +110,9 @@ export class Group {
|
|||
public ixGate: BN,
|
||||
public buybackFeesSwapMangoAccount: PublicKey,
|
||||
public buybackFeesExpiryInterval: BN,
|
||||
public fastListingIntervalStart: BN,
|
||||
public fastListingsInInterval: number,
|
||||
public allowedFastListingsPerInterval: number,
|
||||
public addressLookupTablesList: AddressLookupTableAccount[],
|
||||
public banksMapByName: Map<string, Bank[]>,
|
||||
public banksMapByMint: Map<string, Bank[]>,
|
||||
|
|
|
@ -1263,6 +1263,8 @@ export class Serum3Orders {
|
|||
dto.quoteTokenIndex as TokenIndex,
|
||||
dto.highestPlacedBidInv,
|
||||
dto.lowestPlacedAsk,
|
||||
// dto.baseDepositsReserved.toNumber(),
|
||||
// dto.quoteDepositsReserved.toNumber(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1272,7 +1274,7 @@ export class Serum3Orders {
|
|||
public baseTokenIndex: TokenIndex,
|
||||
public quoteTokenIndex: TokenIndex,
|
||||
public highestPlacedBidInv: number,
|
||||
public lowestPlacedAsk: number,
|
||||
public lowestPlacedAsk: number, // public baseDepositsReserved: number, // public quoteDepositsReserved: number,
|
||||
) {}
|
||||
|
||||
public isActive(): boolean {
|
||||
|
@ -1290,6 +1292,8 @@ export class Serum3PositionDto {
|
|||
public quoteTokenIndex: number,
|
||||
public highestPlacedBidInv: number,
|
||||
public lowestPlacedAsk: number,
|
||||
// public baseDepositsReserved: BN,
|
||||
// public quoteDepositsReserved: BN,
|
||||
public reserved: number[],
|
||||
) {}
|
||||
}
|
||||
|
|
|
@ -114,6 +114,7 @@ export type MangoClientOptions = {
|
|||
txConfirmationCommitment?: Commitment;
|
||||
openbookFeesToDao?: boolean;
|
||||
prependedGlobalAdditionalInstructions?: TransactionInstruction[];
|
||||
multipleConnections?: Connection[];
|
||||
};
|
||||
|
||||
export class MangoClient {
|
||||
|
@ -124,6 +125,7 @@ export class MangoClient {
|
|||
private txConfirmationCommitment: Commitment;
|
||||
private openbookFeesToDao: boolean;
|
||||
private prependedGlobalAdditionalInstructions: TransactionInstruction[] = [];
|
||||
private multipleProviders: AnchorProvider[] = [];
|
||||
|
||||
constructor(
|
||||
public program: Program<MangoV4>,
|
||||
|
@ -144,6 +146,16 @@ export class MangoClient {
|
|||
'processed';
|
||||
// TODO: evil side effect, but limited backtraces are a nightmare
|
||||
Error.stackTraceLimit = 1000;
|
||||
this.multipleProviders = opts?.multipleConnections
|
||||
? opts.multipleConnections.map(
|
||||
(c) =>
|
||||
new AnchorProvider(
|
||||
c,
|
||||
new Wallet(new Keypair()),
|
||||
(program.provider as AnchorProvider).opts,
|
||||
),
|
||||
)
|
||||
: [];
|
||||
}
|
||||
|
||||
/// Convenience accessors
|
||||
|
@ -159,6 +171,40 @@ export class MangoClient {
|
|||
public async sendAndConfirmTransaction(
|
||||
ixs: TransactionInstruction[],
|
||||
opts: any = {},
|
||||
): Promise<MangoSignatureStatus> {
|
||||
let prioritizationFee: number;
|
||||
if (opts.prioritizationFee) {
|
||||
prioritizationFee = opts.prioritizationFee;
|
||||
} else if (this.estimateFee) {
|
||||
prioritizationFee = await this.estimatePrioritizationFee(ixs);
|
||||
} else {
|
||||
prioritizationFee = this.prioritizationFee;
|
||||
}
|
||||
const providers = [
|
||||
this.program.provider as AnchorProvider,
|
||||
...this.multipleProviders,
|
||||
];
|
||||
const status = await Promise.race(
|
||||
providers.map((p) =>
|
||||
sendTransaction(
|
||||
p,
|
||||
[...this.prependedGlobalAdditionalInstructions, ...ixs],
|
||||
opts.alts ?? [],
|
||||
{
|
||||
postSendTxCallback: this.postSendTxCallback,
|
||||
prioritizationFee,
|
||||
txConfirmationCommitment: this.txConfirmationCommitment,
|
||||
...opts,
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
return status;
|
||||
}
|
||||
|
||||
public async sendAndConfirmTransactionSingle(
|
||||
ixs: TransactionInstruction[],
|
||||
opts: any = {},
|
||||
): Promise<MangoSignatureStatus> {
|
||||
let prioritizationFee: number;
|
||||
if (opts.prioritizationFee) {
|
||||
|
@ -3618,7 +3664,7 @@ export class MangoClient {
|
|||
[
|
||||
...preInstructions,
|
||||
flashLoanBeginIx,
|
||||
...userDefinedInstructions.filter((ix) => ix.keys.length > 2),
|
||||
...userDefinedInstructions,
|
||||
flashLoanEndIx,
|
||||
],
|
||||
{ alts: [...group.addressLookupTablesList, ...userDefinedAlts] },
|
||||
|
@ -3719,6 +3765,31 @@ export class MangoClient {
|
|||
pricePremium: number | null,
|
||||
expiryTimestamp: number | null,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
const ixs = await this.tcsTakeProfitOnDepositIx(
|
||||
group,
|
||||
account,
|
||||
sellBank,
|
||||
buyBank,
|
||||
thresholdPrice,
|
||||
thresholdPriceInSellPerBuyToken,
|
||||
maxSell,
|
||||
pricePremium,
|
||||
expiryTimestamp,
|
||||
);
|
||||
return await this.sendAndConfirmTransactionForGroup(group, ixs);
|
||||
}
|
||||
|
||||
public async tcsTakeProfitOnDepositIx(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
sellBank: Bank,
|
||||
buyBank: Bank,
|
||||
thresholdPrice: number,
|
||||
thresholdPriceInSellPerBuyToken: boolean,
|
||||
maxSell: number | null,
|
||||
pricePremium: number | null,
|
||||
expiryTimestamp: number | null,
|
||||
): Promise<TransactionInstruction[]> {
|
||||
if (account.getTokenBalanceUi(sellBank) < 0) {
|
||||
throw new Error(
|
||||
`Only allowed to take profits on deposits! Current balance ${account.getTokenBalanceUi(
|
||||
|
@ -3738,7 +3809,7 @@ export class MangoClient {
|
|||
const lowerLimit = 0;
|
||||
const upperLimit = thresholdPriceNativeNative;
|
||||
|
||||
return await this.tokenConditionalSwapCreate(
|
||||
return await this.tokenConditionalSwapCreateIx(
|
||||
group,
|
||||
account,
|
||||
sellBank,
|
||||
|
@ -3767,6 +3838,31 @@ export class MangoClient {
|
|||
pricePremium: number | null,
|
||||
expiryTimestamp: number | null,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
const ixs = await this.tcsStopLossOnDepositIx(
|
||||
group,
|
||||
account,
|
||||
sellBank,
|
||||
buyBank,
|
||||
thresholdPrice,
|
||||
thresholdPriceInSellPerBuyToken,
|
||||
maxSell,
|
||||
pricePremium,
|
||||
expiryTimestamp,
|
||||
);
|
||||
return await this.sendAndConfirmTransactionForGroup(group, ixs);
|
||||
}
|
||||
|
||||
public async tcsStopLossOnDepositIx(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
sellBank: Bank,
|
||||
buyBank: Bank,
|
||||
thresholdPrice: number,
|
||||
thresholdPriceInSellPerBuyToken: boolean,
|
||||
maxSell: number | null,
|
||||
pricePremium: number | null,
|
||||
expiryTimestamp: number | null,
|
||||
): Promise<TransactionInstruction[]> {
|
||||
if (account.getTokenBalanceUi(sellBank) < 0) {
|
||||
throw new Error(
|
||||
`Only allowed to set a stop loss on deposits! Current balance ${account.getTokenBalanceUi(
|
||||
|
@ -3786,7 +3882,7 @@ export class MangoClient {
|
|||
const lowerLimit = thresholdPriceNativeNative;
|
||||
const upperLimit = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
return await this.tokenConditionalSwapCreate(
|
||||
return await this.tokenConditionalSwapCreateIx(
|
||||
group,
|
||||
account,
|
||||
sellBank,
|
||||
|
@ -3816,6 +3912,33 @@ export class MangoClient {
|
|||
allowMargin: boolean | null,
|
||||
expiryTimestamp: number | null,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
const ixs = await this.tcsTakeProfitOnBorrowIx(
|
||||
group,
|
||||
account,
|
||||
sellBank,
|
||||
buyBank,
|
||||
thresholdPrice,
|
||||
thresholdPriceInSellPerBuyToken,
|
||||
maxBuyUi,
|
||||
pricePremium,
|
||||
allowMargin,
|
||||
expiryTimestamp,
|
||||
);
|
||||
return await this.sendAndConfirmTransactionForGroup(group, ixs);
|
||||
}
|
||||
|
||||
public async tcsTakeProfitOnBorrowIx(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
sellBank: Bank,
|
||||
buyBank: Bank,
|
||||
thresholdPrice: number,
|
||||
thresholdPriceInSellPerBuyToken: boolean,
|
||||
maxBuyUi: number | null,
|
||||
pricePremium: number | null,
|
||||
allowMargin: boolean | null,
|
||||
expiryTimestamp: number | null,
|
||||
): Promise<TransactionInstruction[]> {
|
||||
if (account.getTokenBalanceUi(buyBank) > 0) {
|
||||
throw new Error(
|
||||
`Only allowed to take profits on borrows! Current balance ${account.getTokenBalanceUi(
|
||||
|
@ -3835,7 +3958,7 @@ export class MangoClient {
|
|||
const lowerLimit = 0;
|
||||
const upperLimit = thresholdPriceNativeNative;
|
||||
|
||||
return await this.tokenConditionalSwapCreate(
|
||||
return await this.tokenConditionalSwapCreateIx(
|
||||
group,
|
||||
account,
|
||||
sellBank,
|
||||
|
@ -3865,6 +3988,33 @@ export class MangoClient {
|
|||
allowMargin: boolean | null,
|
||||
expiryTimestamp: number | null,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
const ixs = await this.tcsStopLossOnBorrowIx(
|
||||
group,
|
||||
account,
|
||||
sellBank,
|
||||
buyBank,
|
||||
thresholdPrice,
|
||||
thresholdPriceInSellPerBuyToken,
|
||||
maxBuyUi,
|
||||
pricePremium,
|
||||
allowMargin,
|
||||
expiryTimestamp,
|
||||
);
|
||||
return await this.sendAndConfirmTransactionForGroup(group, ixs);
|
||||
}
|
||||
|
||||
public async tcsStopLossOnBorrowIx(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
sellBank: Bank,
|
||||
buyBank: Bank,
|
||||
thresholdPrice: number,
|
||||
thresholdPriceInSellPerBuyToken: boolean,
|
||||
maxBuyUi: number | null,
|
||||
pricePremium: number | null,
|
||||
allowMargin: boolean | null,
|
||||
expiryTimestamp: number | null,
|
||||
): Promise<TransactionInstruction[]> {
|
||||
if (account.getTokenBalanceUi(buyBank) > 0) {
|
||||
throw new Error(
|
||||
`Only allowed to set stop loss on borrows! Current balance ${account.getTokenBalanceUi(
|
||||
|
@ -3884,7 +4034,7 @@ export class MangoClient {
|
|||
const lowerLimit = thresholdPriceNativeNative;
|
||||
const upperLimit = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
return await this.tokenConditionalSwapCreate(
|
||||
return await this.tokenConditionalSwapCreateIx(
|
||||
group,
|
||||
account,
|
||||
sellBank,
|
||||
|
@ -3902,6 +4052,81 @@ export class MangoClient {
|
|||
);
|
||||
}
|
||||
|
||||
public async tokenConditionalSwapCreateIx(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
sellBank: Bank,
|
||||
buyBank: Bank,
|
||||
lowerLimitNativeNative: number,
|
||||
upperLimitNativeNative: number,
|
||||
maxBuy: number,
|
||||
maxSell: number,
|
||||
tcsIntention:
|
||||
| 'TakeProfitOnDeposit'
|
||||
| 'StopLossOnDeposit'
|
||||
| 'TakeProfitOnBorrow'
|
||||
| 'StopLossOnBorrow'
|
||||
| null,
|
||||
pricePremium: number | null,
|
||||
allowCreatingDeposits: boolean,
|
||||
allowCreatingBorrows: boolean,
|
||||
expiryTimestamp: number | null,
|
||||
displayPriceInSellTokenPerBuyToken: boolean,
|
||||
): Promise<TransactionInstruction[]> {
|
||||
const maxBuyNative =
|
||||
maxBuy == Number.MAX_SAFE_INTEGER
|
||||
? U64_MAX_BN
|
||||
: toNative(maxBuy, buyBank.mintDecimals);
|
||||
const maxSellNative =
|
||||
maxSell == Number.MAX_SAFE_INTEGER
|
||||
? U64_MAX_BN
|
||||
: toNative(maxSell, sellBank.mintDecimals);
|
||||
pricePremium = TokenConditionalSwap.computePremium(
|
||||
group,
|
||||
buyBank,
|
||||
sellBank,
|
||||
maxBuyNative,
|
||||
maxSellNative,
|
||||
maxBuy,
|
||||
maxSell,
|
||||
);
|
||||
const pricePremiumRate = pricePremium > 0 ? pricePremium / 100 : 0.03;
|
||||
|
||||
let intention: TokenConditionalSwapIntention;
|
||||
switch (tcsIntention) {
|
||||
case 'StopLossOnBorrow':
|
||||
case 'StopLossOnDeposit':
|
||||
intention = TokenConditionalSwapIntention.stopLoss;
|
||||
break;
|
||||
case 'TakeProfitOnBorrow':
|
||||
case 'TakeProfitOnDeposit':
|
||||
intention = TokenConditionalSwapIntention.takeProfit;
|
||||
break;
|
||||
default:
|
||||
intention = TokenConditionalSwapIntention.unknown;
|
||||
break;
|
||||
}
|
||||
|
||||
return await this.tokenConditionalSwapCreateRawIx(
|
||||
group,
|
||||
account,
|
||||
buyBank.mint,
|
||||
sellBank.mint,
|
||||
maxBuyNative,
|
||||
maxSellNative,
|
||||
expiryTimestamp,
|
||||
lowerLimitNativeNative,
|
||||
upperLimitNativeNative,
|
||||
pricePremiumRate,
|
||||
allowCreatingDeposits,
|
||||
allowCreatingBorrows,
|
||||
displayPriceInSellTokenPerBuyToken
|
||||
? TokenConditionalSwapDisplayPriceStyle.sellTokenPerBuyToken
|
||||
: TokenConditionalSwapDisplayPriceStyle.buyTokenPerSellToken,
|
||||
intention,
|
||||
);
|
||||
}
|
||||
|
||||
public async tokenConditionalSwapCreate(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
|
@ -3977,7 +4202,7 @@ export class MangoClient {
|
|||
);
|
||||
}
|
||||
|
||||
public async tokenConditionalSwapCreateLinearAuction(
|
||||
public async tokenConditionalSwapCreateLinearAuctionIx(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
sellBank: Bank,
|
||||
|
@ -3992,7 +4217,7 @@ export class MangoClient {
|
|||
startTimestamp: number,
|
||||
durationSeconds: number,
|
||||
expiryTimestamp: number | null,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
): Promise<TransactionInstruction[]> {
|
||||
let maxBuyNative, maxSellNative;
|
||||
if (maxBuy == Number.MAX_SAFE_INTEGER) {
|
||||
maxBuyNative = U64_MAX_BN;
|
||||
|
@ -4056,10 +4281,45 @@ export class MangoClient {
|
|||
}
|
||||
ixs.push(tcsIx);
|
||||
|
||||
return ixs;
|
||||
}
|
||||
|
||||
public async tokenConditionalSwapCreateLinearAuction(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
sellBank: Bank,
|
||||
buyBank: Bank,
|
||||
priceStart: number,
|
||||
priceEnd: number,
|
||||
maxBuy: number,
|
||||
maxSell: number,
|
||||
allowCreatingDeposits: boolean,
|
||||
allowCreatingBorrows: boolean,
|
||||
displayPriceInSellTokenPerBuyToken: boolean,
|
||||
startTimestamp: number,
|
||||
durationSeconds: number,
|
||||
expiryTimestamp: number | null,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
const ixs = await this.tokenConditionalSwapCreateLinearAuctionIx(
|
||||
group,
|
||||
account,
|
||||
sellBank,
|
||||
buyBank,
|
||||
priceStart,
|
||||
priceEnd,
|
||||
maxBuy,
|
||||
maxSell,
|
||||
allowCreatingDeposits,
|
||||
allowCreatingBorrows,
|
||||
displayPriceInSellTokenPerBuyToken,
|
||||
startTimestamp,
|
||||
durationSeconds,
|
||||
expiryTimestamp,
|
||||
);
|
||||
return await this.sendAndConfirmTransactionForGroup(group, ixs);
|
||||
}
|
||||
|
||||
public async tokenConditionalSwapCreatePremiumAuction(
|
||||
public async tokenConditionalSwapCreatePremiumAuctionIx(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
sellBank: Bank,
|
||||
|
@ -4080,7 +4340,7 @@ export class MangoClient {
|
|||
expiryTimestamp: number | null,
|
||||
displayPriceInSellTokenPerBuyToken: boolean,
|
||||
durationSeconds: number,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
): Promise<TransactionInstruction[]> {
|
||||
const lowerLimitNative = toNativeSellPerBuyTokenPrice(
|
||||
lowerLimit,
|
||||
sellBank,
|
||||
|
@ -4190,6 +4450,47 @@ export class MangoClient {
|
|||
}
|
||||
ixs.push(tcsIx);
|
||||
|
||||
return ixs;
|
||||
}
|
||||
public async tokenConditionalSwapCreatePremiumAuction(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
sellBank: Bank,
|
||||
buyBank: Bank,
|
||||
lowerLimit: number,
|
||||
upperLimit: number,
|
||||
maxBuy: number,
|
||||
maxSell: number,
|
||||
tcsIntention:
|
||||
| 'TakeProfitOnDeposit'
|
||||
| 'StopLossOnDeposit'
|
||||
| 'TakeProfitOnBorrow'
|
||||
| 'StopLossOnBorrow'
|
||||
| null,
|
||||
maxPricePremiumPercent: number | null,
|
||||
allowCreatingDeposits: boolean,
|
||||
allowCreatingBorrows: boolean,
|
||||
expiryTimestamp: number | null,
|
||||
displayPriceInSellTokenPerBuyToken: boolean,
|
||||
durationSeconds: number,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
const ixs = await this.tokenConditionalSwapCreatePremiumAuctionIx(
|
||||
group,
|
||||
account,
|
||||
sellBank,
|
||||
buyBank,
|
||||
lowerLimit,
|
||||
upperLimit,
|
||||
maxBuy,
|
||||
maxSell,
|
||||
tcsIntention,
|
||||
maxPricePremiumPercent,
|
||||
allowCreatingDeposits,
|
||||
allowCreatingBorrows,
|
||||
expiryTimestamp,
|
||||
displayPriceInSellTokenPerBuyToken,
|
||||
durationSeconds,
|
||||
);
|
||||
return await this.sendAndConfirmTransactionForGroup(group, ixs);
|
||||
}
|
||||
|
||||
|
@ -4209,6 +4510,41 @@ export class MangoClient {
|
|||
priceDisplayStyle: TokenConditionalSwapDisplayPriceStyle,
|
||||
intention: TokenConditionalSwapIntention,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
const ixs = await this.tokenConditionalSwapCreateRawIx(
|
||||
group,
|
||||
account,
|
||||
buyMintPk,
|
||||
sellMintPk,
|
||||
maxBuy,
|
||||
maxSell,
|
||||
expiryTimestamp,
|
||||
priceLowerLimit,
|
||||
priceUpperLimit,
|
||||
pricePremiumRate,
|
||||
allowCreatingDeposits,
|
||||
allowCreatingBorrows,
|
||||
priceDisplayStyle,
|
||||
intention,
|
||||
);
|
||||
return await this.sendAndConfirmTransactionForGroup(group, ixs);
|
||||
}
|
||||
|
||||
public async tokenConditionalSwapCreateRawIx(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
buyMintPk: PublicKey,
|
||||
sellMintPk: PublicKey,
|
||||
maxBuy: BN,
|
||||
maxSell: BN,
|
||||
expiryTimestamp: number | null,
|
||||
priceLowerLimit: number,
|
||||
priceUpperLimit: number,
|
||||
pricePremiumRate: number,
|
||||
allowCreatingDeposits: boolean,
|
||||
allowCreatingBorrows: boolean,
|
||||
priceDisplayStyle: TokenConditionalSwapDisplayPriceStyle,
|
||||
intention: TokenConditionalSwapIntention,
|
||||
): Promise<TransactionInstruction[]> {
|
||||
const buyBank: Bank = group.getFirstBankByMint(buyMintPk);
|
||||
const sellBank: Bank = group.getFirstBankByMint(sellMintPk);
|
||||
const tcsIx = await this.program.methods
|
||||
|
@ -4249,14 +4585,14 @@ export class MangoClient {
|
|||
}
|
||||
ixs.push(tcsIx);
|
||||
|
||||
return await this.sendAndConfirmTransactionForGroup(group, ixs);
|
||||
return ixs;
|
||||
}
|
||||
|
||||
public async tokenConditionalSwapCancel(
|
||||
public async tokenConditionalSwapCancelIx(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
tokenConditionalSwapId: BN,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
): Promise<TransactionInstruction> {
|
||||
const tokenConditionalSwapIndex = account.tokenConditionalSwaps.findIndex(
|
||||
(tcs) => tcs.id.eq(tokenConditionalSwapId),
|
||||
);
|
||||
|
@ -4268,7 +4604,7 @@ export class MangoClient {
|
|||
const buyBank = group.banksMapByTokenIndex.get(tcs.buyTokenIndex)![0];
|
||||
const sellBank = group.banksMapByTokenIndex.get(tcs.sellTokenIndex)![0];
|
||||
|
||||
const ix = await this.program.methods
|
||||
return this.program.methods
|
||||
.tokenConditionalSwapCancel(
|
||||
tokenConditionalSwapIndex,
|
||||
new BN(tokenConditionalSwapId),
|
||||
|
@ -4281,7 +4617,18 @@ export class MangoClient {
|
|||
sellBank: sellBank.publicKey,
|
||||
})
|
||||
.instruction();
|
||||
}
|
||||
|
||||
public async tokenConditionalSwapCancel(
|
||||
group: Group,
|
||||
account: MangoAccount,
|
||||
tokenConditionalSwapId: BN,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
const ix = await this.tokenConditionalSwapCancelIx(
|
||||
group,
|
||||
account,
|
||||
tokenConditionalSwapId,
|
||||
);
|
||||
return await this.sendAndConfirmTransactionForGroup(group, [ix]);
|
||||
}
|
||||
|
||||
|
@ -4322,6 +4669,26 @@ export class MangoClient {
|
|||
maxBuyTokenToLiqee: number,
|
||||
maxSellTokenToLiqor: number,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
const ix = await this.tokenConditionalSwapTriggerIx(
|
||||
group,
|
||||
liqee,
|
||||
liqor,
|
||||
tokenConditionalSwapId,
|
||||
maxBuyTokenToLiqee,
|
||||
maxSellTokenToLiqor,
|
||||
);
|
||||
|
||||
return await this.sendAndConfirmTransactionForGroup(group, [ix]);
|
||||
}
|
||||
|
||||
public async tokenConditionalSwapTriggerIx(
|
||||
group: Group,
|
||||
liqee: MangoAccount,
|
||||
liqor: MangoAccount,
|
||||
tokenConditionalSwapId: BN,
|
||||
maxBuyTokenToLiqee: number,
|
||||
maxSellTokenToLiqor: number,
|
||||
): Promise<TransactionInstruction> {
|
||||
const tokenConditionalSwapIndex = liqee.tokenConditionalSwaps.findIndex(
|
||||
(tcs) => tcs.id.eq(tokenConditionalSwapId),
|
||||
);
|
||||
|
@ -4353,7 +4720,7 @@ export class MangoClient {
|
|||
} as AccountMeta),
|
||||
);
|
||||
|
||||
const ix = await this.program.methods
|
||||
return this.program.methods
|
||||
.tokenConditionalSwapTrigger(
|
||||
tokenConditionalSwapIndex,
|
||||
new BN(tokenConditionalSwapId),
|
||||
|
@ -4369,8 +4736,6 @@ export class MangoClient {
|
|||
})
|
||||
.remainingAccounts(parsedHealthAccounts)
|
||||
.instruction();
|
||||
|
||||
return await this.sendAndConfirmTransactionForGroup(group, [ix]);
|
||||
}
|
||||
|
||||
public async altSet(
|
||||
|
|
|
@ -2,6 +2,9 @@ import { Connection } from '@solana/web3.js';
|
|||
import { JUPITER } from './constants';
|
||||
|
||||
export enum TransactionErrors {
|
||||
MangoNoFreeTokenPositionIndex,
|
||||
MangoNoFreeSerum3OpenOrdersIndex,
|
||||
MangoNoFreePerpPositionIndex,
|
||||
// Slippage incurred was higher than user expected
|
||||
JupiterSlippageToleranceExceeded,
|
||||
Unknown,
|
||||
|
@ -25,6 +28,25 @@ export async function parseTxForKnownErrors(
|
|||
});
|
||||
|
||||
if (tx && tx.meta && tx.meta.logMessages) {
|
||||
if (
|
||||
tx.meta.logMessages.some((msg) =>
|
||||
msg.includes('NoFreeTokenPositionIndex'),
|
||||
)
|
||||
) {
|
||||
return TransactionErrors.MangoNoFreeTokenPositionIndex;
|
||||
}
|
||||
if (
|
||||
tx.meta.logMessages.some((msg) =>
|
||||
msg.includes('NoFreeSerum3OpenOrdersIndex'),
|
||||
)
|
||||
) {
|
||||
return TransactionErrors.MangoNoFreeSerum3OpenOrdersIndex;
|
||||
}
|
||||
if (
|
||||
tx.meta.logMessages.some((msg) => msg.includes('NoFreePerpPositionIndex'))
|
||||
) {
|
||||
return TransactionErrors.MangoNoFreePerpPositionIndex;
|
||||
}
|
||||
if (
|
||||
tx.meta.logMessages.some((msg) =>
|
||||
msg.includes('SlippageToleranceExceeded'),
|
||||
|
|
|
@ -4,6 +4,7 @@ import { MangoClient } from './client';
|
|||
import { MANGO_V4_ID } from './constants';
|
||||
|
||||
export * from './accounts/bank';
|
||||
export * from './accounts/oracle';
|
||||
export * from './accounts/mangoAccount';
|
||||
export * from './accounts/perp';
|
||||
export {
|
||||
|
|
|
@ -461,20 +461,25 @@ export async function getRiskStats(
|
|||
.map((bank) => bank.mint.toString()),
|
||||
),
|
||||
];
|
||||
const prices = await getOnChainPriceForMints([
|
||||
...new Set(
|
||||
Array.from(group.banksMapByTokenIndex.values())
|
||||
.flat()
|
||||
.map((bank) => bank.mint.toString()),
|
||||
),
|
||||
]);
|
||||
const onChainPrices = Object.fromEntries(
|
||||
prices.map((price, i) => [mints[i], price]),
|
||||
);
|
||||
|
||||
// Note:
|
||||
// Disable for now
|
||||
// Getting rate limited
|
||||
// const prices = await getOnChainPriceForMints([
|
||||
// ...new Set(
|
||||
// Array.from(group.banksMapByTokenIndex.values())
|
||||
// .flat()
|
||||
// .map((bank) => bank.mint.toString()),
|
||||
// ),
|
||||
// ]);
|
||||
// const onChainPrices = Object.fromEntries(
|
||||
// prices.map((price, i) => [mints[i], price]),
|
||||
// );
|
||||
|
||||
Array.from(group.banksMapByTokenIndex.values())
|
||||
.flat()
|
||||
.forEach((b) => {
|
||||
b['onChainPrice'] = onChainPrices[b.mint.toBase58()];
|
||||
b['onChainPrice'] = b.uiPrice;
|
||||
});
|
||||
|
||||
// Clone group, and simulate change % price drop for all assets except stables
|
||||
|
|
|
@ -5,6 +5,8 @@ import {
|
|||
AddressLookupTableAccount,
|
||||
ComputeBudgetProgram,
|
||||
MessageV0,
|
||||
RpcResponseAndContext,
|
||||
SignatureResult,
|
||||
Signer,
|
||||
TransactionConfirmationStatus,
|
||||
TransactionError,
|
||||
|
@ -15,11 +17,11 @@ import {
|
|||
import { COMPUTE_BUDGET_PROGRAM_ID } from '../constants';
|
||||
|
||||
export interface MangoSignatureStatus {
|
||||
slot: number;
|
||||
confirmations: number | null;
|
||||
err: TransactionError | null;
|
||||
confirmations?: number | null;
|
||||
confirmationStatus?: TransactionConfirmationStatus;
|
||||
err: TransactionError | null;
|
||||
signature: TransactionSignature;
|
||||
slot: number;
|
||||
}
|
||||
|
||||
export async function sendTransaction(
|
||||
|
@ -96,13 +98,6 @@ export async function sendTransaction(
|
|||
skipPreflight: true, // mergedOpts.skipPreflight,
|
||||
});
|
||||
|
||||
// const signature = await connection.sendTransactionss(
|
||||
// vtx as any as VersionedTransaction,
|
||||
// {
|
||||
// skipPreflight: true,
|
||||
// },
|
||||
// );
|
||||
|
||||
if (opts.postSendTxCallback) {
|
||||
try {
|
||||
opts.postSendTxCallback({ txid: signature });
|
||||
|
@ -112,28 +107,27 @@ export async function sendTransaction(
|
|||
}
|
||||
|
||||
const txConfirmationCommitment = opts.txConfirmationCommitment ?? 'processed';
|
||||
let status: any;
|
||||
let status: RpcResponseAndContext<SignatureResult>;
|
||||
if (
|
||||
latestBlockhash.blockhash != null &&
|
||||
latestBlockhash.lastValidBlockHeight != null
|
||||
) {
|
||||
status = (
|
||||
await connection.confirmTransaction(
|
||||
{
|
||||
signature: signature,
|
||||
blockhash: latestBlockhash.blockhash,
|
||||
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
||||
},
|
||||
txConfirmationCommitment,
|
||||
)
|
||||
).value;
|
||||
status = await connection.confirmTransaction(
|
||||
{
|
||||
signature: signature,
|
||||
blockhash: latestBlockhash.blockhash,
|
||||
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
||||
},
|
||||
txConfirmationCommitment,
|
||||
);
|
||||
} else {
|
||||
status = (
|
||||
await connection.confirmTransaction(signature, txConfirmationCommitment)
|
||||
).value;
|
||||
status = await connection.confirmTransaction(
|
||||
signature,
|
||||
txConfirmationCommitment,
|
||||
);
|
||||
}
|
||||
|
||||
if (status.err) {
|
||||
const signatureResult = status.value;
|
||||
if (signatureResult.err) {
|
||||
console.warn('Tx status: ', status);
|
||||
throw new MangoError({
|
||||
txid: signature,
|
||||
|
@ -141,7 +135,7 @@ export async function sendTransaction(
|
|||
});
|
||||
}
|
||||
|
||||
return { signature, ...status };
|
||||
return { signature, slot: status.context.slot, ...signatureResult };
|
||||
}
|
||||
|
||||
export const createComputeBudgetIx = (
|
||||
|
|
Loading…
Reference in New Issue