@tracer-protocol/pools-js
Version:
Javascript library for interacting with Tracer's Perpetual Pools
220 lines (186 loc) • 9.73 kB
JavaScript
"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;