Skip to content

Commit d431ba8

Browse files
authored
Merge pull request #552 from drift-labs/master
mainnet
2 parents 5b01aee + c3e2612 commit d431ba8

File tree

8 files changed

+250
-14
lines changed

8 files changed

+250
-14
lines changed

DockerfileLinked

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
FROM public.ecr.aws/docker/library/node:20 AS builder
2+
3+
# Set working dir first so all paths are relative to /app
4+
WORKDIR /app
5+
6+
7+
COPY ./keeper-bots-v2 .
8+
9+
WORKDIR /app/drift-common/protocol/sdk
10+
RUN rm -rf ../../../node_modules
11+
RUN yarn && yarn build
12+
13+
WORKDIR /app/drift-common/common-ts
14+
RUN yarn && yarn build
15+
16+
WORKDIR /app
17+
COPY ./protocol-v2/sdk ./protocol-v2/sdk
18+
WORKDIR /app/protocol-v2/sdk
19+
RUN yarn && yarn build
20+
21+
# Inject local dependency
22+
WORKDIR /app
23+
RUN node -e "\
24+
const fs = require('fs'); \
25+
const pkg = JSON.parse(fs.readFileSync('package.json')); \
26+
pkg.dependencies = pkg.dependencies || {}; \
27+
pkg.dependencies['@drift-labs/sdk'] = 'file:./protocol-v2/sdk'; \
28+
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));"
29+
30+
RUN node -e "console.log(require('./package.json').dependencies['@drift-labs/sdk'])"
31+
32+
WORKDIR /app
33+
RUN yarn && yarn build
34+
35+
# Final minimal image
36+
FROM public.ecr.aws/docker/library/node:20-alpine
37+
WORKDIR /app
38+
39+
COPY --from=builder /app/lib ./lib/
40+
# 'bigint-buffer' native lib for performance
41+
# @triton-one/yellowstone-grpc for .wasm lib
42+
RUN apk add --virtual .build python3 make g++ &&\
43+
npm install bigint-buffer @triton-one/[email protected] [email protected] &&\
44+
apk del .build &&\
45+
rm -rf /root/.cache/ /root/.npm /usr/local/lib/node_modules
46+
47+
# Create a non-root user
48+
RUN addgroup -S nonroot && \
49+
adduser -S -u 1001 -G root nonroot && \
50+
chown -R nonroot:root /app && \
51+
chmod 755 /app
52+
USER nonroot
53+
54+
CMD [ "node", "./lib/index.js" ]

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
"main": "lib/index.js",
66
"license": "Apache-2.0",
77
"dependencies": {
8-
"@drift-labs/jit-proxy": "0.21.77",
9-
"@drift-labs/sdk": "2.144.0-beta.1",
8+
"@drift-labs/jit-proxy": "0.21.86",
9+
"@drift-labs/sdk": "2.145.0-beta.3",
1010
"@drift/common": "file:./drift-common/common-ts",
1111
"@opentelemetry/api": "1.7.0",
1212
"@opentelemetry/auto-instrumentations-node": "^0.62.1",

src/bots/pythLazerCranker.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
DevnetSpotMarkets,
99
DriftClient,
1010
getVariant,
11+
isOneOfVariant,
1112
MainnetPerpMarkets,
1213
MainnetSpotMarkets,
1314
PriorityFeeSubscriber,
@@ -84,14 +85,18 @@ export class PythLazerCrankerBot implements Bot {
8485

8586
const allFeedIds: number[] = [];
8687
for (const market of [...spotMarkets, ...perpMarkets]) {
88+
const onChainMarket = market.symbol.toUpperCase().includes('PERP')
89+
? this.driftClient.getPerpMarketAccount(market.marketIndex)
90+
: this.driftClient.getSpotMarketAccount(market.marketIndex);
8791
if (
8892
(this.crankConfigs.onlyCrankUsedOracles &&
8993
!getVariant(market.oracleSource).toLowerCase().includes('lazer')) ||
9094
market.pythLazerId == undefined
9195
)
9296
continue;
9397
if (
94-
this.crankConfigs.ignorePythLazerIds?.includes(market.pythLazerId!)
98+
this.crankConfigs.ignorePythLazerIds?.includes(market.pythLazerId!) ||
99+
isOneOfVariant(onChainMarket?.status, ['delisted', 'settlement'])
95100
) {
96101
continue;
97102
}

src/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ export type SwitchboardCrankerBotConfig = BaseBotConfig & {
146146
writableAccounts?: string[];
147147
};
148148

149+
export type LpPoolTargetBaseCrankerConfig = BaseBotConfig & {
150+
intervalMs: number;
151+
lpPoolId: number;
152+
};
153+
149154
export type BotConfigMap = {
150155
fillerMultithreaded?: FillerMultiThreadedConfig;
151156
spotFillerMultithreaded?: FillerMultiThreadedConfig;
@@ -167,6 +172,7 @@ export type BotConfigMap = {
167172
swiftMaker?: BaseBotConfig;
168173
swiftPlacer?: BaseBotConfig;
169174
jitMaker?: JitMakerConfig;
175+
lpTargetBaseCranker?: LpPoolTargetBaseCrankerConfig;
170176
};
171177

172178
export interface GlobalConfig {

src/experimental-bots/entrypoint.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { SwiftMaker } from './swift/makerExample';
4141
import { SwiftTaker } from './swift/takerExample';
4242
import * as net from 'net';
4343
import { SwiftPlacer } from './swift/placerExample';
44+
import { LpPoolTargetBaseCranker } from './lp-pool/targetBaseCranker';
4445

4546
setGlobalDispatcher(
4647
new Agent({
@@ -446,6 +447,15 @@ const runBot = async () => {
446447
bots.push(signedMsgMaker);
447448
}
448449

450+
if (configHasBot(config, 'lpTargetBaseCranker')) {
451+
const lpTargetBaseCranker = new LpPoolTargetBaseCranker(
452+
driftClient,
453+
config.botConfigs?.lpTargetBaseCranker?.intervalMs || 300_000,
454+
config.botConfigs!.lpTargetBaseCranker!.lpPoolId
455+
);
456+
bots.push(lpTargetBaseCranker);
457+
}
458+
449459
// Initialize bots
450460
logger.info(`initializing bots`);
451461
await Promise.all(bots.map((bot: any) => bot.init()));
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import {
2+
BlockhashSubscriber,
3+
ConstituentMap,
4+
DriftClient,
5+
encodeName,
6+
getConstituentTargetBasePublicKey,
7+
getLpPoolPublicKey,
8+
LPPoolAccount,
9+
PriorityFeeMethod,
10+
PriorityFeeSubscriber,
11+
} from '@drift-labs/sdk';
12+
import { ComputeBudgetProgram } from '@solana/web3.js';
13+
import { simulateAndGetTxWithCUs } from '../../utils';
14+
15+
export class LpPoolTargetBaseCranker {
16+
interval: NodeJS.Timeout | null = null;
17+
lpPoolAccount?: LPPoolAccount;
18+
constituentMap: ConstituentMap;
19+
20+
priorityFeeSubscriber: PriorityFeeSubscriber;
21+
blockhashSubscriber: BlockhashSubscriber;
22+
23+
public constructor(
24+
private driftClient: DriftClient,
25+
private intervalMs: number,
26+
private lpPoolId: number
27+
) {
28+
this.constituentMap = new ConstituentMap({
29+
driftClient: this.driftClient,
30+
subscriptionConfig: {
31+
type: 'websocket',
32+
resubTimeoutMs: 30_000,
33+
},
34+
lpPoolId: lpPoolId,
35+
});
36+
this.priorityFeeSubscriber = new PriorityFeeSubscriber({
37+
connection: this.driftClient.connection,
38+
frequencyMs: 30_000,
39+
addresses: [
40+
getConstituentTargetBasePublicKey(
41+
this.driftClient.program.programId,
42+
getLpPoolPublicKey(this.driftClient.program.programId, this.lpPoolId)
43+
),
44+
],
45+
priorityFeeMethod: PriorityFeeMethod.SOLANA,
46+
slotsToCheck: 10,
47+
});
48+
49+
this.blockhashSubscriber = new BlockhashSubscriber({
50+
connection: this.driftClient.connection,
51+
});
52+
}
53+
54+
async init() {
55+
await this.constituentMap.sync();
56+
await this.constituentMap.subscribe();
57+
this.lpPoolAccount = await this.driftClient.getLpPoolAccount(this.lpPoolId);
58+
await this.blockhashSubscriber.subscribe();
59+
await this.priorityFeeSubscriber.subscribe();
60+
await this.startInterval();
61+
}
62+
63+
public async healthCheck() {
64+
return true;
65+
}
66+
67+
private async getBlockhashForTx(): Promise<string> {
68+
const cachedBlockhash = this.blockhashSubscriber.getLatestBlockhash(5);
69+
if (cachedBlockhash) {
70+
return cachedBlockhash.blockhash as string;
71+
}
72+
73+
const recentBlockhash =
74+
await this.driftClient.connection.getLatestBlockhash({
75+
commitment: 'confirmed',
76+
});
77+
78+
return recentBlockhash.blockhash;
79+
}
80+
81+
async startInterval() {
82+
setInterval(async () => {
83+
this.lpPoolAccount = await this.driftClient.getLpPoolAccount(
84+
this.lpPoolId
85+
);
86+
}, 10_000);
87+
88+
this.interval = setInterval(async () => {
89+
const perpMarkets = this.driftClient.getPerpMarketAccounts();
90+
const marketIndexes = perpMarkets
91+
.filter((market) => market.lpStatus == 1)
92+
.map((market) => market.marketIndex);
93+
if (marketIndexes.length === 0) {
94+
console.warn(
95+
`No markets found with LP status for pool ${this.lpPoolId}. Skipping update.`
96+
);
97+
return;
98+
}
99+
if (!this.lpPoolAccount) {
100+
this.lpPoolAccount = await this.driftClient.getLpPoolAccount(
101+
this.lpPoolId
102+
);
103+
}
104+
const ixs = [
105+
ComputeBudgetProgram.setComputeUnitLimit({
106+
units: 1_400_000, // will be overridden by simulateTx
107+
}),
108+
];
109+
ixs.push(
110+
ComputeBudgetProgram.setComputeUnitPrice({
111+
microLamports: Math.floor(
112+
this.priorityFeeSubscriber.getCustomStrategyResult() *
113+
this.driftClient.txSender.getSuggestedPriorityFeeMultiplier()
114+
),
115+
})
116+
);
117+
const updateIxs =
118+
await this.driftClient.getAllUpdateConstituentTargetBaseIxs(
119+
marketIndexes,
120+
this.lpPoolAccount,
121+
this.constituentMap,
122+
true
123+
);
124+
ixs.push(...updateIxs);
125+
const simResult = await simulateAndGetTxWithCUs({
126+
ixs,
127+
connection: this.driftClient.connection,
128+
payerPublicKey: this.driftClient.wallet.publicKey,
129+
lookupTableAccounts:
130+
await this.driftClient.fetchAllLookupTableAccounts(),
131+
cuLimitMultiplier: 1.5,
132+
doSimulation: true,
133+
recentBlockhash: await this.getBlockhashForTx(),
134+
});
135+
136+
if (simResult.simError) {
137+
console.log(simResult.simTxLogs);
138+
return;
139+
}
140+
141+
this.driftClient.txSender
142+
.sendVersionedTransaction(simResult.tx)
143+
.then((response) => {
144+
console.log(response);
145+
})
146+
.catch((error) => {
147+
console.log(error);
148+
});
149+
}, this.intervalMs);
150+
}
151+
}

src/experimental-bots/swift/placerExample.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,19 @@ export class SwiftPlacer {
142142
) {
143143
for (const perpMarket of PerpMarkets[
144144
this.runtimeSpec.driftEnv as DriftEnv
145-
])
145+
]) {
146+
console.log(
147+
`Subscribing to perp market: ${perpMarket.marketIndex}`
148+
);
146149
ws.send(
147150
JSON.stringify({
148151
action: 'subscribe',
149152
market_type: 'perp',
150153
market_name: perpMarket.symbol,
151154
})
152155
);
153-
await sleepMs(50);
156+
await sleepMs(50);
157+
}
154158
}
155159

156160
if (message['order'] && this.driftClient.isSubscribed) {

yarn.lock

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,26 +112,27 @@
112112
enabled "2.0.x"
113113
kuler "^2.0.0"
114114

115-
"@drift-labs/[email protected].77":
116-
version "0.21.77"
117-
resolved "https://registry.yarnpkg.com/@drift-labs/jit-proxy/-/jit-proxy-0.21.77.tgz#fa4f0997b49713f5a073edab750afacb2fa17c9c"
118-
integrity sha512-cojQYfGoHrV5k7a7g9pAJNSx2cVNEWnsWLe2Ub+KH1Qp1cbXbL64nKgPfcH4kqu+NCVEhaqQmqiK7Dpq4MtfcQ==
115+
"@drift-labs/[email protected].86":
116+
version "0.21.86"
117+
resolved "https://registry.yarnpkg.com/@drift-labs/jit-proxy/-/jit-proxy-0.21.86.tgz#f7331d4a19ae30a98acd6256dc70563cd782b061"
118+
integrity sha512-Du7Oqt70q6aRqdE5MHRkJPwk3iM/TgZTzTyvOmSFPyXQcIR6ckzWrfEqnHGJUIZHoIouFBX2qLDUA30wBrx6kQ==
119119
dependencies:
120120
"@coral-xyz/anchor" "0.29.0"
121-
"@drift-labs/sdk" "2.144.0-beta.1"
121+
"@drift-labs/sdk" "2.145.0-beta.3"
122122
"@solana/web3.js" "1.98.0"
123123
tweetnacl-util "^0.15.1"
124124
typescript "5.4.5"
125125

126-
"@drift-labs/sdk@2.144.0-beta.1":
127-
version "2.144.0-beta.1"
128-
resolved "https://registry.yarnpkg.com/@drift-labs/sdk/-/sdk-2.144.0-beta.1.tgz#74c618379aee6c4f3f71369bb06ce113d0f7cf82"
129-
integrity sha512-LH7kgkx06MIbJKoL6GjQIrKpv/eQzq7JUg//j7XBq3Mi4uDOjrIXnbLTfqTnGBknANCZx8j1RYQ5Kyt1YLIS3Q==
126+
"@drift-labs/sdk@2.145.0-beta.3":
127+
version "2.145.0-beta.3"
128+
resolved "https://registry.yarnpkg.com/@drift-labs/sdk/-/sdk-2.145.0-beta.3.tgz#ba42a36ad788d112c84400543bf110d5e007411b"
129+
integrity sha512-wkIbDyODhBdTsgzcniOQxUUpSc81DF9RfpbZk7o0dNQs9jmkt/lpZg0KWK9r2Rd0oOIYU/sUQlLc2w7FCbShOA==
130130
dependencies:
131131
"@coral-xyz/anchor" "0.29.0"
132132
"@coral-xyz/anchor-30" "npm:@coral-xyz/[email protected]"
133133
"@ellipsis-labs/phoenix-sdk" "1.4.5"
134134
"@grpc/grpc-js" "1.14.0"
135+
"@msgpack/msgpack" "^3.1.2"
135136
"@openbook-dex/openbook-v2" "0.2.10"
136137
"@project-serum/serum" "0.13.65"
137138
"@pythnetwork/client" "2.5.3"
@@ -474,6 +475,11 @@
474475
snake-case "^3.0.4"
475476
spok "^1.4.3"
476477

478+
"@msgpack/msgpack@^3.1.2":
479+
version "3.1.2"
480+
resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-3.1.2.tgz#fdd25cc2202297519798bbaf4689152ad9609e19"
481+
integrity sha512-JEW4DEtBzfe8HvUYecLU9e6+XJnKDlUAIve8FvPzF3Kzs6Xo/KuZkZJsDH0wJXl/qEZbeeE7edxDNY3kMs39hQ==
482+
477483
"@noble/curves@^1.0.0", "@noble/curves@^1.4.0", "@noble/curves@^1.4.2":
478484
version "1.9.7"
479485
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.9.7.tgz#79d04b4758a43e4bca2cbdc62e7771352fa6b951"

0 commit comments

Comments
 (0)