diff --git a/package.json b/package.json index 4ca9f97..21433a2 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,8 @@ "dependencies": { "@project-serum/serum": "^0.13.20", "@project-serum/sol-wallet-adapter": "^0.1.4", + "@solana/spl-token": "0.0.13", + "@solana/web3.js": "^0.90.0", "bn.js": "^5.1.2", "buffer-layout": "^1.2.0", diff --git a/src/client.ts b/src/client.ts index 30203b5..ba241d1 100644 --- a/src/client.ts +++ b/src/client.ts @@ -22,17 +22,18 @@ import BN from 'bn.js'; import { createAccountInstruction, getFilteredProgramAccounts, getUnixTs, - nativeToUi, + nativeToUi, parseTokenAccountData, promiseUndef, uiToNative, zeroKey, } from './utils'; import { Market, OpenOrders, Orderbook } from '@project-serum/serum'; -import { SRM_DECIMALS, TOKEN_PROGRAM_ID } from '@project-serum/serum/lib/token-instructions'; +import { SRM_DECIMALS } from '@project-serum/serum/lib/token-instructions'; import { Order } from '@project-serum/serum/lib/market'; import Wallet from '@project-serum/sol-wallet-adapter'; import { makeCancelOrderInstruction, makeSettleFundsInstruction } from './instruction'; import { Aggregator } from './schema' +import { AccountLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token'; export class MangoGroup { @@ -57,9 +58,15 @@ export class MangoGroup { mintDecimals!: number[]; oracleDecimals!: number[]; - constructor(publicKey: PublicKey, decoded: any) { - this.publicKey = publicKey; - Object.assign(this, decoded); + nativeSrm: number | null; + constructor(publicKey: PublicKey, decoded: any, nativeSrm?: number) { + this.publicKey = publicKey + Object.assign(this, decoded) + if (nativeSrm) { + this.nativeSrm = nativeSrm + } else { + this.nativeSrm = null + } } async getPrices( @@ -251,6 +258,16 @@ export class MarginAccount { return this.computeValue(mangoGroup, prices) } + getDeposits(mangoGroup: MangoGroup): number[] { + const deposits = new Array(NUM_TOKENS) + + for (let i = 0; i < NUM_TOKENS; i++) { + deposits[i] = this.getUiDeposit(mangoGroup, i) + } + + return deposits + } + getAssets(mangoGroup: MangoGroup): number[] { const assets = new Array(NUM_TOKENS) @@ -366,9 +383,7 @@ export class MangoClient { connection: Connection, programId: PublicKey, payer: PublicKey, - ) { - throw new Error("Not Implemented"); } @@ -564,10 +579,20 @@ export class MangoClient { ): Promise { const transaction = new Transaction() + + const assetGains: number[] = new Array(NUM_TOKENS).fill(0) + for (let i = 0; i < NUM_MARKETS; i++) { - if (marginAccount.openOrdersAccounts[i] == undefined) { + const openOrdersAccount = marginAccount.openOrdersAccounts[i] + if (openOrdersAccount === undefined) { + continue + } else if (openOrdersAccount.quoteTokenFree.toNumber() === 0 && openOrdersAccount.baseTokenFree.toNumber() === 0) { continue } + + assetGains[i] += openOrdersAccount.baseTokenFree.toNumber() + assetGains[NUM_TOKENS-1] += openOrdersAccount.quoteTokenFree.toNumber() + const spotMarket = markets[i] const dexSigner = await PublicKey.createProgramAddress( [ @@ -577,6 +602,8 @@ export class MangoClient { spotMarket.programId ) + + const keys = [ { isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey}, { isSigner: true, isWritable: false, pubkey: owner.publicKey }, @@ -599,10 +626,15 @@ export class MangoClient { transaction.add(instruction) } - const assets = marginAccount.getAssets(mangoGroup) + const deposits = marginAccount.getDeposits(mangoGroup) const liabs = marginAccount.getLiabs(mangoGroup) for (let i = 0; i < NUM_TOKENS; i++) { // TODO test this. maybe it hits transaction size limit + + const deposit = deposits[i] + assetGains[i] + if (deposit === 0 || liabs[i] === 0) { + continue + } const keys = [ { isSigner: false, isWritable: true, pubkey: mangoGroup.publicKey}, { isSigner: false, isWritable: true, pubkey: marginAccount.publicKey }, @@ -900,11 +932,23 @@ export class MangoClient { async getMangoGroup( connection: Connection, - mangoGroupPk: PublicKey + mangoGroupPk: PublicKey, + srmVaultPk?: PublicKey ): Promise { - const acc = await connection.getAccountInfo(mangoGroupPk); - const decoded = MangoGroupLayout.decode(acc == null ? undefined : acc.data); - return new MangoGroup(mangoGroupPk, decoded); + if (srmVaultPk) { + const [acc, srmVaultAcc] = await Promise.all( + [connection.getAccountInfo(mangoGroupPk), connection.getAccountInfo(srmVaultPk)] + ) + const decoded = MangoGroupLayout.decode(acc == null ? undefined : acc.data); + if (!srmVaultAcc) { return new MangoGroup(mangoGroupPk, decoded) } + + const srmVault = parseTokenAccountData(srmVaultAcc.data) + return new MangoGroup(mangoGroupPk, decoded, srmVault.amount) + } else { + const acc = await connection.getAccountInfo(mangoGroupPk); + const decoded = MangoGroupLayout.decode(acc == null ? undefined : acc.data); + return new MangoGroup(mangoGroupPk, decoded); + } } async getMarginAccount( diff --git a/src/ids.json b/src/ids.json index fd86242..1f3d6db 100644 --- a/src/ids.json +++ b/src/ids.json @@ -33,7 +33,8 @@ "BUgSeSxAyKdXc9VUqadCsS4ZuZZhdGjUr8JkRrxoZXV9", "DcfCtZrmHwEi1J3ksU2hYKGDR4n8Pbwi1fxURzNC6xEe", "HfiDTJ4V6KN7o4dtDfZgYxWLTipGJUPVCQxk6ajFkNEc" - ] + ], + "srm_vault_pk": "Fxoyr3u1oxzcY8NdYUwW6Mbi5mBW6KLx44J6zKiix43r" } }, "mango_program_id": "AHKufSeSqvjehJcW1uTy1RKhV4BafbpQxVGXGHn7G3hX", diff --git a/src/index.ts b/src/index.ts index 803e8ef..2669dee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ import { MangoClient, MangoGroup } from './client'; import IDS from './ids.json'; +import { Connection, PublicKey } from '@solana/web3.js'; +import { getUnixTs } from './utils'; export { MangoClient, MangoGroup, MarginAccount } from './client'; export { MangoIndexLayout, MarginAccountLayout, MangoGroupLayout } from './layout'; @@ -9,25 +11,6 @@ export * from './utils' export { IDS } - -// async function testMangoGroup() { -// const cluster = "devnet"; -// const client = new MangoClient(); -// const clusterIds = IDS[cluster] -// -// const connection = new Connection(IDS.cluster_urls[cluster], 'singleGossip') -// const mangoGroupPk = new PublicKey(clusterIds.mango_groups.BTC_ETH_USDC.mango_group_pk); -// const mangoProgramId = new PublicKey(clusterIds.mango_program_id); -// -// const mangoGroup = await client.getMangoGroup(connection, mangoGroupPk) -// -// for (let i = 0; i < NUM_TOKENS; i++) { -// console.log(nativeToUi(mangoGroup.borrowLimits[i], mangoGroup.mintDecimals[i])) -// } -// -// } -// testMangoGroup() - // async function testSolink() { // const cluster = "devnet"; // const client = new MangoClient(); diff --git a/src/utils.ts b/src/utils.ts index d3ffa5f..dad6d46 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,7 +2,7 @@ import { Account, AccountInfo, Connection, PublicKey, SystemProgram, Transaction import { publicKeyLayout, u64 } from './layout'; import BN from 'bn.js'; import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions'; -import { blob, struct, u8 } from 'buffer-layout'; +import { blob, struct, u8, nu64 } from 'buffer-layout'; export const zeroKey = new PublicKey(new Uint8Array(32)) @@ -171,4 +171,23 @@ export async function promiseUndef(): Promise { export const getUnixTs = () => { return new Date().getTime() / 1000; +} + + +export const ACCOUNT_LAYOUT = struct([ + blob(32, 'mint'), + blob(32, 'owner'), + nu64('amount'), + blob(93) +]); + +export function parseTokenAccountData( + data: Buffer, +): { mint: PublicKey; owner: PublicKey; amount: number } { + let { mint, owner, amount } = ACCOUNT_LAYOUT.decode(data); + return { + mint: new PublicKey(mint), + owner: new PublicKey(owner), + amount, + }; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0289e71..c6b690e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -269,6 +269,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.12.13" +"@babel/runtime@^7.10.5": + version "7.13.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.7.tgz#d494e39d198ee9ca04f4dcb76d25d9d7a1dc961a" + integrity sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.11.2", "@babel/runtime@^7.3.1": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.0.tgz#7f2cd0e2086626eea186a5a062e21e77ec0e45d0" @@ -577,6 +584,18 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@solana/spl-token@0.0.13": + version "0.0.13" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.0.13.tgz#5e0b235b1f8b34643280401dbfddeb34d13d1acd" + integrity sha512-WT8M9V/hxURR5jLbhr3zgwVsgcY6m8UhHtK045w7o+jx8FJ9MKARkj387WBFU7mKiFq0k8jw/8YL7XmnIUuH8Q== + dependencies: + "@babel/runtime" "^7.10.5" + "@solana/web3.js" "^0.86.1" + bn.js "^5.0.0" + buffer-layout "^1.2.0" + dotenv "8.2.0" + mkdirp "1.0.4" + "@solana/web3.js@0.86.1": version "0.86.1" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-0.86.1.tgz#034a2cef742569f74dfc9960dfbcabc92e674b08" @@ -600,6 +619,29 @@ tweetnacl "^1.0.0" ws "^7.0.0" +"@solana/web3.js@^0.86.1": + version "0.86.4" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-0.86.4.tgz#69216d3928ca4727c25a1ea96c405e897156ac3b" + integrity sha512-FpabDmdyxBN5aHIVUWc9Q6pXJFWiLRm/xeyxFg9O9ICHjiUkd38omds7G0CAmykIccG7zaMziwtkXp+0KvQOhA== + dependencies: + "@babel/runtime" "^7.3.1" + bn.js "^5.0.0" + bs58 "^4.0.1" + buffer "^5.4.3" + buffer-layout "^1.2.0" + crypto-hash "^1.2.2" + esdoc-inject-style-plugin "^1.0.0" + jayson "^3.0.1" + keccak "^3.0.1" + mz "^2.7.0" + node-fetch "^2.2.0" + npm-run-all "^4.1.5" + rpc-websockets "^7.4.2" + secp256k1 "^4.0.2" + superstruct "^0.8.3" + tweetnacl "^1.0.0" + ws "^7.0.0" + "@solana/web3.js@^0.90.0": version "0.90.5" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-0.90.5.tgz#5be7d78a19f0b5e01bf82c52e3cbf0bb72a38cfd" @@ -1802,6 +1844,11 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" +dotenv@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -3694,7 +3741,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@1.x: +mkdirp@1.0.4, mkdirp@1.x: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==