Skip to content

Commit

Permalink
Merge pull request #340 from drift-labs/master
Browse files Browse the repository at this point in the history
mainnet
  • Loading branch information
wphan authored Jan 6, 2025
2 parents d22096a + 60e7671 commit 7acca3f
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 174 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"main": "lib/index.js",
"license": "Apache-2.0",
"dependencies": {
"@drift-labs/jit-proxy": "0.12.29",
"@drift-labs/sdk": "2.105.0-beta.2",
"@drift-labs/jit-proxy": "0.12.37",
"@drift-labs/sdk": "2.106.0-beta.2",
"@opentelemetry/api": "1.7.0",
"@opentelemetry/auto-instrumentations-node": "0.31.2",
"@opentelemetry/exporter-prometheus": "0.31.0",
Expand Down
100 changes: 24 additions & 76 deletions src/bots/pythLazerCranker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import { PriceUpdateAccount } from '@pythnetwork/pyth-solana-receiver/lib/PythSo
import {
BlockhashSubscriber,
DriftClient,
getOracleClient,
getPythLazerOraclePublicKey,
OracleClient,
OracleSource,
PriorityFeeSubscriber,
TxSigAndSlot,
} from '@drift-labs/sdk';
Expand All @@ -19,7 +16,7 @@ import {
} from '@solana/web3.js';
import { chunks, simulateAndGetTxWithCUs, sleepMs } from '../utils';
import { Agent, setGlobalDispatcher } from 'undici';
import { PythLazerClient } from '@pythnetwork/pyth-lazer-sdk';
import { PythLazerSubscriber } from '../pythLazerSubscriber';

setGlobalDispatcher(
new Agent({
Expand All @@ -30,20 +27,16 @@ setGlobalDispatcher(
const SIM_CU_ESTIMATE_MULTIPLIER = 1.5;

export class PythLazerCrankerBot implements Bot {
private wsClient: PythLazerClient;
private pythOracleClient: OracleClient;
private pythLazerClient: PythLazerSubscriber;
readonly decodeFunc: (name: string, data: Buffer) => PriceUpdateAccount;

public name: string;
public dryRun: boolean;
private intervalMs: number;
private feedIdChunkToPriceMessage: Map<number[], string> = new Map();
public defaultIntervalMs = 30_000;

private blockhashSubscriber: BlockhashSubscriber;
private health: boolean = true;
private slotStalenessThresholdRestart: number = 300;
private txSuccessRateThreshold: number = 0.5;

constructor(
private globalConfig: GlobalConfig,
Expand All @@ -64,16 +57,20 @@ export class PythLazerCrankerBot implements Bot {
throw new Error('Only devnet drift env is supported');
}

const hermesEndpointParts = globalConfig.hermesEndpoint.split('?token=');
this.wsClient = new PythLazerClient(
hermesEndpointParts[0],
hermesEndpointParts[1]
const updateConfigs = this.crankConfigs.updateConfigs;
const feedIdChunks = chunks(Object.keys(updateConfigs), 11).map((chunk) =>
chunk.map((alias) => {
return updateConfigs[alias].feedId;
})
);

this.pythOracleClient = getOracleClient(
OracleSource.PYTH_LAZER,
driftClient.connection,
driftClient.program
if (!this.globalConfig.lazerEndpoint || !this.globalConfig.lazerToken) {
throw new Error('Missing lazerEndpoint or lazerToken in global config');
}
this.pythLazerClient = new PythLazerSubscriber(
this.globalConfig.lazerEndpoint,
this.globalConfig.lazerToken,
feedIdChunks
);
this.decodeFunc =
this.driftClient.program.account.pythLazerOracle.coder.accounts.decodeUnchecked.bind(
Expand All @@ -83,9 +80,6 @@ export class PythLazerCrankerBot implements Bot {
this.blockhashSubscriber = new BlockhashSubscriber({
connection: driftClient.connection,
});
this.txSuccessRateThreshold = crankConfigs.txSuccessRateThreshold;
this.slotStalenessThresholdRestart =
crankConfigs.slotStalenessThresholdRestart;
}

async init(): Promise<void> {
Expand All @@ -95,70 +89,23 @@ export class PythLazerCrankerBot implements Bot {
await this.driftClient.fetchMarketLookupTableAccount()
);

const updateConfigs = this.crankConfigs.updateConfigs;

let subscriptionId = 1;
for (const configChunk of chunks(Object.keys(updateConfigs), 11)) {
const priceFeedIds: number[] = configChunk.map((alias) => {
return updateConfigs[alias].feedId;
});

const sendMessage = () =>
this.wsClient.send({
type: 'subscribe',
subscriptionId,
priceFeedIds,
properties: ['price'],
chains: ['solana'],
deliveryFormat: 'json',
channel: 'fixed_rate@200ms',
jsonBinaryEncoding: 'hex',
});
if (this.wsClient.ws.readyState != 1) {
this.wsClient.ws.addEventListener('open', () => {
sendMessage();
});
} else {
sendMessage();
}

this.wsClient.addMessageListener((message) => {
switch (message.type) {
case 'json': {
if (message.value.type == 'streamUpdated') {
if (message.value.solana?.data)
this.feedIdChunkToPriceMessage.set(
priceFeedIds,
message.value.solana.data
);
}
break;
}
default: {
break;
}
}
});
subscriptionId++;
}
await this.pythLazerClient.subscribe();

this.priorityFeeSubscriber?.updateAddresses(
Object.keys(this.feedIdChunkToPriceMessage)
.flat()
.map((feedId) =>
getPythLazerOraclePublicKey(
this.driftClient.program.programId,
Number(feedId)
)
this.pythLazerClient.allSubscribedIds.map((feedId) =>
getPythLazerOraclePublicKey(
this.driftClient.program.programId,
Number(feedId)
)
)
);
}

async reset(): Promise<void> {
logger.info(`Resetting ${this.name} bot`);
this.blockhashSubscriber.unsubscribe();
await this.driftClient.unsubscribe();
this.wsClient.ws.close();
this.pythLazerClient.unsubscribe();
}

async startIntervalLoop(intervalMs = this.intervalMs): Promise<void> {
Expand Down Expand Up @@ -187,9 +134,10 @@ export class PythLazerCrankerBot implements Bot {

async runCrankLoop() {
for (const [
feedIds,
feedIdsStr,
priceMessage,
] of this.feedIdChunkToPriceMessage.entries()) {
] of this.pythLazerClient.feedIdChunkToPriceMessage.entries()) {
const feedIds = this.pythLazerClient.getPriceFeedIdsFromHash(feedIdsStr);
const ixs = [
ComputeBudgetProgram.setComputeUnitLimit({
units: 1_400_000,
Expand Down
2 changes: 1 addition & 1 deletion src/bundleSender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes';
import { sleepMs } from './utils';

export const jitoBundlePriceEndpoint =
'ws://bundles-api-rest.jito.wtf/api/v1/bundles/tip_stream';
'wss://bundles.jito.wtf/api/v1/bundles/tip_stream';

const logPrefix = '[BundleSender]';
const MS_DELAY_BEFORE_CHECK_INCLUSION = 30_000;
Expand Down
16 changes: 0 additions & 16 deletions src/experimental-bots/entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import { PythPriceFeedSubscriber } from '../pythPriceFeedSubscriber';
import { SwiftMaker } from './swift/makerExample';
import { SwiftTaker } from './swift/takerExample';
import * as net from 'net';
import { PythLazerClient } from '@pythnetwork/pyth-lazer-sdk';

setGlobalDispatcher(
new Agent({
Expand Down Expand Up @@ -308,20 +307,6 @@ const runBot = async () => {
if (!config.botConfigs?.fillerMultithreaded) {
throw new Error('fillerMultithreaded bot config not found');
}

let pythLazerClient: PythLazerClient | undefined;
if (config.global.driftEnv! === 'devnet') {
if (!config.global.lazerEndpoint || !config.global.lazerToken) {
throw new Error(
'Must set environment variables LAZER_ENDPOINT and LAZER_TOKEN'
);
}
pythLazerClient = new PythLazerClient(
config.global.lazerEndpoint,
config.global.lazerToken
);
}

// Ensure that there are no duplicate market indexes in the Array<number[]> marketIndexes config
const marketIndexes = new Set<number>();
for (const marketIndexList of config.botConfigs.fillerMultithreaded
Expand Down Expand Up @@ -350,7 +335,6 @@ const runBot = async () => {
},
bundleSender,
pythPriceSubscriber,
pythLazerClient,
[]
);
bots.push(fillerMultithreaded);
Expand Down
18 changes: 17 additions & 1 deletion src/experimental-bots/filler-common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ const serializeDLOBNode = (
): SerializedDLOBNode => {
if (node instanceof OrderNode) {
return {
type: node.constructor.name,
type: getOrderNodeType(node),
userAccountData: userAccountData,
order: serializeOrder(node.order),
userAccount: node.userAccount,
Expand All @@ -278,6 +278,22 @@ const serializeDLOBNode = (
}
};

const getOrderNodeType = (node: OrderNode): string => {
if (node instanceof TakingLimitOrderNode) {
return 'TakingLimitOrderNode';
} else if (node instanceof RestingLimitOrderNode) {
return 'RestingLimitOrderNode';
} else if (node instanceof FloatingLimitOrderNode) {
return 'FloatingLimitOrderNode';
} else if (node instanceof MarketOrderNode) {
return 'MarketOrderNode';
} else if (node instanceof SwiftOrderNode) {
return 'SwiftOrderNode';
} else {
throw new Error('Invalid node type');
}
};

export const deserializeNodeToFill = (
serializedNode: SerializedNodeToFill
): NodeToFillWithBuffer => {
Expand Down
Loading

0 comments on commit 7acca3f

Please sign in to comment.