diff --git a/package.json b/package.json index 250b6e7..894dab5 100644 --- a/package.json +++ b/package.json @@ -45,10 +45,7 @@ "prettier": "^2.0.5", "ts-jest": "^26.2.0", "ts-node": "^9.1.1", - "typescript": "^4.1.3", - "antd": "^4.12.2", - "react": "^17.0.1", - "react-dom": "^17.0.1" + "typescript": "^4.1.3" }, "files": [ "lib" @@ -58,11 +55,10 @@ "trailingComma": "all" }, "dependencies": { - "@project-serum/serum": "^0.13.18", + "@project-serum/serum": "^0.13.20", "@solana/web3.js": "^0.90.0", "bn.js": "^5.1.2", "buffer-layout": "^1.2.0", - "double.js": "^1.0.14", "@project-serum/sol-wallet-adapter": "^0.1.4" }, "browserslist": [ diff --git a/src/client.ts b/src/client.ts index 8e0b4bd..3ec4288 100644 --- a/src/client.ts +++ b/src/client.ts @@ -13,7 +13,9 @@ import { import { encodeMangoInstruction, MangoGroupLayout, - MarginAccountLayout, NUM_MARKETS, NUM_TOKENS, + MarginAccountLayout, + NUM_MARKETS, + NUM_TOKENS, WideBits, } from './layout'; import BN from 'bn.js'; @@ -24,10 +26,8 @@ import { nativeToUi, uiToNative, zeroKey, - sendTransaction } from './utils'; import { Market, OpenOrders } from '@project-serum/serum'; -import { Wallet } from '@project-serum/sol-wallet-adapter'; import { TOKEN_PROGRAM_ID } from '@project-serum/serum/lib/token-instructions'; import { Order } from '@project-serum/serum/lib/market'; @@ -212,30 +212,8 @@ export class MarginAccount { } getCollateralRatio(mangoGroup: MangoGroup, prices: number[]): number { - let assetsVal = 0 - let liabsVal = 0 - for (let i = 0; i < NUM_TOKENS; i++) { - assetsVal += this.getUiDeposit(mangoGroup, i) * prices[i] - liabsVal += this.getUiBorrow(mangoGroup, i) * prices[i] - } - - if (liabsVal === 0) { - return 100 - } - - if (this.openOrdersAccounts == undefined) { - return assetsVal / liabsVal - } - for (let i = 0; i < NUM_MARKETS; i++) { - const openOrdersAccount = this.openOrdersAccounts[i] - if (openOrdersAccount == undefined) { - continue - } - - assetsVal += nativeToUi(openOrdersAccount.baseTokenTotal.toNumber(), mangoGroup.mintDecimals[i]) * prices[i] - assetsVal += nativeToUi(openOrdersAccount.quoteTokenTotal.toNumber(), mangoGroup.mintDecimals[NUM_TOKENS-1]) - - } + const assetsVal = this.getAssetsVal(mangoGroup, prices) + const liabsVal = this.getLiabsVal(mangoGroup, prices) return assetsVal / liabsVal } @@ -249,53 +227,35 @@ export class MangoClient { async sendTransaction( connection: Connection, transaction: Transaction, - payer: Account | Wallet, + payer: Account, additionalSigners: Account[], - notifications?: {sendingMessage: string, sentMessage: string, successMessage: string} ): Promise { - transaction.recentBlockhash = (await connection.getRecentBlockhash('max')).blockhash - transaction.setSigners(payer.publicKey, ...additionalSigners.map( a => a.publicKey )) // TODO test on mainnet - // if Wallet was provided, sign with wallet - if (!(payer instanceof Account)) { - // TODO test with wallet + transaction.recentBlockhash = (await connection.getRecentBlockhash('max')).blockhash + transaction.setSigners(payer.publicKey, ...additionalSigners.map( a => a.publicKey )) - let args = { - transaction, - wallet: payer, - signers: additionalSigners, - connection, - } - if (notifications) { - args = {...args, ...notifications} - } + const signers = [payer].concat(additionalSigners) + transaction.sign(...signers) + const rawTransaction = transaction.serialize() + return await sendAndConfirmRawTransaction(connection, rawTransaction, {skipPreflight: true}) - return await sendTransaction(args) - } else { - // otherwise sign with the payer account - const signers = [payer].concat(additionalSigners) - transaction.sign(...signers) - const rawTransaction = transaction.serialize() - return await sendAndConfirmRawTransaction(connection, rawTransaction, {skipPreflight: true}) - } } async initMarginAccount( connection: Connection, programId: PublicKey, - dexProgramId: PublicKey, // Serum DEX program ID mangoGroup: MangoGroup, - payer: Account | Wallet, + owner: Account, // assumed to be same as payer for now ): Promise { - // Create a Solana account for the MarginAccount and allocate space - const accInstr = await createAccountInstruction(connection, payer.publicKey, MarginAccountLayout.span, programId) + const accInstr = await createAccountInstruction(connection, + owner.publicKey, MarginAccountLayout.span, programId) // Specify the accounts this instruction takes in (see program/src/instruction.rs) const keys = [ - { isSigner: false, isWritable: false, pubkey: mangoGroup.publicKey}, + { isSigner: false, isWritable: false, pubkey: mangoGroup.publicKey }, { isSigner: false, isWritable: true, pubkey: accInstr.account.publicKey }, - { isSigner: true, isWritable: false, pubkey: payer.publicKey }, + { isSigner: true, isWritable: false, pubkey: owner.publicKey }, { isSigner: false, isWritable: false, pubkey: SYSVAR_RENT_PUBKEY } ] @@ -313,18 +273,8 @@ export class MangoClient { accInstr.account, ] - let notifications; - if (!(payer instanceof Account)) { - const functionName = 'InitMarginAccount' - notifications = { - sendingMessage: `sending ${functionName} instruction...`, - sentMessage: `${functionName} instruction sent`, - successMessage: `${functionName} instruction success` - } - } - // sign, send and confirm transaction - await this.sendTransaction(connection, transaction, payer, additionalSigners, notifications) + await this.sendTransaction(connection, transaction, owner, additionalSigners) return accInstr.account.publicKey } @@ -334,7 +284,7 @@ export class MangoClient { programId: PublicKey, mangoGroup: MangoGroup, marginAccount: MarginAccount, - owner: Account | Wallet, + owner: Account, token: PublicKey, tokenAcc: PublicKey, @@ -362,16 +312,7 @@ export class MangoClient { transaction.add(instruction) const additionalSigners = [] - let notifications; - if (!(owner instanceof Account)) { - const functionName = 'Deposit' - notifications = { - sendingMessage: `sending ${functionName} instruction...`, - sentMessage: `${functionName} instruction sent`, - successMessage: `${functionName} instruction success` - } - } - return await this.sendTransaction(connection, transaction, owner, additionalSigners, notifications) + return await this.sendTransaction(connection, transaction, owner, additionalSigners) } async withdraw( @@ -379,7 +320,7 @@ export class MangoClient { programId: PublicKey, mangoGroup: MangoGroup, marginAccount: MarginAccount, - owner: Account | Wallet, + owner: Account, token: PublicKey, tokenAcc: PublicKey, @@ -418,7 +359,7 @@ export class MangoClient { programId: PublicKey, mangoGroup: MangoGroup, marginAccount: MarginAccount, - owner: Account | Wallet, + owner: Account, token: PublicKey, quantity: number @@ -452,7 +393,7 @@ export class MangoClient { programId: PublicKey, mangoGroup: MangoGroup, marginAccount: MarginAccount, - owner: Account | Wallet, + owner: Account, token: PublicKey, quantity: number @@ -484,7 +425,7 @@ export class MangoClient { programId: PublicKey, mangoGroup: MangoGroup, liqeeMarginAccount: MarginAccount, // liquidatee marginAccount - liqor: Account | Wallet, // liquidator + liqor: Account, // liquidator tokenAccs: PublicKey[], depositQuantities: number[] ): Promise { @@ -519,7 +460,7 @@ export class MangoClient { mangoGroup: MangoGroup, marginAccount: MarginAccount, spotMarket: Market, - owner: Account | Wallet, + owner: Account, side: 'buy' | 'sell', price: number, @@ -609,7 +550,7 @@ export class MangoClient { programId: PublicKey, mangoGroup: MangoGroup, marginAccount: MarginAccount, - owner: Account | Wallet, + owner: Account, spotMarket: Market, ): Promise { @@ -659,7 +600,7 @@ export class MangoClient { programId: PublicKey, mangoGroup: MangoGroup, marginAccount: MarginAccount, - owner: Account | Wallet, + owner: Account, spotMarket: Market, order: Order, ): Promise { @@ -740,7 +681,7 @@ export class MangoClient { connection: Connection, programId: PublicKey, mangoGroup: MangoGroup, - owner: Account | Wallet + owner: Account ): Promise { const filters = [ @@ -766,7 +707,8 @@ export class MangoClient { const accounts = await getFilteredProgramAccounts(connection, programId, filters); return accounts.map( ({ publicKey, accountInfo }) => - new MarginAccount(publicKey, MarginAccountLayout.decode(accountInfo == null ? undefined : accountInfo.data)) + new MarginAccount(publicKey, MarginAccountLayout.decode( + accountInfo == null ? undefined : accountInfo.data)) ); } } diff --git a/src/layout.ts b/src/layout.ts index a36a7bb..4ae0768 100644 --- a/src/layout.ts +++ b/src/layout.ts @@ -1,5 +1,4 @@ - -import {struct, u8, blob, union, u32, Layout, bits, Blob, seq, BitStructure, UInt } from 'buffer-layout'; +import { bits, BitStructure, Blob, Layout, seq, struct, u32, u8, UInt, union } from 'buffer-layout'; import { PublicKey } from '@solana/web3.js'; import BN from 'bn.js'; diff --git a/src/notifications.tsx b/src/notifications.tsx deleted file mode 100644 index e420088..0000000 --- a/src/notifications.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from "react"; -import { notification } from "antd"; -// import Link from '../components/Link'; - -export function notify({ - message = "", - description = undefined as any, - txid = "", - type = "info", - placement = "bottomLeft", - }) { - if (txid) { - // - // View transaction {txid.slice(0, 8)}...{txid.slice(txid.length - 8)} - // - - description = <>; - } - (notification as any)[type]({ - message: {message}, - description: ( - {description} - ), - placement, - style: { - backgroundColor: "white", - }, - }); -} diff --git a/src/utils.ts b/src/utils.ts index ec409e4..473a041 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,18 +1,8 @@ -import { - Account, Commitment, - Connection, - PublicKey, RpcResponseAndContext, SimulatedTransactionResponse, - SystemProgram, - Transaction, - TransactionInstruction, - TransactionSignature, -} from '@solana/web3.js'; +import { Account, Connection, PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js'; 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 {notify} from "./notifications"; - export const zeroKey = new PublicKey(new Uint8Array(32)) @@ -142,283 +132,4 @@ export function nativeToUi(amount: number, decimals: number): number { return amount / Math.pow(10, decimals) -} - - -const DEFAULT_TIMEOUT = 15000; - -export async function sendTransaction({ - transaction, - wallet, - signers = [], - connection, - sendingMessage = 'Sending transaction...', - sentMessage = 'Transaction sent', - successMessage = 'Transaction confirmed', - timeout = DEFAULT_TIMEOUT, - }: { - transaction: Transaction; - wallet: any; - signers?: Array; - connection: Connection; - sendingMessage?: string; - sentMessage?: string; - successMessage?: string; - timeout?: number; -}) { - const signedTransaction = await signTransaction({ - transaction, - wallet, - signers, - connection, - }); - return await sendSignedTransaction({ - signedTransaction, - connection, - sendingMessage, - sentMessage, - successMessage, - timeout, - }); -} - -export async function signTransaction({ - transaction, - wallet, - signers = [], - connection, - }: { - transaction: Transaction; - wallet: any; - signers?: Array; - connection: Connection; -}) { - transaction.recentBlockhash = ( - await connection.getRecentBlockhash('max') - ).blockhash; - transaction.setSigners(wallet.publicKey, ...signers.map((s) => s.publicKey)); - if (signers.length > 0) { - transaction.partialSign(...signers); - } - return await wallet.signTransaction(transaction); -} - -export async function signTransactions({ - transactionsAndSigners, - wallet, - connection, - }: { - transactionsAndSigners: { - transaction: Transaction; - signers?: Array; - }[]; - wallet: any; - connection: Connection; -}) { - const blockhash = (await connection.getRecentBlockhash('max')).blockhash; - transactionsAndSigners.forEach(({ transaction, signers = [] }) => { - transaction.recentBlockhash = blockhash; - transaction.setSigners( - wallet.publicKey, - ...signers.map((s) => s.publicKey), - ); - - if (signers && signers.length > 0) { - transaction.partialSign(...signers); - } - }); - return await wallet.signAllTransactions( - transactionsAndSigners.map(({ transaction }) => transaction), - ); -} - -export const getUnixTs = () => { - return new Date().getTime() / 1000; -}; - -export async function sendSignedTransaction({ - signedTransaction, - connection, - sendingMessage = 'Sending transaction...', - sentMessage = 'Transaction sent', - successMessage = 'Transaction confirmed', - timeout = DEFAULT_TIMEOUT, - }: { - signedTransaction: Transaction; - connection: Connection; - sendingMessage?: string; - sentMessage?: string; - successMessage?: string; - timeout?: number; -}): Promise { - const rawTransaction = signedTransaction.serialize(); - const startTime = getUnixTs(); - notify({ message: sendingMessage }); - const txid: TransactionSignature = await connection.sendRawTransaction( - rawTransaction, - { - skipPreflight: true, - }, - ); - notify({ message: sentMessage, type: 'success', txid }); - - console.log('Started awaiting confirmation for', txid); - - let done = false; - (async () => { - while (!done && getUnixTs() - startTime < timeout) { - connection.sendRawTransaction(rawTransaction, { - skipPreflight: true, - }); - await sleep(300); - } - })(); - try { - await awaitTransactionSignatureConfirmation(txid, timeout, connection); - } catch (err) { - if (err.timeout) { - throw new Error('Timed out awaiting confirmation on transaction'); - } - let simulateResult: SimulatedTransactionResponse | null = null; - try { - simulateResult = ( - await simulateTransaction(connection, signedTransaction, 'single') - ).value; - } catch (e) {} - if (simulateResult && simulateResult.err) { - if (simulateResult.logs) { - for (let i = simulateResult.logs.length - 1; i >= 0; --i) { - const line = simulateResult.logs[i]; - if (line.startsWith('Program log: ')) { - throw new Error( - 'Transaction failed: ' + line.slice('Program log: '.length), - ); - } - } - } - throw new Error(JSON.stringify(simulateResult.err)); - } - throw new Error('Transaction failed'); - } finally { - done = true; - } - notify({ message: successMessage, type: 'success', txid }); - - console.log('Latency', txid, getUnixTs() - startTime); - return txid; -} - -async function awaitTransactionSignatureConfirmation( - txid: TransactionSignature, - timeout: number, - connection: Connection, -) { - let done = false; - const result = await new Promise((resolve, reject) => { - (async () => { - setTimeout(() => { - if (done) { - return; - } - done = true; - console.log('Timed out for txid', txid); - reject({ timeout: true }); - }, timeout); - try { - connection.onSignature( - txid, - (result) => { - console.log('WS confirmed', txid, result); - done = true; - if (result.err) { - reject(result.err); - } else { - resolve(result); - } - }, - 'recent', - ); - console.log('Set up WS connection', txid); - } catch (e) { - done = true; - console.log('WS error in setup', txid, e); - } - while (!done) { - // eslint-disable-next-line no-loop-func - (async () => { - try { - const signatureStatuses = await connection.getSignatureStatuses([ - txid, - ]); - const result = signatureStatuses && signatureStatuses.value[0]; - if (!done) { - if (!result) { - console.log('REST null result for', txid, result); - } else if (result.err) { - console.log('REST error for', txid, result); - done = true; - reject(result.err); - } else if (!result.confirmations) { - console.log('REST no confirmations for', txid, result); - } else { - console.log('REST confirmation for', txid, result); - done = true; - resolve(result); - } - } - } catch (e) { - if (!done) { - console.log('REST connection error: txid', txid, e); - } - } - })(); - await sleep(300); - } - })(); - }); - done = true; - return result; -} - -function mergeTransactions(transactions: (Transaction | undefined)[]) { - const transaction = new Transaction(); - transactions - .filter((t): t is Transaction => t !== undefined) - .forEach((t) => { - transaction.add(t); - }); - return transaction; -} - - -export async function sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - - -/** Copy of Connection.simulateTransaction that takes a commitment parameter. */ -async function simulateTransaction( - connection: Connection, - transaction: Transaction, - commitment: Commitment, -): Promise> { - // @ts-ignore - transaction.recentBlockhash = await connection._recentBlockhash( - // @ts-ignore - connection._disableBlockhashCaching, - ); - - const signData = transaction.serializeMessage(); - // @ts-ignore - const wireTransaction = transaction._serialize(signData); - const encodedTransaction = wireTransaction.toString('base64'); - const config: any = { encoding: 'base64', commitment }; - const args = [encodedTransaction, config]; - - // @ts-ignore - const res = await connection._rpcRequest('simulateTransaction', args); - if (res.error) { - throw new Error('failed to simulate transaction: ' + res.error.message); - } - return res.result; -} +} \ No newline at end of file