@xspswap/smart-order-router
Version:
XSwap Protocol V3 Smart Order Router
522 lines • 50.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OnChainQuoteProvider = exports.ProviderGasError = exports.ProviderTimeoutError = exports.ProviderBlockHeaderError = exports.SuccessRateError = exports.BlockConflictError = void 0;
const bignumber_1 = require("@ethersproject/bignumber");
const router_sdk_1 = require("@x-swap-protocol/router-sdk");
const sdk_core_1 = require("@x-swap-protocol/sdk-core");
const v3_sdk_1 = require("@x-swap-protocol/v3-sdk");
const async_retry_1 = __importDefault(require("async-retry"));
const lodash_1 = __importDefault(require("lodash"));
const stats_lite_1 = __importDefault(require("stats-lite"));
const router_1 = require("../routers/router");
const IMixedRouteQuoterV1__factory_1 = require("../types/other/factories/IMixedRouteQuoterV1__factory");
const IQuoterV2__factory_1 = require("../types/v3/factories/IQuoterV2__factory");
const util_1 = require("../util");
const addresses_1 = require("../util/addresses");
const log_1 = require("../util/log");
const routes_1 = require("../util/routes");
class BlockConflictError extends Error {
constructor() {
super(...arguments);
this.name = 'BlockConflictError';
}
}
exports.BlockConflictError = BlockConflictError;
class SuccessRateError extends Error {
constructor() {
super(...arguments);
this.name = 'SuccessRateError';
}
}
exports.SuccessRateError = SuccessRateError;
class ProviderBlockHeaderError extends Error {
constructor() {
super(...arguments);
this.name = 'ProviderBlockHeaderError';
}
}
exports.ProviderBlockHeaderError = ProviderBlockHeaderError;
class ProviderTimeoutError extends Error {
constructor() {
super(...arguments);
this.name = 'ProviderTimeoutError';
}
}
exports.ProviderTimeoutError = ProviderTimeoutError;
/**
* This error typically means that the gas used by the multicall has
* exceeded the total call gas limit set by the node provider.
*
* This can be resolved by modifying BatchParams to request fewer
* quotes per call, or to set a lower gas limit per quote.
*
* @export
* @class ProviderGasError
*/
class ProviderGasError extends Error {
constructor() {
super(...arguments);
this.name = 'ProviderGasError';
}
}
exports.ProviderGasError = ProviderGasError;
const DEFAULT_BATCH_RETRIES = 2;
/**
* Computes on chain quotes for swaps. For pure V3 routes, quotes are computed on-chain using
* the 'QuoterV2' smart contract. For exactIn mixed and V2 routes, quotes are computed using the 'MixedRouteQuoterV1' contract
* This is because computing quotes off-chain would require fetching all the tick data for each pool, which is a lot of data.
*
* To minimize the number of requests for quotes we use a Multicall contract. Generally
* the number of quotes to fetch exceeds the maximum we can fit in a single multicall
* while staying under gas limits, so we also batch these quotes across multiple multicalls.
*
* The biggest challenge with the quote provider is dealing with various gas limits.
* Each provider sets a limit on the amount of gas a call can consume (on Infura this
* is approximately 10x the block max size), so we must ensure each multicall does not
* exceed this limit. Additionally, each quote on V3 can consume a large number of gas if
* the pool lacks liquidity and the swap would cause all the ticks to be traversed.
*
* To ensure we don't exceed the node's call limit, we limit the gas used by each quote to
* a specific value, and we limit the number of quotes in each multicall request. Users of this
* class should set BatchParams such that multicallChunk * gasLimitPerCall is less than their node
* providers total gas limit per call.
*
* @export
* @class OnChainQuoteProvider
*/
class OnChainQuoteProvider {
/**
* Creates an instance of OnChainQuoteProvider.
*
* @param chainId The chain to get quotes for.
* @param provider The web 3 provider.
* @param multicall2Provider The multicall provider to use to get the quotes on-chain.
* Only supports the Uniswap Multicall contract as it needs the gas limitting functionality.
* @param retryOptions The retry options for each call to the multicall.
* @param batchParams The parameters for each batched call to the multicall.
* @param gasErrorFailureOverride The gas and chunk parameters to use when retrying a batch that failed due to out of gas.
* @param successRateFailureOverrides The parameters for retries when we fail to get quotes.
* @param blockNumberConfig Parameters for adjusting which block we get quotes from, and how to handle block header not found errors.
* @param [quoterAddressOverride] Overrides the address of the quoter contract to use.
*/
constructor(chainId, provider,
// Only supports Uniswap Multicall as it needs the gas limitting functionality.
multicall2Provider, retryOptions = {
retries: DEFAULT_BATCH_RETRIES,
minTimeout: 25,
maxTimeout: 250,
}, batchParams = {
multicallChunk: 150,
gasLimitPerCall: 1000000,
quoteMinSuccessRate: 0.2,
}, gasErrorFailureOverride = {
gasLimitOverride: 1500000,
multicallChunk: 100,
}, successRateFailureOverrides = {
gasLimitOverride: 1300000,
multicallChunk: 110,
}, blockNumberConfig = {
baseBlockOffset: 0,
rollback: { enabled: false },
}, quoterAddressOverride) {
this.chainId = chainId;
this.provider = provider;
this.multicall2Provider = multicall2Provider;
this.retryOptions = retryOptions;
this.batchParams = batchParams;
this.gasErrorFailureOverride = gasErrorFailureOverride;
this.successRateFailureOverrides = successRateFailureOverrides;
this.blockNumberConfig = blockNumberConfig;
this.quoterAddressOverride = quoterAddressOverride;
}
getQuoterAddress(useMixedRouteQuoter, useUniV3RouteQuoter) {
if (this.quoterAddressOverride) {
return this.quoterAddressOverride;
}
const quoterAddress = useUniV3RouteQuoter
? useMixedRouteQuoter
? '0x955d998330042d7F7872eA453255544eb4D2d712' // mixed UNI_v3 - XSWAP_v2
: '0x5911cB3633e764939edc2d92b7e1ad375Bb57649' // UNI_v3
: useMixedRouteQuoter
? sdk_core_1.MIXED_ROUTE_QUOTER_V1_ADDRESSES[this.chainId]
: addresses_1.QUOTER_V2_ADDRESSES[this.chainId];
if (!quoterAddress) {
throw new Error(`No address for the quoter contract on chain id: ${this.chainId}`);
}
return quoterAddress;
}
async getQuotesManyExactIn(amountIns, routes, providerConfig) {
return this.getQuotesManyData(amountIns, routes, 'quoteExactInput', providerConfig);
}
async getQuotesManyExactOut(amountOuts, routes, providerConfig) {
return this.getQuotesManyData(amountOuts, routes, 'quoteExactOutput', providerConfig);
}
async getQuotesManyData(amounts, routes, functionName, _providerConfig) {
var _a;
const useMixedRouteQuoter = routes.some((route) => route.protocol === router_sdk_1.Protocol.V2 || route.protocol === router_sdk_1.Protocol.FATHOM) || routes.some((route) => route.protocol === router_sdk_1.Protocol.MIXED);
const useUniV3RouteQuoter = routes.some((route) => route.protocol === router_sdk_1.Protocol.UNI_V3 ||
(route.protocol === router_sdk_1.Protocol.MIXED &&
route.pools.some((pool) => pool instanceof v3_sdk_1.Pool && pool.factory)));
/// Validate that there are no incorrect routes / function combinations
this.validateRoutes(routes, functionName, useMixedRouteQuoter);
let multicallChunk = this.batchParams.multicallChunk;
let gasLimitOverride = this.batchParams.gasLimitPerCall;
const { baseBlockOffset, rollback } = this.blockNumberConfig;
// Apply the base block offset if provided
const originalBlockNumber = await this.provider.getBlockNumber();
const providerConfig = Object.assign(Object.assign({}, _providerConfig), { blockNumber: (_a = _providerConfig === null || _providerConfig === void 0 ? void 0 : _providerConfig.blockNumber) !== null && _a !== void 0 ? _a : originalBlockNumber + baseBlockOffset });
const inputs = (0, lodash_1.default)(routes)
.flatMap((route) => {
const encodedRoute = (0, util_1.isV3)(route.protocol)
? (0, v3_sdk_1.encodeRouteToPath)(
// @ts-ignore
route, functionName == 'quoteExactOutput' // For exactOut must be true to ensure the routes are reversed.
)
: (0, router_sdk_1.encodeMixedRouteToPath)(
// @ts-ignore
route instanceof router_1.V2Route
? new router_sdk_1.MixedRouteSDK(route.pairs, route.input, route.output)
: route);
const routeInputs = amounts.map((amount) => [
encodedRoute,
`0x${amount.quotient.toString(16)}`,
]);
return routeInputs;
})
.value();
const normalizedChunk = Math.ceil(inputs.length / Math.ceil(inputs.length / multicallChunk));
const inputsChunked = lodash_1.default.chunk(inputs, normalizedChunk);
let quoteStates = lodash_1.default.map(inputsChunked, (inputChunk) => {
return {
status: 'pending',
inputs: inputChunk,
};
});
log_1.log.info(`About to get ${inputs.length} quotes in chunks of ${normalizedChunk} [${lodash_1.default.map(inputsChunked, (i) => i.length).join(',')}] ${gasLimitOverride
? `with a gas limit override of ${gasLimitOverride}`
: ''} and block number: ${await providerConfig.blockNumber} [Original before offset: ${originalBlockNumber}].`);
let haveRetriedForSuccessRate = false;
let haveRetriedForBlockHeader = false;
let blockHeaderRetryAttemptNumber = 0;
let haveIncrementedBlockHeaderFailureCounter = false;
let blockHeaderRolledBack = false;
let haveRetriedForBlockConflictError = false;
let haveRetriedForOutOfGas = false;
let haveRetriedForTimeout = false;
let haveRetriedForUnknownReason = false;
let finalAttemptNumber = 1;
const expectedCallsMade = quoteStates.length;
let totalCallsMade = 0;
const { results: quoteResults, blockNumber, approxGasUsedPerSuccessCall, } = await (0, async_retry_1.default)(async (_bail, attemptNumber) => {
haveIncrementedBlockHeaderFailureCounter = false;
finalAttemptNumber = attemptNumber;
const [success, failed, pending] = this.partitionQuotes(quoteStates);
log_1.log.info(`Starting attempt: ${attemptNumber}.
Currently ${success.length} success, ${failed.length} failed, ${pending.length} pending.
Gas limit override: ${gasLimitOverride} Block number override: ${providerConfig.blockNumber}.`);
quoteStates = await Promise.all(lodash_1.default.map(quoteStates, async (quoteState, idx) => {
if (quoteState.status == 'success') {
return quoteState;
}
// QuoteChunk is pending or failed, so we try again
const { inputs } = quoteState;
try {
totalCallsMade = totalCallsMade + 1;
const quoterAddress = this.getQuoterAddress(useMixedRouteQuoter, useUniV3RouteQuoter);
const results = await this.multicall2Provider.callSameFunctionOnContractWithMultipleParams({
address: quoterAddress,
contractInterface: useMixedRouteQuoter
? IMixedRouteQuoterV1__factory_1.IMixedRouteQuoterV1__factory.createInterface()
: IQuoterV2__factory_1.IQuoterV2__factory.createInterface(),
functionName,
functionParams: inputs,
providerConfig,
additionalConfig: {
gasLimitPerCallOverride: gasLimitOverride,
},
});
const successRateError = this.validateSuccessRate(results.results, haveRetriedForSuccessRate);
if (successRateError) {
return {
status: 'failed',
inputs,
reason: successRateError,
results,
};
}
return {
status: 'success',
inputs,
results,
};
}
catch (err) {
// Error from providers have huge messages that include all the calldata and fill the logs.
// Catch them and rethrow with shorter message.
if (err.message.includes('header not found')) {
return {
status: 'failed',
inputs,
reason: new ProviderBlockHeaderError(err.message.slice(0, 500)),
};
}
if (err.message.includes('timeout')) {
return {
status: 'failed',
inputs,
reason: new ProviderTimeoutError(`Req ${idx}/${quoteStates.length}. Request had ${inputs.length} inputs. ${err.message.slice(0, 500)}`),
};
}
if (err.message.includes('out of gas')) {
return {
status: 'failed',
inputs,
reason: new ProviderGasError(err.message.slice(0, 500)),
};
}
return {
status: 'failed',
inputs,
reason: new Error(`Unknown error from provider: ${err.message.slice(0, 500)}`),
};
}
}));
const [successfulQuoteStates, failedQuoteStates, pendingQuoteStates] = this.partitionQuotes(quoteStates);
if (pendingQuoteStates.length > 0) {
throw new Error('Pending quote after waiting for all promises.');
}
let retryAll = false;
const blockNumberError = this.validateBlockNumbers(successfulQuoteStates, inputsChunked.length, gasLimitOverride);
// If there is a block number conflict we retry all the quotes.
if (blockNumberError) {
retryAll = true;
}
const reasonForFailureStr = lodash_1.default.map(failedQuoteStates, (failedQuoteState) => failedQuoteState.reason.name).join(', ');
if (failedQuoteStates.length > 0) {
log_1.log.info(`On attempt ${attemptNumber}: ${failedQuoteStates.length}/${quoteStates.length} quotes failed. Reasons: ${reasonForFailureStr}`);
for (const failedQuoteState of failedQuoteStates) {
const { reason: error } = failedQuoteState;
log_1.log.info({ error }, `[QuoteFetchError] Attempt ${attemptNumber}. ${error.message}`);
if (error instanceof BlockConflictError) {
if (!haveRetriedForBlockConflictError) {
util_1.metric.putMetric('QuoteBlockConflictErrorRetry', 1, util_1.MetricLoggerUnit.Count);
haveRetriedForBlockConflictError = true;
}
retryAll = true;
}
else if (error instanceof ProviderBlockHeaderError) {
if (!haveRetriedForBlockHeader) {
util_1.metric.putMetric('QuoteBlockHeaderNotFoundRetry', 1, util_1.MetricLoggerUnit.Count);
haveRetriedForBlockHeader = true;
}
// Ensure that if multiple calls fail due to block header in the current pending batch,
// we only count once.
if (!haveIncrementedBlockHeaderFailureCounter) {
blockHeaderRetryAttemptNumber =
blockHeaderRetryAttemptNumber + 1;
haveIncrementedBlockHeaderFailureCounter = true;
}
if (rollback.enabled) {
const { rollbackBlockOffset, attemptsBeforeRollback } = rollback;
if (blockHeaderRetryAttemptNumber >= attemptsBeforeRollback &&
!blockHeaderRolledBack) {
log_1.log.info(`Attempt ${attemptNumber}. Have failed due to block header ${blockHeaderRetryAttemptNumber - 1} times. Rolling back block number by ${rollbackBlockOffset} for next retry`);
providerConfig.blockNumber = providerConfig.blockNumber
? (await providerConfig.blockNumber) + rollbackBlockOffset
: (await this.provider.getBlockNumber()) +
rollbackBlockOffset;
retryAll = true;
blockHeaderRolledBack = true;
}
}
}
else if (error instanceof ProviderTimeoutError) {
if (!haveRetriedForTimeout) {
util_1.metric.putMetric('QuoteTimeoutRetry', 1, util_1.MetricLoggerUnit.Count);
haveRetriedForTimeout = true;
}
}
else if (error instanceof ProviderGasError) {
if (!haveRetriedForOutOfGas) {
util_1.metric.putMetric('QuoteOutOfGasExceptionRetry', 1, util_1.MetricLoggerUnit.Count);
haveRetriedForOutOfGas = true;
}
gasLimitOverride = this.gasErrorFailureOverride.gasLimitOverride;
multicallChunk = this.gasErrorFailureOverride.multicallChunk;
retryAll = true;
}
else if (error instanceof SuccessRateError) {
if (!haveRetriedForSuccessRate) {
util_1.metric.putMetric('QuoteSuccessRateRetry', 1, util_1.MetricLoggerUnit.Count);
haveRetriedForSuccessRate = true;
// Low success rate can indicate too little gas given to each call.
gasLimitOverride =
this.successRateFailureOverrides.gasLimitOverride;
multicallChunk =
this.successRateFailureOverrides.multicallChunk;
retryAll = true;
}
}
else {
if (!haveRetriedForUnknownReason) {
util_1.metric.putMetric('QuoteUnknownReasonRetry', 1, util_1.MetricLoggerUnit.Count);
haveRetriedForUnknownReason = true;
}
}
}
}
if (retryAll) {
log_1.log.info(`Attempt ${attemptNumber}. Resetting all requests to pending for next attempt.`);
const normalizedChunk = Math.ceil(inputs.length / Math.ceil(inputs.length / multicallChunk));
const inputsChunked = lodash_1.default.chunk(inputs, normalizedChunk);
quoteStates = lodash_1.default.map(inputsChunked, (inputChunk) => {
return {
status: 'pending',
inputs: inputChunk,
};
});
}
if (failedQuoteStates.length > 0) {
// TODO: Work with Arbitrum to find a solution for making large multicalls with gas limits that always
// successfully.
//
// On Arbitrum we can not set a gas limit for every call in the multicall and guarantee that
// we will not run out of gas on the node. This is because they have a different way of accounting
// for gas, that seperates storage and compute gas costs, and we can not cover both in a single limit.
//
// To work around this and avoid throwing errors when really we just couldn't get a quote, we catch this
// case and return 0 quotes found.
throw new Error(`Failed to get ${failedQuoteStates.length} quotes. Reasons: ${reasonForFailureStr}`);
}
const callResults = lodash_1.default.map(successfulQuoteStates, (quoteState) => quoteState.results);
return {
results: lodash_1.default.flatMap(callResults, (result) => result.results),
blockNumber: bignumber_1.BigNumber.from(callResults[0].blockNumber),
approxGasUsedPerSuccessCall: stats_lite_1.default.percentile(lodash_1.default.map(callResults, (result) => result.approxGasUsedPerSuccessCall), 100),
};
}, Object.assign({ retries: DEFAULT_BATCH_RETRIES }, this.retryOptions));
const routesQuotes = this.processQuoteResults(quoteResults, routes, amounts);
util_1.metric.putMetric('QuoteApproxGasUsedPerSuccessfulCall', approxGasUsedPerSuccessCall, util_1.MetricLoggerUnit.Count);
util_1.metric.putMetric('QuoteNumRetryLoops', finalAttemptNumber - 1, util_1.MetricLoggerUnit.Count);
util_1.metric.putMetric('QuoteTotalCallsToProvider', totalCallsMade, util_1.MetricLoggerUnit.Count);
util_1.metric.putMetric('QuoteExpectedCallsToProvider', expectedCallsMade, util_1.MetricLoggerUnit.Count);
util_1.metric.putMetric('QuoteNumRetriedCalls', totalCallsMade - expectedCallsMade, util_1.MetricLoggerUnit.Count);
const [successfulQuotes, failedQuotes] = (0, lodash_1.default)(routesQuotes)
.flatMap((routeWithQuotes) => routeWithQuotes[1])
.partition((quote) => quote.quote != null)
.value();
log_1.log.info(`Got ${successfulQuotes.length} successful quotes, ${failedQuotes.length} failed quotes. Took ${finalAttemptNumber - 1} attempt loops. Total calls made to provider: ${totalCallsMade}. Have retried for timeout: ${haveRetriedForTimeout}`);
return { routesWithQuotes: routesQuotes, blockNumber };
}
partitionQuotes(quoteStates) {
const successfulQuoteStates = lodash_1.default.filter(quoteStates, (quoteState) => quoteState.status == 'success');
const failedQuoteStates = lodash_1.default.filter(quoteStates, (quoteState) => quoteState.status == 'failed');
const pendingQuoteStates = lodash_1.default.filter(quoteStates, (quoteState) => quoteState.status == 'pending');
return [successfulQuoteStates, failedQuoteStates, pendingQuoteStates];
}
processQuoteResults(quoteResults, routes, amounts) {
const routesQuotes = [];
const quotesResultsByRoute = lodash_1.default.chunk(quoteResults, amounts.length);
const debugFailedQuotes = [];
for (let i = 0; i < quotesResultsByRoute.length; i++) {
const route = routes[i];
const quoteResults = quotesResultsByRoute[i];
const quotes = lodash_1.default.map(quoteResults, (quoteResult, index) => {
const amount = amounts[index];
if (!quoteResult.success) {
const percent = (100 / amounts.length) * (index + 1);
const amountStr = amount.toFixed(Math.min(amount.currency.decimals, 2));
const routeStr = (0, routes_1.routeToString)(route);
debugFailedQuotes.push({
route: routeStr,
percent,
amount: amountStr,
});
return {
amount,
quote: null,
sqrtPriceX96AfterList: null,
gasEstimate: null,
initializedTicksCrossedList: null,
};
}
return {
amount,
quote: quoteResult.result[0],
sqrtPriceX96AfterList: quoteResult.result[1],
initializedTicksCrossedList: quoteResult.result[2],
gasEstimate: quoteResult.result[3],
};
});
routesQuotes.push([route, quotes]);
}
// For routes and amounts that we failed to get a quote for, group them by route
// and batch them together before logging to minimize number of logs.
const debugChunk = 80;
lodash_1.default.forEach(lodash_1.default.chunk(debugFailedQuotes, debugChunk), (quotes, idx) => {
const failedQuotesByRoute = lodash_1.default.groupBy(quotes, (q) => q.route);
const failedFlat = lodash_1.default.mapValues(failedQuotesByRoute, (f) => (0, lodash_1.default)(f)
.map((f) => `${f.percent}%[${f.amount}]`)
.join(','));
log_1.log.info({
failedQuotes: lodash_1.default.map(failedFlat, (amounts, routeStr) => `${routeStr} : ${amounts}`),
}, `Failed on chain quotes for routes Part ${idx}/${Math.ceil(debugFailedQuotes.length / debugChunk)}`);
});
return routesQuotes;
}
validateBlockNumbers(successfulQuoteStates, totalCalls, gasLimitOverride) {
if (successfulQuoteStates.length <= 1) {
return null;
}
const results = lodash_1.default.map(successfulQuoteStates, (quoteState) => quoteState.results);
const blockNumbers = lodash_1.default.map(results, (result) => result.blockNumber);
const uniqBlocks = (0, lodash_1.default)(blockNumbers)
.map((blockNumber) => blockNumber.toNumber())
.uniq()
.value();
if (uniqBlocks.length == 1) {
return null;
}
/* if (
uniqBlocks.length == 2 &&
Math.abs(uniqBlocks[0]! - uniqBlocks[1]!) <= 1
) {
return null;
} */
return new BlockConflictError(`Quotes returned from different blocks. ${uniqBlocks}. ${totalCalls} calls were made with gas limit ${gasLimitOverride}`);
}
validateSuccessRate(allResults, haveRetriedForSuccessRate) {
const numResults = allResults.length;
const numSuccessResults = allResults.filter((result) => result.success).length;
const successRate = (1.0 * numSuccessResults) / numResults;
const { quoteMinSuccessRate } = this.batchParams;
if (successRate < quoteMinSuccessRate) {
if (haveRetriedForSuccessRate) {
log_1.log.info(`Quote success rate still below threshold despite retry. Continuing. ${quoteMinSuccessRate}: ${successRate}`);
return;
}
return new SuccessRateError(`Quote success rate below threshold of ${quoteMinSuccessRate}: ${successRate}`);
}
}
/**
* Throw an error for incorrect routes / function combinations
* @param routes Any combination of V3, V2, and Mixed routes.
* @param functionName
* @param useMixedRouteQuoter true if there are ANY V2Routes or MixedRoutes in the routes parameter
*/
validateRoutes(routes, functionName, useMixedRouteQuoter) {
/// We do not send any V3Routes to new qutoer becuase it is not deployed on chains besides mainnet
if (routes.some((route) => (0, util_1.isV3)(route.protocol)) && useMixedRouteQuoter) {
throw new Error(`Cannot use mixed route quoter with V3 routes`);
}
/// We cannot call quoteExactOutput with V2 or Mixed routes
if (functionName === 'quoteExactOutput' && useMixedRouteQuoter) {
throw new Error('Cannot call quoteExactOutput with V2 or Mixed routes');
}
}
}
exports.OnChainQuoteProvider = OnChainQuoteProvider;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib24tY2hhaW4tcXVvdGUtcHJvdmlkZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcHJvdmlkZXJzL29uLWNoYWluLXF1b3RlLXByb3ZpZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUFBLHdEQUFxRDtBQUVyRCw0REFJcUM7QUFDckMsd0RBR21DO0FBQ25DLG9EQUFrRTtBQUNsRSw4REFBNkQ7QUFDN0Qsb0RBQXVCO0FBQ3ZCLDREQUErQjtBQUUvQiw4Q0FBaUU7QUFDakUsd0dBQXFHO0FBQ3JHLGlGQUE4RTtBQUM5RSxrQ0FBeUQ7QUFDekQsaURBQXdEO0FBRXhELHFDQUFrQztBQUNsQywyQ0FBK0M7QUErQi9DLE1BQWEsa0JBQW1CLFNBQVEsS0FBSztJQUE3Qzs7UUFDUyxTQUFJLEdBQUcsb0JBQW9CLENBQUM7SUFDckMsQ0FBQztDQUFBO0FBRkQsZ0RBRUM7QUFDRCxNQUFhLGdCQUFpQixTQUFRLEtBQUs7SUFBM0M7O1FBQ1MsU0FBSSxHQUFHLGtCQUFrQixDQUFDO0lBQ25DLENBQUM7Q0FBQTtBQUZELDRDQUVDO0FBRUQsTUFBYSx3QkFBeUIsU0FBUSxLQUFLO0lBQW5EOztRQUNTLFNBQUksR0FBRywwQkFBMEIsQ0FBQztJQUMzQyxDQUFDO0NBQUE7QUFGRCw0REFFQztBQUVELE1BQWEsb0JBQXFCLFNBQVEsS0FBSztJQUEvQzs7UUFDUyxTQUFJLEdBQUcsc0JBQXNCLENBQUM7SUFDdkMsQ0FBQztDQUFBO0FBRkQsb0RBRUM7QUFFRDs7Ozs7Ozs7O0dBU0c7QUFDSCxNQUFhLGdCQUFpQixTQUFRLEtBQUs7SUFBM0M7O1FBQ1MsU0FBSSxHQUFHLGtCQUFrQixDQUFDO0lBQ25DLENBQUM7Q0FBQTtBQUZELDRDQUVDO0FBaUpELE1BQU0scUJBQXFCLEdBQUcsQ0FBQyxDQUFDO0FBRWhDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBc0JHO0FBQ0gsTUFBYSxvQkFBb0I7SUFDL0I7Ozs7Ozs7Ozs7Ozs7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