Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2021-09-24 16:10:08 +02:00
parent 2204b8c758
commit 17a70b4155
6 changed files with 252 additions and 24 deletions

View File

@ -33,7 +33,6 @@ losely sorted in order of importance/priority
- stop loss,
- market orders
- modify order
- funding rates
- withdraw
- funding payments
- advanced order types e.g. split

View File

@ -277,6 +277,95 @@
}
]
},
{
"name": "wallet - withdraw",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"coin\": \"USDC\",\n \"size\": 1000\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{baseUrl}}/wallet/withdrawals",
"host": [
"{{baseUrl}}"
],
"path": [
"wallet",
"withdrawals"
]
}
},
"response": [
{
"name": "wallet - withdraw",
"originalRequest": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"coin\": \"USDC\",\n \"size\": 1000\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{baseUrl}}/wallet/withdrawals",
"host": [
"{{baseUrl}}"
],
"path": [
"wallet",
"withdrawals"
]
}
},
"status": "Bad Request",
"code": 400,
"_postman_previewlanguage": "json",
"header": [
{
"key": "X-Powered-By",
"value": "Express"
},
{
"key": "Content-Type",
"value": "application/json; charset=utf-8"
},
{
"key": "Content-Length",
"value": "98"
},
{
"key": "ETag",
"value": "W/\"62-a5tgkKEcwWc3BLd/jTRTDN5QwDI\""
},
{
"key": "Date",
"value": "Fri, 24 Sep 2021 14:00:15 GMT"
},
{
"key": "Connection",
"value": "keep-alive"
},
{
"key": "Keep-Alive",
"value": "timeout=5"
}
],
"cookie": [],
"body": "{\n \"errors\": [\n {\n \"msg\": \"Transaction failed: MangoErrorCode::InsufficientFunds; src/processor.rs:831\"\n }\n ]\n}"
}
]
},
{
"name": "markets - get all",
"request": {
@ -875,7 +964,7 @@
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"market\": \"BTC-PERP\",\n \"side\": \"buy\",\n \"price\": 20000,\n \"type\": \"limit\",\n \"size\": 0.0001,\n \"reduceOnly\": false,\n \"ioc\": false,\n \"postOnly\": false,\n \"clientId\": 123\n}\n",
"raw": "{\n \"market\": \"BTC-PERP\",\n \"side\": \"buy\",\n \"price\": 20000,\n \"type\": \"limit\",\n \"size\": 0.0001,\n \"reduceOnly\": false,\n \"ioc\": false,\n \"postOnly\": false,\n \"clientId\": \"{{$randomInt}}\"\n}\n",
"options": {
"raw": {
"language": "json"

View File

@ -205,6 +205,60 @@ paths:
total: 50.000004999999994
usdValue: 50.000004999999994
availableWithoutBorrow: 50.000004999999994
/wallet/withdrawals:
post:
tags:
- default
summary: wallet - withdraw
requestBody:
content:
application/json:
schema:
type: object
example:
coin: USDC
size: 1000
responses:
'400':
description: Bad Request
headers:
X-Powered-By:
schema:
type: string
example: Express
Content-Type:
schema:
type: string
example: application/json; charset=utf-8
Content-Length:
schema:
type: integer
example: '98'
ETag:
schema:
type: string
example: W/"62-a5tgkKEcwWc3BLd/jTRTDN5QwDI"
Date:
schema:
type: string
example: Fri, 24 Sep 2021 14:00:15 GMT
Connection:
schema:
type: string
example: keep-alive
Keep-Alive:
schema:
type: string
example: timeout=5
content:
application/json:
schema:
type: object
example:
errors:
- msg: >-
Transaction failed: MangoErrorCode::InsufficientFunds;
src/processor.rs:831
/markets:
get:
tags:

View File

@ -6,6 +6,7 @@ import {
getMarketByBaseSymbolAndKind,
getMarketByPublicKey,
getMultipleAccounts,
getTokenBySymbol,
GroupConfig,
MangoAccount,
MangoClient,
@ -23,12 +24,14 @@ import {
Commitment,
Connection,
PublicKey,
TransactionSignature,
} from "@solana/web3.js";
import fs from "fs";
import fetch from "node-fetch";
import os from "os";
import { OrderInfo } from "types";
import { logger, zipDict } from "./utils";
import BN from "bn.js";
class MangoSimpleClient {
constructor(
@ -42,25 +45,6 @@ class MangoSimpleClient {
setInterval(this.roundRobinClusterUrl, 20_000);
}
private roundRobinClusterUrl() {
if (process.env.CLUSTER_URL) {
return;
}
let possibleClustersUrls = [
"https://api.mainnet-beta.solana.com",
"https://lokidfxnwlabdq.main.genesysgo.net:8899/",
"https://solana-api.projectserum.com/",
];
const clusterUrl =
possibleClustersUrls[
Math.floor(Math.random() * possibleClustersUrls.length)
];
logger.info(`switching to rpc node - ${clusterUrl}...`);
this.connection = new Connection(clusterUrl, "processed" as Commitment);
}
static async create() {
const groupName = "mainnet.1";
const clusterUrl =
@ -429,7 +413,8 @@ class MangoSimpleClient {
side,
price,
quantity,
orderType
orderType,
new BN(clientOrderId)
);
}
}
@ -510,6 +495,28 @@ class MangoSimpleClient {
return orderInfos;
}
public async withdraw(
tokenSymbol: string,
amount: number
): Promise<TransactionSignature> {
const tokenToWithdraw = getTokenBySymbol(
this.mangoGroupConfig,
tokenSymbol
);
const tokenIndex = this.mangoGroup.getTokenIndex(tokenToWithdraw.mintKey);
return this.client.withdraw(
this.mangoGroup,
this.mangoAccount,
this.owner,
this.mangoGroup.tokens[tokenIndex].rootBank,
this.mangoGroup.rootBankAccounts[tokenIndex].nodeBankAccounts[0]
.publicKey,
this.mangoGroup.rootBankAccounts[tokenIndex].nodeBankAccounts[0].vault,
Number(amount),
false
);
}
/// private
private parseSpotOrders(
@ -572,6 +579,25 @@ class MangoSimpleClient {
market: { account: market, config },
}));
}
private roundRobinClusterUrl() {
if (process.env.CLUSTER_URL) {
return;
}
let possibleClustersUrls = [
"https://api.mainnet-beta.solana.com",
"https://lokidfxnwlabdq.main.genesysgo.net:8899/",
"https://solana-api.projectserum.com/",
];
const clusterUrl =
possibleClustersUrls[
Math.floor(Math.random() * possibleClustersUrls.length)
];
logger.info(`switching to rpc node - ${clusterUrl}...`);
this.connection = new Connection(clusterUrl, "processed" as Commitment);
}
}
export default MangoSimpleClient;

View File

@ -22,6 +22,10 @@ const allMarketNames = mangoGroupConfig.spotMarkets
)
);
const allCoins = mangoGroupConfig.tokens.map(
(tokenConfig) => tokenConfig.symbol
);
/// general
export function zipDict<K extends string | number | symbol, V>(
@ -47,3 +51,10 @@ export const isValidMarket: CustomValidator = (marketName) => {
}
return Promise.resolve();
};
export const isValidCoin: CustomValidator = (coin) => {
if (allCoins.indexOf(coin) === -1) {
return Promise.reject(`Coin ${coin} not supported!`);
}
return Promise.resolve();
};

View File

@ -7,11 +7,13 @@ import {
} from "@blockworks-foundation/mango-client";
import { OpenOrders } from "@project-serum/serum";
import Controller from "controller.interface";
import { NextFunction, Request, Response, Router } from "express";
import { BadRequestErrorCustom } from "dtos";
import e, { NextFunction, Request, Response, Router } from "express";
import { body } from "express-validator";
import { sumBy } from "lodash";
import MangoSimpleClient from "mango.simple.client";
import { Balances } from "./types";
import { i80f48ToPercent } from "./utils";
import { i80f48ToPercent, isValidCoin } from "./utils";
class WalletController implements Controller {
public path = "/api/wallet";
@ -22,7 +24,16 @@ class WalletController implements Controller {
}
private initializeRoutes() {
// POST /wallet/balances
this.router.get(`${this.path}/balances`, this.fetchBalances);
// POST /wallet/withdrawals
this.router.post(
`${this.path}/withdrawals`,
body("coin").not().isEmpty().custom(isValidCoin),
body("size").isNumeric(),
this.withdraw
);
}
private fetchBalances = async (
@ -214,6 +225,24 @@ class WalletController implements Controller {
response.send({ success: true, result: balanceDtos } as BalancesDto);
};
private withdraw = async (
request: Request,
response: Response,
next: NextFunction
) => {
const withdrawDto = request.body as WithdrawDto;
this.mangoSimpleClient
.withdraw(withdrawDto.coin, withdrawDto.size)
.then(() => {
response.status(200);
})
.catch((error) => {
return response.status(400).send({
errors: [{ msg: error.message } as BadRequestErrorCustom],
});
});
};
}
export default WalletController;
@ -248,3 +277,23 @@ interface BalanceDto {
usdValue: number;
availableWithoutBorrow: number;
}
// e.g.
// {
// "coin": "USDTBEAR",
// "size": 20.2,
// "address": "0x83a127952d266A6eA306c40Ac62A4a70668FE3BE",
// "tag": null,
// "password": "my_withdrawal_password",
// "code": 152823
// }
interface WithdrawDto {
coin: string;
size: number;
// unused
address: undefined;
tag: undefined;
password: undefined;
code: undefined;
}