progress
This commit is contained in:
parent
787a82a9e9
commit
2c6c3c7ef8
|
@ -117,3 +117,5 @@ dist
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
lib/
|
lib/
|
||||||
|
|
||||||
|
secrets.json
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
lib/
|
||||||
|
node_modules/
|
2
shell
2
shell
|
@ -1,3 +1,5 @@
|
||||||
const lib = require('./lib/index');
|
const lib = require('./lib/index');
|
||||||
const solana = require('@solana/web3.js');
|
const solana = require('@solana/web3.js');
|
||||||
const serum = require('@project-serum/serum');
|
const serum = require('@project-serum/serum');
|
||||||
|
|
||||||
|
const SerumApi = lib.exchange.SerumApi;
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import dotenv from "dotenv";
|
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
|
// use passed port if specified otherwise default to the .env file
|
||||||
const PASSED_PORT = process.env.PORT;
|
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 HARD_CODED_MINTS = process.env.HARD_CODED_MINTS || {};
|
||||||
export const DEFAULT_TIMEOUT = 15000;
|
export const DEFAULT_TIMEOUT = 15000;
|
||||||
export const NUM_CONNECTIONS = 1;
|
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";
|
||||||
|
|
|
@ -1,22 +1,56 @@
|
||||||
import { Connection, PublicKey } from "@solana/web3.js";
|
import { Account, Connection, PublicKey } from "@solana/web3.js";
|
||||||
import { Exchange, Market, SerumMarketInfo } from "./types";
|
import { Coin, Exchange, MarketInfo, Pair } from "./types";
|
||||||
import * as config from "../config";
|
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 {
|
export class SerumApi {
|
||||||
static readonly exchange: Exchange = "serum";
|
static readonly exchange: Exchange = "serum";
|
||||||
private _connections: Connection[];
|
|
||||||
static url = config.SOLANA_URL;
|
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(
|
constructor(
|
||||||
|
exchange: Exchange,
|
||||||
conections: Connection[],
|
conections: Connection[],
|
||||||
marketInfo: { [market: string]: SerumMarketInfo },
|
marketInfo: { [market: string]: MarketInfo },
|
||||||
markets: Market[],
|
markets: Pair[],
|
||||||
marketAddresses: { [market: string]: PublicKey },
|
marketAddresses: { [market: string]: PublicKey },
|
||||||
addressProgramIds: { [address: string]: PublicKey },
|
addressProgramIds: { [address: string]: PublicKey },
|
||||||
url: string
|
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[] = [];
|
const connections: Connection[] = [];
|
||||||
for (let i = 0; i < config.NUM_CONNECTIONS; i++) {
|
for (let i = 0; i < config.NUM_CONNECTIONS; i++) {
|
||||||
const url =
|
const url =
|
||||||
|
@ -28,16 +62,20 @@ export class SerumApi {
|
||||||
connections.push(connection);
|
connections.push(connection);
|
||||||
}
|
}
|
||||||
const marketAddresses = Object.fromEntries(
|
const marketAddresses = Object.fromEntries(
|
||||||
this.constMarketInfo.map((info) => [info.market.key(), info.address])
|
EXCHANGE_ENABLED_MARKETS[this.exchange].map((info) => [
|
||||||
);
|
info.market.key(),
|
||||||
const markets = this.constMarketInfo.map((marketInfo) => marketInfo.market);
|
info.address,
|
||||||
const addressProgramIds = Object.fromEntries(
|
|
||||||
Object.entries(this.constMarketInfo).map(([market, info]) => [
|
|
||||||
info.address.toBase58(),
|
|
||||||
info.programId,
|
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
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) =>
|
markets.map((market) =>
|
||||||
this.getMarketInfo(
|
this.getMarketInfo(
|
||||||
connections[0],
|
connections[0],
|
||||||
|
@ -60,4 +98,62 @@ export class SerumApi {
|
||||||
this.url
|
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,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { getLayoutVersion, MARKETS, TOKEN_MINTS } from "@project-serum/serum";
|
import { getLayoutVersion, MARKETS, TOKEN_MINTS } from "@project-serum/serum";
|
||||||
import { HARD_CODED_MINTS } from "../config";
|
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) => {
|
export const MARKET_PARAMS = MARKETS.map((marketInfo) => {
|
||||||
const [coin, priceCurrency] = marketInfo.name.split("/");
|
const [coin, priceCurrency] = marketInfo.name.split("/");
|
||||||
return {
|
return {
|
||||||
address: marketInfo.address,
|
address: marketInfo.address,
|
||||||
market: new Market(coin, priceCurrency),
|
market: new Pair(coin, priceCurrency),
|
||||||
programId: marketInfo.programId,
|
programId: marketInfo.programId,
|
||||||
version: getLayoutVersion(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 HARD_CODED_COINS = new Set(Object.keys(HARD_CODED_MINTS));
|
||||||
|
|
||||||
export const COIN_MINTS = Object.fromEntries(
|
export const COIN_MINTS: { [coin: string]: string } = Object.fromEntries(
|
||||||
TOKEN_MINTS.filter(mint => !(mint.name in HARD_CODED_MINTS))
|
TOKEN_MINTS.filter((mint) => !(mint.name in HARD_CODED_MINTS))
|
||||||
.map((mint) => [mint.name, mint.address.toBase58()])
|
.map((mint) => [mint.name, mint.address.toBase58()])
|
||||||
.concat(Object.entries(HARD_CODED_MINTS))
|
.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,
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from "./api";
|
||||||
|
export * from "./config";
|
||||||
|
export * from "./solana";
|
||||||
|
export * from "./utils";
|
|
@ -5,7 +5,7 @@ import BN from "bn.js";
|
||||||
export type Coin = string;
|
export type Coin = string;
|
||||||
export type Exchange = string;
|
export type Exchange = string;
|
||||||
|
|
||||||
export class Market {
|
export class Pair {
|
||||||
coin;
|
coin;
|
||||||
priceCurrency;
|
priceCurrency;
|
||||||
|
|
||||||
|
@ -19,15 +19,15 @@ export class Market {
|
||||||
}
|
}
|
||||||
|
|
||||||
key(): string {
|
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("/");
|
const [coin, priceCurrency] = key.split("/");
|
||||||
return new Market(coin, priceCurrency);
|
return new Pair(coin, priceCurrency);
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: Market): boolean {
|
equals(other: Pair): boolean {
|
||||||
return (
|
return (
|
||||||
other.coin === this.coin && other.priceCurrency === this.priceCurrency
|
other.coin === this.coin && other.priceCurrency === this.priceCurrency
|
||||||
);
|
);
|
||||||
|
@ -109,7 +109,7 @@ export interface Fill<T = any> {
|
||||||
export interface L2OrderBook {
|
export interface L2OrderBook {
|
||||||
bids: [number, number][];
|
bids: [number, number][];
|
||||||
asks: [number, number][];
|
asks: [number, number][];
|
||||||
market: Market;
|
market: Pair;
|
||||||
validAt: number;
|
validAt: number;
|
||||||
receivedAt: number;
|
receivedAt: number;
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ export interface OwnOrders<T = Order> {
|
||||||
[orderId: string]: T;
|
[orderId: string]: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SerumMarketInfo {
|
export interface MarketInfo {
|
||||||
address: PublicKey;
|
address: PublicKey;
|
||||||
baseMint: PublicKey;
|
baseMint: PublicKey;
|
||||||
quoteMint: PublicKey;
|
quoteMint: PublicKey;
|
||||||
|
|
|
@ -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 BufferLayout from "buffer-layout";
|
||||||
import { TokenInstructions } from "@project-serum/serum";
|
import { TokenInstructions } from "@project-serum/serum";
|
||||||
|
|
||||||
|
@ -30,7 +36,6 @@ export const MINT_LAYOUT = BufferLayout.struct([
|
||||||
BufferLayout.blob(32, "freezeAuthority"),
|
BufferLayout.blob(32, "freezeAuthority"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
export function parseMintData(
|
export function parseMintData(
|
||||||
data: Buffer
|
data: Buffer
|
||||||
): { mintAuthority: PublicKey; supply: number; decimals: number } {
|
): { mintAuthority: PublicKey; supply: number; decimals: number } {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as utils from "./utils";
|
import * as utils from "./utils";
|
||||||
import * as configs from "./config";
|
import * as configs from "./config";
|
||||||
|
import * as exchange from "./exchange/index";
|
||||||
|
|
||||||
export { utils, configs };
|
export { utils, configs, exchange };
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import {SerumApi} from "./exchange/api";
|
import { SerumApi } from "./exchange/api";
|
||||||
import expressAsyncHandler from "express-async-handler";
|
import expressAsyncHandler from "express-async-handler";
|
||||||
import { logger } from "./utils";
|
import { logger } from "./utils";
|
||||||
|
|
||||||
|
@ -7,9 +7,7 @@ const router = express.Router();
|
||||||
let api: SerumApi;
|
let api: SerumApi;
|
||||||
|
|
||||||
router.get("/", (req, res, next) => {
|
router.get("/", (req, res, next) => {
|
||||||
res.send(
|
res.send("Hello from the Serum rest server!");
|
||||||
"Hello from the Serum rest server!"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.use(
|
router.use(
|
||||||
|
|
|
@ -71,9 +71,7 @@ class MorganStream {
|
||||||
}
|
}
|
||||||
export const morganStream = new MorganStream();
|
export const morganStream = new MorganStream();
|
||||||
|
|
||||||
export const getKeys = (
|
export const getKeys = (keys: string[]): any[] => {
|
||||||
keys: string[]
|
|
||||||
): string[] => {
|
|
||||||
const allSecrets = JSON.parse(readFileSync(SECRETS_FILE, "utf-8"));
|
const allSecrets = JSON.parse(readFileSync(SECRETS_FILE, "utf-8"));
|
||||||
const secrets: string[] = [];
|
const secrets: string[] = [];
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
|
|
Loading…
Reference in New Issue