Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor ts client #226

Merged
merged 13 commits into from
Mar 13, 2024
24 changes: 0 additions & 24 deletions .eslintrc.js

This file was deleted.

34 changes: 34 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"linebreak-style": [
"error",
"unix"
],
"semi": [
"error",
"always"
],
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-function-return-type": "warn"
}
}
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,12 @@ just test-dev
```bash
yarn build
```

### TS Testing

```bash
export SOL_RPC_URL=https://a.b.c
export KEYPAIR="[1,2,3,4,...]"
yarn ts/client/src/test/market.ts
yarn ts/client/src/test/openOrders.ts
```
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
},
"dependencies": {
"@coral-xyz/anchor": "^0.28.1-beta.2",
"@solana/spl-token": "0.3.8",
"@solana/spl-token": "^0.4.0",
"@solana/web3.js": "^1.77.3",
"big.js": "^6.2.1"
},
Expand All @@ -51,6 +51,7 @@
"mocha": "^9.0.3",
"prettier": "^2.6.2",
"ts-mocha": "^10.0.0",
"ts-node": "^10.9.2",
"typescript": "*"
},
"license": "MIT"
Expand Down
166 changes: 166 additions & 0 deletions ts/client/src/accounts/bookSide.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { PublicKey } from '@solana/web3.js';
import {
Market,
BookSideAccount,
SideUtils,
Side,
OpenBookV2Client,
LeafNode,
InnerNode,
U64_MAX_BN,
} from '..';
import { BN } from '@coral-xyz/anchor';
import { Order } from '../structs/order';

export class BookSide {
public clusterTime: BN;

constructor(
public market: Market,
public pubkey: PublicKey,
public account: BookSideAccount,
public side: Side,
) {
this.clusterTime = new BN(0);
}

public *items(): Generator<Order> {
const fGen = this.fixedItems();
const oPegGen = this.oraclePeggedItems();

let fOrderRes = fGen.next();
let oPegOrderRes = oPegGen.next();

while (true) {
if (fOrderRes.value && oPegOrderRes.value) {
if (this.compareOrders(fOrderRes.value, oPegOrderRes.value)) {
yield fOrderRes.value;
fOrderRes = fGen.next();
} else {
yield oPegOrderRes.value;
oPegOrderRes = oPegGen.next();
}
} else if (fOrderRes.value && !oPegOrderRes.value) {
yield fOrderRes.value;
fOrderRes = fGen.next();
} else if (!fOrderRes.value && oPegOrderRes.value) {
yield oPegOrderRes.value;
oPegOrderRes = oPegGen.next();
} else if (!fOrderRes.value && !oPegOrderRes.value) {
break;
}
}
}

get rootFixed() {
return this.account.roots[0];
}

get rootOraclePegged() {
return this.account.roots[1];
}

public *fixedItems(): Generator<Order> {
if (this.rootFixed.leafCount === 0) {
return;
}
const stack = [this.rootFixed.maybeNode];
const [left, right] = this.side === SideUtils.Bid ? [1, 0] : [0, 1];

while (stack.length > 0) {
const index = stack.pop()!;
const node = this.account.nodes.nodes[index];
if (node.tag === BookSide.INNER_NODE_TAG) {
const innerNode = this.toInnerNode(node.data);
stack.push(innerNode.children[right], innerNode.children[left]);
} else if (node.tag === BookSide.LEAF_NODE_TAG) {
const leafNode = this.toLeafNode(node.data);
const expiryTimestamp = leafNode.timeInForce
? leafNode.timestamp.add(new BN(leafNode.timeInForce))
: U64_MAX_BN;

yield new Order(
this.market,
leafNode,
this.side,
this.clusterTime.gt(expiryTimestamp),
);
}
}
}

public *oraclePeggedItems(): Generator<Order> {
if (this.rootOraclePegged.leafCount === 0) {
return;
}
const stack = [this.rootOraclePegged.maybeNode];
const [left, right] = this.side === SideUtils.Bid ? [1, 0] : [0, 1];

while (stack.length > 0) {
const index = stack.pop()!;
const node = this.account.nodes.nodes[index];
if (node.tag === BookSide.INNER_NODE_TAG) {
const innerNode = this.toInnerNode(node.data);
stack.push(innerNode.children[right], innerNode.children[left]);
} else if (node.tag === BookSide.LEAF_NODE_TAG) {
const leafNode = this.toLeafNode(node.data);
const expiryTimestamp = leafNode.timeInForce
? leafNode.timestamp.add(new BN(leafNode.timeInForce))
: U64_MAX_BN;

yield new Order(
this.market,
leafNode,
this.side,
this.clusterTime.gt(expiryTimestamp),
true,
);
}
}
}

public compareOrders(a: Order, b: Order): boolean {
return a.priceLots.eq(b.priceLots)
? a.seqNum.lt(b.seqNum) // if prices are equal prefer orders in the order they are placed
: this.side === SideUtils.Bid // else compare the actual prices
? a.priceLots.gt(b.priceLots)
: b.priceLots.gt(a.priceLots);
}

public best(): Order | undefined {
return this.items().next().value;
}

public getL2(depth: number): [number, number, BN, BN][] {
const levels: [BN, BN][] = [];
for (const { priceLots, sizeLots } of this.items()) {
if (levels.length > 0 && levels[levels.length - 1][0].eq(priceLots)) {
levels[levels.length - 1][1].iadd(sizeLots);
} else if (levels.length === depth) {
break;
} else {
levels.push([priceLots, sizeLots]);
}
}
return levels.map(([priceLots, sizeLots]) => [
this.market.priceLotsToUi(priceLots),
this.market.baseLotsToUi(sizeLots),
priceLots,
sizeLots,
]);
}

private static INNER_NODE_TAG = 1;
private static LEAF_NODE_TAG = 2;

private toInnerNode(data: number[]): InnerNode {
return (this.market.client.program as any)._coder.types.typeLayouts
.get('InnerNode')
.decode(Buffer.from([BookSide.INNER_NODE_TAG].concat(data)));
}
private toLeafNode(data: number[]): LeafNode {
return (this.market.client.program as any)._coder.types.typeLayouts
.get('LeafNode')
.decode(Buffer.from([BookSide.LEAF_NODE_TAG].concat(data)));
}
}
Loading
Loading