2022-04-07 12:00:08 -07:00
|
|
|
import { BN, Program, Provider } from '@project-serum/anchor';
|
2022-04-08 07:57:37 -07:00
|
|
|
import { getFeeRates, getFeeTier, Market } from '@project-serum/serum';
|
|
|
|
import { Order } from '@project-serum/serum/lib/market';
|
2022-04-07 09:58:42 -07:00
|
|
|
import * as spl from '@solana/spl-token';
|
|
|
|
import {
|
2022-04-07 12:00:08 -07:00
|
|
|
AccountMeta,
|
2022-04-08 03:30:21 -07:00
|
|
|
MemcmpFilter,
|
2022-04-07 12:00:08 -07:00
|
|
|
PublicKey,
|
|
|
|
SYSVAR_RENT_PUBKEY,
|
|
|
|
TransactionSignature,
|
|
|
|
} from '@solana/web3.js';
|
2022-04-07 23:29:35 -07:00
|
|
|
import bs58 from 'bs58';
|
2022-04-07 12:00:08 -07:00
|
|
|
import { Bank, getMintInfoForTokenIndex } from './accounts/types/bank';
|
|
|
|
import { Group } from './accounts/types/group';
|
|
|
|
import { I80F48 } from './accounts/types/I80F48';
|
|
|
|
import { MangoAccount } from './accounts/types/mangoAccount';
|
|
|
|
import { StubOracle } from './accounts/types/oracle';
|
2022-04-08 03:30:21 -07:00
|
|
|
import {
|
|
|
|
Serum3Market,
|
|
|
|
Serum3OrderType,
|
|
|
|
Serum3SelfTradeBehavior,
|
|
|
|
Serum3Side,
|
|
|
|
} from './accounts/types/serum3';
|
2022-03-31 04:55:59 -07:00
|
|
|
import { IDL, MangoV4 } from './mango_v4';
|
2022-02-23 02:09:17 -08:00
|
|
|
|
|
|
|
export const MANGO_V4_ID = new PublicKey(
|
2022-03-30 08:16:32 -07:00
|
|
|
'm43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD',
|
2022-02-23 02:09:17 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
export class MangoClient {
|
2022-04-07 06:06:26 -07:00
|
|
|
constructor(public program: Program<MangoV4>, public devnet?: boolean) {}
|
2022-02-23 02:09:17 -08:00
|
|
|
|
2022-04-07 09:58:42 -07:00
|
|
|
/// public
|
|
|
|
|
2022-04-07 10:42:00 -07:00
|
|
|
// Group
|
|
|
|
|
2022-04-07 12:00:08 -07:00
|
|
|
public async createGroup(): Promise<TransactionSignature> {
|
|
|
|
const adminPk = this.program.provider.wallet.publicKey;
|
|
|
|
return await this.program.methods
|
|
|
|
.createGroup()
|
|
|
|
.accounts({
|
|
|
|
admin: adminPk,
|
|
|
|
payer: adminPk,
|
|
|
|
})
|
|
|
|
.rpc();
|
2022-04-07 10:42:00 -07:00
|
|
|
}
|
|
|
|
|
2022-04-08 11:47:12 -07:00
|
|
|
public async getGroup(groupPk: PublicKey): Promise<Group> {
|
|
|
|
const group = Group.from(
|
|
|
|
groupPk,
|
|
|
|
await this.program.account.group.fetch(groupPk),
|
|
|
|
);
|
|
|
|
await group.reload(this);
|
|
|
|
return group;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getGroupForAdmin(adminPk: PublicKey): Promise<Group> {
|
2022-04-07 12:00:08 -07:00
|
|
|
const groups = (
|
|
|
|
await this.program.account.group.all([
|
|
|
|
{
|
|
|
|
memcmp: {
|
|
|
|
bytes: adminPk.toBase58(),
|
|
|
|
offset: 8,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
).map((tuple) => Group.from(tuple.publicKey, tuple.account));
|
2022-04-08 03:30:21 -07:00
|
|
|
await groups[0].reload(this);
|
2022-04-07 12:00:08 -07:00
|
|
|
return groups[0];
|
2022-04-07 09:58:42 -07:00
|
|
|
}
|
|
|
|
|
2022-04-07 10:42:00 -07:00
|
|
|
// Tokens/Banks
|
|
|
|
|
|
|
|
public async registerToken(
|
|
|
|
group: Group,
|
|
|
|
mintPk: PublicKey,
|
|
|
|
oraclePk: PublicKey,
|
|
|
|
tokenIndex: number,
|
2022-04-09 08:09:06 -07:00
|
|
|
util0: number,
|
|
|
|
rate0: number,
|
|
|
|
util1: number,
|
|
|
|
rate1: number,
|
|
|
|
maxRate: number,
|
|
|
|
maintAssetWeight: number,
|
|
|
|
initAssetWeight: number,
|
|
|
|
maintLiabWeight: number,
|
|
|
|
initLiabWeight: number,
|
|
|
|
liquidationFee: number,
|
2022-04-07 10:42:00 -07:00
|
|
|
): Promise<TransactionSignature> {
|
2022-04-07 12:00:08 -07:00
|
|
|
return await this.program.methods
|
|
|
|
.registerToken(
|
|
|
|
tokenIndex,
|
2022-04-09 08:09:06 -07:00
|
|
|
util0,
|
|
|
|
rate0,
|
|
|
|
util1,
|
|
|
|
rate1,
|
|
|
|
maxRate,
|
|
|
|
maintAssetWeight,
|
|
|
|
initAssetWeight,
|
|
|
|
maintLiabWeight,
|
|
|
|
initLiabWeight,
|
|
|
|
liquidationFee,
|
2022-04-07 12:00:08 -07:00
|
|
|
)
|
|
|
|
.accounts({
|
|
|
|
group: group.publicKey,
|
|
|
|
admin: this.program.provider.wallet.publicKey,
|
|
|
|
mint: mintPk,
|
|
|
|
oracle: oraclePk,
|
|
|
|
payer: this.program.provider.wallet.publicKey,
|
|
|
|
rent: SYSVAR_RENT_PUBKEY,
|
|
|
|
})
|
|
|
|
.rpc();
|
2022-04-07 10:42:00 -07:00
|
|
|
}
|
|
|
|
|
2022-04-07 09:58:42 -07:00
|
|
|
public async getBanksForGroup(group: Group): Promise<Bank[]> {
|
2022-04-07 12:00:08 -07:00
|
|
|
return (
|
|
|
|
await this.program.account.bank.all([
|
|
|
|
{
|
|
|
|
memcmp: {
|
|
|
|
bytes: group.publicKey.toBase58(),
|
|
|
|
offset: 8,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
).map((tuple) => Bank.from(tuple.publicKey, tuple.account));
|
2022-04-07 09:58:42 -07:00
|
|
|
}
|
|
|
|
|
2022-04-07 11:05:06 -07:00
|
|
|
// Stub Oracle
|
|
|
|
|
2022-04-07 10:42:00 -07:00
|
|
|
public async createStubOracle(
|
|
|
|
group: Group,
|
|
|
|
mintPk: PublicKey,
|
|
|
|
price: number,
|
|
|
|
): Promise<TransactionSignature> {
|
2022-04-07 12:00:08 -07:00
|
|
|
return await this.program.methods
|
|
|
|
.createStubOracle({ val: I80F48.fromNumber(price).getData() })
|
|
|
|
.accounts({
|
|
|
|
group: group.publicKey,
|
|
|
|
admin: this.program.provider.wallet.publicKey,
|
|
|
|
tokenMint: mintPk,
|
|
|
|
payer: this.program.provider.wallet.publicKey,
|
|
|
|
})
|
|
|
|
.rpc();
|
2022-04-07 10:42:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public async setStubOracle(
|
|
|
|
group: Group,
|
|
|
|
mintPk: PublicKey,
|
|
|
|
price: number,
|
|
|
|
): Promise<TransactionSignature> {
|
2022-04-07 12:00:08 -07:00
|
|
|
return await this.program.methods
|
|
|
|
.setStubOracle({ val: I80F48.fromNumber(price).getData() })
|
|
|
|
.accounts({
|
|
|
|
group: group.publicKey,
|
|
|
|
admin: this.program.provider.wallet.publicKey,
|
|
|
|
tokenMint: mintPk,
|
|
|
|
payer: this.program.provider.wallet.publicKey,
|
|
|
|
})
|
|
|
|
.rpc();
|
2022-04-07 10:42:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public async getStubOracle(
|
|
|
|
group: Group,
|
|
|
|
mintPk: PublicKey,
|
|
|
|
): Promise<StubOracle> {
|
2022-04-07 12:00:08 -07:00
|
|
|
const stubOracles = (
|
|
|
|
await this.program.account.stubOracle.all([
|
|
|
|
{
|
|
|
|
memcmp: {
|
|
|
|
bytes: group.publicKey.toBase58(),
|
|
|
|
offset: 8,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
memcmp: {
|
|
|
|
bytes: mintPk.toBase58(),
|
|
|
|
offset: 40,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
).map((pa) => StubOracle.from(pa.publicKey, pa.account));
|
|
|
|
return stubOracles[0];
|
2022-04-07 10:42:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// MangoAccount
|
|
|
|
|
2022-04-08 03:30:21 -07:00
|
|
|
public async getOrCreateMangoAccount(
|
|
|
|
group: Group,
|
|
|
|
ownerPk: PublicKey,
|
|
|
|
accountNumber?: number,
|
|
|
|
): Promise<MangoAccount> {
|
|
|
|
let mangoAccounts = await this.getMangoAccountForOwner(group, ownerPk);
|
|
|
|
if (mangoAccounts.length === 0) {
|
|
|
|
await this.createMangoAccount(group, accountNumber ?? 0);
|
2022-04-08 11:47:12 -07:00
|
|
|
mangoAccounts = await this.getMangoAccountForOwner(group, ownerPk);
|
2022-04-08 03:30:21 -07:00
|
|
|
}
|
|
|
|
return mangoAccounts[0];
|
|
|
|
}
|
|
|
|
|
2022-04-07 09:58:42 -07:00
|
|
|
public async createMangoAccount(
|
|
|
|
group: Group,
|
|
|
|
accountNumber: number,
|
|
|
|
): Promise<TransactionSignature> {
|
2022-04-07 12:00:08 -07:00
|
|
|
return await this.program.methods
|
|
|
|
.createAccount(accountNumber)
|
|
|
|
.accounts({
|
|
|
|
group: group.publicKey,
|
|
|
|
owner: this.program.provider.wallet.publicKey,
|
|
|
|
payer: this.program.provider.wallet.publicKey,
|
|
|
|
})
|
|
|
|
.rpc();
|
2022-04-07 09:58:42 -07:00
|
|
|
}
|
|
|
|
|
2022-04-08 03:30:21 -07:00
|
|
|
public async getMangoAccount(mangoAccount: MangoAccount) {
|
|
|
|
return MangoAccount.from(
|
|
|
|
mangoAccount.publicKey,
|
|
|
|
await this.program.account.mangoAccount.fetch(mangoAccount.publicKey),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getMangoAccountForOwner(
|
2022-04-07 09:58:42 -07:00
|
|
|
group: Group,
|
|
|
|
ownerPk: PublicKey,
|
|
|
|
): Promise<MangoAccount[]> {
|
2022-04-07 12:00:08 -07:00
|
|
|
return (
|
|
|
|
await this.program.account.mangoAccount.all([
|
|
|
|
{
|
|
|
|
memcmp: {
|
|
|
|
bytes: group.publicKey.toBase58(),
|
|
|
|
offset: 8,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
memcmp: {
|
|
|
|
bytes: ownerPk.toBase58(),
|
|
|
|
offset: 40,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
])
|
|
|
|
).map((pa) => {
|
|
|
|
return MangoAccount.from(pa.publicKey, pa.account);
|
|
|
|
});
|
2022-04-07 09:58:42 -07:00
|
|
|
}
|
|
|
|
|
2022-04-08 08:21:49 -07:00
|
|
|
public async closeMangoAccount(
|
|
|
|
mangoAccount: MangoAccount,
|
|
|
|
): Promise<TransactionSignature> {
|
|
|
|
return await this.program.methods
|
|
|
|
.closeAccount()
|
|
|
|
.accounts({
|
|
|
|
account: mangoAccount.publicKey,
|
|
|
|
owner: this.program.provider.wallet.publicKey,
|
|
|
|
solDestination: this.program.provider.wallet.publicKey,
|
|
|
|
})
|
|
|
|
.rpc();
|
|
|
|
}
|
|
|
|
|
2022-04-07 09:58:42 -07:00
|
|
|
public async deposit(
|
|
|
|
group: Group,
|
|
|
|
mangoAccount: MangoAccount,
|
2022-04-08 03:30:21 -07:00
|
|
|
tokenName: string,
|
2022-04-07 09:58:42 -07:00
|
|
|
amount: number,
|
|
|
|
) {
|
2022-04-08 03:30:21 -07:00
|
|
|
const bank = group.banksMap.get(tokenName)!;
|
|
|
|
|
2022-04-07 09:58:42 -07:00
|
|
|
const tokenAccountPk = await spl.getAssociatedTokenAddress(
|
|
|
|
bank.mint,
|
|
|
|
mangoAccount.owner,
|
|
|
|
);
|
|
|
|
|
|
|
|
const healthRemainingAccounts: PublicKey[] =
|
|
|
|
await this.buildHealthRemainingAccounts(group, mangoAccount, bank);
|
|
|
|
|
2022-04-07 12:00:08 -07:00
|
|
|
return await this.program.methods
|
|
|
|
.deposit(new BN(amount))
|
|
|
|
.accounts({
|
|
|
|
group: group.publicKey,
|
|
|
|
account: mangoAccount.publicKey,
|
|
|
|
bank: bank.publicKey,
|
|
|
|
vault: bank.vault,
|
|
|
|
tokenAccount: tokenAccountPk,
|
|
|
|
tokenAuthority: this.program.provider.wallet.publicKey,
|
|
|
|
})
|
|
|
|
.remainingAccounts(
|
|
|
|
healthRemainingAccounts.map(
|
|
|
|
(pk) =>
|
|
|
|
({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.rpc();
|
2022-04-07 09:58:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public async withdraw(
|
|
|
|
group: Group,
|
|
|
|
mangoAccount: MangoAccount,
|
2022-04-08 03:30:21 -07:00
|
|
|
tokenName: string,
|
2022-04-07 09:58:42 -07:00
|
|
|
amount: number,
|
|
|
|
allowBorrow: boolean,
|
|
|
|
) {
|
2022-04-08 03:30:21 -07:00
|
|
|
const bank = group.banksMap.get(tokenName)!;
|
|
|
|
|
2022-04-07 09:58:42 -07:00
|
|
|
const tokenAccountPk = await spl.getAssociatedTokenAddress(
|
|
|
|
bank.mint,
|
|
|
|
mangoAccount.owner,
|
|
|
|
);
|
|
|
|
|
|
|
|
const healthRemainingAccounts: PublicKey[] =
|
|
|
|
await this.buildHealthRemainingAccounts(group, mangoAccount, bank);
|
|
|
|
|
2022-04-07 12:00:08 -07:00
|
|
|
return await this.program.methods
|
|
|
|
.withdraw(new BN(amount), allowBorrow)
|
|
|
|
.accounts({
|
|
|
|
group: group.publicKey,
|
|
|
|
account: mangoAccount.publicKey,
|
|
|
|
bank: bank.publicKey,
|
|
|
|
vault: bank.vault,
|
|
|
|
tokenAccount: tokenAccountPk,
|
|
|
|
tokenAuthority: this.program.provider.wallet.publicKey,
|
|
|
|
})
|
|
|
|
.remainingAccounts(
|
|
|
|
healthRemainingAccounts.map(
|
|
|
|
(pk) =>
|
|
|
|
({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.rpc();
|
2022-04-07 09:58:42 -07:00
|
|
|
}
|
|
|
|
|
2022-04-07 23:29:35 -07:00
|
|
|
// Serum
|
|
|
|
|
|
|
|
public async serum3RegisterMarket(
|
|
|
|
group: Group,
|
|
|
|
serum3ProgramId: PublicKey,
|
|
|
|
serum3MarketExternalPk: PublicKey,
|
|
|
|
baseBank: Bank,
|
|
|
|
quoteBank: Bank,
|
|
|
|
marketIndex: number,
|
|
|
|
): Promise<TransactionSignature> {
|
|
|
|
return await this.program.methods
|
|
|
|
.serum3RegisterMarket(marketIndex)
|
|
|
|
.accounts({
|
|
|
|
group: group.publicKey,
|
|
|
|
admin: this.program.provider.wallet.publicKey,
|
|
|
|
serumProgram: serum3ProgramId,
|
|
|
|
serumMarketExternal: serum3MarketExternalPk,
|
|
|
|
baseBank: baseBank.publicKey,
|
|
|
|
quoteBank: quoteBank.publicKey,
|
|
|
|
payer: this.program.provider.wallet.publicKey,
|
|
|
|
})
|
|
|
|
.rpc();
|
|
|
|
}
|
|
|
|
|
2022-04-08 03:30:21 -07:00
|
|
|
public async serum3GetMarket(
|
2022-04-07 23:29:35 -07:00
|
|
|
group: Group,
|
2022-04-08 03:30:21 -07:00
|
|
|
baseTokenIndex?: number,
|
|
|
|
quoteTokenIndex?: number,
|
2022-04-07 23:29:35 -07:00
|
|
|
): Promise<Serum3Market[]> {
|
|
|
|
const bumpfbuf = Buffer.alloc(1);
|
|
|
|
bumpfbuf.writeUInt8(255);
|
|
|
|
|
2022-04-08 03:30:21 -07:00
|
|
|
const filters: MemcmpFilter[] = [
|
|
|
|
{
|
|
|
|
memcmp: {
|
|
|
|
bytes: group.publicKey.toBase58(),
|
|
|
|
offset: 8,
|
2022-04-07 23:29:35 -07:00
|
|
|
},
|
2022-04-08 03:30:21 -07:00
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
if (baseTokenIndex) {
|
|
|
|
const bbuf = Buffer.alloc(2);
|
|
|
|
bbuf.writeUInt16LE(baseTokenIndex);
|
|
|
|
filters.push({
|
|
|
|
memcmp: {
|
|
|
|
bytes: bs58.encode(bbuf),
|
|
|
|
offset: 106,
|
2022-04-07 23:29:35 -07:00
|
|
|
},
|
2022-04-08 03:30:21 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (quoteTokenIndex) {
|
|
|
|
const qbuf = Buffer.alloc(2);
|
|
|
|
qbuf.writeUInt16LE(quoteTokenIndex);
|
|
|
|
filters.push({
|
|
|
|
memcmp: {
|
|
|
|
bytes: bs58.encode(qbuf),
|
2022-04-08 08:17:26 -07:00
|
|
|
offset: 108,
|
2022-04-07 23:29:35 -07:00
|
|
|
},
|
2022-04-08 03:30:21 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return (await this.program.account.serum3Market.all(filters)).map((tuple) =>
|
|
|
|
Serum3Market.from(tuple.publicKey, tuple.account),
|
|
|
|
);
|
2022-04-07 23:29:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public async serum3CreateOpenOrders(
|
|
|
|
group: Group,
|
|
|
|
mangoAccount: MangoAccount,
|
2022-04-08 03:30:21 -07:00
|
|
|
marketName: string,
|
2022-04-07 23:29:35 -07:00
|
|
|
): Promise<TransactionSignature> {
|
2022-04-08 03:30:21 -07:00
|
|
|
const serum3Market: Serum3Market = group.serum3MarketsMap.get(marketName)!;
|
|
|
|
|
2022-04-07 23:29:35 -07:00
|
|
|
return await this.program.methods
|
|
|
|
.serum3CreateOpenOrders()
|
|
|
|
.accounts({
|
|
|
|
group: group.publicKey,
|
|
|
|
account: mangoAccount.publicKey,
|
|
|
|
serumMarket: serum3Market.publicKey,
|
|
|
|
serumProgram: serum3Market.serumProgram,
|
|
|
|
serumMarketExternal: serum3Market.serumMarketExternal,
|
|
|
|
owner: this.program.provider.wallet.publicKey,
|
|
|
|
payer: this.program.provider.wallet.publicKey,
|
|
|
|
})
|
|
|
|
.rpc();
|
|
|
|
}
|
|
|
|
|
2022-04-08 03:30:21 -07:00
|
|
|
public async serum3PlaceOrder(
|
|
|
|
group: Group,
|
|
|
|
mangoAccount: MangoAccount,
|
|
|
|
serum3ProgramId: PublicKey,
|
|
|
|
serum3MarketName: string,
|
|
|
|
side: Serum3Side,
|
2022-04-08 07:57:37 -07:00
|
|
|
price: number,
|
|
|
|
size: number,
|
2022-04-08 03:30:21 -07:00
|
|
|
selfTradeBehavior: Serum3SelfTradeBehavior,
|
|
|
|
orderType: Serum3OrderType,
|
|
|
|
clientOrderId: number,
|
|
|
|
limit: number,
|
|
|
|
) {
|
|
|
|
const serum3Market = group.serum3MarketsMap.get(serum3MarketName)!;
|
|
|
|
|
|
|
|
if (!mangoAccount.findSerum3Account(serum3Market.marketIndex)) {
|
|
|
|
await this.serum3CreateOpenOrders(group, mangoAccount, 'BTC/USDC');
|
|
|
|
mangoAccount = await this.getMangoAccount(mangoAccount);
|
|
|
|
}
|
|
|
|
|
|
|
|
const serum3MarketExternal = await Market.load(
|
|
|
|
this.program.provider.connection,
|
|
|
|
serum3Market.serumMarketExternal,
|
|
|
|
{ commitment: this.program.provider.connection.commitment },
|
|
|
|
serum3ProgramId,
|
|
|
|
);
|
|
|
|
|
|
|
|
const serum3MarketExternalVaultSigner =
|
|
|
|
await PublicKey.createProgramAddress(
|
|
|
|
[
|
|
|
|
serum3Market.serumMarketExternal.toBuffer(),
|
|
|
|
serum3MarketExternal.decoded.vaultSignerNonce.toArrayLike(
|
|
|
|
Buffer,
|
|
|
|
'le',
|
|
|
|
8,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
serum3ProgramId,
|
|
|
|
);
|
|
|
|
|
|
|
|
const healthRemainingAccounts: PublicKey[] =
|
|
|
|
await this.buildHealthRemainingAccounts(group, mangoAccount);
|
|
|
|
|
2022-04-08 07:57:37 -07:00
|
|
|
const limitPrice = serum3MarketExternal.priceNumberToLots(price);
|
|
|
|
const maxBaseQuantity = serum3MarketExternal.baseSizeNumberToLots(size);
|
|
|
|
const feeTier = getFeeTier(0, 0 /** TODO: fix msrm/srm balance */);
|
|
|
|
const rates = getFeeRates(feeTier);
|
|
|
|
const maxQuoteQuantity = new BN(
|
|
|
|
serum3MarketExternal.decoded.quoteLotSize.toNumber() *
|
|
|
|
(1 + rates.taker) /** TODO: fix taker/maker */,
|
|
|
|
).mul(
|
|
|
|
serum3MarketExternal
|
|
|
|
.baseSizeNumberToLots(size)
|
|
|
|
.mul(serum3MarketExternal.priceNumberToLots(price)),
|
|
|
|
);
|
|
|
|
|
2022-04-08 03:30:21 -07:00
|
|
|
return await this.program.methods
|
|
|
|
.serum3PlaceOrder(
|
|
|
|
side,
|
2022-04-08 07:57:37 -07:00
|
|
|
limitPrice,
|
|
|
|
maxBaseQuantity,
|
|
|
|
maxQuoteQuantity,
|
2022-04-08 03:30:21 -07:00
|
|
|
selfTradeBehavior,
|
|
|
|
orderType,
|
|
|
|
new BN(clientOrderId),
|
|
|
|
limit,
|
|
|
|
)
|
|
|
|
.accounts({
|
|
|
|
group: group.publicKey,
|
|
|
|
account: mangoAccount.publicKey,
|
|
|
|
owner: this.program.provider.wallet.publicKey,
|
|
|
|
openOrders: mangoAccount.findSerum3Account(serum3Market.marketIndex)
|
|
|
|
?.openOrders,
|
|
|
|
serumMarket: serum3Market.publicKey,
|
|
|
|
serumProgram: serum3ProgramId,
|
|
|
|
serumMarketExternal: serum3Market.serumMarketExternal,
|
|
|
|
marketBids: serum3MarketExternal.bidsAddress,
|
|
|
|
marketAsks: serum3MarketExternal.asksAddress,
|
|
|
|
marketEventQueue: serum3MarketExternal.decoded.eventQueue,
|
|
|
|
marketRequestQueue: serum3MarketExternal.decoded.requestQueue,
|
|
|
|
marketBaseVault: serum3MarketExternal.decoded.baseVault,
|
|
|
|
marketQuoteVault: serum3MarketExternal.decoded.quoteVault,
|
|
|
|
marketVaultSigner: serum3MarketExternalVaultSigner,
|
|
|
|
quoteBank: group.findBank(serum3Market.quoteTokenIndex)?.publicKey,
|
|
|
|
quoteVault: group.findBank(serum3Market.quoteTokenIndex)?.vault,
|
|
|
|
baseBank: group.findBank(serum3Market.baseTokenIndex)?.publicKey,
|
|
|
|
baseVault: group.findBank(serum3Market.baseTokenIndex)?.vault,
|
|
|
|
})
|
|
|
|
.remainingAccounts(
|
|
|
|
healthRemainingAccounts.map(
|
|
|
|
(pk) =>
|
|
|
|
({ pubkey: pk, isWritable: false, isSigner: false } as AccountMeta),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
.rpc();
|
|
|
|
}
|
|
|
|
|
2022-04-08 11:47:12 -07:00
|
|
|
async serum3SettleFunds(
|
|
|
|
group: Group,
|
|
|
|
mangoAccount: MangoAccount,
|
|
|
|
serum3ProgramId: PublicKey,
|
|
|
|
serum3MarketName: string,
|
|
|
|
): Promise<TransactionSignature> {
|
|
|
|
const serum3Market = group.serum3MarketsMap.get(serum3MarketName)!;
|
|
|
|
|
|
|
|
const serum3MarketExternal = await Market.load(
|
|
|
|
this.program.provider.connection,
|
|
|
|
serum3Market.serumMarketExternal,
|
|
|
|
{ commitment: this.program.provider.connection.commitment },
|
|
|
|
serum3ProgramId,
|
|
|
|
);
|
|
|
|
|
|
|
|
const serum3MarketExternalVaultSigner =
|
|
|
|
// TODO: put into a helper method, and remove copy pasta
|
|
|
|
await PublicKey.createProgramAddress(
|
|
|
|
[
|
|
|
|
serum3Market.serumMarketExternal.toBuffer(),
|
|
|
|
serum3MarketExternal.decoded.vaultSignerNonce.toArrayLike(
|
|
|
|
Buffer,
|
|
|
|
'le',
|
|
|
|
8,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
serum3ProgramId,
|
|
|
|
);
|
|
|
|
|
|
|
|
return await this.program.methods
|
|
|
|
.serum3SettleFunds()
|
|
|
|
.accounts({
|
|
|
|
group: group.publicKey,
|
|
|
|
account: mangoAccount.publicKey,
|
|
|
|
owner: this.program.provider.wallet.publicKey,
|
|
|
|
openOrders: mangoAccount.findSerum3Account(serum3Market.marketIndex)
|
|
|
|
?.openOrders,
|
|
|
|
serumMarket: serum3Market.publicKey,
|
|
|
|
serumProgram: serum3ProgramId,
|
|
|
|
serumMarketExternal: serum3Market.serumMarketExternal,
|
|
|
|
marketBaseVault: serum3MarketExternal.decoded.baseVault,
|
|
|
|
marketQuoteVault: serum3MarketExternal.decoded.quoteVault,
|
|
|
|
marketVaultSigner: serum3MarketExternalVaultSigner,
|
|
|
|
quoteBank: group.findBank(serum3Market.quoteTokenIndex)?.publicKey,
|
|
|
|
quoteVault: group.findBank(serum3Market.quoteTokenIndex)?.vault,
|
|
|
|
baseBank: group.findBank(serum3Market.baseTokenIndex)?.publicKey,
|
|
|
|
baseVault: group.findBank(serum3Market.baseTokenIndex)?.vault,
|
|
|
|
})
|
|
|
|
.rpc();
|
|
|
|
}
|
|
|
|
|
|
|
|
async serum3CancelOrder(
|
2022-04-08 07:57:37 -07:00
|
|
|
group: Group,
|
|
|
|
mangoAccount: MangoAccount,
|
|
|
|
serum3ProgramId: PublicKey,
|
|
|
|
serum3MarketName: string,
|
|
|
|
side: Serum3Side,
|
|
|
|
orderId: BN,
|
|
|
|
): Promise<TransactionSignature> {
|
|
|
|
const serum3Market = group.serum3MarketsMap.get(serum3MarketName)!;
|
|
|
|
|
|
|
|
const serum3MarketExternal = await Market.load(
|
|
|
|
this.program.provider.connection,
|
|
|
|
serum3Market.serumMarketExternal,
|
|
|
|
{ commitment: this.program.provider.connection.commitment },
|
|
|
|
serum3ProgramId,
|
|
|
|
);
|
|
|
|
return await this.program.methods
|
|
|
|
.serum3CancelOrder(side, orderId)
|
|
|
|
.accounts({
|
|
|
|
group: group.publicKey,
|
|
|
|
account: mangoAccount.publicKey,
|
|
|
|
openOrders: mangoAccount.findSerum3Account(serum3Market.marketIndex)
|
|
|
|
?.openOrders,
|
|
|
|
serumMarket: serum3Market.publicKey,
|
|
|
|
serumProgram: serum3ProgramId,
|
|
|
|
serumMarketExternal: serum3Market.serumMarketExternal,
|
|
|
|
marketBids: serum3MarketExternal.bidsAddress,
|
|
|
|
marketAsks: serum3MarketExternal.asksAddress,
|
|
|
|
marketEventQueue: serum3MarketExternal.decoded.eventQueue,
|
|
|
|
})
|
|
|
|
.rpc();
|
|
|
|
}
|
|
|
|
|
|
|
|
async getSerum3Orders(
|
|
|
|
group: Group,
|
|
|
|
serum3ProgramId: PublicKey,
|
|
|
|
serum3MarketName: string,
|
|
|
|
): Promise<Order[]> {
|
|
|
|
const serum3Market = group.serum3MarketsMap.get(serum3MarketName)!;
|
|
|
|
|
|
|
|
const serum3MarketExternal = await Market.load(
|
|
|
|
this.program.provider.connection,
|
|
|
|
serum3Market.serumMarketExternal,
|
|
|
|
{ commitment: this.program.provider.connection.commitment },
|
|
|
|
serum3ProgramId,
|
|
|
|
);
|
|
|
|
return await serum3MarketExternal.loadOrdersForOwner(
|
|
|
|
this.program.provider.connection,
|
|
|
|
group.publicKey,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-04-07 09:58:42 -07:00
|
|
|
/// static
|
|
|
|
|
2022-04-07 06:06:26 -07:00
|
|
|
static async connect(
|
|
|
|
provider: Provider,
|
|
|
|
devnet?: boolean,
|
|
|
|
): Promise<MangoClient> {
|
2022-03-31 23:59:39 -07:00
|
|
|
// TODO: use IDL on chain or in repository? decide...
|
2022-03-31 04:55:59 -07:00
|
|
|
// Alternatively we could fetch IDL from chain.
|
|
|
|
// const idl = await Program.fetchIdl(MANGO_V4_ID, provider);
|
2022-03-31 00:10:06 -07:00
|
|
|
let idl = IDL;
|
|
|
|
|
2022-03-31 23:59:39 -07:00
|
|
|
// TODO: remove...
|
2022-03-31 07:16:11 -07:00
|
|
|
// Temporarily add missing (dummy) type definitions, so we can do new Program(...) below
|
|
|
|
// without anchor throwing errors. These types come from part of the code we don't yet care about
|
|
|
|
// in the client.
|
|
|
|
function addDummyType(idl: MangoV4, typeName: string) {
|
2022-04-02 02:52:28 -07:00
|
|
|
if (idl.types.find((type) => type.name === typeName)) {
|
|
|
|
return;
|
|
|
|
}
|
2022-03-31 07:16:11 -07:00
|
|
|
(idl.types as any).push({
|
|
|
|
name: typeName,
|
|
|
|
type: {
|
|
|
|
kind: 'struct',
|
|
|
|
fields: [],
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
addDummyType(idl, 'usize');
|
|
|
|
addDummyType(idl, 'AnyNode');
|
|
|
|
addDummyType(idl, 'EventQueueHeader');
|
|
|
|
addDummyType(idl, 'AnyEvent');
|
|
|
|
addDummyType(idl, 'H');
|
|
|
|
addDummyType(idl, 'H::Item');
|
|
|
|
addDummyType(idl, 'NodeHandle');
|
2022-02-23 02:09:17 -08:00
|
|
|
|
|
|
|
return new MangoClient(
|
2022-03-30 08:16:32 -07:00
|
|
|
new Program<MangoV4>(idl as MangoV4, MANGO_V4_ID, provider),
|
2022-04-07 06:06:26 -07:00
|
|
|
devnet,
|
2022-02-23 02:09:17 -08:00
|
|
|
);
|
|
|
|
}
|
2022-04-07 08:16:46 -07:00
|
|
|
|
2022-04-07 09:58:42 -07:00
|
|
|
/// private
|
2022-04-07 08:58:20 -07:00
|
|
|
|
2022-04-07 09:58:42 -07:00
|
|
|
private async buildHealthRemainingAccounts(
|
2022-04-07 08:58:20 -07:00
|
|
|
group: Group,
|
2022-04-07 09:58:42 -07:00
|
|
|
mangoAccount: MangoAccount,
|
2022-04-08 03:30:21 -07:00
|
|
|
bank?: Bank /** TODO for serum3PlaceOrde we are just ingoring this atm */,
|
2022-04-07 09:58:42 -07:00
|
|
|
) {
|
|
|
|
const healthRemainingAccounts: PublicKey[] = [];
|
2022-04-07 08:58:20 -07:00
|
|
|
|
2022-04-08 03:30:21 -07:00
|
|
|
const tokenIndices = mangoAccount.tokens
|
|
|
|
.filter((token) => token.tokenIndex !== 65535)
|
|
|
|
.map((token) => token.tokenIndex);
|
|
|
|
|
|
|
|
if (bank) {
|
|
|
|
tokenIndices.push(bank.tokenIndex);
|
2022-04-07 09:58:42 -07:00
|
|
|
}
|
2022-04-08 03:30:21 -07:00
|
|
|
|
|
|
|
const mintInfos = await Promise.all(
|
|
|
|
[...new Set(tokenIndices)].map(async (tokenIndex) =>
|
|
|
|
getMintInfoForTokenIndex(this, group.publicKey, tokenIndex),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
healthRemainingAccounts.push(
|
|
|
|
...mintInfos.flatMap((mintinfos) => {
|
|
|
|
return mintinfos.flatMap((mintinfo) => {
|
|
|
|
return mintinfo.bank;
|
|
|
|
});
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
healthRemainingAccounts.push(
|
|
|
|
...mintInfos.flatMap((mintinfos) => {
|
|
|
|
return mintinfos.flatMap((mintinfo) => {
|
|
|
|
return mintinfo.oracle;
|
|
|
|
});
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
healthRemainingAccounts.push(
|
|
|
|
...mangoAccount.serum3
|
|
|
|
.filter((serum3Account) => serum3Account.marketIndex !== 65535)
|
|
|
|
.map((serum3Account) => serum3Account.openOrders),
|
|
|
|
);
|
|
|
|
|
2022-04-07 09:58:42 -07:00
|
|
|
return healthRemainingAccounts;
|
2022-04-07 08:58:20 -07:00
|
|
|
}
|
2022-02-23 02:09:17 -08:00
|
|
|
}
|