This commit is contained in:
Nathaniel Parke 2020-11-11 14:26:51 +08:00
parent 787a82a9e9
commit 2c6c3c7ef8
12 changed files with 165 additions and 39 deletions

2
.gitignore vendored
View File

@ -117,3 +117,5 @@ dist
.idea/
lib/
secrets.json

2
.prettierignore Normal file
View File

@ -0,0 +1,2 @@
lib/
node_modules/

2
shell
View File

@ -1,3 +1,5 @@
const lib = require('./lib/index');
const solana = require('@solana/web3.js');
const serum = require('@project-serum/serum');
const SerumApi = lib.exchange.SerumApi;

View File

@ -1,6 +1,4 @@
import dotenv from "dotenv";
import {PublicKey} from "@solana/web3.js";
import {Market} from "./exchange/types";
// use passed port if specified otherwise default to the .env file
const PASSED_PORT = process.env.PORT;
@ -20,4 +18,5 @@ export const RESTART_INTERVAL_SEC = parseInt(
export const HARD_CODED_MINTS = process.env.HARD_CODED_MINTS || {};
export const DEFAULT_TIMEOUT = 15000;
export const NUM_CONNECTIONS = 1;
export const SOLANA_URL = process.env.SOLANA_URL || "http://validator-lb.wirelesstable.net";
export const SOLANA_URL =
process.env.SOLANA_URL || "http://validator-lb.wirelesstable.net";

View File

@ -1,22 +1,56 @@
import { Connection, PublicKey } from "@solana/web3.js";
import { Exchange, Market, SerumMarketInfo } from "./types";
import { Account, Connection, PublicKey } from "@solana/web3.js";
import { Coin, Exchange, MarketInfo, Pair } from "./types";
import * as config from "../config";
import { COIN_MINTS, EXCHANGE_ENABLED_MARKETS } from "./config";
import { getKeys } from "../utils";
import assert from "assert";
import { Market } from "@project-serum/serum";
export class SerumApi {
static readonly exchange: Exchange = "serum";
private _connections: Connection[];
static url = config.SOLANA_URL;
readonly exchange: Exchange;
readonly marketInfo: { [market: string]: MarketInfo };
readonly markets: Pair[];
readonly addressMarkets: { [address: string]: Market };
readonly marketAddresses: { [market: string]: PublicKey };
readonly addressProgramIds: { [address: string]: PublicKey };
private _connections: Connection[];
private _publicKey: PublicKey;
private _privateKey: Array<number>;
private _account: Account;
private _wsConnection: Connection;
constructor(
exchange: Exchange,
conections: Connection[],
marketInfo: { [market: string]: SerumMarketInfo },
markets: Market[],
marketInfo: { [market: string]: MarketInfo },
markets: Pair[],
marketAddresses: { [market: string]: PublicKey },
addressProgramIds: { [address: string]: PublicKey },
url: string
) {}
) {
this.exchange = exchange;
this._connections = conections;
this._privateKey = getKeys([`${this.exchange}_private_key`])[0];
this._account = new Account(this._privateKey);
this._publicKey = this._account.publicKey;
this.marketInfo = marketInfo;
this.markets = markets;
this.marketAddresses = marketAddresses;
this.addressMarkets = Object.assign(
{},
...Object.entries(marketAddresses).map(([market, address]) => ({
[address.toBase58()]: Pair.fromKey(market),
}))
);
this.addressProgramIds = addressProgramIds;
this._wsConnection = new Connection(url, "recent");
}
static async create(options: { [optionName: string]: unknown } = {}): Promise<SerumApi> {
static async create(
options: { [optionName: string]: unknown } = {}
): Promise<SerumApi> {
const connections: Connection[] = [];
for (let i = 0; i < config.NUM_CONNECTIONS; i++) {
const url =
@ -28,16 +62,20 @@ export class SerumApi {
connections.push(connection);
}
const marketAddresses = Object.fromEntries(
this.constMarketInfo.map((info) => [info.market.key(), info.address])
);
const markets = this.constMarketInfo.map((marketInfo) => marketInfo.market);
const addressProgramIds = Object.fromEntries(
Object.entries(this.constMarketInfo).map(([market, info]) => [
info.address.toBase58(),
info.programId,
EXCHANGE_ENABLED_MARKETS[this.exchange].map((info) => [
info.market.key(),
info.address,
])
);
const marketInfo: Array<[Market, SerumMarketInfo]> = await Promise.all(
const markets = EXCHANGE_ENABLED_MARKETS[this.exchange].map(
(marketInfo) => marketInfo.market
);
const addressProgramIds = Object.fromEntries(
Object.entries(
EXCHANGE_ENABLED_MARKETS[this.exchange]
).map(([market, info]) => [info.address.toBase58(), info.programId])
);
const marketInfo: Array<[Pair, MarketInfo]> = await Promise.all(
markets.map((market) =>
this.getMarketInfo(
connections[0],
@ -60,4 +98,62 @@ export class SerumApi {
this.url
);
}
static async getMarketInfo(
connection: Connection,
coin: Coin,
priceCurrency: Coin,
marketAddress: PublicKey,
programId: PublicKey
): Promise<any> {
const market = new Pair(coin, priceCurrency);
const serumMarket = await Market.load(
connection,
marketAddress,
{},
programId
);
assert(
serumMarket.baseMintAddress.toBase58() === COIN_MINTS[coin],
`${coin} on ${coin}/${priceCurrency} has wrong mint. Our mint: ${
COIN_MINTS[coin]
} Serum's mint ${serumMarket.baseMintAddress.toBase58()}`
);
assert(
serumMarket.quoteMintAddress.toBase58() === COIN_MINTS[priceCurrency],
`${priceCurrency} on ${coin}/${priceCurrency} has wrong mint. Our mint: ${
COIN_MINTS[priceCurrency]
} Serum's mint ${serumMarket.quoteMintAddress.toBase58()}`
);
return [
market,
{
coin: coin,
priceCurrency: priceCurrency,
address: marketAddress,
baseMint: serumMarket.baseMintAddress,
quoteMint: serumMarket.quoteMintAddress,
minOrderSize: serumMarket.minOrderSize,
tickSize: serumMarket.tickSize,
programId: programId,
},
];
}
async getMarketInfo(): Promise<{ [k: string]: {[prop: string]: string | number}}> {
return Object.fromEntries(
Object.entries(this.marketInfo).map(([market, info]) => [
market,
{
...info,
address: info.address.toBase58(),
baseMint: info.baseMint.toBase58(),
quoteMint: info.quoteMint.toBase58(),
programId: info.programId.toBase58(),
minOrderSize: info.minOrderSize,
tickSize: info.tickSize,
},
])
);
}
}

View File

@ -1,12 +1,13 @@
import { getLayoutVersion, MARKETS, TOKEN_MINTS } from "@project-serum/serum";
import { HARD_CODED_MINTS } from "../config";
import { Market } from "./types";
import { Pair } from "./types";
import { PublicKey } from "@solana/web3.js";
export const MARKET_PARAMS = MARKETS.map((marketInfo) => {
const [coin, priceCurrency] = marketInfo.name.split("/");
return {
address: marketInfo.address,
market: new Market(coin, priceCurrency),
market: new Pair(coin, priceCurrency),
programId: marketInfo.programId,
version: getLayoutVersion(marketInfo.programId),
};
@ -14,8 +15,26 @@ export const MARKET_PARAMS = MARKETS.map((marketInfo) => {
export const HARD_CODED_COINS = new Set(Object.keys(HARD_CODED_MINTS));
export const COIN_MINTS = Object.fromEntries(
TOKEN_MINTS.filter(mint => !(mint.name in HARD_CODED_MINTS))
export const COIN_MINTS: { [coin: string]: string } = Object.fromEntries(
TOKEN_MINTS.filter((mint) => !(mint.name in HARD_CODED_MINTS))
.map((mint) => [mint.name, mint.address.toBase58()])
.concat(Object.entries(HARD_CODED_MINTS))
);
export const MINT_COINS: { [mint: string]: string } = Object.assign(
{},
...Object.entries(COIN_MINTS).map(([coin, mint]) => ({
[mint]: coin,
}))
);
export const EXCHANGE_ENABLED_MARKETS: {
[exchange: string]: {
address: PublicKey;
market: Pair;
programId: PublicKey;
version: number;
}[];
} = {
serum: MARKET_PARAMS,
};

View File

@ -0,0 +1,4 @@
export * from "./api";
export * from "./config";
export * from "./solana";
export * from "./utils";

View File

@ -5,7 +5,7 @@ import BN from "bn.js";
export type Coin = string;
export type Exchange = string;
export class Market {
export class Pair {
coin;
priceCurrency;
@ -19,15 +19,15 @@ export class Market {
}
key(): string {
return Market.key(this.coin, this.priceCurrency);
return Pair.key(this.coin, this.priceCurrency);
}
static fromKey(key: string): Market {
static fromKey(key: string): Pair {
const [coin, priceCurrency] = key.split("/");
return new Market(coin, priceCurrency);
return new Pair(coin, priceCurrency);
}
equals(other: Market): boolean {
equals(other: Pair): boolean {
return (
other.coin === this.coin && other.priceCurrency === this.priceCurrency
);
@ -109,7 +109,7 @@ export interface Fill<T = any> {
export interface L2OrderBook {
bids: [number, number][];
asks: [number, number][];
market: Market;
market: Pair;
validAt: number;
receivedAt: number;
}
@ -118,7 +118,7 @@ export interface OwnOrders<T = Order> {
[orderId: string]: T;
}
export interface SerumMarketInfo {
export interface MarketInfo {
address: PublicKey;
baseMint: PublicKey;
quoteMint: PublicKey;

View File

@ -1,4 +1,10 @@
import {Account, Connection, PublicKey, SystemProgram, Transaction} from "@solana/web3.js";
import {
Account,
Connection,
PublicKey,
SystemProgram,
Transaction,
} from "@solana/web3.js";
import BufferLayout from "buffer-layout";
import { TokenInstructions } from "@project-serum/serum";
@ -30,7 +36,6 @@ export const MINT_LAYOUT = BufferLayout.struct([
BufferLayout.blob(32, "freezeAuthority"),
]);
export function parseMintData(
data: Buffer
): { mintAuthority: PublicKey; supply: number; decimals: number } {

View File

@ -1,4 +1,5 @@
import * as utils from "./utils";
import * as configs from "./config";
import * as exchange from "./exchange/index";
export { utils, configs };
export { utils, configs, exchange };

View File

@ -1,5 +1,5 @@
import express from "express";
import {SerumApi} from "./exchange/api";
import { SerumApi } from "./exchange/api";
import expressAsyncHandler from "express-async-handler";
import { logger } from "./utils";
@ -7,9 +7,7 @@ const router = express.Router();
let api: SerumApi;
router.get("/", (req, res, next) => {
res.send(
"Hello from the Serum rest server!"
);
res.send("Hello from the Serum rest server!");
});
router.use(

View File

@ -71,9 +71,7 @@ class MorganStream {
}
export const morganStream = new MorganStream();
export const getKeys = (
keys: string[]
): string[] => {
export const getKeys = (keys: string[]): any[] => {
const allSecrets = JSON.parse(readFileSync(SECRETS_FILE, "utf-8"));
const secrets: string[] = [];
for (const key of keys) {