UNPKG

myria-core-sdk

Version:

Latest version SDK

510 lines (507 loc) 46.8 kB
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==