myria-core-sdk
Version:
Latest version SDK
448 lines • 41 kB
JavaScript
import { MyriaClient } from "../";
import { ContractFactory } from "../core/ContractFactory";
import { ConfirmationType, TokenType } from "../types/CommonTypes";
import { DepositAPI, CommonAPI } from "../core/apis";
import { convertEthToWei } from "../utils/Converter";
import BN from "bn.js";
import DepositABI from '../contracts/Deposits.json';
import { validationChainNetwork } from "../utils/ValidationUtils";
import { DELAY_IN_RETRY, retryPromise, RETRY_DEFAULT } from "../utils/RetryUtils";
import { UserManager } from "./UserManager";
import { WithdrawalModule } from "./WithdrawalModule";
import Web3 from "web3";
import { CommonModule } from "./CommonModule";
const StarkwareLib = require("@starkware-industries/starkware-crypto-utils");
const assetUtils = StarkwareLib.asset;
const DEFAULT_QUANTUM = "10000000000";
export class DepositModule {
constructor(client) {
this.client = new MyriaClient(client);
if (!validationChainNetwork(this.client)) {
throw Error('Network has been wrong and mismatch with the environment');
}
this.commonModule = new CommonModule(client);
this.depositContract = this.getDepositContract(this.client);
this.erc20Contract = this.getERC20Contract(this.client);
this.depositAPI = new DepositAPI(this.client.env);
this.commonAPI = new CommonAPI(this.client.env);
this.userManager = new UserManager(client);
this.withdrawModule = new WithdrawalModule(client);
}
getDepositContract(client) {
const contractFactory = new ContractFactory(client);
return contractFactory.getDepositContract();
}
getCustomDepositContract(customERC20Network) {
const contractFactory = new ContractFactory(this.client);
return contractFactory.getCustomDepositContract(customERC20Network);
}
getERC20Contract(client) {
const contractFactory = new ContractFactory(client);
return contractFactory.getERC20Contract();
}
getCustomERC20Contract(customERC20Network) {
const contractFactory = new ContractFactory(this.client);
return contractFactory.getCustomERC20Contract(customERC20Network);
}
async depositEthereum(depositParams, options) {
var _a;
if (depositParams.tokenType !== TokenType.ETH) {
throw new Error("Token type is invalid, only allow to execute the operation with Ethereum");
}
if (!depositParams.starkKey) {
throw new Error("Stark key is required to do this operation");
}
if (!depositParams.amount || depositParams.amount === "0") {
throw new Error("Amount must be greater than 0 to do this operation");
}
if (!(options === null || options === void 0 ? void 0 : options.from)) {
throw new Error('Required wallet address to operate');
}
const quantumEth = "10000000000";
console.time('Get_asset_type');
const assetType = assetUtils.getAssetType({
type: depositParams.tokenType,
data: {
quantum: quantumEth,
},
});
console.timeEnd('Get_asset_type');
let vaultIdData;
console.time('retrieveVault');
if (!depositParams.vaultId) {
try {
const vaultResponse = await this.commonAPI.retrieveVault({
starkKey: depositParams.starkKey,
tokenType: depositParams.tokenType,
quantum: quantumEth,
tokenAddress: (depositParams === null || depositParams === void 0 ? void 0 : depositParams.tokenAddress) || undefined,
});
if ((vaultResponse === null || vaultResponse === void 0 ? void 0 : vaultResponse.status) === "success") {
vaultIdData = (_a = vaultResponse === null || vaultResponse === void 0 ? void 0 : vaultResponse.data) === null || _a === void 0 ? void 0 : _a.vaultId;
}
else {
throw new Error("Fetching vaultID failure - check BE server or validation request for calling");
}
}
catch (ex) {
throw new Error("Fetching vaultID failure");
}
}
else {
vaultIdData = depositParams.vaultId;
}
console.timeEnd('retrieveVault');
// Make on-chain deposit request
console.time('Deposit_onchain');
let onchainDepositResult;
try {
onchainDepositResult = await this.depositContract.deposit(depositParams.starkKey, assetType, vaultIdData, options);
console.log(`Onchain deposit result ==> ${JSON.stringify(onchainDepositResult)}`);
}
catch (ex) {
throw new Error(`Onchain deposit has been failure... ${JSON.stringify(ex)}`);
}
console.timeEnd('Deposit_onchain');
if (!(onchainDepositResult === null || onchainDepositResult === void 0 ? void 0 : onchainDepositResult.transactionHash) ||
(onchainDepositResult === null || onchainDepositResult === void 0 ? void 0 : onchainDepositResult.transactionHash) === undefined) {
throw new Error("Onchain deposit has been failure - transaction hash must be returned from onchain..");
}
const convertAmount = convertEthToWei(depositParams.amount);
console.time('getSignatureDepositEthOffchain');
const depositSignParams = {
ethAddress: options === null || options === void 0 ? void 0 : options.from,
vaultId: String(vaultIdData),
txHash: onchainDepositResult === null || onchainDepositResult === void 0 ? void 0 : onchainDepositResult.transactionHash,
amount: convertAmount
};
const depositSignature = await this.commonModule.generateStarkSignatureForDeposit(depositSignParams);
if (!depositSignature) {
throw new Error("Failed to generate deposit signature");
}
console.timeEnd('getSignatureDepositEthOffchain');
const depositEthRequestPayload = {
amount: convertAmount,
starkKey: depositParams.starkKey,
transactionHash: onchainDepositResult.transactionHash,
vaultId: vaultIdData,
isNetworkTimeout: onchainDepositResult.isNetworkTimeout || false,
signature: depositSignature
};
console.time('Deposit_offchain');
try {
const depositResponse = await retryPromise(this.depositAPI.offchainDepositEth(depositEthRequestPayload), RETRY_DEFAULT, DELAY_IN_RETRY);
console.timeEnd('Deposit_offchain');
if ((depositResponse === null || depositResponse === void 0 ? void 0 : depositResponse.status) !== "success") {
console.log('[Core-SDK] Deposit offchain failure');
throw new Error("Off-chain deposit failed!");
}
}
catch (err) {
console.log('[Core-SDK] Deposit offchain failure with exception', err);
throw new Error("Off-chain deposit failed!");
}
return onchainDepositResult;
}
async depositEth(depositParams, options) {
return this.depositEthereum(depositParams, options);
}
async depositCancel(depositParams, options) {
if (!depositParams.starkKey) {
throw new Error('Stark Key Is Required');
}
if (!depositParams.assetId) {
throw new Error("Asset ID is required");
}
if (!depositParams.vaultId) {
throw new Error("Vault ID is required");
}
if (!depositParams.transactionId) {
throw new Error("Transaction id is required");
}
// Register on-chain
let userHasRegistered = false;
const responseUserOnchain = await this.withdrawModule.checkUserRegisterOnchain(depositParams.starkKey);
if (!responseUserOnchain ||
String(responseUserOnchain).toLowerCase() !== depositParams.ethAddress.toLowerCase() ||
Web3.utils.toBN(String(responseUserOnchain)).isZero()) {
const registerOnchainResult = await this.userManager.registerUserOnChain(depositParams.ethAddress, options);
if (registerOnchainResult && registerOnchainResult.transactionHash) {
await this.userManager.patchRegisterUserOnchain(depositParams.ethAddress, registerOnchainResult.transactionHash);
userHasRegistered = true;
console.log('Register Onchain Result -> ', registerOnchainResult);
}
}
else {
userHasRegistered = true;
}
if (!userHasRegistered) {
throw new Error('User has not registered on-chain successfully yet, required register on-chain before doing this operation');
}
// Call deposit cancel in the onchain
let depositCancelResponse = undefined;
try {
depositCancelResponse = await this.depositContract.depositCancel(depositParams.starkKey, depositParams.assetId, depositParams.vaultId, {
...options,
gas: 100000
});
}
catch (ex) {
console.log('Deposit cancellation error -> ', JSON.stringify(ex));
throw new Error('Deposit cancellation error -> ' + JSON.stringify(ex));
}
if (!depositCancelResponse.transactionHash) {
throw new Error('Transaction hash for depositCancel is required');
}
// Submit deposit cancel to system
let depositCancelOffchain = undefined;
const depositParamsAPI = {
...depositParams,
transactionHash: depositCancelResponse.transactionHash || ''
};
try {
depositCancelOffchain = await this.depositAPI.depositCancel(depositParamsAPI);
if (depositCancelOffchain.status != 'success') {
throw new Error('Deposit cancel offchain has been failed with internal server error');
}
}
catch (ex) {
console.log('Deposit cancellation error -> ', JSON.stringify(ex));
throw new Error('Deposit cancellation error -> ' + JSON.stringify(ex));
}
return depositCancelResponse;
}
async depositReclaim(depositParams, options) {
if (!depositParams.starkKey) {
throw new Error('Stark Key Is Required');
}
if (!depositParams.assetId) {
throw new Error("Asset ID is required");
}
if (!depositParams.vaultId) {
throw new Error("Vault ID is required");
}
if (!depositParams.transactionId) {
throw new Error("Transaction id is required");
}
// Call deposit cancel in the onchain
const depositReclaimResponse = await this.depositContract.depositReclaim(depositParams.starkKey, depositParams.assetId, depositParams.vaultId, options);
if (!depositReclaimResponse || !depositReclaimResponse.transactionHash) {
throw new Error('Deposit reclaimed have been failed');
}
// Call deposit cancel complete endpoints
const depositReclaimParams = {
starkKey: depositParams.starkKey,
vaultId: depositParams.vaultId,
assetId: depositParams.assetId,
reclaimTransactionHash: depositReclaimResponse.transactionHash,
transactionId: depositParams.transactionId
};
try {
await this.depositAPI.depositReclaim(depositReclaimParams);
}
catch (ex) {
console.log('Patch reclaim to system error -> ', ex);
throw new Error('Patch reclaim to system error -> ' + ex);
}
return depositReclaimResponse;
}
async depositERC20(params, options) {
if (!params.starkKey) {
throw new Error('Stark key is required!');
}
if (!params.ethAddress) {
throw new Error('Connected wallet address is required!');
}
if (!params.amount) {
throw new Error('Amount is required!');
}
if (!params.contractAddress) {
throw new Error('Token address is required!');
}
if (!params.quantum) {
params.quantum = DEFAULT_QUANTUM;
}
if (!options) {
options = {
confirmationType: ConfirmationType.Confirmed
};
}
const networkId = String(this.client.networkId);
const spenderAddress = params.spenderTokenAddress ? params.spenderTokenAddress : DepositABI.networks[networkId].address;
const approvalOfSpender = await this.erc20Contract.allowance(params.ethAddress, spenderAddress, options);
console.log('Approval of spender -> ', approvalOfSpender);
if (Number(approvalOfSpender) < Number(params.amount)) {
console.time('Approve_erc20');
try {
await this.erc20Contract.approve(spenderAddress, params.amount, {
from: params.ethAddress,
confirmationType: ConfirmationType.Confirmed,
nonce: options.nonce
});
}
catch (err) {
throw new Error(`Approve Error: ${err}`);
}
console.timeEnd('Approve_erc20');
}
console.timeEnd('Approve_erc20');
let vaultId = params.vaultId;
if (!options.confirmationType) {
options = { ...options, confirmationType: ConfirmationType.Confirmed };
}
console.time('Get_vaults');
if (!params.vaultId) {
try {
const vaultRequest = {
quantum: params.quantum,
starkKey: params.starkKey,
tokenAddress: params.contractAddress
};
const retrieveResult = await this.commonAPI.retrieveERC20Vault(vaultRequest);
if (retrieveResult.status === "success") {
vaultId = retrieveResult.data.vaultId;
}
else {
throw new Error('Retrieve vault failed!');
}
}
catch (err) {
throw new Error(`Retriving vault error!: ${err}`);
}
}
console.timeEnd('Get_vaults');
const assetType = assetUtils.getAssetType({
type: TokenType.ERC20,
data: {
quantum: params.quantum,
tokenAddress: params.contractAddress,
},
});
const quantizedAmount = new BN(params.amount, 10).div(new BN(params.quantum, 10)).toString();
let onchainDepositResult;
console.time('depositOnchain');
try {
onchainDepositResult = await this.depositContract.depositERC20(params.starkKey, assetType, String(vaultId), quantizedAmount, options);
}
catch (ex) {
throw new Error(`Onchain deposit has been failure... ${JSON.stringify(ex)}`);
}
console.timeEnd('depositOnchain');
if (!(onchainDepositResult === null || onchainDepositResult === void 0 ? void 0 : onchainDepositResult.transactionHash) ||
(onchainDepositResult === null || onchainDepositResult === void 0 ? void 0 : onchainDepositResult.transactionHash) === undefined) {
throw new Error("Onchain deposit has been failure - transaction hash must be returned from onchain..");
}
console.time('getSignatureDepositOffchain');
const depositSignParams = {
ethAddress: params.ethAddress,
vaultId: String(vaultId),
txHash: onchainDepositResult === null || onchainDepositResult === void 0 ? void 0 : onchainDepositResult.transactionHash,
amount: params.amount
};
const depositSignature = await this.commonModule.generateStarkSignatureForDeposit(depositSignParams);
if (!depositSignature) {
throw new Error("Failed to generate deposit signature");
}
console.timeEnd('getSignatureDepositOffchain');
console.time('depositOffchain');
const depositERC20Request = {
...params,
vaultId,
transactionHash: onchainDepositResult.transactionHash,
isNetworkTimeout: onchainDepositResult.isNetworkTimeout || false,
signature: depositSignature
};
try {
const depositResult = await retryPromise(this.depositAPI.offchainDepositERC20(depositERC20Request), RETRY_DEFAULT, DELAY_IN_RETRY);
console.timeEnd('depositOffchain');
return depositResult;
}
catch (err) {
throw new Error(`Deposit ERC20 for off-chain failed: ${err}`);
}
}
/**
* Function will be DEPRECATED due to the off-chain events
* will be in place at core-service
* @param vaultIdData
* @param starkKey
* @param quantizedAmount
* @param assetType
* @param transactionHash
*
*/
async depositOffchain(vaultIdData, starkKey, quantizedAmount, assetType, transactionHash) {
// Make off-chain deposit request
let offchainResult;
try {
offchainResult = await this.depositAPI.makeDepositTransaction(vaultIdData, starkKey, quantizedAmount, assetType, transactionHash);
if ((offchainResult === null || offchainResult === void 0 ? void 0 : offchainResult.status) === "failed") {
throw new Error("Onchain deposit transaction has been failed");
}
console.log(`Offchain deposit result ==> ${JSON.stringify(offchainResult)}`);
}
catch (ex) {
throw new Error(`Onchain deposit has been failure... ${JSON.stringify(ex)}`);
}
return offchainResult;
}
async depositNFT(depositParams, options) {
var _a;
if ((depositParams === null || depositParams === void 0 ? void 0 : depositParams.tokenType) === TokenType.ERC20 ||
(depositParams === null || depositParams === void 0 ? void 0 : depositParams.tokenType) === TokenType.MINTABLE_ERC20 ||
(depositParams === null || depositParams === void 0 ? void 0 : depositParams.tokenType) === TokenType.ETH) {
throw new Error("Token type is invalided and only support for ERC721/MINTABLE_ERC721 token");
}
if (!depositParams.starkKey) {
throw new Error("Stark key is required to execute this operation");
}
if (!depositParams.tokenId) {
throw new Error("Token Id is required to execute this operation");
}
if (!depositParams.tokenAddress) {
throw new Error("ERC721 token address is required to execute this operation");
}
const quantumNft = "1";
let vaultData = "";
const assetType = assetUtils.getAssetType({
type: depositParams.tokenType,
data: {
quantum: quantumNft,
tokenAddress: depositParams.tokenAddress,
},
});
if (!(depositParams === null || depositParams === void 0 ? void 0 : depositParams.vaultId)) {
try {
const vaultResponse = await this.commonAPI.retrieveVault({
starkKey: depositParams.starkKey,
tokenType: depositParams.tokenType,
quantum: "1",
tokenAddress: depositParams.tokenAddress,
});
if ((vaultResponse === null || vaultResponse === void 0 ? void 0 : vaultResponse.status) === "success") {
vaultData = (_a = vaultResponse === null || vaultResponse === void 0 ? void 0 : vaultResponse.data) === null || _a === void 0 ? void 0 : _a.vaultId;
}
else {
throw new Error("Fetching vaultID failure - check BE server or validation request for calling");
}
}
catch (ex) {
throw new Error("Fetching vaultID failure");
}
}
else {
vaultData = depositParams === null || depositParams === void 0 ? void 0 : depositParams.vaultId;
}
// Make on-chain deposit request
let onchainDepositResult;
try {
onchainDepositResult = await this.depositContract.depositNft(depositParams === null || depositParams === void 0 ? void 0 : depositParams.starkKey, assetType, depositParams === null || depositParams === void 0 ? void 0 : depositParams.tokenId, vaultData, options);
console.log(`Onchain deposit nft result ==> ${JSON.stringify(onchainDepositResult)}`);
}
catch (ex) {
throw new Error(`Onchain deposit nft has been failure... ${JSON.stringify(ex)}`);
}
return onchainDepositResult;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRGVwb3NpdE1vZHVsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL0RlcG9zaXRNb2R1bGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFnQixXQUFXLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFFaEQsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzFELE9BQU8sRUFBRSxnQkFBZ0IsRUFBa0MsU0FBUyxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDbkcsT0FBTyxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFlckQsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRXJELE9BQU8sRUFBRSxNQUFNLE9BQU8sQ0FBQztBQUV2QixPQUFPLFVBQVUsTUFBTSw0QkFBNEIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUNsRSxPQUFPLEVBQUUsY0FBYyxFQUFFLFlBQVksRUFBRSxhQUFhLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUNsRixPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQzVDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ3RELE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUN4QixPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFOUMsTUFBTSxZQUFZLEdBQUcsT0FBTyxDQUFDLDhDQUE4QyxDQUFDLENBQUM7QUFDN0UsTUFBTSxVQUFVLEdBQUcsWUFBWSxDQUFDLEtBQUssQ0FBQztBQUV0QyxNQUFNLGVBQWUsR0FBRyxhQUFhLENBQUM7QUFFdEMsTUFBTSxPQUFPLGFBQWE7SUFVeEIsWUFBWSxNQUFvQjtRQUM5QixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksV0FBVyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3RDLElBQUksQ0FBQyxzQkFBc0IsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUU7WUFDeEMsTUFBTSxLQUFLLENBQUMsMERBQTBELENBQUMsQ0FBQztTQUN6RTtRQUVELElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDN0MsSUFBSSxDQUFDLGVBQWUsR0FBRyxJQUFJLENBQUMsa0JBQWtCLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzVELElBQUksQ0FBQyxhQUFhLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUN4RCxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksVUFBVSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDbEQsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLFNBQVMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ2hELElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxXQUFXLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDM0MsSUFBSSxDQUFDLGNBQWMsR0FBRyxJQUFJLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBRXJELENBQUM7SUFFTSxrQkFBa0IsQ0FBQyxNQUFtQjtRQUMzQyxNQUFNLGVBQWUsR0FBRyxJQUFJLGVBQWUsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNwRCxPQUFPLGVBQWUsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO0lBQzlDLENBQUM7SUFFTSx3QkFBd0IsQ0FBQyxrQkFBdUM7UUFDckUsTUFBTSxlQUFlLEdBQUcsSUFBSSxlQUFlLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3pELE9BQU8sZUFBZSxDQUFDLHdCQUF3QixDQUFDLGtCQUFrQixDQUFDLENBQUM7SUFDdEUsQ0FBQztJQUVNLGdCQUFnQixDQUFDLE1BQW1CO1FBQ3pDLE1BQU0sZUFBZSxHQUFHLElBQUksZUFBZSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3BELE9BQU8sZUFBZSxDQUFDLGdCQUFnQixFQUFFLENBQUM7SUFDNUMsQ0FBQztJQUVNLHNCQUFzQixDQUFDLGtCQUF1QztRQUNuRSxNQUFNLGVBQWUsR0FBRyxJQUFJLGVBQWUsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDekQsT0FBTyxlQUFlLENBQUMsc0JBQXNCLENBQUMsa0JBQWtCLENBQUMsQ0FBQztJQUNwRSxDQUFDO0lBRU8sS0FBSyxDQUFDLGVBQWUsQ0FDM0IsYUFBa0MsRUFDbEMsT0FBcUI7O1FBRXJCLElBQUksYUFBYSxDQUFDLFNBQVMsS0FBSyxTQUFTLENBQUMsR0FBRyxFQUFFO1lBQzdDLE1BQU0sSUFBSSxLQUFLLENBQ2IsMEVBQTBFLENBQzNFLENBQUM7U0FDSDtRQUVELElBQUksQ0FBQyxhQUFhLENBQUMsUUFBUSxFQUFFO1lBQzNCLE1BQU0sSUFBSSxLQUFLLENBQUMsNENBQTRDLENBQUMsQ0FBQztTQUMvRDtRQUVELElBQUksQ0FBQyxhQUFhLENBQUMsTUFBTSxJQUFJLGFBQWEsQ0FBQyxNQUFNLEtBQUssR0FBRyxFQUFFO1lBQ3pELE1BQU0sSUFBSSxLQUFLLENBQUMsb0RBQW9ELENBQUMsQ0FBQztTQUN2RTtRQUVELElBQUksQ0FBQyxDQUFBLE9BQU8sYUFBUCxPQUFPLHVCQUFQLE9BQU8sQ0FBRSxJQUFJLENBQUEsRUFBRTtZQUNsQixNQUFNLElBQUksS0FBSyxDQUFDLG9DQUFvQyxDQUFDLENBQUM7U0FDdkQ7UUFFRCxNQUFNLFVBQVUsR0FBRyxhQUFhLENBQUM7UUFFakMsT0FBTyxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBQy9CLE1BQU0sU0FBUyxHQUFHLFVBQVUsQ0FBQyxZQUFZLENBQUM7WUFDeEMsSUFBSSxFQUFFLGFBQWEsQ0FBQyxTQUFTO1lBQzdCLElBQUksRUFBRTtnQkFDSixPQUFPLEVBQUUsVUFBVTthQUNwQjtTQUNGLENBQUMsQ0FBQztRQUNILE9BQU8sQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztRQUVsQyxJQUFJLFdBQVcsQ0FBQztRQUVoQixPQUFPLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDO1FBQzlCLElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxFQUFFO1lBQzFCLElBQUk7Z0JBQ0YsTUFBTSxhQUFhLEdBQUcsTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLGFBQWEsQ0FBQztvQkFDdkQsUUFBUSxFQUFFLGFBQWEsQ0FBQyxRQUFRO29CQUNoQyxTQUFTLEVBQUUsYUFBYSxDQUFDLFNBQVM7b0JBQ2xDLE9BQU8sRUFBRSxVQUFVO29CQUNuQixZQUFZLEVBQUUsQ0FBQSxhQUFhLGFBQWIsYUFBYSx1QkFBYixhQUFhLENBQUUsWUFBWSxLQUFJLFNBQVM7aUJBQ3ZELENBQUMsQ0FBQztnQkFDSCxJQUFJLENBQUEsYUFBYSxhQUFiLGFBQWEsdUJBQWIsYUFBYSxDQUFFLE1BQU0sTUFBSyxTQUFTLEVBQUU7b0JBQ3ZDLFdBQVcsR0FBRyxNQUFBLGFBQWEsYUFBYixhQUFhLHVCQUFiLGFBQWEsQ0FBRSxJQUFJLDBDQUFFLE9BQU8sQ0FBQztpQkFDNUM7cUJBQU07b0JBQ0wsTUFBTSxJQUFJLEtBQUssQ0FDYiw4RUFBOEUsQ0FDL0UsQ0FBQztpQkFDSDthQUNGO1lBQUMsT0FBTyxFQUFPLEVBQUU7Z0JBQ2hCLE1BQU0sSUFBSSxLQUFLLENBQUMsMEJBQTBCLENBQUMsQ0FBQzthQUM3QztTQUNGO2FBQU07WUFDTCxXQUFXLEdBQUcsYUFBYSxDQUFDLE9BQU8sQ0FBQztTQUNyQztRQUNELE9BQU8sQ0FBQyxPQUFPLENBQUMsZUFBZSxDQUFDLENBQUM7UUFFakMsZ0NBQWdDO1FBRWhDLE9BQU8sQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUNoQyxJQUFJLG9CQUE4QixDQUFDO1FBR25DLElBQUk7WUFDRixvQkFBb0IsR0FBRyxNQUFNLElBQUksQ0FBQyxlQUFlLENBQUMsT0FBTyxDQUN2RCxhQUFhLENBQUMsUUFBUSxFQUN0QixTQUFTLEVBQ1QsV0FBVyxFQUNYLE9BQU8sQ0FDUixDQUFDO1lBRUYsT0FBTyxDQUFDLEdBQUcsQ0FDVCw4QkFBOEIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxvQkFBb0IsQ0FBQyxFQUFFLENBQ3JFLENBQUM7U0FDSDtRQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ1gsTUFBTSxJQUFJLEtBQUssQ0FDYix1Q0FBdUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUM1RCxDQUFDO1NBQ0g7UUFDRCxPQUFPLENBQUMsT0FBTyxDQUFDLGlCQUFpQixDQUFDLENBQUM7UUFFbkMsSUFDRSxDQUFDLENBQUEsb0JBQW9CLGFBQXBCLG9CQUFvQix1QkFBcEIsb0JBQW9CLENBQUUsZUFBZSxDQUFBO1lBQ3RDLENBQUEsb0JBQW9CLGFBQXBCLG9CQUFvQix1QkFBcEIsb0JBQW9CLENBQUUsZUFBZSxNQUFLLFNBQVMsRUFDbkQ7WUFDQSxNQUFNLElBQUksS0FBSyxDQUNiLHFGQUFxRixDQUN0RixDQUFDO1NBQ0g7UUFFRCxNQUFNLGFBQWEsR0FBRyxlQUFlLENBQUMsYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBRTVELE9BQU8sQ0FBQyxJQUFJLENBQUMsZ0NBQWdDLENBQUMsQ0FBQztRQUMvQyxNQUFNLGlCQUFpQixHQUFzQjtZQUMzQyxVQUFVLEVBQUUsT0FBTyxhQUFQLE9BQU8sdUJBQVAsT0FBTyxDQUFFLElBQUk7WUFDekIsT0FBTyxFQUFFLE1BQU0sQ0FBQyxXQUFXLENBQUM7WUFDNUIsTUFBTSxFQUFFLG9CQUFvQixhQUFwQixvQkFBb0IsdUJBQXBCLG9CQUFvQixDQUFFLGVBQWU7WUFDN0MsTUFBTSxFQUFFLGFBQWE7U0FDdEIsQ0FBQztRQUVGLE1BQU0sZ0JBQWdCLEdBQUcsTUFBTSxJQUFJLENBQUMsWUFBWSxDQUFDLGdDQUFnQyxDQUFDLGlCQUFpQixDQUFDLENBQUM7UUFFckcsSUFBSSxDQUFDLGdCQUFnQixFQUFFO1lBQ3JCLE1BQU0sSUFBSSxLQUFLLENBQUMsc0NBQXNDLENBQUMsQ0FBQztTQUN6RDtRQUNELE9BQU8sQ0FBQyxPQUFPLENBQUMsZ0NBQWdDLENBQUMsQ0FBQztRQUVsRCxNQUFNLHdCQUF3QixHQUFvQztZQUNoRSxNQUFNLEVBQUUsYUFBYTtZQUNyQixRQUFRLEVBQUUsYUFBYSxDQUFDLFFBQVE7WUFDaEMsZUFBZSxFQUFFLG9CQUFvQixDQUFDLGVBQWU7WUFDckQsT0FBTyxFQUFFLFdBQVc7WUFDcEIsZ0JBQWdCLEVBQUUsb0JBQW9CLENBQUMsZ0JBQWdCLElBQUksS0FBSztZQUNoRSxTQUFTLEVBQUUsZ0JBQWdCO1NBQzVCLENBQUE7UUFFRCxPQUFPLENBQUMsSUFBSSxDQUFDLGtCQUFrQixDQUFDLENBQUM7UUFDakMsSUFBSTtZQUVGLE1BQU0sZUFBZSxHQUFHLE1BQU0sWUFBWSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsa0JBQWtCLENBQUMsd0JBQXdCLENBQUMsRUFBRSxhQUFhLEVBQUUsY0FBYyxDQUFDLENBQUM7WUFDeEksT0FBTyxDQUFDLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO1lBQ3BDLElBQUksQ0FBQSxlQUFlLGFBQWYsZUFBZSx1QkFBZixlQUFlLENBQUUsTUFBTSxNQUFLLFNBQVMsRUFBRTtnQkFDekMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQ0FBcUMsQ0FBQyxDQUFDO2dCQUNuRCxNQUFNLElBQUksS0FBSyxDQUFDLDJCQUEyQixDQUFDLENBQUM7YUFFOUM7U0FDRjtRQUFDLE9BQU8sR0FBRyxFQUFFO1lBQ1osT0FBTyxDQUFDLEdBQUcsQ0FBQyxvREFBb0QsRUFBRSxHQUFHLENBQUMsQ0FBQztZQUN2RSxNQUFNLElBQUksS0FBSyxDQUFDLDJCQUEyQixDQUFDLENBQUM7U0FDOUM7UUFFRCxPQUFPLG9CQUFvQixDQUFDO0lBQzlCLENBQUM7SUFFTSxLQUFLLENBQUMsVUFBVSxDQUNyQixhQUFrQyxFQUNsQyxPQUFxQjtRQUVyQixPQUFPLElBQUksQ0FBQyxlQUFlLENBQUMsYUFBYSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3RELENBQUM7SUFFTSxLQUFLLENBQUMsYUFBYSxDQUN4QixhQUF3QyxFQUN4QyxPQUFxQjtRQUdyQixJQUFJLENBQUMsYUFBYSxDQUFDLFFBQVEsRUFBRTtZQUMzQixNQUFNLElBQUksS0FBSyxDQUFDLHVCQUF1QixDQUFDLENBQUM7U0FDMUM7UUFFRCxJQUFJLENBQUMsYUFBYSxDQUFDLE9BQU8sRUFBRTtZQUMxQixNQUFNLElBQUksS0FBSyxDQUFDLHNCQUFzQixDQUFDLENBQUM7U0FDekM7UUFFRCxJQUFJLENBQUMsYUFBYSxDQUFDLE9BQU8sRUFBRTtZQUMxQixNQUFNLElBQUksS0FBSyxDQUFDLHNCQUFzQixDQUFDLENBQUM7U0FDekM7UUFFRCxJQUFJLENBQUMsYUFBYSxDQUFDLGFBQWEsRUFBRTtZQUNoQyxNQUFNLElBQUksS0FBSyxDQUFDLDRCQUE0QixDQUFDLENBQUM7U0FDL0M7UUFFRCxvQkFBb0I7UUFDcEIsSUFBSSxpQkFBaUIsR0FBRyxLQUFLLENBQUM7UUFDOUIsTUFBTSxtQkFBbUIsR0FBRyxNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsd0JBQXdCLENBQUMsYUFBYSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBRXZHLElBQUksQ0FBQyxtQkFBbUI7WUFDcEIsTUFBTSxDQUFDLG1CQUFtQixDQUFDLENBQUMsV0FBVyxFQUFFLEtBQUssYUFBYSxDQUFDLFVBQVUsQ0FBQyxXQUFXLEVBQUU7WUFDcEYsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLG1CQUFtQixDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRTtZQUN6RCxNQUFNLHFCQUFxQixHQUFHLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxtQkFBbUIsQ0FBQyxhQUFhLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFDO1lBQzVHLElBQUkscUJBQXFCLElBQUkscUJBQXFCLENBQUMsZUFBZSxFQUFFO2dCQUNsRSxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUMsd0JBQXdCLENBQUMsYUFBYSxDQUFDLFVBQVUsRUFBRSxxQkFBcUIsQ0FBQyxlQUFlLENBQUMsQ0FBQztnQkFDakgsaUJBQWlCLEdBQUcsSUFBSSxDQUFDO2dCQUN6QixPQUFPLENBQUMsR0FBRyxDQUFDLDZCQUE2QixFQUFFLHFCQUFxQixDQUFDLENBQUM7YUFDbkU7U0FDRjthQUFNO1lBQ0wsaUJBQWlCLEdBQUcsSUFBSSxDQUFDO1NBQzFCO1FBRUQsSUFBSSxDQUFDLGlCQUFpQixFQUFFO1lBQ3RCLE1BQU0sSUFBSSxLQUFLLENBQUMsMkdBQTJHLENBQUMsQ0FBQTtTQUM3SDtRQUVELHFDQUFxQztRQUNyQyxJQUFJLHFCQUFxQixHQUFHLFNBQVMsQ0FBQztRQUN0QyxJQUFJO1lBQ0YscUJBQXFCLEdBQUcsTUFBTSxJQUFJLENBQUMsZUFBZSxDQUFDLGFBQWEsQ0FDOUQsYUFBYSxDQUFDLFFBQVEsRUFDdEIsYUFBYSxDQUFDLE9BQU8sRUFDckIsYUFBYSxDQUFDLE9BQU8sRUFDckI7Z0JBQ0UsR0FBRyxPQUFPO2dCQUNWLEdBQUcsRUFBRSxNQUFNO2FBQ1osQ0FDRixDQUFDO1NBQ0g7UUFBQyxPQUFPLEVBQUUsRUFBRTtZQUNYLE9BQU8sQ0FBQyxHQUFHLENBQUMsZ0NBQWdDLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1lBQ2xFLE1BQU0sSUFBSSxLQUFLLENBQUMsZ0NBQWdDLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1NBQ3hFO1FBRUQsSUFBSSxDQUFDLHFCQUFxQixDQUFDLGVBQWUsRUFBRTtZQUMxQyxNQUFNLElBQUksS0FBSyxDQUFDLGdEQUFnRCxDQUFDLENBQUM7U0FDbkU7UUFHRCxrQ0FBa0M7UUFDbEMsSUFBSSxxQkFBcUIsR0FBRyxTQUFTLENBQUM7UUFDdEMsTUFBTSxnQkFBZ0IsR0FBaUM7WUFDckQsR0FBRyxhQUFhO1lBQ2hCLGVBQWUsRUFBRSxxQkFBcUIsQ0FBQyxlQUFlLElBQUksRUFBRTtTQUM3RCxDQUFDO1FBRUYsSUFBSTtZQUNGLHFCQUFxQixHQUFHLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxhQUFhLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztZQUM5RSxJQUFJLHFCQUFxQixDQUFDLE1BQU0sSUFBSSxTQUFTLEVBQUU7Z0JBQzdDLE1BQU0sSUFBSSxLQUFLLENBQUMsb0VBQW9FLENBQUMsQ0FBQzthQUN2RjtTQUNGO1FBQUMsT0FBTyxFQUFFLEVBQUU7WUFDWCxPQUFPLENBQUMsR0FBRyxDQUFDLGdDQUFnQyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztZQUNsRSxNQUFNLElBQUksS0FBSyxDQUFDLGdDQUFnQyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztTQUN4RTtRQUdELE9BQU8scUJBQXFCLENBQUM7SUFDL0IsQ0FBQztJQUVNLEtBQUssQ0FBQyxjQUFjLENBQ3pCLGFBQW1DLEVBQ25DLE9BQXFCO1FBRXJCLElBQUksQ0FBQyxhQUFhLENBQUMsUUFBUSxFQUFFO1lBQzNCLE1BQU0sSUFBSSxLQUFLLENBQUMsdUJBQXVCLENBQUMsQ0FBQztTQUMxQztRQUVELElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxFQUFFO1lBQzFCLE1BQU0sSUFBSSxLQUFLLENBQUMsc0JBQXNCLENBQUMsQ0FBQztTQUN6QztRQUVELElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxFQUFFO1lBQzFCLE1BQU0sSUFBSSxLQUFLLENBQUMsc0JBQXNCLENBQUMsQ0FBQztTQUN6QztRQUVELElBQUksQ0FBQyxhQUFhLENBQUMsYUFBYSxFQUFFO1lBQ2hDLE1BQU0sSUFBSSxLQUFLLENBQUMsNEJBQTRCLENBQUMsQ0FBQztTQUMvQztRQUVELHFDQUFxQztRQUNyQyxNQUFNLHNCQUFzQixHQUFHLE1BQU0sSUFBSSxDQUFDLGVBQWUsQ0FBQyxjQUFjLENBQ3RFLGFBQWEsQ0FBQyxRQUFRLEVBQ3RCLGFBQWEsQ0FBQyxPQUFPLEVBQ3JCLGFBQWEsQ0FBQyxPQUFPLEVBQ3JCLE9BQU8sQ0FDUixDQUFDO1FBRUYsSUFBSSxDQUFDLHNCQUFzQixJQUFJLENBQUMsc0JBQXNCLENBQUMsZUFBZSxFQUFFO1lBQ3RFLE1BQU0sSUFBSSxLQUFLLENBQUMsb0NBQW9DLENBQUMsQ0FBQztTQUN2RDtRQUVELHlDQUF5QztRQUN6QyxNQUFNLG9CQUFvQixHQUE0QjtZQUNwRCxRQUFRLEVBQUUsYUFBYSxDQUFDLFFBQVE7WUFDaEMsT0FBTyxFQUFFLGFBQWEsQ0FBQyxPQUFPO1lBQzlCLE9BQU8sRUFBRSxhQUFhLENBQUMsT0FBTztZQUM5QixzQkFBc0IsRUFBRSxzQkFBc0IsQ0FBQyxlQUFlO1lBQzlELGFBQWEsRUFBRSxhQUFhLENBQUMsYUFBYTtTQUMzQyxDQUFDO1FBRUYsSUFBSTtZQUNGLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxjQUFjLENBQUMsb0JBQW9CLENBQUMsQ0FBQTtTQUMzRDtRQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ1gsT0FBTyxDQUFDLEdBQUcsQ0FBQyxtQ0FBbUMsRUFBRSxFQUFFLENBQUMsQ0FBQztZQUNyRCxNQUFNLElBQUksS0FBSyxDQUFDLG1DQUFtQyxHQUFHLEVBQUUsQ0FBQyxDQUFBO1NBQzFEO1FBRUQsT0FBTyxzQkFBc0IsQ0FBQztJQUNoQyxDQUFDO0lBRU0sS0FBSyxDQUFDLFlBQVksQ0FBQyxNQUEwQixFQUFFLE9BQXFCO1FBRXpFLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxFQUFFO1lBQ3BCLE1BQU0sSUFBSSxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztTQUMzQztRQUVELElBQUksQ0FBQyxNQUFNLENBQUMsVUFBVSxFQUFFO1lBQ3RCLE1BQU0sSUFBSSxLQUFLLENBQUMsdUNBQXVDLENBQUMsQ0FBQztTQUMxRDtRQUVELElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFO1lBQ2xCLE1BQU0sSUFBSSxLQUFLLENBQUMscUJBQXFCLENBQUMsQ0FBQztTQUN4QztRQUVELElBQUksQ0FBQyxNQUFNLENBQUMsZUFBZSxFQUFFO1lBQzNCLE1BQU0sSUFBSSxLQUFLLENBQUMsNEJBQTRCLENBQUMsQ0FBQztTQUMvQztRQUVELElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFO1lBQ25CLE1BQU0sQ0FBQyxPQUFPLEdBQUcsZUFBZSxDQUFDO1NBQ2xDO1FBRUQsSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUNaLE9BQU8sR0FBRztnQkFDUixnQkFBZ0IsRUFBRSxnQkFBZ0IsQ0FBQyxTQUFTO2FBQzdDLENBQUE7U0FDRjtRQUVELE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ2hELE1BQU0sY0FBYyxHQUFHLE1BQU0sQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLG1CQUFtQixDQUFDLENBQUMsQ0FBRSxVQUFVLENBQUMsUUFBZ0IsQ0FBQyxTQUFTLENBQUMsQ0FBQyxPQUFPLENBQUM7UUFFakksTUFBTSxpQkFBaUIsR0FBRyxNQUFNLElBQUksQ0FBQyxhQUFhLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxVQUFVLEVBQUUsY0FBYyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ3pHLE9BQU8sQ0FBQyxHQUFHLENBQUMseUJBQXlCLEVBQUUsaUJBQWlCLENBQUMsQ0FBQztRQUUxRCxJQUFJLE1BQU0sQ0FBQyxpQkFBaUIsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUU7WUFDckQsT0FBTyxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQztZQUM5QixJQUFJO2dCQUNGLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FBQyxPQUFPLENBQUMsY0FBYyxFQUFFLE1BQU0sQ0FBQyxNQUFNLEVBQUU7b0JBQzlELElBQUksRUFBRSxNQUFNLENBQUMsVUFBVTtvQkFDdkIsZ0JBQWdCLEVBQUUsZ0JBQWdCLENBQUMsU0FBUztvQkFDNUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLO2lCQUNyQixDQUFDLENBQUM7YUFDSjtZQUFDLE9BQU8sR0FBRyxFQUFFO2dCQUNaLE1BQU0sSUFBSSxLQUFLLENBQUMsa0JBQWtCLEdBQUcsRUFBRSxDQUFDLENBQUE7YUFDekM7WUFDRCxPQUFPLENBQUMsT0FBTyxDQUFDLGVBQWUsQ0FBQyxDQUFDO1NBQ2xDO1FBQ0QsT0FBTyxDQUFDLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUdqQyxJQUFJLE9BQU8sR0FBRyxNQUFNLENBQUMsT0FBTyxDQUFDO1FBRTdCLElBQUksQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLEVBQUU7WUFDN0IsT0FBTyxHQUFHLEVBQUUsR0FBRyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsZ0JBQWdCLENBQUMsU0FBUyxFQUFFLENBQUM7U0FDeEU7UUFHRCxPQUFPLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQzNCLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFO1lBQ25CLElBQUk7Z0JBQ0YsTUFBTSxZQUFZLEdBQWtCO29CQUNsQyxPQUFPLEVBQUUsTUFBTSxDQUFDLE9BQU87b0JBQ3ZCLFFBQVEsRUFBRSxNQUFNLENBQUMsUUFBUTtvQkFDekIsWUFBWSxFQUFFLE1BQU0sQ0FBQyxlQUFlO2lCQUNyQyxDQUFBO2dCQUNELE1BQU0sY0FBYyxHQUFtQyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsa0JBQWtCLENBQUMsWUFBWSxDQUFDLENBQUM7Z0JBQzdHLElBQUksY0FBYyxDQUFDLE1BQU0sS0FBSyxTQUFTLEVBQUU7b0JBQ3ZDLE9BQU8sR0FBRyxjQUFjLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQztpQkFDdkM7cUJBQU07b0JBQ0wsTUFBTSxJQUFJLEtBQUssQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDO2lCQUMzQzthQUNGO1lBQUMsT0FBTyxHQUFHLEVBQUU7Z0JBQ1osTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsR0FBRyxFQUFFLENBQUMsQ0FBQzthQUNuRDtTQUNGO1FBQ0QsT0FBTyxDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUU5QixNQUFNLFNBQVMsR0FBRyxVQUFVLENBQUMsWUFBWSxDQUFDO1lBQ3hDLElBQUksRUFBRSxTQUFTLENBQUMsS0FBSztZQUNyQixJQUFJLEVBQUU7Z0JBQ0osT0FBTyxFQUFFLE1BQU0sQ0FBQyxPQUFPO2dCQUN2QixZQUFZLEVBQUUsTUFBTSxDQUFDLGVBQWU7YUFDckM7U0FDRixDQUFDLENBQUM7UUFFSCxNQUFNLGVBQWUsR0FBRyxJQUFJLEVBQUUsQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsUUFBUSxFQUFFLENBQUM7UUFFN0YsSUFBSSxvQkFBOEIsQ0FBQztRQUNuQyxPQUFPLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLENBQUM7UUFDL0IsSUFBSTtZQUNGLG9CQUFvQixHQUFHLE1BQU0sSUFBSSxDQUFDLGVBQWUsQ0FBQyxZQUFZLENBQzVELE1BQU0sQ0FBQyxRQUFRLEVBQ2YsU0FBUyxFQUNULE1BQU0sQ0FBQyxPQUFPLENBQUMsRUFDZixlQUFlLEVBQ2YsT0FBTyxDQUNSLENBQUM7U0FFSDtRQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ1gsTUFBTSxJQUFJLEtBQUssQ0FDYix1Q0FBdUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUM1RCxDQUFDO1NBQ0g7UUFDRCxPQUFPLENBQUMsT0FBTyxDQUFDLGdCQUFnQixDQUFDLENBQUM7UUFFbEMsSUFDRSxDQUFDLENBQUEsb0JBQW9CLGFBQXBCLG9CQUFvQix1QkFBcEIsb0JBQW9CLENBQUUsZUFBZSxDQUFBO1lBQ3RDLENBQUEsb0JBQW9CLGFBQXBCLG9CQUFvQix1QkFBcEIsb0JBQW9CLENBQUUsZUFBZSxNQUFLLFNBQVMsRUFDbkQ7WUFDQSxNQUFNLElBQUksS0FBSyxDQUNiLHFGQUFxRixDQUN0RixDQUFDO1NBQ0g7UUFFRCxPQUFPLENBQUMsSUFBSSxDQUFDLDZCQUE2QixDQUFDLENBQUM7UUFDNUMsTUFBTSxpQkFBaUIsR0FBc0I7WUFDM0MsVUFBVSxFQUFFLE1BQU0sQ0FBQyxVQUFVO1lBQzdCLE9BQU8sRUFBRSxNQUFNLENBQUMsT0FBTyxDQUFDO1lBQ3hCLE1BQU0sRUFBRSxvQkFBb0IsYUFBcEIsb0JBQW9CLHVCQUFwQixvQkFBb0IsQ0FBRSxlQUFlO1lBQzdDLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTTtTQUN0QixDQUFDO1FBQ0YsTUFBTSxnQkFBZ0IsR0FBRyxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsZ0NBQWdDLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUVyRyxJQUFJLENBQUMsZ0JBQWdCLEVBQUU7WUFDckIsTUFBTSxJQUFJLEtBQUssQ0FBQyxzQ0FBc0MsQ0FBQyxDQUFDO1NBQ3pEO1FBQ0QsT0FBTyxDQUFDLE9BQU8sQ0FBQyw2QkFBNkIsQ0FBQyxDQUFDO1FBRS9DLE9BQU8sQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUVoQyxNQUFNLG1CQUFtQixHQUErQjtZQUN0RCxHQUFHLE1BQU07WUFDVCxPQUFPO1lBQ1AsZUFBZSxFQUFFLG9CQUFvQixDQUFDLGVBQWU7WUFDckQsZ0JBQWdCLEVBQUUsb0JBQW9CLENBQUMsZ0JBQWdCLElBQUksS0FBSztZQUNoRSxTQUFTLEVBQUUsZ0JBQWdCO1NBQzVCLENBQUE7UUFFRCxJQUFJO1lBRUYsTUFBTSxhQUFhLEdBQUcsTUFBTSxZQUFZLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxvQkFBb0IsQ0FBQyxtQkFBbUIsQ0FBQyxFQUFFLGFBQWEsRUFBRSxjQUFjLENBQUMsQ0FBQztZQUNuSSxPQUFPLENBQUMsT0FBTyxDQUFDLGlCQUFpQixDQUFDLENBQUM7WUFDbkMsT0FBTyxhQUFhLENBQUM7U0FDdEI7UUFBQyxPQUFPLEdBQUcsRUFBRTtZQUNaLE1BQU0sSUFBSSxLQUFLLENBQUMsdUNBQXVDLEdBQUcsRUFBRSxDQUFDLENBQUM7U0FDL0Q7SUFFSCxDQUFDO0lBRUQ7Ozs7Ozs7OztPQVNHO0lBQ0ssS0FBSyxDQUFDLGVBQWUsQ0FDM0IsV0FBbUIsRUFDbkIsUUFBZ0IsRUFDaEIsZUFBdUIsRUFDdkIsU0FBaUIsRUFDakIsZUFBdUI7UUFFdkIsaUNBQWlDO1FBQ2pDLElBQUksY0FBYyxDQUFDO1FBQ25CLElBQUk7WUFDRixjQUFjLEdBQUcsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDLHNCQUFzQixDQUMzRCxXQUFXLEVBQ1gsUUFBUSxFQUNSLGVBQWUsRUFDZixTQUFTLEVBQ1QsZUFBZSxDQUNoQixDQUFDO1lBRUYsSUFBSSxDQUFBLGNBQWMsYUFBZCxjQUFjLHVCQUFkLGNBQWMsQ0FBRSxNQUFNLE1BQUssUUFBUSxFQUFFO2dCQUN2QyxNQUFNLElBQUksS0FBSyxDQUFDLDZDQUE2QyxDQUFDLENBQUM7YUFDaEU7WUFFRCxPQUFPLENBQUMsR0FBRyxDQUNULCtCQUErQixJQUFJLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQyxFQUFFLENBQ2hFLENBQUM7U0FDSDtRQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ1gsTUFBTSxJQUFJLEtBQUssQ0FDYix1Q0FBdUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUM1RCxDQUFDO1NBQ0g7UUFFRCxPQUFPLGNBQWMsQ0FBQztJQUN4QixDQUFDO0lBRU0sS0FBSyxDQUFDLFVBQVUsQ0FDckIsYUFBa0MsRUFDbEMsT0FBcUI7O1FBRXJCLElBQ0UsQ0FBQSxhQUFhLGFBQWIsYUFBYSx1QkFBYixhQUFhLENBQUUsU0FBUyxNQUFLLFNBQVMsQ0FBQyxLQUFLO1lBQzVDLENBQUEsYUFBYSxhQUFiLGFBQWEsdUJBQWIsYUFBYSxDQUFFLFNBQVMsTUFBSyxTQUFTLENBQUMsY0FBYztZQUNyRCxDQUFBLGFBQWEsYUFBYixhQUFhLHVCQUFiLGFBQWEsQ0FBRSxTQUFTLE1BQUssU0FBUyxDQUFDLEdBQUcsRUFDMUM7WUFDQSxNQUFNLElBQUksS0FBSyxDQUNiLDJFQUEyRSxDQUM1RSxDQUFDO1NBQ0g7UUFFRCxJQUFJLENBQUMsYUFBYSxDQUFDLFFBQVEsRUFBRTtZQUMzQixNQUFNLElBQUksS0FBSyxDQUFDLGlEQUFpRCxDQUFDLENBQUM7U0FDcEU7UUFFRCxJQUFJLENBQUMsYUFBYSxDQUFDLE9BQU8sRUFBRTtZQUMxQixNQUFNLElBQUksS0FBSyxDQUFDLGdEQUFnRCxDQUFDLENBQUM7U0FDbkU7UUFFRCxJQUFJLENBQUMsYUFBYSxDQUFDLFlBQVksRUFBRTtZQUMvQixNQUFNLElBQUksS0FBSyxDQUNiLDREQUE0RCxDQUM3RCxDQUFDO1NBQ0g7UUFFRCxNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUM7UUFDdkIsSUFBSSxTQUFTLEdBQUcsRUFBRSxDQUFDO1FBQ25CLE1BQU0sU0FBUyxHQUFHLFVBQVUsQ0FBQyxZQUFZLENBQUM7WUFDeEMsSUFBSSxFQUFFLGFBQWEsQ0FBQyxTQUFTO1lBQzdCLElBQUksRUFBRTtnQkFDSixPQUFPLEVBQUUsVUFBVTtnQkFDbkIsWUFBWSxFQUFFLGFBQWEsQ0FBQyxZQUFZO2FBQ3pDO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLENBQUEsYUFBYSxhQUFiLGFBQWEsdUJBQWIsYUFBYSxDQUFFLE9BQU8sQ0FBQSxFQUFFO1lBQzNCLElBQUk7Z0JBQ0YsTUFBTSxhQUFhLEdBQUcsTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLGFBQWEsQ0FBQztvQkFDdkQsUUFBUSxFQUFFLGFBQWEsQ0FBQyxRQUFRO29CQUNoQyxTQUFTLEVBQUUsYUFBYSxDQUFDLFNBQVM7b0JBQ2xDLE9BQU8sRUFBRSxHQUFHO29CQUNaLFlBQVksRUFBRSxhQUFhLENBQUMsWUFBWTtpQkFDekMsQ0FBQyxDQUFDO2dCQUNILElBQUksQ0FBQSxhQUFhLGFBQWIsYUFBYSx1QkFBYixhQUFhLENBQUUsTUFBTSxNQUFLLFNBQVMsRUFBRTtvQkFDdkMsU0FBUyxHQUFHLE1BQUEsYUFBYSxhQUFiLGFBQWEsdUJBQWIsYUFBYSxDQUFFLElBQUksMENBQUUsT0FBTyxDQUFDO2lCQUMxQztxQkFBTTtvQkFDTCxNQUFNLElBQUksS0FBSyxDQUNiLDhFQUE4RSxDQUMvRSxDQUFDO2lCQUNIO2FBQ0Y7WUFBQyxPQUFPLEVBQU8sRUFBRTtnQkFDaEIsTUFBTSxJQUFJLEtBQUssQ0FBQywwQkFBMEIsQ0FBQyxDQUFDO2FBQzdDO1NBQ0Y7YUFBTTtZQUNMLFNBQVMsR0FBRyxhQUFhLGFBQWIsYUFBYSx1QkFBYixhQUFhLENBQUUsT0FBTyxDQUFDO1NBQ3BDO1FBRUQsZ0NBQWdDO1FBQ2hDLElBQUksb0JBQThCLENBQUM7UUFDbkMsSUFBSTtZQUNGLG9CQUFvQixHQUFHLE1BQU0sSUFBSSxDQUFDLGVBQWUsQ0FBQyxVQUFVLENBQzFELGFBQWEsYUFBYixhQUFhLHVCQUFiLGFBQWEsQ0FBRSxRQUFRLEVBQ3ZCLFNBQVMsRUFDVCxhQUFhLGFBQWIsYUFBYSx1QkFBYixhQUFhLENBQUUsT0FBTyxFQUN0QixTQUFTLEVBQ1QsT0FBTyxDQUNSLENBQUM7WUFFRixPQUFPLENBQUMsR0FBRyxDQUNULGtDQUFrQyxJQUFJLENBQUMsU0FBUyxDQUFDLG9CQUFvQixDQUFDLEVBQUUsQ0FDekUsQ0FBQztTQUNIO1FBQUMsT0FBTyxFQUFFLEVBQUU7WUFDWCxNQUFNLElBQUksS0FBSyxDQUNiLDJDQUEyQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQ2hFLENBQUM7U0FDSDtRQUVELE9BQU8sb0JBQW9CLENBQUM7SUFDOUIsQ0FBQztDQUNGIn0=