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
TS client - see ts dir, and ts/example.ts, run as `yarn ts-node ts/example.ts`
### Module structure
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
TS client based examples (for details check respective scripts)
(cd ts/client && yarn example1-admin) - create a devnet group
(cd ts/client && yarn example1-user) - run through some mangoaccount operations for a user on devnet

View File

@ -13,7 +13,7 @@ anchor build --skip-lint
./idl-fixup.sh
# 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
# publish program
@ -29,13 +29,4 @@ fi
# build npm package
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
(cd ./ts/client && tsc)

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import { utf8 } from '@project-serum/anchor/dist/cjs/utils/bytes';
import { PublicKey } from '@solana/web3.js';
import { MangoClient } from '../client';
import { Bank } from './bank';
import { I80F48, I80F48Dto } from './I80F48';
export class MangoAccount {
@ -61,6 +62,10 @@ export class MangoAccount {
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 {
return this.tokens.find((ta) => ta.tokenIndex == tokenIndex);
}

View File

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

View File

@ -1,34 +1,6 @@
import { PublicKey } from '@solana/web3.js';
export const DEVNET_GROUP = 'DESBjo923XfXcAfKTmAdJ5UPguaAMoND4sYb18KY95Vx';
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_GROUP = 'NDLmPdjzm7Tm3gnsZw8EoBbBTR7wfAcyDY8LCqFSGtX';
export const DEVNET_SERUM3_PROGRAM_ID = new PublicKey(
'DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY',

View File

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

View File

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

View File

@ -1,16 +1,28 @@
import { Provider, Wallet } from '@project-serum/anchor';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import fs from 'fs';
import { MangoClient } from './client';
import {
DEVNET_MINTS,
DEVNET_ORACLES,
DEVNET_SERUM3_MARKETS,
DEVNET_SERUM3_PROGRAM_ID,
} from './constants';
import { MangoClient } from '../client';
import { DEVNET_SERUM3_PROGRAM_ID } from '../constants';
const DEVNET_SERUM3_MARKETS = new Map([
['BTC/USDC', 'DW83EpHFywBxCHmyARxwj3nzxJd7MUdSeznmrdzZKNZB'],
]);
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
// 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() {
const options = Provider.defaultOptions();
@ -30,15 +42,15 @@ async function main() {
const client = await MangoClient.connect(adminProvider, true);
// group
console.log(`Group...`);
console.log(`Creating Group...`);
try {
await client.createGroup();
} catch (error) {}
const group = await client.getGroupForAdmin(admin.publicKey);
console.log(`Group ${group.publicKey}`);
console.log(`...registered group ${group.publicKey}`);
// register token 0
console.log(`Token 0...`);
console.log(`Registering BTC...`);
const btcDevnetMint = new PublicKey(DEVNET_MINTS.get('BTC')!);
const btcDevnetOracle = new PublicKey(DEVNET_ORACLES.get('BTC')!);
try {
@ -59,10 +71,11 @@ async function main() {
1.4,
0.02,
);
await group.reload(client);
} catch (error) {}
// stub oracle + register token 1
console.log(`Token 1...`);
console.log(`Registering USDC...`);
const usdcDevnetMint = new PublicKey(DEVNET_MINTS.get('USDC')!);
try {
await client.createStubOracle(group, usdcDevnetMint, 1.0);
@ -86,18 +99,18 @@ async function main() {
1.4,
0.02,
);
await group.reload(client);
} catch (error) {}
// log tokens/banks
const banks = await client.getBanksForGroup(group);
for (const bank of banks) {
for (const bank of await group.banksMap.values()) {
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
console.log(`Serum3 market...`);
console.log(`Registering serum3 market...`);
const serumMarketExternalPk = new PublicKey(
DEVNET_SERUM3_MARKETS.get('BTC/USDC')!,
);
@ -106,20 +119,18 @@ async function main() {
group,
DEVNET_SERUM3_PROGRAM_ID,
serumMarketExternalPk,
banks[0],
banks[1],
group.banksMap.get('BTC')!,
group.banksMap.get('USDC')!,
0,
'BTC/USDC',
);
} catch (error) {}
const markets = await client.serum3GetMarket(
group,
banks.find((bank) => bank.mint.toBase58() === DEVNET_MINTS.get('BTC'))
?.tokenIndex,
banks.find((bank) => bank.mint.toBase58() === DEVNET_MINTS.get('USDC'))
?.tokenIndex,
group.banksMap.get('BTC')?.tokenIndex,
group.banksMap.get('USDC')?.tokenIndex,
);
console.log(`Serum3 market ${markets[0].publicKey}`);
console.log(`...registerd serum3 market ${markets[0].publicKey}`);
process.exit();
}

View File

@ -1,16 +1,19 @@
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 {
Serum3OrderType,
Serum3SelfTradeBehavior,
Serum3Side,
} from './accounts/serum3';
import { MangoClient } from './client';
import { DEVNET_GROUP, DEVNET_SERUM3_PROGRAM_ID } from './constants';
} from '../accounts/serum3';
import { MangoClient } from '../client';
import { DEVNET_SERUM3_PROGRAM_ID } from '../constants';
//
// 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() {
const options = Provider.defaultOptions();
@ -30,56 +33,40 @@ async function main() {
console.log(`User ${userWallet.publicKey.toBase58()}`);
// fetch group
const group = await client.getGroup(new PublicKey(DEVNET_GROUP));
console.log(`Group ${group.publicKey.toBase58()}`);
for (const bank of group.banksMap.values()) {
console.log(bank.publicKey.toBase58());
}
const admin = Keypair.fromSecretKey(
Buffer.from(
JSON.parse(fs.readFileSync(process.env.ADMIN_KEYPAIR!, 'utf-8')),
),
);
const group = await client.getGroupForAdmin(admin.publicKey);
console.log(`Found group ${group.publicKey.toBase58()}`);
// create + fetch account
let mangoAccount = await client.getOrCreateMangoAccount(
console.log(`Creating mangoaccount...`);
const mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
0,
'my_mango_account',
);
console.log(`MangoAccount ${mangoAccount.publicKey}`);
console.log(`...created/found mangoAccount ${mangoAccount.publicKey}`);
// deposit and withdraw
console.log(`Depositing...50_000000 USDC`);
console.log(`Depositing...5000000 USDC`);
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`);
await client.deposit(group, mangoAccount, 'BTC', 5000000);
await mangoAccount.reload(client);
console.log(`Withdrawing...1000000`);
// manual reload for now
mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
0,
'my_mango_account',
);
console.log(`Withdrawing...1000000 USDC`);
await client.withdraw(group, mangoAccount, 'USDC', 1_000000, false);
await mangoAccount.reload(client);
// serum3
console.log(
`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',
`Placing serum3 bid which would not be settled since its relatively low then midprice...`,
);
await client.serum3PlaceOrder(
group,
@ -94,15 +81,9 @@ async function main() {
Date.now(),
10,
);
await mangoAccount.reload(client);
console.log(`Placing serum3 bid way above midprice`);
// manual reload for now
mangoAccount = await client.getOrCreateMangoAccount(
group,
user.publicKey,
0,
'my_mango_account',
);
console.log(`Placing serum3 bid way above midprice...`);
await client.serum3PlaceOrder(
group,
mangoAccount,
@ -116,22 +97,9 @@ async function main() {
Date.now(),
10,
);
await mangoAccount.reload(client);
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',
);
console.log(`Placing serum3 ask way below midprice...`);
await client.serum3PlaceOrder(
group,
mangoAccount,
@ -154,9 +122,9 @@ async function main() {
);
for (const order of orders) {
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(
group,
mangoAccount,

View File

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

View File

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