UNPKG

@tracer-protocol/pools-js

Version:

Javascript library for interacting with Tracer's Perpetual Pools

220 lines (186 loc) 9.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _tinyTypedEmitter = require("tiny-typed-emitter"); var _ethers = require("ethers"); var _utils = require("../utils"); var _types = require("@tracer-protocol/perpetual-pools-contracts/types"); var _entities = require("../entities"); var _constants = require("../utils/constants"); // TODO: update to latest version after redeploy/abis are provided via sdk or other package class PoolWatcher extends _tinyTypedEmitter.TypedEmitter { constructor(args) { super(); if (!_constants.POOL_STATE_HELPER_BY_NETWORK[args.chainId]) { const supportedNetworks = Object.keys(_constants.POOL_STATE_HELPER_BY_NETWORK); throw new Error(`unsupported chainId: ${args.chainId}, supported values are [${supportedNetworks.join(', ')}]`); } this.provider = _ethers.ethers.getDefaultProvider(args.nodeUrl); this.poolInstance = _types.LeveragedPool__factory.connect(args.poolAddress, this.provider); this.poolSwapLibrary = _types.PoolSwapLibrary__factory.connect(_constants.POOL_STATE_HELPER_BY_NETWORK[args.chainId], this.provider); this.poolAddress = args.poolAddress; this.chainId = args.chainId; this.watchedPool = {}; this.commitmentWindowBuffer = args.commitmentWindowBuffer; this.isWatching = false; this.oraclePriceTransformer = args.oraclePriceTransformer || _utils.movingAveragePriceTransformer; this.ignoreEvents = args.ignoreEvents || {}; } // fetches details about pool to watch and // initialises smart contract instances of other perpetual pools components (keeper, committer, tokens) async initializeWatchedPool() { const [lastPriceTimestamp, sdkInstance] = await Promise.all([(0, _utils.attemptPromiseRecursively)({ promise: () => this.poolInstance.lastPriceTimestamp() }), (0, _utils.attemptPromiseRecursively)({ promise: () => _entities.Pool.Create({ address: this.poolAddress, provider: this.provider }) })]); this.watchedPool = { address: this.poolAddress, sdkInstance, // do not re-use keeper and committer instances from SDK since this class registers event listeners on them // and event listeners don't work properly on contracts connected to multicall providers committerInstance: _types.PoolCommitter__factory.connect(sdkInstance.committer.address, this.provider), keeperInstance: _types.PoolKeeper__factory.connect(sdkInstance.keeper, this.provider), updateInterval: sdkInstance.updateInterval.toNumber(), frontRunningInterval: sdkInstance.frontRunningInterval.toNumber(), lastPriceTimestamp: lastPriceTimestamp.toNumber() }; } async isCommitmentWindowStillOpen(updateIntervalId) { if (!this.watchedPool.address) { throw new Error('isCommitmentWindowStillOpen: watched pool not initialised'); } const appropriateUpdateIntervalId = await (0, _utils.attemptPromiseRecursively)({ promise: () => this.watchedPool.committerInstance.getAppropriateUpdateIntervalId() }); return appropriateUpdateIntervalId.eq(updateIntervalId); } async startWatchingPool() { if (this.isWatching) { throw new Error('startWatchingPool: already watching'); } this.isWatching = true; if (!this.watchedPool.address) { throw new Error('startWatchingPool: watched pool not initialised'); } const upkeepSuccessfulFilter = this.watchedPool.keeperInstance.filters.UpkeepSuccessful(this.poolAddress); if (!this.ignoreEvents[_constants.EVENT_NAMES.COMMITMENT_WINDOW_ENDING] || !this.ignoreEvents[_constants.EVENT_NAMES.COMMITMENT_WINDOW_ENDED]) { const [emitWindowEnding, emitWindowEnded] = [!this.ignoreEvents[_constants.EVENT_NAMES.COMMITMENT_WINDOW_ENDING], !this.ignoreEvents[_constants.EVENT_NAMES.COMMITMENT_WINDOW_ENDED]]; const scheduleStateCalculation = async () => { const [lastPriceTimestampEthersBN, appropriateIntervalIdBefore] = await Promise.all([(0, _utils.attemptPromiseRecursively)({ promise: () => this.poolInstance.lastPriceTimestamp() }), (0, _utils.attemptPromiseRecursively)({ promise: () => this.watchedPool.committerInstance.getAppropriateUpdateIntervalId() })]); const { frontRunningInterval, updateInterval } = this.watchedPool; const lastPriceTimestamp = lastPriceTimestampEthersBN.toNumber(); const commitmentWindowEnd = frontRunningInterval < updateInterval // simple case ? lastPriceTimestamp + updateInterval - frontRunningInterval // complex case, multiple update intervals within frontRunningInterval : lastPriceTimestamp + updateInterval; // calculate the time at which we should wait until to calculate expected pool state const waitUntil = commitmentWindowEnd - this.commitmentWindowBuffer; const nowSeconds = Math.floor(Date.now() / 1000); // if we are already past the start of the acceptable commitment window end // do nothing and wait until next upkeep to schedule anything if (nowSeconds > waitUntil) { if (emitWindowEnded) { if (nowSeconds > commitmentWindowEnd) { // if we are already ended this.emit(_constants.EVENT_NAMES.COMMITMENT_WINDOW_ENDED); } else { // time is between buffer and commitmentWindowEnd setTimeout(() => { this.emit(_constants.EVENT_NAMES.COMMITMENT_WINDOW_ENDED); }, (nowSeconds - commitmentWindowEnd) * 1000); } } this.watchedPool.keeperInstance.once(upkeepSuccessfulFilter, () => { scheduleStateCalculation(); }); } else { // set time out for waitUntil - nowSeconds // wake up and check if we are still inside of the same commitment window setTimeout(async () => { if (emitWindowEnded) { // wait the buffer time and fire an ended event setTimeout(() => { this.emit(_constants.EVENT_NAMES.COMMITMENT_WINDOW_ENDED); }, this.commitmentWindowBuffer * 1000); } if (emitWindowEnding) { const windowIsOpenBeforeStateCalc = await this.isCommitmentWindowStillOpen(appropriateIntervalIdBefore.toNumber()); // if the appropriate update interval id is still the same as before we slept, // we are still within the acceptable commitment window if (windowIsOpenBeforeStateCalc) { const expectedState = await this.watchedPool.sdkInstance.getExpectedPoolState('frontRunningInterval'); // do one last check to make sure commitment window has not ended const windowIsOpenAfterStateCalc = await (0, _utils.attemptPromiseRecursively)({ promise: () => this.watchedPool.committerInstance.getAppropriateUpdateIntervalId() }); if (windowIsOpenAfterStateCalc) { this.emit(_constants.EVENT_NAMES.COMMITMENT_WINDOW_ENDING, { ...expectedState, updateIntervalId: (0, _utils.ethersBNtoBN)(appropriateIntervalIdBefore) }); } } } this.watchedPool.keeperInstance.once(upkeepSuccessfulFilter, () => { scheduleStateCalculation(); }); }, (waitUntil - nowSeconds) * 1000); } }; scheduleStateCalculation(); } if (!this.ignoreEvents[_constants.EVENT_NAMES.COMMIT]) { const createCommitFilter = this.watchedPool.committerInstance.filters.CreateCommit(); this.watchedPool.committerInstance.on(createCommitFilter, async (user, amount, commitType, appropriateIntervalId, fromAggregateBalance, payForClaim, mintingFee, event) => { const block = await event.getBlock(); this.emit(_constants.EVENT_NAMES.COMMIT, { user, amount: (0, _utils.ethersBNtoBN)(amount), commitType: commitType, appropriateIntervalId: appropriateIntervalId.toNumber(), fromAggregateBalance, payForClaim, mintingFee, txHash: event.transactionHash, blockNumber: event.blockNumber, timestamp: block.timestamp, settlementTokenDecimals: this.watchedPool.sdkInstance.settlementToken.decimals }); }); } if (!this.ignoreEvents[_constants.EVENT_NAMES.UPKEEP]) { this.watchedPool.keeperInstance.on(upkeepSuccessfulFilter, async (poolAddress, data, startPrice, endPrice, event) => { const block = await event.getBlock(); this.emit(_constants.EVENT_NAMES.UPKEEP, { poolAddress, data, startPrice: (0, _utils.ethersBNtoBN)(startPrice), endPrice: (0, _utils.ethersBNtoBN)(endPrice), txHash: event.transactionHash, blockNumber: event.blockNumber, timestamp: block.timestamp }); }); } if (!this.ignoreEvents[_constants.EVENT_NAMES.COMMITS_EXECUTED]) { const commitsExecutedFilter = this.watchedPool.committerInstance.filters.ExecutedCommitsForInterval(); this.watchedPool.committerInstance.on(commitsExecutedFilter, async (updateIntervalId, burningFee, event) => { const block = await event.getBlock(); this.emit(_constants.EVENT_NAMES.COMMITS_EXECUTED, { updateIntervalId: updateIntervalId.toNumber(), burningFee, txHash: event.transactionHash, blockNumber: event.blockNumber, timestamp: block.timestamp }); }); } } } exports.default = PoolWatcher;