-
Notifications
You must be signed in to change notification settings - Fork 52
/
serum3.ts
233 lines (212 loc) · 6.54 KB
/
serum3.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
import { utf8 } from '@coral-xyz/anchor/dist/cjs/utils/bytes';
import { Market, Orderbook } from '@project-serum/serum';
import { Cluster, PublicKey } from '@solana/web3.js';
import BN from 'bn.js';
import { MangoClient } from '../client';
import { OPENBOOK_PROGRAM_ID } from '../constants';
import { MAX_I80F48, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48';
import { As } from '../utils';
import { TokenIndex } from './bank';
import { Group } from './group';
export type MarketIndex = number & As<'market-index'>;
export class Serum3Market {
public name: string;
static from(
publicKey: PublicKey,
obj: {
group: PublicKey;
baseTokenIndex: number;
quoteTokenIndex: number;
name: number[];
serumProgram: PublicKey;
serumMarketExternal: PublicKey;
marketIndex: number;
registrationTime: BN;
reduceOnly: number;
forceClose: number;
},
): Serum3Market {
return new Serum3Market(
publicKey,
obj.group,
obj.baseTokenIndex as TokenIndex,
obj.quoteTokenIndex as TokenIndex,
obj.name,
obj.serumProgram,
obj.serumMarketExternal,
obj.marketIndex as MarketIndex,
obj.registrationTime,
obj.reduceOnly == 1,
obj.forceClose == 1,
);
}
constructor(
public publicKey: PublicKey,
public group: PublicKey,
public baseTokenIndex: TokenIndex,
public quoteTokenIndex: TokenIndex,
name: number[],
public serumProgram: PublicKey,
public serumMarketExternal: PublicKey,
public marketIndex: MarketIndex,
public registrationTime: BN,
public reduceOnly: boolean,
public forceClose: boolean,
) {
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
}
public async findOoPda(
programId: PublicKey,
mangoAccount: PublicKey,
): Promise<PublicKey> {
const [openOrderPublicKey] = await PublicKey.findProgramAddress(
[
Buffer.from('Serum3OO'),
mangoAccount.toBuffer(),
this.publicKey.toBuffer(),
],
programId,
);
return openOrderPublicKey;
}
public getFeeRates(taker = true): number {
// See https://github.com/openbook-dex/program/blob/master/dex/src/fees.rs#L81
const ratesBps =
this.name === 'USDT/USDC'
? { maker: -0.5, taker: 1 }
: { maker: -2, taker: 4 };
return taker ? ratesBps.taker * 0.0001 : ratesBps.maker * 0.0001;
}
/**
*
* @param group
* @returns maximum leverage one can bid on this market, this is only for display purposes,
* also see getMaxQuoteForSerum3BidUi and getMaxBaseForSerum3AskUi
*/
maxBidLeverage(group: Group): number {
const baseBank = group.getFirstBankByTokenIndex(this.baseTokenIndex);
const quoteBank = group.getFirstBankByTokenIndex(this.quoteTokenIndex);
if (
quoteBank.initLiabWeight.sub(baseBank.initAssetWeight).lte(ZERO_I80F48())
) {
return MAX_I80F48().toNumber();
}
return ONE_I80F48()
.div(quoteBank.initLiabWeight.sub(baseBank.initAssetWeight))
.toNumber();
}
/**
*
* @param group
* @returns maximum leverage one can ask on this market, this is only for display purposes,
* also see getMaxQuoteForSerum3BidUi and getMaxBaseForSerum3AskUi
*/
maxAskLeverage(group: Group): number {
const baseBank = group.getFirstBankByTokenIndex(this.baseTokenIndex);
const quoteBank = group.getFirstBankByTokenIndex(this.quoteTokenIndex);
if (
baseBank.initLiabWeight.sub(quoteBank.initAssetWeight).lte(ZERO_I80F48())
) {
return MAX_I80F48().toNumber();
}
return ONE_I80F48()
.div(baseBank.initLiabWeight.sub(quoteBank.initAssetWeight))
.toNumber();
}
public async loadBids(client: MangoClient, group: Group): Promise<Orderbook> {
const serum3MarketExternal = group.getSerum3ExternalMarket(
this.serumMarketExternal,
);
return await serum3MarketExternal.loadBids(
client.program.provider.connection,
);
}
public async loadAsks(client: MangoClient, group: Group): Promise<Orderbook> {
const serum3MarketExternal = group.getSerum3ExternalMarket(
this.serumMarketExternal,
);
return await serum3MarketExternal.loadAsks(
client.program.provider.connection,
);
}
public async computePriceForMarketOrderOfSize(
client: MangoClient,
group: Group,
size: number,
side: 'buy' | 'sell',
): Promise<number> {
const ob =
side == 'buy'
? await this.loadBids(client, group)
: await this.loadAsks(client, group);
let acc = 0;
let selectedOrder;
const orderSize = size;
for (const order of ob.getL2(size * 2 /* TODO Fix random constant */)) {
acc += order[1];
if (acc >= orderSize) {
selectedOrder = order;
break;
}
}
if (!selectedOrder) {
throw new Error(
'Unable to place market order for this order size. Please retry.',
);
}
if (side === 'buy') {
return selectedOrder[0] * 1.05 /* TODO Fix random constant */;
} else {
return selectedOrder[0] * 0.95 /* TODO Fix random constant */;
}
}
public async logOb(client: MangoClient, group: Group): Promise<string> {
let res = ``;
res += ` ${this.name} OrderBook`;
let orders = await this?.loadAsks(client, group);
for (const order of orders!.items(true)) {
res += `\n ${order.price.toString().padStart(10)}, ${order.size
.toString()
.padStart(10)}`;
}
res += `\n --------------------------`;
orders = await this?.loadBids(client, group);
for (const order of orders!.items(true)) {
res += `\n ${order.price.toString().padStart(10)}, ${order.size
.toString()
.padStart(10)}`;
}
return res;
}
}
export class Serum3SelfTradeBehavior {
static decrementTake = { decrementTake: {} };
static cancelProvide = { cancelProvide: {} };
static abortTransaction = { abortTransaction: {} };
}
export class Serum3OrderType {
static limit = { limit: {} };
static immediateOrCancel = { immediateOrCancel: {} };
static postOnly = { postOnly: {} };
}
export class Serum3Side {
static bid = { bid: {} };
static ask = { ask: {} };
}
export async function generateSerum3MarketExternalVaultSignerAddress(
cluster: Cluster,
serum3Market: Serum3Market,
serum3MarketExternal: Market,
): Promise<PublicKey> {
return await PublicKey.createProgramAddress(
[
serum3Market.serumMarketExternal.toBuffer(),
serum3MarketExternal.decoded.vaultSignerNonce.toArrayLike(
Buffer,
'le',
8,
),
],
OPENBOOK_PROGRAM_ID[cluster],
);
}