diff --git a/README.md b/README.md index 0d5c3fd..0a961da 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,48 @@ # Mango Simulation - test solana cluster by simulating mango markets -This project is use to stress a solana cluster like devnet, testnet or local solana cluster by simulating mango markets. This code requires ids.json which describe mango group for a cluster and accounts.json file which has preconfigured user accounts in mango. +This project is use to stress a solana cluster like devnet, testnet or local +solana cluster by simulating mango markets. This code requires ids.json which +describe mango group for a cluster and accounts.json file which has +preconfigured user accounts in mango. -To create a new configuration for mango for your cluster please check the following project: - +The code then will create transaction request (q) requests per seconds for (n) +seconds per perp market perp user. Each transaction request will contains remove +following instruction CancelAllPerpOrders and two PlacePerpOrder (one for bid +and another for ask). -The code then will create transaction request (q) requests per seconds for (n) seconds per perp market perp user. Each transaction request will contains remove following instruction CancelAllPerpOrders and two PlacePerpOrder (one for bid and another for ask). - -For the best results to avoid limits by quic it is better to fill the argument "identity" of a valid staked validator for the cluster you are testing with. +For the best results to avoid limits by quic it is better to fill the argument +"identity" of a valid staked validator for the cluster you are testing with. ## Build -Install configure-mango -```sh -git clone https://github.com/godmodegalactus/configure_mango.git -cd configure_mango -yarn install -sh scripts/configure_local.sh - -# open a new terminal as the previous one will continue running a solana validator -# this command will hang for around a minute, just wait for it to finish -NB_USERS=50 yarn ts-node index.ts - -``` - -Install mango-simulation ```sh git clone https://github.com/blockworks-foundation/mango-simulation.git cd mango-simulation -cargo build +cargo build --release +``` -# copy over files from configure_mango while you wait for the build to finish -mkdir -p localnet -cp ../configure_mango/ids.json localnet -cp ../configure_mango/accounts.json localnet -cp ../configure_mango/authority.json localnet -cp ../configure_mango/config/validator-identity.json localnet +## Configure cluster + +Install all nodejs dependencies + +```sh +yarn install +``` + +To start a local cluster. This will start a validator with all the data in +config directory. + +```sh +sh configure_cluster/scripts/configure_local.sh +``` + +To create a configuration. This will create two files `ids.json` which contain +cluster information and `account.json` which contains user information. + +```sh +# open a new terminal as the previous one will continue running a solana validator +# this command will hang for around a minute, just wait for it to finish +NB_USERS=50 yarn ts-node configure_cluster/configure_mango_v3.ts ``` ## Run @@ -43,7 +50,7 @@ cp ../configure_mango/config/validator-identity.json localnet To run against your local validator: ```sh -cargo run --bin mango-simulation -- -u http://127.0.0.1:8899 --identity localnet/validator-identity.json --keeper-authority localnet/authority.json --accounts localnet/accounts.json --mango localnet/ids.json --mango-cluster localnet --duration 10 -q 2 --transaction-save-file tlog.csv --block-data-save-file blog.csv +cargo run --bin mango-simulation -- -u http://127.0.0.1:8899 --identity config/validator-identity.json --keeper-authority authority.json --accounts accounts.json --mango ids.json --mango-cluster localnet --duration 10 -q 2 --transaction-save-file tlog.csv --block-data-save-file blog.csv ``` You can also run the simulation against testnet, but you will need to run configure_mango diff --git a/configure_cluster/README.md b/configure_cluster/README.md new file mode 100644 index 0000000..81c6d2a --- /dev/null +++ b/configure_cluster/README.md @@ -0,0 +1,46 @@ +# Configure Mango - A faster and easier way to configure Mango Markets on a cluster + +This code can be used to configure mango group, tokens, spot market, perp markets and oracles on local solana validator and create 50 mango user accounts. +It will create authority.json which is keypair of authority file, accounts.json which contains all user data and ids.json which contains info about mango group. + +The project also contains necessary binary files. You can always update the binary files by compiling from source and replacing the binaries locally. You have to apply bin/mango.patch to your mango repository to use it to create a local cluster. +From the root directory of this project do: + +## How to use + +To install all the dependencies : +```sh +yarn install +``` + +To start a solana test validator +```sh +sh scripts/start_test_validator.sh +``` +Or +To start a local solana validator +```sh +sh scripts/configure_local.sh +``` + +To configure mango +```sh +yarn ts-node index.ts +``` + +To run mango keeper (deprecated) +```sh +yarn ts-node keeper.ts +``` + +To create 50 users and store in the file accounts.json +```sh +ts-node create-users.ts 50 accounts.json +``` + +To refund users in account file with some sols +```sh +ts-node refund_users.ts accounts.json +``` + +Pyth oracle is a mock it is a program which will just rewrite an account with a binary data. \ No newline at end of file diff --git a/configure_cluster/bin/mango-mint.json b/configure_cluster/bin/mango-mint.json new file mode 100644 index 0000000..5d6d0c9 --- /dev/null +++ b/configure_cluster/bin/mango-mint.json @@ -0,0 +1,13 @@ +{ + "pubkey": "MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac", + "account": { + "lamports": 2951730457, + "data": [ + "AAAAAAtrwmw9oIkyCiWWCwghgfcii0hdoI/YwMv3ysQDw2Rnv7V4NXnDEQAGAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "base64" + ], + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "rentEpoch": 354 + } +} \ No newline at end of file diff --git a/configure_cluster/bin/mango.json b/configure_cluster/bin/mango.json new file mode 100644 index 0000000..86b1192 --- /dev/null +++ b/configure_cluster/bin/mango.json @@ -0,0 +1 @@ +[58,245,175,8,29,172,112,246,82,58,159,33,67,70,96,142,201,49,206,2,141,176,61,126,194,219,211,186,64,157,154,80,182,57,102,252,249,87,92,208,23,30,135,230,135,224,66,83,33,200,183,24,186,60,88,107,147,124,205,29,21,90,177,190] \ No newline at end of file diff --git a/configure_cluster/bin/mango.so b/configure_cluster/bin/mango.so new file mode 100755 index 0000000..4379b56 Binary files /dev/null and b/configure_cluster/bin/mango.so differ diff --git a/configure_cluster/bin/mango_logs.json b/configure_cluster/bin/mango_logs.json new file mode 100644 index 0000000..0fb26d1 --- /dev/null +++ b/configure_cluster/bin/mango_logs.json @@ -0,0 +1 @@ +[7,63,139,28,171,130,217,59,234,218,62,230,18,110,107,250,248,157,38,140,144,249,132,3,227,171,69,83,143,133,221,200,79,71,58,223,237,15,99,138,254,212,98,142,85,62,107,155,14,192,247,103,98,211,182,228,4,150,79,174,78,235,4,240] \ No newline at end of file diff --git a/configure_cluster/bin/mango_logs.so b/configure_cluster/bin/mango_logs.so new file mode 100755 index 0000000..bdf5562 Binary files /dev/null and b/configure_cluster/bin/mango_logs.so differ diff --git a/configure_cluster/bin/mango_patch.patch b/configure_cluster/bin/mango_patch.patch new file mode 100644 index 0000000..6285031 --- /dev/null +++ b/configure_cluster/bin/mango_patch.patch @@ -0,0 +1,133 @@ +diff --git a/program/src/ids.rs b/program/src/ids.rs +index 7b4caae..e69de29 100644 +--- a/program/src/ids.rs ++++ b/program/src/ids.rs +@@ -1,28 +0,0 @@ +-pub mod srm_token { +- use solana_program::declare_id; +- #[cfg(feature = "devnet")] +- declare_id!("AvtB6w9xboLwA145E221vhof5TddhqsChYcx7Fy3xVMH"); +- #[cfg(not(feature = "devnet"))] +- declare_id!("SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"); +-} +- +-pub mod msrm_token { +- use solana_program::declare_id; +- #[cfg(feature = "devnet")] +- declare_id!("8DJBo4bF4mHNxobjdax3BL9RMh5o71Jf8UiKsf5C5eVH"); +- #[cfg(not(feature = "devnet"))] +- declare_id!("MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L"); +-} +- +-pub mod mngo_token { +- use solana_program::declare_id; +- #[cfg(feature = "devnet")] +- declare_id!("Bb9bsTQa1bGEtQ5KagGkvSHyuLqDWumFUcRqFusFNJWC"); +- #[cfg(not(feature = "devnet"))] +- declare_id!("MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac"); +-} +- +-pub mod luna_pyth_oracle { +- use solana_program::declare_id; +- declare_id!("5bmWuR1dgP4avtGYMNKLuxumZTVKGgoN2BCMXWDNL9nY"); +-} +diff --git a/program/src/matching.rs b/program/src/matching.rs +index 16088cf..35b4862 100644 +--- a/program/src/matching.rs ++++ b/program/src/matching.rs +@@ -19,7 +19,6 @@ use mango_logs::{mango_emit_stack, ReferralFeeAccrualLog}; + use mango_macro::{Loadable, Pod}; + + use crate::error::{check_assert, MangoError, MangoErrorCode, MangoResult, SourceFileId}; +-use crate::ids::mngo_token; + use crate::queue::{EventQueue, FillEvent, OutEvent}; + use crate::state::{ + DataType, MangoAccount, MangoCache, MangoGroup, MetaData, PerpMarket, PerpMarketCache, +@@ -2063,10 +2062,7 @@ fn determine_ref_vars<'a>( + referrer_mango_account_ai: Option<&'a AccountInfo>, + now_ts: u64, + ) -> MangoResult<(I80F48, Option>)> { +- let mngo_index = match mango_group.find_token_index(&mngo_token::id()) { +- None => return Ok((ZERO_I80F48, None)), +- Some(i) => i, +- }; ++ let mngo_index = 0; + + let mngo_cache = &mango_cache.root_bank_cache[mngo_index]; + let tier_2_enabled = mango_group.ref_surcharge_centibps_tier_2 != 0 +diff --git a/program/src/processor.rs b/program/src/processor.rs +index 3181140..7bc9789 100644 +--- a/program/src/processor.rs ++++ b/program/src/processor.rs +@@ -35,7 +35,6 @@ use mango_logs::{ + }; + + use crate::error::{check_assert, MangoError, MangoErrorCode, MangoResult, SourceFileId}; +-use crate::ids::{luna_pyth_oracle, msrm_token, srm_token}; + use crate::instruction::MangoInstruction; + use crate::matching::{Book, BookSide, ExpiryType, OrderType, Side}; + use crate::oracle::{determine_oracle_type, OracleType, StubOracle, STUB_MAGIC}; +@@ -129,7 +128,6 @@ impl Processor { + check!(msrm_vault.delegate.is_none(), MangoErrorCode::InvalidVault)?; + check!(msrm_vault.close_authority.is_none(), MangoErrorCode::InvalidVault)?; + check_eq!(msrm_vault.owner, mango_group.signer_key, MangoErrorCode::InvalidVault)?; +- check_eq!(&msrm_vault.mint, &msrm_token::ID, MangoErrorCode::InvalidVault)?; + check_eq!(msrm_vault_ai.owner, &spl_token::ID, MangoErrorCode::InvalidVault)?; + mango_group.msrm_vault = *msrm_vault_ai.key; + } +@@ -508,14 +506,6 @@ impl Processor { + mango_group.tokens[QUOTE_INDEX].mint.to_aligned_bytes(), + MangoErrorCode::Default + )?; +- +- // TODO - what if quote currency is mngo, srm or msrm +- // if mint is SRM set srm_vault +- +- if mint_ai.key == &srm_token::ID { +- check!(mango_group.srm_vault == Pubkey::default(), MangoErrorCode::Default)?; +- mango_group.srm_vault = *vault_ai.key; +- } + Ok(()) + } + +@@ -8155,11 +8145,6 @@ pub fn read_oracle( + conf.to_num::() + ); + +- // For luna, to prevent market from getting stuck, just continue using last known price in cache +- if oracle_ai.key == &luna_pyth_oracle::ID { +- return Ok(last_known_price_in_cache); +- } +- + return Err(throw_err!(MangoErrorCode::InvalidOraclePrice)); + } + +diff --git a/program/src/state.rs b/program/src/state.rs +index 2b79ab7..c06b046 100644 +--- a/program/src/state.rs ++++ b/program/src/state.rs +@@ -24,7 +24,6 @@ use mango_common::Loadable; + use mango_macro::{Loadable, Pod, TriviallyTransmutable}; + + use crate::error::{check_assert, MangoError, MangoErrorCode, MangoResult, SourceFileId}; +-use crate::ids::mngo_token; + use crate::matching::{Book, LeafNode, OrderType, Side}; + use crate::queue::{EventQueue, EventType, FillEvent}; + use crate::utils::{ +@@ -1197,7 +1196,7 @@ impl HealthCache { + let taker_quote_native = + I80F48::from_num(info.quote_lot_size.checked_mul(taker_quote.abs()).unwrap()); + let mut market_fees = info.taker_fee * taker_quote_native; +- if let Some(mngo_index) = mango_group.find_token_index(&mngo_token::id()) { ++ if let Some(mngo_index) = Some(0) { + let mngo_cache = &mango_cache.root_bank_cache[mngo_index]; + let mngo_deposits = mango_account.get_native_deposit(mngo_cache, mngo_index)?; + let ref_mngo_req = I80F48::from_num(mango_group.ref_mngo_required); +@@ -2235,7 +2234,6 @@ impl PerpMarket { + check!(vault.owner == mango_group.signer_key, MangoErrorCode::InvalidOwner)?; + check!(vault.delegate.is_none(), MangoErrorCode::InvalidVault)?; + check!(vault.close_authority.is_none(), MangoErrorCode::InvalidVault)?; +- check!(vault.mint == mngo_token::ID, MangoErrorCode::InvalidVault)?; + check!(mngo_vault_ai.owner == &spl_token::ID, MangoErrorCode::InvalidOwner)?; + state.mngo_vault = *mngo_vault_ai.key; + diff --git a/configure_cluster/bin/pyth_mock.json b/configure_cluster/bin/pyth_mock.json new file mode 100644 index 0000000..751b528 --- /dev/null +++ b/configure_cluster/bin/pyth_mock.json @@ -0,0 +1 @@ +[247,81,41,82,59,37,100,159,77,210,53,136,212,197,48,156,35,235,118,52,60,249,192,135,247,217,123,52,53,60,223,78,205,15,251,191,21,149,104,160,69,71,75,237,133,4,137,222,132,215,169,167,197,234,74,145,175,129,228,68,34,166,170,187] \ No newline at end of file diff --git a/configure_cluster/bin/pyth_mock.so b/configure_cluster/bin/pyth_mock.so new file mode 100755 index 0000000..ac12abe Binary files /dev/null and b/configure_cluster/bin/pyth_mock.so differ diff --git a/configure_cluster/bin/serum_dex.json b/configure_cluster/bin/serum_dex.json new file mode 100644 index 0000000..03a9340 --- /dev/null +++ b/configure_cluster/bin/serum_dex.json @@ -0,0 +1 @@ +[127,193,75,90,107,91,27,214,61,161,202,125,83,195,77,34,68,138,67,157,106,248,191,233,146,76,25,54,166,46,176,8,37,48,54,71,239,206,113,208,127,201,25,167,143,65,171,203,221,82,164,96,94,240,221,57,154,153,99,238,4,215,240,109] \ No newline at end of file diff --git a/configure_cluster/bin/serum_dex.so b/configure_cluster/bin/serum_dex.so new file mode 100755 index 0000000..557ed60 Binary files /dev/null and b/configure_cluster/bin/serum_dex.so differ diff --git a/configure_cluster/bin/spl-genesis-args.sh b/configure_cluster/bin/spl-genesis-args.sh new file mode 100644 index 0000000..40de000 --- /dev/null +++ b/configure_cluster/bin/spl-genesis-args.sh @@ -0,0 +1 @@ +--bpf-program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA BPFLoader2111111111111111111111111111111111 spl_token-3.5.0.so --bpf-program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo BPFLoader1111111111111111111111111111111111 spl_memo-1.0.0.so --bpf-program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr BPFLoader2111111111111111111111111111111111 spl_memo-3.0.0.so --bpf-program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL BPFLoader2111111111111111111111111111111111 spl_associated-token-account-1.1.1.so --bpf-program Feat1YXHhH6t1juaWF74WLcfv4XoNocjXA6sPWHNgAse BPFLoader2111111111111111111111111111111111 spl_feature-proposal-1.0.0.so diff --git a/configure_cluster/bin/spl_associated-token-account-1.1.1.so b/configure_cluster/bin/spl_associated-token-account-1.1.1.so new file mode 100644 index 0000000..63c3958 Binary files /dev/null and b/configure_cluster/bin/spl_associated-token-account-1.1.1.so differ diff --git a/configure_cluster/bin/spl_feature-proposal-1.0.0.so b/configure_cluster/bin/spl_feature-proposal-1.0.0.so new file mode 100644 index 0000000..ed07a7b Binary files /dev/null and b/configure_cluster/bin/spl_feature-proposal-1.0.0.so differ diff --git a/configure_cluster/bin/spl_memo-1.0.0.so b/configure_cluster/bin/spl_memo-1.0.0.so new file mode 100644 index 0000000..77d4f48 Binary files /dev/null and b/configure_cluster/bin/spl_memo-1.0.0.so differ diff --git a/configure_cluster/bin/spl_memo-3.0.0.so b/configure_cluster/bin/spl_memo-3.0.0.so new file mode 100644 index 0000000..88385a0 Binary files /dev/null and b/configure_cluster/bin/spl_memo-3.0.0.so differ diff --git a/configure_cluster/bin/spl_noop.so b/configure_cluster/bin/spl_noop.so new file mode 100755 index 0000000..620e039 Binary files /dev/null and b/configure_cluster/bin/spl_noop.so differ diff --git a/configure_cluster/bin/spl_token-3.5.0.so b/configure_cluster/bin/spl_token-3.5.0.so new file mode 100644 index 0000000..60cbbea Binary files /dev/null and b/configure_cluster/bin/spl_token-3.5.0.so differ diff --git a/configure_cluster/configure_mango_v3.ts b/configure_cluster/configure_mango_v3.ts new file mode 100755 index 0000000..bf4465b --- /dev/null +++ b/configure_cluster/configure_mango_v3.ts @@ -0,0 +1,110 @@ +import { MangoUtils } from "./utils/mango_utils"; +import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js'; +import { sleep, Cluster } from "@blockworks-foundation/mango-client"; +import { getProgramMap } from "./utils/config" +import * as fs from 'fs'; + +export async function main() { + + // cluster should be in 'devnet' | 'mainnet' | 'localnet' | 'testnet' + const cluster = (process.env.CLUSTER || 'localnet') as Cluster; + + const programNameToId = getProgramMap(cluster); + const endpoint = process.env.ENDPOINT_URL || 'http://0.0.0.0:8899'; + const connection = new Connection(endpoint, 'confirmed'); + + const nbUsers = Number(process.env.NB_USERS || "1"); + console.log('Connecting to cluster ' + endpoint) + if (!fs.existsSync('authority.json')) { + //create an authority.json + const new_authority = Keypair.generate(); + fs.writeFileSync('authority.json', '[' + new_authority.secretKey.toString() + ']'); + } + const authority = Keypair.fromSecretKey( + Uint8Array.from( + JSON.parse( + process.env.KEYPAIR || + fs.readFileSync('authority.json', 'utf-8'), + ), + ), + ); + const do_log_str = process.env.LOG || "false"; + const do_log = do_log_str === "true"; + + console.log('Configuring authority') + const balance = await connection.getBalance(authority.publicKey) + console.log('Authority balance is : ' + balance + ' lamports'); + if (balance < (nbUsers + 100) * LAMPORTS_PER_SOL) { + console.log('Balance too low airdropping ' + (nbUsers + 100) + ' SOLs') + const signature = await connection.requestAirdrop(authority.publicKey, (nbUsers + 100) * LAMPORTS_PER_SOL); + await connection.confirmTransaction(signature, 'confirmed'); + } + const beginSlot = await connection.getSlot(); + console.log('Creating Mango Cookie') + const mangoProgramId = new PublicKey(programNameToId['mango']) + const dexProgramId = new PublicKey(programNameToId['serum_dex']); + const pythProgramId = new PublicKey(programNameToId['pyth_mock']); + + let logId = 0 + if (do_log) { + logId = connection.onLogs(mangoProgramId, (log, ctx) => { + if (log.err != null) { + console.log("mango error : ", log.err) + } + else { + for (const l of log.logs) { + console.log("mango log : " + l) + } + } + }); + } + + try { + const mangoUtils = new MangoUtils(connection, authority, mangoProgramId, dexProgramId, pythProgramId); + + const cookie = await mangoUtils.createMangoCookie(['MNGO', 'SOL', 'BTC', 'ETH', 'AVAX', 'SRM', 'FTT', 'RAY', 'MNGO', 'BNB', 'GMT', 'ADA']) + + console.log('Creating ids.json'); + const json = mangoUtils.convertCookie2Json(cookie, cluster) + fs.writeFileSync('ids.json', JSON.stringify(json, null, 2)); + + console.log('Mango cookie created successfully') + console.log('Creating ' + nbUsers + ' Users'); + const users = (await mangoUtils.createAndMintUsers(cookie, nbUsers, authority)).map(x => { + const info = {}; + info['publicKey'] = x.kp.publicKey.toBase58(); + info['secretKey'] = Array.from(x.kp.secretKey); + info['mangoAccountPks'] = [x.mangoAddress.toBase58()]; + return info; + }) + console.log('created ' + nbUsers + ' Users'); + fs.writeFileSync('accounts.json', JSON.stringify(users)); + + } finally { + if (logId) { + // to log mango logs + await sleep(5000) + const endSlot = await connection.getSlot(); + const blockSlots = await connection.getBlocks(beginSlot, endSlot); + console.log("\n\n===============================================") + for (let blockSlot of blockSlots) { + const block = await connection.getBlock(blockSlot); + for (let i = 0; i < block.transactions.length; ++i) { + if (block.transactions[i].meta.logMessages) { + for (const msg of block.transactions[i].meta.logMessages) { + console.log('solana_message : ' + msg); + } + } + } + } + + connection.removeOnLogsListener(logId); + } + } +} + +main().then(x => { + console.log('finished sucessfully') +}).catch(e => { + console.log('caught an error : ' + e) +}) diff --git a/configure_cluster/create_users.ts b/configure_cluster/create_users.ts new file mode 100755 index 0000000..b3143e6 --- /dev/null +++ b/configure_cluster/create_users.ts @@ -0,0 +1,61 @@ +import { Cluster, Config } from "@blockworks-foundation/mango-client"; +import { Connection, Keypair, PublicKey } from "@solana/web3.js"; +import { readFileSync, writeFileSync } from 'fs'; +import { MangoUtils } from "./utils/mango_utils"; +import { getProgramMap } from "./utils/config" + +if (process.argv.length < 4) { + console.log("please enter arguments as follows\n `ts-node create_users number_of_users output_file cluster_config_file(o)`"); +} + +export async function main( + file: any, + nbUsers: number, + authority: Keypair, + outFile: string, +) { + const cluster = (process.env.CLUSTER || 'localnet') as Cluster; + + const programNameToId = getProgramMap(cluster); + const endpoint = process.env.ENDPOINT_URL || 'http://0.0.0.0:8899'; + const connection = new Connection(endpoint, 'confirmed'); + console.log('Connecting to cluster ' + endpoint) + const mangoProgramId = new PublicKey(programNameToId['mango']) + const dexProgramId = new PublicKey(programNameToId['serum_dex']); + const pythProgramId = new PublicKey(programNameToId['pyth_mock']); + const json = JSON.parse(file); + try { + const mangoUtils = new MangoUtils(connection, authority, mangoProgramId, dexProgramId, pythProgramId); + let mangoCookie = await mangoUtils.json2Cookie(json, cluster); + const users = (await mangoUtils.createAndMintUsers(mangoCookie, nbUsers, authority)).map(x => { + const info = {}; + info['publicKey'] = x.kp.publicKey.toBase58(); + info['secretKey'] = Array.from(x.kp.secretKey); + info['mangoAccountPks'] = [x.mangoAddress.toBase58()]; + return info; + }) + console.log('created ' + nbUsers + ' Users'); + + writeFileSync(outFile, JSON.stringify(users)); + } + catch (e) { + console.log('failed to create error ' + e); + } +} + +const configFile = process.argv.length >= 5 ? process.argv[4] : 'ids.json'; +const file = readFileSync(configFile, 'utf-8'); + +const authority = Keypair.fromSecretKey( + Uint8Array.from( + JSON.parse( + process.env.KEYPAIR || + readFileSync('authority.json', 'utf-8'), + ), + ), +); +let nbUsers = +process.argv[2]; +let outFile = process.argv[3]; +main(file, nbUsers, authority, outFile).then(x=> { + console.log("finished"); +}) \ No newline at end of file diff --git a/configure_cluster/keeper.ts b/configure_cluster/keeper.ts new file mode 100644 index 0000000..0b18e8a --- /dev/null +++ b/configure_cluster/keeper.ts @@ -0,0 +1,379 @@ +/** +This will probably move to its own repo at some point but easier to keep it here for now + */ +import * as os from 'os'; +import * as fs from 'fs'; +import { + Keypair, + Commitment, + Connection, + PublicKey, + Transaction, + ComputeBudgetProgram +} from '@solana/web3.js'; +import { + MangoClient, + makeCachePerpMarketsInstruction, + makeCachePricesInstruction, + makeCacheRootBankInstruction, + makeUpdateFundingInstruction, + makeUpdateRootBankInstruction, + getMultipleAccounts, + zeroKey, + Cluster, + Config, + PerpEventQueueLayout, + MangoGroup, PerpMarket, promiseUndef, + PerpEventQueue, + sleep, + makeConsumeEventsInstruction +} from "@blockworks-foundation/mango-client"; +import BN from 'bn.js'; + +let lastRootBankCacheUpdate = 0; +const groupName = process.env.GROUP || 'localnet'; +const updateCacheInterval = parseInt( + process.env.UPDATE_CACHE_INTERVAL || '2000', +); +const updateRootBankCacheInterval = parseInt( + process.env.UPDATE_ROOT_BANK_CACHE_INTERVAL || '3000', +); +const processKeeperInterval = parseInt( + process.env.PROCESS_KEEPER_INTERVAL || '3000', +); +const consumeEventsInterval = parseInt( + process.env.CONSUME_EVENTS_INTERVAL || '100', +); +const maxUniqueAccounts = parseInt(process.env.MAX_UNIQUE_ACCOUNTS || '24'); +const consumeEventsLimit = new BN(process.env.CONSUME_EVENTS_LIMIT || '20'); +const consumeEvents = process.env.CONSUME_EVENTS ? process.env.CONSUME_EVENTS === 'true' : true; +const skipPreflight = process.env.SKIP_PREFLIGHT ? process.env.SKIP_PREFLIGHT === 'true' : true; +const cluster = (process.env.CLUSTER || 'localnet') as Cluster; +import configFile from './ids.json'; +const config = new Config(configFile); +const groupIds = config.getGroup(cluster, groupName); + +if (!groupIds) { + throw new Error(`Group ${groupName} not found`); +} +const mangoProgramId = groupIds.mangoProgramId; +const mangoGroupKey = groupIds.publicKey; +const payer = Keypair.fromSecretKey( + Uint8Array.from( + JSON.parse( + process.env.KEYPAIR || + fs.readFileSync('authority.json', 'utf-8'), + ), + ), +); +const connection = new Connection( + process.env.ENDPOINT_URL || config.cluster_urls[cluster], + 'processed' as Commitment, +); +const client = new MangoClient(connection, mangoProgramId, { + timeout: 10000, + prioritizationFee: 10000, // number of micro lamports +}); + +async function main() { + if (!groupIds) { + throw new Error(`Group ${groupName} not found`); + } + const mangoGroup = await client.getMangoGroup(mangoGroupKey); + const perpMarkets = await Promise.all( + groupIds.perpMarkets.map((m) => { + return mangoGroup.loadPerpMarket( + connection, + m.marketIndex, + m.baseDecimals, + m.quoteDecimals, + ); + }), + ); + + const do_log_str = process.env.LOG || "false"; + const do_log = do_log_str === "true"; + let logId = 0 + if (do_log) { + console.log("LOGGING ON"); + logId = connection.onLogs(mangoProgramId, (log, ctx) => { + if (log.err != null) { + console.log("mango error : ", log.err) + } + else { + for (const l of log.logs) { + console.log("mango log : " + l) + } + } + }); + } + const beginSlot = await connection.getSlot(); + + try { + processUpdateCache(mangoGroup); + processKeeperTransactions(mangoGroup, perpMarkets); + + if (consumeEvents) { + processConsumeEvents(mangoGroup, perpMarkets); + } + } finally { + if (logId) { + // to log mango logs + await sleep(5000) + const endSlot = await connection.getSlot(); + const blockSlots = await connection.getBlocks(beginSlot, endSlot); + console.log("\n\n===============================================") + for (let blockSlot of blockSlots) { + const block = await connection.getBlock(blockSlot); + for (let i = 0; i < block.transactions.length; ++i) { + if (block.transactions[i].meta.logMessages) { + for (const msg of block.transactions[i].meta.logMessages) { + console.log('solana_message : ' + msg); + } + } + } + } + + connection.removeOnLogsListener(logId); + } + } +} +console.time('processUpdateCache'); +console.time('processKeeperTransactions'); +console.time('processConsumeEvents'); + +async function processUpdateCache(mangoGroup: MangoGroup) { + try { + const batchSize = 8; + const promises: Promise[] = []; + const rootBanks = mangoGroup.tokens + .map((t) => t.rootBank) + .filter((t) => !t.equals(zeroKey)); + const oracles = mangoGroup.oracles.filter((o) => !o.equals(zeroKey)); + const perpMarkets = mangoGroup.perpMarkets + .filter((pm) => !pm.isEmpty()) + .map((pm) => pm.perpMarket); + const nowTs = Date.now(); + let shouldUpdateRootBankCache = false; + if (nowTs - lastRootBankCacheUpdate > updateRootBankCacheInterval) { + shouldUpdateRootBankCache = true; + lastRootBankCacheUpdate = nowTs; + } + for (let i = 0; i < Math.ceil(rootBanks.length / batchSize); i++) { + const startIndex = i * batchSize; + const endIndex = Math.min(i * batchSize + batchSize, rootBanks.length); + const cacheTransaction = new Transaction(); + + cacheTransaction.add( + makeCachePricesInstruction( + mangoProgramId, + mangoGroup.publicKey, + mangoGroup.mangoCache, + oracles.slice(startIndex, endIndex), + ), + ); + + if (shouldUpdateRootBankCache) { + cacheTransaction.add( + makeCacheRootBankInstruction( + mangoProgramId, + mangoGroup.publicKey, + mangoGroup.mangoCache, + rootBanks.slice(startIndex, endIndex), + ), + ); + } + + cacheTransaction.add( + makeCachePerpMarketsInstruction( + mangoProgramId, + mangoGroup.publicKey, + mangoGroup.mangoCache, + perpMarkets.slice(startIndex, endIndex), + ), + ); + if (cacheTransaction.instructions.length > 0) { + promises.push(connection.sendTransaction(cacheTransaction, [payer], {skipPreflight})); + } + } + + Promise.all(promises).catch((err) => { + console.error('Error updating cache', err); + }); + } catch (err) { + console.error('Error in processUpdateCache', err); + } finally { + console.timeLog('processUpdateCache'); + setTimeout(processUpdateCache, updateCacheInterval, mangoGroup); + } +} + +async function processConsumeEvents( + mangoGroup: MangoGroup, + perpMarkets: PerpMarket[], +) { + let eventsConsumed = []; + try { + const eventQueuePks = perpMarkets.map((mkt) => mkt.eventQueue); + const eventQueueAccts = await getMultipleAccounts( + connection, + eventQueuePks, + ); + + const perpMktAndEventQueue = eventQueueAccts.map( + ({ publicKey, accountInfo }) => { + const parsed = PerpEventQueueLayout.decode(accountInfo?.data); + const eventQueue = new PerpEventQueue(parsed); + const perpMarket = perpMarkets.find((mkt) => + mkt.eventQueue.equals(publicKey), + ); + if (!perpMarket) { + throw new Error('PerpMarket not found'); + } + return { perpMarket, eventQueue }; + }, + ); + + const promises: Promise[] = perpMktAndEventQueue.map( + ({ perpMarket, eventQueue }) => { + const events = eventQueue.getUnconsumedEvents(); + if (events.length === 0) { + // console.log('No events to consume', perpMarket.publicKey.toString(), perpMarket.eventQueue.toString()); + return promiseUndef(); + } + + const accounts: Set = new Set(); + for (const event of events) { + if (event.fill) { + accounts.add(event.fill.maker.toBase58()); + accounts.add(event.fill.taker.toBase58()); + } else if (event.out) { + accounts.add(event.out.owner.toBase58()); + } + + // Limit unique accounts to first 20 or 21 + if (accounts.size >= maxUniqueAccounts) { + break; + } + } + + const consumeEventsInstruction = makeConsumeEventsInstruction( + mangoProgramId, + mangoGroup.publicKey, + mangoGroup.mangoCache, + perpMarket.publicKey, + perpMarket.eventQueue, + Array.from(accounts) + .map((s) => new PublicKey(s)) + .sort(), + consumeEventsLimit, + ); + + const transaction = new Transaction(); + transaction.add(consumeEventsInstruction); + eventsConsumed.push(perpMarket.eventQueue.toString()); + + return connection.sendTransaction(transaction, [payer], {skipPreflight}) + .catch((err) => { + console.error('Error consuming events', err); + }); + }, + ); + + Promise.all(promises).catch((err) => { + console.error('Error consuming events', err); + }); + } catch (err) { + console.error('Error in processConsumeEvents', err); + } finally { + console.timeLog('processConsumeEvents', eventsConsumed); + setTimeout( + processConsumeEvents, + consumeEventsInterval, + mangoGroup, + perpMarkets, + ); + } +} + +async function processKeeperTransactions( + mangoGroup: MangoGroup, + perpMarkets: PerpMarket[], +) { + try { + if (!groupIds) { + throw new Error(`Group ${groupName} not found`); + } + const batchSize = 8; + const promises: Promise[] = []; + + const filteredPerpMarkets = perpMarkets.filter( + (pm) => !pm.publicKey.equals(zeroKey), + ); + + for (let i = 0; i < groupIds.tokens.length / batchSize; i++) { + const startIndex = i * batchSize; + const endIndex = i * batchSize + batchSize; + + const updateRootBankTransaction = new Transaction(); + groupIds.tokens.slice(startIndex, endIndex).forEach((token) => { + updateRootBankTransaction.add( + makeUpdateRootBankInstruction( + mangoProgramId, + mangoGroup.publicKey, + mangoGroup.mangoCache, + token.rootKey, + token.nodeKeys, + ), + ); + }); + + const updateFundingTransaction = new Transaction(); + filteredPerpMarkets.slice(startIndex, endIndex).forEach((market) => { + if (market) { + updateFundingTransaction.add( + makeUpdateFundingInstruction( + mangoProgramId, + mangoGroup.publicKey, + mangoGroup.mangoCache, + market.publicKey, + market.bids, + market.asks, + ), + ); + } + }); + + if (updateRootBankTransaction.instructions.length > 0) { + promises.push( + connection.sendTransaction(updateRootBankTransaction, [payer], {skipPreflight}), + ); + } + if (updateFundingTransaction.instructions.length > 0) { + promises.push( + connection.sendTransaction(updateFundingTransaction, [payer], {skipPreflight}), + ); + } + } + + Promise.all(promises).catch((err) => { + console.error('Error processing keeper instructions', err); + }); + } catch (err) { + console.error('Error in processKeeperTransactions', err); + } finally { + console.timeLog('processKeeperTransactions'); + setTimeout( + processKeeperTransactions, + processKeeperInterval, + mangoGroup, + perpMarkets, + ); + } +} + +process.on('unhandledRejection', (err: any, p: any) => { + console.error(`Unhandled rejection: ${err} promise: ${p})`); +}); + +main(); diff --git a/configure_cluster/refund_users.ts b/configure_cluster/refund_users.ts new file mode 100644 index 0000000..161a754 --- /dev/null +++ b/configure_cluster/refund_users.ts @@ -0,0 +1,110 @@ +import { web3 } from "@project-serum/anchor"; +import { Cluster, Connection, Keypair, LAMPORTS_PER_SOL, SystemProgram } from "@solana/web3.js"; +import { sleep } from "@blockworks-foundation/mango-client"; + +if (process.argv.length < 3) { + console.log("please enter user file name as argument"); +} +import { readFileSync } from 'fs'; +let fileName = process.argv[2]; + +interface Users { + publicKey: string; + secretKey: any; + mangoAccountPks: string, +} + +enum Result { + SUCCESS = 0, + FAILURE = 1 +}; + + +export async function main(users: Users[], + authority: Keypair, + targetBalance: number, + n_try: number) { + // cluster should be in 'devnet' | 'mainnet' | 'localnet' | 'testnet' + const endpoint = process.env.ENDPOINT_URL || 'http://localhost:8899'; + const connection = new Connection(endpoint, 'confirmed'); + + let accounts_to_fund = new Array<[Users, number]>(); + try { + for (let i = 0; i <= n_try; i++) { + if (i > 0) { + await sleep(2000); + } + // add all accounts which have balance less than threshold to set + { + let promises : Promise[]= [] + for (let curAccount of users) { + let curPubkey = new web3.PublicKey(curAccount.publicKey) + promises.push(connection.getBalance(curPubkey)); + } + const balance = await Promise.all(promises); + users.forEach( (cur_account, index) => { + if (balance[index] < LAMPORTS_PER_SOL * targetBalance) { + accounts_to_fund.push([cur_account, balance[index]]); + } + }); + if (accounts_to_fund.length == 0) { + return Result.SUCCESS; + } + } + // fund all these accounts + { + let promises : Promise[]= [] + let blockHash = await connection.getLatestBlockhash(); + for (const [user, balance] of accounts_to_fund) { + let userPubkey = new web3.PublicKey(user.publicKey) + const ix = SystemProgram.transfer({ + fromPubkey: authority.publicKey, + lamports: LAMPORTS_PER_SOL * targetBalance - balance, + toPubkey: userPubkey, + }) + let tx = new web3.Transaction().add(ix); + tx.recentBlockhash = blockHash.blockhash; + console.log("Fund for " + (LAMPORTS_PER_SOL * targetBalance - balance)/LAMPORTS_PER_SOL + ""); + promises.push(connection.sendTransaction(tx, [authority])) + } + + try { + const result = await Promise.all(promises); + } catch(e) { + console.log('While sending transactions caught an error : ' + e + ". Will try again.") + } + } + accounts_to_fund.length = 0; + } + } catch(e) { + console.log('caught an error : ' + e) + } + return Result.FAILURE; +} + + +const targetBalance = parseFloat(process.env.REFUND_TARGET_SOL || '1.0'); +const nTry = parseFloat(process.env.NUMBER_TRY_REFUNG || '3'); +const file = readFileSync(fileName, 'utf-8'); +const users : Users[] = JSON.parse(file); +if (users === undefined) { + console.log("cannot read users list") +} +const authority = Keypair.fromSecretKey( + Uint8Array.from( + JSON.parse( + process.env.KEYPAIR || + readFileSync('authority.json', 'utf-8'), + ), + ), +); +console.log('refunding to have up to ' + targetBalance + ' sol for ' + users.length + ' users') +main(users, authority, targetBalance, nTry).then(x => { + if (x == Result.SUCCESS) { + console.log('finished sucessfully') + process.exit(0); + } else { + console.log('failed') + process.exit(1); + } +}); diff --git a/configure_cluster/scripts/configure_local.sh b/configure_cluster/scripts/configure_local.sh new file mode 100755 index 0000000..bc3873f --- /dev/null +++ b/configure_cluster/scripts/configure_local.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# +# Run a minimal Solana cluster. Ctrl-C to exit. +# +# Before running this script ensure standard Solana programs are available +# in the PATH, or that `cargo build` ran successfully +# +# Prefer possible `cargo build` binaries over PATH binaries + +# ctrl-c trap to stop child processes +trap ctrl_c INT +function ctrl_c() { + echo "Kill them all" + pkill -P $$ + exit 1 +} + +outDir=$PWD + +export RUST_LOG=${RUST_LOG:-solana=info,solana_runtime::message_processor=debug} # if RUST_LOG is unset, default to info +export RUST_BACKTRACE=1 +dataDir=$outDir/config +ledgerDir=$outDir/config/ledger +binDir=$(dirname $0)/../bin + +set -x +if ! solana address; then + echo Generating default keypair + solana-keygen new --no-passphrase +fi +validator_identity="$dataDir/validator-identity.json" +if [[ -e $validator_identity ]]; then + echo "Use existing validator keypair" +else + solana-keygen new --no-passphrase -so "$validator_identity" +fi +validator_vote_account="$dataDir/validator-vote-account.json" +if [[ -e $validator_vote_account ]]; then + echo "Use existing validator vote account keypair" +else + solana-keygen new --no-passphrase -so "$validator_vote_account" +fi +validator_stake_account="$dataDir/validator-stake-account.json" +if [[ -e $validator_stake_account ]]; then + echo "Use existing validator stake account keypair" +else + solana-keygen new --no-passphrase -so "$validator_stake_account" +fi + +if [[ -e "$ledgerDir"/genesis.bin || -e "$ledgerDir"/genesis.tar.bz2 ]]; then + echo "Use existing genesis" +else + echo $SPL_GENESIS_ARGS + # shellcheck disable=SC2086 + solana-genesis \ + --hashes-per-tick sleep \ + --faucet-lamports 500000000000000000 \ + --bootstrap-validator \ + "$validator_identity" \ + "$validator_vote_account" \ + "$validator_stake_account" \ + --ledger "$ledgerDir" \ + --cluster-type "development" \ + --bpf-program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA BPFLoader2111111111111111111111111111111111 $binDir/spl_token-3.5.0.so \ + --bpf-program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL BPFLoader2111111111111111111111111111111111 $binDir/spl_associated-token-account-1.1.1.so \ + --bpf-program DGKy8w8RtRsWB48qHa4yCd3AeP5uv4m3Qn7LU8z93RWV BPFLoader2111111111111111111111111111111111 $binDir/mango.so \ + --bpf-program 3WAiypER8fm6vHjUPRiigGifq6ueSY645aYGH5Jj14pU BPFLoader2111111111111111111111111111111111 $binDir/serum_dex.so \ + --bpf-program EoUiQKGpM4jsdb5oRnYnuWMaE4Gcey72QjEBbFxhk23C BPFLoader2111111111111111111111111111111111 $binDir/pyth_mock.so \ + --bpf-program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV BPFLoader2111111111111111111111111111111111 $binDir/spl_noop.so \ + --bpf-program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo BPFLoader1111111111111111111111111111111111 $binDir/spl_memo-1.0.0.so \ + --bpf-program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr BPFLoader2111111111111111111111111111111111 $binDir/spl_memo-3.0.0.so \ + $SOLANA_RUN_SH_GENESIS_ARGS +fi + +solana-faucet & +faucet=$! + +args=( + --identity "$validator_identity" + --vote-account "$validator_vote_account" + --ledger "$ledgerDir" + --gossip-port 8001 + --full-rpc-api + --rpc-port 8899 + --rpc-faucet-address 127.0.0.1:9900 + --log "$dataDir/validator.log" + --enable-rpc-transaction-history + --enable-extended-tx-metadata-storage + --init-complete-file "$dataDir"/init-completed + --snapshot-compression none + --require-tower + --no-wait-for-vote-to-start-leader + --no-os-network-limits-test + --allow-private-addr +) +# shellcheck disable=SC2086 +solana-validator "${args[@]}" $SOLANA_RUN_SH_VALIDATOR_ARGS & +validator=$! + +wait "$validator" diff --git a/configure_cluster/scripts/spl-genesis-args.sh b/configure_cluster/scripts/spl-genesis-args.sh new file mode 100755 index 0000000..e5a9351 --- /dev/null +++ b/configure_cluster/scripts/spl-genesis-args.sh @@ -0,0 +1 @@ +--bpf-program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA BPFLoader2111111111111111111111111111111111 ${binDir}/spl_token-3.5.0.so --bpf-program Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo BPFLoader1111111111111111111111111111111111 $binDir/spl_memo-1.0.0.so --bpf-program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr BPFLoader2111111111111111111111111111111111 $binDir/spl_memo-3.0.0.so --bpf-program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL BPFLoader2111111111111111111111111111111111 $binDir/spl_associated-token-account-1.1.1.so --bpf-program Feat1YXHhH6t1juaWF74WLcfv4XoNocjXA6sPWHNgAse BPFLoader2111111111111111111111111111111111 $binDir/spl_feature-proposal-1.0.0.so --bpf-program DGKy8w8RtRsWB48qHa4yCd3AeP5uv4m3Qn7LU8z93RWV BPFLoader2111111111111111111111111111111111 $binDir/mango.so --bpf-program 3WAiypER8fm6vHjUPRiigGifq6ueSY645aYGH5Jj14pU BPFLoader2111111111111111111111111111111111 $binDir/serum_dex.so --bpf-program EoUiQKGpM4jsdb5oRnYnuWMaE4Gcey72QjEBbFxhk23C BPFLoader2111111111111111111111111111111111 $binDir/pyth_mock.so --bpf-program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV BPFLoader2111111111111111111111111111111111 $binDir/spl_noop.so diff --git a/configure_cluster/scripts/start_test_validator.sh b/configure_cluster/scripts/start_test_validator.sh new file mode 100755 index 0000000..f611259 --- /dev/null +++ b/configure_cluster/scripts/start_test_validator.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +binDir=$(dirname $0)/../bin +solana-test-validator --account MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac $binDir/mango-mint.json --bpf-program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV $binDir/spl_noop.so & +pid="$!" + +# handle ctrl-c +trap cleanup INT EXIT KILL 2 + +cleanup() +{ + echo "cleanup $pid" + kill -9 $pid +} + +sleep 5 + +solana program deploy bin/mango.so -ul --program-id $binDir/mango.json +solana program deploy bin/serum_dex.so -ul --program-id $binDir/serum_dex.json +solana program deploy bin/pyth_mock.so -ul --program-id $binDir/pyth_mock.json + +# idle waiting for abort +wait $pid diff --git a/configure_cluster/scripts/testnet-deploy-programs.sh b/configure_cluster/scripts/testnet-deploy-programs.sh new file mode 100755 index 0000000..07ab054 --- /dev/null +++ b/configure_cluster/scripts/testnet-deploy-programs.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +URL_OR_MONIKER="${URL_OR_MONIKER:=testnet}" +# spl programs are already deployed to testnet +spl_soFiles=("spl_token-3.5.0.so" "spl_memo-1.0.0.so" "spl_memo-3.0.0.so" "spl_associated-token-account-1.1.1.so" "spl_feature-proposal-1.0.0.so") +soFiles=("mango.so" "serum_dex.so" "pyth_mock.so") +DEPLOY_PROGRAM="solana program deploy -u ${URL_OR_MONIKER} --use-quic" +mkdir -p program-keypairs +output=() +for program in ${soFiles[@]}; do + programName="${program%.*}" + keypair="./program-keypairs/${programName}.json" + solana-keygen new --no-passphrase -so ${keypair} + ${DEPLOY_PROGRAM} --program-id ${keypair} bin/${program} + pubkey=$(solana-keygen pubkey ${keypair}) + output+=("\"${programName}\":\"${pubkey}\"") +done + +mapFile="testnet-program-name-to-id.json" +echo "{" > ${mapFile} +last=$((${#output[@]}-1)) +echo $last +for v in ${output[@]:0:$last}; do + echo "$v," >> ${mapFile} +done +echo ${output[-1]} >> ${mapFile} +echo "}" >> ${mapFile} diff --git a/configure_cluster/utils/config.ts b/configure_cluster/utils/config.ts new file mode 100644 index 0000000..a55bee0 --- /dev/null +++ b/configure_cluster/utils/config.ts @@ -0,0 +1,99 @@ + +import { GroupConfig, OracleConfig, PerpMarketConfig, SpotMarketConfig, TokenConfig, Cluster } from '@blockworks-foundation/mango-client'; +import { readFileSync } from 'fs'; + + +export function getProgramMap(cluster : Cluster): Map { + var file = ""; + if (cluster == "testnet") { + file = readFileSync('./configure_cluster/utils/testnet-program-name-to-id.json', 'utf-8'); + } else { + file = readFileSync('./configure_cluster/utils/genesis-program-name-to-id.json', 'utf-8'); + }; + return JSON.parse(file); +} + +export class Config { + public cluster_urls: Record; + public groups: GroupConfig[]; + + constructor(cluster_urls: Record, groups : GroupConfig[]) { + this.cluster_urls = cluster_urls; + this.groups = groups; + } + + oracleConfigToJson(o: OracleConfig): any { + return { + ...o, + publicKey: o.publicKey.toBase58(), + }; + } + + perpMarketConfigToJson(p: PerpMarketConfig): any { + return { + ...p, + publicKey: p.publicKey.toBase58(), + bidsKey: p.bidsKey.toBase58(), + asksKey: p.asksKey.toBase58(), + eventsKey: p.eventsKey.toBase58(), + }; + } + + spotMarketConfigToJson(p: SpotMarketConfig): any { + return { + ...p, + publicKey: p.publicKey.toBase58(), + bidsKey: p.bidsKey.toBase58(), + asksKey: p.asksKey.toBase58(), + eventsKey: p.eventsKey.toBase58(), + }; + } + + tokenConfigToJson(t: TokenConfig): any { + return { + ...t, + mintKey: t.mintKey.toBase58(), + rootKey: t.rootKey.toBase58(), + nodeKeys: t.nodeKeys.map((k) => k.toBase58()), + }; + } + + + groupConfigToJson(g: GroupConfig): any { + return { + ...g, + publicKey: g.publicKey.toBase58(), + mangoProgramId: g.mangoProgramId.toBase58(), + serumProgramId: g.serumProgramId.toBase58(), + oracles: g.oracles.map((o) => this.oracleConfigToJson(o)), + perpMarkets: g.perpMarkets.map((p) => this.perpMarketConfigToJson(p)), + spotMarkets: g.spotMarkets.map((p) => this.spotMarketConfigToJson(p)), + tokens: g.tokens.map((t) => this.tokenConfigToJson(t)), + }; + } + + public toJson(): any { + return { + ...this, + groups: this.groups.map((g) => this.groupConfigToJson(g)), + }; + } + + public getGroup(cluster: Cluster, name: string) { + return this.groups.find((g) => g.cluster === cluster && g.name === name); + } + + public getGroupWithName(name: string) { + return this.groups.find((g) => g.name === name); + } + + public storeGroup(group: GroupConfig) { + const _group = this.getGroup(group.cluster, group.name); + if (_group) { + Object.assign(_group, group); + } else { + this.groups.unshift(group); + } + } +} + diff --git a/configure_cluster/utils/genesis-program-name-to-id.json b/configure_cluster/utils/genesis-program-name-to-id.json new file mode 100644 index 0000000..e87d2c1 --- /dev/null +++ b/configure_cluster/utils/genesis-program-name-to-id.json @@ -0,0 +1,5 @@ +{ + "mango": "DGKy8w8RtRsWB48qHa4yCd3AeP5uv4m3Qn7LU8z93RWV", + "serum_dex": "3WAiypER8fm6vHjUPRiigGifq6ueSY645aYGH5Jj14pU", + "pyth_mock" : "EoUiQKGpM4jsdb5oRnYnuWMaE4Gcey72QjEBbFxhk23C" +} diff --git a/configure_cluster/utils/mango_utils.ts b/configure_cluster/utils/mango_utils.ts new file mode 100755 index 0000000..fc15ce1 --- /dev/null +++ b/configure_cluster/utils/mango_utils.ts @@ -0,0 +1,811 @@ +import * as anchor from '@project-serum/anchor'; +import { Market, OpenOrders } from "@project-serum/serum"; +import * as mango_client_v3 from '@blockworks-foundation/mango-client'; +import { Connection, LAMPORTS_PER_SOL } from '@solana/web3.js'; +import * as splToken from '@solana/spl-token'; +import { + NATIVE_MINT, + Mint, + TOKEN_PROGRAM_ID, + MintLayout, +} from "@solana/spl-token"; + +import { + PublicKey, + Keypair, + Transaction, + SystemProgram, + sendAndConfirmTransaction, + Signer, +} from '@solana/web3.js'; +import { SerumUtils, } from "./serum_utils"; +import { MintUtils, TokenData, } from './mint_utils'; +import { BN } from 'bn.js'; +import { + GroupConfig, + OracleConfig, + PerpMarketConfig, + SpotMarketConfig, + TokenConfig, + Cluster, + PerpEventQueueHeaderLayout, + PerpEventLayout, + BookSideLayout, + makeCreatePerpMarketInstruction, + I80F48, + PerpMarketLayout, + makeAddPerpMarketInstruction, + makeDepositInstruction, + Config as MangoConfig, + sleep +} from '@blockworks-foundation/mango-client'; +import { token } from '@project-serum/anchor/dist/cjs/utils'; +import { Config } from './config'; + +export interface PerpMarketData { + publicKey: PublicKey, + asks: PublicKey, + bids: PublicKey, + eventQ: PublicKey, +} + +export interface MangoTokenData extends TokenData { + rootBank: PublicKey, + nodeBank: PublicKey, + marketIndex: number, + perpMarket: PerpMarketData, +} + +export interface MangoCookie { + mangoGroup: PublicKey, + signerKey: PublicKey, + mangoCache: PublicKey, + usdcRootBank: PublicKey, + usdcNodeBank: PublicKey, + usdcVault: PublicKey, + usdcMint: PublicKey, + tokens: Array<[String, MangoTokenData]>, + MSRM: PublicKey, +} + +export interface MangoUser { + kp: Keypair, + mangoAddress: PublicKey, +} + +export class MangoUtils { + private conn: Connection; + private serumUtils: SerumUtils; + private mintUtils: MintUtils; + private mangoClient: mango_client_v3.MangoClient; + private authority: Keypair; + private mangoProgramId: PublicKey; + private dexProgramId: PublicKey; + + constructor(conn: Connection, authority: Keypair, mangoProgramId: PublicKey, dexProgramId: PublicKey, pythProgramId: PublicKey) { + this.conn = conn; + this.authority = authority; + this.mangoProgramId = mangoProgramId; + this.dexProgramId = dexProgramId; + + this.serumUtils = new SerumUtils(conn, authority, dexProgramId); + this.mintUtils = new MintUtils(conn, authority, dexProgramId, pythProgramId); + this.mangoClient = new mango_client_v3.MangoClient(conn, mangoProgramId); + } + + async createAccountForMango(size: number): Promise { + const lamports = await this.conn.getMinimumBalanceForRentExemption(size); + let address = Keypair.generate(); + + const transaction = new Transaction().add( + SystemProgram.createAccount({ + fromPubkey: this.authority.publicKey, + newAccountPubkey: address.publicKey, + lamports, + space: size, + programId: this.mangoProgramId, + })) + + transaction.feePayer = this.authority.publicKey; + let hash = await this.conn.getRecentBlockhash(); + transaction.recentBlockhash = hash.blockhash; + // Sign transaction, broadcast, and confirm + await sendAndConfirmTransaction( + this.conn, + transaction, + [this.authority, address], + { commitment: 'confirmed' }, + ); + + // airdrop all mongo accounts 1000 SOLs + // const signature = await this.conn.requestAirdrop(address.publicKey, LAMPORTS_PER_SOL * 1000); + // const blockHash = await this.conn.getRecentBlockhash('confirmed'); + // const blockHeight = await this.conn.getBlockHeight('confirmed') + // await this.conn.confirmTransaction({signature: signature, blockhash: blockHash.blockhash, lastValidBlockHeight: blockHeight}); + return address.publicKey; + } + + public async createMangoCookie(tokensList: Array): Promise { + + const size = mango_client_v3.MangoGroupLayout.span; + let group_address = await this.createAccountForMango(size); + let root_bank_address = await this.createAccountForMango(mango_client_v3.RootBankLayout.span); + let node_bank_address = await this.createAccountForMango(mango_client_v3.NodeBankLayout.span); + let mango_cache = await this.createAccountForMango(mango_client_v3.MangoCacheLayout.span); + + const { signerKey, signerNonce } = await mango_client_v3.createSignerKeyAndNonce( + this.mangoProgramId, + group_address, + ); + + let mangoCookie: MangoCookie = { + mangoGroup: null, + signerKey, + mangoCache: null, + usdcRootBank: null, + usdcNodeBank: null, + usdcVault: null, + tokens: new Array<[String, MangoTokenData]>(), + usdcMint: await this.mintUtils.createMint(6), + MSRM: await this.mintUtils.createMint(6), + }; + + let usdc_vault = await this.mintUtils.createTokenAccount(mangoCookie.usdcMint, this.authority, signerKey); + splToken.mintTo(this.conn, this.authority, mangoCookie.usdcMint, usdc_vault, this.authority, 1000000 * 1000000); + mangoCookie.usdcVault = usdc_vault; + + let insurance_vault = await this.mintUtils.createTokenAccount(mangoCookie.usdcMint, this.authority, signerKey); + splToken.mintTo(this.conn, this.authority, mangoCookie.usdcMint, insurance_vault, this.authority, 1000000 * 1000000); + + let fee_vault = await this.mintUtils.createTokenAccount(mangoCookie.usdcMint, this.authority, TOKEN_PROGRAM_ID); + splToken.mintTo(this.conn, this.authority, mangoCookie.usdcMint, fee_vault, this.authority, 1000000 * 1000000); + + let msrm_vault = await this.mintUtils.createTokenAccount(mangoCookie.MSRM, this.authority, signerKey); + mangoCookie.usdcRootBank = root_bank_address; + mangoCookie.usdcNodeBank = node_bank_address; + + + console.log('mango program id : ' + this.mangoProgramId) + console.log('serum program id : ' + this.dexProgramId) + + let ix = mango_client_v3.makeInitMangoGroupInstruction( + this.mangoProgramId, + group_address, + signerKey, + this.authority.publicKey, + mangoCookie.usdcMint, + usdc_vault, + node_bank_address, + root_bank_address, + insurance_vault, + PublicKey.default, + fee_vault, + mango_cache, + this.dexProgramId, + new anchor.BN(signerNonce), + new anchor.BN(10), + mango_client_v3.I80F48.fromNumber(0.7), + mango_client_v3.I80F48.fromNumber(0.06), + mango_client_v3.I80F48.fromNumber(1.5), + ); + + let ixCacheRootBank = mango_client_v3.makeCacheRootBankInstruction(this.mangoProgramId, + group_address, + mango_cache, + [root_bank_address]); + + let ixupdateRootBank = mango_client_v3.makeUpdateRootBankInstruction(this.mangoProgramId, + group_address, + mango_cache, + root_bank_address, + [node_bank_address]); + + await this.processInstruction(ix, [this.authority]); + await this.processInstruction(ixCacheRootBank, [this.authority]); + await this.processInstruction(ixupdateRootBank, [this.authority]); + + mangoCookie.mangoGroup = group_address; + mangoCookie.mangoCache = mango_cache; + console.log('Mango group created, creating tokens') + // create mngo + const mngoData = await this.createMangoToken(mangoCookie, tokensList[0], 0, 6, 100); + let tokenData = await Promise.all(tokensList.filter((a,b)=> b > 0).map((tokenStr, tokenIndex) => this.createMangoToken(mangoCookie, tokenStr, tokenIndex+1, 6, 100))); + tokenData.push(mngoData) + await this.mangoClient.cachePrices(mangoCookie.mangoGroup, mangoCookie.mangoCache, tokenData.map(x=>x.priceOracle.publicKey), this.authority); + //tokensList.map((tokenStr, tokenIndex) => this.createMangoToken(mangoCookie, tokenStr, tokenIndex, 6, 100)); + return mangoCookie; + } + + public async createMangoToken(mangoCookie: MangoCookie, tokenName: String, tokenIndex: number, nbDecimals, startingPrice): Promise { + console.log('Creating token ' + tokenName + " at index " + tokenIndex) + const tokenData = await this.mintUtils.createNewToken(mangoCookie.usdcMint, nbDecimals, startingPrice); + let mangoTokenData: MangoTokenData = { + market: tokenData.market, + marketIndex: tokenIndex, + mint: tokenData.mint, + priceOracle: tokenData.priceOracle, + nbDecimals: tokenData.nbDecimals, + startingPrice: startingPrice, + nodeBank: await this.createAccountForMango(mango_client_v3.NodeBankLayout.span), + rootBank: await this.createAccountForMango(mango_client_v3.RootBankLayout.span), + perpMarket: null, + }; + // add oracle to mango + let add_oracle_ix = mango_client_v3.makeAddOracleInstruction( + this.mangoProgramId, + mangoCookie.mangoGroup, + mangoTokenData.priceOracle.publicKey, + this.authority.publicKey, + ); + // add oracle + { + const transaction = new Transaction(); + transaction.add(add_oracle_ix); + transaction.feePayer = this.authority.publicKey; + let hash = await this.conn.getRecentBlockhash(); + transaction.recentBlockhash = hash.blockhash; + // Sign transaction, broadcast, and confirm + await this.mangoClient.sendTransaction( + transaction, + this.authority, + [], + 3600, + 'confirmed', + ); + } + await this.initSpotMarket(mangoCookie, mangoTokenData); + + const group = await this.mangoClient.getMangoGroup(mangoCookie.mangoGroup); + mangoTokenData.marketIndex = group.getTokenIndex(mangoTokenData.mint) + mangoTokenData.perpMarket = await this.initPerpMarket(mangoCookie, mangoTokenData); + + mangoCookie.tokens.push([tokenName, mangoTokenData]); + return mangoTokenData; + } + + public async initSpotMarket(mangoCookie: MangoCookie, token: MangoTokenData) { + + const vault = await this.mintUtils.createTokenAccount(token.mint, this.authority, mangoCookie.signerKey); + // add spot market to mango + let add_spot_ix = mango_client_v3.makeAddSpotMarketInstruction( + this.mangoProgramId, + mangoCookie.mangoGroup, + token.priceOracle.publicKey, + token.market.address, + this.dexProgramId, + token.mint, + token.nodeBank, + vault, + token.rootBank, + this.authority.publicKey, + mango_client_v3.I80F48.fromNumber(10), + mango_client_v3.I80F48.fromNumber(5), + mango_client_v3.I80F48.fromNumber(0.05), + mango_client_v3.I80F48.fromNumber(0.7), + mango_client_v3.I80F48.fromNumber(0.06), + mango_client_v3.I80F48.fromNumber(1.5), + ); + + let ixCacheRootBank = mango_client_v3.makeCacheRootBankInstruction(this.mangoProgramId, + mangoCookie.mangoGroup, + mangoCookie.mangoCache, + [token.rootBank]); + + let ixupdateRootBank = mango_client_v3.makeUpdateRootBankInstruction(this.mangoProgramId, + mangoCookie.mangoGroup, + mangoCookie.mangoCache, + token.rootBank, + [token.nodeBank]); + + const transaction = new Transaction(); + transaction.add(add_spot_ix); + transaction.add(ixCacheRootBank); + transaction.add(ixupdateRootBank); + transaction.feePayer = this.authority.publicKey; + let hash = await this.conn.getRecentBlockhash(); + transaction.recentBlockhash = hash.blockhash; + + await sendAndConfirmTransaction( + this.conn, + transaction, + [this.authority], + { commitment: 'confirmed', maxRetries: 100 } + ) + } + + public async getMangoGroup(mangoCookie: MangoCookie) { + return this.mangoClient.getMangoGroup(mangoCookie.mangoGroup) + } + + private async initPerpMarket(mangoCookie: MangoCookie, token: MangoTokenData) { + const maxNumEvents = 256; + // const perpMarketPk = await this.createAccountForMango( + // PerpMarketLayout.span, + // ); + + const [perpMarketPk] = await PublicKey.findProgramAddress( + [ + mangoCookie.mangoGroup.toBytes(), + new Buffer('PerpMarket', 'utf-8'), + token.priceOracle.publicKey.toBytes(), + ], + this.mangoProgramId, + ); + + const eventQ = await this.createAccountForMango( + PerpEventQueueHeaderLayout.span + maxNumEvents * PerpEventLayout.span, + ); + + const bids = await this.createAccountForMango( + BookSideLayout.span, + ); + const mangoMint = token.marketIndex == 0 ? token.mint : mangoCookie.tokens[0][1].mint; + + const asks = await this.createAccountForMango( + BookSideLayout.span, + ); + + const [mngoVaultPk] = await PublicKey.findProgramAddress( + [ + perpMarketPk.toBytes(), + TOKEN_PROGRAM_ID.toBytes(), + mangoMint.toBytes(), + ], + this.mangoProgramId, + ); + + const instruction = await makeCreatePerpMarketInstruction( + this.mangoProgramId, + mangoCookie.mangoGroup, + token.priceOracle.publicKey, + perpMarketPk, + eventQ, + bids, + asks, + mangoMint, + mngoVaultPk, + this.authority.publicKey, + mangoCookie.signerKey, + I80F48.fromNumber(10), + I80F48.fromNumber(5), + I80F48.fromNumber(0.05), + I80F48.fromNumber(0), + I80F48.fromNumber(0.005), + new BN(1000), + new BN(1000), + I80F48.fromNumber(1), + I80F48.fromNumber(200), + new BN(3600), + new BN(0), + new BN(2), + new BN(2), + new BN(0), + new BN(6) + ) + + const transaction = new Transaction(); + transaction.add(instruction); + + await this.mangoClient.sendTransaction(transaction, this.authority, [], 3600, 'confirmed'); + //await sendAndConfirmTransaction(this.conn, transaction, additionalSigners); + const perpMarketData: PerpMarketData = { + publicKey: perpMarketPk, + asks: asks, + bids: bids, + eventQ: eventQ, + } + return perpMarketData; + } + + public async createAndInitPerpMarket(mangoCookie: MangoCookie, token: MangoTokenData): Promise { + const blockHash = await this.conn.getLatestBlockhashAndContext('confirmed') + const signature = await this.mangoClient.addPerpMarket( + await this.getMangoGroup(mangoCookie), + token.priceOracle.publicKey, + token.mint, + this.authority, + 10, + 5, + 0.05, + 0, + 0.0005, + 1000, + 1000, + 256, + 1, + 200, + 3600, + 0, + 2, + ); + this.conn.confirmTransaction({ blockhash: blockHash.value.blockhash, lastValidBlockHeight: blockHash.value.lastValidBlockHeight, signature: signature }, 'confirmed') + + const mangoGroup = await this.mangoClient.getMangoGroup(mangoCookie.mangoGroup); + const tokenIndex = token.marketIndex; + const perpMarketPk = mangoGroup.perpMarkets[tokenIndex].perpMarket + const perpMarket = await this.mangoClient.getPerpMarket(perpMarketPk, token.nbDecimals, 6); + // const perpMarketData : PerpMarketData = { + // publicKey: perpMarketPk, + // asks: perpMarket.asks, + // bids: perpMarket.bids, + // eventQ: perpMarket.eventQueue, + // } + + const perpMarketData: PerpMarketData = { + publicKey: PublicKey.default, + asks: PublicKey.default, + bids: PublicKey.default, + eventQ: PublicKey.default, + } + return perpMarketData + } + + public async refreshTokenCache(mangoCookie: MangoCookie, tokenData: MangoTokenData) { + + let ixupdateRootBank = mango_client_v3.makeUpdateRootBankInstruction(this.mangoProgramId, + mangoCookie.mangoGroup, + mangoCookie.mangoCache, + tokenData.rootBank, + [tokenData.nodeBank]); + + const transaction = new Transaction(); + transaction.add(ixupdateRootBank); + transaction.feePayer = this.authority.publicKey; + let hash = await this.conn.getRecentBlockhash(); + transaction.recentBlockhash = hash.blockhash; + // Sign transaction, broadcast, and confirm + await sendAndConfirmTransaction( + this.conn, + transaction, + [this.authority], + { commitment: 'confirmed' }, + ); + } + + + public async refreshRootBankCache(mangoContext: MangoCookie) { + + let ixCacheRootBank = mango_client_v3.makeCacheRootBankInstruction(this.mangoProgramId, + mangoContext.mangoGroup, + mangoContext.mangoCache, + Array.from(mangoContext.tokens).map(x => x[1].rootBank)); + + const transaction = new Transaction(); + transaction.add(ixCacheRootBank); + transaction.feePayer = this.authority.publicKey; + let hash = await this.conn.getRecentBlockhash(); + transaction.recentBlockhash = hash.blockhash; + // Sign transaction, broadcast, and confirm + await sendAndConfirmTransaction( + this.conn, + transaction, + [this.authority], + { commitment: 'confirmed' }, + ); + } + + async refreshAllTokenCache(mangoContext: MangoCookie) { + await this.refreshRootBankCache(mangoContext); + await Promise.all( + Array.from(mangoContext.tokens).map(x => this.refreshTokenCache(mangoContext, x[1])) + ); + } + + async createSpotOpenOrdersAccount(mangoContext: MangoCookie, mangoAccount: PublicKey, owner: Keypair, tokenData: TokenData): Promise { + + let mangoGroup = await this.mangoClient.getMangoGroup(mangoContext.mangoGroup); + const marketIndex = new BN(mangoGroup.tokens.findIndex(x => x.mint.equals(tokenData.mint))); + + const [spotOpenOrdersAccount, _bump] = await PublicKey.findProgramAddress( + [ + mangoAccount.toBuffer(), + marketIndex.toBuffer("le", 8), + Buffer.from("OpenOrders"), + ], this.mangoProgramId); + + const space = OpenOrders.getLayout(this.dexProgramId).span; + //await this.createAccount( spotOpenOrdersAccount, owner, DEX_ID, space); + const lamports = await this.conn.getMinimumBalanceForRentExemption(space); + + let ix2 = mango_client_v3.makeCreateSpotOpenOrdersInstruction( + this.mangoProgramId, + mangoContext.mangoGroup, + mangoAccount, + owner.publicKey, + this.dexProgramId, + spotOpenOrdersAccount, + tokenData.market.address, + mangoContext.signerKey, + ) + await this.processInstruction(ix2, [owner]); + + return spotOpenOrdersAccount; + } + + async processInstruction(ix: anchor.web3.TransactionInstruction, signers: Array) { + const transaction = new Transaction(); + transaction.add(ix); + transaction.feePayer = this.authority.publicKey; + signers.push(this.authority); + let hash = await this.conn.getRecentBlockhash(); + transaction.recentBlockhash = hash.blockhash; + // Sign transaction, broadcast, and confirm + try { + await this.mangoClient.sendTransaction(transaction, + this.authority, + [], + 3600, + 'confirmed'); + + } + catch (ex) { + const ext = ex as anchor.web3.SendTransactionError; + if (ext != null) { + console.log("Error processing instruction : " + ext.message) + } + throw ex; + } + } + + convertCookie2Json(mangoCookie: MangoCookie, cluster: Cluster) { + const oracles = Array.from(mangoCookie.tokens).map(x => { + const oracle: OracleConfig = { + publicKey: x[1].priceOracle.publicKey, + symbol: x[0] + 'USDC' + } + return oracle; + }) + + const perpMarkets = Array.from(mangoCookie.tokens).map(x => { + const perpMarket: PerpMarketConfig = { + publicKey: x[1].perpMarket.publicKey, + asksKey: x[1].perpMarket.asks, + bidsKey: x[1].perpMarket.bids, + eventsKey: x[1].perpMarket.eventQ, + marketIndex: x[1].marketIndex, + baseDecimals: x[1].nbDecimals, + baseSymbol: x[0].toString(), + name: (x[0] + 'USDC PERP'), + quoteDecimals: 6, + } + return perpMarket + }) + + const spotMarkets = Array.from(mangoCookie.tokens).map(x => { + + const spotMarket: SpotMarketConfig = { + name: x[0] + 'USDC spot Market', + marketIndex: x[1].marketIndex, + publicKey: x[1].market.address, + asksKey: x[1].market.asksAddress, + bidsKey: x[1].market.bidsAddress, + baseDecimals: x[1].nbDecimals, + baseSymbol: x[0].toString(), + eventsKey: x[1].market.decoded.eventQueue, + quoteDecimals: 6, + } + return spotMarket; + }) + + const tokenConfigs = Array.from(mangoCookie.tokens).map(x => { + const tokenConfig: TokenConfig = { + decimals: x[1].nbDecimals, + mintKey: x[1].mint, + nodeKeys: [x[1].nodeBank], + rootKey: x[1].rootBank, + symbol: x[0].toString(), + } + return tokenConfig + }) + + // quote token config + tokenConfigs.push( + { + decimals: 6, + mintKey: mangoCookie.usdcMint, + rootKey: mangoCookie.usdcRootBank, + nodeKeys: [mangoCookie.usdcNodeBank], + symbol: "USDC", + } + ) + + const groupConfig: GroupConfig = { + cluster, + mangoProgramId: this.mangoProgramId, + name: cluster, + publicKey: mangoCookie.mangoGroup, + quoteSymbol: "USDC", + oracles: oracles, + serumProgramId: this.dexProgramId, + perpMarkets: perpMarkets, + spotMarkets: spotMarkets, + tokens: tokenConfigs, + } + groupConfig["cacheKey"] = mangoCookie.mangoCache + + const groupConfigs: GroupConfig[] = [groupConfig] + const cluster_urls: Record = { + "devnet": "https://mango.devnet.rpcpool.com", + "localnet": "http://127.0.0.1:8899", + "mainnet": "https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88", + "testnet": "http://api.testnet.rpcpool.com" + }; + + const config = new Config(cluster_urls, groupConfigs); + return config.toJson(); + } + + async json2Cookie(json: any, cluster: Cluster) : Promise { + const clusterConfig = new MangoConfig(json) + const group = clusterConfig.getGroup(cluster, cluster); + let mangoCookie: MangoCookie = { + mangoGroup: null, + signerKey: null, + mangoCache: null, + usdcRootBank: null, + usdcNodeBank: null, + usdcVault: null, + tokens: new Array<[String, MangoTokenData]>(), + usdcMint: null, + MSRM: null, + }; + const mangoGroup = await this.mangoClient.getMangoGroup(group.publicKey); + + let quoteToken = mangoGroup.getQuoteTokenInfo(); + const quoteRootBankIndex = await mangoGroup.getRootBankIndex(quoteToken.rootBank); + const rootBanks = await (await mangoGroup.loadRootBanks(this.conn)); + const quoteRootBank = rootBanks[quoteRootBankIndex]; + const quoteNodeBanks = await quoteRootBank.loadNodeBanks(this.conn); + + mangoCookie.mangoGroup = group.publicKey; + mangoCookie.mangoCache = mangoGroup.mangoCache; + mangoCookie.usdcRootBank = quoteToken.rootBank; + mangoCookie.usdcNodeBank = quoteNodeBanks[0].publicKey; + mangoCookie.usdcVault = quoteNodeBanks[0].vault; + mangoCookie.usdcMint = mangoGroup.getQuoteTokenInfo().mint; + const rootBanksF = rootBanks.filter(x => x != undefined); + mangoCookie.tokens = mangoGroup.tokens.map((x, index) => { + if (x.rootBank.equals(PublicKey.default)) { + return ["", null] + } + let rootBank = rootBanksF.find(y => y.publicKey.equals(x.rootBank)) + let tokenData : MangoTokenData = { + market: null, + marketIndex: index, + mint: x.mint, + nbDecimals: 6, + rootBank: x.rootBank, + nodeBank: rootBank.nodeBanks[0], + perpMarket: null, + priceOracle: null, + startingPrice: 0 + }; + return ["", tokenData] + }) + return mangoCookie + } + + async createUser( + mangoCookie, + mangoGroup: mango_client_v3.MangoGroup, + usdcAcc: PublicKey, + rootBanks : mango_client_v3.RootBank[], + nodeBanks : mango_client_v3.NodeBank[], + fundingAccounts : PublicKey[], + authority: Keypair, + ): Promise { + const user = Keypair.generate(); + + // transfer 1 sol to the user + { + const ix = SystemProgram.transfer({ + fromPubkey: authority.publicKey, + lamports : LAMPORTS_PER_SOL, + programId: anchor.web3.SystemProgram.programId, + toPubkey: user.publicKey, + }) + await anchor.web3.sendAndConfirmTransaction( this.conn, new Transaction().add(ix), [authority]); + } + + const mangoAcc = await this.mangoClient.createMangoAccount( + mangoGroup, + user, + 1, + user.publicKey, + ); + + //const mangoAccount = await this.mangoClient.getMangoAccount(user.mangoAddress, this.dexProgramId) + const depositUsdcIx = makeDepositInstruction( + this.mangoProgramId, + mangoCookie.mangoGroup, + this.authority.publicKey, + mangoGroup.mangoCache, + mangoAcc, + mangoCookie.usdcRootBank, + mangoCookie.usdcNodeBank, + mangoCookie.usdcVault, + usdcAcc, + new BN(10_000_000_000), + ); + const blockHashInfo = await this.conn.getLatestBlockhashAndContext(); + const transaction = new Transaction() + .add(depositUsdcIx) + transaction.recentBlockhash = blockHashInfo.value.blockhash; + transaction.feePayer = this.authority.publicKey; + await this.mangoClient.sendTransaction(transaction, this.authority, [], 3600, 'confirmed') + for (const tokenIte of mangoCookie.tokens) + { + if (tokenIte[1] === null) + { + continue; + } + const marketIndex = tokenIte[1].marketIndex; + + const deposit = makeDepositInstruction( + this.mangoProgramId, + mangoCookie.mangoGroup, + this.authority.publicKey, + mangoGroup.mangoCache, + mangoAcc, + rootBanks[marketIndex].publicKey, + nodeBanks[marketIndex].publicKey, + nodeBanks[marketIndex].vault, + fundingAccounts[marketIndex], + new BN(1_000_000_000), + ); + const blockHashInfo = await this.conn.getLatestBlockhashAndContext(); + const transaction = new Transaction() + .add(deposit) + transaction.recentBlockhash = blockHashInfo.value.blockhash; + transaction.feePayer = this.authority.publicKey; + await this.mangoClient.sendTransaction(transaction, this.authority, [], 3600, 'confirmed') + } + + return { kp: user, mangoAddress: mangoAcc }; + } + + public async createAndMintUsers(mangoCookie: MangoCookie, nbUsers: number, authority : Keypair): Promise { + const mangoGroup = await this.getMangoGroup(mangoCookie) + const rootBanks = await mangoGroup.loadRootBanks(this.conn) + const nodeBanksList = await Promise.all(rootBanks.map(x => x != undefined ? x.loadNodeBanks(this.conn): Promise.resolve(undefined))) + const nodeBanks = nodeBanksList.map(x => x!=undefined ? x[0] : undefined); + const usdcAcc = await this.mintUtils.createTokenAccount(mangoCookie.usdcMint, this.authority, this.authority.publicKey); + await splToken.mintTo( + this.conn, + this.authority, + mangoCookie.usdcMint, + usdcAcc, + this.authority, + 10_000_000_000 * nbUsers, + ) + const tmpAccounts : PublicKey []= new Array(mangoCookie.tokens.length) + for (const tokenIte of mangoCookie.tokens) { + if (tokenIte[1] === null) + { + continue; + } + const acc = await this.mintUtils.createTokenAccount(tokenIte[1].mint, this.authority, this.authority.publicKey); + await splToken.mintTo( + this.conn, + this.authority, + tokenIte[1].mint, + acc, + this.authority, + 1_000_000_000 * nbUsers, + ) + tmpAccounts[tokenIte[1].marketIndex] = acc + } + + // create in batches of 10 + let users : MangoUser[] = []; + for (let i=0; i this.createUser(mangoCookie, mangoGroup, usdcAcc, rootBanks, nodeBanks, tmpAccounts, authority))); + users = users.concat(users_batch) + } + if (nbUsers%10 != 0) { + let last_batch = await Promise.all( [...Array(nbUsers%10)].map(_x => this.createUser(mangoCookie, mangoGroup, usdcAcc, rootBanks, nodeBanks, tmpAccounts, authority))); + users = users.concat(last_batch) + } + return users; + } +} diff --git a/configure_cluster/utils/mint_utils.ts b/configure_cluster/utils/mint_utils.ts new file mode 100644 index 0000000..ce1998e --- /dev/null +++ b/configure_cluster/utils/mint_utils.ts @@ -0,0 +1,88 @@ +import * as splToken from "@solana/spl-token"; +import { + PublicKey, + Connection, + Keypair, +} from "@solana/web3.js"; + +import { Market, OpenOrders } from "@project-serum/serum"; +import { PythUtils } from "./pyth_utils"; +import { SerumUtils } from "./serum_utils"; + +export interface TokenData { + mint : PublicKey, + market : Market | undefined, + startingPrice : number, + nbDecimals: number, + priceOracle: Keypair | undefined, +} + +export class MintUtils { + + private conn: Connection; + private authority: Keypair; + + private recentBlockhash: string; + private pythUtils : PythUtils; + private serumUtils : SerumUtils; + + + constructor(conn: Connection, authority: Keypair, dexProgramId: PublicKey, pythProgramId: PublicKey) { + this.conn = conn; + this.authority = authority; + this.recentBlockhash = ""; + this.pythUtils = new PythUtils(conn, authority, pythProgramId); + this.serumUtils = new SerumUtils(conn, authority, dexProgramId); + } + + async createMint(nb_decimals = 6) : Promise { + const kp = Keypair.generate(); + return await splToken.createMint(this.conn, + this.authority, + this.authority.publicKey, + this.authority.publicKey, + nb_decimals, + kp) + } + + public async updateTokenPrice(tokenData: TokenData, newPrice: number) { + this.pythUtils.updatePriceAccount(tokenData.priceOracle, + { + exponent: tokenData.nbDecimals, + aggregatePriceInfo: { + price: BigInt(newPrice), + conf: BigInt(newPrice * 0.01), + }, + }); + } + + public async createNewToken(quoteToken: PublicKey, nbDecimals = 6, startingPrice = 1_000_000) { + const mint = await this.createMint(nbDecimals); + const tokenData : TokenData = { + mint: mint, + market : await this.serumUtils.createMarket({ + baseToken : mint, + quoteToken: quoteToken, + baseLotSize : 1000, + quoteLotSize : 1000, + feeRateBps : 0, + }), + startingPrice : startingPrice, + nbDecimals: nbDecimals, + priceOracle : await this.pythUtils.createPriceAccount(), + }; + await this.updateTokenPrice(tokenData, startingPrice) + return tokenData; + } + + public async createTokenAccount(mint: PublicKey, payer: Keypair, owner: PublicKey) { + const account = Keypair.generate(); + return splToken.createAccount( + this.conn, + payer, + mint, + owner, + account + ) + } +} \ No newline at end of file diff --git a/configure_cluster/utils/pyth_utils.ts b/configure_cluster/utils/pyth_utils.ts new file mode 100755 index 0000000..589139c --- /dev/null +++ b/configure_cluster/utils/pyth_utils.ts @@ -0,0 +1,322 @@ +import * as anchor from "@project-serum/anchor"; +import * as pyth from "@pythnetwork/client"; +import { + Connection, + Keypair, + PublicKey, + SystemProgram, + Transaction, + sendAndConfirmTransaction, +} from "@solana/web3.js"; + +const PRICE_ACCOUNT_SIZE = 3312; + +export interface Price { + version?: number; + type?: number; + size?: number; + priceType?: string; + exponent?: number; + currentSlot?: bigint; + validSlot?: bigint; + twap?: Ema; + productAccountKey?: PublicKey; + nextPriceAccountKey?: PublicKey; + aggregatePriceUpdaterAccountKey?: PublicKey; + aggregatePriceInfo?: PriceInfo; + priceComponents?: PriceComponent[]; +} + +export interface PriceInfo { + price?: bigint; + conf?: bigint; + status?: string; + corpAct?: string; + pubSlot?: bigint; +} + +export interface PriceComponent { + publisher?: PublicKey; + agg?: PriceInfo; + latest?: PriceInfo; +} + +export interface Product { + version?: number; + atype?: number; + size?: number; + priceAccount?: PublicKey; + attributes?: Record; +} + +export interface Ema { + valueComponent?: bigint; + numerator?: bigint; + denominator?: bigint; +} +export class PythUtils { + + conn: Connection; + authority: Keypair; + pythProgramId: PublicKey; + + async createAccount(size : number) : Promise { + const lamports = await this.conn.getMinimumBalanceForRentExemption(size); + let address = Keypair.generate(); + + const transaction = new Transaction().add( + SystemProgram.createAccount({ + fromPubkey: this.authority.publicKey, + newAccountPubkey: address.publicKey, + lamports, + space: size, + programId : this.pythProgramId, + })) + + transaction.feePayer = this.authority.publicKey; + let hash = await this.conn.getRecentBlockhash(); + transaction.recentBlockhash = hash.blockhash; + // Sign transaction, broadcast, and confirm + await sendAndConfirmTransaction( + this.conn, + transaction, + [this.authority, address], + { commitment: 'confirmed' }, + ); + return address; + } + + async store(account: Keypair, offset: number, input: Buffer) { + + let keys = [ + { isSigner: true, isWritable: true, pubkey: account.publicKey }, + ]; + let offsetBN = new anchor.BN(offset); + + //const instructionData = Buffer.from([235, 116, 91, 200, 206, 170, 144, 120]); + const data = Buffer.concat([offsetBN.toBuffer("le", 8), input]); + + const transaction = new anchor.web3.Transaction().add( + new anchor.web3.TransactionInstruction({ + keys, + programId : this.pythProgramId, + data + } + ), + ); + transaction.feePayer = this.authority.publicKey; + let hash = await this.conn.getRecentBlockhash(); + transaction.recentBlockhash = hash.blockhash; + // Sign transaction, broadcast, and confirm + const signature = await anchor.web3.sendAndConfirmTransaction( + this.conn, + transaction, + [this.authority, account], + { commitment: 'confirmed' }, + ); + } + + constructor(conn: Connection, authority: Keypair, pythProgramId: PublicKey) { + this.conn = conn; + this.authority = authority; + this.pythProgramId = pythProgramId; + } + + async createPriceAccount(): Promise { + return this.createAccount(PRICE_ACCOUNT_SIZE); + } + + async createProductAccount(): Promise { + return this.createPriceAccount(); + } + + async updatePriceAccount(account: Keypair, data: Price) { + const buf = Buffer.alloc(512); + const d = getPriceDataWithDefaults(data); + if (!d.aggregatePriceInfo || !d.twap) + { + return; + } + d.aggregatePriceInfo = getPriceInfoWithDefaults(d.aggregatePriceInfo); + d.twap = getEmaWithDefaults(d.twap); + writePriceBuffer(buf, 0, d); + + await this.store(account, 0, buf); + } + + async updateProductAccount(account: Keypair, data: Product) { + const buf = Buffer.alloc(512); + const d = getProductWithDefaults(data); + + writeProductBuffer(buf, 0, d); + await this.store(account, 0, buf); + } +} + +function writePublicKeyBuffer(buf: Buffer, offset: number, key: PublicKey) { + buf.write(key.toBuffer().toString("binary"), offset, "binary"); +} + +function writePriceBuffer(buf: Buffer, offset: number, data: Price) { + buf.writeUInt32LE(pyth.Magic, offset + 0); + buf.writeUInt32LE(data.version, offset + 4); + buf.writeUInt32LE(data.type, offset + 8); + buf.writeUInt32LE(data.size, offset + 12); + buf.writeUInt32LE(convertPriceType(data.priceType), offset + 16); + buf.writeInt32LE(data.exponent, offset + 20); + buf.writeUInt32LE(data.priceComponents.length, offset + 24); + buf.writeBigUInt64LE(data.currentSlot, offset + 32); + buf.writeBigUInt64LE(data.validSlot, offset + 40); + buf.writeBigInt64LE(data.twap.valueComponent, offset + 48); + buf.writeBigInt64LE(data.twap.numerator, offset + 56); + buf.writeBigInt64LE(data.twap.denominator, offset + 64); + writePublicKeyBuffer(buf, offset + 112, data.productAccountKey); + writePublicKeyBuffer(buf, offset + 144, data.nextPriceAccountKey); + writePublicKeyBuffer( + buf, + offset + 176, + data.aggregatePriceUpdaterAccountKey + ); + + writePriceInfoBuffer(buf, 208, data.aggregatePriceInfo); + + let pos = offset + 240; + for (const component of data.priceComponents) { + writePriceComponentBuffer(buf, pos, component); + pos += 96; + } +} + +function writePriceInfoBuffer(buf: Buffer, offset: number, info: PriceInfo) { + buf.writeBigInt64LE(info.price, offset + 0); + buf.writeBigUInt64LE(info.conf, offset + 8); + buf.writeUInt32LE(convertPriceStatus(info.status), offset + 16); + buf.writeBigUInt64LE(info.pubSlot, offset + 24); +} + +function writePriceComponentBuffer( + buf: Buffer, + offset: number, + component: PriceComponent +) { + component.publisher.toBuffer().copy(buf, offset); + writePriceInfoBuffer(buf, offset + 32, component.agg); + writePriceInfoBuffer(buf, offset + 64, component.latest); +} + +function writeProductBuffer(buf: Buffer, offset: number, product: Product) { + let accountSize = product.size; + + if (!accountSize) { + accountSize = 48; + + for (const key in product.attributes) { + accountSize += 1 + key.length; + accountSize += 1 + product.attributes[key].length; + } + } + + buf.writeUInt32LE(pyth.Magic, offset + 0); + buf.writeUInt32LE(product.version, offset + 4); + buf.writeUInt32LE(product.atype, offset + 8); + buf.writeUInt32LE(accountSize, offset + 12); + + writePublicKeyBuffer(buf, offset + 16, product.priceAccount); + + let pos = offset + 48; + + for (const key in product.attributes) { + buf.writeUInt8(key.length, pos); + buf.write(key, pos + 1); + + pos += 1 + key.length; + + const value = product.attributes[key]; + buf.writeUInt8(value.length, pos); + buf.write(value, pos + 1); + } +} + +function convertPriceType(type: string): number { + return 1; +} + +function convertPriceStatus(status: string): number { + return 1; +} + +function getPriceDataWithDefaults({ + version = pyth.Version2, + type = 3, + size = PRICE_ACCOUNT_SIZE, + priceType = "price", + exponent = 0, + currentSlot = 0n, + validSlot = 0n, + twap = {}, + productAccountKey = PublicKey.default, + nextPriceAccountKey = PublicKey.default, + aggregatePriceUpdaterAccountKey = PublicKey.default, + aggregatePriceInfo = {}, + priceComponents = [], +}: Price): Price { + return { + version, + type, + size, + priceType, + exponent, + currentSlot, + validSlot, + twap, + productAccountKey, + nextPriceAccountKey, + aggregatePriceUpdaterAccountKey, + aggregatePriceInfo, + priceComponents, + }; +} + +function getPriceInfoWithDefaults({ + price = 0n, + conf = 0n, + status = "trading", + corpAct = "no_corp_act", + pubSlot = 0n, +}: PriceInfo): PriceInfo { + return { + price, + conf, + status, + corpAct, + pubSlot, + }; +} + +function getEmaWithDefaults({ + valueComponent = 0n, + denominator = 0n, + numerator = 0n, +}: Ema): Ema { + return { + valueComponent, + denominator, + numerator, + }; +} + +function getProductWithDefaults({ + version = pyth.Version2, + atype = 2, + size = 0, + priceAccount = PublicKey.default, + attributes = {}, +}: Product): Product { + return { + version, + atype, + size, + priceAccount, + attributes, + }; +} diff --git a/configure_cluster/utils/serum_utils.ts b/configure_cluster/utils/serum_utils.ts new file mode 100644 index 0000000..b8edb75 --- /dev/null +++ b/configure_cluster/utils/serum_utils.ts @@ -0,0 +1,360 @@ +import { Market, DexInstructions, OpenOrders, } from "@project-serum/serum"; +import { + Connection, + Keypair, + LAMPORTS_PER_SOL, + PublicKey, + SystemProgram, + TransactionInstruction, + Transaction, + sendAndConfirmTransaction, + Signer, +} from "@solana/web3.js"; + +import { BN } from "@project-serum/anchor"; +import * as splToken from "@solana/spl-token"; + +export class SerumUtils { + private authority: Keypair; + private payer : PublicKey; + private conn: Connection; + private dexProgramId : PublicKey; + constructor( + conn: Connection, + authority: Keypair, + dexProgramId : PublicKey) { + this.conn = conn; + this.authority = authority; + this.payer = authority.publicKey; + this.dexProgramId = dexProgramId; + } + + private async createAccountIx( + account: PublicKey, + space: number, + programId: PublicKey + ): Promise { + return SystemProgram.createAccount({ + newAccountPubkey: account, + fromPubkey: this.payer, + lamports: await this.conn.getMinimumBalanceForRentExemption(space), + space, + programId, + }); + } + + async createAccount( owner : Keypair, pid : PublicKey, space: number): Promise { + const newAccount = new Keypair(); + const createTx = new Transaction().add( + SystemProgram.createAccount({ + fromPubkey: owner.publicKey, + newAccountPubkey: newAccount.publicKey, + programId: pid, + lamports: await this.conn.getMinimumBalanceForRentExemption( + space + ), + space, + }) + ); + + await sendAndConfirmTransaction(this.conn, createTx, [ + owner, + newAccount, + ]); + return newAccount; + } + + async createTokenAccount(mint: PublicKey, payer: Keypair, owner: PublicKey) : Promise { + return splToken.createAccount(this.conn, + payer, + mint, + owner,) + } + + async transaction(): Promise { + return new Transaction({ + feePayer: this.authority.publicKey, + recentBlockhash: (await this.conn.getRecentBlockhash()).blockhash, + }); + } + + /** + * Create a new Serum market + */ + public async createMarket(info: CreateMarketInfo): Promise { + const owner = this.authority; + const market = await this.createAccount( owner, this.dexProgramId, Market.getLayout(this.dexProgramId).span,); + const requestQueue = await this.createAccount( owner, this.dexProgramId, 5132); + const eventQueue = await this.createAccount( owner, this.dexProgramId, 262156); + const bids = await this.createAccount( owner, this.dexProgramId, 65548); + const asks = await this.createAccount( owner, this.dexProgramId, 65548); + const quoteDustThreshold = new BN(100); + + const [vaultOwner, vaultOwnerBump] = await this.findVaultOwner( + market.publicKey + ); + + const [baseVault, quoteVault] = await Promise.all([ + splToken.createAccount(this.conn, this.authority, info.baseToken, vaultOwner, Keypair.generate()), + splToken.createAccount(this.conn, this.authority, info.quoteToken, vaultOwner, Keypair.generate()), + ]); + + + const initMarketTx = (await this.transaction()).add( + DexInstructions.initializeMarket( + toPublicKeys({ + market, + requestQueue, + eventQueue, + bids, + asks, + baseVault, + quoteVault, + baseMint: info.baseToken, + quoteMint: info.quoteToken, + baseLotSize: new BN(info.baseLotSize), + quoteLotSize: new BN(info.quoteLotSize), + feeRateBps: info.feeRateBps, + vaultSignerNonce: vaultOwnerBump, + quoteDustThreshold, + programId: this.dexProgramId, + }) + ) + ); + + await sendAndConfirmTransaction(this.conn, initMarketTx, [this.authority]); + + let mkt = await Market.load( + this.conn, + market.publicKey, + { commitment: "recent" }, + this.dexProgramId + ); + return mkt; + } + + async createWallet(lamports: number): Promise { + const wallet = Keypair.generate(); + const fundTx = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: this.authority.publicKey, + toPubkey: wallet.publicKey, + lamports, + }) + ); + + await sendAndConfirmTransaction(this.conn, fundTx, [this.authority]); + return wallet; + } + + public async getMarket(market: PublicKey) { + return Market.load(this.conn, market, {commitment: "confirmed"}, this.dexProgramId); + } + + public async createMarketMaker( + lamports: number, + tokens: [PublicKey, BN][] + ): Promise { + const account = await this.createWallet(lamports); + const tokenAccounts = {}; + const transactions = []; + for (const [token, amount] of tokens) { + const publicKey = await this.createTokenAccount( + token, + this.authority, + account.publicKey, + ); + splToken.mintTo( this.conn, this.authority, token, publicKey, this.authority, amount.toNumber()) + tokenAccounts[token.toBase58()] = publicKey; + } + + return new MarketMaker(account, tokenAccounts); + } + + public async createAndMakeMarket(baseToken: PublicKey, quoteToken: PublicKey, marketPrice: number, exp : number): Promise { + const market = await this.createMarket({ + baseToken, + quoteToken, + baseLotSize: 1000, + quoteLotSize: 100, + feeRateBps: 0, + }); + let nb = Math.floor(40000/marketPrice); + { + + const marketMaker = await this.createMarketMaker( + 1 * LAMPORTS_PER_SOL, + [ + [baseToken, new BN(nb * 10)], + [quoteToken, new BN(nb * 10)], + ] + ); + const bids = MarketMaker.makeOrders([[marketPrice * 0.995, nb]]); + const asks = MarketMaker.makeOrders([[marketPrice * 1.005, nb]]); + + await marketMaker.placeOrders(this.conn, market, bids, asks); + } + return market; + } + + public async findVaultOwner(market: PublicKey): Promise<[PublicKey, BN]> { + const bump = new BN(0); + + while (bump.toNumber() < 255) { + try { + const vaultOwner = await PublicKey.createProgramAddress( + [market.toBuffer(), bump.toArrayLike(Buffer, "le", 8)], + this.dexProgramId + ); + + return [vaultOwner, bump]; + } catch (_e) { + bump.iaddn(1); + } + } + + throw new Error("no seed found for vault owner"); + } + +} + +export interface CreateMarketInfo { + baseToken: PublicKey; + quoteToken: PublicKey; + baseLotSize: number; + quoteLotSize: number; + feeRateBps: number; +} + +export interface Order { + price: number; + size: number; +} + +export class MarketMaker { + public account: Keypair; + public tokenAccounts: { [mint: string]: PublicKey }; + + constructor( + account: Keypair, + tokenAccounts: { [mint: string]: PublicKey } + ) { + this.account = account; + this.tokenAccounts = tokenAccounts; + } + + static makeOrders(orders: [number, number][]): Order[] { + return orders.map(([price, size]) => ({ price, size })); + } + + async placeOrders(connection : Connection, market: Market, bids: Order[], asks: Order[]) { + await connection.confirmTransaction( + await connection.requestAirdrop(this.account.publicKey, 20 * LAMPORTS_PER_SOL), + "confirmed" + ); + + const baseTokenAccount = + this.tokenAccounts[market.baseMintAddress.toBase58()]; + + const quoteTokenAccount = + this.tokenAccounts[market.quoteMintAddress.toBase58()]; + + const askOrderTxs = []; + const bidOrderTxs = []; + + const placeOrderDefaultParams = { + owner: this.account.publicKey, + clientId: undefined, + openOrdersAddressKey: undefined, + openOrdersAccount: undefined, + feeDiscountPubkey: null, + }; + for (const entry of asks) { + const { transaction, signers } = + await market.makePlaceOrderTransaction( + connection, + { + payer: baseTokenAccount, + side: "sell", + price: entry.price, + size: entry.size, + orderType: "limit", + selfTradeBehavior: "decrementTake", + ...placeOrderDefaultParams, + } + ); + + askOrderTxs.push([transaction, [this.account, ...signers]]); + } + + for (const entry of bids) { + const { transaction, signers } = + await market.makePlaceOrderTransaction( + connection, + { + payer: quoteTokenAccount, + side: "buy", + price: entry.price, + size: entry.size, + orderType: "limit", + selfTradeBehavior: "decrementTake", + ...placeOrderDefaultParams, + } + ); + + bidOrderTxs.push([transaction, [this.account, ...signers]]); + } + + await this.sendAndConfirmTransactionSet( + connection, + ...askOrderTxs, + ...bidOrderTxs + ); + } + + async sendAndConfirmTransactionSet( + connection: Connection, + ...transactions: [Transaction, Signer[]][] + ): Promise { + const signatures = await Promise.all( + transactions.map(([t, s]) => + connection.sendTransaction(t, s) + ) + ); + const result = await Promise.all( + signatures.map((s) => connection.confirmTransaction(s)) + ); + + const failedTx = result.filter((r) => r.value.err != null); + + if (failedTx.length > 0) { + throw new Error(`Transactions failed: ${failedTx}`); + } + + return signatures; + } +} + +export function toPublicKeys( + obj: Record +): any { + const newObj = {}; + + for (const key in obj) { + const value = obj[key]; + + if (typeof value == "string") { + newObj[key] = new PublicKey(value); + } else if (typeof value == "object" && "publicKey" in value) { + newObj[key] = value.publicKey; + } else { + newObj[key] = value; + } + } + + return newObj; +} + +interface HasPublicKey { + publicKey: PublicKey; +} \ No newline at end of file diff --git a/configure_cluster/utils/testnet-program-name-to-id.json b/configure_cluster/utils/testnet-program-name-to-id.json new file mode 100644 index 0000000..1840d44 --- /dev/null +++ b/configure_cluster/utils/testnet-program-name-to-id.json @@ -0,0 +1,5 @@ +{ +"mango":"97Jd7kcwULGAdqsWTsdYQrqt6gQdYeowR767iTS4JFpD", +"serum_dex":"JE56RNCPMmb5x8QUo9FFpPyBjPesE3YZhFajDk4g5g7h", +"pyth_mock":"DwRHy1GcMxVtZJTcbMTe4TyKNRy8Dsb5hSiEVr2hRKZG" +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..640a723 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "devDependencies": { + "typescript": "^4.8.3", + "ts-node": "^10.9.1" + }, + "dependencies": { + "@blockworks-foundation/mango-client": "^3.6.20", + "@project-serum/anchor": "^0.25.0", + "@project-serum/serum": "^0.13.65", + "@pythnetwork/client": "^2.8.0", + "@solana/spl-token": "^0.3.5", + "@solana/web3.js": "^1.70.1", + "yarn": "^1.22.19" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100755 index 0000000..f84201d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,100 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..e8ec498 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,863 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/runtime@^7.10.5", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" + integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== + dependencies: + regenerator-runtime "^0.13.11" + +"@blockworks-foundation/mango-client@^3.6.20": + version "3.6.20" + resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-3.6.20.tgz#66ed0ae4545959fdc31ca1918de0f931de579d41" + integrity sha512-Te0i52KUyp5e8jQQZlIMsTy9fKIfefPHvkA8+NRGIH80kQcnJKKfzw3T1NxaDsc3KFMZwpuN3m4afDNpKTuF0g== + dependencies: + "@project-serum/anchor" "^0.21.0" + "@project-serum/serum" "^0.13.65" + "@project-serum/sol-wallet-adapter" "^0.2.0" + "@solana/spl-token" "^0.1.6" + "@solana/web3.js" "^1.43.5" + big.js "^6.1.1" + bn.js "^5.1.0" + buffer-layout "^1.2.1" + cross-fetch "^3.1.5" + dotenv "^10.0.0" + toformat "^2.0.0" + yargs "^17.0.1" + +"@coral-xyz/anchor@^0.28.1-beta.1": + version "0.28.1-beta.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.28.1-beta.1.tgz#0f71a3b62e38527f50d4a80e1fb4d20ba95926e2" + integrity sha512-JdKr4IQqY719wts0wzhHIffpAo4fdJ25gPLfFN75d6LMfCKvwPupyDUigaT+ac6UWTw/bDdBbJFY6QSRvlTnrA== + dependencies: + "@coral-xyz/borsh" "^0.28.0" + "@solana/web3.js" "^1.68.0" + base64-js "^1.5.1" + bn.js "^5.1.2" + bs58 "^4.0.1" + buffer-layout "^1.2.2" + camelcase "^6.3.0" + cross-fetch "^3.1.5" + crypto-hash "^1.3.0" + eventemitter3 "^4.0.7" + js-sha256 "^0.9.0" + pako "^2.0.3" + snake-case "^3.0.4" + superstruct "^0.15.4" + toml "^3.0.0" + +"@coral-xyz/borsh@^0.28.0": + version "0.28.0" + resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.28.0.tgz#fa368a2f2475bbf6f828f4657f40a52102e02b6d" + integrity sha512-/u1VTzw7XooK7rqeD7JLUSwOyRSesPUk0U37BV9zK0axJc1q0nRbKFGFLYCQ16OtdOJTTwGfGp11Lx9B45bRCQ== + dependencies: + bn.js "^5.1.2" + buffer-layout "^1.2.0" + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@noble/curves@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" + integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== + dependencies: + "@noble/hashes" "1.3.1" + +"@noble/hashes@1.3.1", "@noble/hashes@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" + integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== + +"@project-serum/anchor@^0.11.1": + version "0.11.1" + resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.11.1.tgz#155bff2c70652eafdcfd5559c81a83bb19cec9ff" + integrity sha512-oIdm4vTJkUy6GmE6JgqDAuQPKI7XM4TPJkjtoIzp69RZe0iAD9JP2XHx7lV1jLdYXeYHqDXfBt3zcq7W91K6PA== + dependencies: + "@project-serum/borsh" "^0.2.2" + "@solana/web3.js" "^1.17.0" + base64-js "^1.5.1" + bn.js "^5.1.2" + bs58 "^4.0.1" + buffer-layout "^1.2.0" + camelcase "^5.3.1" + crypto-hash "^1.3.0" + eventemitter3 "^4.0.7" + find "^0.3.0" + js-sha256 "^0.9.0" + pako "^2.0.3" + snake-case "^3.0.4" + toml "^3.0.0" + +"@project-serum/anchor@^0.21.0": + version "0.21.0" + resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.21.0.tgz#ad5fb33744991ec1900cdb2fd22707c908b12b5f" + integrity sha512-flRuW/F+iC8mitNokx82LOXyND7Dyk6n5UUPJpQv/+NfySFrNFlzuQZaBZJ4CG5g9s8HS/uaaIz1nVkDR8V/QA== + dependencies: + "@project-serum/borsh" "^0.2.4" + "@solana/web3.js" "^1.17.0" + base64-js "^1.5.1" + bn.js "^5.1.2" + bs58 "^4.0.1" + buffer-layout "^1.2.2" + camelcase "^5.3.1" + cross-fetch "^3.1.5" + crypto-hash "^1.3.0" + eventemitter3 "^4.0.7" + find "^0.3.0" + js-sha256 "^0.9.0" + pako "^2.0.3" + snake-case "^3.0.4" + toml "^3.0.0" + +"@project-serum/anchor@^0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.25.0.tgz#88ee4843336005cf5a64c80636ce626f0996f503" + integrity sha512-E6A5Y/ijqpfMJ5psJvbw0kVTzLZFUcOFgs6eSM2M2iWE1lVRF18T6hWZVNl6zqZsoz98jgnNHtVGJMs+ds9A7A== + dependencies: + "@project-serum/borsh" "^0.2.5" + "@solana/web3.js" "^1.36.0" + base64-js "^1.5.1" + bn.js "^5.1.2" + bs58 "^4.0.1" + buffer-layout "^1.2.2" + camelcase "^5.3.1" + cross-fetch "^3.1.5" + crypto-hash "^1.3.0" + eventemitter3 "^4.0.7" + js-sha256 "^0.9.0" + pako "^2.0.3" + snake-case "^3.0.4" + superstruct "^0.15.4" + toml "^3.0.0" + +"@project-serum/borsh@^0.2.2", "@project-serum/borsh@^0.2.4", "@project-serum/borsh@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.2.5.tgz#6059287aa624ecebbfc0edd35e4c28ff987d8663" + integrity sha512-UmeUkUoKdQ7rhx6Leve1SssMR/Ghv8qrEiyywyxSWg7ooV7StdpPBhciiy5eB3T0qU1BXvdRNC8TdrkxK7WC5Q== + dependencies: + bn.js "^5.1.2" + buffer-layout "^1.2.0" + +"@project-serum/serum@^0.13.65": + version "0.13.65" + resolved "https://registry.yarnpkg.com/@project-serum/serum/-/serum-0.13.65.tgz#6d3cf07912f13985765237f053cca716fe84b0b0" + integrity sha512-BHRqsTqPSfFB5p+MgI2pjvMBAQtO8ibTK2fYY96boIFkCI3TTwXDt2gUmspeChKO2pqHr5aKevmexzAcXxrSRA== + dependencies: + "@project-serum/anchor" "^0.11.1" + "@solana/spl-token" "^0.1.6" + "@solana/web3.js" "^1.21.0" + bn.js "^5.1.2" + buffer-layout "^1.2.0" + +"@project-serum/sol-wallet-adapter@^0.2.0": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@project-serum/sol-wallet-adapter/-/sol-wallet-adapter-0.2.6.tgz#b4cd25a566294354427c97c26d716112b91a0107" + integrity sha512-cpIb13aWPW8y4KzkZAPDgw+Kb+DXjCC6rZoH74MGm3I/6e/zKyGnfAuW5olb2zxonFqsYgnv7ev8MQnvSgJ3/g== + dependencies: + bs58 "^4.0.1" + eventemitter3 "^4.0.7" + +"@pythnetwork/client@^2.8.0": + version "2.19.0" + resolved "https://registry.yarnpkg.com/@pythnetwork/client/-/client-2.19.0.tgz#4b3b4fccb402d93ff00c01beec633e6f2bac94fe" + integrity sha512-0VSQ0NqBOa5EtloXbOVYZ6Wpu8CLP3oaOKVTaUMSX/HXbB00S6G+xdwF7stxo6emgrAMopotx3icEVug5Lpomg== + dependencies: + "@coral-xyz/anchor" "^0.28.1-beta.1" + "@coral-xyz/borsh" "^0.28.0" + buffer "^6.0.1" + +"@solana/buffer-layout-utils@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz#b45a6cab3293a2eb7597cceb474f229889d875ca" + integrity sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g== + dependencies: + "@solana/buffer-layout" "^4.0.0" + "@solana/web3.js" "^1.32.0" + bigint-buffer "^1.1.5" + bignumber.js "^9.0.1" + +"@solana/buffer-layout@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" + integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA== + dependencies: + buffer "~6.0.3" + +"@solana/spl-token@^0.1.6": + version "0.1.8" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.1.8.tgz#f06e746341ef8d04165e21fc7f555492a2a0faa6" + integrity sha512-LZmYCKcPQDtJgecvWOgT/cnoIQPWjdH+QVyzPcFvyDUiT0DiRjZaam4aqNUyvchLFhzgunv3d9xOoyE34ofdoQ== + dependencies: + "@babel/runtime" "^7.10.5" + "@solana/web3.js" "^1.21.0" + bn.js "^5.1.0" + buffer "6.0.3" + buffer-layout "^1.2.0" + dotenv "10.0.0" + +"@solana/spl-token@^0.3.5": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.8.tgz#8e9515ea876e40a4cc1040af865f61fc51d27edf" + integrity sha512-ogwGDcunP9Lkj+9CODOWMiVJEdRtqHAtX2rWF62KxnnSWtMZtV9rDhTrZFshiyJmxDnRL/1nKE1yJHg4jjs3gg== + dependencies: + "@solana/buffer-layout" "^4.0.0" + "@solana/buffer-layout-utils" "^0.2.0" + buffer "^6.0.3" + +"@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.43.5", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.70.1": + version "1.77.3" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.77.3.tgz#2cbeaa1dd24f8fa386ac924115be82354dfbebab" + integrity sha512-PHaO0BdoiQRPpieC1p31wJsBaxwIOWLh8j2ocXNKX8boCQVldt26Jqm2tZE4KlrvnCIV78owPLv1pEUgqhxZ3w== + dependencies: + "@babel/runtime" "^7.12.5" + "@noble/curves" "^1.0.0" + "@noble/hashes" "^1.3.0" + "@solana/buffer-layout" "^4.0.0" + agentkeepalive "^4.2.1" + bigint-buffer "^1.1.5" + bn.js "^5.0.0" + borsh "^0.7.0" + bs58 "^4.0.1" + buffer "6.0.3" + fast-stable-stringify "^1.0.0" + jayson "^4.1.0" + node-fetch "^2.6.7" + rpc-websockets "^7.5.1" + superstruct "^0.14.2" + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/connect@^3.4.33": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "20.3.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe" + integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg== + +"@types/node@^12.12.54": + version "12.20.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== + +"@types/ws@^7.4.4": + version "7.4.7" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" + integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== + dependencies: + "@types/node" "*" + +JSONStream@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.4.1: + version "8.9.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59" + integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== + +agentkeepalive@^4.2.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255" + integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg== + dependencies: + debug "^4.1.0" + depd "^2.0.0" + humanize-ms "^1.2.1" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +base-x@^3.0.2: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + +base64-js@^1.3.1, base64-js@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +big.js@^6.1.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.1.tgz#7205ce763efb17c2e41f26f121c420c6a7c2744f" + integrity sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ== + +bigint-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" + integrity sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA== + dependencies: + bindings "^1.3.0" + +bignumber.js@^9.0.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" + integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== + +bindings@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bn.js@^5.0.0, bn.js@^5.1.0, bn.js@^5.1.2, bn.js@^5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +borsh@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" + integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== + dependencies: + bn.js "^5.2.0" + bs58 "^4.0.0" + text-encoding-utf-8 "^1.0.2" + +bs58@^4.0.0, bs58@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + dependencies: + base-x "^3.0.2" + +buffer-layout@^1.2.0, buffer-layout@^1.2.1, buffer-layout@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5" + integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA== + +buffer@6.0.3, buffer@^6.0.1, buffer@^6.0.3, buffer@~6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +bufferutil@^4.0.1: + version "4.0.7" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad" + integrity sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw== + dependencies: + node-gyp-build "^4.3.0" + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^2.20.3: + version "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== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-fetch@^3.1.5: + version "3.1.6" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.6.tgz#bae05aa31a4da760969756318feeee6e70f15d6c" + integrity sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g== + dependencies: + node-fetch "^2.6.11" + +crypto-hash@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247" + integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== + +debug@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delay@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" + integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== + +depd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +dotenv@10.0.0, dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== + dependencies: + es6-promise "^4.0.3" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +eventemitter3@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +eyes@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" + integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== + +fast-stable-stringify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" + integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +find@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/find/-/find-0.3.0.tgz#4082e8fc8d8320f1a382b5e4f521b9bc50775cb8" + integrity sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw== + dependencies: + traverse-chain "~0.1.0" + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + +jayson@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.0.tgz#60dc946a85197317f2b1439d672a8b0a99cea2f9" + integrity sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A== + dependencies: + "@types/connect" "^3.4.33" + "@types/node" "^12.12.54" + "@types/ws" "^7.4.4" + JSONStream "^1.3.5" + commander "^2.20.3" + delay "^5.0.0" + es6-promisify "^5.0.0" + eyes "^0.1.8" + isomorphic-ws "^4.0.1" + json-stringify-safe "^5.0.1" + uuid "^8.3.2" + ws "^7.4.5" + +js-sha256@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" + integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +jsonparse@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.0.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-fetch@^2.6.11, node-fetch@^2.6.7: + version "2.6.11" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25" + integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w== + dependencies: + whatwg-url "^5.0.0" + +node-gyp-build@^4.3.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" + integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== + +pako@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +rpc-websockets@^7.5.1: + version "7.5.1" + resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.5.1.tgz#e0a05d525a97e7efc31a0617f093a13a2e10c401" + integrity sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w== + dependencies: + "@babel/runtime" "^7.17.2" + eventemitter3 "^4.0.7" + uuid "^8.3.2" + ws "^8.5.0" + optionalDependencies: + bufferutil "^4.0.1" + utf-8-validate "^5.0.2" + +safe-buffer@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +superstruct@^0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.14.2.tgz#0dbcdf3d83676588828f1cf5ed35cda02f59025b" + integrity sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ== + +superstruct@^0.15.4: + version "0.15.5" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.15.5.tgz#0f0a8d3ce31313f0d84c6096cd4fa1bfdedc9dab" + integrity sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ== + +text-encoding-utf-8@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" + integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== + +"through@>=2.2.7 <3": + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +toformat@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/toformat/-/toformat-2.0.0.tgz#7a043fd2dfbe9021a4e36e508835ba32056739d8" + integrity sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ== + +toml@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" + integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +traverse-chain@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1" + integrity sha512-up6Yvai4PYKhpNp5PkYtx50m3KbwQrqDwbuZP/ItyL64YEWHAvH6Md83LFLV/GRSk/BoUVwwgUzX6SOQSbsfAg== + +ts-node@^10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tslib@^2.0.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" + integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== + +typescript@^4.8.3: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + +utf-8-validate@^5.0.2: + version "5.0.10" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" + integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== + dependencies: + node-gyp-build "^4.3.0" + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +ws@^7.4.5: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +ws@^8.5.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.0.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yarn@^1.22.19: + version "1.22.19" + resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.19.tgz#4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" + integrity sha512-/0V5q0WbslqnwP91tirOvldvYISzaqhClxzyUKXYxs07yUILIs5jx/k6CFe8bvKSkds5w+eiOqta39Wk3WxdcQ== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==