Skip to content

Commit

Permalink
Add phoenix subscriber (#444)
Browse files Browse the repository at this point in the history
* Add phoenix subscriber

* Address PR comments
  • Loading branch information
jarry-xiao authored and crispheaney committed Apr 26, 2023
1 parent df0a8b6 commit 1065550
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 0 deletions.
40 changes: 40 additions & 0 deletions sdk/examples/phoenix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Connection, PublicKey } from '@solana/web3.js';
import { PRICE_PRECISION, PhoenixSubscriber } from '../src';
import { PROGRAM_ID } from '@ellipsis-labs/phoenix-sdk';

export async function listenToBook(): Promise<void> {
const connection = new Connection('https://api.mainnet-beta.solana.com');

const phoenixSubscriber = new PhoenixSubscriber({
connection,
programId: PROGRAM_ID,
marketAddress: new PublicKey(
'4DoNfFBfF7UokCC2FQzriy7yHK6DY6NVdYpuekQ5pRgg'
),
accountSubscription: {
type: 'websocket',
},
});

await phoenixSubscriber.subscribe();

for (let i = 0; i < 10; i++) {
const bid = phoenixSubscriber.getBestBid().toNumber() / PRICE_PRECISION;
const ask = phoenixSubscriber.getBestAsk().toNumber() / PRICE_PRECISION;
console.log(`iter ${i}:`, bid.toFixed(3), '@', ask.toFixed(3));
await new Promise((r) => setTimeout(r, 2000));
}

await phoenixSubscriber.unsubscribe();
}

(async function () {
try {
await listenToBook();
} catch (err) {
console.log('Error: ', err);
process.exit(1);
}

process.exit(0);
})();
1 change: 1 addition & 0 deletions sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@coral-xyz/anchor": "0.26.0",
"@ellipsis-labs/phoenix-sdk": "^1.3.2",
"@project-serum/serum": "^0.13.38",
"@ellipsis-labs/phoenix-sdk": "^1.3.1",
"@pythnetwork/client": "2.5.3",
"@solana/spl-token": "^0.1.6",
"@solana/web3.js": "1.73.2",
Expand Down
2 changes: 2 additions & 0 deletions sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export * from './config';
export * from './constants/numericConstants';
export * from './serum/serumSubscriber';
export * from './serum/serumFulfillmentConfigMap';
export * from './phoenix/phoenixSubscriber';
export * from './phoenix/phoenixFulfillmentConfigMap';
export * from './tx/retryTxSender';
export * from './util/computeUnits';
export * from './util/tps';
Expand Down
26 changes: 26 additions & 0 deletions sdk/src/phoenix/phoenixFulfillmentConfigMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { PublicKey } from '@solana/web3.js';
import { PhoenixV1FulfillmentConfigAccount } from '../types';
import { DriftClient } from '../driftClient';

export class PhoenixFulfillmentConfigMap {
driftClient: DriftClient;
map = new Map<number, PhoenixV1FulfillmentConfigAccount>();

public constructor(driftClient: DriftClient) {
this.driftClient = driftClient;
}

public async add(
marketIndex: number,
phoenixMarketAddress: PublicKey
): Promise<void> {
const account = await this.driftClient.getPhoenixV1FulfillmentConfig(
phoenixMarketAddress
);
this.map.set(marketIndex, account);
}

public get(marketIndex: number): PhoenixV1FulfillmentConfigAccount {
return this.map.get(marketIndex);
}
}
156 changes: 156 additions & 0 deletions sdk/src/phoenix/phoenixSubscriber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { Connection, PublicKey, SYSVAR_CLOCK_PUBKEY } from '@solana/web3.js';
import { BulkAccountLoader } from '../accounts/bulkAccountLoader';
import {
MarketData,
Client,
deserializeMarketData,
deserializeClockData,
toNum,
getMarketUiLadder,
} from '@ellipsis-labs/phoenix-sdk';
import { PRICE_PRECISION } from '../constants/numericConstants';
import { BN } from '@coral-xyz/anchor';

export type PhoenixMarketSubscriberConfig = {
connection: Connection;
programId: PublicKey;
marketAddress: PublicKey;
accountSubscription:
| {
// enables use to add web sockets in the future
type: 'polling';
accountLoader: BulkAccountLoader;
}
| {
type: 'websocket';
};
};

export class PhoenixSubscriber {
connection: Connection;
client: Client;
programId: PublicKey;
marketAddress: PublicKey;
subscriptionType: 'polling' | 'websocket';
accountLoader: BulkAccountLoader | undefined;
market: MarketData;
marketCallbackId: string | number;
clockCallbackId: string | number;

subscribed: boolean;
lastSlot: number;
lastUnixTimestamp: number;

public constructor(config: PhoenixMarketSubscriberConfig) {
this.connection = config.connection;
this.programId = config.programId;
this.marketAddress = config.marketAddress;
if (config.accountSubscription.type === 'polling') {
this.subscriptionType = 'polling';
this.accountLoader = config.accountSubscription.accountLoader;
} else {
this.subscriptionType = 'websocket';
}
this.lastSlot = 0;
this.lastUnixTimestamp = 0;
}

public async subscribe(): Promise<void> {
if (this.subscribed) {
return;
}

this.market = deserializeMarketData(
(await this.connection.getAccountInfo(this.marketAddress, 'confirmed'))
.data
);

const clock = deserializeClockData(
(await this.connection.getAccountInfo(SYSVAR_CLOCK_PUBKEY, 'confirmed'))
.data
);
this.lastUnixTimestamp = toNum(clock.unixTimestamp);

if (this.subscriptionType === 'websocket') {
this.marketCallbackId = this.connection.onAccountChange(
this.marketAddress,
(accountInfo, _ctx) => {
this.market = deserializeMarketData(accountInfo.data);
}
);
this.clockCallbackId = this.connection.onAccountChange(
SYSVAR_CLOCK_PUBKEY,
(accountInfo, ctx) => {
this.lastSlot = ctx.slot;
const clock = deserializeClockData(accountInfo.data);
this.lastUnixTimestamp = toNum(clock.unixTimestamp);
}
);
} else {
this.marketCallbackId = await this.accountLoader.addAccount(
this.marketAddress,
(buffer, slot) => {
this.lastSlot = slot;
this.market = deserializeMarketData(buffer);
}
);
this.clockCallbackId = await this.accountLoader.addAccount(
SYSVAR_CLOCK_PUBKEY,
(buffer, slot) => {
this.lastSlot = slot;
const clock = deserializeClockData(buffer);
this.lastUnixTimestamp = toNum(clock.unixTimestamp);
}
);
}

this.subscribed = true;
}

public getBestBid(): BN {
const ladder = getMarketUiLadder(
this.market,
this.lastSlot,
this.lastUnixTimestamp,
1
);
return new BN(Math.floor(ladder.bids[0][0] * PRICE_PRECISION));
}

public getBestAsk(): BN {
const ladder = getMarketUiLadder(
this.market,
this.lastSlot,
this.lastUnixTimestamp,
1
);
return new BN(Math.floor(ladder.asks[0][0] * PRICE_PRECISION));
}

public async unsubscribe(): Promise<void> {
if (!this.subscribed) {
return;
}

// remove listeners
if (this.subscriptionType === 'websocket') {
await this.connection.removeAccountChangeListener(
this.marketCallbackId as number
);
await this.connection.removeAccountChangeListener(
this.clockCallbackId as number
);
} else {
this.accountLoader.removeAccount(
this.marketAddress,
this.marketCallbackId as string
);
this.accountLoader.removeAccount(
SYSVAR_CLOCK_PUBKEY,
this.clockCallbackId as string
);
}

this.subscribed = false;
}
}

0 comments on commit 1065550

Please sign in to comment.