Skip to content

Commit

Permalink
refactor ts client (#226)
Browse files Browse the repository at this point in the history
* add a test for findAllMarkets
* add a test for testFindAccountsByMints
* add sdk classes for market & orderbook

* Load OpenOrders, Place & CancelAll
* display orders
* speed up open order iterator
* cancel by id and clientOrderId
* implement watcher helper for setting up subscriptions
* calculate lockedBase & lockedQuote
  • Loading branch information
mschneider authored Mar 13, 2024
1 parent 7ff8b26 commit bac3f16
Show file tree
Hide file tree
Showing 19 changed files with 1,316 additions and 106 deletions.
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

0 comments on commit bac3f16

Please sign in to comment.