import { AnchorProvider, Wallet } from '@coral-xyz/anchor'; import { AddressLookupTableProgram, Cluster, Connection, Keypair, PublicKey, } from '@solana/web3.js'; import fs from 'fs'; import { MangoClient } from '../../src/client'; import { DefaultTokenRegisterParams } from '../../src/clientIxParamBuilder'; import { MANGO_V4_ID } from '../../src/constants'; // // Script which depoys a new mango group, and registers 3 tokens // with stub oracles // // default to group 1, to not conflict with the normal group const GROUP_NUM = Number(process.env.GROUP_NUM || 200); const CLUSTER = process.env.CLUSTER || 'mainnet-beta'; const MINTS = JSON.parse(process.env.MINTS || '').map((s) => new PublicKey(s)); const SERUM_MARKETS = JSON.parse(process.env.SERUM_MARKETS || '').map( (s) => new PublicKey(s), ); const MAINNET_MINTS = new Map([ ['USDC', MINTS[0]], ['ETH', MINTS[1]], ['SOL', MINTS[2]], ['MNGO', MINTS[3]], ['MSOL', MINTS[4]], ]); const STUB_PRICES = new Map([ ['USDC', 1.0], ['ETH', 1200.0], // eth and usdc both have 6 decimals ['SOL', 0.015], // sol has 9 decimals, equivalent to $15 per SOL ['MNGO', 0.02], ['MSOL', 0.017], ]); const MIN_VAULT_TO_DEPOSITS_RATIO = 0.2; const NET_BORROWS_WINDOW_SIZE_TS = 24 * 60 * 60; const NET_BORROWS_LIMIT_NATIVE = 1 * Math.pow(10, 7) * Math.pow(10, 6); async function main(): Promise { const options = AnchorProvider.defaultOptions(); options.commitment = 'processed'; options.preflightCommitment = 'finalized'; const connection = new Connection(process.env.CLUSTER_URL!, options); const admin = Keypair.fromSecretKey( Buffer.from( JSON.parse(fs.readFileSync(process.env.PAYER_KEYPAIR!, 'utf-8')), ), ); const adminWallet = new Wallet(admin); console.log(`Admin ${adminWallet.publicKey.toBase58()}`); const adminProvider = new AnchorProvider(connection, adminWallet, options); const client = await MangoClient.connect( adminProvider, CLUSTER as Cluster, MANGO_V4_ID[CLUSTER], { idsSource: 'get-program-accounts', prioritizationFee: 100, txConfirmationCommitment: 'confirmed', }, ); // group console.log(`Creating Group...`); try { const insuranceMint = new PublicKey(MAINNET_MINTS.get('USDC')!); await client.groupCreate(GROUP_NUM, true, 0, insuranceMint); } catch (error) { console.log(error); } const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM); console.log(`...registered group ${group.publicKey}`); // stub oracles const oracles = new Map(); for (const [name, mint] of MAINNET_MINTS) { console.log(`Creating stub oracle for ${name}...`); const mintPk = new PublicKey(mint); if ((await client.getStubOracle(group, mintPk)).length == 0) { try { const price = STUB_PRICES.get(name)!; await client.stubOracleCreate(group, mintPk, price); } catch (error) { console.log(error); } } const oracle = (await client.getStubOracle(group, mintPk))[0]; console.log(`...created stub oracle ${oracle.publicKey}`); oracles.set(name, oracle.publicKey); } const defaultOracleConfig = { confFilter: 0.1, maxStalenessSlots: null, }; const defaultInterestRate = { adjustmentFactor: 0.01, util0: 0.4, rate0: 0.07, util1: 0.8, rate1: 0.9, maxRate: 1.5, }; const noFallbackOracle = PublicKey.default; // register token 0 console.log(`Registering USDC...`); const usdcMint = new PublicKey(MAINNET_MINTS.get('USDC')!); const usdcOracle = oracles.get('USDC'); try { await client.tokenRegister( group, usdcMint, usdcOracle, noFallbackOracle, 0, 'USDC', { ...DefaultTokenRegisterParams, loanOriginationFeeRate: 0, loanFeeRate: 0.0001, initAssetWeight: 1, maintAssetWeight: 1, initLiabWeight: 1, maintLiabWeight: 1, liquidationFee: 0, netBorrowLimitPerWindowQuote: NET_BORROWS_LIMIT_NATIVE, }, ); await group.reloadAll(client); } catch (error) { console.log(error); } // register token 1 console.log(`Registering ETH...`); const ethMint = new PublicKey(MAINNET_MINTS.get('ETH')!); const ethOracle = oracles.get('ETH'); try { await client.tokenRegister( group, ethMint, ethOracle, noFallbackOracle, 1, 'ETH', { ...DefaultTokenRegisterParams, loanOriginationFeeRate: 0, loanFeeRate: 0.0001, maintAssetWeight: 0.9, initAssetWeight: 0.8, maintLiabWeight: 1.1, initLiabWeight: 1.2, liquidationFee: 0.05, netBorrowLimitPerWindowQuote: NET_BORROWS_LIMIT_NATIVE, }, ); await group.reloadAll(client); } catch (error) { console.log(error); } // register token 2 console.log(`Registering SOL...`); const solMint = new PublicKey(MAINNET_MINTS.get('SOL')!); const solOracle = oracles.get('SOL'); try { await client.tokenRegister( group, solMint, solOracle, noFallbackOracle, 2, // tokenIndex 'SOL', { ...DefaultTokenRegisterParams, loanOriginationFeeRate: 0, loanFeeRate: 0.0001, maintAssetWeight: 0.9, initAssetWeight: 0.8, maintLiabWeight: 1.1, initLiabWeight: 1.2, liquidationFee: 0.05, netBorrowLimitPerWindowQuote: NET_BORROWS_LIMIT_NATIVE, }, ); await group.reloadAll(client); } catch (error) { console.log(error); } const genericBanks = ['MNGO', 'MSOL']; let nextTokenIndex = 3; for (const name of genericBanks) { console.log(`Registering ${name}...`); const mint = new PublicKey(MAINNET_MINTS.get(name)!); const oracle = oracles.get(name); try { await client.tokenRegister( group, mint, oracle, noFallbackOracle, nextTokenIndex, name, { ...DefaultTokenRegisterParams, loanOriginationFeeRate: 0, loanFeeRate: 0.0001, maintAssetWeight: 0.9, initAssetWeight: 0.8, maintLiabWeight: 1.1, initLiabWeight: 1.2, liquidationFee: 0.05, netBorrowLimitPerWindowQuote: NET_BORROWS_LIMIT_NATIVE, }, ); nextTokenIndex += 1; await group.reloadAll(client); } catch (error) { console.log(error); } } // log tokens/banks for (const bank of await group.banksMapByMint.values()) { console.log(`${bank.toString()}`); } let nextSerumMarketIndex = 0; for (const [name, mint] of MAINNET_MINTS) { if (name == 'USDC') { continue; } console.log(`Registering ${name}/USDC serum market...`); try { await client.serum3RegisterMarket( group, new PublicKey(SERUM_MARKETS[nextSerumMarketIndex]), group.getFirstBankByMint(new PublicKey(mint)), group.getFirstBankByMint(usdcMint), nextSerumMarketIndex, `${name}/USDC`, 0, ); nextSerumMarketIndex += 1; } catch (error) { console.log(error); } } console.log('Registering MNGO-PERP market...'); if (!group.banksMapByMint.get(usdcMint.toString())) { console.log('stopping, no USDC bank'); return; } const mngoOracle = oracles.get('MNGO'); try { await client.perpCreateMarket( group, mngoOracle, 0, 'MNGO-PERP', defaultOracleConfig, 6, 10, 100000, // base lots 0.9, 0.8, 1.1, 1.2, 0.0, 0.0, 0.05, -0.001, 0.002, 0, -0.1, 0.1, 10, false, 0, 0, 0, 0, -1.0, 2 * 60 * 60, 0.025, 0.0, ); } catch (error) { console.log(error); } await createAndPopulateAlt(client, admin); process.exit(); } main(); async function createAndPopulateAlt( client: MangoClient, admin: Keypair, ): Promise { let group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM); const connection = client.program.provider.connection; // Create ALT, and set to group at index 0 if (group.addressLookupTables[0].equals(PublicKey.default)) { try { console.log(`ALT: Creating`); const createIx = AddressLookupTableProgram.createLookupTable({ authority: admin.publicKey, payer: admin.publicKey, recentSlot: await connection.getSlot('finalized'), }); let sig = await client.sendAndConfirmTransaction([createIx[0]]); console.log( `...created ALT ${createIx[1]} https://explorer.solana.com/tx/${sig.signature}`, ); console.log(`ALT: set at index 0 for group...`); sig = await client.altSet(group, createIx[1], 0); console.log(`...https://explorer.solana.com/tx/${sig.signature}`); group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM); } catch (error) { console.log(error); } } async function extendTable(addresses: PublicKey[]): Promise { await group.reloadAll(client); const alt = await client.program.provider.connection.getAddressLookupTable( group.addressLookupTables[0], ); addresses = addresses.filter( (newAddress) => alt.value?.state.addresses && alt.value?.state.addresses.findIndex((addressInALt) => addressInALt.equals(newAddress), ) === -1, ); if (addresses.length === 0) { return; } const extendIx = AddressLookupTableProgram.extendLookupTable({ lookupTable: group.addressLookupTables[0], payer: admin.publicKey, authority: admin.publicKey, addresses, }); const sig = await client.sendAndConfirmTransaction([extendIx]); console.log(`https://explorer.solana.com/tx/${sig.signature}`); } // Extend using mango v4 relevant pub keys try { const bankAddresses = Array.from(group.banksMapByMint.values()) .flat() .map((bank) => [bank.publicKey, bank.oracle, bank.vault]) .flat() .concat( Array.from(group.banksMapByMint.values()) .flat() .map((mintInfo) => mintInfo.publicKey), ); const serum3MarketAddresses = Array.from( group.serum3MarketsMapByExternal.values(), ) .flat() .map((serum3Market) => serum3Market.publicKey); const serum3ExternalMarketAddresses = Array.from( group.serum3ExternalMarketsMap.values(), ) .flat() .map((serum3ExternalMarket) => [ serum3ExternalMarket.publicKey, serum3ExternalMarket.bidsAddress, serum3ExternalMarket.asksAddress, ]) .flat(); const perpMarketAddresses = Array.from( group.perpMarketsMapByMarketIndex.values(), ) .flat() .map((perpMarket) => [ perpMarket.publicKey, perpMarket.oracle, perpMarket.bids, perpMarket.asks, perpMarket.eventQueue, ]) .flat(); console.log(`ALT: extending using mango v4 relevant public keys`); await extendTable(bankAddresses); await extendTable(serum3MarketAddresses); await extendTable(serum3ExternalMarketAddresses); await extendTable(perpMarketAddresses); } catch (error) { console.log(error); } }