stability improvements (#5)

* add notify to discord hook

* split into worker & web roles

* dont log 404s

* use the correct connection url for perp market collection

* try to handle rate limit better

* change fetch interval to never double submit

* add extra try catch around collect market

* try with express api server using one redis connection

* just use a single Tedis instance

* fix error in tracking perp trades

* add LRU cache to reduce load on redis

Co-authored-by: Tyler Shipe <tjshipe@gmail.com>
This commit is contained in:
Maximilian Schneider 2021-09-20 18:17:12 +02:00 committed by GitHub
parent 89e97de28a
commit 6a62db975c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 280 additions and 270 deletions

2
Procfile Normal file
View File

@ -0,0 +1,2 @@
web: env WEBHOOK_URL=https://discord.com/api/webhooks/881943164309606421/sib-EX1K1mJYZGMoe_Quoc7Vob8pdmsxiG8vnXjDqkJQkMebvPTGikrRqHlyi8xDdrNg ROLE=web yarn start
worker: env WEBHOOK_URL=https://discord.com/api/webhooks/881946637784731691/xAskf3QXuj1Oxaq9ixYu1b9kk_LV-DlMF_CyZ-hUwzjbpl4yd2QAXjx391BMYt9uSfE5 ROLE=worker yarn start

View File

@ -12,6 +12,7 @@
"@types/cors": "^2.8.10",
"@types/express": "^4.17.11",
"@types/jasmine": "^3.6.3",
"@types/lru-cache": "^5.1.1",
"@types/node": "^14.14.28",
"jasmine": "^3.6.4",
"jasmine-spec-reporter": "^6.0.0",
@ -24,8 +25,10 @@
"@blockworks-foundation/mango-client": "^3.0.11",
"@project-serum/serum": "^0.13.20",
"@solana/web3.js": "^0.91.0",
"axios": "^0.21.1",
"cors": "^2.8.5",
"express": "^4.17.1",
"lru-cache": "^5.1.0",
"tedis": "^0.1.12"
},
"scripts": {
@ -39,5 +42,9 @@
},
"engines": {
"node": "14.x"
},
"resolutions": {
"bn.js": "5.1.3",
"@types/bn.js": "5.1.0"
}
}

View File

@ -17,73 +17,8 @@ import {
FillEvent,
} from '@blockworks-foundation/mango-client'
import BN from 'bn.js'
async function collectEventQueue(m: MarketConfig, r: RedisConfig) {
const store = await createRedisStore(r, m.marketName)
const marketAddress = new PublicKey(m.marketPk)
const programKey = new PublicKey(m.programId)
const connection = new Connection(m.clusterUrl)
const market = await Market.load(
connection,
marketAddress,
undefined,
programKey
)
async function fetchTrades(lastSeqNum?: number): Promise<[Trade[], number]> {
const now = Date.now()
const accountInfo = await connection.getAccountInfo(
market['_decoded'].eventQueue
)
if (accountInfo === null) {
throw new Error(
`Event queue account for market ${m.marketName} not found`
)
}
const { header, events } = decodeRecentEvents(accountInfo.data, lastSeqNum)
const takerFills = events.filter(
(e) => e.eventFlags.fill && !e.eventFlags.maker
)
const trades = takerFills
.map((e) => market.parseFillEvent(e))
.map((e) => {
return {
price: e.price,
side: e.side === 'buy' ? TradeSide.Buy : TradeSide.Sell,
size: e.size,
ts: now,
}
})
/*
if (trades.length > 0)
console.log({e: events.map(e => e.eventFlags), takerFills, trades})
*/
return [trades, header.seqNum]
}
async function storeTrades(ts: Trade[]) {
if (ts.length > 0) {
console.log(m.marketName, ts.length)
for (let i = 0; i < ts.length; i += 1) {
await store.storeTrade(ts[i])
}
}
}
while (true) {
try {
const lastSeqNum = await store.loadNumber('LASTSEQ')
const [trades, currentSeqNum] = await fetchTrades(lastSeqNum)
storeTrades(trades)
store.storeNumber('LASTSEQ', currentSeqNum)
} catch (err) {
console.error(m.marketName, err.toString())
}
await sleep({
Seconds: process.env.INTERVAL ? parseInt(process.env.INTERVAL) : 10,
})
}
}
import notify from './notify'
import LRUCache from 'lru-cache'
const redisUrl = new URL(process.env.REDISCLOUD_URL || 'redis://localhost:6379')
const host = redisUrl.hostname
@ -96,14 +31,15 @@ if (redisUrl.password !== '') {
const network = 'mainnet-beta'
const clusterUrl =
process.env.RPC_ENDPOINT_URL || 'https://solana-api.projectserum.com'
const fetchInterval = process.env.INTERVAL ? parseInt(process.env.INTERVAL) : 30
console.log({ clusterUrl, fetchInterval })
const programIdV3 = '9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin'
const nativeMarketsV3: Record<string, string> = {
'BTC/USDT': 'C1EuT9VokAKLiW7i2ASnZUvxDoKuKkCpDDeNxAptuNe4',
'ETH/USDT': '7dLVkUfBVfCGkFhSXDCq1ukM9usathSgS716t643iFGF',
'SOL/USDT': 'HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1',
'SRM/USDT': 'AtNnsY1AyRERWJ8xCskfz38YdvruWVJQUVXgScC1iPb',
'RAY/USDT': 'teE55QrL4a4QSfydR9dnHF97jgCfptpuigbb53Lo95g',
'BTC/USDC': 'A8YFbxQYFVqKZaoYJLLUVcQiWP7G2MeEgW5wsAQgMvFw',
'ETH/USDC': '4tSvZvnbyzHXLMTiFonMyxZoHmFqau1XArcRCVHLZ5gX',
@ -136,7 +72,86 @@ const symbolsByPk = Object.assign(
...Object.entries(nativeMarketsV3).map(([a, b]) => ({ [b]: a }))
)
async function collectEventQueue(m: MarketConfig, r: RedisConfig) {
try {
const store = await createRedisStore(r, m.marketName)
const marketAddress = new PublicKey(m.marketPk)
const programKey = new PublicKey(m.programId)
const connection = new Connection(m.clusterUrl)
const market = await Market.load(
connection,
marketAddress,
undefined,
programKey
)
async function fetchTrades(
lastSeqNum?: number
): Promise<[Trade[], number]> {
const now = Date.now()
const accountInfo = await connection.getAccountInfo(
market['_decoded'].eventQueue
)
if (accountInfo === null) {
throw new Error(
`Event queue account for market ${m.marketName} not found`
)
}
const { header, events } = decodeRecentEvents(
accountInfo.data,
lastSeqNum
)
const takerFills = events.filter(
(e) => e.eventFlags.fill && !e.eventFlags.maker
)
const trades = takerFills
.map((e) => market.parseFillEvent(e))
.map((e) => {
return {
price: e.price,
side: e.side === 'buy' ? TradeSide.Buy : TradeSide.Sell,
size: e.size,
ts: now,
}
})
/*
if (trades.length > 0)
console.log({e: events.map(e => e.eventFlags), takerFills, trades})
*/
return [trades, header.seqNum]
}
async function storeTrades(ts: Trade[]) {
if (ts.length > 0) {
console.log(m.marketName, ts.length)
for (let i = 0; i < ts.length; i += 1) {
await store.storeTrade(ts[i])
}
}
}
while (true) {
try {
const lastSeqNum = await store.loadNumber('LASTSEQ')
const [trades, currentSeqNum] = await fetchTrades(lastSeqNum)
storeTrades(trades)
store.storeNumber('LASTSEQ', currentSeqNum)
} catch (e) {
notify(`collectEventQueue ${m.marketName} ${e.toString()}`)
}
await sleep({ Seconds: fetchInterval })
}
} catch (e) {
notify(`collectEventQueue ${m.marketName} ${e.toString()}`)
}
}
function collectMarketData(programId: string, markets: Record<string, string>) {
if (process.env.ROLE === 'web') {
console.warn('ROLE=web detected. Not collecting market data.')
return
}
Object.entries(markets).forEach((e) => {
const [marketName, marketPk] = e
const marketConfig = {
@ -154,10 +169,7 @@ collectMarketData(programIdV3, nativeMarketsV3)
const groupConfig = Config.ids().getGroup('mainnet', 'mainnet.1') as GroupConfig
async function collectPerpEventQueue(r: RedisConfig, m: PerpMarketConfig) {
const connection = new Connection(
'https://mango.rpcpool.com',
'processed' as Commitment
)
const connection = new Connection(clusterUrl, 'processed' as Commitment)
const store = await createRedisStore(r, m.name)
const mangoClient = new MangoClient(connection, groupConfig!.mangoProgramId)
@ -170,10 +182,11 @@ async function collectPerpEventQueue(r: RedisConfig, m: PerpMarketConfig) {
)
async function fetchTrades(lastSeqNum?: BN): Promise<[Trade[], BN]> {
lastSeqNum ||= new BN(0)
const now = Date.now()
const eventQueue = await perpMarket.loadEventQueue(connection)
const events = eventQueue.eventsSince(lastSeqNum || new BN(0))
const events = eventQueue.eventsSince(lastSeqNum)
const trades = events
.map((e) => e.fill)
@ -188,7 +201,14 @@ async function collectPerpEventQueue(r: RedisConfig, m: PerpMarketConfig) {
}
})
return [trades, eventQueue.seqNum as any]
if (events.length > 0) {
const last = events[events.length - 1]
const latestSeqNum =
last.fill?.seqNum || last.liquidate?.seqNum || last.out?.seqNum
lastSeqNum = latestSeqNum
}
return [trades, lastSeqNum as BN]
}
async function storeTrades(ts: Trade[]) {
@ -207,21 +227,30 @@ async function collectPerpEventQueue(r: RedisConfig, m: PerpMarketConfig) {
storeTrades(trades)
store.storeNumber('LASTSEQ', currentSeqNum.toString() as any)
} catch (err) {
console.error(m.name, err.toString())
notify(`collectPerpEventQueue ${m.name} ${err.toString()}`)
}
await sleep({
Seconds: process.env.INTERVAL ? parseInt(process.env.INTERVAL) : 10,
})
await sleep({ Seconds: fetchInterval })
}
}
groupConfig.perpMarkets.forEach((m) =>
collectPerpEventQueue({ host, port, password, db: 0 }, m)
)
if (process.env.ROLE === 'web') {
console.warn('ROLE=web detected. Not collecting perp market data.')
} else {
groupConfig.perpMarkets.forEach((m) =>
collectPerpEventQueue({ host, port, password, db: 0 }, m)
)
}
const max_conn = parseInt(process.env.REDIS_MAX_CONN || '') || 200
const redisConfig = { host, port, password, db: 0, max_conn }
const pool = new TedisPool(redisConfig)
const conn = new Tedis({
host,
port,
password,
})
const cache = new LRUCache<string, Trade[]>(
parseInt(process.env.CACHE_LIMIT ?? '500')
)
const app = express()
app.use(cors())
@ -301,36 +330,31 @@ app.get('/tv/history', async (req, res) => {
// respond
try {
const conn = await pool.getTedis()
try {
const store = new RedisStore(conn, marketName)
const store = new RedisStore(conn, marketName)
// snap candle boundaries to exact hours
from = Math.floor(from / resolution) * resolution
to = Math.ceil(to / resolution) * resolution
// snap candle boundaries to exact hours
from = Math.floor(from / resolution) * resolution
to = Math.ceil(to / resolution) * resolution
// ensure the candle is at least one period in length
if (from == to) {
to += resolution
}
const candles = await store.loadCandles(resolution, from, to)
const response = {
s: 'ok',
t: candles.map((c) => c.start / 1000),
c: candles.map((c) => c.close),
o: candles.map((c) => c.open),
h: candles.map((c) => c.high),
l: candles.map((c) => c.low),
v: candles.map((c) => c.volume),
}
res.set('Cache-control', 'public, max-age=1')
res.send(response)
return
} finally {
pool.putTedis(conn)
// ensure the candle is at least one period in length
if (from == to) {
to += resolution
}
const candles = await store.loadCandles(resolution, from, to, cache)
const response = {
s: 'ok',
t: candles.map((c) => c.start / 1000),
c: candles.map((c) => c.close),
o: candles.map((c) => c.open),
h: candles.map((c) => c.high),
l: candles.map((c) => c.low),
v: candles.map((c) => c.volume),
}
res.set('Cache-control', 'public, max-age=1')
res.send(response)
return
} catch (e) {
console.error({ req, e })
notify(`tv/history ${marketName} ${e.toString()}`)
const error = { s: 'error' }
res.status(500).send(error)
}
@ -348,40 +372,34 @@ app.get('/trades/address/:marketPk', async (req, res) => {
const validPk = marketName != undefined
if (!validPk) {
const error = { s: 'error', validPk }
console.error({ marketPk, error })
res.status(404).send(error)
return
}
// respond
try {
const conn = await pool.getTedis()
try {
const store = new RedisStore(conn, marketName)
const trades = await store.loadRecentTrades()
const response = {
success: true,
data: trades.map((t) => {
return {
market: marketName,
marketAddress: marketPk,
price: t.price,
size: t.size,
side: t.side == TradeSide.Buy ? 'buy' : 'sell',
time: t.ts,
orderId: '',
feeCost: 0,
}
}),
}
res.set('Cache-control', 'public, max-age=5')
res.send(response)
return
} finally {
pool.putTedis(conn)
const store = new RedisStore(conn, marketName)
const trades = await store.loadRecentTrades()
const response = {
success: true,
data: trades.map((t) => {
return {
market: marketName,
marketAddress: marketPk,
price: t.price,
size: t.size,
side: t.side == TradeSide.Buy ? 'buy' : 'sell',
time: t.ts,
orderId: '',
feeCost: 0,
}
}),
}
res.set('Cache-control', 'public, max-age=5')
res.send(response)
return
} catch (e) {
console.error({ req, e })
notify(`trades ${marketName} ${e.toString()}`)
const error = { s: 'error' }
res.status(500).send(error)
}

View File

@ -1,4 +1,5 @@
import BN from 'bn.js';
import BN from 'bn.js'
import LRUCache from 'lru-cache'
export interface MarketConfig {
clusterUrl: string
@ -9,45 +10,48 @@ export interface MarketConfig {
export enum TradeSide {
None = 0,
Buy = 1,
Sell = 2
Buy = 1,
Sell = 2,
}
export interface Trade {
price: number;
side: TradeSide;
size: number;
ts: number;
};
price: number
side: TradeSide
size: number
ts: number
}
export interface Coder<T> {
encode: (t: T) => string;
decode: (s: string) => T;
};
encode: (t: T) => string
decode: (s: string) => T
}
export interface Candle {
open: number;
close: number;
high: number;
low: number;
volume: number;
vwap: number;
start: number;
end: number;
};
open: number
close: number
high: number
low: number
volume: number
vwap: number
start: number
end: number
}
export interface CandleStore {
storeTrade: (t: Trade) => Promise<void>;
loadCandles: (resolution: number, from: number, to:number) => Promise<Candle[]>;
};
storeTrade: (t: Trade) => Promise<void>
loadCandles: (
resolution: number,
from: number,
to: number,
cache?: LRUCache<string, Trade[]>
) => Promise<Candle[]>
}
export interface BufferStore {
storeBuffer: (ts: number, b: Buffer) => Promise<void>;
};
storeBuffer: (ts: number, b: Buffer) => Promise<void>
}
export interface KeyValStore {
storeNumber: (key: string, val: number) => Promise<void>;
loadNumber: (key: string) => Promise<number | undefined>;
};
storeNumber: (key: string, val: number) => Promise<void>
loadNumber: (key: string) => Promise<number | undefined>
}

13
src/notify.ts Normal file
View File

@ -0,0 +1,13 @@
import axios from 'axios'
export default function notify(content: string) {
if (process.env.WEBHOOK_URL) {
try {
axios.post(process.env.WEBHOOK_URL, { content })
} catch (e) {
console.error(`could not notify webhook: ${content}`)
}
} else {
console.warn(content)
}
}

View File

@ -10,6 +10,7 @@ import {
Trade,
} from './interfaces'
import { Tedis } from 'tedis'
import LRUCache from 'lru-cache'
export interface RedisConfig {
host: string
@ -68,15 +69,28 @@ export class RedisStore implements CandleStore, BufferStore, KeyValStore {
await this.connection.rpush(this.keyForTrade(t), coder.encode(t))
}
async loadTrades(
key: string,
cache?: LRUCache<string, Trade[]>
): Promise<Trade[]> {
const cached = cache?.get(key)
if (cached && key != this.keyForDay(Date.now())) return cached
const response = await this.connection.lrange(key, 0, -1)
const result = response.map((t) => coder.decode(t))
cache?.set(key, result)
return result
}
async loadCandles(
resolution: number,
from: number,
to: number
to: number,
cache?: LRUCache<string, Trade[]>
): Promise<Candle[]> {
const keys = this.keysForCandles(resolution, from, to)
const tradeRequests = keys.map((k) => this.connection.lrange(k, 0, -1))
const tradeRequests = keys.map((k) => this.loadTrades(k, cache))
const tradeResponses = await Promise.all(tradeRequests)
const trades = tradeResponses.flat().map((t) => coder.decode(t))
const trades = tradeResponses.flat()
const candles: Candle[] = []
while (from + resolution <= to) {
let candle = batch(trades, from, from + resolution)

150
yarn.lock
View File

@ -2,15 +2,6 @@
# yarn lockfile v1
"101@^1.0.0", "101@^1.2.0":
version "1.6.3"
resolved "https://registry.yarnpkg.com/101/-/101-1.6.3.tgz#9071196e60c47e4ce327075cf49c0ad79bd822fd"
integrity sha512-4dmQ45yY0Dx24Qxp+zAsNLlMF6tteCyfVzgbulvSyC7tCyd3V8sW76sS0tHq8NpcbXfWTKasfyfzU1Kd86oKzw==
dependencies:
clone "^1.0.2"
deep-eql "^0.1.3"
keypather "^1.10.2"
"@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5":
version "7.15.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b"
@ -26,14 +17,15 @@
regenerator-runtime "^0.13.4"
"@blockworks-foundation/mango-client@^3.0.11":
version "3.0.11"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-3.0.11.tgz#b27b3fad18dde15aa5ad3ea1edf5ea9a1951a831"
integrity sha512-FDn0Px0Tk9h1yQgFzus92nxfrtVlkLrynLW9UyMTPpnlCmCuWivYeEeIH+kCGyNUCI7RWzACAZI0xvHIpfVdNQ==
version "3.0.13"
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-3.0.13.tgz#a1ef6187ed4155c43d219617a9176a8515ba24c4"
integrity sha512-7VqkOFuFKlUN2IHLC20H+mZznBDX5vb283MJqlIAFZjjYSC09Avo/Il11gZhtm0JOL51mmrlgSGTCzZHk1QCGQ==
dependencies:
"@project-serum/serum" "0.13.55"
"@project-serum/sol-wallet-adapter" "^0.2.0"
"@solana/spl-token" "^0.1.6"
"@solana/web3.js" "1.21.0"
axios "^0.21.1"
big.js "^6.1.1"
bigint-buffer "^1.1.5"
bn.js "^5.1.0"
@ -198,14 +190,7 @@
superstruct "^0.14.2"
tweetnacl "^1.0.0"
"@types/bn.js@^4.11.5":
version "4.11.6"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==
dependencies:
"@types/node" "*"
"@types/bn.js@^5.1.0":
"@types/bn.js@5.1.0", "@types/bn.js@^4.11.5", "@types/bn.js@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68"
integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==
@ -277,20 +262,25 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"
integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==
"@types/lru-cache@^5.1.1":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef"
integrity sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==
"@types/mime@^1":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
"@types/node@*":
version "16.7.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.1.tgz#c6b9198178da504dfca1fd0be9b2e1002f1586f0"
integrity sha512-ncRdc45SoYJ2H4eWU9ReDfp3vtFqDYhjOsKlFFUDEn8V1Bgr2RjYal8YT5byfadWIRluhPFU6JiDOl0H6Sl87A==
version "16.7.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.10.tgz#7aa732cc47341c12a16b7d562f519c2383b6d4fc"
integrity sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==
"@types/node@^12.12.54":
version "12.20.20"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.20.tgz#ce3d6c13c15c5e622a85efcd3a1cb2d9c7fa43a6"
integrity sha512-kqmxiJg4AT7rsSPIhO6eoBIx9mNwwpeH42yjtgQh6X2ANSpLpvToMXv+LMFdfxpwG1FZXZ41OGZMiUAtbBLEvg==
version "12.20.23"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.23.tgz#d0d5885bb885ee9b1ed114a04ea586540a1b2e2a"
integrity sha512-FW0q7NI8UnjbKrJK8NGr6QXY69ATw9IFe6ItIo5yozPwA9DU/xkhiPddctUVyrmFXvyFYerYgQak/qu200UBDw==
"@types/node@^14.14.28":
version "14.14.28"
@ -405,17 +395,12 @@ array-flatten@1.1.1:
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
assert-args@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/assert-args/-/assert-args-1.2.1.tgz#404103a1452a32fe77898811e54e590a8a9373bd"
integrity sha1-QEEDoUUqMv53iYgR5U5ZCoqTc70=
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
"101" "^1.2.0"
compound-subject "0.0.1"
debug "^2.2.0"
get-prototype-of "0.0.0"
is-capitalized "^1.0.0"
is-class "0.0.4"
follow-redirects "^1.10.0"
balanced-match@^1.0.0:
version "1.0.0"
@ -458,15 +443,10 @@ bindings@^1.3.0:
dependencies:
file-uri-to-path "1.0.0"
bn.js@^4.11.9:
version "4.12.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
bn.js@^5.0.0, bn.js@^5.1.0, bn.js@^5.1.2:
version "5.2.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002"
integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==
bn.js@5.1.3, bn.js@^4.11.9, bn.js@^5.0.0, bn.js@^5.1.0, bn.js@^5.1.2:
version "5.1.3"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b"
integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==
body-parser@1.19.0:
version "1.19.0"
@ -499,9 +479,9 @@ borsh@^0.4.0:
bs58 "^4.0.0"
text-encoding-utf-8 "^1.0.2"
"borsh@git+https://github.com/defactojob/borsh-js.git#field-mapper":
"borsh@https://github.com/defactojob/borsh-js#field-mapper":
version "0.3.1"
resolved "git+https://github.com/defactojob/borsh-js.git#33a0d24af281112c0a48efb3fa503f3212443de9"
resolved "https://github.com/defactojob/borsh-js#33a0d24af281112c0a48efb3fa503f3212443de9"
dependencies:
"@types/bn.js" "^4.11.5"
bn.js "^5.0.0"
@ -681,11 +661,6 @@ cliui@^7.0.2:
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
clone@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
@ -725,11 +700,6 @@ commander@^2.20.3:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
compound-subject@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/compound-subject/-/compound-subject-0.0.1.tgz#271554698a15ae608b1dfcafd30b7ba1ea892c4b"
integrity sha1-JxVUaYoVrmCLHfyv0wt7oeqJLEs=
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -825,7 +795,7 @@ dateformat@~1.0.4-1.2.3:
get-stdin "^4.0.1"
meow "^3.3.0"
debug@2.6.9, debug@^2.2.0:
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@ -837,13 +807,6 @@ decamelize@^1.1.1, decamelize@^1.1.2:
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
deep-eql@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2"
integrity sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=
dependencies:
type-detect "0.1.1"
define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@ -1160,6 +1123,11 @@ find@^0.3.0:
dependencies:
traverse-chain "~0.1.0"
follow-redirects@^1.10.0:
version "1.14.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.2.tgz#cecb825047c00f5e66b142f90fed4f515dec789b"
integrity sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@ -1213,11 +1181,6 @@ get-intrinsic@^1.0.2:
has "^1.0.3"
has-symbols "^1.0.1"
get-prototype-of@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/get-prototype-of/-/get-prototype-of-0.0.0.tgz#98772bd10716d16deb4b322516c469efca28ac44"
integrity sha1-mHcr0QcW0W3rSzIlFsRp78oorEQ=
get-stdin@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
@ -1389,16 +1352,6 @@ is-callable@^1.1.4, is-callable@^1.2.2:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
is-capitalized@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-capitalized/-/is-capitalized-1.0.0.tgz#4c8464b4d91d3e4eeb44889dd2cd8f1b0ac4c136"
integrity sha1-TIRktNkdPk7rRIid0s2PGwrEwTY=
is-class@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/is-class/-/is-class-0.0.4.tgz#e057451705bb34e39e3e33598c93a9837296b736"
integrity sha1-4FdFFwW7NOOePjNZjJOpg3KWtzY=
is-core-module@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
@ -1595,13 +1548,6 @@ keccak@^3.0.1:
node-addon-api "^2.0.0"
node-gyp-build "^4.2.0"
keypather@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/keypather/-/keypather-1.10.2.tgz#e0449632d4b3e516f21cc014ce7c5644fddce614"
integrity sha1-4ESWMtSz5RbyHMAUznxWRP3c5hQ=
dependencies:
"101" "^1.0.0"
kind-of@^6.0.2:
version "6.0.3"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
@ -1748,6 +1694,13 @@ lru-cache@^4.0.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
lru-cache@^5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
dependencies:
yallist "^3.0.2"
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
@ -2273,12 +2226,11 @@ rimraf@^2.6.1:
glob "^7.1.3"
rpc-websockets@^7.4.2:
version "7.4.12"
resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.4.12.tgz#6a187a772cbe9ee48ed04e001b6d9c29b8e69bae"
integrity sha512-WxZRM4443SiYbJhsLwVJc6P/VAQJIkeDS89CQAuHqyMt/GX8GEplWZezcLw6MMGemzA6Kp32kz7CbQppMTLQxA==
version "7.4.14"
resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.4.14.tgz#d1774ce4d4c231dea6ed448d2bc224587b9561a5"
integrity sha512-x/2Rwzla6bXAyE8A21yx3sHjn49JUlgBUYfnKurNeqrZQgFxfD43Udo5NkTWQp+TASrssTlks8ipcJfvswgv5g==
dependencies:
"@babel/runtime" "^7.11.2"
assert-args "^1.2.1"
circular-json "^0.5.9"
eventemitter3 "^4.0.7"
uuid "^8.3.0"
@ -2685,11 +2637,6 @@ tweetnacl@^1.0.0:
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
type-detect@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822"
integrity sha1-C6XsKohWQORw6k6FBZcZANrFiCI=
type-is@~1.6.17, type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
@ -2788,9 +2735,9 @@ ws@^7.0.0:
integrity sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==
ws@^7.4.5:
version "7.5.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74"
integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==
version "7.5.4"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.4.tgz#56bfa20b167427e138a7795de68d134fe92e21f9"
integrity sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg==
xtend@^4.0.0:
version "4.0.2"
@ -2812,6 +2759,11 @@ yallist@^2.1.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yargs-parser@^20.2.2:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"