myria-core-sdk
Version:
Latest version SDK
510 lines (507 loc) • 46.8 kB
JavaScript
import BN from "bn.js";
import { MyriaClient } from "..";
import { AssetAPI, CommonAPI, WithdrawalAPI, WithdrawalMarketpAPI, } from "../core/apis";
import { ContractFactory } from "../core/ContractFactory";
import { stripHexPrefix } from "../core/helpers";
import { TokenType } from "../types/CommonTypes";
import { DEFAULT_QUANTUM } from "../utils/Constants";
import { DELAY_IN_RETRY, retryPromise, RETRY_DEFAULT } from "../utils/RetryUtils";
import { CommonModule } from "./CommonModule";
/**
* Create WithdrawalModule instance object
* @class WithdrawalModule
* @param {IMyriaClient} IMyriaClient Interface of Myria Client
*/
export class WithdrawalModule {
constructor(client) {
this.client = new MyriaClient(client);
this.withdrawalContract = this.getWithdrawalContract(this.client);
this.withdrawalAPI = new WithdrawalAPI(this.client.env);
this.withdrawalMarketpAPI = new WithdrawalMarketpAPI(this.client.env);
this.commonAPI = new CommonAPI(this.client.env);
this.assetAPI = new AssetAPI(this.client.env);
this.commonModule = CommonModule.getInstance(client);
}
getWithdrawalContract(client) {
const contractFactory = new ContractFactory(client);
return contractFactory.getWithdrawContract();
}
getCustomWithdrawContract(customERC20Network) {
const contractFactory = new ContractFactory(this.client);
return contractFactory.getCustomWithdrawContract(customERC20Network);
}
async withdrawalOffchain(withdrawalParams) {
var _a, _b;
if (!withdrawalParams.ethAddress) {
throw new Error("Eth address is required");
}
if (!withdrawalParams.amount) {
throw new Error("Amount is required.");
}
if (!withdrawalParams.starkKey) {
throw new Error("StarkKey is required.");
}
if (!withdrawalParams.quantum) {
withdrawalParams.quantum = DEFAULT_QUANTUM; // default quantum is 10^10
}
// Get vault ID
let vaultData;
const quantizedAmount = (new BN(withdrawalParams.amount)).div(new BN(withdrawalParams.quantum)).toString();
let nonce = withdrawalParams.nonce;
if (!nonce) {
nonce = await this.client.web3.eth.getTransactionCount(withdrawalParams.ethAddress);
}
console.log("nonce is ", nonce);
if (!(withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.vaultId)) {
try {
const vaultResponse = await this.commonAPI.createVault({
starkKey: withdrawalParams.starkKey,
tokenType: withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.tokenType,
tokenAddress: (withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.tokenAddress) || undefined,
quantum: withdrawalParams.quantum, // TODO might update the quantum later to respect with the input
});
if ((vaultResponse === null || vaultResponse === void 0 ? void 0 : vaultResponse.status) === "success") {
vaultData = String(vaultResponse.data.vaultId);
}
else {
throw new Error("Fetching vaultID failure - check BE server or validation request for calling");
}
}
catch (ex) {
throw new Error(ex);
}
}
else {
vaultData = String(withdrawalParams.vaultId);
}
const assetDict = {
type: withdrawalParams.tokenType,
data: {
quantum: withdrawalParams.quantum,
tokenAddress: withdrawalParams.tokenAddress
}
};
const assetId = this.commonModule.generateAssetId(assetDict);
const withdrawalHashPayload = {
vaultId: vaultData,
assetId: stripHexPrefix(assetId),
quantizedAmount: quantizedAmount,
nonce: nonce,
ethAddress: withdrawalParams.ethAddress
};
console.log("[Core-SDK] withdrawal hash payload ->", withdrawalHashPayload);
const signature = await this.commonModule.generateSignatureForWithdrawal(withdrawalHashPayload);
if (!signature) {
throw new Error("Stark signature generation error ");
}
// Make withdraw offchain request
let withdrawOffchainResult;
try {
if ((withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.tokenType) === TokenType.MINTABLE_ERC721 ||
(withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.tokenType) === TokenType.ERC721) {
withdrawOffchainResult = await this.withdrawalAPI.makeWithdrawalTransaction(vaultData, (_a = withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.starkKey) === null || _a === void 0 ? void 0 : _a.toLowerCase(), withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.amount, assetId, signature, nonce);
}
else {
withdrawOffchainResult = await this.withdrawalAPI.makeWithdrawalTransaction(vaultData, (_b = withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.starkKey) === null || _b === void 0 ? void 0 : _b.toLowerCase(), quantizedAmount, assetId, signature, nonce);
}
console.log(`Withdrawal offchain response -> ${JSON.stringify(withdrawOffchainResult)}`);
}
catch (ex) {
console.log(`Exception for withdraw offchain -> ${JSON.stringify(ex)}`);
throw new Error(`Withdrawal failure on server with exception -> ${JSON.stringify(ex)}`);
}
return withdrawOffchainResult;
}
/**
* @description The withdraw offchain function in V2
* @param {WithdrawOffchainParamsV2} withdrawalParams Withdraw off-chain params input
* @throws {string} Exception: Sender starkKey is required!
* @throws {string} Exception: Receiver eth address is required!
* @throws {string} Exception: Quantum is required!
* @throws {string} Exception: Amount is required!
* @throws {string} Exception: Token Type is required.
* @throws {string} Exception: Token Address is required!
* @throws {string} Exception: Token ID is required!
* @returns {TransactionData} Transactions data which indicate the transaction results, transaction details information
* (such as transactionID, transactionStatus...)
* @example <caption>Sample code on Testnet (Staging) env</caption>
const mClient: IMyriaClient = {
networkId: Network.GOERLI,
provider: web3Instance.currentProvider,
web3: web3Instance,
env: EnvTypes.STAGING,
};
const senderStarkKey = '0xfb....'; // Sample of sender stark public key
const senderEthAddress = '0x....'; // Sender wallet address
const QUANTUM_CONSTANT = 10000000000; // Quantum 10^10
const weiAmount = 1000000000000000000; // 1 ETH we can use this page to convert the ETH to wei: https://eth-converter.com/
const withdrawParamsV2: WithdrawOffchainParamsV2 = {
senderPublicKey: senderStarkKey,
senderEthAddress: senderEthAddress,
receiverPublicKey: senderEthAddress,
amount: String(weiAmount),
tokenType: TokenType.ETH, // tokenType is ETH if users want to withdraw ETH
quantum: QUANTUM_CONSTANT.toString(),
};
responseWithdraw = await withdrawModule.withdrawalOffchainV2(
withdrawParamsV2,
);
console.log('Transaction result -> ', result);
*/
async withdrawalOffchainV2(withdrawalParams) {
var _a, _b, _c, _d;
if (!withdrawalParams.senderPublicKey) {
throw new Error("Sender starkKey is required!");
}
if (!withdrawalParams.receiverPublicKey) {
throw new Error("Receiver eth address is required!");
}
if (!withdrawalParams.quantum) {
throw new Error("Quantum is required!");
}
if (!withdrawalParams.amount) {
throw new Error("Amount is required!");
}
if (!withdrawalParams.tokenType) {
throw new Error("Token Type is required.");
}
if (withdrawalParams.tokenType === TokenType.ERC20) {
if (!withdrawalParams.tokenAddress) {
throw new Error("Token Address is required!");
}
}
if (withdrawalParams.tokenType === TokenType.ERC721) {
if (!withdrawalParams.tokenAddress) {
throw new Error("Token Address is required!");
}
if (!withdrawalParams.tokenId) {
throw new Error("Token ID is required!");
}
}
let withdrawalOffchainResult;
try {
const senderVault = await this.commonAPI.createVault({
quantum: withdrawalParams.quantum,
starkKey: withdrawalParams.senderPublicKey,
tokenType: withdrawalParams.tokenType,
tokenAddress: withdrawalParams.tokenAddress
});
if (!senderVault || senderVault.status !== "success") {
throw new Error(`Failed to get vault for sender ${withdrawalParams.senderPublicKey}, please retry`);
}
const receiverVault = await this.commonAPI.createVault({
quantum: withdrawalParams.quantum,
starkKey: withdrawalParams.receiverPublicKey,
tokenType: withdrawalParams.tokenType,
tokenAddress: withdrawalParams.tokenAddress
});
if (!receiverVault || receiverVault.status !== "success") {
throw new Error(`Failed to get vault for receiver ${withdrawalParams.receiverPublicKey}, please retry`);
}
const nonceByStarkKey = await this.commonAPI.getNonceByStarkKey(withdrawalParams.senderPublicKey);
// Get nonce from our BE server
const nonce = (nonceByStarkKey === null || nonceByStarkKey === void 0 ? void 0 : nonceByStarkKey.data) || Math.floor(Math.random() * 10000000) + 1;
// SET EXPIRATION TO BE EXPIRE IN 12 YEARS
const expirationTimestamp = new Date();
expirationTimestamp.setFullYear(expirationTimestamp.getFullYear() + 12);
const expirationTime = Math.floor(expirationTimestamp.getTime() / (3600 * 1000));
const assetDict = {
type: withdrawalParams.tokenType,
data: {
quantum: withdrawalParams.quantum,
tokenAddress: withdrawalParams.tokenAddress
}
};
const assetId = this.commonModule.generateAssetId(assetDict);
const quantizedAmount = (new BN(withdrawalParams.amount, 10)).div(new BN(withdrawalParams.quantum, 10)).toString();
const msgBody = {
senderVaultId: (_a = senderVault.data) === null || _a === void 0 ? void 0 : _a.vaultId,
senderPublicKey: withdrawalParams.senderPublicKey,
receiverVaultId: (_b = receiverVault === null || receiverVault === void 0 ? void 0 : receiverVault.data) === null || _b === void 0 ? void 0 : _b.vaultId,
receiverPublicKey: withdrawalParams.receiverPublicKey,
nonce: nonce,
expirationTimestamp: expirationTime,
quantizedAmount: quantizedAmount,
token: assetId,
senderEthAddress: withdrawalParams.senderEthAddress,
};
const starkSignature = await this.commonModule.generateStarkSignatureForWithdrawal(msgBody);
if (!starkSignature) {
throw new Error("Error on signing!");
}
const requestPayload = {
senderVaultId: (_c = senderVault.data) === null || _c === void 0 ? void 0 : _c.vaultId,
senderPublicKey: withdrawalParams.senderPublicKey,
receiverVaultId: (_d = receiverVault === null || receiverVault === void 0 ? void 0 : receiverVault.data) === null || _d === void 0 ? void 0 : _d.vaultId,
receiverPublicKey: withdrawalParams.receiverPublicKey,
nonce: nonce,
expirationTimestamp: expirationTime,
signature: starkSignature,
quantizedAmount: quantizedAmount,
token: assetId,
};
const response = await this.withdrawalAPI.makeWithdrawalTransactionV2(requestPayload);
if ((response === null || response === void 0 ? void 0 : response.status) === "success") {
withdrawalOffchainResult = response === null || response === void 0 ? void 0 : response.data;
}
else {
throw new Error("Withdrawal failed!");
}
}
catch (err) {
console.log("Error -> ", err);
}
return withdrawalOffchainResult;
}
/**
* @description Function to check if user has registered on-chain with Starkware dedicated instance
* @param {string} starkKey Public stark key of user
* @returns {TxResult | undefined} On-chain transactions results information (block confirmed, transaction hash,....)
*/
async checkUserRegisterOnchain(starkKey) {
if (!starkKey) {
throw new Error('Stark key is required');
}
let txResult;
try {
txResult = await this.withdrawalContract.getEthKey(starkKey);
}
catch (ex) {
console.log('[SDK-Exception] Error -> ', ex);
}
return txResult;
}
/**
* @description The withdraw on-chain function to withdraw the Tokens available in the On-chain to User's Wallet
* @typedef {Object} WithdrawOnchainParams The payload for requesting withdraw on-chain actions (assetType and starkKey)
* @param {WithdrawOnchainParams} withdrawalParams The withdraw on-chain params where it include both of the StarkKey and AssetType
* @param {SendOptions} options The native options for withdraw on-chain options
* @returns {Promise<TxResult>} The promise with transaction results
*/
async withdrawalOnchain(withdrawalParams, options) {
let txResult;
try {
txResult = await this.withdrawalContract.withdrawal(withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.starkKey, withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.assetType, options);
console.log(`Withdrawal onchain response -> ${JSON.stringify(txResult)}`);
}
catch (ex) {
console.log(`Exception for withdraw onchain -> ${JSON.stringify(txResult)}`);
throw new Error(`Withdraw onchain failure with error ${JSON.stringify(ex)}`);
}
return txResult;
}
async fullWithdrawal(payload) {
if (!payload.ethAddress) {
throw new Error("User address is required!");
}
if (!payload.starkKey) {
throw new Error("User starkKey is required!");
}
if (!payload.vaultId) {
throw new Error("VaultId is required!");
}
if (!payload.nonce) {
throw new Error("Nonce is required!");
}
let fullWithdrawalResult;
try {
const starkSignature = await this.commonModule.generateStarkSignatureForFullWithdrawal(payload.vaultId, payload.nonce, payload.ethAddress);
if (!starkSignature) {
throw new Error("Sigining current request failed!");
}
else {
const requestPayload = {
vaultId: payload.vaultId,
nonce: payload.nonce,
starkKey: payload.starkKey,
signature: starkSignature
};
const fullWithdrawalResponse = await this.withdrawalAPI.fullWithdrawal(requestPayload);
if ((fullWithdrawalResponse === null || fullWithdrawalResponse === void 0 ? void 0 : fullWithdrawalResponse.status) === "success") {
fullWithdrawalResult = fullWithdrawalResponse === null || fullWithdrawalResponse === void 0 ? void 0 : fullWithdrawalResponse.data;
}
else {
throw new Error("Full withdrawal failed!");
}
}
}
catch (err) {
throw new Error("Full withdrawal failed!");
}
return fullWithdrawalResult;
}
async withdrawalNFT(withdrawalParams, options) {
let txResult;
try {
txResult = await this.withdrawalContract.withdrawalNft(withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.ownerKey, withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.assetType, withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.tokenId, options);
console.log(`Withdraw nft onchain response -> ${txResult}`);
}
catch (ex) {
console.log(`Exception for withdraw nft onchain -> ${JSON.stringify(txResult)}`);
throw new Error(`Withdraw nft onchain failure with error ${JSON.stringify(ex)}`);
}
return txResult;
}
/**
* @description Withdraw and mint the assets NFTs (ERC_721) in the on-chain and send the NFTs/tokens to user
* @param {WithdrawAndMintParams} withdrawalParams Withdraw and mint params options
* @param {SendOptions?} options The native options for transaction in Web3
* @returns {Promise<TxResult>} Transaction results in the on-chain after withdraw and mint action
*/
async withdrawAndMint(withdrawalParams, options) {
let txResult;
try {
txResult = await this.withdrawalContract.withdrawAndMint(withdrawalParams.walletAddress, withdrawalParams.assetType, withdrawalParams === null || withdrawalParams === void 0 ? void 0 : withdrawalParams.mintingBlob, options);
console.log(`Withdrawal onchain response -> ${JSON.stringify(txResult)}`);
}
catch (ex) {
console.log(`Exception for withdraw onchain -> ${JSON.stringify(txResult)}`);
throw new Error(`Withdraw onchain failure with error ${JSON.stringify(ex)}`);
}
return txResult;
}
/**
* @description The function is to get the available fund for user to be withdraw in the L1 and it requests to StarkEx smart contract
* @param {string} ownerKey The owner key of users (Wallet Address / Stark Key) where it locates the fund in the on-chain
* @param {string} assetId The asset ID (hex string) to be represent for the tokens that we'd to check with the available balance for withdraw
* @returns {Promise<TxResult>} The transaction result data which is on-chain data object
*/
async getWithdrawalBalance(ownerKey, assetId, options) {
return this.withdrawalContract.getWithdrawalBalance(ownerKey, assetId, options);
}
/**
* @description The withdraw nft off-chain actions to bring the NFTs to user's wallet (Metamask/Trust Wallet)
* @param {WithdrawNftOffChainParams} payload Withdraw NFTs Off-chain params
* @returns {APIResponseType<WithdrawNftOffChainResponse> | undefined} The withdraw NFT off-chain response including transaction details data
*/
async withdrawNftOffChain(payload) {
var _a;
let result;
if (!payload.id) {
throw new Error("Id is required");
}
if (!payload.tokenId) {
throw new Error("TokenId is required");
}
if (!payload.tokenAddress) {
throw new Error("Token address is required");
}
if (!payload.senderPublicKey) {
throw new Error("StarkKey is required");
}
if (!payload.receiverPublicKey) {
throw new Error("Receiver public key is required");
}
if (!payload.assetId) {
throw new Error("AssetId is required");
}
if (!Number.isInteger(Number(payload.quantizedAmount))) {
throw new Error("QuantizedAmount the required");
}
if (!Number.isInteger(Number(payload.senderVaultId))) {
throw new Error("Missing the vaultId");
}
const vaultForERC721 = {
tokenId: payload.tokenId,
depositEthAddress: payload.receiverPublicKey,
tokenAddress: payload.tokenAddress,
starkKey: payload.receiverPublicKey,
};
const assetVaultsByEthKey = await this.assetAPI.createERC721VaultByEthAddress(vaultForERC721);
const receiverVaultId = (_a = assetVaultsByEthKey === null || assetVaultsByEthKey === void 0 ? void 0 : assetVaultsByEthKey.data) === null || _a === void 0 ? void 0 : _a.vaultId;
const nonceByStarkKey = await this.commonAPI.getNonceByStarkKey(payload.senderPublicKey);
const nonceData = (nonceByStarkKey === null || nonceByStarkKey === void 0 ? void 0 : nonceByStarkKey.data) || Math.floor(Math.random() * 100000000) + 1;
// const nonceData = Math.floor(Math.random() * 80000000);
// SET EXPIRATION TO BE EXPIRE IN 12 YEARS
const expirationTimestamp = new Date();
expirationTimestamp.setDate(expirationTimestamp.getFullYear() + 12);
const expirationTime = Math.floor(expirationTimestamp.getTime() / (3600 * 1000));
// Generate signature
const msgBody = {
senderVaultId: payload.senderVaultId,
senderPublicKey: payload.senderPublicKey,
receiverVaultId: receiverVaultId,
receiverPublicKey: payload.receiverPublicKey,
nonce: nonceData,
expirationTimestamp: expirationTime,
quantizedAmount: payload.quantizedAmount,
token: payload.assetId,
senderEthAddress: payload.receiverPublicKey,
};
const starkSignature = await this.commonModule.generateStarkSignatureForWithdrawal(msgBody);
if (!starkSignature) {
throw new Error("Error on signing!");
}
const requestPayload = {
id: payload.id,
senderVaultId: payload.senderVaultId,
senderPublicKey: payload.senderPublicKey,
receiverVaultId: receiverVaultId,
receiverPublicKey: payload.receiverPublicKey,
token: payload.assetId,
quantizedAmount: payload.quantizedAmount,
nonce: nonceData,
expirationTimestamp: expirationTime,
signature: starkSignature,
};
try {
const withdrawRes = await this.withdrawalMarketpAPI.requestWithdrawNftOffChain(requestPayload);
if ((withdrawRes === null || withdrawRes === void 0 ? void 0 : withdrawRes.status) === "success") {
result = withdrawRes === null || withdrawRes === void 0 ? void 0 : withdrawRes.data;
}
else {
throw new Error("Withdraw nft onchain failure");
}
}
catch (error) {
throw new Error(`Withdraw nft onchain failure with error ${JSON.stringify(error)}`);
}
return result;
}
/**
* @description As long as the NFTs has been completed with the on-chain withdraw and user receive the NFTs/tokens into their wallet (Metamask,etc...)
* This is one last step for tracking and notify to Myria service that this NFTs has been completed on withdraw process and
* the token is no longer existed in Myria system
* @param {WithdrawNftCompleteParams} payload The payload for requesting the NFTs withdraw completed in Myria system
* @returns {APIResponseType<WithdrawNftCompleteResponse> | undefined} The withdraw nft completion response from Myria services
*/
async withdrawNftComplete(payload) {
var _a, _b;
let result;
if (!payload.assetId) {
throw new Error("Asset id is required");
}
if (!payload.starkKey) {
throw new Error("StarkKey is required");
}
if (!payload.transactionHash) {
throw new Error("Transaction hash is required");
}
try {
const assetVaultDetails = await this.assetAPI.getAssetVaultDetails({
starkKey: payload.starkKey,
assetId: payload.assetId,
});
if ((assetVaultDetails === null || assetVaultDetails === void 0 ? void 0 : assetVaultDetails.data) && (assetVaultDetails === null || assetVaultDetails === void 0 ? void 0 : assetVaultDetails.status) === "success") {
const payloadWithDrawComplete = {
vaultId: (_a = assetVaultDetails.data) === null || _a === void 0 ? void 0 : _a.vaultId,
starkKey: payload.starkKey,
assetId: (_b = assetVaultDetails.data) === null || _b === void 0 ? void 0 : _b.assetId,
transactionHash: payload.transactionHash
};
const withdrawCompleteRes = await retryPromise(this.withdrawalMarketpAPI.requestWithdrawNftComplete(payloadWithDrawComplete), RETRY_DEFAULT, DELAY_IN_RETRY);
if ((withdrawCompleteRes === null || withdrawCompleteRes === void 0 ? void 0 : withdrawCompleteRes.status) === "success") {
result = withdrawCompleteRes === null || withdrawCompleteRes === void 0 ? void 0 : withdrawCompleteRes.data;
}
else {
throw new Error("Withdraw Complete failure");
}
}
}
catch (error) {
throw new Error(`WithdrawNft Complete failure with error ${JSON.stringify(error)}`);
}
return result;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiV2l0aGRyYXdhbE1vZHVsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL1dpdGhkcmF3YWxNb2R1bGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sT0FBTyxDQUFDO0FBQ3ZCLE9BQU8sRUFBZ0IsV0FBVyxFQUFzQixNQUFNLElBQUksQ0FBQztBQUNuRSxPQUFPLEVBQ0wsUUFBUSxFQUNSLFNBQVMsRUFDVCxhQUFhLEVBQ2Isb0JBQW9CLEdBQ3JCLE1BQU0sY0FBYyxDQUFDO0FBQ3RCLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUMxRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFFakQsT0FBTyxFQUFlLFNBQVMsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBb0I5RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUFFLGNBQWMsRUFBRSxZQUFZLEVBQUUsYUFBYSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFbEYsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRTlDOzs7O0dBSUc7QUFDSCxNQUFNLE9BQU8sZ0JBQWdCO0lBUzNCLFlBQVksTUFBb0I7UUFDOUIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLFdBQVcsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUN0QyxJQUFJLENBQUMsa0JBQWtCLEdBQUcsSUFBSSxDQUFDLHFCQUFxQixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNsRSxJQUFJLENBQUMsYUFBYSxHQUFHLElBQUksYUFBYSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDeEQsSUFBSSxDQUFDLG9CQUFvQixHQUFHLElBQUksb0JBQW9CLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN0RSxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksU0FBUyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDaEQsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLFFBQVEsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQzlDLElBQUksQ0FBQyxZQUFZLEdBQUcsWUFBWSxDQUFDLFdBQVcsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUN2RCxDQUFDO0lBRU0scUJBQXFCLENBQUMsTUFBbUI7UUFDOUMsTUFBTSxlQUFlLEdBQUcsSUFBSSxlQUFlLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDcEQsT0FBTyxlQUFlLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztJQUMvQyxDQUFDO0lBRU0seUJBQXlCLENBQUMsa0JBQXVDO1FBQ3RFLE1BQU0sZUFBZSxHQUFHLElBQUksZUFBZSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUN6RCxPQUFPLGVBQWUsQ0FBQyx5QkFBeUIsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO0lBQ3ZFLENBQUM7SUFHTSxLQUFLLENBQUMsa0JBQWtCLENBQzdCLGdCQUF3Qzs7UUFHeEMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFVBQVUsRUFBRTtZQUNoQyxNQUFNLElBQUksS0FBSyxDQUFDLHlCQUF5QixDQUFDLENBQUM7U0FDNUM7UUFFRCxJQUFJLENBQUMsZ0JBQWdCLENBQUMsTUFBTSxFQUFFO1lBQzVCLE1BQU0sSUFBSSxLQUFLLENBQUMscUJBQXFCLENBQUMsQ0FBQztTQUN4QztRQUdELElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLEVBQUU7WUFDOUIsTUFBTSxJQUFJLEtBQUssQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDO1NBQzFDO1FBRUQsSUFBSSxDQUFDLGdCQUFnQixDQUFDLE9BQU8sRUFBRTtZQUM3QixnQkFBZ0IsQ0FBQyxPQUFPLEdBQUcsZUFBZSxDQUFDLENBQUMsMkJBQTJCO1NBQ3hFO1FBRUQsZUFBZTtRQUNmLElBQUksU0FBUyxDQUFDO1FBQ2QsTUFBTSxlQUFlLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQzNHLElBQUksS0FBSyxHQUFHLGdCQUFnQixDQUFDLEtBQUssQ0FBQztRQUNuQyxJQUFJLENBQUMsS0FBSyxFQUFFO1lBQ1YsS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLG1CQUFtQixDQUFDLGdCQUFnQixDQUFDLFVBQVUsQ0FBQyxDQUFDO1NBQ3JGO1FBRUQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFFaEMsSUFBSSxDQUFDLENBQUEsZ0JBQWdCLGFBQWhCLGdCQUFnQix1QkFBaEIsZ0JBQWdCLENBQUUsT0FBTyxDQUFBLEVBQUU7WUFDOUIsSUFBSTtnQkFDRixNQUFNLGFBQWEsR0FBRyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsV0FBVyxDQUFDO29CQUNyRCxRQUFRLEVBQUUsZ0JBQWdCLENBQUMsUUFBUTtvQkFDbkMsU0FBUyxFQUFFLGdCQUFnQixhQUFoQixnQkFBZ0IsdUJBQWhCLGdCQUFnQixDQUFFLFNBQVM7b0JBQ3RDLFlBQVksRUFBRSxDQUFBLGdCQUFnQixhQUFoQixnQkFBZ0IsdUJBQWhCLGdCQUFnQixDQUFFLFlBQVksS0FBSSxTQUFTO29CQUN6RCxPQUFPLEVBQUUsZ0JBQWdCLENBQUMsT0FBTyxFQUFFLGdFQUFnRTtpQkFDcEcsQ0FBQyxDQUFDO2dCQUNILElBQUksQ0FBQSxhQUFhLGFBQWIsYUFBYSx1QkFBYixhQUFhLENBQUUsTUFBTSxNQUFLLFNBQVMsRUFBRTtvQkFDdkMsU0FBUyxHQUFHLE1BQU0sQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO2lCQUNoRDtxQkFBTTtvQkFDTCxNQUFNLElBQUksS0FBSyxDQUNiLDhFQUE4RSxDQUMvRSxDQUFDO2lCQUNIO2FBQ0Y7WUFBQyxPQUFPLEVBQU8sRUFBRTtnQkFDaEIsTUFBTSxJQUFJLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQzthQUNyQjtTQUNGO2FBQU07WUFDTCxTQUFTLEdBQUcsTUFBTSxDQUFDLGdCQUFnQixDQUFDLE9BQU8sQ0FBQyxDQUFDO1NBQzlDO1FBRUQsTUFBTSxTQUFTLEdBQW9CO1lBQ2pDLElBQUksRUFBRSxnQkFBZ0IsQ0FBQyxTQUFTO1lBQ2hDLElBQUksRUFBRTtnQkFDSixPQUFPLEVBQUUsZ0JBQWdCLENBQUMsT0FBTztnQkFDakMsWUFBWSxFQUFFLGdCQUFnQixDQUFDLFlBQVk7YUFDNUM7U0FDRixDQUFBO1FBRUQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxlQUFlLENBQUMsU0FBUyxDQUFDLENBQUM7UUFFN0QsTUFBTSxxQkFBcUIsR0FBMkI7WUFDcEQsT0FBTyxFQUFFLFNBQVM7WUFDbEIsT0FBTyxFQUFFLGNBQWMsQ0FBQyxPQUFPLENBQUM7WUFDaEMsZUFBZSxFQUFFLGVBQWU7WUFDaEMsS0FBSyxFQUFFLEtBQUs7WUFDWixVQUFVLEVBQUUsZ0JBQWdCLENBQUMsVUFBVTtTQUN4QyxDQUFDO1FBRUYsT0FBTyxDQUFDLEdBQUcsQ0FDVCx1Q0FBdUMsRUFDdkMscUJBQXFCLENBQ3RCLENBQUM7UUFFRixNQUFNLFNBQVMsR0FBRyxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsOEJBQThCLENBQ3RFLHFCQUFxQixDQUN0QixDQUFDO1FBRUYsSUFBSSxDQUFDLFNBQVMsRUFBRTtZQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMsbUNBQW1DLENBQUMsQ0FBQztTQUN0RDtRQUVELGlDQUFpQztRQUNqQyxJQUFJLHNCQUFzQixDQUFDO1FBQzNCLElBQUk7WUFDRixJQUNFLENBQUEsZ0JBQWdCLGFBQWhCLGdCQUFnQix1QkFBaEIsZ0JBQWdCLENBQUUsU0FBUyxNQUFLLFNBQVMsQ0FBQyxlQUFlO2dCQUN6RCxDQUFBLGdCQUFnQixhQUFoQixnQkFBZ0IsdUJBQWhCLGdCQUFnQixDQUFFLFNBQVMsTUFBSyxTQUFTLENBQUMsTUFBTSxFQUNoRDtnQkFDQSxzQkFBc0IsR0FBRyxNQUFNLElBQUksQ0FBQyxhQUFhLENBQUMseUJBQXlCLENBQ3pFLFNBQVMsRUFDVCxNQUFBLGdCQUFnQixhQUFoQixnQkFBZ0IsdUJBQWhCLGdCQUFnQixDQUFFLFFBQVEsMENBQUUsV0FBVyxFQUFFLEVBQ3pDLGdCQUFnQixhQUFoQixnQkFBZ0IsdUJBQWhCLGdCQUFnQixDQUFFLE1BQU0sRUFDeEIsT0FBTyxFQUNQLFNBQVMsRUFDVCxLQUFLLENBQ04sQ0FBQzthQUNIO2lCQUFNO2dCQUNMLHNCQUFzQixHQUFHLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FBQyx5QkFBeUIsQ0FDekUsU0FBUyxFQUNULE1BQUEsZ0JBQWdCLGFBQWhCLGdCQUFnQix1QkFBaEIsZ0JBQWdCLENBQUUsUUFBUSwwQ0FBRSxXQUFXLEVBQUUsRUFDekMsZUFBZSxFQUNmLE9BQU8sRUFDUCxTQUFTLEVBQ1QsS0FBSyxDQUNOLENBQUM7YUFDSDtZQUVELE9BQU8sQ0FBQyxHQUFHLENBQ1QsbUNBQW1DLElBQUksQ0FBQyxTQUFTLENBQy9DLHNCQUFzQixDQUN2QixFQUFFLENBQ0osQ0FBQztTQUNIO1FBQUMsT0FBTyxFQUFFLEVBQUU7WUFDWCxPQUFPLENBQUMsR0FBRyxDQUFDLHNDQUFzQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUN4RSxNQUFNLElBQUksS0FBSyxDQUNiLGtEQUFrRCxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQ3ZFLENBQUM7U0FDSDtRQUVELE9BQU8sc0JBQXNCLENBQUM7SUFDaEMsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQXNDRztJQUNJLEtBQUssQ0FBQyxvQkFBb0IsQ0FDL0IsZ0JBQTBDOztRQUUxQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsZUFBZSxFQUFFO1lBQ3JDLE1BQU0sSUFBSSxLQUFLLENBQUMsOEJBQThCLENBQUMsQ0FBQztTQUNqRDtRQUVELElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxpQkFBaUIsRUFBRTtZQUN2QyxNQUFNLElBQUksS0FBSyxDQUFDLG1DQUFtQyxDQUFDLENBQUM7U0FDdEQ7UUFFRCxJQUFJLENBQUMsZ0JBQWdCLENBQUMsT0FBTyxFQUFFO1lBQzdCLE1BQU0sSUFBSSxLQUFLLENBQUMsc0JBQXNCLENBQUMsQ0FBQztTQUN6QztRQUVELElBQUcsQ0FBQyxnQkFBZ0IsQ0FBQyxNQUFNLEVBQUU7WUFDM0IsTUFBTSxJQUFJLEtBQUssQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO1NBQ3hDO1FBRUQsSUFBRyxDQUFDLGdCQUFnQixDQUFDLFNBQVMsRUFBRTtZQUM5QixNQUFNLElBQUksS0FBSyxDQUFDLHlCQUF5QixDQUFDLENBQUM7U0FDNUM7UUFFRCxJQUFHLGdCQUFnQixDQUFDLFNBQVMsS0FBSyxTQUFTLENBQUMsS0FBSyxFQUFFO1lBQ2pELElBQUcsQ0FBQyxnQkFBZ0IsQ0FBQyxZQUFZLEVBQUU7Z0JBQ2pDLE1BQU0sSUFBSSxLQUFLLENBQUMsNEJBQTRCLENBQUMsQ0FBQzthQUMvQztTQUNGO1FBRUQsSUFBRyxnQkFBZ0IsQ0FBQyxTQUFTLEtBQUssU0FBUyxDQUFDLE1BQU0sRUFBRTtZQUNsRCxJQUFHLENBQUMsZ0JBQWdCLENBQUMsWUFBWSxFQUFFO2dCQUNqQyxNQUFNLElBQUksS0FBSyxDQUFDLDRCQUE0QixDQUFDLENBQUM7YUFDL0M7WUFDRCxJQUFHLENBQUMsZ0JBQWdCLENBQUMsT0FBTyxFQUFFO2dCQUM1QixNQUFNLElBQUksS0FBSyxDQUFDLHVCQUF1QixDQUFDLENBQUM7YUFDMUM7U0FDRjtRQUVELElBQUksd0JBQXdCLENBQUM7UUFFN0IsSUFBSTtZQUNGLE1BQU0sV0FBVyxHQUFHLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxXQUFXLENBQUM7Z0JBQ25ELE9BQU8sRUFBRSxnQkFBZ0IsQ0FBQyxPQUFPO2dCQUNqQyxRQUFRLEVBQUUsZ0JBQWdCLENBQUMsZUFBZTtnQkFDMUMsU0FBUyxFQUFFLGdCQUFnQixDQUFDLFNBQVM7Z0JBQ3JDLFlBQVksRUFBRSxnQkFBZ0IsQ0FBQyxZQUFZO2FBQzVDLENBQUMsQ0FBQztZQUNILElBQUksQ0FBQyxXQUFXLElBQUksV0FBVyxDQUFDLE1BQU0sS0FBSyxTQUFTLEVBQUU7Z0JBQ3BELE1BQU0sSUFBSSxLQUFLLENBQ2Isa0NBQWtDLGdCQUFnQixDQUFDLGVBQWUsZ0JBQWdCLENBQ25GLENBQUM7YUFDSDtZQUVELE1BQU0sYUFBYSxHQUFHLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxXQUFXLENBQ3BEO2dCQUNFLE9BQU8sRUFBRSxnQkFBZ0IsQ0FBQyxPQUFPO2dCQUNqQyxRQUFRLEVBQUUsZ0JBQWdCLENBQUMsaUJBQWlCO2dCQUM1QyxTQUFTLEVBQUUsZ0JBQWdCLENBQUMsU0FBUztnQkFDckMsWUFBWSxFQUFFLGdCQUFnQixDQUFDLFlBQVk7YUFDNUMsQ0FDRixDQUFDO1lBQ0YsSUFBSSxDQUFDLGFBQWEsSUFBSSxhQUFhLENBQUMsTUFBTSxLQUFLLFNBQVMsRUFBRTtnQkFDeEQsTUFBTSxJQUFJLEtBQUssQ0FDYixvQ0FBb0MsZ0JBQWdCLENBQUMsaUJBQWlCLGdCQUFnQixDQUN2RixDQUFDO2FBQ0g7WUFFRCxNQUFNLGVBQWUsR0FBRyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsa0JBQWtCLENBQzdELGdCQUFnQixDQUFDLGVBQWUsQ0FDakMsQ0FBQztZQUVGLCtCQUErQjtZQUMvQixNQUFNLEtBQUssR0FBRyxDQUFBLGVBQWUsYUFBZixlQUFlLHVCQUFmLGVBQWUsQ0FBRSxJQUFJLEtBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBRWhGLDBDQUEwQztZQUMxQyxNQUFNLG1CQUFtQixHQUFHLElBQUksSUFBSSxFQUFFLENBQUM7WUFDdkMsbUJBQW1CLENBQUMsV0FBVyxDQUFDLG1CQUFtQixDQUFDLFdBQVcsRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDO1lBQ3hFLE1BQU0sY0FBYyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQy9CLG1CQUFtQixDQUFDLE9BQU8sRUFBRSxHQUFHLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQyxDQUM5QyxDQUFDO1lBQ0YsTUFBTSxTQUFTLEdBQW9CO2dCQUNqQyxJQUFJLEVBQUUsZ0JBQWdCLENBQUMsU0FBUztnQkFDaEMsSUFBSSxFQUFFO29CQUNKLE9BQU8sRUFBRSxnQkFBZ0IsQ0FBQyxPQUFPO29CQUNqQyxZQUFZLEVBQUUsZ0JBQWdCLENBQUMsWUFBWTtpQkFDNUM7YUFDRixDQUFBO1lBRUQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxlQUFlLENBQUMsU0FBUyxDQUFDLENBQUM7WUFDN0QsTUFBTSxlQUFlLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUMsZ0JBQWdCLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDbkgsTUFBTSxPQUFPLEdBQXVDO2dCQUNsRCxhQUFhLEVBQUUsTUFBQSxXQUFXLENBQUMsSUFBSSwwQ0FBRSxPQUFPO2dCQUN4QyxlQUFlLEVBQUUsZ0JBQWdCLENBQUMsZUFBZTtnQkFDakQsZUFBZSxFQUFFLE1BQUEsYUFBYSxhQUFiLGFBQWEsdUJBQWIsYUFBYSxDQUFFLElBQUksMENBQUUsT0FBTztnQkFDN0MsaUJBQWlCLEVBQUUsZ0JBQWdCLENBQUMsaUJBQWlCO2dCQUNyRCxLQUFLLEVBQUUsS0FBSztnQkFDWixtQkFBbUIsRUFBRSxjQUFjO2dCQUNuQyxlQUFlLEVBQUUsZUFBZTtnQkFDaEMsS0FBSyxFQUFFLE9BQU87Z0JBQ2QsZ0JBQWdCLEVBQUUsZ0JBQWdCLENBQUMsZ0JBQWdCO2FBQ3BELENBQUM7WUFFRixNQUFNLGNBQWMsR0FBRyxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsbUNBQW1DLENBQUMsT0FBTyxDQUFDLENBQUM7WUFFNUYsSUFBSSxDQUFDLGNBQWMsRUFBRTtnQkFDbkIsTUFBTSxJQUFJLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO2FBQ3RDO1lBRUQsTUFBTSxjQUFjLEdBQThCO2dCQUNoRCxhQUFhLEVBQUUsTUFBQSxXQUFXLENBQUMsSUFBSSwwQ0FBRSxPQUFPO2dCQUN4QyxlQUFlLEVBQUUsZ0JBQWdCLENBQUMsZUFBZTtnQkFDakQsZUFBZSxFQUFFLE1BQUEsYUFBYSxhQUFiLGFBQWEsdUJBQWIsYUFBYSxDQUFFLElBQUksMENBQUUsT0FBTztnQkFDN0MsaUJBQWlCLEVBQUUsZ0JBQWdCLENBQUMsaUJBQWlCO2dCQUNyRCxLQUFLLEVBQUUsS0FBSztnQkFDWixtQkFBbUIsRUFBRSxjQUFjO2dCQUNuQyxTQUFTLEVBQUUsY0FBYztnQkFDekIsZUFBZSxFQUFFLGVBQWU7Z0JBQ2hDLEtBQUssRUFBRSxPQUFPO2FBQ2YsQ0FBQztZQUNGLE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FBQywyQkFBMkIsQ0FDbkUsY0FBYyxDQUNmLENBQUM7WUFFRixJQUFJLENBQUEsUUFBUSxhQUFSLFFBQVEsdUJBQVIsUUFBUSxDQUFFLE1BQU0sTUFBSyxTQUFTLEVBQUU7Z0JBQ2xDLHdCQUF3QixHQUFHLFFBQVEsYUFBUixRQUFRLHVCQUFSLFFBQVEsQ0FBRSxJQUFJLENBQUM7YUFDM0M7aUJBQU07Z0JBQ0wsTUFBTSxJQUFJLEtBQUssQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO2FBQ3ZDO1NBQ0Y7UUFBQyxPQUFPLEdBQUcsRUFBRTtZQUNaLE9BQU8sQ0FBQyxHQUFHLENBQUMsV0FBVyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1NBQy9CO1FBQ0QsT0FBTyx3QkFBd0IsQ0FBQztJQUNsQyxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNJLEtBQUssQ0FBQyx3QkFBd0IsQ0FBQyxRQUFnQjtRQUVwRCxJQUFJLENBQUMsUUFBUSxFQUFFO1lBQ2IsTUFBTSxJQUFJLEtBQUssQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDO1NBQzFDO1FBRUQsSUFBSSxRQUFRLENBQUM7UUFDYixJQUFJO1lBQ0YsUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLGtCQUFrQixDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsQ0FBQztTQUM5RDtRQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ1gsT0FBTyxDQUFDLEdBQUcsQ0FBQywyQkFBMkIsRUFBRSxFQUFFLENBQUMsQ0FBQztTQUM5QztRQUNELE9BQU8sUUFBUSxDQUFDO0lBQ2xCLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSSxLQUFLLENBQUMsaUJBQWlCLENBQzVCLGdCQUF1QyxFQUN2QyxPQUFxQjtRQUVyQixJQUFJLFFBQVEsQ0FBQztRQUNiLElBQUk7WUFDRixRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsa0JBQWtCLENBQUMsVUFBVSxDQUNqRCxnQkFBZ0IsYUFBaEIsZ0JBQWdCLHVCQUFoQixnQkFBZ0IsQ0FBRSxRQUFRLEVBQzFCLGdCQUFnQixhQUFoQixnQkFBZ0IsdUJBQWhCLGdCQUFnQixDQUFFLFNBQVMsRUFDM0IsT0FBTyxDQUNSLENBQUM7WUFDRixPQUFPLENBQUMsR0FBRyxDQUFDLGtDQUFrQyxJQUFJLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQztTQUMzRTtRQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ1gsT0FBTyxDQUFDLEdBQUcsQ0FDVCxxQ0FBcUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUNoRSxDQUFDO1lBQ0YsTUFBTSxJQUFJLEtBQUssQ0FDYix1Q0FBdUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUM1RCxDQUFDO1NBQ0g7UUFFRCxPQUFPLFFBQVEsQ0FBQztJQUNsQixDQUFDO0lBRU0sS0FBSyxDQUFDLGNBQWMsQ0FBQyxPQUE0QjtRQUN0RCxJQUFJLENBQUMsT0FBTyxDQUFDLFVBQVUsRUFBRTtZQUN2QixNQUFNLElBQUksS0FBSyxDQUFDLDJCQUEyQixDQUFDLENBQUM7U0FDOUM7UUFFRCxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsRUFBRTtZQUNyQixNQUFNLElBQUksS0FBSyxDQUFDLDRCQUE0QixDQUFDLENBQUM7U0FDL0M7UUFFRCxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sRUFBRTtZQUNwQixNQUFNLElBQUksS0FBSyxDQUFDLHNCQUFzQixDQUFDLENBQUM7U0FDekM7UUFFRCxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRTtZQUNsQixNQUFNLElBQUksS0FBSyxDQUFDLG9CQUFvQixDQUFDLENBQUM7U0FDdkM7UUFFRCxJQUFJLG9CQUFvQixDQUFDO1FBRXpCLElBQUk7WUFDRixNQUFNLGNBQWMsR0FBRyxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsdUNBQXVDLENBQ3BGLE9BQU8sQ0FBQyxPQUFPLEVBQ2YsT0FBTyxDQUFDLEtBQUssRUFDYixPQUFPLENBQUMsVUFBVSxDQUNuQixDQUFDO1lBRUYsSUFBSSxDQUFDLGNBQWMsRUFBRTtnQkFDbkIsTUFBTSxJQUFJLEtBQUssQ0FBQyxrQ0FBa0MsQ0FBQyxDQUFDO2FBQ3JEO2lCQUFNO2dCQUNMLE1BQU0sY0FBYyxHQUEwQjtvQkFDNUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxPQUFPO29CQUN4QixLQUFLLEVBQUUsT0FBTyxDQUFDLEtBQUs7b0JBQ3BCLFFBQVEsRUFBRSxPQUFPLENBQUMsUUFBUTtvQkFDMUIsU0FBUyxFQUFFLGNBQWM7aUJBQzFCLENBQUE7Z0JBRUQsTUFBTSxzQkFBc0IsR0FBRyxNQUFNLElBQUksQ0FBQyxhQUFhLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxDQUFDO2dCQUV2RixJQUFJLENBQUEsc0JBQXNCLGFBQXRCLHNCQUFzQix1QkFBdEIsc0JBQXNCLENBQUUsTUFBTSxNQUFLLFNBQVMsRUFBRTtvQkFDaEQsb0JBQW9CLEdBQUcsc0JBQXNCLGFBQXRCLHNCQUFzQix1QkFBdEIsc0JBQXNCLENBQUUsSUFBSSxDQUFDO2lCQUNyRDtxQkFBTTtvQkFDTCxNQUFNLElBQUksS0FBSyxDQUFDLHlCQUF5QixDQUFDLENBQUM7aUJBQzVDO2FBQ0Y7U0FDRjtRQUFDLE9BQU8sR0FBRyxFQUFFO1lBQ1osTUFBTSxJQUFJLEtBQUssQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDO1NBQzVDO1FBRUQsT0FBTyxvQkFBb0IsQ0FBQztJQUM5QixDQUFDO0lBRU0sS0FBSyxDQUFDLGFBQWEsQ0FDeEIsZ0JBQXNDLEVBQ3RDLE9BQXFCO1FBRXJCLElBQUksUUFBUSxDQUFDO1FBRWIsSUFBSTtZQUNGLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxhQUFhLENBQ3BELGdCQUFnQixhQUFoQixnQkFBZ0IsdUJBQWhCLGdCQUFnQixDQUFFLFFBQVEsRUFDMUIsZ0JBQWdCLGFBQWhCLGdCQUFnQix1QkFBaEIsZ0JBQWdCLENBQUUsU0FBUyxFQUMzQixnQkFBZ0IsYUFBaEIsZ0JBQWdCLHVCQUFoQixnQkFBZ0IsQ0FBRSxPQUFPLEVBQ3pCLE9BQU8sQ0FDUixDQUFDO1lBQ0YsT0FBTyxDQUFDLEdBQUcsQ0FBQyxvQ0FBb0MsUUFBUSxFQUFFLENBQUMsQ0FBQztTQUM3RDtRQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ1gsT0FBTyxDQUFDLEdBQUcsQ0FDVCx5Q0FBeUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUNwRSxDQUFDO1lBQ0YsTUFBTSxJQUFJLEtBQUssQ0FDYiwyQ0FBMkMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUNoRSxDQUFDO1NBQ0g7UUFFRCxPQUFPLFFBQVEsQ0FBQztJQUNsQixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSSxLQUFLLENBQUMsZUFBZSxDQUMxQixnQkFBdUMsRUFDdkMsT0FBcUI7UUFFckIsSUFBSSxRQUFRLENBQUM7UUFDYixJQUFJO1lBQ0YsUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLGtCQUFrQixDQUFDLGVBQWUsQ0FDdEQsZ0JBQWdCLENBQUMsYUFBYSxFQUM5QixnQkFBZ0IsQ0FBQyxTQUFTLEVBQzFCLGdCQUFnQixhQUFoQixnQkFBZ0IsdUJBQWhCLGdCQUFnQixDQUFFLFdBQVcsRUFDN0IsT0FBTyxDQUNSLENBQUM7WUFDRixPQUFPLENBQUMsR0FBRyxDQUFDLGtDQUFrQyxJQUFJLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQztTQUMzRTtRQUFDLE9BQU8sRUFBRSxFQUFFO1lBQ1gsT0FBTyxDQUFDLEdBQUcsQ0FDVCxxQ0FBcUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUNoRSxDQUFDO1lBQ0YsTUFBTSxJQUFJLEtBQUssQ0FDYix1Q0FBdUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUM1RCxDQUFDO1NBQ0g7UUFFRCxPQUFPLFFBQVEsQ0FBQztJQUNsQixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSSxLQUFLLENBQUMsb0JBQW9CLENBQy9CLFFBQWdCLEVBQ2hCLE9BQWUsRUFDZixPQUFxQjtRQUVyQixPQUFPLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxvQkFBb0IsQ0FDakQsUUFBUSxFQUNSLE9BQU8sRUFDUCxPQUFPLENBQ1IsQ0FBQztJQUNKLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksS0FBSyxDQUFDLG1CQUFtQixDQUM5QixPQUFrQzs7UUFFbEMsSUFBSSxNQUFXLENBQUM7UUFDaEIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUU7WUFDZixNQUFNLElBQUksS0FBSyxDQUFDLGdCQUFnQixDQUFDLENBQUM7U0FDbkM7UUFDRCxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sRUFBRTtZQUNwQixNQUFNLElBQUksS0FBSyxDQUFDLHFCQUFxQixDQUFDLENBQUM7U0FDeEM7UUFDRCxJQUFJLENBQUMsT0FBTyxDQUFDLFlBQVksRUFBRTtZQUN6QixNQUFNLElBQUksS0FBSyxDQUFDLDJCQUEyQixDQUFDLENBQUM7U0FDOUM7UUFDRCxJQUFJLENBQUMsT0FBTyxDQUFDLGVBQWUsRUFBRTtZQUM1QixNQUFNLElBQUksS0FBSyxDQUFDLHNCQUFzQixDQUFDLENBQUM7U0FDekM7UUFDRCxJQUFJLENBQUMsT0FBTyxDQUFDLGlCQUFpQixFQUFFO1lBQzlCLE1BQU0sSUFBSSxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztTQUNwRDtRQUNELElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFO1lBQ3BCLE1BQU0sSUFBSSxLQUFLLENBQUMscUJBQXFCLENBQUMsQ0FBQztTQUN4QztRQUNELElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsZUFBZSxDQUFDLENBQUMsRUFBRTtZQUN0RCxNQUFNLElBQUksS0FBSyxDQUFDLDhCQUE4QixDQUFDLENBQUM7U0FDakQ7UUFDRCxJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDLEVBQUU7WUFDcEQsTUFBTSxJQUFJLEtBQUssQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO1NBQ3hDO1FBRUQsTUFBTSxjQUFjLEdBQW9DO1lBQ3RELE9BQU8sRUFBRSxPQUFPLENBQUMsT0FBTztZQUN4QixpQkFBaUIsRUFBRSxPQUFPLENBQUMsaUJBQWlCO1lBQzVDLFlBQVksRUFBRSxPQUFPLENBQUMsWUFBWTtZQUNsQyxRQUFRLEVBQUUsT0FBTyxDQUFDLGlCQUFpQjtTQUNwQyxDQUFDO1FBRUYsTUFBTSxtQkFBbUIsR0FBRyxNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsNkJBQTZCLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDOUYsTUFBTSxlQUFlLEdBQUcsTUFBQSxtQkFBbUIsYUFBbkIsbUJBQW1CLHVCQUFuQixtQkFBbUIsQ0FBRSxJQUFJLDBDQUFFLE9BQU8sQ0FBQztRQUUzRCxNQUFNLGVBQWUsR0FBRyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsa0JBQWtCLENBQzdELE9BQU8sQ0FBQyxlQUFlLENBQ3hCLENBQUM7UUFDRixNQUFNLFNBQVMsR0FBRyxDQUFBLGVBQWUsYUFBZixlQUFlLHVCQUFmLGVBQWUsQ0FBRSxJQUFJLEtBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEdBQUcsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3JGLDBEQUEwRDtRQUUxRCwwQ0FBMEM7UUFDMUMsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDO1FBQ3ZDLG1CQUFtQixDQUFDLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQyxXQUFXLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztRQUNwRSxNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUMvQixtQkFBbUIsQ0FBQyxPQUFPLEVBQUUsR0FBRyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsQ0FDOUMsQ0FBQztRQUVGLHFCQUFxQjtRQUNyQixNQUFNLE9BQU8sR0FBaUM7WUFDNUMsYUFBYSxFQUFFLE9BQU8sQ0FBQyxhQUFhO1lBQ3BDLGVBQWUsRUFBRSxPQUFPLENBQUMsZUFBZTtZQUN4QyxlQUFlLEVBQUUsZUFBZTtZQUNoQyxpQkFBaUIsRUFBRSxPQUFPLENBQUMsaUJBQWlCO1lBQzVDLEtBQUssRUFBRSxTQUFTO1lBQ2hCLG1CQUFtQixFQUFFLGNBQWM7WUFDbkMsZUFBZSxFQUFFLE9BQU8sQ0FBQyxlQUFlO1lBQ3hDLEtBQUssRUFBRSxPQUFPLENBQUMsT0FBTztZQUN0QixnQkFBZ0IsRUFBRSxPQUFPLENBQUMsaUJBQWlCO1NBQzVDLENBQUM7UUFFRixNQUFNLGNBQWMsR0FBRyxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsbUNBQW1DLENBQUMsT0FBTyxDQUFDLENBQUM7UUFFNUYsSUFBSSxDQUFDLGNBQWMsRUFBRTtZQUNuQixNQUFNLElBQUksS0FBSyxDQUFDLG1CQUFtQixDQUFDLENBQUM7U0FDdEM7UUFFRCxNQUFNLGNBQWMsR0FBa0M7WUFDcEQsRUFBRSxFQUFFLE9BQU8sQ0FBQyxFQUFFO1lBQ2QsYUFBYSxFQUFFLE9BQU8sQ0FBQyxhQUFhO1lBQ3BDLGVBQWUsRUFBRSxPQUFPLENBQUMsZUFBZTtZQUN4QyxlQUFlLEVBQUUsZUFBZTtZQUNoQyxpQkFBaUIsRUFBRSxPQUFPLENBQUMsaUJBQWlCO1lBQzVDLEtBQUssRUFBRSxPQUFPLENBQUMsT0FBTztZQUN0QixlQUFlLEVBQUUsT0FBTyxDQUFDLGVBQWU7WUFDeEMsS0FBSyxFQUFFLFNBQVM7WUFDaEIsbUJBQW1CLEVBQUUsY0FBYztZQUNuQyxTQUFTLEVBQUUsY0FBYztTQUMxQixDQUFDO1FBRUYsSUFBSTtZQUNGLE1BQU0sV0FBVyxHQUFHLE1BQU0sSUFBSSxDQUFDLG9CQUFvQixDQUFDLDBCQUEwQixDQUM1RSxjQUFjLENBQ2YsQ0FBQztZQUNGLElBQUksQ0FBQSxXQUFXLGFBQVgsV0FBVyx1QkFBWCxXQUFXLENBQUUsTUFBTSxNQUFLLFNBQVMsRUFBRTtnQkFDckMsTUFBTSxHQUFHLFdBQVcsYUFBWCxXQUFXLHVCQUFYLFdBQVcsQ0FBRSxJQUFJLENBQUM7YUFDNUI7aUJBQU07Z0JBQ0wsTUFBTSxJQUFJLEtBQUssQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO2FBQ2pEO1NBQ0Y7UUFBQyxPQUFPLEtBQUssRUFBRTtZQUNkLE1BQU0sSUFBSSxLQUFLLENBQ2IsMkNBQTJDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FDbkUsQ0FBQztTQUNIO1FBQ0QsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNJLEtBQUssQ0FBQyxtQkFBbUIsQ0FDOUIsT0FBa0M7O1FBRWxDLElBQUksTUFBVyxDQUFDO1FBRWhCLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFO1lBQ3BCLE1BQU0sSUFBSSxLQUFLLENBQUMsc0JBQXNCLENBQUMsQ0FBQztTQUN6QztRQUVELElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFO1lBQ3JCLE1BQU0sSUFBSSxLQUFLLENBQUMsc0JBQXNCLENBQUMsQ0FBQztTQUN6QztRQUVELElBQUksQ0FBQyxPQUFPLENBQUMsZUFBZSxFQUFFO1lBQzVCLE1BQU0sSUFBSSxLQUFLLENBQUMsOEJBQThCLENBQUMsQ0FBQztTQUNqRDtRQUNELElBQUk7WUFDRixNQUFNLGlCQUFpQixHQUFHLE1BQU0sSUFBSSxDQUFDLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBQztnQkFDakUsUUFBUSxFQUFFLE9BQU8sQ0FBQyxRQUFRO2dCQUMxQixPQUFPLEVBQUUsT0FBTyxDQUFDLE9BQU87YUFDekIsQ0FBQyxDQUFDO1lBRUgsSUFBSSxDQUFBLGlCQUFpQixhQUFqQixpQkFBaUIsdUJBQWpCLGlCQUFpQixDQUFFLElBQUksS0FBSSxDQUFBLGlCQUFpQixhQUFqQixpQkFBaUIsdUJBQWpCLGlCQUFpQixDQUFFLE1BQU0sTUFBSyxTQUFTLEVBQUU7Z0JBQ3RFLE1BQU0sdUJBQXVCLEdBQWtDO29CQUM3RCxPQUFPLEVBQUUsTUFBQSxpQkFBaUIsQ0FBQyxJQUFJLDBDQUFFLE9BQU87b0JBQ3hDLFFBQVEsRUFBRSxPQUFPLENBQUMsUUFBUTtvQkFDMUIsT0FBTyxFQUFFLE1BQUEsaUJBQWlCLENBQUMsSUFBSSwwQ0FBRSxPQUFPO29CQUN4QyxlQUFlLEVBQUUsT0FBTyxDQUFDLGVBQWU7aUJBQ3pDLENBQUM7Z0JBQ0YsTUFBTSxtQkFBbUIsR0FBRyxNQUFNLFlBQVksQ0FBQyxJQUFJLENBQUMsb0JBQW9CLENBQUMsMEJBQTBCLENBQUMsdUJBQXVCLENBQUMsRUFBRSxhQUFhLEVBQUUsY0FBYyxDQUFDLENBQUM7Z0JBQzdKLElBQUksQ0FBQSxtQkFBbUIsYUFBbkIsbUJBQW1CLHVCQUFuQixtQkFBbUIsQ0FBRSxNQUFNLE1BQUssU0FBUyxFQUFFO29CQUM3QyxNQUFNLEdBQUcsbUJBQW1CLGFBQW5CLG1CQUFtQix1QkFBbkIsbUJBQW1CLENBQUUsSUFBSSxDQUFDO2lCQUNwQztxQkFBTTtvQkFDTCxNQUFNLElBQUksS0FBSyxDQUFDLDJCQUEyQixDQUFDLENBQUM7aUJBQzlDO2FBQ0Y7U0FDRjtRQUFDLE9BQU8sS0FBSyxFQUFFO1lBQ2QsTUFBTSxJQUFJLEtBQUssQ0FDYiwyQ0FBMkMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUNuRSxDQUFDO1NBQ0g7UUFDRCxPQUFPLE1BQU0sQ0FBQztJQUNoQixDQUFDO0NBQ0YifQ==