-
Notifications
You must be signed in to change notification settings - Fork 125
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add phoenix subscriber * Address PR comments
- Loading branch information
1 parent
2a8bebe
commit 882fdb2
Showing
5 changed files
with
225 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |