Merge branch 'dev'

This commit is contained in:
microwavedcola1 2022-04-13 09:15:34 +02:00
commit 1337d48257
13 changed files with 99 additions and 177 deletions

View File

@ -8,33 +8,6 @@
Devnet deployment - m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD Devnet deployment - m43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD
TS client - see ts dir, and ts/example.ts, run as `yarn ts-node ts/example.ts` TS client based examples (for details check respective scripts)
(cd ts/client && yarn example1-admin) - create a devnet group
### Module structure (cd ts/client && yarn example1-user) - run through some mangoaccount operations for a user on devnet
As and when we move to a more complete project, we should think of having multiple modules
e.g. core/shared, spot, perpetuals, etc., and then each would have its own instructions
and state sub module. Goal is that new contributors find relevant code easily and can navigate
easily.
```
programs
└── mango-v4
├── Cargo.toml
├── Xargo.toml
└── src
│ ├── error.rs
│ ├── instructions # instructions go here, each instruction gets an individual file
│ │   ├── initialiaze.rs
│ │   └── mod.rs
│ ├── lib.rs
│ └── state # state goes here, each account state gets an individual file
│ └── mod.rs
└── tests # rust tests, TODO
```
### How to open and manage pull requests
- when in doubt dont squash commits, specially when merge request is very large, specially if your branch contains unrelated commits
- use the why along with what for commit messages, code comments, makes it easy to understand the context
- add descriptions to your merge requests if they are non trivial, helps code reviewer watch out for things, understand the motivation for the merge request

View File

@ -13,7 +13,7 @@ anchor build --skip-lint
./idl-fixup.sh ./idl-fixup.sh
# update types in ts client package # update types in ts client package
cp -v ./target/types/mango_v4.ts ./ts/mango_v4.ts cp -v ./target/types/mango_v4.ts ./ts/client/src/mango_v4.ts
if [[ -z "${NO_DEPLOY}" ]]; then if [[ -z "${NO_DEPLOY}" ]]; then
# publish program # publish program
@ -29,13 +29,4 @@ fi
# build npm package # build npm package
tsc (cd ./ts/client && tsc)
# yarn clean && yarn build && cp package.json ./dist/
# publish the npm package
# yarn publish dist
# echo
# echo Remember to commit and push the version update as well as the changes
# echo to ts/mango_v4.tx.
# echo

View File

@ -1 +0,0 @@
See example1-user.ts and example1-admin.ts

View File

@ -8,8 +8,8 @@
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"clean": "rm -rf dist", "clean": "rm -rf dist",
"example1-user": "ts-node src/example1-user.ts", "example1-user": "ts-node src/scripts/example1-user.ts",
"example1-admin": "ts-node src/example1-admin.ts", "example1-admin": "ts-node src/scripts/example1-admin.ts",
"format": "prettier --check .", "format": "prettier --check .",
"lint": "eslint . --ext ts --ext tsx --ext js --quiet", "lint": "eslint . --ext ts --ext tsx --ext js --quiet",
"type-check": "tsc --pretty --noEmit" "type-check": "tsc --pretty --noEmit"

View File

@ -1,5 +1,6 @@
import { utf8 } from '@project-serum/anchor/dist/cjs/utils/bytes'; import { utf8 } from '@project-serum/anchor/dist/cjs/utils/bytes';
import { PublicKey } from '@solana/web3.js'; import { PublicKey } from '@solana/web3.js';
import { MangoClient } from '../client';
import { Bank } from './bank'; import { Bank } from './bank';
import { I80F48, I80F48Dto } from './I80F48'; import { I80F48, I80F48Dto } from './I80F48';
export class MangoAccount { export class MangoAccount {
@ -61,6 +62,10 @@ export class MangoAccount {
this.serum3 = serum3.values.map((dto) => Serum3Account.from(dto)); this.serum3 = serum3.values.map((dto) => Serum3Account.from(dto));
} }
async reload(client: MangoClient) {
Object.assign(this, await client.getMangoAccount(this));
}
findToken(tokenIndex: number): TokenAccount | undefined { findToken(tokenIndex: number): TokenAccount | undefined {
return this.tokens.find((ta) => ta.tokenIndex == tokenIndex); return this.tokens.find((ta) => ta.tokenIndex == tokenIndex);
} }

View File

@ -227,13 +227,13 @@ export class MangoClient {
{ {
memcmp: { memcmp: {
bytes: group.publicKey.toBase58(), bytes: group.publicKey.toBase58(),
offset: 24, offset: 40,
}, },
}, },
{ {
memcmp: { memcmp: {
bytes: ownerPk.toBase58(), bytes: ownerPk.toBase58(),
offset: 56, offset: 72,
}, },
}, },
]) ])
@ -700,10 +700,6 @@ export class MangoClient {
.map((serum3Account) => serum3Account.openOrders), .map((serum3Account) => serum3Account.openOrders),
); );
// TODO: remove
console.log(
`number of healthRemainingAccounts: ${healthRemainingAccounts.length}`,
);
return healthRemainingAccounts; return healthRemainingAccounts;
} }
} }

View File

@ -1,34 +1,6 @@
import { PublicKey } from '@solana/web3.js'; import { PublicKey } from '@solana/web3.js';
export const DEVNET_GROUP = 'DESBjo923XfXcAfKTmAdJ5UPguaAMoND4sYb18KY95Vx'; export const DEVNET_GROUP = 'NDLmPdjzm7Tm3gnsZw8EoBbBTR7wfAcyDY8LCqFSGtX';
export const DEVNET_MINTS = new Map([
['USDC', '8FRFC6MoGGkMFQwngccyu69VnYbzykGeez7ignHVAFSN'],
['BTC', '3UNBZ6o52WTWwjac2kPUb4FyodhU1vFkRJheu1Sh2TvU'],
]);
export const DEVNET_MINTS_REVERSE = Array.from(DEVNET_MINTS.entries()).reduce(
function (map, obj) {
map[obj[1]] = obj[0];
return map;
},
{},
);
export const DEVNET_ORACLES = new Map([
['BTC', 'HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J'],
]);
export const DEVNET_SERUM3_MARKETS = new Map([
['BTC/USDC', 'DW83EpHFywBxCHmyARxwj3nzxJd7MUdSeznmrdzZKNZB'],
]);
export const DEVNET_SERUM3_MARKETS_REVERSE = Array.from(
DEVNET_SERUM3_MARKETS.entries(),
).reduce(function (map, obj) {
map[obj[1]] = obj[0];
return map;
}, {});
export const DEVNET_SERUM3_PROGRAM_ID = new PublicKey( export const DEVNET_SERUM3_PROGRAM_ID = new PublicKey(
'DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY', 'DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY',

View File

@ -1,10 +1,10 @@
export { Group } from './client/accounts/group'; export { Group } from './accounts/group';
export * from './client/accounts/I80F48'; export * from './accounts/I80F48';
export { export {
MangoAccount, MangoAccount,
TokenAccount, TokenAccount,
TokenAccountDto, TokenAccountDto,
} from './client/accounts/mangoAccount'; } from './accounts/mangoAccount';
export { StubOracle } from './client/accounts/oracle'; export { StubOracle } from './accounts/oracle';
export { Serum3Market } from './client/accounts/serum3'; export { Serum3Market } from './accounts/serum3';
export * from './client/client'; export * from './client';

View File

@ -1569,7 +1569,7 @@ export type MangoV4 = {
"type": { "type": {
"array": [ "array": [
"u8", "u8",
16 32
] ]
} }
}, },
@ -4085,7 +4085,7 @@ export const IDL: MangoV4 = {
"type": { "type": {
"array": [ "array": [
"u8", "u8",
16 32
] ]
} }
}, },

View File

@ -1,16 +1,28 @@
import { Provider, Wallet } from '@project-serum/anchor'; import { Provider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs'; import fs from 'fs';
import { MangoClient } from './client'; import { MangoClient } from '../client';
import { import { DEVNET_SERUM3_PROGRAM_ID } from '../constants';
DEVNET_MINTS,
DEVNET_ORACLES, const DEVNET_SERUM3_MARKETS = new Map([
DEVNET_SERUM3_MARKETS, ['BTC/USDC', 'DW83EpHFywBxCHmyARxwj3nzxJd7MUdSeznmrdzZKNZB'],
DEVNET_SERUM3_PROGRAM_ID, ]);
} from './constants'; const DEVNET_MINTS = new Map([
['USDC', '8FRFC6MoGGkMFQwngccyu69VnYbzykGeez7ignHVAFSN'],
['BTC', '3UNBZ6o52WTWwjac2kPUb4FyodhU1vFkRJheu1Sh2TvU'],
]);
const DEVNET_ORACLES = new Map([
['BTC', 'HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J'],
]);
// //
// An example for admins based on high level api i.e. the client // An example for admins based on high level api i.e. the client
// Depoys a new mango group to devnet, registers 2 tokens, and 1 serum3 spot market
//
// process.env.ADMIN_KEYPAIR - group admin keypair path
// to create a new admin keypair:
// * solana-keygen new --outfile ~/.config/solana/admin.json
// * solana airdrop 1 -k ~/.config/solana/admin.json
// //
async function main() { async function main() {
const options = Provider.defaultOptions(); const options = Provider.defaultOptions();
@ -30,15 +42,15 @@ async function main() {
const client = await MangoClient.connect(adminProvider, true); const client = await MangoClient.connect(adminProvider, true);
// group // group
console.log(`Group...`); console.log(`Creating Group...`);
try { try {
await client.createGroup(); await client.createGroup();
} catch (error) {} } catch (error) {}
const group = await client.getGroupForAdmin(admin.publicKey); const group = await client.getGroupForAdmin(admin.publicKey);
console.log(`Group ${group.publicKey}`); console.log(`...registered group ${group.publicKey}`);
// register token 0 // register token 0
console.log(`Token 0...`); console.log(`Registering BTC...`);
const btcDevnetMint = new PublicKey(DEVNET_MINTS.get('BTC')!); const btcDevnetMint = new PublicKey(DEVNET_MINTS.get('BTC')!);
const btcDevnetOracle = new PublicKey(DEVNET_ORACLES.get('BTC')!); const btcDevnetOracle = new PublicKey(DEVNET_ORACLES.get('BTC')!);
try { try {
@ -59,10 +71,11 @@ async function main() {
1.4, 1.4,
0.02, 0.02,
); );
await group.reload(client);
} catch (error) {} } catch (error) {}
// stub oracle + register token 1 // stub oracle + register token 1
console.log(`Token 1...`); console.log(`Registering USDC...`);
const usdcDevnetMint = new PublicKey(DEVNET_MINTS.get('USDC')!); const usdcDevnetMint = new PublicKey(DEVNET_MINTS.get('USDC')!);
try { try {
await client.createStubOracle(group, usdcDevnetMint, 1.0); await client.createStubOracle(group, usdcDevnetMint, 1.0);
@ -86,18 +99,18 @@ async function main() {
1.4, 1.4,
0.02, 0.02,
); );
await group.reload(client);
} catch (error) {} } catch (error) {}
// log tokens/banks // log tokens/banks
const banks = await client.getBanksForGroup(group); for (const bank of await group.banksMap.values()) {
for (const bank of banks) {
console.log( console.log(
`Bank ${bank.tokenIndex} ${bank.publicKey}, mint ${bank.mint}, oracle ${bank.oracle}`, `...registered Bank ${bank.tokenIndex} ${bank.publicKey}, mint ${bank.mint}, oracle ${bank.oracle}`,
); );
} }
// register serum market // register serum market
console.log(`Serum3 market...`); console.log(`Registering serum3 market...`);
const serumMarketExternalPk = new PublicKey( const serumMarketExternalPk = new PublicKey(
DEVNET_SERUM3_MARKETS.get('BTC/USDC')!, DEVNET_SERUM3_MARKETS.get('BTC/USDC')!,
); );
@ -106,20 +119,18 @@ async function main() {
group, group,
DEVNET_SERUM3_PROGRAM_ID, DEVNET_SERUM3_PROGRAM_ID,
serumMarketExternalPk, serumMarketExternalPk,
banks[0], group.banksMap.get('BTC')!,
banks[1], group.banksMap.get('USDC')!,
0, 0,
'BTC/USDC', 'BTC/USDC',
); );
} catch (error) {} } catch (error) {}
const markets = await client.serum3GetMarket( const markets = await client.serum3GetMarket(
group, group,
banks.find((bank) => bank.mint.toBase58() === DEVNET_MINTS.get('BTC')) group.banksMap.get('BTC')?.tokenIndex,
?.tokenIndex, group.banksMap.get('USDC')?.tokenIndex,
banks.find((bank) => bank.mint.toBase58() === DEVNET_MINTS.get('USDC'))
?.tokenIndex,
); );
console.log(`Serum3 market ${markets[0].publicKey}`); console.log(`...registerd serum3 market ${markets[0].publicKey}`);
process.exit(); process.exit();
} }

View File

@ -1,16 +1,19 @@
import { Provider, Wallet } from '@project-serum/anchor'; import { Provider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import { Connection, Keypair } from '@solana/web3.js';
import fs from 'fs'; import fs from 'fs';
import { import {
Serum3OrderType, Serum3OrderType,
Serum3SelfTradeBehavior, Serum3SelfTradeBehavior,
Serum3Side, Serum3Side,
} from './accounts/serum3'; } from '../accounts/serum3';
import { MangoClient } from './client'; import { MangoClient } from '../client';
import { DEVNET_GROUP, DEVNET_SERUM3_PROGRAM_ID } from './constants'; import { DEVNET_SERUM3_PROGRAM_ID } from '../constants';
// //
// An example for users based on high level api i.e. the client // An example for users based on high level api i.e. the client
// Create
// process.env.USER_KEYPAIR - mango account owner keypair path
// process.env.ADMIN_KEYPAIR - group admin keypair path (useful for automatically finding the group)
// //
async function main() { async function main() {
const options = Provider.defaultOptions(); const options = Provider.defaultOptions();
@ -30,56 +33,40 @@ async function main() {
console.log(`User ${userWallet.publicKey.toBase58()}`); console.log(`User ${userWallet.publicKey.toBase58()}`);
// fetch group // fetch group
const group = await client.getGroup(new PublicKey(DEVNET_GROUP)); const admin = Keypair.fromSecretKey(
console.log(`Group ${group.publicKey.toBase58()}`); Buffer.from(
JSON.parse(fs.readFileSync(process.env.ADMIN_KEYPAIR!, 'utf-8')),
for (const bank of group.banksMap.values()) { ),
console.log(bank.publicKey.toBase58()); );
} const group = await client.getGroupForAdmin(admin.publicKey);
console.log(`Found group ${group.publicKey.toBase58()}`);
// create + fetch account // create + fetch account
let mangoAccount = await client.getOrCreateMangoAccount( console.log(`Creating mangoaccount...`);
const mangoAccount = await client.getOrCreateMangoAccount(
group, group,
user.publicKey, user.publicKey,
0, 0,
'my_mango_account', 'my_mango_account',
); );
console.log(`MangoAccount ${mangoAccount.publicKey}`); console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
// deposit and withdraw // deposit and withdraw
console.log(`Depositing...50_000000 USDC`); console.log(`Depositing...5000000 USDC`);
await client.deposit(group, mangoAccount, 'USDC', 50_000000); await client.deposit(group, mangoAccount, 'USDC', 50_000000);
await mangoAccount.reload(client);
// manual reload for now
mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
0,
'my_mango_account',
);
console.log(`Depositing...5000000 BTC`); console.log(`Depositing...5000000 BTC`);
await client.deposit(group, mangoAccount, 'BTC', 5000000); await client.deposit(group, mangoAccount, 'BTC', 5000000);
await mangoAccount.reload(client);
console.log(`Withdrawing...1000000`); console.log(`Withdrawing...1000000 USDC`);
// manual reload for now
mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
0,
'my_mango_account',
);
await client.withdraw(group, mangoAccount, 'USDC', 1_000000, false); await client.withdraw(group, mangoAccount, 'USDC', 1_000000, false);
await mangoAccount.reload(client);
// serum3 // serum3
console.log( console.log(
`Placing serum3 bid which would not be settled since its relatively low then midprice`, `Placing serum3 bid which would not be settled since its relatively low then midprice...`,
);
// manual reload for now
mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
0,
'my_mango_account',
); );
await client.serum3PlaceOrder( await client.serum3PlaceOrder(
group, group,
@ -94,15 +81,9 @@ async function main() {
Date.now(), Date.now(),
10, 10,
); );
await mangoAccount.reload(client);
console.log(`Placing serum3 bid way above midprice`); console.log(`Placing serum3 bid way above midprice...`);
// manual reload for now
mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
0,
'my_mango_account',
);
await client.serum3PlaceOrder( await client.serum3PlaceOrder(
group, group,
mangoAccount, mangoAccount,
@ -116,22 +97,9 @@ async function main() {
Date.now(), Date.now(),
10, 10,
); );
await mangoAccount.reload(client);
console.log(`Placing serum3 ask way below midprice`); console.log(`Placing serum3 ask way below midprice...`);
// manual reload for now
mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
0,
'my_mango_account',
);
// manual reload for now
mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
0,
'my_mango_account',
);
await client.serum3PlaceOrder( await client.serum3PlaceOrder(
group, group,
mangoAccount, mangoAccount,
@ -154,9 +122,9 @@ async function main() {
); );
for (const order of orders) { for (const order of orders) {
console.log( console.log(
`Order orderId ${order.orderId}, ${order.side}, ${order.price}, ${order.size}`, ` - Order orderId ${order.orderId}, ${order.side}, ${order.price}, ${order.size}`,
); );
console.log(`Cancelling order with ${order.orderId}`); console.log(` - Cancelling order with ${order.orderId}`);
await client.serum3CancelOrder( await client.serum3CancelOrder(
group, group,
mangoAccount, mangoAccount,

View File

@ -1,6 +1,5 @@
{ {
"extends": "@tsconfig/recommended/tsconfig.json", "extends": "@tsconfig/recommended/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
@ -8,7 +7,9 @@
"declarationDir": "dist", "declarationDir": "dist",
"declarationMap": true, "declarationMap": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["es2019"], "lib": [
"es2019"
],
"noImplicitAny": false, "noImplicitAny": false,
"outDir": "dist", "outDir": "dist",
"resolveJsonModule": true, "resolveJsonModule": true,
@ -16,6 +17,12 @@
"strictNullChecks": true, "strictNullChecks": true,
"target": "es6" "target": "es6"
}, },
"include": ["./ts/**/*"], "include": [
"exclude": ["./ts/**/*.test.js", "node_modules", "**/node_modules"] "./src/**/*"
} ],
"exclude": [
"./src/**/*.test.js",
"node_modules",
"**/node_modules"
]
}

View File

@ -4,5 +4,5 @@ set -e pipefail
anchor build --skip-lint anchor build --skip-lint
./idl-fixup.sh ./idl-fixup.sh
cp -v ./target/types/mango_v4.ts ./ts/mango_v4.ts cp -v ./target/types/mango_v4.ts ./ts/client/src/mango_v4.ts
tsc (cd ./ts/client && tsc)