ts: update to on demand code

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2024-06-28 16:39:47 +02:00
parent 95ba57627e
commit 737977f45e
5 changed files with 140 additions and 103 deletions

View File

@ -80,7 +80,7 @@
"fast-copy": "^3.0.1",
"lodash": "^4.17.21",
"node-kraken-api": "^2.2.2",
"switchboard-anchor": "npm:@coral-xyz/anchor@0.30.0"
"switchboard-anchor": "npm:@coral-xyz/anchor@0.30.1"
},
"license": "MIT",
"packageManager": "yarn@4.3.1"

View File

@ -18,7 +18,7 @@ import {
} from '@switchboard-xyz/on-demand';
import fs from 'fs';
import chunk from 'lodash/chunk';
import uniq from 'lodash/uniq';
import uniqWith from 'lodash/uniqWith';
import { Program as Anchor30Program } from 'switchboard-anchor';
import { OracleConfig } from '../src/accounts/bank';
@ -43,62 +43,72 @@ const GROUP = process.env.GROUP_OVERRIDE || MANGO_V4_MAIN_GROUP.toBase58();
client,
);
// TODO reload group once in a while
const filteredOracles = await prepareCandidateOracles(group, client);
// eslint-disable-next-line no-constant-condition
while (true) {
const slot = await client.connection.getSlot();
try {
const filteredOracles = await prepareCandidateOracles(group, client);
for (let i = 0; i < 10; i++) {
const slot = await client.connection.getSlot();
const staleOracles = await filterForStaleOracles(
filteredOracles,
client,
slot,
);
const staleOracles = await filterForStaleOracles(
filteredOracles,
client,
slot,
);
const varianceThresholdCrossedOracles =
await filterForVarianceThresholdOracles(
filteredOracles,
client,
sbOnDemandProgram,
crossbarClient,
);
const varianceThresholdCrossedOracles =
await filterForVarianceThresholdOracles(
filteredOracles,
client,
sbOnDemandProgram,
crossbarClient,
);
const oraclesToCrank = uniq(
[...staleOracles, ...varianceThresholdCrossedOracles],
function (item) {
return item.oracle.oraclePk.toString();
},
);
const oraclesToCrank = uniqWith(
[...staleOracles, ...varianceThresholdCrossedOracles],
function (item) {
return item.oracle.oraclePk.toString();
},
);
const pullIxs: TransactionInstruction[] = [];
const lutOwners: (PublicKey | Oracle)[] = [];
for (const oracle of oraclesToCrank) {
await preparePullIx(sbOnDemandProgram, oracle, queue, lutOwners, pullIxs);
const pullIxs: TransactionInstruction[] = [];
const lutOwners: (PublicKey | Oracle)[] = [];
for (const oracle of oraclesToCrank) {
await preparePullIx(
sbOnDemandProgram,
oracle,
queue,
lutOwners,
pullIxs,
);
}
for (const c of chunk(pullIxs, 5, false)) {
const tx = await asV0Tx({
connection,
ixs: [...c],
signers: [user],
computeUnitPrice: 200_000,
computeUnitLimitMultiple: 1.3,
lookupTables: await loadLookupTables(lutOwners),
});
const txOpts = {
commitment: 'processed' as Commitment,
skipPreflight: true,
maxRetries: 0,
};
const sim = await client.connection.simulateTransaction(tx, txOpts);
const sig = await client.connection.sendTransaction(tx, txOpts);
console.log(`updated in ${sig}`); // TODO add token names
}
await new Promise((r) => setTimeout(r, 5000));
}
} catch (error) {
console.log(error);
}
for (const c of chunk(pullIxs, 5)) {
const tx = await asV0Tx({
connection,
ixs: [...c],
signers: [user],
computeUnitPrice: 200_000,
computeUnitLimitMultiple: 1.3,
lookupTables: await loadLookupTables(lutOwners),
});
const txOpts = {
commitment: 'processed' as Commitment,
skipPreflight: true,
maxRetries: 0,
};
const sim = await client.connection.simulateTransaction(tx, txOpts);
const sig = await client.connection.sendTransaction(tx, txOpts);
console.log(`updated in ${sig}`); // TODO add token names
}
await new Promise((r) => setTimeout(r, 5000));
}
})();
@ -123,12 +133,17 @@ async function preparePullIx(
queue: queue,
maxVariance: decodedPullFeed.maxVariance.toNumber(),
minResponses: decodedPullFeed.minResponses,
numSignatures: 3, // TODO hardcoded
minSampleSize: decodedPullFeed.minSampleSize,
maxStaleness: decodedPullFeed.maxStaleness,
numSignatures: 1,
// TODO: I think these are not required
// minSampleSize: decodedPullFeed.minSampleSize,
// maxStaleness: decodedPullFeed.maxStaleness,
};
const [pullIx, responses, success] = await pullFeed.fetchUpdateIx(conf);
if (pullIx === undefined) {
return;
}
const lutOwners_ = [...responses.map((x) => x.oracle), pullFeed.pubkey];
lutOwners.push(...lutOwners_);
pullIxs.push(pullIx!);
@ -171,7 +186,14 @@ async function filterForVarianceThresholdOracles(
crossBarSim[0].results.length;
if ((res.price - simPrice) / res.price > 0.01) {
console.log(
`- Variance threshold crossed oracles candidate ${item.oracle.name}`,
);
varianceThresholdCrossedOracles.push(item);
} else {
console.log(
`- Variance threshold crossed oracles non candidate ${item.oracle.name}`,
);
}
}
return varianceThresholdCrossedOracles;
@ -200,12 +222,15 @@ async function filterForStaleOracles(
);
if (slot > res.lastUpdatedSlot) {
console.log(`- Stale oracle candidate ${item.oracle.name}`);
if (
slot - res.lastUpdatedSlot >
item.oracle.oracleConfig.maxStalenessSlots.toNumber()
) {
staleOracles.push(item);
}
} else {
console.log(`- Stale oracle non candidate ${item.oracle.name}`);
}
}
return staleOracles;
@ -213,11 +238,23 @@ async function filterForStaleOracles(
async function prepareCandidateOracles(group: Group, client: MangoClient) {
const oracles = getOraclesForMangoGroup(group);
oracles.push(...extendOraclesManually());
oracles.push(...extendOraclesManually(CLUSTER));
const ais = (
await Promise.all(
chunk(
oracles.map((item) => item.oraclePk),
50,
false,
).map(
async (chunk) =>
await client.program.provider.connection.getMultipleAccountsInfo(
chunk,
),
),
)
).flat();
const ais = await client.program.provider.connection.getMultipleAccountsInfo(
oracles.map((item) => item.oraclePk),
);
for (const [idx, ai] of ais.entries()) {
if (ai == null || ai.data == null) {
throw new Error(
@ -239,15 +276,28 @@ async function prepareCandidateOracles(group: Group, client: MangoClient) {
return filteredOracles;
}
function extendOraclesManually() {
function extendOraclesManually(cluster: Cluster) {
if (cluster == 'devnet') {
return [
{
oraclePk: new PublicKey('EtbG8PSDCyCSmDH8RE4Nf2qTV9d6P6zShzHY2XWvjFJf'),
oracleConfig: {
confFilter: I80F48.fromString('0.1'),
maxStalenessSlots: new BN(5),
},
name: 'BTC/USD',
},
];
}
return [
{
oraclePk: new PublicKey('EtbG8PSDCyCSmDH8RE4Nf2qTV9d6P6zShzHY2XWvjFJf'),
// https://ondemand.switchboard.xyz/solana/mainnet/user/8SSLjXBEVk9nesbhi9UMCA32uijbVBUqWoKPPQPTekzt/
oraclePk: new PublicKey('EFSBitmy2LZexy6CqYVDAqzWN6wYHZmHkFZxmUjNgwpb'),
oracleConfig: {
confFilter: I80F48.fromString('0.1'),
maxStalenessSlots: new BN(5),
confFilter: I80F48.fromString('1000'),
maxStalenessSlots: new BN(-1),
},
name: 'BTC/USD',
name: 'MNGO/USD',
},
];
}
@ -342,7 +392,7 @@ async function setupSwitchboard(client: MangoClient) {
}
const crossbarClient = new CrossbarClient(
'https://crossbar.switchboard.xyz',
true,
false,
);
return { sbOnDemandProgram, crossbarClient, queue };
}

View File

@ -1,3 +1,4 @@
import { LISTING_PRESETS } from '@blockworks-foundation/mango-v4-settings/lib/helpers/listingTools';
import {
Cluster,
Commitment,
@ -21,15 +22,15 @@ import {
AnchorProvider,
Wallet,
} from 'switchboard-anchor';
//basic configuration
// basic configuration
const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
const SWAP_VALUE = '100';
const TOKEN_MINT = 'SLCLww7nc1PD2gQPQdGayHviVVcpMthnqUz2iWKhNQV';
const TOKEN_MINT = 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac';
const FALLBACK_POOL_NAME: 'orcaPoolAddress' | 'raydiumPoolAddress' =
'raydiumPoolAddress';
const FALLBACK_POOL = '5hbCYGfGGenjeYzfWGFZ8zoXTo3fcHw5KkdsnXpXdbeX';
const TOKEN_SYMBOL = 'SLCL';
//basic configuration
const FALLBACK_POOL = '34tFULRrRwh4bMcBLPtJaNqqe5pVgGZACi5sR8Xz95KC';
const TOKEN_SYMBOL = 'MNGO';
// basic configuration
const pythUsdOracle = 'Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD';
const switchboardUsdDaoOracle = 'FwYfsmj5x8YZXtQBNo2Cz8TE7WRCMFqA6UTffK4xQKMH';
@ -73,6 +74,10 @@ async function setupSwitchboard(userProvider: AnchorProvider) {
}
(async function main(): Promise<void> {
const tier = Object.values(LISTING_PRESETS).find(
(x) => x.preset_name === 'C',
);
const { userProvider, connection, user } = await setupAnchor();
const { sbOnDemandProgram, crossbarClient, queue } = await setupSwitchboard(
@ -93,15 +98,14 @@ async function setupSwitchboard(userProvider: AnchorProvider) {
maxRetries: 0,
};
// TODO @Adrian
const conf = {
name: `${TOKEN_SYMBOL}/USD`, // the feed name (max 32 bytes)
queue, // the queue of oracles to bind to
maxVariance: 1.0, // allow 1% variance between submissions and jobs
minResponses: 2, // minimum number of responses of jobs to allow
numSignatures: 3, // number of signatures to fetch per update
maxVariance: 10, // allow 1% variance between submissions and jobs
minResponses: 1, // minimum number of responses of jobs to allow
numSignatures: 1, // number of signatures to fetch per update
minSampleSize: 1, // minimum number of responses to sample
maxStaleness: 60, // maximum staleness of responses in seconds to sample
maxStaleness: tier!.maxStalenessSlots!, // maximum staleness of responses in seconds to sample
};
console.log('Initializing new data feed');

View File

@ -1,7 +1,7 @@
import { AnchorProvider, Wallet } from '@coral-xyz/anchor';
import { Magic as PythMagic } from '@pythnetwork/client';
import { AccountInfo, Connection, Keypair, PublicKey } from '@solana/web3.js';
import { SB_ON_DEMAND_PID } from '@switchboard-xyz/on-demand';
import { SB_ON_DEMAND_PID, toFeedValue } from '@switchboard-xyz/on-demand';
import SwitchboardProgram from '@switchboard-xyz/sbv2-lite';
import Big from 'big.js';
import BN from 'bn.js';
@ -130,16 +130,19 @@ export function parseSwitchboardOnDemandOracle(
accountInfo.data,
);
// This code was used when decodedPullFeed.result used to be empty
// const feedValue = toFeedValue(decodedPullFeed.submissions, new BN(0));
// const price = new Big(feedValue?.value.toString()).div(1e18);
// const lastUpdatedSlot = feedValue!.slot!.toNumber(); // TODO the !
// const stdDeviation = 0; // TODO the 0
if (decodedPullFeed.result == undefined) {
const feedValue = toFeedValue(decodedPullFeed.submissions, new BN(0));
console.log(decodedPullFeed.submissions);
console.log(feedValue);
const price = new Big(feedValue?.value.toString()).div(1e18);
const lastUpdatedSlot = feedValue!.slot!.toNumber(); // TODO the !
const stdDeviation = 0; // TODO the 0
return { price, lastUpdatedSlot, uiDeviation: stdDeviation };
}
const price = new Big(decodedPullFeed.result.value.toString()).div(1e18);
const lastUpdatedSlot = decodedPullFeed.result.slot.toNumber();
const stdDeviation = decodedPullFeed.result.stdDev.toNumber();
return { price, lastUpdatedSlot, uiDeviation: stdDeviation };
} catch (e) {
console.log(

View File

@ -108,7 +108,7 @@
superstruct "^0.15.4"
toml "^3.0.0"
"@coral-xyz/anchor@^0.30.0":
"@coral-xyz/anchor@^0.30.0", "switchboard-anchor@npm:@coral-xyz/anchor@0.30.1":
version "0.30.1"
resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.1.tgz#17f3e9134c28cd0ea83574c6bab4e410bcecec5d"
integrity sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ==
@ -145,7 +145,7 @@
bn.js "^5.1.2"
buffer-layout "^1.2.0"
"@coral-xyz/borsh@^0.30.0", "@coral-xyz/borsh@^0.30.1":
"@coral-xyz/borsh@^0.30.1":
version "0.30.1"
resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.30.1.tgz#869d8833abe65685c72e9199b8688477a4f6b0e3"
integrity sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ==
@ -3007,26 +3007,6 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
"switchboard-anchor@npm:@coral-xyz/anchor@0.30.0":
version "0.30.0"
resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.0.tgz#52acdba504b0008f1026d3a4bbbcb2d4feb5c69e"
integrity sha512-qreDh5ztiRHVnCbJ+RS70NJ6aSTPBYDAgFeQ7Z5QvaT5DcDIhNyt4onOciVz2ieIE1XWePOJDDu9SbNvPGBkvQ==
dependencies:
"@coral-xyz/borsh" "^0.30.0"
"@noble/hashes" "^1.3.1"
"@solana/web3.js" "^1.68.0"
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"
pako "^2.0.3"
snake-case "^3.0.4"
superstruct "^0.15.4"
toml "^3.0.0"
table@^6.0.9:
version "6.8.0"
resolved "https://registry.npmjs.org/table/-/table-6.8.0.tgz"