From aeca912797bab2c817109df83ff14c7e991776cd Mon Sep 17 00:00:00 2001 From: owlsua Date: Fri, 28 Aug 2026 15:27:54 +0200 Subject: [PATCH 01/23] Build ML transactions from the wallet UTXO set Replace the SDK sugar methods (transfer, transferNft, delegationCreate, delegationStake, delegationWithdraw, fillOrder) with direct buildTransaction calls that pass opts.withUTXO, since only buildTransaction accepts the option. The SDK otherwise refetches spendable UTXOs from the indexer on every build and knows nothing about the UTXOs the wallet already filtered out. Passing our own list also drops one request per fee recalculation. Order filling is extracted into a useFillOrder hook, shared by OrderDetails and ManualSwap. --- .../composed/ManualSwap/ManualSwap.js | 7 +-- .../Orders/OrderDetails/OrderDetails.js | 6 ++- .../Orders/OrderDetails/OrderDetails.test.js | 12 +++-- src/hooks/UseFillOrder/useFillOrder.js | 43 +++++++++++++++ src/hooks/index.js | 2 + .../CreateDelegation/CreateDelegation.js | 24 +++++++-- src/pages/DelegationStake/DelegationStake.js | 19 +++++-- .../DelegationWithdraw/DelegationWithdraw.js | 25 +++++++-- src/pages/NftSend/NftSend.js | 53 +++++++++++++++---- .../SendMlTransaction/SendMlTransaction.js | 38 ++++++++----- 10 files changed, 184 insertions(+), 45 deletions(-) create mode 100644 src/hooks/UseFillOrder/useFillOrder.js diff --git a/src/components/composed/ManualSwap/ManualSwap.js b/src/components/composed/ManualSwap/ManualSwap.js index 63141b84..491ba08f 100644 --- a/src/components/composed/ManualSwap/ManualSwap.js +++ b/src/components/composed/ManualSwap/ManualSwap.js @@ -4,13 +4,14 @@ import { Loading, TextField } from '@ComposedComponents' import { Button, Error } from '@BasicComponents' import { CenteredLayout, VerticalGroup } from '@LayoutComponents' import { ML as MlHelpers } from '@Helpers' -import { AccountContext, SettingsContext, MintlayerContext } from '@Contexts' +import { useFillOrder } from '@Hooks' +import { AccountContext, SettingsContext } from '@Contexts' import './ManualSwap.css' const ManualSwap = () => { const { balanceLoading } = useContext(AccountContext) - const { client } = useContext(MintlayerContext) + const fillOrder = useFillOrder() const { networkType } = useContext(SettingsContext) const [orderId, setOrderId] = useState('') const [orderIdValidity, setOrderIdValidity] = useState(false) @@ -54,7 +55,7 @@ const ManualSwap = () => { try { setLoading(true) if (isFormValid) { - await client.fillOrder({ + await fillOrder({ order_id: orderId, amount, destination: destinationAddress, diff --git a/src/components/containers/Wallet/Orders/OrderDetails/OrderDetails.js b/src/components/containers/Wallet/Orders/OrderDetails/OrderDetails.js index 3451a268..4b3d3733 100644 --- a/src/components/containers/Wallet/Orders/OrderDetails/OrderDetails.js +++ b/src/components/containers/Wallet/Orders/OrderDetails/OrderDetails.js @@ -6,6 +6,7 @@ import { CryptoFiatField } from '@ComposedComponents' import { ReactComponent as IconArrowTopRight } from '@Assets/images/icon-swap.svg' import { ReactComponent as ArrowIcon } from '@Assets/images/icon-arrow-down.svg' import { ML } from '@Helpers' +import { useFillOrder } from '@Hooks' import { MintlayerContext } from '@Contexts' @@ -89,8 +90,9 @@ const SwapInfoContent = ({ order, from }) => { } const OrderDetails = ({ order }) => { - const { client, unusedAddresses, balance, tokenBalances } = + const { unusedAddresses, balance, tokenBalances } = useContext(MintlayerContext) + const fillOrder = useFillOrder() const [txErrorMessage, setTxErrorMessage] = useState(null) const [loading, setLoading] = useState(false) const [amount, setAmount] = useState('') @@ -135,7 +137,7 @@ const OrderDetails = ({ order }) => { try { setLoading(true) if (order) { - await client.fillOrder({ + await fillOrder({ order_id: order.order_id, amount, destination: unusedAddresses.receive, diff --git a/src/components/containers/Wallet/Orders/OrderDetails/OrderDetails.test.js b/src/components/containers/Wallet/Orders/OrderDetails/OrderDetails.test.js index 2bde1986..0c365650 100644 --- a/src/components/containers/Wallet/Orders/OrderDetails/OrderDetails.test.js +++ b/src/components/containers/Wallet/Orders/OrderDetails/OrderDetails.test.js @@ -10,6 +10,13 @@ import { } from '@Contexts' import { ML } from '@Helpers' +const mockFillOrder = jest.fn() + +jest.mock('@Hooks', () => ({ + ...jest.requireActual('@Hooks'), + useFillOrder: () => mockFillOrder, +})) + describe('OrderDetailsItem', () => { it('renders with title and content', () => { const title = 'Test Title' @@ -296,9 +303,6 @@ const mockCoinOrder = { } const mockMintlayerContext = { - client: { - fillOrder: jest.fn(), - }, unusedAddresses: { receive: 'testnet_addr1', }, @@ -389,7 +393,7 @@ describe('OrderDetails', () => { fireEvent.click(swapButton) await waitFor(() => { - expect(mockMintlayerContext.client.fillOrder).toHaveBeenCalledWith({ + expect(mockFillOrder).toHaveBeenCalledWith({ order_id: 'order123456789', amount: '50', destination: 'testnet_addr1', diff --git a/src/hooks/UseFillOrder/useFillOrder.js b/src/hooks/UseFillOrder/useFillOrder.js new file mode 100644 index 00000000..33482895 --- /dev/null +++ b/src/hooks/UseFillOrder/useFillOrder.js @@ -0,0 +1,43 @@ +import { useCallback, useContext } from 'react' + +import { MintlayerContext } from '@Contexts' +import { Mintlayer } from '@APIs' + +const useFillOrder = () => { + const { client, utxos } = useContext(MintlayerContext) + + const fillOrder = useCallback( + async ({ order_id, amount, destination }) => { + const order_details = JSON.parse(await Mintlayer.getOrderById(order_id)) + + const [ask_token_details, give_token_details] = await Promise.all([ + order_details.ask_currency.type === 'Coin' + ? null + : Mintlayer.getTokenById(order_details.ask_currency.token_id), + order_details.give_currency.type === 'Coin' + ? null + : Mintlayer.getTokenById(order_details.give_currency.token_id), + ]) + + const transaction = await client.buildTransaction({ + type: 'FillOrder', + params: { + order_id, + amount, + destination, + order_details, + ask_token_details, + give_token_details, + }, + ...(utxos.length ? { opts: { withUTXO: utxos } } : {}), + }) + + return client.signTransaction(transaction) + }, + [client, utxos], + ) + + return fillOrder +} + +export default useFillOrder diff --git a/src/hooks/index.js b/src/hooks/index.js index 43f14763..6304ed8c 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -5,6 +5,7 @@ import useMlWalletInfo from './UseWalletInfo/useMlWalletInfo' import useExchangeRates from './UseExchangeRates/useExchangeRates' import useOneDayAgoExchangeRates from './UseOneDayAgoExchangeRates/useOneDayAgoExchangeRates' import useMediaQuery from './useMediaQuery/useMediaQuery' +import useFillOrder from './UseFillOrder/useFillOrder' export { useStyleClasses, @@ -14,4 +15,5 @@ export { useExchangeRates, useOneDayAgoExchangeRates, useMediaQuery, + useFillOrder, } diff --git a/src/pages/CreateDelegation/CreateDelegation.js b/src/pages/CreateDelegation/CreateDelegation.js index 6befe6a7..a4e51ea3 100644 --- a/src/pages/CreateDelegation/CreateDelegation.js +++ b/src/pages/CreateDelegation/CreateDelegation.js @@ -1,4 +1,4 @@ -import { useEffect, useContext, useState } from 'react' +import { useCallback, useEffect, useContext, useState } from 'react' import { useLocation, useNavigate } from 'react-router' import { SendMlTransaction } from '@ContainerComponents' @@ -55,13 +55,23 @@ const CreateDelegationPage = () => { const loading = preEnterAddress && (fetchingBalances || fetchingUtxos) + const buildDelegationTransaction = useCallback( + ({ pool_id, destination }) => + client.buildTransaction({ + type: 'CreateDelegationId', + params: { pool_id, destination }, + ...(utxos.length ? { opts: { withUTXO: utxos } } : {}), + }), + [client, utxos], + ) + useEffect(() => { const buildTransaction = async () => { if (transaction_conditions && transactionInformation?.to.length > 0) { setFeeLoading(true) try { const unusedReceivingAddress = unusedAddresses.receive - const transaction = await client.buildDelegationCreate({ + const transaction = await buildDelegationTransaction({ pool_id: transactionInformation.to, destination: unusedReceivingAddress, }) @@ -74,7 +84,12 @@ const CreateDelegationPage = () => { } } buildTransaction() - }, [transaction_conditions, transactionInformation, client, unusedAddresses]) + }, [ + transaction_conditions, + transactionInformation, + buildDelegationTransaction, + unusedAddresses, + ]) if (!accountID) { console.log('No account id.') @@ -89,10 +104,11 @@ const CreateDelegationPage = () => { const confirmMlTransaction = async () => { const unusedReceivingAddress = unusedAddresses.receive - const result = await client.delegationCreate({ + const transaction = await buildDelegationTransaction({ pool_id: transactionInformation.to, destination: unusedReceivingAddress, }) + const result = await client.signTransaction(transaction) if (result) { await fetchDelegations() diff --git a/src/pages/DelegationStake/DelegationStake.js b/src/pages/DelegationStake/DelegationStake.js index 6ffc06a5..b3d1cbcb 100644 --- a/src/pages/DelegationStake/DelegationStake.js +++ b/src/pages/DelegationStake/DelegationStake.js @@ -1,4 +1,4 @@ -import { useEffect, useContext, useState } from 'react' +import { useCallback, useEffect, useContext, useState } from 'react' import { useNavigate, useParams } from 'react-router' import { SendMlTransaction } from '@ContainerComponents' @@ -57,6 +57,16 @@ const DelegationStakePage = () => { const loading = fetchingBalances || fetchingUtxos + const buildStakeTransaction = useCallback( + ({ amount, delegation_id }) => + client.buildTransaction({ + type: 'DelegateStaking', + params: { delegation_id, amount }, + ...(utxos.length ? { opts: { withUTXO: utxos } } : {}), + }), + [client, utxos], + ) + useEffect(() => { const buildTransaction = async () => { if ( @@ -66,7 +76,7 @@ const DelegationStakePage = () => { ) { setFeeLoading(true) try { - const transaction = await client.buildDelegationStake({ + const transaction = await buildStakeTransaction({ amount: transactionInformation.amount, delegation_id: transactionInformation.to, }) @@ -82,7 +92,7 @@ const DelegationStakePage = () => { }, [ transaction_conditions, transactionInformation, - client, + buildStakeTransaction, unusedAddresses, delegationId, ]) @@ -98,10 +108,11 @@ const DelegationStakePage = () => { } const confirmMlTransaction = async () => { - const result = await client.delegationStake({ + const transaction = await buildStakeTransaction({ amount: transactionInformation.amount, delegation_id: transactionInformation.to, }) + const result = await client.signTransaction(transaction) return result } diff --git a/src/pages/DelegationWithdraw/DelegationWithdraw.js b/src/pages/DelegationWithdraw/DelegationWithdraw.js index c1ed8251..5cad4b34 100644 --- a/src/pages/DelegationWithdraw/DelegationWithdraw.js +++ b/src/pages/DelegationWithdraw/DelegationWithdraw.js @@ -1,4 +1,4 @@ -import { useEffect, useContext, useState } from 'react' +import { useCallback, useEffect, useContext, useState } from 'react' import { useNavigate, useParams } from 'react-router' import { SendMlTransaction } from '@ContainerComponents' @@ -6,6 +6,7 @@ import { VerticalGroup } from '@LayoutComponents' import { useExchangeRates, useMlWalletInfo } from '@Hooks' import { AccountContext, MintlayerContext, TransactionContext } from '@Contexts' import { AppInfo } from '@Constants' +import { Mintlayer } from '@APIs' import './DelegationWithdraw.css' import { Error, PageWrapper } from '@BasicComponents' @@ -60,6 +61,21 @@ const DelegationWithdrawPage = () => { const loading = fetchingBalances || fetchingUtxos + const buildWithdrawTransaction = useCallback( + async ({ amount, delegation_id }) => { + const [delegation_details] = await Mintlayer.getDelegationDetails([ + delegation_id, + ]) + + return client.buildTransaction({ + type: 'DelegationWithdraw', + params: { delegation_id, amount, delegation_details }, + ...(utxos.length ? { opts: { withUTXO: utxos } } : {}), + }) + }, + [client, utxos], + ) + useEffect(() => { const buildTransaction = async () => { if ( @@ -68,7 +84,7 @@ const DelegationWithdrawPage = () => { transactionInformation?.amount > 0 ) { setFeeLoading(true) - const transaction = await client.buildDelegationWithdraw({ + const transaction = await buildWithdrawTransaction({ amount: transactionInformation.amount, delegation_id: transactionInformation.to, }) @@ -80,7 +96,7 @@ const DelegationWithdrawPage = () => { }, [ transaction_conditions, transactionInformation, - client, + buildWithdrawTransaction, unusedAddresses, delegationId, ]) @@ -96,10 +112,11 @@ const DelegationWithdrawPage = () => { } const confirmMlTransaction = async () => { - const result = await client.delegationWithdraw({ + const transaction = await buildWithdrawTransaction({ amount: transactionInformation.amount, delegation_id: transactionInformation.to, }) + const result = await client.signTransaction(transaction) return result } diff --git a/src/pages/NftSend/NftSend.js b/src/pages/NftSend/NftSend.js index 812e815c..55c81a87 100644 --- a/src/pages/NftSend/NftSend.js +++ b/src/pages/NftSend/NftSend.js @@ -1,4 +1,4 @@ -import { useContext, useState, useEffect, useMemo } from 'react' +import { useCallback, useContext, useState, useEffect, useMemo } from 'react' import { useNavigate, useParams } from 'react-router' import { SendMlTransaction } from '@ContainerComponents' @@ -24,7 +24,8 @@ const NftSendPage = () => { [coinType, tokenId], ) - const { client } = useContext(MintlayerContext) + const { client, utxos, nftInitialUtxos, nftData } = + useContext(MintlayerContext) const currentMlAddresses = addresses.mlAddresses const [totalFeeCrypto, setTotalFeeCrypto] = useState(0) const [feeLoading, setFeeLoading] = useState(false) @@ -60,20 +61,52 @@ const NftSendPage = () => { const [transactionInformation, setTransactionInformation] = useState(null) const { exchangeRate } = useExchangeRates(tokenName, fiatName) + const nftUtxos = useMemo(() => { + const outpointKey = ({ outpoint }) => + `${outpoint.source_id}:${outpoint.index}` + const selectedKeys = new Set(utxos.map(outpointKey)) + return [ + ...utxos, + ...nftInitialUtxos.filter((utxo) => !selectedKeys.has(outpointKey(utxo))), + ] + }, [utxos, nftInitialUtxos]) + + const nftDetails = useMemo(() => { + const nft = nftData.find((item) => item.token_id === tokenId) + return nft ? { ...nft.data, number_of_decimals: 0 } : null + }, [nftData, tokenId]) + + const buildNftTransaction = useCallback( + ({ to }) => { + if (!nftDetails || !nftUtxos.length) { + return client.buildTransferNft({ to, token_id: tokenId }) + } + + return client.buildTransaction({ + type: 'Transfer', + params: { + to, + amount: 1, + token_id: tokenId, + token_details: nftDetails, + }, + opts: { withUTXO: nftUtxos }, + }) + }, + [client, tokenId, nftDetails, nftUtxos], + ) + useEffect(() => { const buildTransaction = async () => { if (transactionInformation?.to.length > 0) { setFeeLoading(true) - const transaction = await client.buildTransferNft({ - to: transactionInformation.to, - token_id: tokenId, - }) + const transaction = await buildNftTransaction(transactionInformation) setTotalFeeCrypto(transaction.JSONRepresentation.fee.decimal) setFeeLoading(false) } } buildTransaction() - }, [transactionInformation, client, walletType, tokenId]) + }, [transactionInformation, buildNftTransaction]) if (!accountID) { console.log('No account id.') @@ -86,10 +119,8 @@ const NftSendPage = () => { } const confirmMlTransaction = async () => { - const result = await client.transferNft({ - to: transactionInformation.to, - token_id: tokenId, - }) + const transaction = await buildNftTransaction(transactionInformation) + const result = await client.signTransaction(transaction) return result } diff --git a/src/pages/SendMlTransaction/SendMlTransaction.js b/src/pages/SendMlTransaction/SendMlTransaction.js index 5449a516..4b504503 100644 --- a/src/pages/SendMlTransaction/SendMlTransaction.js +++ b/src/pages/SendMlTransaction/SendMlTransaction.js @@ -1,4 +1,4 @@ -import { useContext, useState, useEffect, useMemo } from 'react' +import { useCallback, useContext, useState, useEffect, useMemo } from 'react' import { useNavigate, useParams } from 'react-router' import { SendMlTransaction } from '@ContainerComponents' @@ -28,7 +28,7 @@ const SendMlTransactionPage = () => { ) const datahook = useMlWalletInfo - const { client } = useContext(MintlayerContext) + const { client, utxos } = useContext(MintlayerContext) const currentMlAddresses = addresses.mlAddresses const [totalFeeCrypto, setTotalFeeCrypto] = useState(0) const [feeLoading, setFeeLoading] = useState(false) @@ -61,6 +61,25 @@ const SendMlTransactionPage = () => { const [transactionInformation, setTransactionInformation] = useState(null) const { exchangeRate } = useExchangeRates(tokenName, fiatName) + const buildMlTransaction = useCallback( + ({ to, amount }) => { + if (walletType?.tokenId) { + return client.buildTransfer({ + to, + amount, + token_id: walletType.tokenId, + }) + } + + return client.buildTransaction({ + type: 'Transfer', + params: { to, amount }, + ...(utxos?.length ? { opts: { withUTXO: utxos } } : {}), + }) + }, + [client, walletType, utxos], + ) + useEffect(() => { if ( !isFormValid || @@ -77,11 +96,7 @@ const SendMlTransactionPage = () => { const timer = setTimeout(async () => { try { - const transaction = await client.buildTransfer({ - to: transactionInformation.to, - amount: transactionInformation.amount, - token_id: walletType?.tokenId, - }) + const transaction = await buildMlTransaction(transactionInformation) if (cancelled) return setTotalFeeCrypto(transaction.JSONRepresentation.fee.decimal) } catch (error) { @@ -101,7 +116,7 @@ const SendMlTransactionPage = () => { cancelled = true clearTimeout(timer) } - }, [transactionInformation, client, walletType, isFormValid]) + }, [transactionInformation, buildMlTransaction, isFormValid]) if (!accountID) { console.log('No account id.') @@ -114,11 +129,8 @@ const SendMlTransactionPage = () => { } const confirmMlTransaction = async () => { - const result = await client.transfer({ - to: transactionInformation.to, - amount: transactionInformation.amount, - token_id: walletType?.tokenId, - }) + const transaction = await buildMlTransaction(transactionInformation) + const result = await client.signTransaction(transaction) return result } From 5a6785d994e4ea5c3205db98d76e48d9d7416083 Mon Sep 17 00:00:00 2001 From: owlsua Date: Fri, 28 Aug 2026 15:28:24 +0200 Subject: [PATCH 02/23] Record spent outpoints for unconfirmed transactions MintlayerProvider already filters out UTXOs listed in usedUtxosOutpoints of pending transactions, but only the legacy MLTransaction helpers ever wrote that field. Transactions signed through the SDK path left it undefined, so the filter was a no-op and a second transaction sent before the first one confirmed reused its inputs, which the node rejects as an orphan. --- src/pages/SignInternalTransaction/SignInternalTransaction.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/SignInternalTransaction/SignInternalTransaction.js b/src/pages/SignInternalTransaction/SignInternalTransaction.js index 0b54fb65..efea7c98 100644 --- a/src/pages/SignInternalTransaction/SignInternalTransaction.js +++ b/src/pages/SignInternalTransaction/SignInternalTransaction.js @@ -207,6 +207,9 @@ export const SignTransactionPage = () => { mode: txPreviewInfo.action || 'transfer', poolId: '', delegationId: '', + usedUtxosOutpoints: transactionJSONrepresentation.inputs + .filter(({ input }) => input.input_type === 'UTXO') + .map(({ input: { index, source_id } }) => ({ index, source_id })), }) LocalStorageService.setItem( unconfirmedTransactionString, From 13412735b15ef56f75c1e25b038019df935bb724 Mon Sep 17 00:00:00 2001 From: owlsua Date: Fri, 28 Aug 2026 15:28:51 +0200 Subject: [PATCH 03/23] Allow withdrawing the full delegation balance The withdraw branch only returned early when the amount exceeded the balance, then fell through to the shared check that adds the network fee to the amount. Withdrawing the whole balance always failed there with "Insufficient funds". Nothing is paid from the wallet on a delegation withdrawal: the only input is the delegation account input, so the fee comes out of the withdrawn amount itself. --- .../containers/SendTransaction/SendMlTransaction.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/containers/SendTransaction/SendMlTransaction.js b/src/components/containers/SendTransaction/SendMlTransaction.js index deb7488a..41a1c061 100644 --- a/src/components/containers/SendTransaction/SendMlTransaction.js +++ b/src/components/containers/SendTransaction/SendMlTransaction.js @@ -128,6 +128,13 @@ const SendMlTransaction = ({ setPassErrorMessage('Insufficient delegation balance') return } + if (!validity || amountDecimal.lte(0)) { + setAmountValidity(false) + return + } + setAmountValidity(true) + setPassErrorMessage('') + return } if (!validity || amountDecimal.lte(0)) { setAmountValidity(false) From 1d10a023882bb001992a344c7ccdc767c0eff766 Mon Sep 17 00:00:00 2001 From: owlsua Date: Fri, 28 Aug 2026 15:29:07 +0200 Subject: [PATCH 04/23] Stop clipping the round skeleton loader In the compact variant the compact size was applied only to the inner circle, so the wrapper kept its default 72px and did not fit the 76px card. The overflow was cut and the circle landed on the rounded corner. --- src/components/basic/SkeletonLoader/SkeletonLoader.css | 1 + src/components/basic/SkeletonLoader/SkeletonLoader.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/basic/SkeletonLoader/SkeletonLoader.css b/src/components/basic/SkeletonLoader/SkeletonLoader.css index d65ebfb9..614fdf1f 100644 --- a/src/components/basic/SkeletonLoader/SkeletonLoader.css +++ b/src/components/basic/SkeletonLoader/SkeletonLoader.css @@ -1,5 +1,6 @@ .card { display: flex; + align-items: center; padding: 12px 30px 12px 18px; margin-bottom: 0.75rem; background-color: rgb(var(--color-gray)); diff --git a/src/components/basic/SkeletonLoader/SkeletonLoader.js b/src/components/basic/SkeletonLoader/SkeletonLoader.js index fd2b7f53..af38be29 100644 --- a/src/components/basic/SkeletonLoader/SkeletonLoader.js +++ b/src/components/basic/SkeletonLoader/SkeletonLoader.js @@ -25,7 +25,7 @@ const SkeletonLoader = ({ variant = 'default' }) => { data-testid="card" >
Date: Fri, 28 Aug 2026 15:29:24 +0200 Subject: [PATCH 05/23] Give both onboarding buttons the same 52px height The declared height never applied: the base button sets min-height: max-content, which won over it, so each button was sized by its content and the outlined one was taller by its border. Dropping the vertical padding keeps the intrinsic height below the fixed one. --- src/pages/CreateRestore/CreateRestore.module.css | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/CreateRestore/CreateRestore.module.css b/src/pages/CreateRestore/CreateRestore.module.css index 82b4ca4a..5324fdeb 100644 --- a/src/pages/CreateRestore/CreateRestore.module.css +++ b/src/pages/CreateRestore/CreateRestore.module.css @@ -97,14 +97,16 @@ .createButton { width: 100%; - padding: var(--space-lg) var(--space-2xl); - height: 46px; + padding: 0 var(--space-2xl); + height: 52px; + min-height: 52px; font-size: var(--font-size-lg); } .restoreButton { width: 100%; - padding: var(--space-lg) var(--space-2xl); - height: 46px; + padding: 0 var(--space-2xl); + height: 52px; + min-height: 52px; font-size: var(--font-size-lg); } From 0f7cc73dd76ee220e5a1aedfbeaeef0afccf983e Mon Sep 17 00:00:00 2001 From: owlsua Date: Fri, 28 Aug 2026 15:30:13 +0200 Subject: [PATCH 06/23] Keep the wallet unlocked when the rates API is down A failing exchange rate request logged the account out and sent it back to the start screen, even with the Mintlayer nodes reachable. Only the chain tip request decides now; the rate is logged and ignored. --- src/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 32c14bde..b5e01e56 100644 --- a/src/index.js +++ b/src/index.js @@ -113,7 +113,12 @@ const App = () => { const isConnectionAvailable = async (accountUnlocked) => { try { const mintlayerResponse = await Mintlayer.getChainTip() - const exchangeResponse = await ExchangeRates.getRate('ml', 'usd') + const exchangeResponse = await ExchangeRates.getRate('ml', 'usd').catch( + (error) => { + console.error('Exchange rates unavailable:', error) + return null + }, + ) return !!mintlayerResponse && !!exchangeResponse } catch (error) { if (accountUnlocked) { From bed6d8dff3ea2a3f665f896f7ca3779991513656 Mon Sep 17 00:00:00 2001 From: owlsua Date: Fri, 28 Aug 2026 15:30:36 +0200 Subject: [PATCH 07/23] Convert BTC public keys to hex without Buffer Addresses are persisted as JSON and read back on session restore, which turns the Uint8Array pubkey into a plain object that Buffer.from rejects. Use the same toHexString helper the Mintlayer keys already go through; it handles both shapes. --- src/pages/ConnectionPage/ConnectionPage.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/ConnectionPage/ConnectionPage.js b/src/pages/ConnectionPage/ConnectionPage.js index 358c2aed..06bc02f2 100644 --- a/src/pages/ConnectionPage/ConnectionPage.js +++ b/src/pages/ConnectionPage/ConnectionPage.js @@ -78,11 +78,10 @@ export const ConnectionPage = () => { ), publicKeys: { receiving: addresses?.btcAddresses?.btcReceivingAddresses.map( - (addr) => - Buffer.from(Object.values(addr)[0].pubkey).toString('hex'), + (addr) => toHexString(Object.values(addr)[0].pubkey), ), change: addresses?.btcAddresses?.btcChangeAddresses.map((addr) => - Buffer.from(Object.values(addr)[0].pubkey).toString('hex'), + toHexString(Object.values(addr)[0].pubkey), ), }, }, From 535e5e6cddd1e4c96fc809d2147c310e9856d6ca Mon Sep 17 00:00:00 2001 From: owlsua Date: Fri, 28 Aug 2026 15:32:02 +0200 Subject: [PATCH 08/23] Ship Montserrat with the extension The font stylesheet was pulled from Google Fonts by a render-blocking link in the HTML shell, so nothing was painted until that request came back. On a cold cache, or a slow network, that is a blank screen for as long as the request takes: a 6s stall moved first contentful paint from 0.4s to 6.4s. Montserrat now ships as two subsetted variable woff2 files, one upright and one italic, 116KB together and covering every weight in use. The remote links are gone and the CSP no longer allows external styles or fonts. --- public/index.html | 18 ---- public/manifestDefault.json | 2 +- public/manifestFirefox.json | 2 +- src/assets/fonts/OFL.txt | 93 ++++++++++++++++++ .../fonts/montserrat-italic-latin.woff2 | Bin 0 -> 59972 bytes src/assets/fonts/montserrat-latin.woff2 | Bin 0 -> 56232 bytes src/assets/styles/fonts.css | 23 +++++ src/index.js | 1 + 8 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 src/assets/fonts/OFL.txt create mode 100644 src/assets/fonts/montserrat-italic-latin.woff2 create mode 100644 src/assets/fonts/montserrat-latin.woff2 create mode 100644 src/assets/styles/fonts.css diff --git a/public/index.html b/public/index.html index 6896c69c..38b31363 100644 --- a/public/index.html +++ b/public/index.html @@ -10,24 +10,6 @@ rel="icon" href="favicon.ico" /> - - - - <{900000000000000000000 z0000QiXa<>=3*SBSU*TsK~juTKTTFaQbq<~KT}jeRDm=nG601}FMbgS3W4xYfx%-i zgOq#$HUcCAkz@;qbN~b(1&kC2AX{|NawXb^_X3gCoNhzlbk^0bIxP(+kaItnDxGIk ze`RBWtYGYJ0NptxNxYg{max6OA1((r8ZUw1|;Eb;`ES5r_G(I&X$eOknKe+>1?6l z24+;*7+b(usO)XoX72BuqebmwrbMdTO=FEQVi9u}_IS{hS2E=42IbNDlH7|7Nm2!J zJ-q8l$u7NOACLFiTTWv~f?Xy;4_}xSF2zT4DG)1UxH%e7VZg5U%Izg5P$6XH2c$|g zBoc|t0mB>b^1ZwY?e@C`Bbii71c|l0bkW7<+!R_e~VCsB{sj)gjN@RFra;@JcCmPf^|^b;kATZqglsX;2IgzCuL8=b z0tyYpq)o=uPV?rra`gP}b-C@HR8C`Rz5Np>LHU1V&)xHWl5Co$W=RQ}F`;&W5B#6j zpNv#06_SRyLU6;TbW%+_D0enb{#-zO%gaB&G3>7rc$gGDg>sqEIX?X-H(Vp^CGiEEw@z@y2>9}E5J zfQQ)D2jA7EpQiMN6fPCfsLmq04y%fho1`S%8|M&;IW_o?ANzLCnc4NUo}vSZ^dsUS z6C$s7#8Sc&bKl%&VP1AKv>RxW17cxI@aY&;%fg**s~9wpF>g{C&%w!gN&{Li-Et1bI%*|JCB2)0|kRB4^*511-!f#LtZwSPON0^3yJ z1n2~)5}uN?S_H832lUrN=w7$b5z=4K=&bVOt9D5V$8@mwD(>pas)jZadq3_rKqpvwLRwDUW*jGyEvvsm)J+RLd^SW`hD1a6zc$Wix;7mbCUM zz3oqF4#glGp7BYjO}3)(bc>S+SE@x=gq8^9Ao_mqH+^1{-v6Y#m+X=-W<#9+6y&#C z_zglK47Cc0@L*gt0_hP(;}k-Bq(dnD|4j2OMH6gtI5Ng(TI2F>j0v(0V&GME-9cA3 zngR*>zoSE~aR#qmSG5I5FYiM|>wFq3+%y3R86M7h*;M*Txla!(+xZE@r$F zV#Yr;W+F3VCb4Wd0H@hacMxD=OCxs*YM8D;tP zEI94?J%ND%k34{mt{{UaPk|C;HkNQPrOc(}N@QgJHO(t8*A^rlU1gM7QFY=lVz1ll z{z5u;I07}`)U7jd%K&l&Vx~&`fXf7x#EY6>W6U#}KQ$XJ(sS_BrG< zSNWD7`JF-R&U0V`4kXaP1<4uoWXOn7qn0nJ8?FIZy|d=A?bfE`Bm^pD>*K>V0a0_pI%@6$0kAn`QFbOMs#v$zE5XU&dDb8?%TRhMV z&1peq1R#q#!^14#J4yz%8fcIU{x=T8EId)xvJy&4! zRoHhGwqAqjYcY2nj^2v-TXF3+oVgPTfL6vR+*Nx9;=td(Vkr&$!wbh}k@v*JguQ!YVH6XZTSgvj)-7n0Z@J1^uHf)bKYzISB*Az1>dcj>EQAmsI(SdoSkc1lxvvd{>g*Ol{u!5hz zQl?9c-L_(v)RLVp<%qgiYN;yA%44Od)FZd4w3l^dFbpdT<0?X6irZFH$*v+Qsg+u( z%(AL#pnMj|9Z(+*xW<}N7ikXw%?c8J&>jt>IQbQ=Ts<(>Q|js;poGbY6GU3@rb?zv zGGo0WpzOM{A;I0`Nm|&c{g_-z&6>#M`Y6^j%>=zNh>@lr2`5c%1Qna>jR}2`DVDE* z1HXq!{sfTTuhxT`tZVHG;4@`MS&We3II$mOpW1cHA+=Hhf5rCL9aChdp<5_P$EY$9 zfKCz{+b^6nPQPC`7IZ9RC5|6T4n0W5rM>pLPH9ub*duU#9^vy|(^qfU?(1L3+j>?@h z^|rLti!P;9(9po+?Y_^7>j5C9HKP^tu_Ecuf#vOSBg#pc&+Do;WXXSVYWJjUd9WIC zbZe_B=od$WdBF6;ZBMciKp&U`0K3Y>xhvj~e!~&K449lQ7+-ZhA^XHJ5pB2o zSCGvA(p4#5DO6M@_en`s;&CwX9zbbe$_=cg?m2<_XuJf5;`FZ*)FolXF%B;aEOqtT z4&__5G8a4-xV7pdtUvoz)EpL~5)GRRMVvZ$H1!&Y^{ncXFtd&-6G$|km0=Lh5aRbv*x(d? za9Qxj&cJ+{7njPmFdneO!iclf(c7}st{L5 zFx!GUa}`mMO69aYJ%7ILDO?bRQnHrcm1!ldoYWo1>FUlx}w<;^mDk*B3+ zLi%ko*0o|)(uzA1n8VlI~h~Wcom4Q%J2p8tG{<}%mtjGR~~J0NM?L_@2dCOJ=i6cR3hPa#%?q>XqXdO0e{Rcq28$Ef zKkN`0RjnIXWZQ@tEss}?p_suj!`K)uY=%-E9aFXt^#Lk(?o5)K(l?7e-N$!sCBQxkmQc{9!4DW>rKrX z_>6x`a?{=`wWHn>j+m>JcYg=}ppH3Db6H1&&%ag&>;a)-Zl6^iNT_Cg_D^}4mq z9J@4bvK45b7I}UYYUD+5^fgdzE09#)Fw1N!$AJ7{RWgS?(;c~fANg>7Z2yy%hgO|f z)z3mZ@kQR*z?XNuG!LNqAzEUkfZY7IV9mzl4lK{~&exz-&6wO)hJOAb4nt6U^Jq?& zUy!g99?sI$UBb;i$fP=HCw-(Ui7&1rNOV)?A*G2`k|YP_vE~$I;&~gYYD9f(O+?@L z5}3Wg=Wu)C^L>4M(S6#@)^H~1RxBwc+R+L+1AT`8M~*KQx(ae;Z^`W%w>;#`%*B{W zi1MUf&=*>4|RJ<$-HNo0!c9HCAWqPjmg!4Ys?gKUlqw9a?Smm_Co*YBimR z_1)5# z)^_Ta{;efmyZyM68jBw^uCu~gDiUkshs>xsWnunWW^sf+%Bd01c07Jx>sd0qSU>=y zpFM3SxVj-fo?BXWDDR3UocO(`y1#-OQL9Vh}Vrd z%V9!sEh%`s)QnVE)Gl{Y-SggU2IZCYksR#a)y zdLul>2p;?W8v)EVuPG0lV7OB3YesPV@Bg}^ntu;&`59_>G1vMjtkpG(F%J3;0qtCA z3K`&t_S`~6nnd>EybUwy;Gf&zJ3ZZ@MN`=_z%6Y75Dg6D)!JB4VPaVXNdn%NfF1gd zl$yjdvOVcL5Ow#i&ULqO>CnoiY^SLuQKG~Zt;%i$zsP3`wz&20%#p+=ejdV$-02)@ zgMV1&Ck1@R3a>T7%IgcFviup6O-ykCAmvD|IJzuBXI~2ER)!4Dpxjk>#S{BB?iSK{ zp!glHy0EL9McpE9RuFbzXcY_az>f|O5`iowD`MCH{7ZfqJPXFx00D?4@j%M~FdrV1 zmRSPTML0^cnSkyCX`mtF;8HCIto>lSOy17o;)x~7!Wl!gs6X-K{D4YD?W$UCqzF2Z;eZ_U_^%xD&YD}6ij2i5hkXC0m z7NLpJ@*4QquWpzAaooJx9v0}Y1fg7ixrl)rhf`uFjZqKW(WFS5d}Z+MX>kt+-9{nFJH1Se!nDN2`kM4iepvXYngm27 zG)urr8Kcibb@j9mT8_x98Q^k&D*&znxEkPEfa?IR2e=7f7T~o2Zv^-(z~=zI0HAtW z4Hf7EFlC&WHPqA^%#0X8YgOBb$n&oNyX_Sa9ch+q$C@SEzXfD1vK(ZLKFPD30lDT{Y`I(>D=d-GzpxE%lp;1} zS4Ms{Z#cCS%Gwg1r&FDNJ6~_u_tP3Q?8sEkq~`4}T5m8^15z&b#1J62{NF>uOq{Y9dTB*%VVvGs8@?%r?hd^USxvBJDbKT4|NlZnMUE2OMw3Fe1!P$! z6{$dof#E#gpq{$q6t2rule1J4fDbcej>Ai*pzkZmHkooz>=J=@q6ckB3q(~~o}5hJ z4m1IFHX57oE^AO|FcvDu=no<5sEyqdLxldkxIdhL)Wr&_Y>5{!4F8 zW}LS1pF%-S3xFXIRT5>bJSrlPq$nnw5>+`6%MZH%kd<&YcL4L`tPpe=7 z`~8)I8@#NN7uA>Viq=A$qs(o{A29p{jFCXln3zbQ&2AH6 zMQpSIzz!bb)+J_pk8dYhia9#hZxmktsZ1VA+_Zo@$? zdDsJed8LR!eV)_Zg8OnJH?VBcD5SF+`XfCiSL%e9@(~%#$v&Y{0Ufsrz0RhcoKlc@ zcWd{w10PpE4pn80Z8Xi3jd(AOk$p@gUXM1YYklVqg&Uig=4JpT=y7`>o7j*2$#}?U zX)d!)AXQRVdSR@CNP_cZ?gt?h7*d8A7gPtU*r}si*M{G6o>B9=2tt~>oww(_sQQlT z8t7GZjAbL3DL>;>T9k62$>rwo_H;{ZJSr+G(Miuu%pxS{jcsBBBUk~b31EJUgRKsD zvZabj)i{eb4IkVo5C#8mC#}YuI`rqNZ)VyV^89QS!5+>G!|6xwI*8Ricy9>f8^_d* zvpt)v!GNHY4*)Cl3LMGFWJQukc5~CSV+yJgwl{iqay-3q_d2XSM05m0CaBdrsh&|y zEA^~}b?Xd+rhdWYkrwYCSB1VVi==8}iq}=BNhSJ92Kgcil4=(*4JjePP+~BpVW(ab z?IO)(BXF9j%}iL6KpKyW1 z9Ybk>WIF6K*GklW-&3#s`$g#j^qr+8JzOc5C~Q~c z3&0eg(s4n2K!?crt_%XOGPOU4#J^W3SEWFJ7dyqWUP2XBiY`e_o6QAif4;WxGUk-& zLYWQ5((dlO)D!#1CWc7KT!%oT=q!-IgPN9DBWi*RkoN+rSH-&o(|M$zyNu8xeXz?% z^R&R08Tu9&(Pb)xd0R55?1haGSWX;6S+LSuW^Qzpts@B=*`cfDlWv{O&g1*=sgjH2ZMV#&UusrULV1s(xagUH#a%+r5TtTw4LC(BD@as&A{l zmg4$u75pKpZ~Vs1xuF@?p#aqqVxSt@5o$9;kITcK_)34Tw+(t*x)n#LO{^IgY09Q2^g%O z-PK5nWg(;1+zMCT3~?PzX($>m8F&Dt&drP-TB@p#*obVvuB+rq1oI&bix!{}txD#b zE7i6o+^JrH!!Bab4937I$W)B!&FmQdvV958i7B2 z><1!Q3#UFyO1$c3KbtaLBGJjbQ~|^J@0c_@!{;dsy(w|eI-+`eG4C_&aiPDPIL|9 zQ7e1^+tD<>UZ&(s09{(t(ALszk&zqlsE_3~atAe>4zSX?r1CwwmDD&UF%><=LtC2> z=>>c=I0ik?kmw<72RD-&y**XYzeZ1jHOLzp{~Ie7`6if3e-FmbH(1dDrV&jQ-~U@a z*}A-6*+BBwRS3n%`@LFkV_`fNOxw6{6cS7_g{5+ackZ|X=maym*kcggNL)Z(IM+<2 zep^c3Loust+0{)o*k$}VCB{WX(v-ckQB*C`to; zt&inS754bnG##0#bjwZ)1U7EBwnsh`ST~MV{vh~E?SE1~ECcRI|lU^i<;{I257|3k<)m4(h z$JH5koLb9NLDRIhur6jCQXQ#lJrIniQ%0lMF5hU;CGIc|*v)=w%$uvz_K-lRL^bA zl&YD50nnvfwzCnaC4&c0ZE*Nn4HFl=!)*;kxCS=F(C5p$j046l+V`oLojVR7ME3pm z?SOlW5t5h6`S!}s6^yk^Tz1|2L)=hS$=Vrr@-sGNt4`1Knq7wfRlVH69bc1dq%os$ zE#KI}i}9)>5o-_*_-OT$HGHo2680|qIlN{+cZgwPM>{gsK=Vt`OCu1^*&yi}yZ4Xz#g14bMtrxE?gB9@2-%r_Wuut-q7~*)t{3 z1>D%kGTJRtUji58j@d?VjB90>vqk}IZw1BOC)s>!A2uZZyNa~s7gyCNWJGepNAY#j z8=t_6?1_{<9x0Gzxtz~-NRN|2R6zwnK+Aq`T5&DQ)J4YlTyAHm?-`*>gy&Byie3K^ zXd6{sA@a16?^I9kvLjiGLh`=T5@e}tcA#*5beUZ;*jKTd8GI!Qqw%kWwlpmt=H6b-j%T4N!sW% zZE}t_yP$4y31h37U}9e4F*%4V%wOp;(veOO9Rq<#q<>vpLNk(L{@kxg5nGte?_B+8J{uK4Mf0g==b~JnaZv|NMHDF4I2C#*&A=I;DD;T9Hdy^84EjJG7 z@-*2KC{Zd+LLquo1bH(>#iSHdFgqz0L$+wEXsakwv{jTT$`oaaPKq{*Hj7S*Hj6fk z4vP9k2SsIq)db`S$PrMryfUbSc}-;oB3uaa&h{)+rIKUZ7G)y{2aGsM zRZ+f^Lj=H6c94w%7Yvk8mOy-Q9UgA*eqAWkCY|COmie#@Rx={EceN0QugDMVon@r* z$r3(HC=)Tn5KLQHE+9pj3dg)}j6|jfyk@n$lKiA}OKFM7*{w2f)Y&0_BJNjd;=cNa zCSV)aY1YW83eR*BZZS1lG;yiM(@er+#~Xn1f%1(e6{40In??X^HQrm)U^E}mfmCBS z)Tu{n;*gSdbHNfgNuXtWBa47j{5xnrH*GM?OxfH_Wr{Uur8|foW~M%RlI)5xluyI# za%Zuc9~FQd^_CPcOu3Y}I&r{709yrI1`u=uu$g}6XR|Z+=MKUK%t)y-34mDus#gMb zB4u#O6g9L3MQzIpWF^s9Wy* z0+K0#>Y6>la*2?dG{97XDJcxbPRrM*4?2_<3xgbn-NiR(IwWQ34NqESGfVkaYS*%4 zRZJ?|MPf-EdUF1T*F0K66SfPNIp8sDn74GdZo9G<^>A=$0v$q5#sRMa`HfGRfMG@=SY4!aM%q1zb)Lph<9P@P~ zR8`MR)I<$CVacoo%#TYIEC-AA6bN1nBu*0Mjnqh}>H){a;OIip?XGV)rq-5YV3{Cn-%nm z_3KsYzw->OH*=Co_$vl2W)09clt20C>*^N=Oc+SK#6URu2pMsy4WB47s_t7f{D4<(NmYmm^@-4J7#J21jhHQ;14L9!k$o*lDwX_m2?&8|h$9 zS!l6CuNh@Hl1q7_b>r$yHgQ&MPAzbh62-S9o{9QER1#&hMIMcWg_lw!wjQSF6{K9v zA+i)yuuWxEBiED;(Fgwz1#42=qjk&MWgv+cNBlM@s31dr!k=3x7y>{ve&y@`5ql+- zxFB_elT>#ko@tZ=hOesuZ>nILO2)+qmhjoW(dEVZgQzSjirKTw=+2{~gdFcIeLhe( z5`#nOY0x2w%Kdn{NvP_UqqJTc`7`uLR~lMAfN91t@ny>L*OGss|@hiHZU{bKdqK&K@+?ur+LGD zKz6$l0}sA8!e$aKuZkt740CEvZt#O6F8*@yRt=PjDiRKNwnmkgN2Zkp#f*AwCRZQQ zLaHhsJLg*b!!Wz8D&Ey%Nj7MH!fs-C#c|^ z|27Wpue5vTn+?mw;Bc|6&Arz!RsBEY3|6jMH0~_ih;9aRuD{cNP!dAWfWSiOKp|*A z*tBlt2)>dPE~pZf3qsLRJJyDaDa8b_`d^JM&*mFWD20Dz$MbA0NRFK#AxUDH;BHcRo)Ch%!RUUO(4nmyDI>Y;vkkqyk`Ea3Uaq*y9wbI|T`jJdrO=9KwJAzoBK#xJE2vb zN~>y}HpQtqezLvN!69z3-%iOb3Hw9#R>b$0ysaf;I|_nRK+>coPet~releunZ(Lhj zqA$Dkx+Lx1hnXKFhuKdHLBK(K&+UG=wPkifc0ou@7UKLl_baA{hpZZfNh$YQ#k%l8 za{Mc%h!_}&sDWX}FYvn>yaW37Uk!lU`Tx}9nTZz|!d>9Glu!%zX+LiS*RFO36Mx!Z z^Z~wBwbMSi%m~geZC5To*--JNK5t;B!}m96elr@MUg?9u6#i>2FKFF+(DC3SSIPT9 z)j`m@O!XVpPG|P@$C(5Rfc?k(a;D>r*5w!eA%t+N-GJ^@^Ha?{UtyprgDucMAbdm6 z%J98Z?Y$C+9ydoGxOLbZ?zfdaBqDjRich7e?FCxz^}0W~idOs!IkC02@bC4t)^(=A zDM~mK!LY^a#n$YaAWG&o_LIseSM!Y>-epzJf8qVg#;wIROn~6XKlQPczOc6_Y<(#$ zV*o5$4rmqYOomle|8p$2MK5;}vGA(#E&pa3?@9-$x>iCpQ(Mc%b$g78X{vAVZ^oRP z;J;hT(^NOrny;rKz?-}^!(W?U-rmXuRSW>-ZACJyvKNYRM2I1ezS3J8Ki+gyT7(Ve z7tfcU;JsS;^Lp_aV=iet_;=?%`2Wu%-#30eH$;KILdlF}GMkA=PAg!T7KCd;!uhMk zOThe8aoCeG(PZ=`=|AG^m9yj{vqLpOY$`vy%u&Pen5FPX@D;sT^+@pPXyy#DvZ)4h z+}oH)?)nu>m|0QEbvCL8vySs#6)xvV>vkgE-SV6%QO!M|fBsyfgtQ-R1dy5D9LW!$ zE^I?NLwbjG!K=mX<*L52cSCA6-@w&7?6**=YX3^XRc_et$`6vsAF$?|T)g5+5@{`? zlKYoBMzkdi0-6!a&Qt*41ujj6RasQUiS;)cTbMnDd5Yn=8icoQaja^vu)jcHqq{Mq zpc!6U2Jx2#cm_1w5xPNV_+Pc!1${t@ghIe7|EAJ8dV+S?^a5W!`QF6UQQEZKS?#p# ztc)yil1u>XiOc$!+!3{6<{2SC!2BI_i#rai6FEp+uu{(d9frrU74J=9o~CYSkUkuN*)#$qIazv0y|sqc;gcw&U62D zL;HZ*(jHc|E*l2|%Xnk>-@3Qi>@sBUplu%`87J^?)IgSoA=LH79Hd-6(n9%- z@0Q)Hhv~*l9gd|mQlps=N&V_e#inW1fjlq<-V*c@!WpZQ-Sn&h8Sru>SV2cCYaagO*ft)Yx0& zyFhwUymwz+{-~gVU!!q`BUi8eO`5m=fWHP*vfp&`y;4XI9Lon(P9qgAsJ)!UM8 zs`mdyLG}M13byc_xhWz^c7K{BtqOIbh{PSr^oa&05KDi2kmDmbd~L0Q@*duNp-073f9yuJUMb7sPr zguZ#_n5m=8xm(V`GnDfIGF=#oAr+BWL~bYJ$1!eh6sd}t zK2xR)0c+;ZN#9?XxYT4S{SoF8EIU;83}a%B`;)O}`^2M7)~33hCDmY=nel?e#KG>< zT>ec42Gqa{vl5yFKo2USW!Dqfz~*92gfI$8P>VZFv~QMy)hN+G(2JxzjWE}c{LPbX zPOJ9#%>~}L^5F~Nsn12B>VR5rVjiz7VP-MlSkzm5tmeJ&o8Au-^dPh_1V}l9LDVy} zpc3?OwYeoED?z7+W*;+5`G?d+28{rf6B0}&|IjlgVxAJzIEa#V!Es0<2@}03owbal zvD-3K>C9l)YWmTm6=d9}+F~fDvguYzB8{0vbbw-{Y^GE}kB$jgzE;Ly_f8<7!@8j= z8(mcYLN#bp&^tS`nyaVmA4njn1WM*(6`2R2VFZ5E)UhLi;~Ao`d_ zRM11~$Su=AM0e#Z)YD|+uUG3q2w1i6BQ2IRoF;w((e@RIEBST*Y%KJ~0Ovk*Kv_(g zP;(|**ivYTfyRo}X7uWVch>1@$G{i_i9B8<1IZWgE7u#*%@KJZy!7RuR1}-<0@`p) z*p~Hq6a_3%b(!$r4ke2wt=@UZEwl$$@b&!fj6g9k!8L=RWY8n8SUqQ3ZBd1KFU*8& z*ZNzo{h=XCByLq&AFgPn%Sd=H85E}4e>E{agE&~@QLdEO>mB7$lrddO1`#y~(o7N? z@RDt=93t)pU}wDpC#+y^M;N<=0^WxM)mpPKp-8e zH`fq}sw40x*%>R=sy5q;=K3zue>3UFA?Z!iDXL+1>G6&<9o&e14fjngA-w8b#!U}D z9{MS61=SzOY4G@l;%f|8i1wJiH(ajY&}=`V)6kL?x0IV8AE;KBPt|zgI=L`ul{GN} z%OwaL0ce5MslAD$%H8xWg?12=c#^A~0-x02#J*{8Lb*?Jfh)^o6CJe6(LGn#G)VT~ zc!gI_Vs5`^xxMdar4#hy1#C=qx?2KrTws+FbWa(UFXgBDwz-zbI@RLyvC6bXm}5af zGFO0A+4TL&4@^UQUCX)$^i6mpQ>Dv-CkgqD90Y&VIh&Gc4lv3f(5TTy=C0wpglqBC ziV%K~8!Y_@PfSF)hrgL3jjG=GRbS#8PA&@-8x=Bc!b!?wSL(m_?K8OeqW|)>y&+P8 zF@O+Co{hcmz^FUj7Ez6u4@A01#c(-VwhPiyhGsUje18a7Y#pgPWD*vA!)`FOCP|V8 zh^N!m>kLepy|OHVoO*A zRkE$fozAxKv{q(whmqEaDnaQbJKt3UxlR7YN;b9tNhJcAXMHam7b^Y+18fio8E`9g zf6|RT-jO0;kF-v=)Q4YSq@Yf{felzH*n{Trtrj+qMiAAprwjD(EfW9l+7d&%+Z^wR zsP^ndj6e{#ow{X^A;Z*pcqZ+K#S?|m6+QscYCn|yVqWUFhAwOkbEO;v>Piv*&G?t`L&pDTOyotmz_ zP(!}>E{Cd|AS8|uFY9Dj)_m;-#7{C>5K;Fbre*L=#=m{5$FPuRQFpkM)4YNY9yJvU za`jc{RV{#grUtTns7n=0+xQ-u9-QZ;2(FG<4j#jD)FG~-*>FnO?HdH75p(`W_sEAo$TXCL(Q zFupnio6EJ}&Js3WKHK}=>6k9LUYEqRmDS4Y8` zwWLm@UAls71 zuB?S<&ek)s3jj<&v%lc+e1Cq7xP)@)NQJo*UT(_6Bu8RO%)HwbsfpJH0Y6i>O!}hMWl(oPfexm?Zpe zH8(e28@Hy?S7FuxC7GS_t!g)#q6jIck_jZs?Y6|N4qI3?lc0U*Uh*U)#oH_P{?-@b z;x*&dX-;1km^u6kvR`CsfR_U%KLz$3HwOj!pv_y3%2aVTCtQ9ktHncC7UCECq${H( z58YVkvE9;UBAf3bnf)P@5J;n9=YFicry0AGxWCrhwdNEEa1cg>FG&4{=nRP22VV+G(3tY{~T=? zFFv8HWbpbEcr=ZBDa(77PyAo}HM)d{Ppg%mp4NV}5Pv-5pF|BUymDzS?;nS!=O#45 zq69~raR?u}d4Svm z!N|hI%rS+>cLioqL1_{ENgmK@AvUm|gv5=xMR$9~d!N5l^Q%*pu}-XB)JyAtI=Mbp ztLvs3X_hzcO+XXTh?`^0-bTf3lQlgJ-R^DU+kNd9ZE+iHhq_n${|($>&rm%&Vv(#N zChTe;-$9jpu&%=B+!oE`F)6Lo`lcE^(QS=@A+ksm8iU2*2_!jx_)BN^2!HTn;u4Zl z(k6_`fT1wBlN<#_1b>X*&>m1TFeGGcwcawb&w#!#{fj=3gbF?+F@wR$+@2^6 zGE-n`BuUQ*SeYHiFMyXLNszCARk?CD6)Gs2HOVwvvmA?b$+gsod@Y#f>3~{jEve;p zQd?`ceCyn=+IkOiI^sEDr@bcW0q=o4>La+vJtTVC4}Lew@BVa^KmF(GqO7j3QPc6a zow6hCq3LM*=sMOx#*WvexDzZkr}Lbhyua2?eY+=k`r%z=#s9odWIKO1So+q9AtSU+ z{E6$B)aE*2yXC*?ami=>z)${3MgdKz;HfN0c~xzF4bljW({-ApX3f=N-5?En>-~_7 znqDz8E4QFJPF_)I7@A1YuKi^ImtYLo#zd3sZkm~9n`^!W7FukDQ=RQX?{TRQ`Iu2l zY_!!i_8W0705TEeUKvAiNyILn zS*cS_JL9Z#b-tD5-pwg4+<%di`{%(8&dSEl!O6wVQ{GwlO8DE(udD~(SF^YYM|KkI zYSCkj69*|ywd%FjSrr*>wdu3YMKu(!V){+Ga+2d$y8%;fToeS=VbFSaZc4)HG-QJZ z50%R5GHjzKFEvqh8?nht4GnQ9w4u_4Mjtvu7>t3Lf-t9oB}~?^*uq8$2Q^%@@X*7@ zhyWZRB$X;6!i*Rz7ofQWYo8^;6LC5p2ywcJ_Dpd<>5>{rGD5^O+&@9o7Nkg+vJH&cx@NKY}%0|27*=`TL9qtxH zEoD23_R@Bu{S4)FL{UHWoIu|i%fc-g*VvX?%7S5{DggvQrbti=0VM{YhL9kU9myA6 zpNr7V?}+f$hbLUHWjY~C3>9%gB!Or~S`vV&6>Gm@Zq|~^`F+VR9^mDlfScN@ zAO|8gMcFQWMo?2sAC~r}l>R}&+Sfey;`UsUk;mcGYxXfH+`Kg2X!{3)2a^2XlKu9$Wtzi`S##z&*Uzu>^6?7@3ayw+qb6cl zj3~?(%##u;DzdNp$d={-5*thwTf;& zT;Gw)liwa@Z&NSi*;5X_ab?N@jiSBKWP7=z%4Qs zKLj*FSR3J67@h{!`KxVP#_enAcM)3koX0M@E?c)RFuv@d8td12pT-c$-D}+$-~nKh z1wSj-?7KI1-MXg%;rx*9>?V>i_lF%t`o%&}*$Fa$)q&0n^jQ!Bi~lE*W(n}pko#YG zY*}(fCW1>^d3j~cU-#Do2@s_$)tmG#x*VznMB_~I_lXF?)kF+W7?80>NW^f>5W(W_ z3*lZ3701^K5vuqjP`O5k1Y>Lqda%3_h|zuD55qW2aEfzW(1Nb|VXtH>}JMUy{vwAKc}BF$Zg;bih^L_Uc7{-D!|jF zV4Mf0cwh}5ObNi05NxOf8!N%4O0Zc3wur!15!faM(-M&TaX(aPzQjDV3jI}HmKz(yb2luE(TD-$iSk(MfDQJ5YnyMUX^7_`|NNKv?g z<1`NUVsTZ53K~+d$-OiKDlH3l%Cnl)mu7QoxBp5iRUioXtIGOZgUojvTRb z-rbYrhc?d|7!;~q6MjrqFaaJ#7EKMvf{ujPP^Fh46UahAy|Px>TR4&Y?uDIzm5+1K_!zw!j`YCZC0v zo#3W<d6>>Dr8JZfmeRRPN|4m-f%?3;64|sqbHJ>F23TvOM-at zmjT>_)D0CXLi%niRreoWSG#8H*^-(>G>Wdy`UeKoZ3v^6V?YRqh)KxE15+eV{u2Ev zU)U`ZBj9>kk3>ST9%}cvWT%&B#V@zl)iq)Z#+DAk&%Cr%j#=y#U)BuG(*E!WLx;r^ z7|6^lth_()iZ=?0$(aFP8lnSWEoF+8C{-nCll^K1H3+E})}&plHZcQw^y<@W0v8J# ziZc+brlOEbNlQp)ku|esd4J2n$*qHj(*ie_oPpm50VWWn&=Ut%M4g<-!{WJDy+)AF zJYj&tiM1ZhawMOB!ogNP1%-owl+QxpU@M=9yaOwri^9QSlut)qpRo24wa8;o?DO|F zJqPd#fF-vBSm!$cbRP!l_u4(E7tw^QWXTZ@y8Z|!0$Hm_=XKa+p@kB9MD0Qg=5^d8 z?%X!f6>N+G6C-y|Bn?&UQ%#MDoFUBiGIWCiIE*rU9v6i z&=!*jW1c__@;U99~jT4Vx%sbBwN!9cadNh^~@f>zl1 zy~HHQFYp}4xdovFVvPl8)F#$@<^*^(j(BK$lNI8gEVi+OtfP&s&8+m1n#-GaXj5!M z+9L2?qxklYcD8Mx zqC%?K_}Os_p{1oJHoVxM5g{@80fS>W8iH0g+*);6XFH=fq;(0;G_~2FsYl6kSKFh{ zqQ+KtyFG89xs|UG8dZBriw%SkQWicpf_PwmQ`aQEfV)?&K(ma`!X_I!kFR!2lHOix zy&qx>M0V6Z<28I)XGfYgr;t+Ui(J-9P8lw?1X&Z=O-p37(i%C?RFFCLOCM`tV|`#j zeRt620C11lH+wE1pD9`3a@QE*CVB0+gOA|V3 zvI3TVRGIK4jd-$y#v3YXupD2pT5N-GckiO>J8orvH&n4;17ICM-*yV&zZY~U!AiDm zPO*YzccS??;hNB8MpSf?Y3V;Dv(U@PDZ*v6r<9{*U;m#jcUZQWW`(_#brt!e=#xRNyFmp{67D+d39MpyPwoWe8|!pb2{ zehewBP>BY8)B+l-S`(Q3+|XMW+o%W9c1MRuxq?k%K0YDPhnrV1OGETP;KX*SO8FCA zmZ2iLXsxYK##^vFqwE)zCV7WN4KBSYO9jn)*DiJxgAvwc7pqEzELQ`6>~dcPE8e$E zP(-B#&7bNHcPHx53`B*m)Hf6}EDFa(A&Cq=&PoO0HMslAKsZG^m9DMbJXp z#l&P2KDF)1hHQL=g!t|#w$ehgL3~=H6!Bp}7{|V`+&n2DDJyca{H$}t#mz<4$}$WJ zCB%SL=7SX|3hIi3dc-Q+QV47jE8)@Q$vQxGtBYfa^=F0{m&u;A_(ayqY1Uf{U2;QP zv?NADZdT=z0P#88t>Q)0cK9e!gQTNkN!AYDZf=)vBn*ZeJVKIoTq!F1^2fsg>Gs-jbt!j5_o2xjx%rSkR&`Nht~Z96 ztVn1aDbQ6>&8BV&QRtjnAQB>#0XyTOAQsZHj&Mb4fc6)5JSXhUH2Y(fg@Y!)^@(}504p)e4GtE;fh$vEKD{$M}jEG@Dp`uKU z*3T=jZom~YRAm-@iAkFxPK+E8rP~3opF8CU?=9u{F@o4Pq9kZpRF$2CA}iKd=Yyd( zNv>|Vaa}t=_B=mx*jz_bAJsQXH&41y49-6M?deRG28rH5=Jv_XlwiV?rF6d|o{9|c z!EAhTaD4-b(1Fuh!VxdLL)G3TYvt^XQCD_}r4dS9`Q6?Zl zgjIoHNYVx|4Mrc`w)W;9kfgsN^6 z(3pQ3jTDa?zz8KuCpBK1Lnn#;fS)A1^Sq~4+^egs)uYkDsbR-5x&2*WIamB!xIDHc zw)r?>aHNVE@V2xPRqA=yS-814ko>&12%s=eYb9pZ^`5{Fl=v+}9_1ynG24-q zAO?HNv_km;CS(qWHw#MNpF8 zQ2P31=he*EjH%C{z}(dTp){GfoaAs8*WvP5@jcUKEH^S~Z{*9oG*ugVJDkj`AN&JX ztbJIxhIt@BOT&G#>b~z_*~pca0s`0+pG=FY>h7XY!VuM#l)~O=2CV_@;AD6+KiNG`k%8Y6J&=_4pfmUsu<|cB2m-)7{>F-%$$BFwETOj3+^R z`Gg%)jC@VPknsh!`zQb1h6%b0e7a_7X;>Pmm0SxO4>io<&^RD(s8X@=5wX?Rmqu7I zbQ<1~w^H+>zVh^|-&99Yk1|r9h0;z$riU&*RPl<}jlVJvT?`BmzsfI7 z^Kjz_fV7n3E*EpASp{3yE)`|TuG@=wRVvQDzYCXZX~|CfsGTJe^ZRnC6qh>77nzNA z?7yjK&_V~xa4R3b4dCoWIk(Y$n zc(Fm5k#_HjERV_uh%Kq>gBCX3)M-P>sWhFT@g~74-LQ+*;&H@HpB7ITDw|P7k(ERbpK3yYIWU_rv^%G{cVpz&0LTYcKB^SIglvO#M}ywS3j)ZzVZJ{&h~ z`&oBsL@UiB|97vA&ko=8L8JVb&S$~W$jyg58Xo%Q$ggn(HEX(?ZqzVMom1kz!&q!ix8B z`9>2+k=g|bF3b}QYvkz;k9L?*^UaW9L&m!bl1KhCys^c2g=P1pu5larHy2D+e`e)M zPCxs*(&Vq|zFZuT`&62`usm0E?&KyBs1)M{fjXP^dvF*JFdWaQ_Nzp}ZxY1OA=0XJ z$n$0XD3c@Q`i@)^MMt0w1%+dJS&^MtY9u-R5oXc(L#kcg^0LB++SqHj7EoRCfk$4X zQ%%-T^<+7zl94+OAPbdpmF>dw@|PfCdLFwfm*wyMGT}+knxn}^s+x-^dwMKm!N30> zFuK24LcJ!ZziHqDqFigun$)ewsr5BFU2FV0I?bH69oUbK<-hhvBtF0_eF`(Q(7`H^ zQg@B9PUD!r+jTZi0j4K0@e3U}RW4Uio)rtmMbG_e6$yEyH{pph;fO~xhkS%qtOl$d z%MeH3x?qqD(feyWA5hPmIY`5P5mw4lQxckMtt?yCv1;9$WsW4j9;DGq-bk!b{fm$+ z!#t{xG(U-c#rOVU`>kz%LF>QLy9FJ*d-sYJEa^9wjb#1EuRC=2E*GKQ-8lCYa&cnk z0y5-6CJ|uSIG5>bRw@-TZ;YxS`rwfKIwsZZ{9o z9zzGoz*ohnZ_ZpBgm=1-e+vRZa5uCu@Xu+>TyxXBBi<~b-@xr_r`Vk zTg*El%ih8rJGSQFRK>XydiQV(@odY1tNjSLi2^6PJeO!-aFkW1j8NdyrAS`=->Vc% zyRi(c`A@6hGt3|wPl+jt`-$yMS~0G+e)81eP`6EcUok!_%SiQWXU4AZR=&fH^*wCM zj2-V-cLV}mTplSJ-;35ZY*MO*9h4Yfx_g_r!FIbz)-3~H7|sK@k*x~#ur1Q}Jcb+@ zzW)4lt&`SoMwS-9C+v~0X(wEm%xp^Fl2K(51_w8lhZS*7=GWA38-_Eeo!zSKACjp#bU#_OS z{4Y{CsXCjgY9Usdc%*Szktx%ZBg3wzN>OJgRl|!G8J^S!FdRjcBrNAXAuBd#nX+Z2 zlT?|qI$jR`xAN3tqySkwV1Odg*{-~P6-o1&cR}VId25@u4fbxI%HL$(37K~l?tFAR zus%JPdHPJ!+?kwb=fKqDIVf-@spkyTa3;CqOxD00RDV>`bqqK(#**gknb)*y*oqv~ zw)CZ<%%=iUqYpe~YB3Mr$hy7|h~CT{c{9fN34jZx4lVtsgg{$=C~Fl0#3ObW?wr{+ z;qFk7*Q6WuHP*Fm9U7dDCwzviM!=8h{9iysf^lSAqFg|jvG1|<*oWUKKNf5}xp(*~ z@_78fw%iHxG_WO2q>Qbu5J}x}*iyjMV~J9V1oie+n8huH2QHlp}a|2O7g? z<4>xKZD|J+S8EqDmuASTfHG_S)@n@tus6Z8m{dDeWzz+CrJdT=dHD$=ny6))(|LT|a4=k56i9*16WF?&31e7;X0lW^x&AW5mj%K^tL&4J^~a9ntud3YcRA?- zJZx8*!*?CB_1sPM8;1)f-NzuW_oijdK>mp5B2;s^aB9m$S0L;KA({f{xErebqu~3~ z(?j-N;V-Mitux&R*5_|5KCY$4Z^`$fd;(EE9ta>+dCnOZa%b5r^6av9u{jY5Z*-a3 z!7dEEDr7(*W18C-2?Qsap$Hda$6G1W@4#6{v>;~Mc;!y!Z?5|t%KE$T`yJcJtt!p~ zs@d3A$TB1$E zdkvly2Isg|(xaS^Im79{f= zMoX$pXRE~n@s|6Ld50x0=r5%3!>|{?D6vCtZ=_4)wFs+5YS&7)-9S2%qZP!Hvd;F= zJ-SU3W{1>QzTv?h2VP+6_wrDQ*41(S#82mcok0sxrP}`926v=&2AaMP1bEmHcB(}! zt(9tBiy}G6q}$&!siVDK%^TVyKU?h(^tVY&8HxrDHzYGhYTRfYn-fpG-UptfCOi@V zNu+9kSw6W_wQe8OlE0QO0to&V1V-WB#$V8sG(TO%w(&UVQqkD3$ zT8me{`r=FS^nGah{`#x*KNlK6OgbMZ{%E<%$}TW!7utx=GZxAS2TYK=~itXw>nN1w=Zmd7Anjw>PL$2)J03mgDb9_`d4{{2No_YonA-v za1S|Q)t%_>y3Nn#$16b5L3xp;+vcfAT$1ST>&l9;R)MF9{6_rzx3BuKELS}$#9vcn z7*{K@ZkAIKnFm)%cjxc<;F>9?+!j&@@UyJ1H`(*$v2SvOoAORQuH{2iSQZ($CA<_y5#R=Sn4&om~)ED4XwVi1>qJuS1sEwJD?aWjOrc zx4_vytk|&w{NelIgeQNn))spX{?J@nTyA=X4}RTTy$J**P4B1wW`6j?{tv}fD?sUq ziTE@BBW5)~Z#VwF!TgecxF3V=zo`PNtJJ^beC3;6jXiaJeM;uoF90ui^^4* z?f%k5t-hPDs|wLs)%?1i?X@?1CY{GnUJvG)7O5QPpuSocx*M6pD+3&Qc(L*wH+;|a zD5_*v<%^ID-3tFFQR%?`u)ZE_ZKSc(uNkFztGZr(i0bOCE?LZB6`!H`8mvc_c6ihS5aa)nlRbfPhY$tSaL54!1yI4hG_4Gut+G1sygA*j0-)4ru{(@ta{n zqy6n|q6W$rtDZ|D@*^!xIRjZ;MP6+&>w9qvAa21x02ODxJO9*eLiR0t1Of96_-~Wq z%|)xVbhAWaP^Vhkc~IE>9NnP8G2qkbh7-|TXTlTCPRsKPVmk`oxb%i8pW$n3%#2A; zNTSS+_jTTXAW=Zv2|2jCg?zY0{>%XM5^OGb?)7WBJgRGOG1$uAKEuB$ls ziU}&8eTf1~meDeH(lRNhWHq7X4>KNXKUI6`woe9@yh_W$X5N3XWaQM5e@dvQ+GsJv z8(tnpD$)=E0+emE{~M~gTn<~~#&LhwOZh?$Th~a?y_f!RLyJMfHPH=UB5ycaby`?Q zSKb9_vbA&h4XB*j>+}t=%#tAPvG-ncmQ-rGVXKqUA@%lj#znQI#S0}Zcuo81Wk&}` zP>0q!cFj~ZKb!tS{SLEvi`Ux|>wf}#gA*VE+CO2|fQ1Wz5X$Q53D8ORT7NESec7$A zFL{eaW8k@Qrh}$BLR?LJIfM8Lm`FPmhW0#Z2^Jhe4*eLJy1zh6BCIO^G5Mnf3Rfvk zG3bW=i+pC@nQAvH!LQrBFN_o*iM!t`T@<(N4mtU)&CAqB^Ucv}n|9@u@!*T;M&Fnk z^W0i->8U{9!?p}NJJ6H`fwKUMtmxTl-yq9qizWHg%Ta1yb3~d} z3%W5&oIm|mj&!}4-T7kkbCK)F3f)UYJ@nYm@8;B#IQAA5TK0rIM-R#KFO!)Zl@G_K zSLfcle9xFeVLOU36#QsWuAz`O$t<3-wiK8Xi{BlX7#t`z)mG`bDxNgGZZYP;v*l4! z9xE7c$f!lFi{o`>)Q`0_6TZn?ME_)g{8M5IU0$dqbN#IcN22+6ky+&KAOpaY+QY>* zt=4p5CK=S|h}HSKn=)HzZ=mw*p3yEav6hxOiAk!%0EZPXo3f z30k%vxk5|MSb7Qp5{V+aXRI@-^>VR|tF=Z+GMk*S`cO}_MT}zpgIx&j&;A=S;}?z} z2LTcI)I3syY@3&$S#6X3;T^~$zM=MvutW+*$Q@EtB1~0)Ak8Dcn^9W9(FKXJ3TJlK zHnu4fcDI^k43u5D4qoSLHp)0IzFpc{_T2&=Cj#z};Z#SbH%zx=%`RM3LsVa*EybW4 zq$glWNY2P5lB^bwLaf!CZnhgUl4v`Ju8R0`i%ECV+38?lSrh|>w%e8=$;hMm?o8md zGMdm*E_z;}J`a4F>agtd-2*fzZy@8}g*wXC6%!?ZkYk9;l~OAW<6SM+2Ozy4^6PwV zM{g**No&fWAx*rk&y`tg4Cejk{rMZQSXB?IN2{47;S{|@1xb`plcm}384E#;GBd!> z%52WT?1wP>b26LrBdwYePz^0ODBSAa&$s^2-_E#_D2*1pe6v52FA}sCfIN~pnZ=RS zI;j;Itx=zQIFuQYo1vJc2+kIl0}@H>w4$2Ta-BcZq3bc$IPKlRtR|JoTlbrT>TL~V z)@iCCtt&+>_Se#6CYKt#wWL(vE(hlz z{t=&u&@DE)6xNiJH58`2k_&6g$!aQ7Q3-M$-2Ciba?){)!duK%d4&r@QZ+~-nU4>* z7^-MTs@Dt~ENBhI;h-HoOhdZ9M{gY=A23H7;x)uKYJjf46rM+(+c*cz3fkc0>r>o`(rFwO?0&(rR|mw*4gAKk3CUn9MS#a)Fgd%--}dgcAUrln)F z*{_Y*hPKy9Lx}UFpX!#ngWB%y8=fJxcR6LvW(vj>^&c2_^sT5eeV|AxmjtBJ-= z#8k~_&FmGVE?TxOS*>$664{ar3u>bk%JjN;qsI|*I?)Cu*U2VP&0Z#0ckAM_ zcTX1XfQk-&M!R_q*pt4Q%r$ZDeNgrw=x#cdrfE)jhUo_e991>9$>R zD1&8)g2Ct8Tc|oTh-a%24$s;`(WXY?E{(j|;H&R(>7`)<$XZZEdBRSY13!vB<%e_s zr7rH{|H6_~dRK(V5r;&1J>_DNsV2_^>7b>Y)5e+4Tq6cO1}5% z)c4vWUY?e@eY`!S3ZPyBt(eX_`7kVDu+wB-EJMLoO2Tn2NSQS`qG#IA+CCQZ8(Q?e zZ@1yzD!WqL(9Hqup|}|MPX~C*9gOM59=)w{g8@)k#ip&cJIK2) zmygnqB1eHw!5}h&>M$r)?AQ1ez2(ktZs-Xv2$t8B(G~QvB8}2~v$Oon2M8!4ug9(K z{)rZMJ}Y<{8DtiDnO0tt0&J3CKN6#h=;EVPvp$&3B4W^Py zcXx$}A+Qy4vfc240~nO*{?}L%S2HRDV!OJFnEzq^P`Od)Dk`FvVx$sDMdPpPJ_K9@ z5hlA{M(^^fQI*kVkkY&3Q6y;stBlfAsY10 z?DWO?pB3u!T$(m#_WX<1JXorzpl0R2e)HYKR~axkkifAPbKhgBF-(n(#~~gNl+mtV zz~{o{)aw`Q=`sbIPI|(K0n3(ASLNic&bi`&xAtWBz)&QE@dG%s(yaH{x&NZuBYE{Z zj0=}Q4t~d^Vo#boJtO3%mTm*r5^OFZTzbS@z_Yvg2==I@aLj0D zQAy?sox#qgkWEO(ErW?ep;rlvT056gBRiRj-4j(> z?#=rmTNga(SqDortJ1&`Fro8@z{Iq<+(3iI%SK zW5x<{KAOvnkK94z$d7l3MQQii0#B$Iy^tj%fwc_m@*hu=;IV(h;XzW3Hf$M?{_ki< zZo3b4wb?bV{+;5OW`Z@-Fhs3BQ#W8!v)OFj ze~2CdKxEOPB`k9x&Ong-$=`bG*)-d72eGDTUhuf(APp+89ui(FUT{m&domBSv*%Vt&V$@}(u*_IOSqF&eg~X+y6>#Vb2J)NKe=J&GgU+5 zmtEe&&5EJ{PC&&W5|8wzxxY93EMh&pd2vzc&v0Tvx1`>&`?O=E`-WxRR@Gc%y?yVo zsvd4QTY;`^%1*iUJ>N4MX>9>Nem(iR1;&VPE2GM&lyZI8=%$*8UO|+A-@%q!w4&mC z^eTSWSH!(nF$x@mq}1V=#IxUpXhf-BY9l!*AvHls61X%b-LRm(ue2tkEEpYGTM71r z!b@R&(LA+MU}+@Ee@LFtLdhuSMJX7-ZuKgc8#HX7~(ze@@GHq#nPKJJL z`6~5_E$SttOE+#h4*THaEeBvWljH;fN+z>gHIT?C(GBVLvZLj=GV;-~rQ|YP`O$Kp z3#!*pg0$Z#Due-lO)6@jrfy1e%LDpby`J}w_gJeTRH&=y^#J0o_e4B;7n1ky9Yx35 z=RUonyqbbwkmmS-?GK}zN~FsRl{j3bU8F9R6lv%WF_;%9YzZIEwYNT$J@}5@HCaF) zeLyZ*vO6CO87j~cCDz3IrNmdsmk8_W*4hHiB8;&*+zbQcTq=S=T2Nfn8)ZM5_t4Lp zT36SupKO5#7r*K0eZRN2b=5b{_Wr6sMW*;yy$^$LE}HpuB)zu0mcuO%Dz=Wy7u1st ztOU87+|7pdq@$WRs*EX=%J#TiqY_5Zu2d*u+!hQjTk%v~2EGXLyZ#u`w#W=;!N0G+ zhKCRQ3!sPoz2jHLJq4lf*3~df-kAHhb+KQ)JHuRi+jKYB5f1KY6A>qs*$0~Zx zut2U2__HThnpJzaYYTBtmIpAT(}yy4aSX*+o|WMDY>(%l(#IzepxgG>F50;_)3#~( zk844CcrGW5Qm5jhdX!%tG0Qjt+0BiaWVIDa5iWoTIcNo!$N-mBqY2bqi z{^sRh9J(@;e`PQ!9LtJdHH+$a&vn394TI3h}l?Nw4n&o3;t@# z`svK?q-XzV#3~nxvOdM~%MfPRXW}Nf_Gf8oDX_mU-}G%}ryB+Q_Ihu^?_bvy6}}TYnV#v2 z*Aere>f21FT9PS`8{Cn05`sspXbH$JO)p&pDtTWvUs2NhOU~*j|HbLCwMr>FVl!ms z(b9^7KNEj3(@K}i4i}()FNC4N^KIvortpi-n@L=g1OO`I&}P?}$=ofc@z~Q=C_cpM z#%#H1PcMN@NFKEp902Uf2c=y;^}mPJx#8k!gt!`ZuFeTr?YL~ur1AOpX{-FxfJG<{ zh?fJl5bPipGsNnxG#>k`wChEPdI{VM4108tfk85K3lST*4j=_VW_(W?&)BjRDy(94 zc{}hm1-KqicSwjrOH>Pp*r2UDW~qgbT0IG3g9Ui*14o1A(Wg?AACvYZM9_?HDLH2+>nFs1?aGmm1z7oH78-n3oE07>E( zzUeaIPt{zpaR^}e&r^5l9$t8h+Ei!x3QsyO1U)|s%4FZ*-6aI33%}qdfjy4tE0k#U)Hs&yH27Lbee8r%b0OICU z1ZGSYpm#(}UAhFZQpVqqY`*V#gd)s@8_ZAy03S&Zn5bJ|nZ)pLGD;|=jB+MWK_wHB z)K5V2FjrRq&fl{TprCw8nF~0QN2b4Elu|}H6R4n)iA?%`n4=M-L>5FD#c!mfoC#D= z$wVaGCh!w}hTMb(8T<3{F%YMuWZ#&H%FaKqtHJQ|2#nQuf{F z&)1><_g-9J1oI4n?m1}V%S_1(EiF;8Ldl-{?wO!zpw|xkG({Zov?ZY>&I{jNkay7U zZ3pah`a$l|ll}@nxvunypRe{d^EFGJ!C73X*|Zoh*8RGG8^(@^ z;KbD)WLz)Yk^eF$q(vS5TT zM83tk=U3@D|Bm!0jQ1;;i1c0B;J0w*USrIH9pHC)1#mC_?6zxrYrGc-NyyY8;2qz0%%U92oBpO4)EqglSuerQsJo>9_;LI zDH@YMy9eU*I8Clu;U@iq0I9Q$)WO>aMOTR9uVv-+_0XtE${u{@-`fG z%OU(YX|J2%*@1sJ3>OF0C!~BC7K8uT4{mUKgGosF?g$6!w)g|$ON1W`;a)MzbBY8$ z$hEv9Sl(YP24E{6=J=dMv;pN9F$}hI4WG1zv<8QPa`K2GVm~M60SFVRaxWmw1JC*j z62jHAojDH?dSFV)8u)6m3f$x{xXXPF7NOxMBWiGMu=Fu=HX}^S^Ig(H5$*v_JKJjF zcP{163;IK@AzrH&Nn|Rxs28ae$|G%<%`(HNjvjV?4==b_2Dgu&nWH{pCQ|+%i1`xB z104K3aK>_xlz}fRT|4~}ezepm$g)IHwmw?)0jrK2j(`)uf~y(k`AReV_ylrpev@0k z32;KI%)rBGEj0^%+WAUhQb5M5Q%jOIn?dFLT=QWu)z0&T;~jFR-xR|@Nbo^B1{(M< z?Z!h~@@xOq+P~)pD}rDIB!+iliI(FP?!_j(io-0lH1)=?;{BPf`j!CrB>m}54O&OY z;PXta=l||xFXgAo3k*<~$Mnn&W>cUJzs%@yFMcd(gvcGaF-5%n&-W4l5MY4x3U2~H zdJOfA?#MM{JF%(1s;yBA9y8wv?Q{OM%o` z94NvIS&>(i7he{J62SK;Pgl{1gsQ(P)n#>fEv<8Eb^TM_);I&GlhA3PMQyX4`5f4B86Ilp9i zDLgt?_~J5bOpVkdvqcXVeX?j^|K7f^Yf8t&h*_` z@$+ub+5=@Tt@5n6;XK1NbKl}|dDXlVyubKf{C@rh{x$w{{5J)#z#%vz zOb}9p1C>iEsg+kn648w4wm4l}B&LXYVx`z74v1UCkBjd~3MGw_X~{cMskC1DhD<2a z$f{%^Sxh!0+aTL5J0`ma*$v^VGu}O^s%^=5?(=drbRZ z?bo_Rx;!1M6X}e)dR?z>gYJOto<2oCtbbrgF~A0y!Dv@)AvsZhQrB?<%vkv2yIpgphD=o|%7nMD8#druuK_|dhu^ws6AV&`<+Y`JZd z#C3%jT#(%WvRZYHFTdZs1NdBo^$Q~uyuB9TO%Ol%rjy|_)=2Pdv479C{(858*sCvZ zYB|^J_C9~Av!$SCG3wQ{vo3GfINaLsg-|)Db~W^Yy_S9Mx|+irJjqycrjg)Zlo%`4 zERGKQt@93C7?liaOC@mqH^baseiwjYMt{m@Wk~B9#!_ydipEX;IEl6pY6;eX zKx)q>+h}KD-l@BZD6vh~CjUdBp!!r}n1BbhXsI5_GAfzihDX01XH~Y|k4BDk69)G0 z-oS?e%51$MHLB?<4iX7B><+pvRT0hB98VMvy*5DE!Wv>R0al)ujqSqEAMIjSA7Wq> zq+E5YqJAUfe#EMRD3V5Imc!4?i;q@O%<)EL`M__Z*hp-C(=7Tq}%5 zU~Q@kbJ)LsR<%e|%g4Rw+!q(a_hi^8g@PP)vXy`gZGYA2F{+4F!K=2jpwUPv0xC2B zNmv{MsI7+w5v+rS>{Nt~nAL_)WO`f&fE&C=?7hTON*3#_OgLV0#KQ!Nm+;2FJhuRG z3-DlL=w#bscUin zXO49oShwe3xg?fudycBlRL{>bKNP8@>r*_xRmDtIJ9F1u99G*$3-7Z2B1MP#%+VD4 z%*w1Sa39lR2t9Td;rawv(;HqkdH!05fB`*ENxrd;Rp5u@+ zfX0bG4?~zMN5HLNVU5bMpDyApFtjro=D@1~)j#9UOO7c53?et?moq14wP6sZ6%r|b zz|qL&7Ty3%TC*xv+ziAZf$%%>9^tlcz4E{B1m9R{7ph=me-X5`F|4Pf+il6FYT&cb zVEf^~sXy;J^@n;j_EpCJt*m_xuhztatL*q>>dfsT+N@4XRuZ@R;-Vf7EDU0ib^yBwJ(?|ndRkE?Mw{4 z$L*OMUATBeHV(%VWqdRJGR&S15I}Y)(9)20*&tZBY$~moKjZ(@+hRHpVVv$$kH7Q`lQA(Kw&0mSS|~LJ3zJv2WH(@B@z0M&{8FnHWMQm@ z9j)M(HrcasBkgXMr=G=;xkW=EzaJ2n-AGTNo@`!LDWd2JM!DAf{27%-y0K{Tp{ZRN z9e=fC@bge?Ixw~?=nFEjcCjfC$;|N-Z{=q30oP#U?5M90tif^hUF=gOLZKN8YSkbnP7%_ zEOg}Ww`98oVzi){3~?zOU7Vclt{Uh;(uk?4cTMmVeAOUV1fyKHm85Y@qP|dpNCHCf z+&EM7qA|Y?v77CYq_1AerIQ%NXuwjaH;QO*yg4Rd;N;vTgCZC$7RJ(mQ1z~WH@_yL zj9pO@Sds{PC>XZ(#2MeJbR}(rfBp+fWiVO2q0zeZT48ffKH`W&KgB|c^in9=QnE1X zIQlH03a^m|Nc=||G*Aj)bZx%_PC}0Hnt6{GGISFSak^@tB7qwjdG$GUx!(2H3waCj zKOh3)o*_p}s%=Q6aV$rJ>aBD!LI_XZ7%^|+q%%}A6q*F;FQ1Y^13;ujD<6||Cy5+>fr$g zE)jAUF7tzvJ5HR!-#Un%{5EzVHiSqEI6q9JpulfhAw|!hT7oF#$Aa61N4%lntjW_o zE5C2}vskmb&?hwz!y%q2M4&F%1}9}uPCK^JEX#Y%h9Zu9UOur4Z^3b`nAqn|yKKEY zxgqP*RG3c?h=r#e=;9Nu*7SS8?#I}U1`$r#A*va1dd=-I7#YHjS}%tO5Xght{~Uw^ zn}{jzV)F=$&cX?oMc&OH*kHwuPd6Ik_#qQ*frBez@PCM+$ojSP(;bHcsM`I&Q(aV<1yTkP%YVby%Qbv| zqEi78BG{0(mbKvr_maY?y>1*;CkX1!o!T;rb2#FlfR$*}7{~ch|FjLF@+Db>BJ}cR zEg3kUV2kIZf4|4wo41Pf1`$0U1#0^&Q4iosa)q}?uf%UmcGv(ftfs#KMmD*D!R;j=y-LwGFML#8tt2h zVXZstg-lb)ASoUJON+VvfUK8 zRXV_#fbu7A3EN*lm9Hc$DL_mIQPP^aj)%tZ9btmpLsN#t_Qy z8~2cAT;+@C9bBswrg^@?rF`VN`E_^G&}bG*1%1J+-0If+)J(f6r{`#}ordfvQPRP^ z-@Xd^L7wa-E97kMQ~Y2nMI&>^<9Lp{RmhW`g7j%`F?}`0nJ$(OB)aO%XXPho z_cCbct@wGIt#V+2ZubCN(~x%ASs=U25^!ZaAq}-j5tam`a@8WQ=VG&7sI)>G!UKqd zMjkH=w5s3ZbF^?>IIy@>dJ&9mGCOXr~ha+O+1P|1`f$=sAg*VNTM23 z2B21Nm+X>91d%VtH^{pxD&6ppya6ehVLxvx?EgWO$nbe_Y&ED6c`Dqcz~xN=(SP>h z8)QiQZX!0^M*n0zE_GGkP=Jj9ODa0J;Dciajvt|x@vM~c<3mpuoGIt&{H;8`!=l2H z4XmgU1`d)Uj?A{*2RxF7w96EM>~esBE92Xj1NtO`AOk~Y-fZ14#uer*XEwSjcNZHd zqHH|DR(PuLwjA7**P^V<87ZVe4Og{d+1H$t{ybkum)`ZwC(FE^$TUF+1_>(%jgmsH zK>8$7tabR6*o~$Ev#WdaK!^LYLx~MX|M)gN(?2%QHQJEKm1`4`Nbkw@EZ92tOw0W6 z^#3|xcABoVPcE(z;1x)umGq_?YDoNOoJKmLP_jcWUw1tbD}DIETzdcXp4mo+#v=4` zk5soAQIP**+}oEIhgO>>GDmCKG(TiFZL?zB;a4MWs{e{07Jd}(bMf3(r|TP|R1ub? z)FbP^K!U*v$~6qqRhc;UyTF<2xj@>X*Khu^omMmsVlhW42pKd#IOa8Afk)uWt4Mia z*ZTP|gd8puvi?^&ORg1AM@l6-1U#>DeAnq4TC4k?AItMTS2?NIISB6N=Axy*+kynN z^P@yBmm8j3ZbpzALU2DjC}RuOYyX2FE&<8)3(m|gF!=W&0_t_V!LG~=#b}awiz$&L z{??2QT9wPo7#OTyT1HI!T)SW0LHWn+KzppKz=yMJ3}f;nhWi9Lw!*%1dVGfCe@!j8 zQJ`XH^#;s^$w=&@rw81@V#xr$n*_M=}RFJjqpO}rBVQ8~bzB8V10 z3;#eUv39mr>q2FEWKM#RY8uimuM23GEd)~;D9pm6`^abw#u$+~)#YuxYc?J|P;NO$ z7WORcNG18TR(ZcCOo3pZ*|5nJp;s5Qc;95(;7+846!qXtjfW?U9gZ44IZ-!EiAT&Z z9U$`l^`z<45zt7kr!*~I*rPoBIqmaJ_!kc(;~>_THlvxDg}gYV*#B%6VIP}RJkUG2 z(=`9`wrRkI!6ZC7UhVaUs6x02hqQ{`aA9WB<0Wzj$+JJB&UgnJxn}_j^dz`6gY)3n z7OFjz&*Ex8dV>mlY1(nMp)O1xvO`jvHx3L>+lu`6%d^09DGgs}YM37L5KRihjG7OB z33K-l1)XT)CW+}Mzek}Ev74;)vLhvUE}gprw7iU(gf~)JG1QfrjsXJ(X=tq!@g1(> zveu|%qwACpVUEd+;k9C>`CcLgLa!~S6E<0A%ox;r2|&fLE`6Z+i+>_vn2o?efF zI^b9P+~hflx8FL=FgIQal%Nay1Byp3w#mJggh)?UxB9OSC;C(AOaXaxVPrIk9ez3@ z@&LIG_ZD`34RN$Hs_ii5r?n8n(|5cUe2DwZ7T{5b2Nw%zRrNPLLL=Wb6kt}j>h$ph z$bfgjh|I!C&hoSDS&FHXYj&KuN#TiykU6h_Sf?4AISjiXWV{e3J)diSYs?}}rB8Iu zW%qM0`&PRPtkFc|l2Evz_K=T`l2e}9>ZmD}D3Txkjm2ZV-`$3FIyN3(s=x@8NGSC& zoMkGUFP%1Ea&C`E8_@RVIPix(m4LF5~tou&s zkeKB^-os2RIvqNO3H7@7zZ9#V0=3LmP<>;h)Au|lSpJxRx}cw?@gNP7?Drmp2M9>X zqy<}G^^VL_ix-G4Zxv8w$nZPx7h0z|Nb*Cc#`~#CW9F6_6fA*~@lcvq0QAXo!R2^- zu|#Mo8v$`?Uccy=XvJiuSt38q&AayKHuubcS)sorDht5R4x_0FFCv4^U|{uRn@WW@ zl+-Nci*J+}qQ%OkOiuAbC@%IeY@HllNw_3PJ)M}L4(_PjQG~6EBS|S-1Hs!%CaU7@ ziutJ&j400+yD9CdYvvMdMJH|vzDwq#ifx|Jidd15HS}e6Q3};9YZvOAcAfSVH+IG&DQZ#GyZHfOoY*v&{U$T zHZRRmcpwcd!$rQd^1FXsX@A$ z%5=x;ZtNy(iOBWvMtP(uVh1E9A93bH9WT@MW1q@(xKe-VyhjPZMt z@xYxZCdpuV1q_DZ$$A$)zUn*;7Pi8^K5S>TVF@9c?M->MJIQ=@3H@E2F$nn3TfZJSW3|E}{r(X$uyTW(uV#{hp9}u74}0JV)yXK` zB8nIH1OlOWYHid`KuLbI@0%b~RVUltle~~wmjN_+>t#qNq1-BIWyB*JkfN$Eer6+$ zYc!U8~ zO@I}oh_1U@lAnm@8|#S(s?(sdi3~ap1?hSk1*LgpiEfxAX^bwAVMQBQw4pn(sE4N={k2)_SpGI_9Q z>D?LPg9IMLh>}YIaLE8_q*ez^p!J$u=c2Eyr;;b_#z>2x#?x3^w5zptz5lVcE`dB+ z!Vq0wjOl}81#S$>&j8Lb_9z(BF;-vSo*9^_i;ebf&|nZ_(~MT~qdxFi8u4<6l^ER` zWMMQ!a7!-z8^BjNNTrtG(7bPM$mLoLM|3Zaqa+rX4aSmmSpcct6&0tDG*!h+^n06kWJm?l^nY_YRLphqX#60xls_?n4Hw zPWP$rbl!t`gQhI|M>h|gTO_$#wz7zE^-LmW{u2}G;;Meix*)Taw=GX}s=0}QpCMj! z6B>o?r!&*WMx9vv6^{!myVYjvEPkqMDp&vmM>4bP;<)HwZ{NRG!D2b@B)^8`_eeCF zgIS>@eb4eFGFU{W;=h-CO9sc7fWd$O-s08C0q?>`U=6YqyHInT4Pv|k*)-x*ZLI(( zDXIbJp#eLmC~DdM=3Vv7Hpi0rYB?^Ub#lw(2vx#9A%of%(QYD< zouZZMl!=C%>8I*8y9TU%+S#A6$P7T;Q-+ePrwiIB8z8hIb{0nAK=@bqmq&4tiv^db z#moa8D-TxmJp4HYsi>^`2j5X2Gp+#Z)HE!Zj)8y+L4)L;unLrxCJ0ywT=w*Yd$Y-C zG|3@|?$r1XUjs!F1#TTpQ!DY%+!-ho5fpgusDE4*waYdR^snRaPtilOKsM*e-fJJU z!;AQ-*2&6I6W9dz@>~vS5I}s^dtZkZR7Vq*YNDY=(YW%^aD#@x1_#(b$k9mhN5A=1 z{AyVWQD<^XwLnAVx4E_MfgSM4aoR~kH@N;^TV*4xR8{i@*DO>LCU%#QCmQ*j!IF%k zFDM}xnc9Ll^=1sFb&Zz7;@o`+MW`?zA%>haG#ZuUF||*VB5LKl(+|e~q`@4OUE6sq zqPm-BLeG1bMr2<%6swUV;M_4eYwGp@$M^@FzzusWNgkD~<}8WZYR>ZUAiwIKv+#B; zIcV;3mZJ0!#klVm_m=@$?0AwaxKsl=W^oJ{w4eu@>y$=v$PldBTB7SPMMBjCWcJz-o;8ujifU-M8$kH=mT-Po|f7e*fcRF_kzOgqNf{1z(-+acAQmi zc~v)tS9S2*kndvzl$~;5K~|ig?t74TK;xbG%_?Xq z32^#PN|t+U@z&L2gCo_kGqp1~=6C!od1Bn3Z=JFQ=T+CTEN;hus6KUjE8wx6Uk)0j zZVC4W{1w2Z&B4|+@dUf{!|;;B3q`&;y8FtVTMA%on^EqcNVZ|_1o|!L1i~{UENldT05>|Sz2qTHw|=DF2Dvf8`Ad1xZM|>^zQQf^GB|GOV$?tHk!DTFT+6uk zgp|xut92H|7&WtkC{c=F=$1XCsR0R((Ny{5FA)pAg8S%|JRCub!;*n&SJ9S~!cTKu zlECry5S6m(uYA`XX|n7+VNX5quF#lHLuoEu(|rz?H-3c#iht)$kH3p6JGFUfrqR1D zGus=D%h<7+DheSp>;a|7oQs?9&OSehYy6p$!wL!s=*{$epe*Kc;!Q+&*yr(jH))KQ zmnkKKEHsViR(ws&?>{Ihv^`^+$Dr)d@ii2qX_jLosn;iF5#?Rh1b7kR1I2|H6d6Pj zGdunH@ZrAXcrW(^|7iwq5p;U}@GUL&>>N!9N5Y*CeW#LV1Ee2TAP>!9n1~)`X>x$# zsb!AA`{Hkgp)?J2K{A@sE@G`9GuzSi4A$ym!B3Lr4mXhWdNDn}2p!KpM=wuqx>}~) zIc?}ncO+JJ$)Na?e?0|CT0(gtYNNNK?Kj!fqW zh=4o}$g5rK4uucM*oua?6l)1DN*okBdvU|zJsXT?1c;09>)exa6xMPMk;)}r+;yo3 zcVDgi(AJs5djun8dtR$u=$0?yv@+2u>~Ot3fZ{AIzG6eEk{oUwCoUxV729W$cZ;@L z+7%wq#9=h0Je}ja&k2+1V68>QEA*B)d}x#h^u`yCEKCiO?UeGtAdJX<$g7;rzgLJh zp0Iv^Wvg2hM`vJaQ&Sf2inZs1dv{fnhU<{JM{4q{7>Q|(eGbA1zQX~1)wJtCA*}K8 zAD;Ac|B$K~<0{4D3#CibZ@kOt?CKg?OU3_zcr*R!BX6jOViaEFs>wWxY0-4Q{_YQg z0OrI95|4O9d*CAQ5g4vc(GA9| zr-_NvXb?A4VivxU=HS?BTWPT99hs*VFA!a}6HsLg@Jnm#_rfxfB}4H%#>kwTo=YpJ zU1@~#i_MsLiRCS5%%Ri5=QnT_DCj+YxV}Vf#CZ5B%cIi1pkiIi(J}>oyD1~lfL56N zlOmt^;ib|7wW$@Z;x{ot9haM?f3$!>U8|hiwQ$dN9oBmL%g>u;j?ae%UAP?bYzUNP zBgzE!%bWm$Y`6#$+MIUX5iRg@%}$U5N#$l2^lu2}Q9URHN8s3M6@&b{TjPS!5R>rk z$mGY*#s$Oz{yyQkk2!)_8|Yyfj}msx_sRGn3Zh$A>3p@kw9F{{8m{Ki7Xy6on(p2u zlJ)^A7LJ{6wzI&lYK>8U+e@rj(Nwb1`ze|%u<7@}K!Cb#oG=V)w#Aj`+_E`*sZGyq zpBg2>RXDA@amRTU-J#}N?~?#kT)scDCmHk^Ci{)0gKCXc2Yh->u2a(ot&NXV)-+P_ zc-@BVf|f5-i|ycq#ehV?nACjQelJy<>&~OIGnvKB6|XfqtTUT*;IaKO*F1cm)@(hl zI&kq`{8XQ(ZG~LcQaO-<8I@gMOosJbn(>TLLunZ)mx`5zOp>*Lr!iPc`ohD*Gw@4V zr_xH4QkBHi6)6^vc$NbZsZ9*ZYsOnr7Sb4F^8=-8zV_a2{~6W@5{xNWhzL}hN&s-* z-rHzmQHl^04S{L-m~vRr6sIg=u41&);QTLv8~;g)LXKd{lYW+zDKzeXkX3TbrRkpKt7nzBcnJ4 za&Ax$R}4>|VP#(~t|`~SW07?h14Ka4vH1nR^P{tpP6_FG&qP>Eg`APXyb&}CFar-d z)jveDg{-d15{VeOL4*ZCEPyH@qV6-K3cuYvWb+Yehx;EEUyfy>GDmj^K^ebn?2QC` zz-a<+9&2x(H8|7eo@cJ6RWLBiI}o7yjkE!sYNRw^dq<{o1kA#|eQuP)A;Xf#I%7Ka z{>oq(Tl~Imf&5} z^KMU8*V*-_UDmZ1wo1NvI_=qToKcx1zbwN7Lq%fX{a>y_YT)z1wbvF(XkQ>-R7Ps2 z3mT7pV7ri3*OF`4-U6N_b)&HEEy&khX$O_ce6$9!GDrjh2+V0o(&Z={D;b&9R21kU zYT5F!Dya%Q>FB{>eFb% z<#nBbN&)kvcCKAM0_LG7$=&i5Rt!RD?4>d<*e&a35cQHQZ`q{aMcWdGq^$2|#{;ux zq#+IUl_I1(o^$t0%}Rrumm%K>F5W@zjNA5kkodOYCzt8w=6B4L?+N@&K5(1>BMS-p z2t^!-MjsM*{`n3;z#3#5D-U+)T<)ZU>ws&SV|y457T96h3)RV*p6#6gb4BH=)+X2j zU(Uxm@z`<{Yr1N(+UkFTWwk=A^T_Pf+UC(axlK;=C4wJoYa)1@!B8>E&a1AGO4?w1 z@-R_SPBu!oc9bUSf3I+7^)k$=2zO4PgnBhSHA(>?q!}G!wc>%jK}Zdv0zJ{L4Xy?l z0sC;!1eoMX4;7EtZEBA2NoyRr^NbAzy@><#sDB#;YtHlW74e538A#BkY0xWo2yK|2 zyi`Jnv^6BOFcFFD&1Ts1SpVn;R4~cl%?>h5 zapm5`3hX961~!f3*rN|vx;eQbrG(vQqRx7Gt&lHI|i#VtwlRq5lt#e>n%_CDt35`}3VgRBj|UW(zqz zND)}4=MvD=!o~b&+o7!1Uh}qtEBq~R0(fZNjT&Bi8eRJ?@W8z)EC@{_HdHCJ8i~D;AHZF8P9ZLm8apB=aNM53fEnc7d*(j1*hC;aRlaD zt^EVP7*d*_>6nPNgKd2>{7@<(c$hIYyq(ue#gdt6b+ToB555#Z74Ny*?t^8mBEiAaf_@;X@LQ_y-rmBOOi<`=*Sgqcn8fLXK;R4p~J%#_K>GhgOlzq8^XwA%W<;#^|8$YKiS; z0!f1d?q~-9BrPPyX+s44wSY41dWK6s4%aX9eotB z;mg>x1dM755v^k7%t_toqCCBB{40w>ZKCAXw~+dQDndGtkkn}B3hX6K)o^15GeL3rwuY#)+6zmX6`h;8aW&wBzVQoU=NEY7(9Ebwiv@Fc}0uV`49JOBOk>~ znzn}@a*odMj>T%Kad|lHkdm!TtaTA-O9shalIbk%lixXL@sdi!nmw+z+uN9^?0Z6~dmGxXeffW;5KxFPvsD2B7wZj0 zGz_2M=QDFA=lq2CmP|?07~*)hhH7l>7U2R=TF1T3Ez zN{3bfJpwWx{z_7Y8>JgM>QiJczux54cYh7J^N!=D@vyZKwB!%ug!^0Re1hSe&$`pOk}vUsR0h1PGi zrVyse?tmIKFS8s=mhirhv6=d71B7M@?{B?XKB^9Egtd?Ek0vhwZRK5PWYuvvxxhba z5z9@89&a^@9lzhOfdwh>4Vc^+p^3l*UIZ!41H7~j01j-dU&Z@p8kv*VQY@OEBN{}U zsY&dhR#HZxx^%NKVNK_t;k)&F@b2VWV_khWx*9crq2bZ|*xK!ucJaqKcW*w~fkZR~ z4_^58#`I?wWau?}&B(^F6{K&d1m;UC>Ie0X^Cszi?9m2J^ZM=bR%;X$-@S@1Za3d= zi#0(&S<#o5vO~YBk*KENVZX2W;`&8a1<@BZT8Q{$){YOR zWsNX5)pFT_$;H<0bcE>b}9%X=o;s%()xsShb356?MmfjDZVC*_V`4kSq6G5z4 z3}=m8?+xqbVJc&Y@CpDWQu`ow8qtc^kpD9_(l* z_8@`cC$HnZGirzu+;yY33q~#ROv&16mWaB7*8%T6T=DuP`hX#Dvu_rt%I=^sUZHQT zvZ&t92FA^as7&~$F-e#@7@ZXJ%cLKz42R%dm*%{&O;S;4N5C9SE% zXScd^jvyZrJXVn4^Z12ibFA<#B2K)b{m%=hMf>pCMB})0a-f_b`}l&ea*T-0GcCBE zbuCyZw1v5WK*Gc3o_B7IV?u}u`P6K;YY+>CxR^oqD~^utvK^;=`hWmGx3#@v7&9E# z&?-}uwnmYO#XN&`1EZO09PkJn;if8#q~NvlJPY2Jq+ygG`Fzcl5?LH;=%suQLip;{ zax|*|DH)h4LhfjNTyDLOi3}sk6J}Zvih994mq$plU=>Z5*$D2TV9)_6(a@SkEx-N_ zq|)iHogF#WkQy7FYtbaR`8n;eerl6wf&oLaip8bPi&sqbZO!8qI36eFh;+ zkCKylduyYisb>0+i3SYZgpt$wY_!1_X8^H;Ut64~v0_(QZLv#}5&D#{M#zB*Sp-C& zYfDa&XZ=az);knzs38!@Av@RF>oZ#W?q=5m2AS(U26MK2CHdh>w;Q zwk&=q!iEYhSECM0-DS#;Bmc%W30=q96JPgO5<8Lp_cxAJs!JRWM*;DG2yj^-1Cga8 z?Zzb5q2Y;*`?kj+e9!Rty07+=;3+EbS4llWOCeZEP9O>80#Kz=AhN{iF55}1gni!a zF^dBbWW52I;M|N)b<%BN*kLdNIn2aAULB$&A)`BGH(&j)NbuF&ly93^&??ZA8Ajbf zK_MuUtoGUIk)caEI}}4zEb~&7wje0)C7I43L=c!Vm$$9BG;)Q1wKj#gPPNSCZSIK|Ki7y0tKdKyxZR?TRd%^NuQ? zHxwTMl|s(fSK@j+^vQY?Cg36L*pkJhvN5s3`~5*Q@n=e&?CbgrfqpjShXhYY+!Kj; zY48Nz@KloSaFo%VpNEe)N4kR@v#i#XNb<#1V7b+*4TIZ~`PZomXo2>Hc@Ai#^XIHE z{Kxv3?eW|CzI4XlC|0(`#ZUB*G)Si>c0uCT-QC8QmxM>677M5(Mh2akoNR-i-9d?Y zuPj{^2huBF>D@_s7;Wkl-ibnG_u3}gMM`0Qb@V09l`2C)Xygr84Z}5sm-VReoYRrk z{c5X4s0%3244nYCDhYDAV=!PdQ^IQCt=07R!RTAQz>Q{cG!aSup%rG!dbbc)Xuuit z*;htEA{r5-wUtoIq&<3V`nkcaHNpSBJae5D70dKtE_RC*DweptDD%|H7UX)pC7c$8 z*1WB=X+9od8(W*xDV^;m1KT8_^9WXe0i+djB~Tot8b3I*!#+nmBw%P&?R~(s@{tKe ztg_4WMnB~CnWkciY>9+S;HoOtG)6Z3^If5d)+&b_{n_=Y6ulWBF7GDr;gtvGY><;z zL^(vBMYf%p| zonP55#%X3rG zQqlBwsn#M?J_KAe^2?f4LeMWeXU3c0fao&iZ7}w>)%PUfwEJJEthsgL2w6@zJjU%j z$Sf-zMmxkx{lZ$t;8gVi7vBs1lZ418J9wpEW!jU@BlSTS1m%xNk7t{HHqCWHA*hqW z2l1%^@cQc*yX5)e!1?kGUforXEc!Ro{vw)q+4$+Dy>CNng{WxgUSY?%-jNtfw%wlr zMvo*JF31ocKN2Wh64)L;JAgT2Bh4q=e^mBh*8<#Le) zvTj_ri#FO8N+lDPY|uL$Q)>=24gk4Zv3_J_$jO1LtkyR0S015GkV;utuU7C)Z_rfXPOy&G)YJ3SKxp(SX;acus{;hI|PDpMqktdoLs zr5wW{jXt{73D~a@6g?6eIb98d!CDW58I_)MFyGIT#OY4IJ-ieT`tFIp5kyHIXoJxk zNOxexrAdE#-yOUTD>+rV1#-ApovLj`Q^hX~56+y^i5fc`&NDUX7^Ng2p__}W zzPAdCe*Z&&eM7Fj^nbDN`)7lJMi3I-TvrzW-=8*fp)^z8ed!Tj@PX}8TopFLCUMq3 zH=QHeg&E<2!U6sL4%?ze7?)|raeor@EI=3|NiGrk^-Cs_4)N2mq!Qo`DMCAOxg=}Mix7R&6R&5Mi?UdPk>3d4`BTWK$8y$f9K?x*0E zPIe06kFS$Fdn_Q!4>?__Jw?a7X?FvMQc*xzAB$c!;nc2>9|M&+P2#M4}zBsj^{;;ujJ?L& zA8{I=z6SBtb|IA04;2oIT4d-N=oOpH4_m@{Rwz-s(1Tu7DMoWQpaG@CrbFRDCuPz^ zs%gzMp9d&2*Gde?1qi~I5~D22T^8#q=mB!|+FmSK^Mkt-a zG|knxdJ~|~qHgR#kxE8R6X`|sTh=~v&p<*+f33)H>$RPVz4tFa<*o@!jq&Ya5(u=w zEHo@hZXQVRVf5ghq|CFKf38mrIRfirYK@;3q;slrzbi^ODonfQoTJpRu5V7nB4&Vx zOIU%4Zo-BY7j6>e^pYUe0q4mZGU2`=Uz!`$2}w|CR)fgRj&^i7a^CwMa!({pm3O2A ziRcByXsWUBUJ0d$E-EThipKdxgz@WeN={RjT)9C7Syv@jm(LNOT={lca2k0w@y*THNM=#n(SKid ze#WH~piD;8KHlvTTa_u0Ru2@g4iskUkM4fl8t2a>MXJ9IWN@Hwz!OaKEg-VSx0!zF z{P%}(uKY^B=U0NVERUX(6!ET3aq@aM(3emarjj0wcsMiw@Cdi9V!dZ$cCYeyw<1f6&?vb~NRpY_mh{YK;VzBkDCThKEg zQnW_Kl@T__Re;HTZN%>F|F1!MMQy{QM?I@xQ0plg;ZyK4TDbuhMe`#-t)takK4=Te zo=Eogq`KlUeKl`rs^JicGU3i>Qj6H!tRdA5WXkjg)SYn1rWgmv|K_%Fy|qoALU!7z zGlU~yokq_xb^hB3agnMK&tj~1mitUwmFLH=h#${3;c(bN?Mpzz z&T42yi_Vi!NE;A8!3JG-MX3Yc+xSJjn#ml6-thuofbZAWs;!4hX^^?9St3*6>$8|? zO>ZZrAQ+2ONk&?r5T1Zmr!moB&hXm+ONHK$x+)4R+z3|3nQ=DLh?EfAiks+vM05EG zLl~}{l%hVpK^^g*U9lg{S67uwsnwEjRX8B${Ar;B9JNLjWQN1SnVZfbgF&gl{fwiNLy zfiPX$+(LBZ2I37Ti+}?+a#_fYi;C({(m7mPWMS@BgU3JI3^8!I+?W9TG&rXF1-Pm! zp}b?vL`r$a|n8h4-snM!eo{85RKo}f{kS=DY+sAKFdC(|>oG&t8q*2UYh-N*`(h|Ol~qe+WV zZkx)EKoR0@N_^FKmXs_%$U>S}2gbS9@z~pJxot*q)9AB-TBo*il;OsDWfEh|#AoB@ ztJ)?JxNR2ej=zMPx2ekuHdsB!7z}ryb#C=69>kv;aBZrSfkzj#vf!Y^rOaalr_g9 zy{y|Gnd!DPM{2rxqDKfsu08;lshPcw(`>UTEW?}32UkLmRtL^}>ySf%>1`)Hrr1Ma z%O8_qXICJZLOeC*%RCEqrQ+-#nf&L;)i6(k_35bzeRxn+@(a2YFVBhLN${X%Oe5n? zs#mhwo$2=N!=T=PRP~pcB?P2~)Z)itO-1S~p9_dRh zGhD4WiXJ9H)Hx)*_hIt4y<>>ru_a{?J?D^QUF2h^yCJjg;1t*)(Wx--igOGh^=)p( zPiqTo$3IG|F;p3I$<_tj@z9?J$~%H!u)H<3xn3h*euS0r@;@5>wtGn2$~>h1O6>%v zboY9_#E{j<3yB}oK;XvCF**Fo06@!%$gFeE)+Pq}FGwT%3&SD^h|Z zBDYe1u)XE>FW&(20)A2}=46`a zWD3Zm;jx$vT!pYR8H-3fM^lwCErJtEE{zF*4}NG}wtmG_%|?c0iN%Jg#-k+BwWqf( z-L0q87!{71b!tUsZm;!r30%?qoTiayX6{ab8i+-6uzS;M`+k!oOwctP_B-W-Lmsuy zo{N*$PfN5Xq(99T>_2{@m7qJ1YWGnX7h2-b`bUj;cF6##(aT)jn6V9Rhdx;CAuZxu zZ}pgVAQx7B+uJ{WF(^>BpAkFj{a?Hv*znY%Lm{iSxh8YR;KpSoFLrv+1+UO%jRRjZ z7K2iS7^yZzLKwbz3lCM7=19wG(POAwBoQ+sFajql^{i2!Fx)UCwhoP%v*|FbTN>=9 z>m0{O#mEsALyOx!lp`vLkx?nm;#I3@RrK5cG%yiP?&UA`P^ed2JF6w@X4r`G1#0f;Hy4NVv*VE0a4pW!>79D_<* zfp2~x!qD=kv1m1|=zqhfE1e!+AUG9IGXkH5A(Wb}ZxQxB7nllpSq={! ztk?p8qktx}d8-3XVw>sODL>$#rEP2A|Nn0`M-<%waw(10U~ivI8IWo@wUzCc*43yh zk5=;q29ehy#4rVqYSF9~veNk&=8c9U38BQ=^Nu>GLDksmNwrPG+ufx`ghk|7k3({i z?2tC9ZmG23b)rqGfs4AOQ+beeb~20t(e4laX+&fxXfj<}|D^R|Mo{4zF#Wn#}bD6SCv*wT>25f}nDxc$fR7u=Vi3C*w| zdWl-WxW+>f8BHKx6xT>2n7Z81Yp5uw(i0b?rHUVt(ow;MqCo|vka|~vM6iTWrdEjx zd!0|O%SiZE_vBQSTXwCas6#+d8D0!^$)B`%@KCUapn<;Xm%j8`5k2ziGs*2lNEFTjc%Xb?g}Cx>tShjX z@e!KaTdxjrv2ze%QJ^mZ)XHn-q_GpaxI<-AkS|9kV-HDLY+o}F9FxfxfyMRj8Aip4 zYatIb7N<-od{X~ZXdjua(ft-TiEMzQPQ#8W+K@d;MX30?4^?Qv^Dc`-q?rm8p8tkl zpL;yA8VYTZJVG>4d~V#SWPsU3WUTTVvPY?$D{a`!({N=}@R0Q(C8zP`s-%E5FoOUX z7=1}6ZpUzzMCv9u$k|YY(ly%2f!!sB3N{2omO%N%P#oQ+N&;sG6Bvfc4x93XhP5Kq zD)zUEQ3y`1qzQ^MMX=F9<9ckczpsZ>97){(NA8DT!(B>wM~JDC{iK;TM+GGW{jJbP zAw(V)re|_ea*0qIskD?^LRzO05|S3v)(X(0?x| zH9Eb~XRh_eqTp<8VK?sZ&Au6OkH{jexk&P=C4^m_lp{+FsR0R)ht*FkKLgD@-cPo|z7z`VODgh1IrT7fW~2O9;Le|DsktNzyj zzXV?JYR4qgb_cEQ6-o0t7f+d^*GQ@&P$8|N-EdMo6svEKVRWE`bQh`6*s!W#)`BOM z4=Xui{MKCzm^`ReotZebgOvY3p&r!?JN%>N^F=Glz=G1H@!+!3!T{B5FJ)dmQaYEz zLIo&H?An{gwv>^k+--<^^R3d??Dzf*;F)kLSQT!1faup9804Y|jlK2%*Mf zx%qHL8Fw}foLc(ed}biWu7V8}9TAA%?;EhA$J)FP86Lq`A$k&ZOH7XUKMJ7Srhk zY*)N6ZC+@&#T@lc;5GQ!%BSeXS>Q9Q!4Xizw-7F_YfWcC1lsCU2Wh6_GDzAJ7Tej(NptL&J- zV0L+n0-uU@4h;Q8-15m~8fJ8pf>dBFg2?BH;nn1LE?X9(;4J?i^ZT#$MAQJPC{_A6 zmH14x6pq>Qvze%W(Yq_1uXQFQct!zA*=;fM9_fRm3&0rCbRklsPE4#uLWU`q*=I&=rCRkz*GG;p;4?UlxVLxvsB_+DQ%%<= ze2MCWJ7-t*Eh97qa2NhrWB{JjrNOOV+OPXJ+2!MxEp;v@&Kpa2|p!1b5(~y6f1Q66y=m^1PSP57e2w_gVa#)z^>bi+U*MA? zFXpdszZv}0=o{Mg6gHRf&CQ&@-^`PT#Rsx2nFY{NVfkDh+r*byR;Qrg1k0|Ctk~?s zpVTv};EgTbv(+l08^>kMvE%(0u@gshBC4_j_7CW; zp1m(6Y!&Q;rrr!UlIjT4L+HBIY_hI=DWzMX5&9UB6%vf?~cF9!h# zgx$HhrSS-35?7+f!8CyZPsz{&NH zX#ys6bQUu2Y4C|_#K6o1N?B1m9oQF<$+yH|M*h@uyLWQ-nH>y&Vu=EckZfkG zEz)dleS&7t4OzJp3kZtb77qu(t9*$>bFo^xmNcvzE~nS}wQC2hPc!{ysxm5st7if+ zxmscge?}s~GqHfTY{qaXM;5|S9_Z(U;vCrh#R&=R^Xthj0k20LDh-KFG#rA=b?6Q@ zHZrkAL#0gMKP$FVCubk#wca8*AIap+(mcgudoQlOJj@=wpbyxN?r~Bov!$^y8Yp0p z8)XJ&$?VcG{V*v^&8`uz2QE-pU@)SfdazC$KA>G0Wk-T*+0IU$6KrF6%>g~bqewe= z&j2JOSOW1UPcsnm!4YW%XWMZTnU>VvhvhA98Ghmf*C%QZZbJ|#6axr$VSoMth@UjK za1aFB@!7G*aSI^`@kY{Cl7P(n9rlfTc&$SZ{~k;~rMz7iM`|?1b8EzNc&TcyZbR#l z%CoFb^2i4>4x*AM93otmnG2nBpp%qJ(tV6H{n9vJTLysWk<9=wXE#>x)iLM4aqt{0 zf&(Ruax(;4DUw0ZQ`K^%7H+m_cdi6ugCp{|{~%#aGhCYRPDXaRr|vqM#~rA(L4Y1* z8w?mIz>Tl*@7_V{niuP39lhE&d2KaqK$FU?g~%JHdlzYDc6vE0Ds57=r~;>gq7l!E z5S(gcujSUtQj0ZW2Vs-}>k{EQPF}3d3Vj2qlrsD1TH>ti!bvD~Fm0it|4Tkc zzG)EjW!kuSMMpj8^pwaH{vc<$0)_EUJQ7a|R9oziM^B|T)(hCB*Ce>R?Q)wrhx6EW zzvhcE=Nh);GK)E)pX|{o{hMu=$%&!2tJY5;e6PxK7LS~cH*lv?DpaR-?ra% z*9+o$?RpSfZE((c7oBs)CR^;V-FDZU$-T<}NjB`-LL~DR(~I|?m4JTbiT2cxRu>+w z?ie@uB~61E7<7Ge&%G;b$q*sGQCd_B25oBc}}tc$~^QkZef zI#wafX!s6Ke|00%x`cnkQ%HDym%0pAmpGk4uaWP|)}mUs`9jK+nnMR>w`8y}IRy20gEI3L=ucJw_Mbl%!@5j3`9EK? zxtoE-A_xTD#-T`?tJ7y$XSF-8HDdbFE|#GjT1W`Z&}*^xp52@C+)z5QrG60>hTx>h zZ{K~_Yi)}vyLQJ3wPgChS76V>jqRC2$&y&F%&J;Hp~ZM4oN3eI%hgcUCRDN;=4sM%EE`8QYP4OQ?^B_T@%`}<)V~^7S4MV8W)9CA zzV&gA+dr^=0rGextp}y>#w(uqi7oT`81r)Tc$TkP`;w zHRE*q5RN|YUwgG_nF8LwzB1auvU=fNE3LD{%O6S*M$^VeCH;XPjogcw?_63UJ9N_psn*o5=( ze8zNgNvA?cAf<~BTJBaE?FZ(`a}+7w6u{$`i5%n9p&U$K@2a_MQJL#SYD( z(yJT3K%q36Nx)~?!t%POC^Q_JD%}kmWy;yRC;$GZUP*p@WfeN3R4t+-2HHE`5zWmF zluE|F$d4#t@d}FQsKSKMGk~OBlWy<$C&g^eA^v#RH~|E~=iukGYCrADhtkZT7D^cC z?yu9;T6@G}8LFhYa;8otRiiN%&d=${6zW_U@FhT-wB8Vg7U)q10evDqj{%&IY~%P1 znk4B$rlCkv;S*gP$>BiBj?1Uqd5-i@&=ME}7ob{97V?sDwazvcER;yYB-)VAdlDrU zuS)gP)aQoFEGfo<$I1!E%j~I~F3R;_SAiE&z=`*z8pfKFpY$i#b$ISFT7$rpT#&x{ zn{Q#ki<>IDl=kAYR~_|4SGM!G=e#Uu9GI|V!%@bdK`Q3h)wNQd>S=O?2fFjUCr)A$ z8euS=#>jj!iFdJj?QHa z3<_w}ZETBE;4Y1St_hMZD^iG%2OSgHN95daNX%AMn3`pILNn9Fyb(AOh5WcEC4M6= z)|fB)HgXgm1S#l>J1})c;tI9+qoXO!YN?wTsU{-qI1N5xSdS_eCWqz4s>OT4-geVj zy$uOA`V(+`Zhq1u4CH_nLYA_{S#yi(!Dey82a>I>zJF$V*5#rtekg8;?{R52?!N@A z9ZlG#%><|s2;W{=h5A&w7gg?FPDJC07+uDHuN+2D^eCE z4NLK9ATemjEzVC)`o-RCmv9yt!UsUd0nUgG7EnIbQ zL};kZuQty8QO|jL)VsnWgFU`R8-lgt2_MT71;0_)ctWh{hpw zx-t7Dspb_@PXb|?4We2@uCC(I;#()_P*32se<*($j3O5^A`fN(!5+vD3STrIB0*QY zj?A&Sak8fc-4ntLi{a zYP$3piZTtD&tO{Cdx6v;1Ou}9zKADr$si`cYg|wfWxdGFeO@A)*HZoz(9?lmoT$Fs zm`Zth_w&I5pu)R<+OU+oZogV;xZ^?fFbCp5rpQ2pkO?x_gAR_}5XP=1)`FIrS*{^E zidL@I|G)fgbx`LwrVs&C>OZtQ4Y04*U_15>8Gry1fErXbD&|L4Htf%u2MiiGkrFyc z*#wJK?_mWjb~wJ(956TcqcIQRTZgZ=z2me&sU%8MBv_OMj?LhIEBBmt$DRHlPfHp1 zP;=I9xB8#p8X00L>N95~E6Bk8ZAkTll$|*4@%$)4-VnZ=U@MEV9(To|TwCwn?0E0a zjdrM6ievwwu2??0dhG@G%{}hN>bnAYbx>CBpv3nDO1&opnzBG-ZX^51ch(uTerXpKpc|GUn#-b+Ba}bIsOsmY= zR0}QD9M1%x@;jkGswbs0?pzrz?6rm~==h2^*ORGamKe*;ZO1YLP;+lr{{#g=E?Hz* zJtyL?S5vx^Y5o&wIppr^wF+-mikmpAiDdBNr5X(CsAYfZ7bKa%HCth5+7+axr$RYD z-+6eGmM)LmvUmy@_$>Uu(3faf5=m4)umV;WuB=H(Gy~-{Df1vz?}zmaOxp+|#Ez2W z&&i~p`N4V>*{+1(P+D2f_CpkHE>Y(env2Ei!KK&bGs!$Xh9q)n`gN`%hM{NnV5~gO zW!nUku)F_S{v|v8ro7hz3c(yqwiyuByL)Kf=GXC$b^R3+(>NW-3j&VJ_~g#9;8%C4 z$aJjv9jGI)xR~3rVL_BtLXn(Tv@D^simOi@m#KQX=GEu(bVY`xqv2NkgP!0YYHw?D z@jmn8Hkc8p$XSJA$R$!BVf>Paha2%1Zu0hM6Y9C<)~xCb3JC=Q27w@;YUuoO(4kO= z|FX~Sr)})A9XqdPT>T03Xu{wYC=ztx=B(%*%%wyYdam5Ra#X=y2t!A|$so$ddq4X~ z@QuC*rlqCq>6*j!VaGg%YS)(aJ%__Hj(za#E=AS-iEIYfa2v`YwnTq=B;T9B{?(ak zChqz`UP^5&xB0@m?>6ei`Q8GwZWcbxhQC=p`ck@>_K@tif*NfEjqIVxoCal-21vHC z8BH~i?6um-z^q0b$RaNZ??6&n95USnPiUvSiMUmX05Z{+eavN;UN*R`9&MA6fch20 z+EwzSV#%+!e)G&(W#MxS%$h3Wcx%nnLDP}<`yG%#>DiNc4fc3;xKM@3$@5`OY7|vX zH{Hl6rpEcGdbuGj*Kg_|iVOA+J7f`6Jec4V-{fxu7yk&Q6)bkz7c;kKLgDyMe4}=N zLID%sYFzs`{Y~?2G3=p7{iC;{x1A#&Ye3X|g2e8;&K6^z9;^L-5z^o03# z>V(l})Qbl5dR!oh27#WoLwchlKB@!H<@~imP9XN7BM~2fJr8_%fg0OKX;v{6VKd_Z z!2iTMM96PNGd1=&7Wm!&Yi|Ot8qkEjIwfMBQXI1y#q~`+Gm)Xt2i~pDBQ{me83;r9O&vb;KIr&Ee|8%i;mE zHWlggf^1WlurQRVg)D&d{ZKDa3P$gK^v*K%JcpSWH9kX5cM+1{s~Ma}K!UFH&q{wj zqp^q6ADhbp5YW9^PG5nx7$1R=E#dEuyrNy_1r2n4yHao5+H?N;Hp&_CRacq{GR@TB zo2X&xCsoe#X{}{+X+?1WX-Lp(cO&|m|4+{CJ|Q@h&nDt=6a4*_xyk{;PL$LjLML;hM3? zN)~pdE(BL{uP2O`Zz7+UuX1X1Cq4V*(8Dip*zz0A&0$syJOox+C4KAp1$vkD{u~2J z8$VgT-t&u83$K6R`)KHzIVPk)+vY#6NqkO|z3dywZ3$^qfw|lLzM zzn2&7&`7jlTe~iwR9mqgY8Xa~iW0HtHicID;r`+w_tJVe_!=_+-c<2T*OwC5f_9Bz zeSuGJn-LDl_Ph#LvqBNnp8^FfNeYx0(B&wz7uc6VV> z+Z+QN5@WrRnxSNJ?sR@>6>t5%w&vI4dyuOEchz?n64Es9^Z7cd`U*6 zCMcbdm`oBzMAROt_3tj>^?{GmTc7R z;$^kqNC~2u;16+`w1JNI5>`MGJQv$-N($q5W@vt!JSDZAJQ~~fjR)eRgEPljJc9I_ zjAV4)w;91sRgJ3*&Uy|Q953iz%!=Qh0L)4gf?PoRTWA2Q?)cx#mFlN6Y`_9LCP+o=6vZ`%Y>W=|y-N-1^(5}yk2(MQF%jH0w|mcd z+Wx&Lru8?^H)1_N&?KjYT}~#oBKK~bw(L8&a%9>-g^`vQ-ZkGkUjLo0w!1!@rM@vB z!^iyv(Jm-LQDw?iE*P6)60^6+C}vf~mac=&H@ty;dlMcJe60qi*llFQEk^rMY&kjm zI__p0OzAz)IT$VIo7*?B3HPF)QWfwBp6i1INx(u!K!r;sK@_4IIJUh@4=xhlkrG40 zTcKvO8&aF|_+YdtaT#CgD(yR0%aH94*BbL{j*FYZ0sX=E1#d8NM*h0*)rT5HWWT`= zp3mM6LizqAZ*;<+#0lfu+#mmWKafCn_J2aRXRgHe(hWx$pf!G6cftSF_4h-z3K`7< zbk*d5F}?L5F1`l1@}%`TQBq`7KyzL1F;WBKkO)%?i7+BuG3AJ4fVXnpTQgb}xU0wDowN;H~M30_9^!Vi6NF!y*6q5w;~PL_c6eiaIjx zQG3NwI8wqEhFhj<1Ip!@5(uK;O4J+n$cHDec8hTbKZ<`fwbe_HNFU+I)E3T!Jmh(F zP++OzoROk8rRC|Wv{H_8{BE*Fj{Rk<2oA}Tr?a>zR+LbFE7!5`>@wayPpRF#`ZALE;gu>(`u4W1DuQj*Yc^4Zqb- z$en<&aKF{4>?QT1^D;*k- zZPq`;clyq>Z_VT5_5Tgg8jM8fhpEIaMy1*IY@l zbvKDXl&f_|!QIaUH_DYASa;5S8TX4;qiRj&MY5=`&$)?-yO*p@36{v@cBN>8im%kX zpTctk#Cu)ZJqy&75ivBlnkY|T>qtpq4cpP`uno2pEK;tmBXa{5bKn_k`ioop<|$>l%G!cM?Ml!f3nLk>Kvq;vb-CCAR@43i=4J`6*oKdHLW0 z&)|U-upK~B>*jTEDh| z&+=Rsx%fXBewLSXF`PWI`-Cm9Z#66?Hby(dx+bqA1FlAdjlKNa{owK#`z#wibh?gT zht74qgMX}VUDmuUBZ;8cCC9SB7vKkLv=*eiQ(ZI;OI%miG{91}UU?@96q>*rbD;Od z8EcksRlCf5X7r=mQ*s2GU@o03L_pq5Y=`GLKv^HZIDRD`&Vc5n|6m5>?qRc^kOA3y z?8z7oJU&hUpaJ;c+cQfCbGVO37$8dk_|Frd4uD&?oSy%?wZ6q?2ALocIA8!k0P&05 z2ms~GW6^5kZ(ZVfc88F+aZJ{FHb)Y%ggNA~_!L!1aj<;iG!thSl~<~J$~mDqCZ6D+c!!cFaO&jj*x$5{Yy7LDqk1e?mBMS0$@6WRP^ z9nKpi0wH3%efwrZi$o%>Zu6YWWz6|P_V#O73R@X+p*vRZvz|`T-x6@0nD?a9EJ_tP zeNLr+0dE>>2qHGIE;2ld9SwqI90=Y6*@DWmWcoYe{ILxoqFF3))0H;C`@G;YVM*a(avaV;t`+?h3KSb}d!DtZZ zVIaZ*eVYwNy;+-Z;{F;5B=`#zr(h-5?1w7qg~-MoN(#hg!g5oNK&*qO@Y^Vjh2y{B z+H=@4ta~zH{U>{LZGAz)QJu53J_1@0iz1sn^o_a!XO8*?u1P38)HV|(>)6;e=W-vR zH3aBzc-ZPRtwKz5c4OcCB;|@{k^DUC2z(KMbCh>*tKBlC*@Q@Sj#O9Fgy1=Na1D6? z>{77ePa^7}ux&wSLGC6ov zK~hAyeFg45(^?+D4gsyRpqp#S>OCa@Fx3hjl{L1nHads6c`?*J6q5ZU*Jn#do{- z3tqHXpVNpfL|!d{j%-z)U=HN($df$AXk^2MhdWl~K@K9F=U5eUj=~swtuI%Nz1GKh z_^&OKHT>1fB&091BW~l^$3n3`hDvF!9x~-QM->4$cydi3vpoC?pFc>!6oU9&5>lI4 zwT%QAmQkvlYKAgdM={{&GEQpg@oR*fCQ&{V32>h=>H2mopDb+^0$BB2u4l(VGsw*R z!6H&@etzYPNa%sl*jui|leFtND8Yvt*{)L@u+R_SQ$?n>59UZY{j&}18lXu~<1`?< z8F%6f_zb=w4_m%lKD>%;0<@9wFZ>PPYp(0?B))~o_TetWt8v=B-IU8(03`GoKG#>d zWX(A+`@ucxYMa)+BRiARh%_N%=r0KX-iEHEAWIS;cAbQCb#AZ-P%^8acpyl*1Pq2w z5Q6g#D0GNm*b_G3&`6#@0MP3c6kx*-(10!HZj6yR;7T(KfXxI;fGCa?6zOdpvcLwc za#->#gM;P|@L+6V3*r<4S!II=xjflXT=DE-)*cSz)H#k&>Mkdi$15bD`pB8&1V?6d zvf{$ZMB++n)3~ubak!J-{5KSm(|?FAVd82$%NbWcU*F$X<{900000000000000000000 z0000Qhyojh;Z_`_96v}_K~jt$KTTFaQbq<~KT}jeRDn1rG601}FMbgS3W4cZfxKui zgN_yfHUcCAkx&bWY5)Wv1&Ce;AX~M?as{2TZixfrUb{(EKids}RbPe+*crAC@|&L9 z3O`x32%3$E!Fd2Es{77l{Qv*|hGY_lUcxTP|MSqIXm#B-P%|^>v`D!`*QXo_WF5vF zVwyde#ypj%`8IZaO~nGn5qEtNfl-)Pl`2SK(y+lm81ZnK;V`xkO5Ihq+R3oMp;&5Q zC!VlZAaH4H9<5y_3~zi#5h47{nth@7F0K_OK7{WE2gOF1;HNj?R5}_XFZUcsnC0ih zk603B&f>C3_Q+T(g1ZN@+`c_2-uqMgHT|y+%5ylm$bg=Q&IVKHvN2xNm5_Q z!(M{A!$~TY*5*yV)f*N}U9>QQEzbH||5qM!^*X#1Z(bTM17nv#iD+Y8zeE9DRb+K9 zPJRg32gUh$n%n!gV7n!|VV{RSJx1g%%S;LJ5%&j6@qVvB5wrENm=nFcB34 zK_vv>znYe>f6qu7Y4hcP9a19T05pI-wmyTJui)?7aiWc$o>)O z@hZxz^c0bjUJzK8WiyFZl>g`dKG1#c|DXv5ECN~4P%xHs96=`R>|GTdMal)~Div#w z|Ns7g-`eNem%_3@yqPG1D2g$#W_m+7wHiCIS(aq6ZLuZGmSv>aLE0iMw@dfe-Q&2< z5%T|c0k8{P*rtF<(-yn zMwdu9M8*w*q)U!;g;3-W5s^^Djuqq@M1tHG5edaE5easQh!n9aUFq6(+hxc64(;!C z=|=mocYJ_CUam`dY4Fege&6={e?&u`G#RqrhnQZ}(ytt>PR68(Hq)k!QdqQ!Y@UDq z($2d-LLo@sQYzO5I!Md39GwCPAAG0P%Md$F+N3nZT_ETj0em5i#|Qj*kKt&|kO# z(3w%qnVOoK4q{?rGI)awGRPnw>VG-^cQ4DnH**h&f$&3TY@OcHqTFS9@InOs!<=aO zwN`O@TdO#|mO@HX-?Eb3IS43*1uzRxe)>7~|E0;{_C6Nw6IcmLXaov{(yuZ*o{s6B zH*x_2BwPp)BhU4V`Sj3QHr&8|;lpAprsa2XNEM{CC!ku&&)Ih8GWbfH;sq z5H04#E53Et$@zBHv-zobU&MC$KhOR09r(gvIDs=AkAHPuWd{HV#9XwsgpC$+-)uYS zyWrETYJH%P9rND=1`aqFe3V>AGgDbV2*lTpBV9 zfMGZxFf=TLfdCl)|M&aU&dymcz<2ISUVv`s2f_Y7Avq*WY~tenI{!^x?wk(+zClIw zLi95MAYlN7BPg&M*RpP1Sw^<(WMp-Ptg5W=vbI8YRjLyII+^~v%+plOIpUG3hJ*(p zzQ$qHn6*gIg1;}-?D`wTgPP=M3~>~*%z ziUn4lZ*nsa2y1U`9Z8X8&ty37&If>^{6$?j@q_%E4?tB{f1W3A=YfPzJ(CqAll7Bu z7*SEel&4T-(212xr9N#gMKb9!V3`oM#|*=uOP7>hQRBhS3?INC4i2S(NSm z?)v}#CvWx2<7-$f(PDk{M}L%PQAUdvEospoCE6&XjB>Q29Lu&Haq^m%FFN1!z#Q3fJRt#Y{f@u*+yMtqduC% zeLRSV@l^eKZIo3*HBw_WL5>rgqF|?FmdukyveZkx(%aOgi*#d*S(vTanNw!7Nw&`p z*)c!m=ln)-El=gm@=kT9zFXgG9yAZzL;J9OEM(s=#eRKMKiN+W*asW~&H;LW9kdRT zL#`qBuw$4V_KZ*?&JjP4;^%~ZkxyKbCge3mQkhqllxE~v3}J?$9^ml&f(pd^(Z9j|wd4<~V5vlS=`YeDq=(hMbSM+) z_oQc|j`|H67vG{)#v0|_t=f5aee1sHk9~o%5-CU_ymTD+mRNpo)R+GSG3t@wVq$_= zFG*6pyJcpKY}a1$6c|fPJ}$#Ni>&f8?@YR`^Bunnm1a|f{(LQpnPk?8S|iR6YKMv_ zQTm{ArmV}l)3mP7V|WnNvcZRMCfHCjW*A1|lGo}ti_yloXkOz@m~2oKg2_H11Q||7}y4S;_47+Dt?0PE3UG{9Piao)jF8B?91>2Vdp2m!lOP*^7IKSLG?<`3*RG84S+Fs;8)sM-|)MT>EcqEnuRjDSAW9i z>{yCC%*`(>zzVe7%mf@3c4^4E*o&OZWhb`9kvSvXhSuhgRcPhX5j6I?x(T!`_ApcKkZvW18>@u}TJ4r^InWoPs6La-* z6o3<64(|Vz>;IV%0EY=SkzI0`=Uiz4&Z#-_vuerJjd4lMtn$;G{}XV0U=DSQflWER zSYK5^ttRweiuGHMY^&~Yn zKjb%@600SVXTS9U*hhkyDeo6G57MNDzUDE!c+K#_w1D_$G&tIH8?XvSqC(v^YO~V`ESZ;=R7x*jFrqITwV~ ziIX77!P?wOnk)m{bLEnFkOh{h@f>*F8ggn0KTBYvp;H7xZ_Qe{n`wo_$gi#GU5neN zxH8JCqN}>6hQ6Aj0>zMqo5{>(vo*$<&-U217+tg}lTERbUA5Zol-#hd4rn8HrHOGj z`H+wJxKDV{BfcZoC;X@vc9#0jvzi^HT{A!oPBR%)c*7m+Qp6Pz;R=Tkv8Y5cGLdib z3vr_ixgW)ch?A&E)1aoA#V^T~X-ztr^kz7PoT_r2OS!ZtE}NxP^Hx5|Mt&{EkQH3B z0w-#)Y_+BWwXrmySw|_+G7)AdTa9??_E7fr#&@D zXXt#R^Ihy}t9#DGU-!av;=-9f#u=IAGbFofhMVB*o9Lt`Kb13h=tCZ7@N;$UO=Hfv z0INSsdk=1W6XYhhbGx@^3p=pG(K59UtG%|BUE)Bu_RIbK2)5vWre1P0$9woAoYZyZ zzM6yJ`ZKW;gUFD$z#&d_K6r4PM zSjBkI@6&)glTsneC(;2Q+E`jUsE=r^eiCK7R70JT_!s%R*E}F^7*l)J2 zlkD0bXP$8^@(+sytuFZz^n^C?!Ey~9)V5pBoLjIxz_JE6*Rp_%MX^IsO5rB*6kbJU zbB0FJiP$6oRxtRdG$ET{jup=|p@%MY*}^xLhoLX>;my+9NiN{*sr^Z{G3 zNOb?ns_aisX7RB@Exo!Z(@Ltn4X~lG3Y%6k9rI-T!E!3#(AP!QWJ;5qn4uz3obJ|VtYD}hOtc6zT`u7P;~1bqnmNrb4^VGE-!ev ze2l_cUM^?Xo-Vo6&2GW%aw|4?sOFPv+y7i=lv%W~Bnl}5i5tV80Ny71J8|zE%}H_B zd@1gladFqw#9ecjxNExuN9B(81S5;2742=VWGncMlx)B+r1dD~mE3V6H?P_QuXjt* zjB#;qne%eL+p$A-cZ%uI?vG4!Z93`zacHWIflR^&Ja)DUj6I8XVb{bJD4^gaknTk)XTgLN zJ2FmN1!C!J3Z(S4(o3x#@lw^L(gCajD_*^qf{YKQw1GG1aEI zVl~uQx@oya>?q5O=B%3?I%tDK7q8*K&EK6r`C0>VCsD1{Er*lhYCVUJ#CBLC2gTD6ak2^`(IM5}9BL(ESak~z;9O|1s+5tTQ0m?{m}jO&Dg!d*l?j_| zu=At^V*oA1EMNh;Z1_F#yW#w<>0e_aO`oPaX z(_T>si0WjGPCN4*+(HIP3qQY7r~>;mx$phNBjf{yw2_w11)eV`y5$pF z{IE$n3JPClR;X_Y!I8E}19Nv~Fb8z$1NfLbt0b;SsyX<81r1u?>hRgFBltjF*5~1Wg%LzZYU?2K#G{)nF8zv><;V+>;>!t>FQ z#{nk+*8 zv^<516f3hry|dL1Qv?iF42*=?)B#HNSDX=IKJ8X&{EMI{LKgiX@Cr~$OAe|cTC^3T z@ltX=BT#vz7-6c1TQqdjGefg_o8EcsMO)?Eh|gtYCGaZ*5{#N<&03kcmW zIcFs}!`LvhvaMX5?7?7AYqXebkoDGG%_x42u)N88W!|gozFbs>w_+Y%!&RH1CvUqz7($mRe!E z9d_Dfw>|dS?|_32IlM_dM`u)OMmDNILLKKLG1#LK_}7=^XY*3W?{hDUkN873KeEaX zIIhs9z6SOCML$DB!sb1$dRuZRuYJ|eZ=mWC(cdjlxe!q~){6tRv~_am6Myh?B(MWYMOV?1Y_NyB&gLD;-h0uXLg^BwW16$Rzq|K=?+wr zay*t?BmBph&>O!w+_l1$IxHjE6anyjthGZ?JA^WE-x20`=s83f#Vj#Ve|t*J08XLB zifR5*@S!vc{R-Dj)sF8&>75uyW9zyMY1ZH1B;=ZZJ_w7)f2AqtQD%8(AgXKuO5$G4 zf%lrMG1wT~&6WQ1&W)UA8c$^*TJ_T$@;3Fs)q^pOV@@yc8=1t&ye_D0`fKA|)^oF< z4K#|`j31V2{&`U7<%gEctX$}O^XbIQ;x?tScS$XJc0 zH+4Dh#fMQHM|ugLTZJj#N<|N&E$;ocRqN)BE)8pp0z_sGP#Nu(ZU;e{*^?zRMso?2 zQ}@rn8O+$t=yctY|2dsC4~HWSM=+*>G)i*jaOhR)8|*hZ;>V-!AB{Ga$FdTiDWo2d z#-*30*c_Ga0(VAnag5<)eG*_t_>cP4XeFfcw)n=om19BVn{u-9cT&gF(zU%!Vs(|> zjZ#X~K?0X5aKd{U$oH9Jo<#~gxZyGc(T)2KxeBHEC>mHq@qv7aNE^jmy+p1W7wyIj zz2+?bKB%|uA0ph}IZ_#Mg#hJ}rAzbZUL0B<3EvKn&7=VnUqFfW!qI^VFNd-mBb#A~ zMvo>Yl}Kw7Eh`-EtZFGT?*}}#D3kbMY^>AldH;V;_>I6;WPBOd3-x~Gmn_0CY6CP8 z%wR}ugObAnJJ(zZi_V;RX-vXy$9q?Q)P9D}tK;0+2irjPib0(G?=!5ZG6WXZI%+V< z5aSOD^s=KBD}vk2w3cRC39ZCM*=vxR0ldF_U=C5V9FxA`IsmrDLXl>*DW(YOM!EFT z4n=WJ8L6Wsmt+%aQc8+Y^Mh$VziP{N=)NCS(4N%%4rOzh5j(frW)+>W1?h+k%P?eJ zaVLFQ>CA3kj2?avPgCpV?z4hLRKEH}%E5BNRt++ilur<3l1tn9;=v?&y`Z3%{et

@YPJcLdo=^hoy$_*Oc- zflW(8C?)|KXrTy?70|iX2qgG$KR6R~@g63)eFm!)11xU7A~EaKN3HY(RHnA6Z{bVk zkgIDdK&Ui|TaLcC`LoML`>D09vf%@C1?%4!l?E5f7a1IUhY8BQt7#!N+pc6MOeS1| zg{~G}f$?ER{aB-Gc%NszWh+I0cPUF=WJV&4OYU`w!JGAPIC$L23<^tvBuZ76xi%(D zaot~>#Zn&RRDb}?EBCAd1^0RNKpi;=Ai&{izNmAr~!LTS<|Sg~U_(VHVD=z1o?U8(A~mQI5J*Dh5I!g6tu2x3!@3~z}JJ4d*YIQdiuqE|gxuXY3rJ>DcdOsv!W9>dx ztqf+xr8eqX7)S}U9S_@XF&|R&%Aw_x23yYY)_(UyYhwi`fE+6efOt}rA#&oOPUq|Z zf?*nX5AcsqwT!41dGG)p7mmf0dLh?SZ{Imc9lqKf3N&;IDow4o78o>EuBWW8?wNvR zmP!9ATbhBjrMeoUB%*O4!Vossg9-j;o4GRlpN)5tAN&8h5DgTut^c%2FfC z;_$(Q=THJd=LQU*YM4Vt+YDOt1l;j*@v%LhP4-c6G`|b3BrD3OWQBgzcb>sV56Sg$wgPbWS5OSiD{%bj{|N$ z46&8T+?Df0V}qCqm1EH!B!JvStGGX)B!Yl*_PJE+A63fQGez^tc1i2uM-^6FKgyyB z`E|5vfIfJ;;=-9KsJ6HP)`GC4-wajI&|P_=HhvJ@HuZnOGk)?(GwzG zyBXK!z9m_wt3ywWASb9L*mESPVpsCXnP5hO{HxCla3wtTTpu;o^k#8@bZV2~Fx^g;|PEEbd^7FBke2blcp02o$#CR@bg1Z9?%8 z5NF*wCXlz|mNHK@{d9JeG7N<50SQ=3e<*j3sD=;fAe+vthDIg`DY{9hH29^~Xs^VN zg;h+hY=_WRMaK0$m=E*_S`Zbmw*02wJJn8dZ7it&k@`VE3$~P>{S$1em}*c2#m}XD z@$R22{aVKtIVFkM--%>a-arv`LN;C~zE4APv-l$|Rp(KF3s)eLp&01?(I5E#1<6$- zk~*ZA3zK`$Q?(`4VZx=K$LVLq(W(7;AvQ?F>0FO?V8fD!omhS%NtjFrf$U~o{RsKV z#~q{3d%h)SucS3!aY3f#I?MytAV)l`k3m`rO^d1`j5qH*FFYx6+b+;|N?-&saoiGiYvZ)r%h&NfhmHNUq)yWK-9I(>S(YY}6PY3ppx)gITcX_BLEHZ0}Q88U%YkI4IdN{7bxzQMd_@rXP-x82f}U9G@xf^>Bl+qwq~W1YF~*f&G3Eu! zFQT|p=^JgA$`3vHytNihz;Ru-HDhaCb$vVZ_+MSDEKvf*g zyB?HjJ0JcC`535)V^}KtvBus$>>VQ71q#v>dWS$B=*=;qk1o!I*yhxRBTzo(LNYZvfRm!IpFK zy&`8hkJ}XlQD(i!Q;mB2?C8*Bq!A^mjMNhGICAkiBNXlcIP=Wn#7hu?#$hs;DN;gx zQ`=A5j>pglpuCSlHs-byBCrb{0o;7J7uCF;Za40gZv{N|h^dj7!q}BWqD`YfQ~yyn zA1QaujvSAR!C6xipgC(xi8iBt*3SxWTQ>Jjt{#Q4ylP#&I#-+a6EO@oa*OAt)-rWu zKPGe_4i5K_r5tr&r;l6{T1D+uT{9*o)~xLPfeNSN9rI@eG=xqb)!x;U`g`RDe$5~E|dZQ~zBlz)AxkA6g z2Be?fjNL^KRxV8nJLh{Y^?*QS49xccM8E=bQI(?w{M7r;?w9#@c>fB!WG~V@IXhS3K(L%)Z?!NRpR%iMFlBZby|}> z?CgjB>2trNbP*m=EOVdOw03LH1y5x_3)Lz7g!K*sBz;XEjkdD;SM9AFQe%Vh8L$)V zLuEtwh@h1Zd1z$|wlOa>-jxV{pA-?&^xg17yok=xaNx%PE){vVLE{juKHu6!Tk zA>{Y5?W3_GQtXS;2Y>J?W1IdX^YZcHBZ44!H6k^I39)aY3F_qubxeUt8e+kzT0HHTTb>i{EV$&6 zRp)r&InOK4`8qroc)Q?*-fO$a_XRI8U_s2VJYfv?Z8s$s$?N?_sP!vCLE zi4(@82#9H%VRSM%SD`?a8m?B0>U1tFYpplr8d(5KOWexJ9UB{9Yin<3XK8P5O+&ZbwsXfFt-BtRo_KLv8rYn?Yf=za3V%2!{3Z@$@b@-Y%1i6&B$B#EwM z$tY4~p_ptkx+$ihnQ9uj>89hzHVa*W0u+S`am-PQqg**h6)JF4nvbH|5;RLKMYqgy zG%KxSqE;;;<@iWi3i$Yo_UVxg%_N?^b+ZnH!vMO zV)*0}o^IV3zWBn%S6|us<{Jk+dT98r7t?QjWd8U^VZZ<#gT|N#2n-p=5TXG=4XJ?y z193o%i@1Q{As&$U1kDkM7!p}VgH|4G+J$uJFSAHnZuR8(KK76BCaM5?X}2^ZNb=TRw%}6=MbiDpi=4H5AvvSPW#j z<6`GdFrXQ|AtNKp4tVCw6O<$Z)PaFfl*c|+Y{y)SGPIsq9BAa(8Q;^6?`4s_J%6o;4q#fBm^gDg;`6$Fi(mR4W-d!j=2nS~Lg_iPqBFnCy$Jj>BEf zAxXAr62x&fg_j9tNtUyd*!Ue&e>W$Z^VVK{2M(-6^Oh-)kWd6LCs*DADJb+i1+Wq$ z)+AYxatJ6!XeNaL8%dinpU6^R6PlmnoTFf9A)oH;*;DXkt5XSq7V@(}SKkt$QcNas zGer3p0T$ry79_%0uw=$l3hb1dGviz!>msnY@p^&g%~s;*;S;gsDFe{wo?bY5bQi{6 zQO!3Bqk-Z$iOybF!Yuc_N_a7QP$X4yC*cA$vt884asjTGNROB~>xzGfg>g?LFhsB< zb=DO7YS*M2QWz9;`ez;#m0D*&wY}JKwayHAc(h%NLw<-dI2q)atyzs~NYH~Ofrx*~ zjc|gHbqxII1Bx@S6xXUo*CR1(3mS$3G8H7Mve+Dftay@hQ;-i;mW^0Z@AX^EOz^m%keK)ONEaAX%gqe;giM-bd1`WDAT0j zq*>s|zCI3Hi&Jk1U;o5$m|b-_b(+I+`0(a;a%vI}rB1!J&%4h79>(yMS@K<7HSA~I z``qt`5~7(;il^FUSB8W%t|+NUlAjQ_34K9tYq4~Gozls_K5f>?*v)!27V|^L!@X1a z6E5>)GvfrQJKpC{cFeaShdtwLLJx z{Q*1Qn>o-T%&8*o0;OXF9df{`7JLc`boklKM;C4xweB*5c0h|t@eAK>l1d97i{>_w z%%Q{GxUv_9k5DxGX93cahLB=FL+kFqcs~@|@1{iy8`y!4P~)tD;M3AZ*$0L_N_PQ7n+;obmVtFUR1MD|5J1jI#q)b7@tqd4K39ib|xGqO@%~@7ryEgRQN(j zja94wd}00gmT~sxN`hU6tF9x$M$}dZGV(Or84>X`A8}aDV>cH#hk5vUxp5uyqxA5` zM3w~2sKnwl3qH7FDLBA@nN|3JMS3g6(O*jAuf*NCVqoX>e?lK2SFn$h2jZ*4$cj-M zT4pge{L@5DGh2RvPo5ArbDf1_Y^eZTMXf8Qx-->}YKWiRSCxzvS#D*~n>zm++OS{e z|1|xVkcyY&Q|2=`h=&7_knf8_q7LB8Obfk}#^-tt*X6~>iGgFFKMxS8GPuN6JQ}{D0NrjiVC%?|X!ATY zH{aD9SR}WF`H6v8yuYlB03PdipIoL+GwC(iv#VReT_L+k@HcNbzoY764UpO3h6}+Z zlD0U=#Br3->Xs6H`_ITNxou}*axckHtVwduCArOQ7HWlAB6<<_B4tZ*mn>)a=jxm$ zZ))l$_sH9Db59vsBLLtzK=Jtc7o&;gGIGX?$xHJcNATQTGp8)5oKYfoAe73&%y{uK zzT6Pu!bSW;<&s3SX4UXlw>szS%-YsVTy4)|@LzC0s!mwUx}`NdqH+9IUgIs+hr^4F&tG}mh!X(p4_Mm zdvr4gknNIhJBNRXI&TgPWyTIraYwplFId`tb{3tNF$vN-?O^m7Ild-WliqIcXZY(N zwz#g%>LU=E`?(%NythGvVD~GcZ74)-y`yL2mEPJrH=v%Auth-lm*$U?6?7__yPqL< z=3;9hm)Bel#vXCYyLtdqd4tBtYM&ZvZ6`->-Kewn*S`B~PThZbus>4C*Oo-OM0-hT zK#lQI_LODt2k$NpP&MH+5TT2h<_cjpqpeU}_40+eciavGHhk0d0HQv^k!{S0O>OG| z`*qiv^3BUfcrkJ3YNu!44S0)zP#X{wR02T#0NB<}w=euC3x27ZOZgR)A8a$UGTgW% zT(7oNyO4S~|5(Wl5Fy`IAqh6In~#`7k=j}mWCL9tBsT`wFsaeIeTE_OLv_8d8L<<6Tge}RHQjTI?cEI1^zWL$hwa!Pusdiz~p{rwvi zqhVIFonm${-UtM$00Eftn-iY{H$^y|kB9npdETiA%lDW{j!M40v5M`g;T zQo|ZWh}wujo58acPPW0zc0}z6gWqyP8ztdn54`L}(qSZ>f|fJrb{2k3@Vkh5mr?H; z>|96I4cNJftX4F;9h!}GjNAvU9zaVQT0Ml8Cm8k=quMd-8Ad%vyO*K;FE^xrAB?}> z5q}K00YC8knh5FzV99rR}f?6O5L1sb#aPC&kNw!Tx=W-eIq1?wE# zo4+HL7F-}USMfpB`(oFCVNN8#nU$pDgqO3<1Bvh{VJH;q)&V7-LH`F(02J_yJ}hp3 z%=Zg^V7`Cn`XQxQ{t%N3N4G8@eJci0IPcVOjwR?M6yNvqZ zUoo7V)bClw;`-_cOPeeJh=uW~W&FulEkt@DxoGzTWVIi`stbAnbe>f{;6Wldy!DIk^We-G*8Xp=O+@dRi*2{MqNnn!S36^Vd>;5;k#x>nUG!3t;?=Fn zNvht`!cA3GT1Tq7`q~(kHjO`!XNc^Xj7dhZXFvVoP)#LY_1JWLFuh;Z`-(O6 zlITZwbcbv4)-T)yd5yc8rth($nJ~<1Wm|gl&*}Do`O)9`x9;c30FE{&fymZf{-cDm zTD$)=kH{Hd*tfysmMWPkm)53J)boISiu>GC3u2Dg1@*=b^^O|TCJx5;9`Ri}ust46VVqR4;W}?<26#{M{ z1>53zvS{POZam|*W4c?@x@Y37A%o?FF`#9E+M6~ZZP^z8V-i}7#OUnlhUjGoXz&UU zByVw}g%LRAXBIhQ(%k zT6oM*>lRrfIB~ix)`Td0;ZK%lsD+#ymNF>h!llUtubVEc`M)T*JvknBE0B(0r-S_M z<7q}|PK78UxPJusYU~=*B+>-HmUvP#PPK_2;Ra|;?ie~TF5(_y4V@R;x6&zTk=|}X z7SGuDF=!Def6dvPN^MriI7`B4RUKqsHSa>Mo=1-Si7~lp<8qF3l=xoF>|A+GE>&HJ zQIFHSqRY3^wgBVaR_AUWAsREe6st(310YcFA&ndbjTQ<(TH z!`A)C=gwoolo@jtEZMN7V8@;VM^2or6E8!iEZKaD1)S2<(dEN4@-k@MI=30jK&{V? zV;D0InJ}qi%Cwppvm)lq6D(O)uwm1XE!z?biYz;JHSF0JbKsEV#Ho@q=ah9~v)dp( z%zha;9FUpQL0RDr$&PT6FWzazd7T%?qEkJycbbtt>J;!vH>)4~+Wc`TVV3j0CF+|Q zXj$Q?P{lzy`m^ZuiylEpGd@ zpfdyCmlR)NOYWzVqh0)cg^PeX+^aR%01sVL5|cw#z6tgbASKQJ6%{o9*HiE?g}30l z@i3LQy=8u=F6rdIO_?4*@3<;_?0K7f_b>-PAU^{Bj&ygpQGWCn``K{UO`22}C*XmQ z^bw)}hYVcLY7ZMT1`7FKFO`loGRU}ZHg+5T2f}lj$sW$kVWN?)G}m=c9@C|$`M1Ti z6D@7@?;|kiAd>LZNUIq*a&Wec>PlKGgN`Bi0|Z96PO(0Cew7k^0bd7Yhs+$DEdQ=a zFSPKPy_z0m;IGT-DwrQQeWHCoQ^pq+pZQ)EO$*j+LYuE7@u%t{2wJnATBQxYF2)cw;`K%irKcHY|-&Z1)bWXfA z?=#Ph?<@#qdF4|A;dwaYYJ(XpI7pV7|K@iUhH0W4MCpNqMxgi|vgf&$7$ z<5kOgH;w}-F?Ch;k7H%aCPwc?f`DX?u0A4D3saeAf~-ZVt;7%M1g#e*%mkN+U#WOq zA~}~?xt7gtcQ-J)F}(v8ks33>IGB#Lf$YFN@UKBbhEM_z+x5T&HgW^}$7%{3)NR;L zeI0YA{uoV%dRBeCBDAFt97_FWi{-T{_=f+HI+zV-IB&b-Q8fqwXY)(TK#OYNgJxzV zZ?H?>=MJN$EJ50uwmw?ZD(4yLT6U{6)q$g72x~+wO;EN`W}kteYS7HDDh@*{5Tw^H zdn41(m)!_^s~>1*gF!Rau(xBpVJf-plXrzJ8|KBI=o+T6ZUb=pP`0bA?Ze_;pl~)l zx^tN?Nv#G`lu}4abm{J=S&;C5uApKYcAO-%m=y9Swa|scmaRUY}-}wtV6I z4;Klq1_la&l`SoJIG&|S3>`kl`hKG1UQVf}M(I09k%gb$&r!GynP^!x)xOL__(47D z)P8Cm|GN6#K7O(5cpIY)I&=mT2l%)qZBT_7dm5DNcjZ94OdK}{?rO45usO127*mT! zmlh0YI)(9r$d{;6$NA33iHVAhJ28hjq^@~f_`Eiy&^x35)nKiW#}GsxFKxhCZEkQ$IkUxM8GS^tLrUT~>;hE&sl_s-hnCmwMuVWq#&mSsLOJ)~Wa6o_DM&vfkdMqZq z&K$;rB7VK~VblttA4s7k9%+Mq;(-J4TV(JFI}_Nn@<$NYuZq!J*(TtOU~u_Vjo+*Q z%%l@z68E%&mLRSONec|mIVYAXFAx1ZLpPX-HJM3vN)9gAC#uCtxa|{L;93^RCh29% zk^7pf2H86}Z}qDu7Pp_zatELPE1eqsdaRi6Nu@3!T3($FbWafuII&Olt$nSOae^Wq zX3`?sV?#kQBe+!A-22@=FbneqtEvaGUq?FG^p?e~q_)mZAPAkhUZ%L4hbUlSjQz|R znb(f+TGJW*;uTPLBNs69tA1uO*oEO{3f8E3+t+*+!CsWBI*kgA8?lV5dZl6A_8E)# z-(kmoCq$~|8BhwXolV_%(Wt9!3rxe>b0SMf#VWrp4V+4rI#SAQs9c`{HnJtDJTM81 zo5Lx#p*4u&0*Ys;t(OE%S$=C+sjTLVH4(sJNy|u!mC!?t*tV`qPzcMdR^D(Ar6J_q zO4myUj768QV09^NS-t5z0tHwr@7uAZO=C-~{IWOSbz^ed`pJ=OZ9qW;;;{CNWuhyUOOOiBJ$>ZuXLRY6 z?`JK4rR0r~Zz4W>5iQW3RC>*m)HEl6fQ^YRrTs+Vm4yv7&maowZ~KL|Zq8TP8nu;b z5~?dC#O<;WjS*^bAx3i9g(fF85FZAM4**xAu3ryfdF3|r;IaSeoh{Vgh!m@51*ppd zx!7_g>VNtX4;K>6o0Bo`HM>NloChV7PDU=@i9;obaF7KVt2`(7kdYZ$Ec*xtC0qpF z{$pPcMTCB^?A1Lr!JcHum-cdiDkY&j%ELzLidsH&Pf=MiC~X~*(ej6wj1l%+vOO~`9D=GC4fx)=q2?TAZd zRu(u{OG6OPMkWFhyJ(3Y#wmGuSfEBTQu5|m=jl)z1LG=t`skMYo6Ig<&FtW#RgA)o z&kAXJvpaRqpgPH`uaqQCeR529#nG0LNHL}TbW5;DNIV4H+Q$`BSC+-6=Bihgz{F*&W-7Y`>A}jdtaGI5%uYc z;Bd~|L4<>5YxeGz>>b7UOwRvkXv-Edmkv?xHxkKM1Tl*5)bTB?T-}I#=*Nh;rWTutnKP!DX`B;= zmCiZccbEcUs~Z?WmmGU=_3fGfH@WVfxqp!Z%vlz4O<$<&Xo{*l%71W&l(a9@e8#{L zFr(LtmbPYTPAY9pJF~JN|1h-;;!dc3C`3%8E^02#rIy9zcERG{ktec8* z-4(~F8i~DYGL3UwS811Fc2owox|W4@T|U_@6; zt{{uCZXGys+skSOLK656^^dp-s3I`zYfzr$aAe3AH(aGhlM8oMj;NsyEAdoZ&iKsi5w6-(x z*`1Sh=Vab7?EIQ9hbBr`{W!~X2Af`N_Gbb+9yiUO#J;EPMn}-_w8LZ`O)t9)WpMfL z$%b(It1y#C+t*}f0_{r4>yr0%-T#ai@#EJX>HkQ7s|7mbXW-a5ywQTFAC) z06{>$ztolgSw=20uOyDSVqt#fW?qtbU#ObmM;CeD}jozw|c#yfqm#WY~WrMvXO4 z5QZp-ifD+A)DQzP5sp}hjW~#lc!-~Rf{4g85++JBt+dlgH@(CeBrP;y2~R{K6P4)1 zBsOu`*ZvN+t33~bY}JMuYwp6Zmqz9Aql*mh`552pvH$*7)6$nyrJ~K1cveCYhc%f% z$0Ekon5~_})m7@In+%efVg?QQ3TT_HoUTgq$<!n{49hoO9fq*TP+^ z`~0U4)n%TP6$!$X$%xY7cIk=>dQvP39aDqFVPPy-8e)WHC_pfv=CoP5{qooU zFvLJyM5di2c@`;B=8<255}w#hOnRmyCj}`fSrsj4S*zRFw)S+W>?#Rcvz@^Fc#%V76% zg|`+yTKT%g&u#wh80W6>?g?;Tpf*9yN%4rvW5J#X(Js_8VO|OMT7)+ub&Jv?+IKO2 zi1kyPU*h#j&?nI!6Z|#tfTU%%&~`iQw99UL?6uE+2OM+-H)GRoQFgjAVOH55F@(P4%{gVSE)+v4n0LCW8_ifIEjYtIaST%kupz{S zh$Fj%scgT1`&KnB8Q1XY0Ak>7RA&L&-(2|6XH1uR)AP*x&m?+~Cx8pK*8W(!# zZls+buQ6+z0LG2-CGB?bh(C742GDVAW^ z#8ZbA^;+*)!%bm-XB6uw_~#N3)hmRE@dj}w!4{)FgViK zm<3o$1u+)~%mG{pXbN^Fl8eq$IJ*KOZUbmQJ4D5aYZKL_Tie`JYp;OOG-r$mKH)42 zFai236J$=_Aw)%>C@`#v7&;dWPj^0vowbJG*wo2$J9yPfwh1cPBJR8fCZY8@&&F?^ zv6?~xg>upp=K09D-dwt+mKm_iLlXz0gn)P894tUGSa<@AzXF4#&OBWjU(2(;0!FHO zq~m)>$W@92U7HKFR-eQOsTHZ|L3vuYYN2kDyhAoQTR!$q_Qj=;naGZEuOA-9a;tBw z?F;{AG1~+?uh|6`U2@qKEo0*IwYufDJMOwSGA?X$Zbm~6Ua*0&W5>|Rd8a+1Z=Cdx zzXsa(U+T3Hj^ATN5g%q^meFAAKjQ*MB)FV*BiCB;@!TiARaNLfs~6=k{JLLSFW+YA ze+EXnW-M_Nx>Vb=>mHtXmLzZTp>gq%SIT6a{6GJn+$x#(U?Hm2!i0+uDN3{$vEsx_kZ6L5l5DrbPP^>3$6ovFcfdi199Hj$qmDW5gp*EbaK_oM zfaK?PUgfUu8ITrflY|UNCKml(6gxfHkv%z({IGU;auVBhWjnTv%=dA--_Vhs2vQh1Cu-sU#Ka4NU8{hA&Rr1uuQ!LT0{K@wy}aL zzgfA?m;j5w<;MDZLeji%Z|{$l(=f?Mt*DQwilsljD9x=+zpWCqzGrfi7ChrPTYKPZd~lfsN- zzWlPId1+Bvl2v5mvZ}nQs4E((3Du-(N<9tDXy!BvI*-pm!>Vxwvem{8o~oXynOQXd zb}uZUOIIwT%U7)+E9ufTwbj?GTeo3-eAC9w*Fd3Ull9Vld-v5HT5;s&2^!9jpF4ix z(zRQpd5M-AyuQQxN1pa~p5BjmVBbD=R1V?n@yt3h+4RU`JX-iX04-$jM^**(oi3C) zE=D4u%oz<%kZ?Bzv+J1(Es|N<0mHX3F=l`*)ud6!iV@~x3FVX9J6MJ^4n|BOIA+L4 zpvqyA7^hZCuuE5=8}|<)jgt%>VG=)~OuDbO&T`eI6G?ncM=WoKk`uG@ z>D5?Iaq|FHfJ@)`R>|RMVJ1{CxeY4q>szz5g&>?&5=4WAsZoPaPt9c45tH2kCeK|f z7%ty#V0Ltq&5|TrH|ATSLOhJVeQ^X^%LLPYcvkpUuoH~Oovj#|wp+-1^4H`^CfE8C z^vYFeoDqV#GlIZZr&-+(z|D35_Ii7y^h+)byf7HASJbSx*-!9Ej9R%e(Lf!&cwkxW z@@zoSKaUmkVM}kDFO$`uQO&pvCV9}-`e>kCJ=cK%staodCPINA3>YvH4on8ybIV+b z2Vm>ZhbDM>5R!xir2x@V%}}Ho0%QPW0^J`3t!fCeK+~?GUM)vg*yfI?pZrma-*=4N zANwS3+^Y@+a$oXt~jteLr}YTk_@L$8*?pqPGhNj?jN;1f7O5>z1}q=k~u-5hDY zqGb~C>n*3W4ws14F<=l08!mP@TDC>j`9RpZ_X@NS5QK74LUDdhsFJb2Jpv=NSosub zzhQ>T?dbpiefYTm_~_@|r{t;s*m&ka=Q}OLU*0Kr`!h}f1h5FOfi9yU=eNVW<~zSx zw^(|c+mC2ncgJ*pkBU0;b*3|IZlG1wSKHQmGyIH<_P>!f)B$0_v2lMH71I5#d`SP7 z^)WLRf9j&gnvLK%T_u;A+Sd7D=;Y*5O)=HEhZO7Cug^<9oi2JfGC7u@H8@oD&TN=L^e3zA~hCR zVqpziY=gDdS#OKwwlcLeR^W^Zsg3GEqfzL$$bC;LtzjS6JE3;GSx~=!E6v{ zK%2Th2?OLP&0xe+1$D0y250K?N*ENSx^D@CGxgcUSG4LLCJb^?eU9;s63%%1&+m>h z1=MVSb}LX_0OS1#nDS4+fJH#>QUFxvGA_Z8)GoK8kjN*fwmD?N1&G-aw9FMLNo5M% z20D=05-~QeQDdco3Wzb*)W+j3$IRmpZ{%u-x-oHvnbzVf8cgkC3y046^-zO<3;}Vc zjtJO?^&HZ4#Yk9AqY|MJc8UsX_u;5P<4sa`=hB;K9K3SkHNAZ(mQuk0Q)HJ(Gx#MD zu|iDA1?ZL-I7Au!V-&PQR$Si3Bq#LlawSnf8-Ol^Z)9q)EA1Y!Dc4k($XdYTfUQ4H z3n)lFtw%yIATWZ(Ac1$z$ic19va*r?~Y6WJQVJ3!bG?I)J zNW3mTU}&My(wpZRQp5rj6j9|{76pd7dA}-ph1;AI6s+Eof^2E8$I}D@6Kjmdk`1(S zuc`Q)J{Gk-xd(}PE}{n&US@M(h9|p{zG|8wLt$*GbCfkA?RrM~krIuy5=ZJdAgZO2 zjSVN*O}TlEqiYru+Z@$t)D9x4fJ~wwlM(B>a+SmqF4Y!qB`7<&8`;zdd>k(pj3r?u zAy8v|8}Sa#X2w_pMuf;Rzae7UP+qf&b(*nY$jIzq6eTjs^fO3iEC(fFOCi)jSF;G1 zva^z9ODI6-Lx9-LfL z#K+0{<%!?ezevDfYBjt<0QveCx2X@somVg-!J_mo zkx$94A|eg{jFd`_2u9tJu2R~)hzj1jX< zvjQT-vJ>gx3j(d|C5Xp5;L8)Mb8K93E?Wlbtrfm2#HA^y194Pu^*9Prjwcy$ieMfq znPWD_-7kdEcAQEYrk9VrU6OM{+k``0G$QA273Q$(i{ff{)oBx(6{B-(Fybw=y1-IG__hI7nhQ3*_C{_JR|#zZOS#FrtDfEd5|?21XeghxeFEAP@R-Yh!PbPb$Rg8$f?db3w`rK z$jovJDxxODG&eBPN0wP~zl-T`Y3O0o&!m(F?)ZN&;_#pk7JwAj$!EcpTrOgaCybs~ zhRH7Xj3u`WjT9+YAxe0yI%IpD9c83@W0x!VA&3|Lut+}ks1nCqyRPcUeAqYhW@lqc zUXR{n%>;C?@B$q2+qZyx>Eg`wBiq^oy92!O=O{i{1`RUSiwCZ9K*sQ#$8as>a-OCP4gXm$*>sjv`?A9U8KghuB_w3^FkiJ z`>5OM`?K_GhsQ-ZM^j-t&v@T^liA=yUs+BPgrwVe1*mq1<8x*+>EI!)rCK;7vE44Y zBfRW8-zmz!H7hF3$vmS#ZzEuKv5vL@uv$pr0PVsZ#d`0%=7w>{zGl+^`^-$Ov3N^V zVY{cLA3xKD;o^(TL~d$OuqT7?v3g2s$E#*>CHU4AUycsWo5!5Un?n3{MbX9W?TBKmOvvUC>KF*||5M$St z-`(5B{<8SSc?;hV&w0t$hJY&zFjhch#H9+WA)852VOT|&}?3zNPTT(LvCH8ooVZ9hKufYg;Yofn3j`~ zG5;@VS;mfa-)R-=a$#x9&NX(_a;_(Xt!^t=I;Bb6$Caa^GUf;4I(b};Ss#Gjo+#5< z2z%I`5QG3EM@>KXr1qMUG62Jn}S2w~aPyM_v@oe=1&#Q@44%H+k zF30}vVMUl=^xRtHxIW7?)Wfz@1iI7m*#7hL_KCxjPhCUY_V(>cy3y23bY(a%Tg_y8 zcA|b&1~G@ke2yxPh_q?5BE#^X7N$K<%E%Ezyez$;>b3RYVja~3^^|&t^o8(yaJVkY zT-Z_6mH1*feUL`jthB|IO%*`NU5FiPbEGN8rt>IyQi}m=)eMF^7(VT+rlIPa`TGid1LFe1=wAT3|!;SWXxOKhiX zh$BQLZBaTmyYdh<7-&UdnZEM6_F27!FB{2NMz(eSA6e_>%a*Uf?T$w#OY) zPtTUe``{Mls+p^kuRW<;bwt$2bwVAN)sAdZ=vge;s4_e5i3$`_CIhu`@8XqdbX&x` z4@dDn+P=>oRUq58V~i>VZ(6ci7^(^+zEv=9(^$(Z@jLsFL(0p+QyJ^u9`^ogJXeLm z+x}fbsBr@cZVK%0d2`#Dy+F7(=MA(QvuP30NYYnP|@Vr$VH0<^HTdNR?4$hbro}c83 zPqoQxv9d`mz_Q#~7D$p3OPR*;hSt2I&AKWr$si2s=V*r0MMZ`tp+6|6j_2j&RDJUJ z`Y>~VGFD-accnYD&jP(#rB<1dUX9?@)$A@A~=m1P^)+o#3zrt1*KQP$}t_SKV-C^^2el9r4J`BIO@mzOIwb~HW~`e)B#l$L?} zK9s9|0&%$h{T$*Tb!cDqv6CL^>)4lgW}A{q_G7q8QhpN7Fg6_bz@RS=V{Zu|1qOua zTxu*BFvAyAp+xC@U@B4fzWL)L((fBOv8<9ueR$=m)xc~%uR(Nvbh5sv^$JxI8y{6% zC62AXNVFsn7Z6a>tTAJ6Bx2MtIUaJ3cbZVcnj%ZdeOy6zYswuAPPtlr;8~eDtlKRX z`AvWe3#o!qQ<`OlAw?o2D{RYdYx94+=WeM5P}9tu zxpypfyij0;vF5+MeK-F&7`!^2hJwpg{{=4N^EE{eQvKm>d$7>?P?~8)DI5)@mY6S` zr=X4{Ra{|(i*le-g0+ix=UUv6F!(L+xgo>0GKqAK+(2&9e6%!MxSD-Td{F`?Fo%iIGq3JUS}U(@_(UhPzuUDo3#@I_ zdGpopj6V5db~CrRH~6kUM{3N35AGa{R}Ut((*E09soNRjT+bQrx^;Vp4Xiz^y<}ZF z68mcF22&DoW6>3xjXtHw z_t+9GQgGOjqMBOhmlqBWAM>%avXL*GUtj$71+a1n!ITu1gYUIU9N|A-9Ju~D{&Bjo zXt$+t#=>}&d5 z2M3cD$w-OnhcPM9ncT_p<(N*UV2ethzBUxU@5H2Dxg#Z(lB4SW%4BDv>)A%MG9_D4 z0E~lFLKob45SC$OwO6g(K(1d_wP5`pz0Ft%IGQG~b=E;t@kee9pj8Y^`nnkW}f6?X-EZCGecP+PW$-+f*W+in=9j!~oA;5U{{x6tY z6;U<*S@N3<&C7b3w0yfUxd*r}6@Wn!T3WHy3GP}zH#Eis5au=l1`f3V~ zWukBCq5OSz&cYsZxo7#kM(Qs8 zRIQf&*#>{22?851pHgWqYTO@FWG@s}yBH5nk6Mvs<)%zx#daT-O8u8C4rpZ_AP*d2 z5&RR3@I+$D<17jOIlNZBd?E@=SP2{zW7Ch>V{8VTd414JJ5fA=}Gb=EXGQ4R8>~bo;g26I){ZN-E zhU=-Z3z26Yj{7$TO}g#VVOAX)LGa<2ejSFj$*$-68LC;>W|J4FzKU<8-0M}CfZ~GR zyOzl1wb}N%|vZ8jLSBqa!^zoMW$Q~GDF<@-^#Zz{Y%S>zvcN% zflYPFZmN~dD9Q?Jb}CbH1hB&b^H5~kMC%;RQV`6 z!?@t78j9mJn`M$-w;p>UyEV^m_sE)i0|iQ_8Ek~PC&O7AJC}V_mpl8+9~UrV5b(K1 z7E7tMnQRf#8m~;=dUSI$`K)aA^r24z@@#0^;Y3>#R=cBRwY8iMTf1embMUg!T)_!* zNl_fD7%739D|-s$cX12SuIPLkmj=9RlV@?(^qo`=9m%B)7UH9q2bMV|pE!S!Rb9GR zpuDat-`D%|drS3ZU1Zn7UhCndLm)p@2e^r=1sZ7G?e&yA=dcnE*@}cLQH8>m>d9OX zO=?xW8fu){-ZgfwvB3gzBdw@byS)t*Y!?OkRIg2s41q4;IHzn)SnrhT2{jn^#k?>L z)55ecJ@47q$cyf>-+lmQ0S0)x&AJ|x+sW;|91x~qT9|fDgIRz9o@W+*P!_$&y-EH* zqbmg@R(7)7brdXV@=U1{_=0(63*Nodlp(alS*w~i_FI}m^ZtC$d^qOe*%XPM*DiHO zX^sXNM8S{$j^;Rde~K^@9E%z|zl^ z-G%c<3t)GzZ+%i&I?}pbN{ys!-nnFNm(!NvsWUDX!vQRBF_kUvC8oUwV!^e+wvH(^Sb7Myu|;bhACB{|4pa;{iUY|6BWxn6E> zGf~c@laDh@1d7> z_H;n;-{-TkY14pR`19T!O8pYJDfLPa)UpD?4S7)~3=8Hpa*gI;7cwG~k)l?Zdx;8< z#wT#G49qsJXUmR%5k6_OSZR`6a&~fR!=HFr-^m3wM4kSpv3x@@mVBE<_Pw!Y`UEy3v_vn z^DZF?MzMq``dhF)JkA2cVc7Elki`7+9rUu-OYhz9@ayx#*faiEALLrV$r;*7&Mx3C zDAwnH2Fs5CCDxxOS5wp;ub0|;fJ(l^X-O=@Y|}Jto8E(ty$CE~63l8-v}YrN`6Mh* z?O>L-5GLvf7)_E~mBzh@QyMj^CUR>MefV>;KO=b;jei%szsumBwGk(8QO!3wz0O+{bax2P43UqC)P#XbjB?5`jsHhbVfF zvZAoDC2((G2_vRy+U(U58rTbN_6hRhxO9oeYcy(n{B)9!UDVq8xwGk|5X+W=*~c%0 znr!f~mh(LZp4JM#P$wWWM-L~(bnoh2!?j1osk_PFz4K-7zMtn?uUfQj%ItJl(971m zsTqf_y@~7JW@E@aWAuf4f5{XZ<9ZmS)uk{R%5^5&n0orvR5od`S5v8SwS{qU7OG?+E(6ki zDK&q@Nsdq}mx8$`*O3ROu%4Wc-#_2^@Dz;)*5UVt&>jr|=4J)*!W{Y`qxA_R_1sKn zeJTA?yLGi4tUPh!S7rS7f42Md&-iEm6SxO#y#7T-|0mh01Q-562E4lp{5;xn9>VdH zay5H!E?KWtQz`t_72(gcV`_{Q&{7hb;TQ^1U?gb(V9DfFDwH8ZPfoqZrtuh7> z*wm>(o4g}hUn7B>gLScR6p08BRibP2N{6UU`B>DUHF~VnK~7Cb){K~vj?5^%?Pm`6 zf9HmPf^ttLTT%o&f4}2%sK#g_9Ax%vSVx$EKqRKg9Pv={GVW!Tvq&Ib2tImx)`CGr z!FYRNTzC)*!CsOPzzH>dvj4CeffG!ft8#HfN^fUlqX2Dihnt zjo|na&Lo=Ua)z5uXOq!LHdpnekWn@D%LE$a$?PgjO*Fh*PtopGDpjtxZRt8Uu%f6y zV$j%njNHP!)i0&#LbQ|qiDG|_32p4s>6^RUM|!fmA*kAY4b_O`0U=nSV6r}9h>K_H zM41HqFl&-bopwZbMoQUaIFd=0BAxYRk?^y>Bid+;r`WQy!$mnaWU>ah!bmIc;jX3Ko1zVGGa)=doZ@aKg?AF4`Kfnv6bVE;9?{aXJ$5S3pEj2mP@p-B6Dq=rp-7&YQ8a6`V zq5`mdz+3!UGfr}~MBMkF4Ld__0uC0U>u5f39fWwYTikqK7u_U59uiD@q$#RO(|E zi$0cD**NAgsoJj(H1kVyLt4GNBqAw_ z7~I+yNpwjFe8sGtw>{uI;W66ukcUo`RY(wVPdc7G?L4+ZZxUNGTIV>>Rvrk&yrm3z zVKgd=+S&`7Jsf(HvMmP!tZ+L`Pf!RetZ)>#7!uAyiw8rF4Qgopt?iogai~a5$$K{U z45FbL)eLGRTQsB4B)oq?F{*9e=l6 z_UG{84$#i4o1I(2)|A%Ue8;MC3puVfkHKbhyri~qED5>BuhnUMX&GkC$qYW7NEIp= z0D9!HM=3sZi+JHy6R}9K%o@}iEdB*)Bq~-`zd>&cF37^1#RCb3p_awP6{R5OLa{j; z#EFGqtu_bi>0o1lmPeS$y0Be=fdrbLj3;KOP>ZyXG zVrFQuGSqR-J=)g0O}6Y^@@vJapiKr&$~8WnR^!jefFz79X7CH6QBl;^ENpgjoK4EQ z90;(&txiXaQBX6>4qr!Tftef}z0`8V=UVWU*jfr#FOUYPya=La&eTif@j0W)n&Bl( zzcXeHr)7j>xK5V;?t`lhz8!_8w$mx8b1TIvKwR62P%#<}6$lavulOR6C^)=m`J#n_ zRqGd33c#Lw(|6{Pw$Z=(bh2~qts7LklpY5>+JELm>CiqK0dN~R#rEq@IEMCVC`==j z;*bw?)EyOSaU|#cMBA&oSt~RI5xs!nobx^l5q++?dje;6c8*g4{{?;CI{FN;1CMV8X3bIqAvLDUS(vA)pA zNEq#V-ff&wCMS)E$!(PHeE%0*f3|j^|6GidA3N_83p_>2u$~nI>G@&1^#(z&kQ!VW z)^mP{50XfTaWP&l=;6o!0uFecCX|x_|6)#E0fpo*lClM`_(3k%v+9r8vOJc8QYCv*279h_EqHlGz>HC>u|FSKD_&- z?x|-k%=gYj7vV!3)EM{HOg(#XJ`-_b>s8fWai&)uIoN4Gb#^YPt1eHTo#V1Q+}AGC zrWET=$`8x=3&yGVH(mZGpMA#$Hfw_4e|A&m15=TVq)tU2z}@6coZb1CiGj0F(`EqM zVxHn6`S3%+4}AF{Tsrw_XMo7y-gK0d6<~gLQu!DOHA9DZb|irrW3SydmrwD^1#Aww z#;Un!o@$c-nG}ZBjgz}IsXi4_U z*21y|Q8?K5s*yR5ujPripbf3YdIh+9M1kf6Sx7&O0ThSTOle9bqu3AzV?nNp2#K}{ zU<3q+HtR1}GKq%2EM4mSD-#FmQ_$~IMHF=&!1+o;1cbta-eKdreIhWp`pkPnik@=k zFgV;<>QQKHP&YUr91|+Ib3Z=Ze)VM$u4otoAfS0=q)gV>* zAyOU{LLi{}%7H2(XQiR7LT6#+0Ii!7O*EyeX#r~wN_&Qq99UAC-VylE($bBtDvg4L zEpK5n)Op7r03w=eTMO#^z% z*$c1b{$U0(m@{?!te@>NrLjSofB$hS;ieI=Z`;)=@LfOi(Rai3hrauf zaJ*S7VBfL}DDWLWGuc{<1RnU|`i_^cYyDA^#h4wxQ3xS{O_-`&lS)I&PN!yBA^S8c zdLNXWg7%WG*_WCgn~9S{&8cH!ss)l#&PK=Pl@v9v8jGpsNK&+DOq*Q`rnm-5S}ahM zwMpt8JpH45e7{KJlgmQ}0~BZ)Q5ISL>*>CJnl|Xm%cr~I*}1pO0<&SyscXi2DHs-v zVrMfBWL9j6C6^A;NjW3I^Pbj7ET8~6Z%ul#{ZtjB?j-8$Th#Q*1gY5 z)!O%;ReDY#gfz$Hmdv@1K2N~6lvUawcJN(OYt$Q7CEvM0TizpAWkfwU*IW90Qr&qg zEgP`!YIew}|6qT>ZcAfc#5g4xonDevro7l_Du`W9t?6~S!bm}s6RgcC`pbDaCvI)7 z==1*+8O2*^rH&blY!M{cp1~cIa73FVh*qv|y6bz<$`SBmz~ggr>Ta+ee*enSxtqi3 z=5v*Owx;ucS-1DeB}~Ewlm|GE3BcdlZ9OWoJ;b;LMAgkhSu1!04w_4%nGIUmu8ccRRKDB@38E@4m38t9$A#@Ibhnf9UBidsB1nXCOR7f>SyO)xDE}-*7!|mLoO)Pc& z!+P4t(6$|J@SKQSLoy>!66`(RiGqO0aGj>?%a4TCOJZVux(vi|?N9Lja?VKv%OJ4b zzxSblopL_Fe9Z*T(#qV4hR>gGM_We4SwEl3nRE>8JOp##w&YB`FVfxN)W#ptfm8uF z0s>XaqLVv|PXayf2CyxP~zy3zp2UZA*7s18(Mm0R*$j)*ZTz<#7y^523 zzxh z%d@z0vLv7Tv`BMcwn{5x@jcEYEa>$t*nO`yNK=~%SzI|q830;w1!q~F#Vtqp`2RGc zd#ApTsc!`qS56K>eQXhyXL04^h#c;{#vn;!DzLb6@&p8!D5a59Go;e-9PdFBV@T&Y7Hz~Ib~ir=D8}hdxYv5m!>n1appuJbza@ECUW(_ zJjTJn=E%JY;Mg9y>R=w@%way%u|IPEf_aRCb8@1{5)HfolK1-zvm02=7c64vq>AUK zsJCm+MFqn7SMK+hI`bH34)dXoYXkBb@Ha-@8U7VbwBFLqz&yr<^VkV(2}@gNy(&m* zR%#6_TR4+YJj<^WySM15ajkXcG0vPQyOGo__+I16z&ysm!Ji@SYv6ehTP9#0ms1LNl^L zBt$_p#6WCGoX5D@=o{!beT}RtGewXz&I3KPH5<5=x{s)ZY$D}33P`=c{SjAXN06fg z^w#MQU!1{NoI{hGKPs;U=t--lYVq_O>(h8%pTSF==fQfstS{hId1>kjruFR|yfFn{ zNK9K|%CFhtnJVb5*Z+sK-mJVGi;eP54VCgfu&p1YD&(Wdsti5`zMa-Z0BlAs+h9I& z-w!_2addQ-+dlSq3^EQK0+O-g;XAO*=O$^?*Q3(6VVCXfnw~0fVBo@o?=lc7;4^if ziaiaWBjCN%U)KLz`?Oeo{J(9Fv^CHV)D@SZWm5^*Q%nNaW4_v5LW1h(_p0Ikt=VKW zsnDJdG8%y?isJi_sq z_hXHCRi|P5pr%HVk^!8NR$Gh5h!(KL_kx9Z{=lrm8{af=b;UdMX4`VC%?CB(t_H%w zMr&;pIOU5_glrgDNnwOf^4{s%U+S|liZFR>5QGpL_!=o%a^={5BN52+6LCNC@wmi1 zw3=rBI&_NJ=lD*)Qy%l_O~D@2NwZc0^4r0V+BzxW_(U8ANQ8%E=l5E18gPF4--x)$ zEMs+K@`z;36FoJudDv;KodP#~la(V1U|}T%&MQcee~B#XfSSz)wwQg6=JyU1J=kyz zN39dc!ex!#CkLP;@_yldXDP5^<~cHBRzXSlqM$_cQ=(w?xu(Et!j+);E^!jh zoYLsjO=A;eZb%m@Fzb~}(rLjiN=&b5)r^YBC1{#XQy~*e5H=uJ*dr>euryc*Be7V* z3X}vBL32N8c++QEZgSjOpp%N@(KK%~<;1X}xAXnHEtkB643t<#otdkW= zzCFPdC^ufNF1hh?b<6o{JmjAR4OxW@v7`wbkSpvF6;@apEQFC*EMbLILLGA|g=2un zDbR?g?HgMud0SJ3ypp{$wG8NVzicM&@Hg|ORmCoz01Wi|o9=Da^hu3k%FpkHPzic4 z#2>8msbeo?2>fGPHd90ELY~F6`@%&~C23|UWDkp|#mKhpXJ=K9;3Ul<%<{wU-4c(p zNPqCK7Wg;od0MQ0icMxUBNx>~iUN{8xHZ=^uY2!|V-vR&)C9>t9y1>e_M|Kg#{$)_ zYykiN**~XTZXInrmGL)1>;zIcLfbfF<@8OvMthTzgt})8oWd@gE`N|avgp`HInxun z3YNY84v%{C=AiMNg_3<~4T@e20fB+`fW7Dcm+fGHskjK1ELWAaUexY3cch;)VzN0d zpb~3FEnVLU&gAAai*NHCxl1?LR2w&o`RqSC=6aggjZV7DSr7Wnm>+C>%9%WsH`Yg^ z*S=)G*8cWgZ|{oSb&UO~$?I5hd}wzv=LqK#=V8;4rb`8`zQ3o@^s4EEX{l)IAMP#T zy=XqwygY5J0r#i&)wY`L4D=VlooSHM=eff3rUS1XC=e0wIq+8w)=o!|I}d$cyjgri zd`bLp`jHvmlVbDhs&1%ydR7|yfvjG3QPwu=@az!&2YJuzm0cL25P4$Y*ydyVj@=v7 z*51B5OAa0XPw>zl_nsa6JNVP+dh`I=gx>AEtNLiyXwAPAJ(q2%eZ8CXZ>rl{_sP1i zh3*Qy(Y;crfPK$W&k@~Sy7%<#dWqg}Z>4v!_cFch;-J1e#OKBL#GT?^-m$+v01mJN z_Z#pBhX$d+_TV9dI)kH9ouSImW73b(e^=_ruyR7~n%BoR*Kk{K0`bT~JE{n%5mS=N zSD4QRo@I~8$_CV!dfooMSzEV>E{AfVmSUBmGPSF!;NE@q8~nF_oEiRv<2~rUSrAsQ z=g;J4JYG~C-P?a_>CyVz)J?Qo>PRoQ+-2EddBgI#6(O6Hty=B1I%9Rq>Xp@ZYa_Wv zewFn@>kl?Qie<%fHVP&H^B*YhRt@Tt}zj+4bOJ8Vv9GSeUr%#FpcGB%B^U`McH*d5rT*h|=B>;v`z z>+5bi0zn)5j|>!F!Vlx8@JI2#5Jto*xt_Ek%jDhU=j0EHO1VE9{WSe4-9`T$wlYG7&uEzz zv!A(!xr6z&OX5w4Ms($(V{9WA&Scgj8CzRUev_u!bDHDyUQ%1(Gl z&(~h2_ks`e-R}E~-`4N+H~cI98~jiDe-j7{L<7GFP6|FBat=Kb=7n3~d&B=PRKzr5 ziKHU8M_!D4h*G2SXeqikdNO+7m^pS{%r+K?&BktseG&T-7sQ2eOFSMw75`bhcdIGk zkzfE;+L}#LR|C0oO&u)ERvTd|~ssFb3|0I8wf)B<&sNF8yuH8-z1O`$A zxq*d&vp=}?;qZs~!5_cishhtYxL{H6RRe%JK&D703NcR_aqRK<=qtn^#AIH#No@XX`pjjaCwH#=P4>T%CcAv$vW;m z)jsVodo2*L4x`4Q*Ue%c&=8yhnko76z3<*^^g^Zk827dGAf5-{EUv<4evVS`)wv3Q zj7R%H?^2EgO?5*m7&>Zq;gl=9*~f{MGrdj)8(>h^EvEV<_LNpEF4c$Zo=Oh`u889O zOJz!FQ7ge|Bnb+tK<6rC3AK6QGb|-(@CUi{u(V#$!r6L00a1s}M(&xBGGF;$d}8I> zdv8GjEm3smaf$&HP`uwrpz zFib1VGR^c!N7YG|P4-5@%AqE#na3>RVNi;u4Mnvy1Bf!I<)88v%4*irz)aTtXwX+> z<8FYs&ry+|(<*SN*Z;uN3471mzNiRQa3NQptAfOu?FL612qO6W?J1zzR?k}HiV9*2 zpix6-odNp29Ml|{jFg)c8NqPPSBwGYcnmCo0S%tG!j0|HPMIX=u$A8M5v$tXxCQk5Hjz`+SNt2SwYsR#%33PSn-{`NHrfH=vMSS($CT-YZwt7s87pJE)3V9p6Z zch2{M&P_g{u$+p~jEbpZ03gp#K$~6C;Bn&1nbf;FYEuii?G*hun6x1v-Y`L!6?_UK z#V>dM0SA+=8=KCl`W1Cg2+n9zw%q%`hkOs-cJP7W9(@9jp1qVG6P@i4Wpg|7t`a6>79EHKo26wsj zZd3tuctf0t+qyhtr{Qm2f@b95uBG(Pb?%56Hr)>65)Mg&u*ggH%ToS9RkN@o;ItQ7D&ffKD%`NJnl>fVVLDDaGyY^RicEosfIR78#>x~%TGfP* zGxTBr=oP+(I31cVJZg{5I<>4=1gn*E(6ghZR6&VbI#kpqY4CqG^QCuptTAPjk%_&#T=1P}=N zs00MhagP(zJOLV|<6DdTpE=UaGT&Pm4 zy)&epH#G)45hec*1DVD_TR?Uhg#zf9{EW+M@Q26>-HFB^9qojPpI;WXM^*B6RLQca z!xUf|l=xo{i#P^AL*VM?66)XiROJqwQK?y2$_HvpGibsT}^1j6~9cdGRei$JC8n%M$l}0U2|BA2L)B#TVfVi4p9;;8Bti;+lEsR0i z6zyXeKyaal;qq@>44D<5e@&1Hp^AX(5wz8CF-}!)jft>(l|ng4&oIY z=K$HHSnD5!7pw1Z7pI)EjCFvu=gNUQn%)Nym4Hag4mK=FfTRe%>$2wv21u&ey8f`hn1YfKtohc|Zu zQF<9DmLNC*&rpvF>L^q;{e>PF##YG0qPT?2S{3FPQ(n^ z%^n}NU_b;4(EDH;{u=`V2eO*y+ql)uX9nwD3_{ElWU7yJS1eRSxRIT)>ZA%;yND^7 z;M$BaxJu9%7^KG*O%{Mvqr!X#@K)L_Fs+4)uS;b+N&Q>_M4UwfU~V5T*-HbfttZJ` z3l9(u9^#gcnB2(`P>IK(u~_Vq;f+%)Rr^-oKOdNxX_YF`CNOV8be+jHq3#Bn2P13o z^-Y=(vV9yK&j{F>U1UIkNp&n|e`{Ik?C+n7KWEU=_zY~Ld(j4qZ1{;@SV;@;Ye<;X zcSc1ZLUKCjYx-wqCw`a8VO@(z$F}2{XqiT3BY5sCFCj8(4a+I@mNK~U0 zsCDsfg5m)GR#mrQ^s>_Y)=xn|IVxQ}BXH4izTatdjBYfShTm1oY{h19D0!_98VD(U zxvVJUDWnsOJy}KrRG7vyhfs)ya~{(opi!iHLwtx2%6_7 zHa=v$+S-%o;jr)(qRvrK7PR}Zdm6dnD1Ok3N^tx4$T!gQ1F6#z;L`=s<9;zR61v=` z^wOyig6Q!Ium{-2DBb#r*h!xBRpP_kI(Lf*2yn#nm1AB4xw3jZM7cEJZzV;>dp%E2 z0~|1wsuSdLe>!ne8JD+AM#w=AVR)R+cXOFQ$-VTXESGVHc?Sc-&Mf<9K4r*K9Q5%9 z)=zdGO28Y2@=HHuicCwtV(nIs(iIlXuY$)NozO+HOX(*wxbMueJH$ylr%8rbAN=+I zGaB~EU1#40SHE0*5k&=O6*;}jtE`)fF&dx>L3I{HsgM7g--sd*dxzUQ=njH?WS7cd z&wZjnRZX!^&U$qtrtW3h4z16#+8+S);Bp^WSS@_5*fazO1E(xCFh;|Uow>8_ch3%dQ-V2O0tZ&b;v5>s`<1DHK`G84N6|JI9Ju;DP8 zj78xBN_Tf~c*>Hk`<7|=P~JR6x2^xg00Se1xfzyHze+K7$0!_#ybtmz-&M*}=R+6` zQ^2-s;P0*^IHQ6w=8ew}sale&*}xiR9NAA0p8MQ~Y(-KP>Ol4wgNfNihS*jmPZo;T zOa>07WW`%Iu%?v&XSk)1sEJ(ILOQ0kVVZx+H-NlVH+!Afgm zB*_@DLbZvFvmh>?c3|4=rhj$l_e)}}nMLl$8}K9m4|yRu^?wtipZBORZLmJ7$_Oen zfab9V+hz|ry*ESju^PuE!ZWVBu=S2VR8pV;jKTFbLRa|Cq7h3abX9fD`A9A(d6Z(c ze3HB*rLtPFDhF*5DI_JgC1v7C+{z}2!~SfXTs0yizO|#FTdl&W$a|$mJ;^*Ro#~JF z)yQ$9FoGCk=EdXNhn%^leL8omoTmi1@5~9m&-T={W%&@5bn66t@XJ@D$$2CF!M@!H zufxt@UEQU4@wol3#Z zA4#6BWeqQ3wR3%c(Q+P4(=TV@ilI_IeKCAexRXWDAnaT%RQ^6!!?CqS%BfoD;9dZ0 zJgd2#sU>nBM1P;;c7J)aSln^`i*V(sRAs796D>AWb>*H0gdK%Kq#9VeS3;%e1{+G$ z>zYGB7%Ef8{O83ir`I{58sbReoPL8J8xWs-ZfFc3?z+w2nH7NHXHPM#PUc5@G8F8j zwuy-Zr-{D?_rklNfuXCI-}Pf)+Y=;jseH2s!-o9?>vP2eF=(`smzddKdTA|nKL3cF zK@(tfUDSnx)mdzDk)g5X?z%qjIG^9{;9{GDs#t4+<83bRbikd;^qNq#ehR?H?4=Oe zhsx6LE;75wFbEr4W%o!pk(HLcWHGgkygw3-@?MH6k@Z&vSh!iD?UbrVhse;aI)}^S zd18%I{?oidgvSJD?2NB^?*qxtRI&7YA9*W}Xr9BJ4L%G&!V}BoqovoL(WKUWI!70V zS@YUw&Mtw%And_plZVCtmY~|O8yJ+3s20Wim)7rd3y+(;$sdsnUEdQLM(z$+smLlC zF&Sfnae>)EJm%xMq6fF12h4Wk#NGa08@t(Ed`u3Gb6dQV)RQ@sR=^!H$VSMY7?`}~ z;?bg?e+E_CHM8wNwI9>kx5NB@cd$EsC%KY>7*taUYw}F6+C3)P13hd(y0$M7iP!}W zWhwtwPK>@vEDtHl>t^`F^17{07T)FEk>n;aG3vZr4X19p8Q=q zPj*_?nLuF2GM2qr;LriP&87vRPfX&yj$89Bg`U2I#~V$S-DK;e`i2UdVGB4s?j^uc zEV8P5Wl`s8R$q0*VoWVo;jPg=80@3qTB@0)F^Ff_GtdV2pqT)Tk5tg8tCM=zEDJX$ zVHo76rm<99={7WP<5sqMC8s2hOxYax_M}Rm`!i1|-Git6C$I^&>-sH~45k#ZcGhz# zE#;vr0{OB$bq`bRkvAh?@2G&0@5P`#L6FjpD{p?*ldK$5jJ{Z_Ei#D8c z<~pp z72Wp#%QxkA9Hpri@8}X`9>5zse6;JU5eDpHv^b`G_CwSES86YvzdslfgIiW%Gu74> zzUWvN!oX70if!a^0?=%nwqh}q069c~xUO&+{Yvvpr)lpU?&aEH$fPrEe60MUtLCRa zNou{W+AqF=Ml|Ba#lwH3%u@vvMo_9&glk}LyQVBUE+oh{dA~%#9cCX>EUruf7ABxf zx<-XF5CBD@>o}%%UTC&Zw+$0{IvBUJ1q7`yFgCnlj~>hO!kH^eY>h^b469D23bo`y z>b~l5q7fnI*YKIwRZ1tewJ#mMmWJNSC!GK+qCpn3@{q1*9rVFQo1LDpV@~wQ~A4f;a_%P8c_MZySrnneEEC%-Jk81Jx1i*+e*3_qXI^z%yw?0?L)+ zvksiQ?QDl4=HQxmg-)@j9IN-?fGSCYXu+z{Kio{bz6oXwxVbg6u-$_&(7t zl<;$$vBp0+fa%hyN)1yCj({%1EM`*a9&nk^3l?uBtb<+a!y@;E{3r>pH;Uo-d{G@K zBF0x%DQo0Aty|{X^OC^NoZ(aggbBq~QRmEKrVim1nX;tIRC9aJ#w08d*Sv;&vu+;+ zR(v_^xW(crE-MXN7VaiW)td}SYu8M#m&``v`Hr=>4{JgHUok<a~h-*O@fhHvx^KV z=+vmU!J5=aG#21gJ=ci^$NfYujDQrlyiKS<;`%Ae0NQ#s|1xOh!OxocZHzvxFF2GV zTq3i^A|8=cJbw`%8o)2XS=+j8Q!MX5m<|f$u+eq(Zvmz}i(lff7p8MfT^||KCM)Um zA>)olSI4~VS^qRLGZ9Lrlg+MnR@<2=q@@DfDgroKJ|YRasoNu_C0}jnJSggeq(!B$ zjBgR}yM?fR5v7{lbkj}|aM9{-M9T|lya%^?j5XWa%2V3@sPKYzRm^p;3o}#cF@mzW5oYW;dj{FyHN9`m zxg$E`$8PQGvdJ5=c>)RO2-4#NA2u|fUyclUUXqPevUcSFjz}vsdZ}6|f2L_ddLZDB z4}|<+-tO@Pufxg@)0XPD+x!QY}Oxd?QWGKTV z5=hrXHWubkOUYmCvLAfhTb+lu=`^v^hpDwsQN^iXG#5 zBZ~CEFji3G1)qz#58hS76^p7v%G9fz1SIfxK0m-3*cS-@X1TK%HdGq(@pD{nXSP#v zr_|H0hAMeFE=&=s!Id)W%!+yCY^8Wb3>TNxEL)^-Hb}^6{7}k6yMy~fd>-*%e01ec0) zs=1{o2VOs2+|)>=MKMjn0DIj_!1e7N&~c8it{iIjaDfvr>Ft;G)wl!23Szg2jTSD^ z>Fk-)D&Q8XH?JQ_mnmr7=4+VEDPlu{skkg%=ji;u@&!>WfIr#2 zXG%^A%xdO>lcYh&%Og?9q1b>Zmkn*=e+9E}1jlf@t%KUFwvmG~?2L%OximIOR}b)S z5fqxpwg~4duoUz=k+(;$E888Hb?c$--6l0a4D!~}uLnMZ!ic{241Jx_n8i!e{>%tzQ?TKB}=;tU}8GQ#uRYi}&;$>JxNq=ZN^SIiXBSrOh zS0^XGzsKIL;)W^8BM?3p)2Fx~d66N%jw4Fe0eM#%}on{AuN0yDIBhwSBf z>{7K1P77+fj#?!_$&_d>!xx9>aC5|3@C2b=V6b3BCh-l`esKw5!((2MQ+3_-B9Zh9 z0jGVoL_<*tihdf>yKb+TS`LpJ3Hkh^Ftsm0ae|qoI?)pzUvOGG`il-Y5t2eICe&@& zf`x{y;JPpdSs?soiUtlA$Du-xpM()o)`4b%Qq=fedg=jth@0$kakgYsa#9z#2|k9V zLumxwPk9MTCm@sJTCYP?Z(wWWD5C?0FN9=@swZ=?aD>%F5I`|Pv$r|jl9_i00jA5h z+FLy<8|G2=XA$74PBfN8!DKB-BHg@OF&wRAgTC3jPlh=@$1tzI)!H}RBPDI~lFW#- z#s+$1ev^P3jqZVX%7@L5U`5VX+HrU~xS@#Dyz^1ItZ-O-{xenBfH{26o7vaWB=?5m zei8}$J)Q*uM`O-Rn?V!`Iv!~oORh*ZM#Ml|muG!J{%HbVp2Dzjp*NcC>XRCWuZ8Y& zpokqpD(*oI89tI53^;pZ?g(p;9o-<()=o3#;yB<xZHo$$=A($jmaj%c0gpzI-lG`q+^0BggsUi`Vi7QMxXjo=KP znbL13slpfX0aR$-;dgp*c*ant>(s^E#-8*QIR-g9sD-`9jGEtVLd@?A{kktTh8@&% zDpolqQ#)6thM%-vcp*%B`F5l!@A&B}C8g{2>g!10vCg=z{!8CxxT*9F^`W0!sUp5j zvU;P=gFn()8~=)#ngDi$(>v7$PekU0wc6c;ACgZYuS}M=4j`c7wljxN2WzlVst28S z5R?RleV+4qSj=sVs2#e>^ItiomLpf+Op+wTb)lxKlGka<+FO!c&W=m;gL+UQ7(;=X zCC{7u;LQb$!VCec>PbuC4 zEf!)iN{!N8@kXv+Xkqb#EH}&39EV2&Dq$FO$=c8TKO-x<)Zizg#Wt8qe2Fo}suDm3 z=O|}ft~U6c<$ca5xWg~m`1eQdT~DEi6w3~7|K+J=lMhD}*s3AyzgzQ8p4A&l)%ZPw zrj@<-u5ttE)vSfI!3+oFu?6fcGZ}uc(|!|xnOG1}Dv(gpDOcL@a+NEcD z3Y`Rc!hc`xJo$Y?&0c0t{!)O4-SI0ND!IJK54`*KlO=&8mx3-6kraU5WyB0hAr`+kA*rx)~50# zBU0(Ow2pA&s$3k*_+|-*cydmV1p%hdU{wB`&=%y@Ra7D8GCsyK_B=zLoS4X_DVVS| zpTEv*1+rH5!;NT4h=QLD?g|qqGhwZ^MZy*3>ViZuRmjjw?!-T8*M)8R!tWB=RCx8;>h9eUXBpC`_UsU$P=K8ondPrjgN9 zMOFn07$76EJlf<~FcGJ-vKTHgUw*uPzUM1*Qr*#T+^q-KKlHmF9^HU3MV7h*Z0pGy z;Szzb?Udix)A~6w8Yvei1r4ySatJRRFRtGbCW<=V?fAgOQMB&m8dD5n$V^Fh*48h# zo~d5BR%GK@!a#Z(u79f>GQ~dsZBQPv-Ml+7gLm(Lmcl|Cpyohtd#87&|1ucRPgj0< zuxS$b&XWrI$*{uGbT9}Q7~)`>{CyCQW_R1uo1>IIHyOc(QtyaA*tQb0_@8w8^7@Gq z{>h8ZuNMaeMzj}jgd^VcQW`u|r;W%WGsaB}qbXf?OZ6V23W+e?^@)uTM6Xr|bLBdX zA?iT;OOm$}>R)?TLXC0 zHEY`OI89q&7`CMbO#vD#ZsAA{FN7mGYr0v7PW99MA#ShNt6$ww>nr0Xgv%r4wJ}yI z`bSVV^AjIAc5^Jo&&7KF`Uh&P>^Y+|$utH!$T&)#%m+9$DSMr?g#}3|UKQ75c!D3l zk6gM8F~|)f=u(R4jN^l;T#hy^B-L=S!pisuX4bEg5+A~c?7Xk~U3~wCmY1=gJgoeu zmYF7t{}|C~gGjL2D_q_1QgguWa$CEi3$G>$2mchf1EckEMy6kd0<018p*mf~^UNz? zgd7rFu4Wj8JKK~N9TR9UUg{yASF4s2{n_arV|}S)Pj8oh7KiK(_rEBO9P?gK*3s)jd^>x7Zs6q$L$nh6H#*`1c#97Er_*N-%=AUm2I#Ncgs2>0PG zJc4U*BF+59#5h~Y7)TC{Tpy#--sXrYB)CUvh7HqW(bE6wITn^Yv98sBmJI7P1*pLQ z#)@$}IguuEJT_#5HGP=cS3ALN;@*OnA^;`y+@{eVcu+{;brnO8+~+D5mUj`$o2cti zT9@)4EvL!Khm!AN`h$VsLcVg>_9ymA&#RL_iRkNU=x-v41Z-X;w^LM-UA+2UM`qz7 zePMIJ>v@-j?pz#jO(EtLHh#DJ+gW4i#n!eLRKX!jN6fbbc&^93W`>{eUgVLkj-}TV zU)Z*7n69rG3?A&^xbwzJ)&nBxgJgyUM2?rm?;K z)WV6TK%N>e2D4#$UC4UeDud^GuQfk-4d&ef>Q_Jg`%6Vo<^lFkYI{&@7`T^5;6CDl zBRW71f)jU6pfwn_poKjGf!*@8bu}7j(tkv4O==>cU!Wv+ctI$XDwZRvO2fBUUvk$c z!OUG@j-#j{OOwn%0D@1%Ot$VqW~1AfE7cLvw1Ab z8m+3X!t^-J{&ec{3F~#87W3vC{4|P#8j3I9(vCeCQG$}Dev}wG!^?i>1ugG|e0p$aLTqx?z=>x#t5Sp|hHXs6jLMzw=@tu#Z_~ zi_g2OzGMKhdSw(nuqk9_cW$4wv!?EserZ=(G9z{l%`!50vU!=tG$Ls$9}!3Z8==S2^2c0#O$_CB@jsl9enPr5*}5(GL}wC0V=yx6Z&*`BR@@c%X3 zLi&6!4_uDGu{%J)0+x5eCR0!oU`N#XGSepx_um4ML35@t;0>@se_7CnU#L4t-fhT1 z3~`K99c1s3Yktt$?3#Rqxt2YC2D>$=558xIsvbl>DyVFH6M8=)*)JJrh{?T^w4Mh@ zRJ6Wj&K0I(vFjTUn-VN2Z2YZ#^#feMm#AQoC76r+%B=mhl0 z`$0#Vc<#3DnjxW(o@r{t%wAdM+8J7Q55H6Pxte3&l4J&7aT}Ua#vRcUXA?R77+FCB zB@YW68<+|q(X`c!5ws?>-hjT%ICmArG19dtl|}<*zrEPJbX?x86sCekfo1E*=?<5& znE^M(v6DrCpgbQ2G>I&#vVj&V5@)tM_DjDP2wTj(gt*+qgU!y z<(ipyJ}^@;)U(E`3C16D7@hCo=bts!GuK4}$Wj62+&o~zqtbc6@@FbIcaF$57J~(f ztxSed7v*m4HL4B<2MB`uZZ74mUXXO%0Pf1A6hdHG)u60w9hAGZ_4Cgx)EY&CKmD_m z-aGIySuu`AxD{{%O`~^QWnT9?OV?Bjg^4^23oIkUIZ*2=Lt1)9yIqj zsVoM>pcc)4;>prMw+e_p=80d?MpVc)C`u>L3ulizzSv6(*cJE=TKw>bGX(O|IEU2Y z_aaXqGI`9k;xL!lLNB>PX}62wF>E$8ZAcT2U=J>ZI- zaIXsY0)cCw9XRH@Yg%X~OG`^uRF0TF9nzN*-{C1w%aYwwDL;3B49H z;(19=!^w_;V^6sqvCo!SC@G4_`dt}!dkK3#GD8Z9N56&1@s-!T=o?e&O4W8u8RC)f zZq5aT5JwE!u6(}axV>u76-GbmV}G0v8Mhspy}RAc z&TLAw7*e-r5z?|@Vx4AvS=;}{_1{pjP$)D&kI=77(5lbc?Uxha)uvjeMO=33qCCd z*?A(I+DyZYw9>4Nd-2r3(8vfu$`Ih7InU561rvw}(vM*HW{^Z-fc9BT>)8unDrmYt zwprNnOOEVIjVe>r@c#|oZY{4#iZs-AaAol@vdC9~L$l8ORP%%G$5S79k{??X>~?%``@V`+HxMyme5Q z01&YIk?vs>#cZSln2|Q2=Gh)%s2RDszl=%I&dxG25KRF_mY&*;hUMWw4ogi6aV(Ut z3fp1_P^fvoz2cT$l^WnXOGO+HZO3%*yNEaSTB9R*x8A`&5dC(+7{O**&C=uArlNSd zSbPv+vcQczE9I&5=*2HsMz!9ozFsh|7EzNC@FKvheSj-n#Ltvmz18H3KW42=e#S0t zL>O@fN19!u>S$jrRCt4SG}_+mMHL#;eQycX^gN?N_c}kJ&?zKwfp?*zLHBL*Lw6vX z)%2RHR_9uhD$Cbu!AXC8&kv?nOJ>B0aG`~T)5v{)_O%1ZRq>XdAopg6aN-Q`{S4>- zw1`?CzrASBU2#fMWnrT9fLr=cPVdL2JIO221oFWYfVb>!j`sH6;G2jMi^=aqb zHFyO_ZGvZem-GfwZDx^w`vz4gp**2R;{9ChW4RKIN&c`cE}#BFk8GeOR1Z7&QAAdBo#doBP-aQ2T%AS=Tb{XQQ3iNjBpdJEk1cKHny zH7!+{TV^t-+nZ-=nemxreZoEgLm#2@JVc~YiAP<}tT=i#rk_@b)(?+%iYmT!*Fjyp zg{#Dx^7mFRy@2~l{WwfyWkEz9s-V+C8I$I zVR$sn3Vb9^Q#2dvf^Bo3t6C0qX>#_Qh+q+k1|oYGpL3)#CW$0DP@aU5&w5>y&RXN- zJzDa$F2i2ToXM$&-g~-by#XW<@&|G;?Tx@l$zJOiD1IUpch9McZT(G?`)1hZx{a|! z`i8h2jAdp&vhrS96qlt2+eS_vNsIj`A%-^lJ!9nlq}1>j106TQL@F46wMzq+*#qM6 z*eoMMtTAnjyrs^+quLX+6m_5N_Szfm+je z!J%-igE81(L%pSTmw7R)3O;UL7$Ap-TuYk=O(J<5>AH_9=AKp(Z}aztGdtIo~D2Sa=(5@!N*I>F$2#us<*23CQh$u2^1eVt1-uT zXPXsd^L6s$)NDH0my(=)O4r<)>mn%Oc%Yl2yw~9{ho3p@S#7dptcz zY^4;(Xdx(sx%{TsF28{!Hzu>QtCne@*9i-G%vo_HtGM2(wK#&0$Y%iXp%VUp{O>Zo z-XG5{E*?Jqu^mX`PtP^L;L*rMK<605NELfV@_9f1>ax3)A5E9JhCh(qr|c`m=*MotxtzD8Z~efnB9++rUW{(3Q5kpRn@mbYz*TU*cfVYj4t(rM10#uR|5k z)`+EIM`=48bfrRti)U_J(1?wl5dOQLhx|m<3r{~w-W)}^B$C!9KMwGzkr*(de^b!G zoi``e?JFHmj4;UDjKKPt;vrOt6@$1)pQy3&_SolcQp<|U-{%&Mb^63t7+u7`E7P7lAyT(MLOV7iGE z^aR{XC}yRxC{AO;5ysBnH_2KaSkaupB@*_KNElgfCGyxSZ_`XMMHXlYT0IDqZ3xB6 z#Djnx3=u2b`dR~&;X&M=e<0Yhc)_!vv(JW!oLjL&Q3mKT=XYBmoX3&MYiB)qd1EuP zrd7H{i@ZbAsF2>sw0BrdxF9v^&J@wY7;Q!ohJ_cWL2Z)Sc7}$MdMOttq4G8pIw7b- z&pjQ9OjiM9L4qMc=)&g=TV!E6TVQ8S`Pa;tXma~gcCqbWTleNq;|IXivlXMYL;h#= z48a1ogGEw8{Kl@p6XCoU{W;FxvkASLNBCH}3M2QpMcA>Keyihtu=g5o z+X`N^_uA4w|9K^o!I6l6?g0DuB;87D<@{NF4ZX0{5Y<)($_Pv99M6!hsrnfU6UxfI zdSrCc1iVZc;xqw5t_F;Pg!1?JJ75sLdQ!AA1ZJO+vXr!jEtnL`h=txwMiGDi9V18M z&MfxEYy_~CP2(^ZQ6G#ckdB=sM*cAjdc=`2N0_z!|`)z;pBtSCK`HpqqkL> zM3^UdG9DETpp!~vRri)G8*m54fvT<<51k6>jUK<=ZUpTtq_16ixqZApIer??RyN&7 zp%LmmG=Np$7-V*qP0iqE;Znpa_+um4YAV|3GDI?YFC{L&WS|=+QU%#m60~zz+YoD| zm@dX^%y|34NZ-C`N_04t04!1sU)s?zB9+4N>+%_gj(G{rm5GA27lF;#&9dfK`xlmi zZBM_5jlR9_;y+bCxb*P^fwAZgMNebgZWZVZqYrKee!ueKozccLAOFl{(+?)iQAhqr z`{?qlQ3cM-R)^|0>}kon_^4?lL)7C59q(dPviG zq#y=(M9|jmrj-S)kAI4s4(5(Qp{QFQEMUzpcUKf7^J9ALWIsrq67Pzu8*q`|dt9Fx zN^?a~Iq1uCO?boX1LqkcK<}$gT<<1Bk^)xNM7ROI6iw--6ba2{eR8%y5lkRG86@DA zmMS{O$zd4YYa>;^q?*{$K+qD8!@P8e;|Np0lu~ie-6isqwtFOTb5u^@I0D%Te>1D| z*?k`ut7qHCg|TA@)NYN0pByr-WY=>xUZ$j!H!-(zA<>h#{|i)jL1 zfZf4CDZ5u()fCn9{Hg)_;R{AlXSZRQ#I!PkA_Y+Q&_TtP*d$fB$F7*%5{>cJoQqeg zxMleq!efAD%@9}ol}6HolBnBG`I}i0vRDPFxLS9!U?3VQ3&qZiMPp(58_ex*=~} zfZM^h^ZJI2lB#o^S!hQKz^_V6HtItIShx2}e!8`QiC?o%CtT)BFMwDNw?9vJW&!$fg5 zL+paN0EvLvFAP!pZo*%xjG&8?y27EI8VG{r+bS>dHW?V)>apXAsvU_7{d_|a>uT;rTeb`v3}XiAmhh4V1) z2ebsq5k=kO7N}ryq@XMuMoz0(dX+5zNHnBKW+BfT;x8Oeb~w@S?%YiT`0y653zs+L zNy}xf=WB%n9v3$;bCtfus{h+F68WO&+W5u*!8f{~;ZDv?{;K%|`PUqO zkY1m&JxDu~M_V^ghud}72^p;ptK&$0zyBpDVL&4Q-g?sdKVN}yDWE{j`ckMh>cPBr^e-39)KmBGk`K$3gvy>Ljy1CLk z{;xYP1Dg*GzSIh^mS;`4Hs%;A-Z8F2#qf+!jx9Z?e3#Ez3KJk*ncWY|sEPSRFf zar-hB;7J(zvby?INEi2wB`kKj#pw%eijR&z*22spVm-<0;AjeExJ;31OVS*LAoHIuiiHrX@ohhtX{fKfCBjdr}=1BnE}7o%)}{H~FDF5s`+&L9S*YNuu4 zOv;|gg)VN?de6V`%1XPbHn%4OJ7`c`Udw84@%tc(CajWBqAT2abLAWKGoH6HkM1J- zdI@6sd<&N74hSJ2M(IlA{9~8ZsJe|v8%{s~1InQ}-?-SZcfkGi&*@0IwblbfKXD%V z)z2*kQ-aU4^5L=`g#Urh8B@2$uvaGA2E+~|mku@?=m;=wr$X??X*v$%t&#vNBag{- z0k(pCZlkhhh-a3Qp>oSG4}Wz5dbKJt;i=utg4s_DfQ@@bVMgGUOl%C!a2!u~!7M>( zcT7f@grpE%6R>I5voNhyHjIxApAgT>Rk^_PD<4MF$ZDU(@%|qksiI3z^`dl}yQUAO zg`8+4iy9*7OyMurT#a*&g52l&*E(zV##sWid1|v)v^h*deil3UBAPZ0 zqv2c~pi(zfAg{fi8eYWybJhiWkS}&IA z@EUyizDMfR+VUTNW__+1pUvO#x2p%SJZ%fOJnUNZ3`KG zZCccm+3iq;iG;wH9&lM?8{@7VJU_I}j*3|3vr{ z@*@5o9wraL&&u(4*XMhC?+Bbyo~c@`RxM^5$V6wh$=1hv&c6gmKgPK~g|d^)9GJme zHyw^lou9tyBT@JL0mpK82?3DKY%*6OMj^u1a)KzU=-9@k)n|raPIWKI5S;-2vrYzeFJqd5*EMJ{Bz|0Y;9! z7z>LBOh^)in5R_1Q6tpOqfUruBo~|RE+)vnczjy%Q+FJ9f^{m)*!%XYwC(D2ibn1n zZ4qC=hqOYl?fyn7%R4@9+^5tJ6nV>zL4D-vMBJ(w)Hy{mNobYAn{iF7#EZCQo5N`| z%qIU|B>1=*llmO2qnlp`OZ`smE6lAirt* zQ4zq8pREuY4p|DeaS_C&T&EOqrh{sIe8cL5<)pg{5+1RhpP6~s0HNjK55}TZy}CJ% zYkzNiZ!jw>=&aQh9_LNyUQYy{T~X`f8yTg$xk>etxTOT!V4EKGB)(`i$(zt`vjq>r z$TfxHQCGosC700|g{d@Yk4mi5UHG3eu80KG!#6^SLcKNPt4gYbDM3Dq6LBuNIFc$k9KSq7inJaxlk7zBC=X099K*K} zL_2Ho3#-p0qh2<}riiq^^m^NMw;G;(KXKXQk&cx%UDQ2n`;jY&SoDR%4t_Ox$1hv) zRRbr^j-4H_f*%NzLL>vO8z9!W&z4&Wo5YmE36a$DTQHQc)VMD}I;>YDI*+q@kN`u$ zJ>oHC^dg^dBUqL}BoP`xDd(_rm~*D61EVYBs#oL(?zE=YRXy~Da!*eC1HCnhNJxAc zJChP?)<5_`3Y^a;HU(8+DD;O^LYZt9Zn%GH=yvc*YRAx)nB$kH`o$aBg)93x(# zWx#$*bsRXrVQEO_(iDFt5cR-q1e7D^Q~H|!4#4BU0v2P6 zV5mD}dFI4Ba+zb*9U1B1fuetP0S+QMRI-jr>;17IOaZd+M&#aj?+|i_l3sSXJG{EO zt$Gvf*Ntl$BAJ_#uiVMPLNnf2t2E6*RiHth^O~khwn=GYATcz$i?-+>i}>$#t1ADU zV>^`*-X(}DKV4Ras&#Tt8*XAzD+Q5Z^!(wk?24T%tkv(snw-{6>Xs(40KvZ50agJD zF3COD4Ca0#XLT~eB+B7tU^t5;lH5PK`97<<426F~uGAt+&C+pKsaYw22h4$Qye#)b zs_U_|MW|&LKKu^D+08jI_Ykf3gKM5cn}VXTBOoG-@L$>G>sM(M;_uVv#Z?P#)vnCL zJC(QeVz{td0B67Sc8dONez^mkqi6?ue|X(y%b9T6eLwJ4SdI2bv%hrov(8VkCw`H_ z2Drl%dF1G1JRA(FwE7dpS(48NLUH7&LZy@Aj++M5KdHLn^Pn zPT9Utz$C!TDGSDAJ&kw=a#QSD^(>V45m0?6R+p@8m>_v-fdPr(|HLB-^9v>zQ@d!S zpo1zMD`AHdmFjnl0$+zenpMMIN-o#OLQSD@yQg?-`)aFeZ&;GPo&{$KBoYd3OlC^8 zZf7y6Qx32tqDNZ^LlpxV50V@<#8bmkf{||gyIIRNsVs)k{_N_cj@r363=w5}+hyUA zi@t1+=Yzeuc@{bna3ORpvt{A=Kt?m=DyU|Y+T-iVPm;EE* zLA582Q=m)gG1$Urm~tQ{1`fsQ!h(BU=CiLi23~shSo+KFw}i32o1R(k^50!=T;qOdK1Dnlc$jVaRtGl4 zV2|Pl)dR!yY~U_Ruc!F@o>#xM1#6{2m^XtJAfxN@tfnJ@l zyo-K_sCtTcQ;)OMUj3d>*~1%>$m+2HHo(ohbEH3R}!2HzmYLuo1Q5$~j2aGb@sm z!M3xF=}l_Ycz7PW(UpPQE-)}ExkMGkNI*xf$bp5R*))P5V3SIAu$T@EhWClBs6w0r zGfv($!(j0Nn(6Rla!7n8FcK@T)s@cXce)1#;=&7NQz6o04{T{+oRx8(stUFkYFy#W=`%Y`eRlB)`8rO4P-)ORbHk%?**5=g(0ESx+3*3t$c zzLFizr&=!=$tUoPkm( zJCf5ofy_SGw5t=ULZR?stLtKBtWLCLO5ZW}c1LZ{=pO*hPA*Wu!CU!-M>zS8x+!Ul zMIz=fCETL=p?Kr8QOS~YF6+G>epuHY&J}KBU+r>TAC;)MPa|XkhSx-s#fD>gNvY?2=jnK7>R`68Ap4CFj%7@aPT=CYZ34~s+FfEbKx-HzQJDq5{Qy8faF)(r8 zFRhnpeG7N0x8k_M;sm9O!lj8u%wR(9n`|V*4RtYT9xKMH+%_B5f?nKg<=Dbo=^fa# z+}574J5SPkY7B*g>?}ba4BImV?fn73R^~bNe?AFzI{#V!b-s4qeel@6yE*jkd~&AC zPTpO1Ojtf@m@mKs9=BJbP3WO9z)e#diOx#U`B@;jg5(|!wYXhV$j0AQoeReHNZn42&l1$^iv?MD)aV`A9;1#GGfrE>|;(rLrj2e!Rcc< z$TEj?Yd{b@U6o6Ve8!1N9J#&PXtU*ku)~rq0YwfTQve>tI#c(eQR~^i$D)u8$8?(e zU6Q63obBE?ngNsw60U*$ZQ%^>d}zaj(ux<==Z!AL7YrqEia;RNvt0}NUL)8DArR-@ zO(r5NVZGVP@VNEPQh&+rO>X8NwWlfdO;K^W9qlGe3M$=xARIlGk#z87 zQ(d>mZ7lTiNhPOW@FY6}sn;##sXk6{IXKSY+Fo4>gPXx+RxI`Ak0}dnrK_eUS zao@^_++b#+D7&l`z)s6l8(el1wO&=FaA)ocAGZ8w6`%<|x^(c*Rx2|40 z@i#t?!^0G>Vy1F&IexBd2OJ-WGXMqhnYFED)^;~6AO?=88N;izc076zS{!HteATlD zyj~9eRVCkG-OEl}>+bgvDq_cQ+Z)o?w898<3o)+btRBj}`Nnul4Df)%iZ} zuq~jA`iit3?B>B*_?NirmniH}E=oV9Iv2xN{UxVY%|&eUJd8Ew7>&p~dmEwUVZ}6C z{Ew0}#}whec-fYij!L`_x;(Lq)Gi4)$~7&u;qT$!WdMoGyl2g%rUrao=wHz5_ZhXY znC$H`E&I@?set%MUUQX{CM>OAH#s+}&i)*YvIT!JCgYzdov(+3ouQ<zt2dpvdY7-Qo-S$=lTR?Go57m%ED93o7LpJuB zV-nEvVea@<+l_UWN~<+`so1sLBe4A>qvR$PW?)Q60e|{47C#Q&t-WeF(WZO~C@{t> z%VMd^o$;r&X}tD)WJae8j5~A7`3~xS_JB+tsNcx@DD}VDwMVx~ejd_S)b-CnK6OyX z;Q!&ZScgodc4k5}Glk`tN}|~e4A7F&QkIn9uoaWx@;I@|k(3b$xJ~vQU!b5U!ig;2 zey}NT!wq|ArTgqImClEtsSsmM3YMlXixG1g~io_3oWzIW9exLdGZ)Dqux9 z*|t6M!B!>YMqurSD6IsEfgD+2h^n4?a?~P88lAJyo?=0Gp3H2+M2+C+_vVeco%S2A zr?}D0)-gN#@!oFjb9KNHZRvwlYH2D>9hNgI=gK&8-iBRwKKt6s2PYO`6JqT0D@TsV za_V|hgmJvz=votoe4$l)J&jKU%%MTX}DY+-7L;fi&+kjsD?>&6Cc zMv&5piA4g(EE31nDR&?hdthlxilOX0UzX#Z&qufc96lRvkx(+-ymLs4$NdP4LvE5g za}$dUBcl;EJ;Q;-MGl^dIZx~i6HtBzxnx4jgXDC$1ecb9Ip8O0Mt&O$17}YQp}F(G zh^gqv>o!L)OpQx#q|=GGPWx-C1~xiaVlLwMinH1TGAk};0=1ae$*b+)F$3KThuURR z{)o_kXbeU#`Is`*{8S}^U8aKMkpWZtdJIjs9Z*#{jH4a+rW1m9g4JE<0t?|ckbWq) z_>6<;&eg!rn*Nd82Fb&il>^K}>cfz%q-7dX6d#Q?7<)ONjvE_-n6V09GZ$1=iLnr~ z0lySAC@+qOpxeXPO4)=Fi5fskq`{>GU0-TwUxwL|aH8s&I+6N2F}_TK0M7>)`V&p6 zZ*jj}`Yc7Uyr@=i7|QvRBSTZ+Jyyq6K;npVN|8#4f!~$<5nY5oe~(OA-77JIO=gPU z&$5U`CJWl5veI}~CS@D@t9gtms%kxxAPQ--^O^4ctTr4m*0QjHny3!#iVNAn!ND~4 zg}DsD6ZC?894ACBHskRGhS6nuV{S(z#)=&srF1d`G5OKB9$Bc2TCFZWgyeV`ZOrjH zeEh6Y=*tmJ@-wCb4sk-i^NXEk!~#;Pp&CXQLF9xXgnT-|W zW$vOIHRSW{Q^aRAdti@$6E%KA$MXQ83#uFG}K+v>4<~ZmHoVF?4{{c4&#sv|bC(r=*^!wj)191evFX^i^h`y`# z3L?hIhhI#P(pJykj6k^c`e{EpA8+=Nou!u!RP5-t{U69tkUeIo)xohA-yC1`uqcMj zj>V3BlrdyumnZQBEuFAw=!m^W|5VMN%jK|%3-Xkej@MXRlXt~uxWl>Gr|q>g z6S)>(BIltPElPaJ`m4+@r+^YSYW^dxObCd2jY~d_(0YZt0QEL$7%zRF zxnB!$=h`sc$OM^lPN&|Q%|c@K$xsFe(0H`qa^-~Y3hk%RKtf=>Ba3W|mf`4L2D{7Y z32lQ7bik!lUKAs_xB}j^eQ2LJb`F&!X)f{qLP7nK<~{tKPP?SfM) zG?u_uKjn(GZ!QJOaUbpe)U&pv@U8UtrY~;~dma;?fhy;!*YOlY-(qSBl&KbD_U#o# zeZ`x7^qseWx73tB#!%tGCwtlZe@?Z_4r;h zRm3Glku(hqe1S)s;I9kvg+TOPu_YBEB$ca6^!?8paY&u|`SfqkN4$!fj=s0no@M8z zM+0of%1iDWXpm*&f_LgWJk3h=|8^tX&TJmj(7UGgjUUJM1DAq>=3>BY@I@o%eTjsX z(o!kf$yidcQjg0*Eiov9u8$B=05cJ!oW+2QmCCxgfLA~xD@VgorXljioUrNBl#nGZ z5vfwVkPOhK0;*}(C;PqDG;WWHGWqUwkZ*B}j*BSC`Tpu21_>yxv;46o?9sz`z*f%j z8$*iLi3&=v%o6R9w&6&ofCuoTqxCcgJ=oDgUY`oI_)9JBqukl z5>pyBrO-JP1kt%(o$RJPPd{Bc$KwU<4m}FDI6r;nAeTvy$*hkkn8!w8xDzl!P!brd zeN1aHseC>O;F$)&SRU{m4}Pz>&1BXsaDviIGJ39xtV|-=Oqq1m)=WciZG@Ww+y?F_ z^&n*-l9f&Gf}=h7l={nNk4bhF*Tg)g_Lbmme%_X4aQ?%PT4(|Va!u2Td<7VQ^*bP^ zwDDw^h39L4XKGp-R~;BrU3ZPI{(JB5?|r)sAvb~KpRl*_Hhb2%MzN%B7s)V?Ec;Ar zdEVvd*ado?_!>6N0fyI($}|jtux#9hh4j?mrq&8pQE`G%A~-V=nT`opdh254Tw=_G z&Ie1Q6JaaU=DANXNt;*0R3=_mcb+v*)i*_}mO;|Tiqc_1j(TO~Ei+k$fO7K`r{DW| z$MtW0yVrMN;WYB;Lluu^St|U?4gTqo3n>CNyA(;&WKZAcf2;kmm7d0KNHOq1EuX1P zUj5JM zWOXKuR3SUqjipD&IQ80KnT$0d1xE;16U;L&3)c9sgF3Ovn`JC>C!Ay&sU1`AHXeVf zvz{t_ltm6r$og@i*4TUw~RV*PF##oz0-uKrF5( z&^#-5Ow@V<>hn#+ECNJAXq)^5W0e&=Jdi>dS_j_04(D|PN6q~A7BXZeWAUy=Z6M1a zo^qu8NgU_68PkTaCv5!YK9NZn$(3AKPM?w9+*y1q1NLS$MY?$(G%VEPd$@zrW=*Pe zBKB0iA?~Q_M)1bi>TA&8zBAnytAY8}|6oTElA{4=#-|5F>d_*JCoZ7Fzx-eR7w9Vm zL)RK!S^kM5LMuu;Bk5V1-g>W2vp5JL7+4#PkdQOI%up+1;QzmMR<*;(kEHJc1$jeV z@Z+R{1WhH?fdnczjbOxq2HW<eN+gXciN5G@KywIbj)S_>IU^FnpT! z?6?Vgyj`n*pkx!6z@5eIvp8JOZ@zrO*S`DYlD7f%<==i0Zf(EhrR#45eop1rId_7R_cZ}GSO=u>Umdvl@v62! z4QeLVLQlw$9F$h=p7bDgw*~MK`esS+ZJP~Ir*M%~uVPaaBd%89RNOosIu`vcddFE2RsVY#OSEV9fv zCbj#6;jp}k2iCW5*CUVD7W%=Ks!H8kr24xxaB3%#9)e!<7VjgC^(Xfqv#`6s|-oN|VpcDdCy(BzdK8R_L&Pzmo1v zrQUyEC-Ra=IY^7VC#46Ke;D_CBd4;J>er(+Id?tSvpXU;R$q0g4|XPDIgGqh9P+i* zmc9=tw_bn0we8+LA9#p-DX-7Yv7-uLkc}9)D+9fXTnYSY;lp3w@4|Za+mOX#Uc)MJ zEF41DWVj2yY_8hajU`ixqB1DexZ_D48#Z=m4w_L3zA~yvj1o;1IyxnLAr*w^aH<~1 z306SSMx>gf2!lcxiO1xH5e%GZC$0c8^`sIKo)Z*2I5^!y1w?8K_D1t@#uvPh;G3TzJ0DEy*q_{)~0 zfYt*T4Tk?`9~?K}!yi}qeds+W`3wgfhY!BX7UWY-CuKG;`FyG2^vANcnBQqy(~Esw z6TM`*5O50@{h}WChqJ@@yO1+%mrbF*j1clXyS8zc!e|OE%&6qr0oo(1vJn_IX)u|Ja1|c3&j@a(d;1pB0>k{G162{8|TiM1K5`ee>GfuhQ1L(D^$5yZ*_K zRZ!uUq+XDLj85r65DGX2oiek%2<5V=Ctr2*J<7NWwad}Ybh6tpqk5*Q)W8CV9=})cE{5WTJoe?b9}})Y9PKU?8SkGPbfgScGqPmiZ$d*MM=7 z%~Oqdxs02^ss8zQr6a(&Zwq0I{$#8+dd;J{h)2m@`AS(`!8`MZ&K1EH4mIg-=(V@Q z=cw;DvpwLVTfsZ;cHkSp0PAUcHMux(a%>HG;UrC{j!v@x@^azh8187isn04S?b=yrB(E~4-#d%!7rt|b??RXHFeK!m)HNTs$$3CM&v zS0yq2V1%F&Wo%~19Skal!1HdbZGlT`F`-dYDf`tlBR%T^jw3W4x_kvc@E97 z zYwd?R3jthjbeqBfK+FL7g)H6XRMSfBTJEL7o(3|a((XO_vvOGKs;YUYZ3$sN)hE43 zCs13dNEM>&t%z7`D;}quUl69P{1ryA5h64_-sX@BuV~15KV~dD< z8b$ALi*l~R;26L*)Jic^S$Ib;bec}QWq4w{>BdLG0?@XP16^86i`+Uh(fWZ8AOHb< zs{a-$l>wP4CtAEtyr{}qIpd1l}cI;rtqw4pn-YK&l7oHC|!h^p^6>iXcesy5xSRH*7A&{8guW)b& zd~PS_J&t~_F4i6HQQ87b{w-3o9`#89yPhZ zweoUN^Hr1EqQ(nJg^5P@l1;qCY%8}FC6*2v3@aIVgIbNw@WbDleKz~ckE?vgueTd& z6x5EpA7=uW$xs_@<@5^M25*%tmrZNwXgI3MDK44v`MwS1FN=3NN_n~KZ69k;DYW$_HILlJBPVE)6B8+G0-kT+~+GsCn>)d_CUYw#%OZM{F;8a+|8zq9P}v?d|1T z25gi|ATRF$!pnkDN@|>_9ez`MkJOoTU456wS| z^lvFxcV@_W6Lg3Pn=Xsa4l&H)^+e>oJ(NK4gvl+YeFP7b8Hh2Ws|kvy+c{ zD)D+@fVgn)QxlS>faTqPsYj%n8TZD-#r4uO>V^3{lG)m0Ivc1eRP2(<^yjof#x+=W zNht2JTzBzGqD!=G01pC}`ZHX*%&&us!!KAk;bru1+1Gz=XIrsX_`*OAyn}nNap{o6 zyIk0dG3>$v-=1fCL7--PEIbLAkh$54@c7Rx;IDiw6Tpo? zhlm}A;ku7P;dN5&Xtd)Noj8+&00w3p;25I#u_qn@)t zK-Lokb_S57qtd{ioREBuL>M)3HHimW1goOWWNYcbtC=pzZqUQ7E{Gqk7+%L*jG<6@ zbQ}iDbm7Z~PoFMKv4{&j3Yp13=ybsId_y_~k3b?-Vl~0;DV>q#LvYHZ-cFnL?j3_O zznTSlgD52L+WP!O9Kr<5h=BL_?vDnRxx|r;F`32! Date: Fri, 28 Aug 2026 15:34:19 +0200 Subject: [PATCH 09/23] Bump @mintlayer/sdk to 1.0.38 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a15afa2..ecd6c50e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.6.1", "dependencies": { "@bitcoinerlab/secp256k1": "^1.2.0", - "@mintlayer/sdk": "1.0.37", + "@mintlayer/sdk": "1.0.38", "@noble/secp256k1": "^3.0.0", "bip32": "^5.0.0", "bip39": "^3.1.0", @@ -4729,9 +4729,9 @@ "license": "MIT" }, "node_modules/@mintlayer/sdk": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/@mintlayer/sdk/-/sdk-1.0.37.tgz", - "integrity": "sha512-roZgeeShLTVTjyosFi55G1F5mEg4cIxu3WpfKPVPBl44cd9GW/l+lG5+cOkH83DDparq+ix2ZhavP6RlSQFBLg==", + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@mintlayer/sdk/-/sdk-1.0.38.tgz", + "integrity": "sha512-4CzGmEGuq5mSJ9mRVsdFbDPxnhzGzt28lp252qKok1zcRqRiVKGUpVoqnNgXpUudR8hxxFdPGKdKEKSMirHhIQ==", "license": "ISC", "dependencies": { "@mintlayer/wasm-lib": "^0.1.0" diff --git a/package.json b/package.json index e774cc43..3388d914 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@bitcoinerlab/secp256k1": "^1.2.0", - "@mintlayer/sdk": "1.0.37", + "@mintlayer/sdk": "1.0.38", "@noble/secp256k1": "^3.0.0", "bip32": "^5.0.0", "bip39": "^3.1.0", From e231573a60663584f4ea536da935ae3d1c72faf4 Mon Sep 17 00:00:00 2001 From: owlsua Date: Fri, 28 Aug 2026 15:34:35 +0200 Subject: [PATCH 10/23] Re-enable the external dApp API Uncomment the connect, signTransaction, signChallenge and delegation handlers in the background script, so websites can talk to the wallet again. The popup read the pending request through a state variable that is still null on the first pass; it now uses the value handed to the handler. --- public/background.js | 257 +++++++++++++++++++++++-------------------- src/index.js | 38 ++++--- 2 files changed, 161 insertions(+), 134 deletions(-) diff --git a/public/background.js b/public/background.js index c3b82d3c..c452a318 100644 --- a/public/background.js +++ b/public/background.js @@ -42,129 +42,125 @@ sendResponse({ result: { isConnected: !!connectedSites[origin] }, }) - // External dApp API disabled — uncomment blocks below to re-enable } else if (message.method === 'connect') { - sendResponse({ error: 'External connections are disabled' }) - // if (connectWindowId === false) { - // pendingResponses.set(message.requestId, sendResponse) - // api.windows.create( - // { - // url: api.runtime.getURL('popup.html'), - // type: 'popup', - // width: 800, - // height: 600, - // focused: true, - // }, - // (win) => { - // connectWindowId = win.id - // api.storage.local.set( - // { - // pendingRequest: { - // origin, - // requestId: message.requestId, - // // networkType: message.params.networkType, - // // permission: message.params.permission, - // action: 'connect', - // }, - // }, - // () => { - // if (api.runtime.lastError) { - // console.error( - // '[Mintlayer] Storage set error:', - // api.runtime.lastError, - // ) - // } - // }, - // ) - // }, - // ) - // return true // Keep channel open - // } else if (typeof connectWindowId === 'number') { - // api.windows.update(connectWindowId, { focused: true }) - // sendResponse({ error: 'Connection window already open' }) - // } + if (connectWindowId === false) { + pendingResponses.set(message.requestId, sendResponse) + api.windows.create( + { + url: api.runtime.getURL('popup.html'), + type: 'popup', + width: 800, + height: 600, + focused: true, + }, + (win) => { + connectWindowId = win.id + api.storage.local.set( + { + pendingRequest: { + origin, + requestId: message.requestId, + // networkType: message.params.networkType, + // permission: message.params.permission, + action: 'connect', + }, + }, + () => { + if (api.runtime.lastError) { + console.error( + '[Mintlayer] Storage set error:', + api.runtime.lastError, + ) + } + }, + ) + }, + ) + return true // Keep channel open + } else if (typeof connectWindowId === 'number') { + api.windows.update(connectWindowId, { focused: true }) + sendResponse({ error: 'Connection window already open' }) + } } else if (message.method === 'signTransaction') { - sendResponse({ error: 'External signing is disabled' }) - // if (!connectedSites[origin]) { - // sendResponse({ error: 'Not connected. Call connect first.' }) - // } else if (popupWindowId === false) { - // pendingResponses.set(message.requestId, sendResponse) - // api.windows.create( - // { - // url: api.runtime.getURL('popup.html'), - // type: 'popup', - // width: 800, - // height: 600, - // focused: true, - // }, - // (win) => { - // popupWindowId = win.id - // api.storage.local.set( - // { - // pendingRequest: { - // origin, - // requestId: message.requestId, - // action: 'signTransaction', - // data: message.params || {}, - // }, - // }, - // () => { - // if (api.runtime.lastError) { - // console.error( - // '[Mintlayer] Storage set error:', - // api.runtime.lastError, - // ) - // } - // }, - // ) - // }, - // ) - // return true - // } else if (typeof popupWindowId === 'number') { - // api.windows.update(popupWindowId, { focused: true }) - // sendResponse({ error: 'Transaction signing window already open' }) - // } + if (!connectedSites[origin]) { + sendResponse({ error: 'Not connected. Call connect first.' }) + } else if (popupWindowId === false) { + pendingResponses.set(message.requestId, sendResponse) + api.windows.create( + { + url: api.runtime.getURL('popup.html'), + type: 'popup', + width: 800, + height: 600, + focused: true, + }, + (win) => { + popupWindowId = win.id + api.storage.local.set( + { + pendingRequest: { + origin, + requestId: message.requestId, + action: 'signTransaction', + data: message.params || {}, + }, + }, + () => { + if (api.runtime.lastError) { + console.error( + '[Mintlayer] Storage set error:', + api.runtime.lastError, + ) + } + }, + ) + }, + ) + return true + } else if (typeof popupWindowId === 'number') { + api.windows.update(popupWindowId, { focused: true }) + sendResponse({ error: 'Transaction signing window already open' }) + } } else if (message.method === 'signChallenge') { - sendResponse({ error: 'External signing is disabled' }) - // if (!connectedSites[origin]) { - // sendResponse({ error: 'Not connected. Call connect first.' }) - // } else if (popupWindowId === false) { - // pendingResponses.set(message.requestId, sendResponse) - // api.windows.create( - // { - // url: api.runtime.getURL('popup.html'), - // type: 'popup', - // width: 800, - // height: 600, - // focused: true, - // }, - // (win) => { - // popupWindowId = win.id - // api.storage.local.set( - // { - // pendingRequest: { - // origin, - // requestId: message.requestId, - // action: 'signChallenge', - // data: message.params || {}, - // }, - // }, - // () => { - // if (api.runtime.lastError) { - // console.error( - // '[Mintlayer] Storage set error:', - // api.runtime.lastError, - // ) - // } - // }, - // ) - // }, - // ) - // return true - // } else if (typeof popupWindowId === 'number') { - // api.windows.update(popupWindowId, { focused: true }) - // sendResponse({ error: 'Transaction signing window already open' }) - // } + if (!connectedSites[origin]) { + sendResponse({ error: 'Not connected. Call connect first.' }) + } else if (popupWindowId === false) { + pendingResponses.set(message.requestId, sendResponse) + api.windows.create( + { + url: api.runtime.getURL('popup.html'), + type: 'popup', + width: 800, + height: 600, + focused: true, + }, + (win) => { + popupWindowId = win.id + api.storage.local.set( + { + pendingRequest: { + origin, + requestId: message.requestId, + action: 'signChallenge', + data: message.params || {}, + }, + }, + () => { + if (api.runtime.lastError) { + console.error( + '[Mintlayer] Storage set error:', + api.runtime.lastError, + ) + } + }, + ) + }, + ) + return true + } else if (typeof popupWindowId === 'number') { + api.windows.update(popupWindowId, { focused: true }) + sendResponse({ error: 'Transaction signing window already open' }) + } } else if (message.method === 'version') { sendResponse({ result: api.runtime.getManifest().version }) } else if (message.method === 'getSession') { @@ -187,6 +183,27 @@ } } + // Handle disconnect requests from the wallet UI + if (message.action === 'disconnectSite') { + const targetOrigin = message.origin + + if (!targetOrigin || !connectedSites[targetOrigin]) { + sendResponse({ result: null }) + return + } + + delete connectedSites[targetOrigin] + api.storage.local.set({ connectedSites }, () => { + if (api.runtime.lastError) { + console.error('[Mintlayer] Storage set error:', api.runtime.lastError) + sendResponse({ error: api.runtime.lastError.message }) + return + } + sendResponse({ result: { origin: targetOrigin } }) + }) + return true + } + if (!message.method && message.action !== 'popupResponse') return // Handle popup responses diff --git a/src/index.js b/src/index.js index a2483d6e..943e9e7a 100644 --- a/src/index.js +++ b/src/index.js @@ -107,7 +107,7 @@ const App = () => { useContext(MintlayerContext) const { networkType } = useContext(SettingsContext) const [nextAfterUnlock, setNextAfterUnlock] = useState(null) - const [request, setRequest] = useState(null) + const [, setRequest] = useState(null) const currentMlAddresses = addresses.mlAddresses @@ -203,38 +203,48 @@ const App = () => { if (!unlocked) { setNextAfterUnlock({ route: '/connect', - state: { action: 'connect', origin, requestId, request }, + state: { + action: 'connect', + origin, + requestId, + request: pendingRequest, + }, }) return } navigate('/connect', { - state: { action: 'connect', origin, requestId, request }, + state: { + action: 'connect', + origin, + requestId, + request: pendingRequest, + }, }) } if (action === 'signTransaction') { - if (request.data.chain === 'bitcoin') { + if (pendingRequest.data.chain === 'bitcoin') { if (!unlocked) { setNextAfterUnlock({ route: '/wallet/Bitcoin/sign-transaction', - state: { action: 'signTransaction', request }, + state: { action: 'signTransaction', request: pendingRequest }, }) return } navigate('/wallet/Bitcoin/sign-transaction', { - state: { action: 'signTransaction', request }, + state: { action: 'signTransaction', request: pendingRequest }, }) } else { if (!unlocked) { setNextAfterUnlock({ route: '/wallet/Mintlayer/sign-external-transaction', - state: { action: 'signTransaction', request }, + state: { action: 'signTransaction', request: pendingRequest }, }) return } navigate('/wallet/Mintlayer/sign-external-transaction', { - state: { action: 'signTransaction', request }, + state: { action: 'signTransaction', request: pendingRequest }, }) } } @@ -243,12 +253,12 @@ const App = () => { if (!unlocked) { setNextAfterUnlock({ route: '/wallet/Mintlayer/sign-challenge', - state: { action: 'signChallenge', request }, + state: { action: 'signChallenge', request: pendingRequest }, }) return } navigate('/wallet/Mintlayer/sign-challenge', { - state: { action: 'signChallenge', request }, + state: { action: 'signChallenge', request: pendingRequest }, }) } @@ -258,8 +268,8 @@ const App = () => { route: '/wallet/Mintlayer/staking/create-delegation', state: { action: 'createDelegate', - pool_id: request.data.pool_id, - referral_code: request.data.referral_code || '', + pool_id: pendingRequest.data.pool_id, + referral_code: pendingRequest.data.referral_code || '', }, }) storage.local.remove('pendingRequest', () => { @@ -276,8 +286,8 @@ const App = () => { navigate('/wallet/Mintlayer/staking/create-delegation', { state: { action: 'createDelegate', - pool_id: request.data.pool_id, - referral_code: request.data.referral_code || '', + pool_id: pendingRequest.data.pool_id, + referral_code: pendingRequest.data.referral_code || '', }, }) storage.local.remove('pendingRequest', () => { From ac9deb3ed438ac3be98624682c7d0150b0208c2f Mon Sep 17 00:00:00 2001 From: owlsua Date: Fri, 28 Aug 2026 15:34:59 +0200 Subject: [PATCH 11/23] Show the requesting site on the signing screens Add a SiteBadge component and put it on the challenge, bitcoin and external transaction screens, so it is visible which origin asked for the signature. Requests without an origin are marked as unknown. --- .../basic/SiteBadge/SiteBadge.module.css | 36 +++++++++++++++++++ src/components/basic/SiteBadge/SiteBadge.tsx | 24 +++++++++++++ src/components/basic/index.js | 2 ++ .../SignBitcoinTransaction.css | 4 +++ .../SignBitcoinTransaction.js | 10 +++++- src/pages/SignChallenge/SignChallenge.css | 4 +++ src/pages/SignChallenge/SignChallenge.js | 10 +++++- .../SignExternalTransaction.css | 4 +++ .../SignExternalTransaction.js | 10 +++++- 9 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 src/components/basic/SiteBadge/SiteBadge.module.css create mode 100644 src/components/basic/SiteBadge/SiteBadge.tsx diff --git a/src/components/basic/SiteBadge/SiteBadge.module.css b/src/components/basic/SiteBadge/SiteBadge.module.css new file mode 100644 index 00000000..36137ac9 --- /dev/null +++ b/src/components/basic/SiteBadge/SiteBadge.module.css @@ -0,0 +1,36 @@ +.badge { + display: inline-flex; + width: fit-content; + align-items: center; + gap: var(--space-xs); + max-width: 100%; + padding: var(--space-xs) var(--space-md); + border-radius: var(--round-size-big); + border: 1px solid rgba(var(--color-main-green), 0.35); + background: rgba(var(--color-main-green), 0.1); +} + +.dot { + width: 8px; + height: 8px; + flex-shrink: 0; + border-radius: 50%; + background: rgb(var(--mojito-green)); +} + +.origin { + font-size: var(--font-size-sm); + font-weight: 600; + color: rgb(var(--color-black)); + overflow-wrap: anywhere; + line-height: 1.3; +} + +.unknown { + border-color: rgba(var(--color-orange), 0.4); + background: rgba(var(--color-orange), 0.12); +} + +.unknown .dot { + background: rgb(var(--color-orange)); +} diff --git a/src/components/basic/SiteBadge/SiteBadge.tsx b/src/components/basic/SiteBadge/SiteBadge.tsx new file mode 100644 index 00000000..0d83bc7c --- /dev/null +++ b/src/components/basic/SiteBadge/SiteBadge.tsx @@ -0,0 +1,24 @@ +import styles from './SiteBadge.module.css' + +interface SiteBadgeProps { + origin: string + unknown?: boolean +} + +const SiteBadge = ({ origin, unknown = false }: SiteBadgeProps) => { + const badgeStyles = [styles.badge, unknown ? styles.unknown : ''] + .filter(Boolean) + .join(' ') + + return ( +

+ + {origin} +
+ ) +} + +export default SiteBadge diff --git a/src/components/basic/index.js b/src/components/basic/index.js index a61413b7..a01e3214 100644 --- a/src/components/basic/index.js +++ b/src/components/basic/index.js @@ -19,6 +19,7 @@ import PageWrapper from './PageWrapper/PageWrapper.tsx' import BrandPanel from './BrandPanel/BrandPanel' import BrandBottomLogo from './BrandBottomLogo/BrandBottomLogo' import OptionCard from './OptionCard/OptionCard' +import SiteBadge from './SiteBadge/SiteBadge.tsx' export { Arc, @@ -42,4 +43,5 @@ export { BrandPanel, BrandBottomLogo, OptionCard, + SiteBadge, } diff --git a/src/pages/SignBitcoinTransaction/SignBitcoinTransaction.css b/src/pages/SignBitcoinTransaction/SignBitcoinTransaction.css index 469578f7..a3f67c3a 100644 --- a/src/pages/SignBitcoinTransaction/SignBitcoinTransaction.css +++ b/src/pages/SignBitcoinTransaction/SignBitcoinTransaction.css @@ -225,3 +225,7 @@ margin-top: 4px; color: #6b7280; } + +.requestOrigin { + margin-bottom: 20px; +} diff --git a/src/pages/SignBitcoinTransaction/SignBitcoinTransaction.js b/src/pages/SignBitcoinTransaction/SignBitcoinTransaction.js index 564f1cde..882bdfe0 100644 --- a/src/pages/SignBitcoinTransaction/SignBitcoinTransaction.js +++ b/src/pages/SignBitcoinTransaction/SignBitcoinTransaction.js @@ -1,7 +1,7 @@ /* eslint-disable no-undef */ import { useLocation } from 'react-router' import { MOCKS } from './mocks' -import { Button, PageWrapper } from '@BasicComponents' +import { Button, PageWrapper, SiteBadge } from '@BasicComponents' import { PopUp, TextField } from '@ComposedComponents' import { SignTransaction } from '@ContainerComponents' @@ -131,6 +131,7 @@ export const SignBitcoinTransactionPage = () => { }, [initialState]) const state = htlcTransactionState || initialState + const origin = state?.request?.origin const revealed_secret = state?.request?.data?.txData?.JSONRepresentation.secret @@ -440,6 +441,13 @@ export const SignBitcoinTransactionPage = () => {
+
+ +
+
{!external_state && (
diff --git a/src/pages/SignChallenge/SignChallenge.css b/src/pages/SignChallenge/SignChallenge.css index 4ea89a72..dbe5010c 100644 --- a/src/pages/SignChallenge/SignChallenge.css +++ b/src/pages/SignChallenge/SignChallenge.css @@ -57,3 +57,7 @@ font-weight: bold; color: #000; } + +.requestOrigin { + margin-bottom: 20px; +} diff --git a/src/pages/SignChallenge/SignChallenge.js b/src/pages/SignChallenge/SignChallenge.js index bb67b34e..2af1d9c8 100644 --- a/src/pages/SignChallenge/SignChallenge.js +++ b/src/pages/SignChallenge/SignChallenge.js @@ -2,7 +2,7 @@ import { useLocation } from 'react-router' import { SignTransaction as SignTxHelpers } from '@Helpers' import { MOCKS } from './mocks' -import { Button, PageWrapper } from '@BasicComponents' +import { Button, PageWrapper, SiteBadge } from '@BasicComponents' import { PopUp, TextField } from '@ComposedComponents' import './SignChallenge.css' @@ -35,6 +35,7 @@ export const SignChallengePage = () => { const extraButtonStyles = ['buttonSignTransaction'] const state = external_state || MOCKS[selectedMock] + const origin = state?.request?.origin const { addresses, accountID } = useContext(AccountContext) const currentMlAddresses = addresses.mlAddresses @@ -144,6 +145,13 @@ export const SignChallengePage = () => {

Sign Challenge

+
+ +
+
{!external_state && (
diff --git a/src/pages/SignExternalTransaction/SignExternalTransaction.css b/src/pages/SignExternalTransaction/SignExternalTransaction.css index 720de7de..f21985da 100644 --- a/src/pages/SignExternalTransaction/SignExternalTransaction.css +++ b/src/pages/SignExternalTransaction/SignExternalTransaction.css @@ -242,3 +242,7 @@ .secret-hint small { font-style: italic; } + +.requestOrigin { + margin-bottom: 20px; +} diff --git a/src/pages/SignExternalTransaction/SignExternalTransaction.js b/src/pages/SignExternalTransaction/SignExternalTransaction.js index c0ff3c9e..6cb0d3f7 100644 --- a/src/pages/SignExternalTransaction/SignExternalTransaction.js +++ b/src/pages/SignExternalTransaction/SignExternalTransaction.js @@ -2,7 +2,7 @@ import { useLocation } from 'react-router' import { SignTransaction as SignTxHelpers, Secret } from '@Helpers' import { MOCKS } from './mocks' -import { Button, PageWrapper } from '@BasicComponents' +import { Button, PageWrapper, SiteBadge } from '@BasicComponents' import { PopUp, TextField } from '@ComposedComponents' import { SignTransaction } from '@ContainerComponents' import { MintlayerContext } from '@Contexts' @@ -54,6 +54,7 @@ export const SignTransactionPage = () => { const [transactionState, setTransactionState] = useState(null) const state = transactionState || external_state || MOCKS[selectedMock] + const origin = state?.request?.origin const { addresses, accountID } = useContext(AccountContext) const currentMlAddresses = addresses.mlAddresses @@ -435,6 +436,13 @@ export const SignTransactionPage = () => {
+
+ +
+
{!external_state && (
From 5282cc46da814b827ace078f246abaa6545f1c1d Mon Sep 17 00:00:00 2001 From: owlsua Date: Fri, 28 Aug 2026 15:35:25 +0200 Subject: [PATCH 12/23] Rebuild the connection page Split the page into PermissionItem and BitcoinDataNotice components, show the requesting site through SiteBadge and move the styles from a global stylesheet to CSS modules. --- .../BitcoinDataNotice.module.css | 69 ++++++++ .../ConnectionPage/BitcoinDataNotice.tsx | 43 +++++ src/pages/ConnectionPage/ConnectionPage.css | 165 ------------------ src/pages/ConnectionPage/ConnectionPage.js | 149 +++++++++------- .../ConnectionPage/ConnectionPage.module.css | 148 ++++++++++++++++ .../ConnectionPage/PermissionItem.module.css | 55 ++++++ src/pages/ConnectionPage/PermissionItem.tsx | 29 +++ 7 files changed, 426 insertions(+), 232 deletions(-) create mode 100644 src/pages/ConnectionPage/BitcoinDataNotice.module.css create mode 100644 src/pages/ConnectionPage/BitcoinDataNotice.tsx delete mode 100644 src/pages/ConnectionPage/ConnectionPage.css create mode 100644 src/pages/ConnectionPage/ConnectionPage.module.css create mode 100644 src/pages/ConnectionPage/PermissionItem.module.css create mode 100644 src/pages/ConnectionPage/PermissionItem.tsx diff --git a/src/pages/ConnectionPage/BitcoinDataNotice.module.css b/src/pages/ConnectionPage/BitcoinDataNotice.module.css new file mode 100644 index 00000000..74d7762a --- /dev/null +++ b/src/pages/ConnectionPage/BitcoinDataNotice.module.css @@ -0,0 +1,69 @@ +.section { + display: flex; + flex-direction: column; + gap: var(--space-md); + width: 100%; +} + +.toggleRow { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-md); +} + +.toggleText { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.toggleTitle { + font-size: var(--font-size-md); + font-weight: 600; + color: rgb(var(--color-black)); +} + +.toggleDescription { + font-size: var(--font-size-sm); + color: rgba(var(--color-black), 0.55); +} + +.infoBlock { + display: flex; + align-items: flex-start; + gap: var(--space-sm); + padding: var(--space-md); + border-radius: 12px; + border: 1px solid rgba(var(--color-main-green), 0.3); + background: rgba(var(--color-main-green), 0.08); +} + +.infoIcon { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + flex-shrink: 0; + margin-top: 1px; + border-radius: 50%; + background: rgb(var(--color-main-green)); + color: rgb(var(--color-white)); + font-size: var(--font-size-xs); + font-weight: bold; + font-style: italic; +} + +.infoText { + margin: 0; + font-size: var(--font-size-sm); + line-height: 1.45; + color: rgb(var(--color-dark-gray)); +} + +.infoText strong { + color: rgb(var(--color-main-green)); + font-weight: 600; +} diff --git a/src/pages/ConnectionPage/BitcoinDataNotice.tsx b/src/pages/ConnectionPage/BitcoinDataNotice.tsx new file mode 100644 index 00000000..8744e482 --- /dev/null +++ b/src/pages/ConnectionPage/BitcoinDataNotice.tsx @@ -0,0 +1,43 @@ +import { Toggle } from '@BasicComponents' + +import styles from './BitcoinDataNotice.module.css' + +interface BitcoinDataNoticeProps { + provideBitcoinData: boolean + onToggle: (value: boolean) => void +} + +const BitcoinDataNotice = ({ + provideBitcoinData, + onToggle, +}: BitcoinDataNoticeProps) => { + return ( +
+
+
+ Provide Bitcoin data + + Addresses and public keys + +
+ +
+ +
+ i +

+ Note: This option is mandatory when connecting to + HTLC Atomic Swaps dApps. It provides both Bitcoin addresses and public + keys required for cross-chain transactions. +

+
+
+ ) +} + +export default BitcoinDataNotice diff --git a/src/pages/ConnectionPage/ConnectionPage.css b/src/pages/ConnectionPage/ConnectionPage.css deleted file mode 100644 index d041986d..00000000 --- a/src/pages/ConnectionPage/ConnectionPage.css +++ /dev/null @@ -1,165 +0,0 @@ -.connect-page__form { - display: flex; - flex-direction: column; - align-items: center; - gap: 1rem; - width: 100%; - height: 100%; - margin: 1rem; - padding: 0 2rem; - /* border: 1px solid #ddd; */ - border-radius: 10px; - /* background-color: #fff; */ - /* box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); */ -} - -.connect-page__title { - display: flex; - flex-direction: column; - gap: 1rem; - align-items: center; -} - -.connect-page__title h2 { - font-size: 1.25rem; - font-weight: bold; -} - -.connect-page__content { - display: flex; - width: 90%; - flex-direction: column; - align-items: center; - gap: 2rem; -} - -.connect-page__description { - font-size: 0.95rem; - margin-bottom: 1rem; -} - -.connect-page__host { - font-weight: 600; - color: rgb(var(--color-black)); -} - -.connect-page__permissions { - position: relative; - text-align: left; - padding-left: 7rem; - list-style: disc !important; - max-width: 400px; - margin: 0 auto; - margin-bottom: 20px; -} - -.connect-page__permissions li { - margin-bottom: 0.5rem; - font-size: 0.9rem; - list-style: inside; -} - -.connect-page__remember { - display: flex; - align-items: center; - gap: 0.5rem; - justify-content: center; - font-size: 0.85rem; - margin-bottom: 1.5rem; -} - -.connect-page__checkbox { - transform: scale(1.1); -} - -.connect-page__actions { - display: flex; - justify-content: space-around; - width: 80%; - gap: 1rem; -} - -.rejectButton { - background-color: #eee; - color: #333; -} - -.connect-page__button--reject:hover { - background-color: #ddd; -} - -.connect-page__button--connect { - background-color: #3cb371; - color: #fff; -} - -.connect-page__button--connect:hover { - background-color: #35a164; -} - -.connectButton { - width: 50%; -} - -.connect-page__icon { - position: absolute; - width: 90px; - height: auto; - left: 4px; - top: 50%; - transform: translate(0, -50%); - stroke: rgb(var(--color-green)); -} - -.connect-page__bitcoin-section { - display: flex; - flex-direction: column; - align-items: center; - gap: 1rem; - width: 100%; -} - -.connect-page__bitcoin-toggle { - display: flex; - align-items: center; - gap: 1rem; -} - -.connect-page__info-block { - display: flex; - align-items: flex-start; - gap: 0.75rem; - padding: 1rem; - background-color: rgba(var(--color-main-green), 0.1); - border: 1px solid rgba(var(--color-main-green), 0.3); - border-radius: 8px; - width: 100%; -} - -.connect-page__info-icon { - width: 20px; - height: 20px; - flex-shrink: 0; - margin-top: 2px; - background-color: rgb(var(--color-main-green)); - color: white; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 12px; - font-weight: bold; - font-style: italic; -} - -.connect-page__info-text { - font-size: 0.85rem; - line-height: 1.4; - margin: 0; - color: rgb(var(--color-dark-gray)); -} - -.connect-page__info-text strong { - color: rgb(var(--color-main-green)); - font-weight: 600; -} diff --git a/src/pages/ConnectionPage/ConnectionPage.js b/src/pages/ConnectionPage/ConnectionPage.js index 06bc02f2..06b57c95 100644 --- a/src/pages/ConnectionPage/ConnectionPage.js +++ b/src/pages/ConnectionPage/ConnectionPage.js @@ -1,10 +1,15 @@ /* eslint-disable no-undef */ -import './ConnectionPage.css' import { useLocation } from 'react-router' import { useContext, useState } from 'react' import { AccountContext } from '@Contexts' -import { Button, Toggle, PageWrapper } from '@BasicComponents' +import { Button, PageWrapper, SiteBadge } from '@BasicComponents' import { ReactComponent as IconShield } from '@Assets/images/icon-shield.svg' +import { ReactComponent as IconEye } from '@Assets/images/icon-eye.svg' +import { ReactComponent as IconSign } from '@Assets/images/icon-sign.svg' +import { ReactComponent as IconLoop } from '@Assets/images/icon-loop.svg' +import PermissionItem from './PermissionItem' +import BitcoinDataNotice from './BitcoinDataNotice' +import styles from './ConnectionPage.module.css' const toHexString = (obj) => { return Object.values(obj) @@ -39,8 +44,9 @@ export const ConnectionPage = () => { const permissions = state?.request?.permissions || [] const requireBTC = permissions.includes('bitcoin') + const isUnknownOrigin = origin === website - const connectButtonExtraStyles = ['connectButton'] + const connectButtonExtraStyles = [styles.actionButton] const handleConnect = () => { const remember = document.querySelector('.connect-page__checkbox')?.checked @@ -161,80 +167,89 @@ export const ConnectionPage = () => { return (
-
-

- Connect Website to Your Mojito Wallet -

-

- The website {origin} is - requesting access to your wallet. -

-
+
+
+ + + +

+ Connect website to your Mojito wallet +

+

+ This website is requesting access to your wallet. +

+ +
+ +
+ + This will allow the website to + -
-
-
    - -
  • View your public addresses
  • -
  • Request transaction signing
  • -
  • Track connection status
  • +
      + + +
    {requireBTC && ( - <> -
    -
    -
    Provide Bitcoin data (addresses AND public keys)
    - -
    - -
    -
    i
    -

    - Note: This option is mandatory when - connecting to HTLC Atomic Swaps dApps. It provides both - Bitcoin addresses and public keys required for cross-chain - transactions. -

    -
    -
    - +
    + +
    )}
- {/* // TODO: Make this work */} - {/* */} - -
- - -
+

+ Only connect to websites you trust. You can reject this request and + nothing will be shared. +

+
+ + {/* // TODO: Make this work */} + {/* */} + +
+ +
diff --git a/src/pages/ConnectionPage/ConnectionPage.module.css b/src/pages/ConnectionPage/ConnectionPage.module.css new file mode 100644 index 00000000..72845535 --- /dev/null +++ b/src/pages/ConnectionPage/ConnectionPage.module.css @@ -0,0 +1,148 @@ +.form { + display: flex; + flex-direction: column; + gap: var(--space-lg); + width: 100%; + max-width: 480px; + margin-top: var(--space-3xl); + align-self: center; + flex: 1; + min-height: 0; +} + +.scrollArea { + display: flex; + flex-direction: column; + gap: var(--space-lg); + flex: 0 1 auto; + min-height: 0; + overflow-y: auto; + overscroll-behavior: contain; +} + +.intro { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-sm); + text-align: center; +} + +.shield { + display: flex; + align-items: center; + justify-content: center; + width: 56px; + height: 56px; + flex-shrink: 0; + border-radius: 18px; + background: rgba(var(--color-main-green), 0.12); + color: rgb(var(--mojito-green)); +} + +.shieldIcon { + width: 28px; + height: 28px; + stroke: rgb(var(--mojito-green)); +} + +.title { + margin: 0; + font-size: var(--font-size-3xl); + font-weight: 700; + line-height: 1.25; + color: rgb(var(--color-black)); +} + +.subtitle { + margin: 0; + font-size: var(--font-size-md); + line-height: 1.4; + color: rgba(var(--color-black), 0.6); +} + +.card { + display: flex; + flex-direction: column; + gap: var(--space-lg); + padding: var(--space-lg); + border-radius: var(--round-size); + border: 1px solid rgba(var(--color-black), 0.07); + background: rgb(var(--surface)); + box-shadow: var(--shadow-sm); +} + +.cardTitle { + font-size: var(--font-size-xs); + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: rgba(var(--color-black), 0.45); +} + +.permissions { + display: flex; + flex-direction: column; + gap: var(--space-lg); + margin: 0; + padding: 0; + list-style: none; +} + +.bitcoinSlot { + padding-top: var(--space-lg); + border-top: 1px solid rgba(var(--color-black), 0.07); +} + +.disclaimer { + margin: 0; + text-align: center; + font-size: var(--font-size-sm); + line-height: 1.45; + color: rgba(var(--color-black), 0.5); +} + +.actions { + display: flex; + gap: var(--space-md); + width: 100%; + flex-shrink: 0; +} + +.actionButton.actionButton { + flex: 1; + min-width: 0; + padding: var(--space-md) var(--space-lg); + font-size: var(--font-size-md); +} + +@media screen and (max-width: 319px) { + .actions { + flex-direction: column-reverse; + } +} + +@media screen and (min-width: 901px) { + .form { + gap: var(--space-xl); + max-width: 520px; + } + + .title { + font-size: var(--font-size-4xl); + } + + .card { + padding: var(--space-2xl); + } + + .shield { + width: 64px; + height: 64px; + } + + .shieldIcon { + width: 32px; + height: 32px; + } +} diff --git a/src/pages/ConnectionPage/PermissionItem.module.css b/src/pages/ConnectionPage/PermissionItem.module.css new file mode 100644 index 00000000..4fb7329a --- /dev/null +++ b/src/pages/ConnectionPage/PermissionItem.module.css @@ -0,0 +1,55 @@ +.item { + display: flex; + align-items: flex-start; + gap: var(--space-md); + list-style: none; +} + +.iconWrapper { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + flex-shrink: 0; + border-radius: 10px; + background: rgba(var(--color-main-green), 0.12); + color: rgb(var(--mojito-green)); +} + +.icon { + width: 18px; + height: 18px; +} + +.text { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.title { + font-size: var(--font-size-md); + font-weight: 600; + color: rgb(var(--color-black)); + line-height: 1.3; +} + +.description { + font-size: var(--font-size-sm); + color: rgba(var(--color-black), 0.55); + line-height: 1.4; +} + +@media screen and (min-width: 901px) { + .iconWrapper { + width: 36px; + height: 36px; + } + + .icon { + width: 20px; + height: 20px; + } +} diff --git a/src/pages/ConnectionPage/PermissionItem.tsx b/src/pages/ConnectionPage/PermissionItem.tsx new file mode 100644 index 00000000..a123f65e --- /dev/null +++ b/src/pages/ConnectionPage/PermissionItem.tsx @@ -0,0 +1,29 @@ +import { FC, SVGProps } from 'react' + +import styles from './PermissionItem.module.css' + +interface PermissionItemProps { + icon: FC> + title: string + description: string +} + +const PermissionItem = ({ + icon: Icon, + title, + description, +}: PermissionItemProps) => { + return ( +
  • + + + + + {title} + {description} + +
  • + ) +} + +export default PermissionItem From 1e166ba5bd2a154b355496ea33657067dfbb9688 Mon Sep 17 00:00:00 2001 From: owlsua Date: Fri, 28 Aug 2026 15:35:39 +0200 Subject: [PATCH 13/23] Add a connections section to settings List the sites that have a stored session and let them be revoked from the settings page. --- .../SettingsConnections.module.css | 67 +++++++++++ .../SettingsConnections.tsx | 110 ++++++++++++++++++ src/components/containers/index.js | 2 + src/pages/Settings/Settings.tsx | 6 + 4 files changed, 185 insertions(+) create mode 100644 src/components/containers/Settings/SettingsConnections/SettingsConnections.module.css create mode 100644 src/components/containers/Settings/SettingsConnections/SettingsConnections.tsx diff --git a/src/components/containers/Settings/SettingsConnections/SettingsConnections.module.css b/src/components/containers/Settings/SettingsConnections/SettingsConnections.module.css new file mode 100644 index 00000000..331813f9 --- /dev/null +++ b/src/components/containers/Settings/SettingsConnections/SettingsConnections.module.css @@ -0,0 +1,67 @@ +.list { + display: flex; + flex-direction: column; + margin: 0; + padding: 0; + list-style: none; +} + +.row { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-md); + padding: var(--space-lg) var(--space-xl); +} + +.row + .row { + border-top: 1px solid rgba(var(--color-light-gray), 0.3); +} + +.info { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--space-2xs); + min-width: 0; +} + +.date { + font-size: var(--font-size-sm); + color: rgba(var(--color-black), 0.5); +} + +.disconnectButton.disconnectButton { + flex-shrink: 0; + padding: var(--space-xs) var(--space-lg); + font-size: var(--font-size-sm); + background: transparent; + background-color: transparent; + border: 1.5px solid rgba(var(--color-red), 0.5); + color: rgb(var(--color-red)); +} + +.disconnectButton.disconnectButton:hover, +.disconnectButton.disconnectButton:focus { + background: rgba(var(--color-red), 0.08); + background-color: rgba(var(--color-red), 0.08); + color: rgb(var(--color-red)); +} + +.empty { + padding: var(--space-lg) var(--space-xl); + font-size: var(--font-size-sm); + color: rgba(var(--color-black), 0.5); +} + +@media screen and (max-width: 420px) { + .row { + flex-direction: column; + align-items: stretch; + gap: var(--space-sm); + } + + .disconnectButton.disconnectButton { + width: 100%; + } +} diff --git a/src/components/containers/Settings/SettingsConnections/SettingsConnections.tsx b/src/components/containers/Settings/SettingsConnections/SettingsConnections.tsx new file mode 100644 index 00000000..9548de00 --- /dev/null +++ b/src/components/containers/Settings/SettingsConnections/SettingsConnections.tsx @@ -0,0 +1,110 @@ +/* eslint-disable no-undef */ +import { useCallback, useEffect, useState } from 'react' + +import { Button, SiteBadge } from '@BasicComponents' + +import styles from './SettingsConnections.module.css' + +const storage = + typeof browser !== 'undefined' && browser.storage + ? browser.storage + : typeof chrome !== 'undefined' && chrome.storage + ? chrome.storage + : null + +const runtime = + typeof browser !== 'undefined' && browser.runtime + ? browser.runtime + : typeof chrome !== 'undefined' && chrome.runtime + ? chrome.runtime + : null + +interface ConnectedSite { + origin: string + timestamp?: number +} + +const formatDate = (timestamp?: number) => + timestamp ? new Date(timestamp).toLocaleString() : 'unknown date' + +const SettingsConnections = () => { + const [sites, setSites] = useState([]) + + const readSites = useCallback(() => { + if (!storage) return + + storage.local.get(['connectedSites'], (data: any) => { + if (runtime?.lastError) { + console.error('[Mojito] Storage get error:', runtime.lastError) + return + } + + const connectedSites = data?.connectedSites || {} + const list = Object.keys(connectedSites).map((origin) => ({ + origin, + timestamp: connectedSites[origin]?.timestamp, + })) + + list.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)) + setSites(list) + }) + }, []) + + useEffect(() => { + readSites() + + if (!storage?.onChanged) return + + const onStorageChanged = (changes: any, area: string) => { + if (area === 'local' && changes.connectedSites) readSites() + } + + storage.onChanged.addListener(onStorageChanged) + return () => storage.onChanged.removeListener(onStorageChanged) + }, [readSites]) + + const disconnectHandler = (origin: string) => { + if (!runtime) return + + runtime.sendMessage({ action: 'disconnectSite', origin }, () => { + if (runtime.lastError) { + console.error('[Mojito] Disconnect error:', runtime.lastError) + } + readSites() + }) + } + + if (sites.length === 0) { + return ( +

    + No websites are connected to your wallet yet. +

    + ) + } + + return ( +
      + {sites.map((site) => ( +
    • +
      + + + Connected {formatDate(site.timestamp)} + +
      + +
    • + ))} +
    + ) +} + +export default SettingsConnections diff --git a/src/components/containers/index.js b/src/components/containers/index.js index 41c73bb8..b87a8ccb 100644 --- a/src/components/containers/index.js +++ b/src/components/containers/index.js @@ -33,6 +33,7 @@ import SettingsTestnet from './Settings/SettingsTestnet/SettingsTestnet.tsx' import SettingsAbout from './Settings/SettingsAbout/SettingsAbout.tsx' import SettingsBackup from './Settings/SettingsBackup/SettingsBackup' import SettingsSection from './Settings/SettingsSection/SettingsSection.tsx' +import SettingsConnections from './Settings/SettingsConnections/SettingsConnections.tsx' import SignMessage from './Message/SignMessage/SignMessage' import VerifyMessage from './Message/VerifyMessage/VerifyMessage' @@ -75,6 +76,7 @@ const Settings = { SettingsDelete, SettingsBackup, SettingsSection, + SettingsConnections, } const RestoreAccount = { diff --git a/src/pages/Settings/Settings.tsx b/src/pages/Settings/Settings.tsx index af02261c..7f1c6cf2 100644 --- a/src/pages/Settings/Settings.tsx +++ b/src/pages/Settings/Settings.tsx @@ -23,6 +23,12 @@ const SettingsPage = ({ unlocked }: SettingsPageProps) => { { key: 'delete', component: }, ], }, + { + title: 'Connections', + key: 'connections', + visible: true, + content: , + }, { title: 'About', key: 'about', From d1617ff5c543982b3d5668ddc0044ec147a057bb Mon Sep 17 00:00:00 2001 From: owlsua Date: Fri, 28 Aug 2026 15:35:54 +0200 Subject: [PATCH 14/23] Use the loop icon for the swap button --- src/components/containers/Wallet/TransactionButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/containers/Wallet/TransactionButton.js b/src/components/containers/Wallet/TransactionButton.js index 3449439a..2a7fbce2 100644 --- a/src/components/containers/Wallet/TransactionButton.js +++ b/src/components/containers/Wallet/TransactionButton.js @@ -2,7 +2,7 @@ import { ReactComponent as ArrowIcon } from '@Assets/images/icon-arrow-down.svg' import { ReactComponent as DelegationIcon } from '@Assets/images/icon-delegation.svg' import { ReactComponent as SignIcon } from '@Assets/images/icon-sign.svg' import { ReactComponent as NftIcon } from '@Assets/images/icon-nft.svg' -import { ReactComponent as SwapIcon } from '@Assets/images/icon-arrow-swap.svg' +import { ReactComponent as SwapIcon } from '@Assets/images/icon-loop.svg' import { ReactComponent as AddressesIcon } from '@Assets/images/icon-inbox.svg' import { Button } from '@BasicComponents' From 7a2cd8b22361b396fbc98c2f06f4bdacebcedcfc Mon Sep 17 00:00:00 2001 From: owlsua Date: Sat, 29 Aug 2026 12:14:18 +0200 Subject: [PATCH 15/23] Answer the site when a connection request is rejected --- public/background.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/background.js b/public/background.js index c452a318..e0899d23 100644 --- a/public/background.js +++ b/public/background.js @@ -228,6 +228,11 @@ pendingResponses.delete(requestId) } }) + } else if (!result && message.method === 'connect') { + if (storedSendResponse) { + storedSendResponse({ error: error || 'User rejected the request' }) + pendingResponses.delete(requestId) + } } else if (result && message.method === 'signTransaction_approve') { storedSendResponse({ result, error }) pendingResponses.delete(requestId) From 4f1f7cd2f242f76bbb69b08cd8c7b9bb2cf406cd Mon Sep 17 00:00:00 2001 From: owlsua Date: Sun, 30 Aug 2026 15:28:18 +0200 Subject: [PATCH 16/23] Search every order when no amount is given Swap Assets refused to look for orders until an amount was typed: the button stayed disabled and the submit handler returned early. The amount only narrows the result, so it is optional now, and an empty field lists every order for the pair. The filter reads the threshold explicitly instead of relying on Number('') being 0. --- .../composed/SwapInterface/SwapInterface.js | 4 ++-- .../composed/SwapInterface/SwapInterface.test.js | 13 +++++++++++-- src/contexts/MintlayerProvider/MintlayerProvider.js | 3 ++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/composed/SwapInterface/SwapInterface.js b/src/components/composed/SwapInterface/SwapInterface.js index b9da5b81..329f210e 100644 --- a/src/components/composed/SwapInterface/SwapInterface.js +++ b/src/components/composed/SwapInterface/SwapInterface.js @@ -61,7 +61,7 @@ const SwapInterface = () => { const handleSubmit = async (e) => { e.preventDefault() - if (!amount || isNaN(amount) || parseFloat(amount) <= 0) { + if (amount && (isNaN(amount) || parseFloat(amount) <= 0)) { console.error('Invalid amount') return } @@ -132,7 +132,7 @@ const SwapInterface = () => { />
    ) @@ -558,9 +580,11 @@ const SummaryView = ({ data }) => { const InternalTransactionPreview = ({ data }) => { return ( -
    - -
    + +
    + +
    +
    ) } diff --git a/src/components/containers/SignTransaction/TransactionBreakdown/TransactionBreakdown.js b/src/components/containers/SignTransaction/TransactionBreakdown/TransactionBreakdown.js new file mode 100644 index 00000000..22a5366c --- /dev/null +++ b/src/components/containers/SignTransaction/TransactionBreakdown/TransactionBreakdown.js @@ -0,0 +1,301 @@ +import Decimal from 'decimal.js' + +import styles from './TransactionBreakdown.module.css' + +const COIN_KEY = 'Coin' +const COIN_DECIMALS = 11 + +// keeps room for the largest balances and never falls back to exponents +const Amount = Decimal.clone({ precision: 40, toExpNeg: -30, toExpPos: 30 }) + +const formatTotal = (total, isCoin) => + (isCoin ? total.toDecimalPlaces(COIN_DECIMALS) : total).toFixed() + +const shortenId = (id) => + id && id.length > 16 ? `${id.slice(0, 8)}…${id.slice(-6)}` : id + +const MAX_FIELD_DEPTH = 2 +const MAX_FIELD_LENGTH = 80 + +const shortenData = (data) => + data && data.length > MAX_FIELD_LENGTH + ? `${data.slice(0, MAX_FIELD_LENGTH)}…` + : data + +const getAmount = (source) => + source?.value?.amount?.decimal ?? + source?.amount?.decimal ?? + (typeof source?.amount === 'string' ? source.amount : null) + +const getAsset = (source, tokenMap, coinTicker) => { + const value = source?.value || source + if (value?.type === 'Coin') { + return { key: COIN_KEY, label: coinTicker } + } + + const tokenId = value?.token_id || source?.token_id + if (tokenId) { + return { key: tokenId, label: tokenMap[tokenId] || shortenId(tokenId) } + } + + // account inputs carry a bare amount, and those are always coins + return getAmount(source) ? { key: COIN_KEY, label: coinTicker } : null +} + +const getOwnership = (address, ownAddresses) => { + if (!address) return null + if (ownAddresses.receiving?.includes(address)) return 'Your address' + if (ownAddresses.change?.includes(address)) return 'Your change address' + return null +} + +const DESCRIBED_FIELDS = [ + 'type', + 'value', + 'destination', + 'amount', + 'input_type', + 'index', + 'source_id', + 'source_type', + 'command', + 'account_type', +] + +const getExtraFields = (source) => + Object.entries(source || {}).filter( + ([key, value]) => !DESCRIBED_FIELDS.includes(key) && value !== undefined, + ) + +const LOCK_LABELS = { + ForBlockCount: (content) => `for ${content} blocks`, + UntilTime: (content) => `until ${content}`, + ForSeconds: (content) => `for ${content} seconds`, + UntilHeight: (content) => `until block ${content}`, +} + +const formatLock = (lock) => { + const label = LOCK_LABELS[lock?.type] + return label ? label(lock.content) : null +} + +const FieldValue = ({ value, depth = 0 }) => { + if (value === null) return 'null' + + // the chain encodes text fields as a hex/string pair, only the text reads + if (value?.string !== undefined && value?.hex !== undefined) { + return {shortenData(String(value.string))} + } + + if (typeof value !== 'object') { + const text = String(value) + return {shortenData(text)} + } + + if (depth >= MAX_FIELD_DEPTH) { + const text = JSON.stringify(value) + return {shortenData(text)} + } + + return ( + + {Object.entries(value).map(([key, nested]) => ( + + {key} + + + ))} + + ) +} + +const ExtraFields = ({ source }) => { + const fields = getExtraFields(source) + if (fields.length === 0) return null + + return ( +
    + {fields.map(([key, value]) => { + const lock = key === 'lock' ? formatLock(value) : null + + return ( +
    +
    {key}
    +
    + {lock || } +
    +
    + ) + })} +
    + ) +} + +const Entry = ({ title, amount, asset, address, ownership, source }) => ( +
  • +
    + + {title} + {ownership && ( + <> + {' · '} + {ownership} + + )} + + {address && {address}} + +
    + {amount && ( + + {amount} {asset?.label} + + )} +
  • +) + +const getBalanceChanges = ({ + inputs, + outputs, + ownAddresses, + tokenMap, + coinTicker, +}) => { + const changes = new Map() + + const apply = (source, address, sign) => { + if (!getOwnership(address, ownAddresses)) return + const asset = getAsset(source, tokenMap, coinTicker) + const amount = getAmount(source) + if (!asset || !amount) return + + const current = changes.get(asset.key) || { + key: asset.key, + label: asset.label, + total: new Amount(0), + } + current.total = current.total.plus(new Amount(amount).times(sign)) + changes.set(asset.key, current) + } + + inputs.forEach((input) => { + if (input.input?.input_type !== 'UTXO') return + apply(input.utxo, input.utxo?.destination, -1) + }) + + outputs.forEach((output) => apply(output, output.destination, 1)) + + return [...changes.values()].filter(({ total }) => !total.isZero()) +} + +const TransactionBreakdown = ({ + JSONRepresentation, + ownAddresses = {}, + tokenMap = {}, + coinTicker = 'ML', +}) => { + const inputs = JSONRepresentation?.inputs || [] + const outputs = JSONRepresentation?.outputs || [] + + const balanceChanges = getBalanceChanges({ + inputs, + outputs, + ownAddresses, + tokenMap, + coinTicker, + }) + + return ( +
    +
    + Balance change + {balanceChanges.length > 0 ? ( + <> +
      + {balanceChanges.map(({ key, label, total }) => ( +
    • + {total.isNegative() ? '−' : '+'} + {formatTotal(total.abs(), key === COIN_KEY)} {label} +
    • + ))} +
    + + The network fee is part of this amount + + + ) : ( +

    + This transaction does not move funds held by this wallet. +

    + )} +
    + +
    +

    Inputs ({inputs.length})

    +
      + {inputs.map((input, index) => { + const isUtxo = input.input?.input_type === 'UTXO' + const source = isUtxo ? input.utxo : input.input + const address = isUtxo + ? input.utxo?.destination + : input.input?.destination + const title = isUtxo + ? input.utxo?.type || 'UTXO' + : input.input?.command || + input.input?.account_type || + input.input?.input_type + + return ( + + ) + })} +
    +
    + +
    +

    Outputs ({outputs.length})

    +
      + {outputs.map((output, index) => ( + + ))} +
    +
    +
    + ) +} + +export default TransactionBreakdown diff --git a/src/components/containers/SignTransaction/TransactionBreakdown/TransactionBreakdown.module.css b/src/components/containers/SignTransaction/TransactionBreakdown/TransactionBreakdown.module.css new file mode 100644 index 00000000..bf5ef061 --- /dev/null +++ b/src/components/containers/SignTransaction/TransactionBreakdown/TransactionBreakdown.module.css @@ -0,0 +1,166 @@ +.breakdown { + display: flex; + flex-direction: column; + gap: var(--space-xl); + margin-top: var(--space-lg); +} + +.hero { + display: flex; + flex-direction: column; + gap: var(--space-3xs); + padding: var(--space-lg); + border: 1px solid rgba(var(--color-light-green), 0.35); + border-radius: 14px; + background: rgba(var(--color-light-green), 0.06); +} + +.heroLabel { + font-size: var(--font-size-sm); + color: rgba(var(--color-black), 0.55); +} + +.heroList { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: var(--space-3xs); +} + +.heroList li { + font-size: var(--font-size-4xl); + font-weight: 700; + letter-spacing: -0.02em; + font-variant-numeric: tabular-nums; +} + +.positive { + color: rgb(var(--color-stats-green)); +} + +.negative { + color: rgb(var(--color-red)); +} + +.heroNote, +.heroEmpty { + margin: 0; + font-size: var(--font-size-sm); + color: rgba(var(--color-black), 0.55); +} + +.group { + display: flex; + flex-direction: column; + gap: var(--space-2xs); +} + +.groupHead { + display: flex; + align-items: center; + gap: var(--space-xs); + margin: 0; + font-size: var(--font-size-xs); + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; + color: rgba(var(--color-black), 0.45); +} + +.groupHead::after { + content: ''; + flex: 1; + height: 1px; + background: rgba(var(--color-black), 0.08); +} + +.rows { + list-style: none; + margin: 0; + padding: 0; +} + +.row { + display: flex; + flex-wrap: wrap; + align-items: baseline; + justify-content: space-between; + gap: var(--space-3xs) var(--space-sm); + padding: var(--space-sm) 0; + border-bottom: 1px solid rgba(var(--color-black), 0.06); +} + +.row:last-child { + border-bottom: none; +} + +.rowMain { + display: flex; + flex: 1 1 60%; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.rowTitle { + font-size: var(--font-size-md); + font-weight: 600; +} + +.rowOwn { + font-weight: 500; + color: rgb(var(--color-stats-green)); +} + +.rowAddress { + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: var(--font-size-xs); + color: rgba(var(--color-black), 0.5); + word-break: break-all; +} + +.rowAmount { + flex: 0 0 auto; + margin-left: auto; + font-size: var(--font-size-md); + font-weight: 600; + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.fields { + display: flex; + flex-direction: column; + gap: 2px; + margin: var(--space-3xs) 0 0; +} + +.field { + display: flex; + gap: var(--space-2xs); + font-size: var(--font-size-xs); +} + +.fieldKey { + flex-shrink: 0; + color: rgba(var(--color-black), 0.45); +} + +.fieldValue { + margin: 0; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + word-break: break-all; +} + +.nested { + display: flex; + flex-direction: column; + gap: 1px; +} + +.nestedLine { + display: flex; + gap: var(--space-2xs); +} diff --git a/src/components/containers/SignTransaction/TransactionBreakdown/TransactionBreakdown.test.js b/src/components/containers/SignTransaction/TransactionBreakdown/TransactionBreakdown.test.js new file mode 100644 index 00000000..a42edc3f --- /dev/null +++ b/src/components/containers/SignTransaction/TransactionBreakdown/TransactionBreakdown.test.js @@ -0,0 +1,120 @@ +import { render, screen } from '@testing-library/react' + +import { MOCKS } from '../../../../pages/SignExternalTransaction/mocks' +import TransactionBreakdown from './TransactionBreakdown' + +const jsonOf = (mock) => mock.request.data.txData.JSONRepresentation + +const renderBreakdown = (mock, ownAddresses = {}) => + render( + , + ) + +describe('TransactionBreakdown', () => { + it('lists every input and output of the transaction', () => { + const json = jsonOf(MOCKS.transfer) + renderBreakdown(MOCKS.transfer) + + expect( + screen.getByText(`Inputs (${json.inputs.length})`), + ).toBeInTheDocument() + expect( + screen.getByText(`Outputs (${json.outputs.length})`), + ).toBeInTheDocument() + expect(screen.getAllByText('Transfer')).toHaveLength( + json.inputs.length + json.outputs.length, + ) + }) + + it('shows the amount that leaves the wallet, fee included', () => { + // 17071.486043 spent, 17059.486043 returned as change, 10 sent, 2 fee + renderBreakdown(MOCKS.transfer, { + receiving: ['tmt1qxrwc3gy2lgf4kvqwwfa388vn3cavgrqyyrgswe6'], + change: [], + }) + + expect(screen.getByTestId('balance-change')).toHaveTextContent( + '\u221212 TML', + ) + }) + + it('marks the addresses that belong to the wallet', () => { + renderBreakdown(MOCKS.transfer, { + receiving: ['tmt1qxrwc3gy2lgf4kvqwwfa388vn3cavgrqyyrgswe6'], + change: [], + }) + + expect(screen.getAllByText('Your address')).toHaveLength(2) + }) + + it('counts what a delegation withdrawal brings back', () => { + renderBreakdown(MOCKS.delegationWithdraw, { + receiving: ['tmt1q9l0g4kd3s6x5rmesaznegz06pw9hxu6qvqu3pa7'], + change: [], + }) + + expect(screen.getByTestId('balance-change')).toHaveTextContent('+8 TML') + expect(screen.getByText('DelegationBalance')).toBeInTheDocument() + }) + + it('keeps coin amounts at eleven decimals and out of exponent notation', () => { + const own = 'tmt1qown' + render( + , + ) + + expect(screen.getByTestId('balance-change')).toHaveTextContent( + '\u22120.00000000001 TML', + ) + }) + + it('spells out a lock instead of dumping its json', () => { + renderBreakdown(MOCKS.delegationWithdraw) + + expect(screen.getByText('for 7200 blocks')).toBeInTheDocument() + expect(screen.queryByText(/ForBlockCount/)).not.toBeInTheDocument() + }) + + it('opens nested fields into readable lines', () => { + renderBreakdown(MOCKS.issueNft) + + expect(screen.getAllByText('name').length).toBeGreaterThan(0) + }) + + it('says so when no funds of this wallet move', () => { + renderBreakdown(MOCKS.transfer) + + expect( + screen.getByText( + 'This transaction does not move funds held by this wallet.', + ), + ).toBeInTheDocument() + }) +}) diff --git a/src/components/containers/SignTransaction/TransactionPreviewErrorBoundary/TransactionPreviewErrorBoundary.js b/src/components/containers/SignTransaction/TransactionPreviewErrorBoundary/TransactionPreviewErrorBoundary.js new file mode 100644 index 00000000..d5c5db61 --- /dev/null +++ b/src/components/containers/SignTransaction/TransactionPreviewErrorBoundary/TransactionPreviewErrorBoundary.js @@ -0,0 +1,56 @@ +import React from 'react' + +// Error Boundary Component for TransactionPreview +class TransactionPreviewErrorBoundary extends React.Component { + constructor(props) { + super(props) + this.state = { hasError: false, error: null } + } + + static getDerivedStateFromError(error) { + return { hasError: true, error } + } + + componentDidCatch(error, errorInfo) { + console.error('TransactionPreview error:', error, errorInfo) + } + + render() { + if (this.state.hasError) { + return ( +
    +
    +
    +

    Transaction Preview

    +
    +
    +
    +

    Unable to display transaction details

    +

    + An error occurred while parsing the transaction data. Please + try again or contact support. +

    +
    + {this.props.basicInfo && ( + <> +
    +

    Request from

    +

    {this.props.basicInfo.origin || 'Unknown'}

    +
    +
    +

    Request id

    +

    {this.props.basicInfo.requestId || 'Unknown'}

    +
    + + )} +
    +
    +
    + ) + } + + return this.props.children + } +} + +export default TransactionPreviewErrorBoundary diff --git a/src/components/containers/SignTransaction/UnrecognizedOperation/UnrecognizedOperation.js b/src/components/containers/SignTransaction/UnrecognizedOperation/UnrecognizedOperation.js new file mode 100644 index 00000000..b05be140 --- /dev/null +++ b/src/components/containers/SignTransaction/UnrecognizedOperation/UnrecognizedOperation.js @@ -0,0 +1,16 @@ +import styles from './UnrecognizedOperation.module.css' + +const UnrecognizedOperation = () => ( +
    +

    This operation is not recognized

    +

    + The wallet cannot name what this transaction does. Read the inputs and + outputs below, and sign it only if you know what you are approving. +

    +
    +) + +export default UnrecognizedOperation diff --git a/src/components/containers/SignTransaction/UnrecognizedOperation/UnrecognizedOperation.module.css b/src/components/containers/SignTransaction/UnrecognizedOperation/UnrecognizedOperation.module.css new file mode 100644 index 00000000..8829b588 --- /dev/null +++ b/src/components/containers/SignTransaction/UnrecognizedOperation/UnrecognizedOperation.module.css @@ -0,0 +1,19 @@ +.notice { + display: flex; + flex-direction: column; + gap: var(--space-3xs); + padding: var(--space-sm); + border: 1px solid rgba(var(--color-red), 0.4); + border-radius: 8px; + background: rgba(var(--color-red), 0.06); +} + +.title { + margin: 0; + font-size: var(--font-size-md); +} + +.text { + margin: 0; + font-size: var(--font-size-sm); +} diff --git a/src/utils/Helpers/ML/SignTransaction.js b/src/utils/Helpers/ML/SignTransaction.js index 27ef58a8..71a9065e 100644 --- a/src/utils/Helpers/ML/SignTransaction.js +++ b/src/utils/Helpers/ML/SignTransaction.js @@ -621,131 +621,120 @@ export function getTransactionIntent({ return encodedIntentHash } -export const getTransactionDetails = (transaction) => { - const { txData } = transaction.request.data - - const flags = { - isTransfer: false, - isBridgeRequest: false, - isTokenMint: false, - isTokenUnmint: false, - isTokenMintWithLock: false, - isIssueToken: false, - isCreateOrder: false, - isFillOrder: false, - isConcludeOrder: false, - isBurnCoin: false, - isBurnToken: false, - isLockTokenSupply: false, - isChangeTokenAuthority: false, - isChangeTokenMetadata: false, - isFreezeToken: false, - isUnfreezeToken: false, - isCreateHtlc: false, - isSpendHtlc: false, - isDelegateWithdraw: false, - } +const ACCOUNT_COMMAND_FLAGS = { + MintTokens: 'isTokenMint', + UnmintTokens: 'isTokenUnmint', + LockTokenSupply: 'isLockTokenSupply', + ChangeTokenAuthority: 'isChangeTokenAuthority', + ChangeMetadataUri: 'isChangeTokenMetadata', + FreezeToken: 'isFreezeToken', + UnfreezeToken: 'isUnfreezeToken', + FillOrder: 'isFillOrder', + ConcludeOrder: 'isConcludeOrder', +} - const { JSONRepresentation, intent } = txData +const OUTPUT_FLAGS = { + IssueFungibleToken: 'isIssueToken', + IssueNft: 'isIssueNft', + CreateOrder: 'isCreateOrder', + BurnCoin: 'isBurnCoin', + BurnToken: 'isBurnToken', + DataDeposit: 'isDataDeposit', + CreateDelegationId: 'isCreateDelegationId', + DelegateStaking: 'isDelegateStaking', + Htlc: 'isCreateHtlc', + CreateHtlc: 'isCreateHtlc', +} - if (intent) { - flags.isBridgeRequest = true +const TRANSFER_OUTPUTS = ['Transfer', 'LockThenTransfer'] + +const FLAG_NAMES = [ + 'isTransfer', + 'isBridgeRequest', + 'isTokenMint', + 'isTokenUnmint', + 'isTokenMintWithLock', + 'isIssueToken', + 'isIssueNft', + 'isCreateOrder', + 'isFillOrder', + 'isConcludeOrder', + 'isBurnCoin', + 'isBurnToken', + 'isLockTokenSupply', + 'isChangeTokenAuthority', + 'isChangeTokenMetadata', + 'isFreezeToken', + 'isUnfreezeToken', + 'isCreateHtlc', + 'isSpendHtlc', + 'isDataDeposit', + 'isCreateDelegationId', + 'isDelegateStaking', + 'isDelegateWithdraw', + 'isUnknown', +] + +/** + * Picks the single operation a transaction represents. The order matters: + * an account command describes the transaction better than the outputs it + * produces, and a burn or an issuance better than the transfer next to it. + */ +export const getTransactionType = (JSONRepresentation, intent) => { + const inputs = JSONRepresentation?.inputs || [] + const outputs = JSONRepresentation?.outputs || [] + + const outputTypes = outputs.map((output) => output.type) + const hasOutput = (type) => outputTypes.includes(type) + + if (intent) return 'isBridgeRequest' + + const command = inputs + .map((input) => input.input?.command) + .find((name) => ACCOUNT_COMMAND_FLAGS[name]) + + if (command === 'MintTokens' && hasOutput('LockThenTransfer')) { + return 'isTokenMintWithLock' } + if (command) return ACCOUNT_COMMAND_FLAGS[command] if ( - JSONRepresentation?.inputs?.some((input) => input?.utxo?.type === 'Htlc') + inputs.some((input) => input.input?.account_type === 'DelegationBalance') ) { - flags.isSpendHtlc = true + return 'isDelegateWithdraw' } - JSONRepresentation?.inputs?.forEach((input) => { - if (input.input.account_type === 'DelegationBalance') { - flags.isDelegateWithdraw = true - return - } + if (inputs.some((input) => input.utxo?.type === 'Htlc')) return 'isSpendHtlc' - switch (input.input?.command) { - case 'MintTokens': - flags.isTokenMint = true - break - case 'UnmintTokens': - flags.isTokenUnmint = true - break - case 'LockTokenSupply': - flags.isLockTokenSupply = true - break - case 'ChangeTokenAuthority': - flags.isChangeTokenAuthority = true - break - case 'ChangeMetadataUri': - flags.isChangeTokenMetadata = true - break - case 'FreezeToken': - flags.isFreezeToken = true - break - case 'UnfreezeToken': - flags.isUnfreezeToken = true - break - case 'FillOrder': - flags.isFillOrder = true - break - case 'ConcludeOrder': - flags.isConcludeOrder = true - break - default: - break - } - }) - - JSONRepresentation?.outputs?.forEach((output) => { - switch (output.type) { - case 'LockThenTransfer': - flags.isTokenMintWithLock = true - break - case 'IssueFungibleToken': - flags.isIssueToken = true - break - case 'IssueNft': - flags.isIssueNft = true - break - case 'CreateOrder': - flags.isCreateOrder = true - break - case 'BurnCoin': - flags.isBurnCoin = true - break - case 'BurnToken': - flags.isBurnToken = true - break - case 'DataDeposit': - flags.isDataDeposit = true - break - case 'CreateDelegationId': - flags.isCreateDelegationId = true - break - case 'DelegateStaking': - flags.isDelegateStaking = true - break - case 'Htlc': - flags.isCreateHtlc = true - break - default: - break - } - }) + const outputType = outputTypes.find((type) => OUTPUT_FLAGS[type]) + if (outputType) return OUTPUT_FLAGS[outputType] if ( - !Object.values(flags).some( - (flag, key) => key !== 'isTransfer' && flag === true, - ) + outputs.length > 0 && + outputTypes.every((type) => TRANSFER_OUTPUTS.includes(type)) ) { - flags.isTransfer = true + return 'isTransfer' } + return 'isUnknown' +} + +export const getTransactionDetails = (transaction) => { + const { txData } = transaction.request.data + const { JSONRepresentation, intent } = txData + + const type = getTransactionType(JSONRepresentation, intent) + + const flags = FLAG_NAMES.reduce( + (acc, name) => ({ ...acc, [name]: name === type }), + {}, + ) + const transactionData = transaction.request return { flags, + type, transactionData, } } diff --git a/src/utils/Helpers/ML/SignTransaction.test.js b/src/utils/Helpers/ML/SignTransaction.test.js new file mode 100644 index 00000000..fb4f0e65 --- /dev/null +++ b/src/utils/Helpers/ML/SignTransaction.test.js @@ -0,0 +1,59 @@ +import { getTransactionDetails, getTransactionType } from './SignTransaction' +import { MOCKS } from '../../../pages/SignExternalTransaction/mocks' + +const typeOf = (name) => { + const { txData } = MOCKS[name].request.data + return getTransactionType(txData.JSONRepresentation, txData.intent) +} + +describe('getTransactionType', () => { + it.each([ + ['transfer', 'isTransfer'], + ['transfer_token', 'isTransfer'], + ['transferNft', 'isTransfer'], + ['tokens_mint', 'isTokenMint'], + ['tokensunmint', 'isTokenUnmint'], + ['tokensmint_with_lock', 'isTokenMintWithLock'], + ['issuetoken', 'isIssueToken'], + ['issueNft', 'isIssueNft'], + ['createorder', 'isCreateOrder'], + ['fillorder', 'isFillOrder'], + ['concludeorder', 'isConcludeOrder'], + ['burnToken', 'isBurnToken'], + ['lockTokenSupply', 'isLockTokenSupply'], + ['changeTokenAuthority', 'isChangeTokenAuthority'], + ['changeTokenMetadata', 'isChangeTokenMetadata'], + ['freezeToken', 'isFreezeToken'], + ['unfreezeToken', 'isUnfreezeToken'], + ['dataDeposit', 'isDataDeposit'], + ['delegationCreateId', 'isCreateDelegationId'], + ['delegationStake', 'isDelegateStaking'], + ['delegationWithdraw', 'isDelegateWithdraw'], + ['createHtlc', 'isCreateHtlc'], + ['refundHtlc', 'isSpendHtlc'], + ])('reads %s as %s', (mock, expected) => { + expect(typeOf(mock)).toBe(expected) + }) + + it('reports an unknown operation instead of guessing a transfer', () => { + expect( + getTransactionType({ + inputs: [{ input: { input_type: 'UTXO' } }], + outputs: [{ type: 'SomethingNewFromTheChain' }], + }), + ).toBe('isUnknown') + }) + + it('reports an unknown operation when there are no outputs', () => { + expect(getTransactionType({ inputs: [], outputs: [] })).toBe('isUnknown') + }) + + it('never raises two flags for the same transaction', () => { + Object.entries(MOCKS).forEach(([name, mock]) => { + const { flags } = getTransactionDetails(mock) + const raised = Object.keys(flags).filter((flag) => flags[flag]) + + expect([name, raised]).toEqual([name, [expect.any(String)]]) + }) + }) +}) From 9091fc19d8590f670d99e276b9001b12cd2e68c6 Mon Sep 17 00:00:00 2001 From: owlsua Date: Sun, 30 Aug 2026 15:29:20 +0200 Subject: [PATCH 18/23] Restyle the signing preview as one list The sections were grey boxes with 20px padding and bold black labels, which read as a stack of unrelated cards and did not sit next to the new breakdown. They are rows of one list now: hairline separators, small uppercase labels, one type scale. Labels lose their trailing colon, which an uppercase label does not need. The action line finally shows its accent colour: it pointed at --color-primary, a variable this project never defines. --- .../ExternalTransactionPreview.css | 35 +++-- .../ExternalTransactionPreview.js | 122 +++++++++--------- .../InternalTransactionPreview.css | 35 +++-- .../InternalTransactionPreview.js | 76 +++++------ 4 files changed, 153 insertions(+), 115 deletions(-) diff --git a/src/components/containers/SignTransaction/ExternalTransactionPreview/ExternalTransactionPreview.css b/src/components/containers/SignTransaction/ExternalTransactionPreview/ExternalTransactionPreview.css index e5953524..c6dd24ab 100644 --- a/src/components/containers/SignTransaction/ExternalTransactionPreview/ExternalTransactionPreview.css +++ b/src/components/containers/SignTransaction/ExternalTransactionPreview/ExternalTransactionPreview.css @@ -9,8 +9,9 @@ } .preview-section h3 { - margin: 0 0 16px; - font-size: 1.2rem; + margin: 0 0 var(--space-md); + font-size: var(--font-size-xl); + font-weight: 700; display: flex; align-items: center; gap: 8px; @@ -109,18 +110,36 @@ .transactionDetails { display: flex; flex-direction: column; - gap: 16px; } .signTxSection { display: flex; flex-direction: column; - gap: 16px; - padding: 20px; - background: #f8fafc; - border-radius: 8px; + gap: var(--space-3xs); + padding: var(--space-sm) 0; + border-bottom: 1px solid rgba(var(--color-black), 0.06); +} + +.signTxSection:last-child { + border-bottom: none; +} + +.signTxSection h4 { + margin: 0; + font-size: var(--font-size-xs); + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; + color: rgba(var(--color-black), 0.45); +} + +.signTxSection p { + margin: 0; + font-size: var(--font-size-md); + overflow-wrap: break-word; } .signTxAction { - color: rgb(var(--color-primary)); + color: rgb(var(--color-stats-green)); + font-weight: 600; } diff --git a/src/components/containers/SignTransaction/ExternalTransactionPreview/ExternalTransactionPreview.js b/src/components/containers/SignTransaction/ExternalTransactionPreview/ExternalTransactionPreview.js index 205411d7..83c87471 100644 --- a/src/components/containers/SignTransaction/ExternalTransactionPreview/ExternalTransactionPreview.js +++ b/src/components/containers/SignTransaction/ExternalTransactionPreview/ExternalTransactionPreview.js @@ -31,7 +31,7 @@ const findRelevantOutput = (inputs, outputs, requiredAddresses) => { const EstimatedChanges = ({ action }) => { return (
    -

    Estimated changes:

    +

    Estimated changes

    You’re approving a one-time request to{' '} {action} @@ -43,9 +43,9 @@ const EstimatedChanges = ({ action }) => { const RequestDetails = ({ transactionData }) => { return (

    -

    Request from:

    +

    Request from

    {transactionData.origin}

    -

    Request id:

    +

    Request id

    {transactionData.requestId}

    ) @@ -60,7 +60,7 @@ const NetworkFee = ({ transactionData }) => { } return (
    -

    Network fee:

    +

    Network fee

    {fee}

    ) @@ -107,7 +107,7 @@ const TransferDetails = ({ transactionData, requiredAddresses }) => {

    Unable to determine transfer details

    {inputWithToken && ( <> -

    Token id:

    +

    Token id

    {tokenId}

    )} @@ -120,15 +120,15 @@ const TransferDetails = ({ transactionData, requiredAddresses }) => {
    -

    Destination:

    +

    Destination

    {outputToUse.destination || 'Unknown'}

    {inputWithToken && ( <> -

    Token id:

    +

    Token id

    {tokenId}

    )} -

    Amount:

    +

    Amount

    {outputToUse.value?.amount?.decimal || 'Unknown'}

    @@ -144,14 +144,14 @@ const FreezeTokenDetails = ({ transactionData, unfreeze }) => { return (
    -

    Estimated changes:

    +

    Estimated changes

    You’re approving a one-time request to{' '} {unfreeze ? 'unfreeze' : 'freeze'} token

    -

    Token id:

    +

    Token id

    {inputWithToken.input.token_id}

    @@ -168,11 +168,11 @@ const ChangeTokenMetadata = ({ transactionData }) => {
    -

    Token id:

    +

    Token id

    {inputWithToken.input.token_id}

    -

    New metadata:

    +

    New metadata

    {inputWithToken.input.new_metadata_uri}

    @@ -189,11 +189,11 @@ const ChangeTokenAuthority = ({ transactionData }) => {
    -

    Token id:

    +

    Token id

    {inputWithToken.input.token_id}

    -

    New authority:

    +

    New authority

    {inputWithToken.input.new_authority}

    @@ -211,7 +211,7 @@ const LockTokenSupply = ({ transactionData }) => {
    -

    Token id:

    +

    Token id

    {inputWithToken.input.token_id}

    @@ -232,7 +232,7 @@ const BurnToken = ({ transactionData }) => { {tokenId ? (
    -

    Token id:

    +

    Token id

    {tokenId}

    ) : ( @@ -265,7 +265,7 @@ const ConcludeOrder = ({ transactionData }) => {
    -

    Order ID:

    +

    Order ID

    {inputWithOrderID.input.order_id}

    @@ -293,7 +293,7 @@ const FillOrder = ({ transactionData }) => {
    -

    Order id:

    +

    Order id

    {inputWithOrderID.input.order_id}

    @@ -311,20 +311,20 @@ const CreateOrder = ({ transactionData }) => {
    -

    Ask balance:

    +

    Ask balance

    {outputWithCreateOrder.ask_balance.decimal}

    -

    Ask currency:

    +

    Ask currency

    {outputWithCreateOrder.ask_currency.type}

    -

    Destination:

    +

    Destination

    {outputWithCreateOrder.conclude_destination}

    -

    Give balance:

    +

    Give balance

    {outputWithCreateOrder.give_balance.decimal}

    -

    Give currency:

    +

    Give currency

    Token id: {outputWithCreateOrder.give_currency.token_id}

    type: {outputWithCreateOrder.give_currency.type}

    -

    Initially asked:

    +

    Initially asked

    {outputWithCreateOrder.initially_asked.decimal}

    -

    Initially given:

    +

    Initially given

    {outputWithCreateOrder.initially_given.decimal}

    @@ -341,23 +341,23 @@ const IssueToken = ({ transactionData }) => {
    -

    Authority:

    +

    Authority

    {outputWithCreateOrder.authority}

    -

    Freezable:

    +

    Freezable

    {outputWithCreateOrder.is_freezable ? 'True' : 'False'}

    -

    Metadata:

    +

    Metadata

    Hex: {outputWithCreateOrder.metadata_uri.hex}

    String: {outputWithCreateOrder.metadata_uri.string}

    -

    Number of decimals:

    +

    Number of decimals

    {outputWithCreateOrder.number_of_decimals}

    -

    Token ticker:

    +

    Token ticker

    Hex: {outputWithCreateOrder.token_ticker.hex}

    String: {outputWithCreateOrder.token_ticker.string}

    -

    Supply type:

    +

    Supply type

    {outputWithCreateOrder.total_supply.type}

    {outputWithCreateOrder.total_supply?.amount?.decimal && ( <> -

    Total supply:

    +

    Total supply

    {outputWithCreateOrder.total_supply.amount.decimal}

    )} @@ -376,7 +376,7 @@ const IssueNft = ({ transactionData }) => {
    -

    Destination:

    +

    Destination

    {outputWithIssueNft.destination}

    @@ -393,7 +393,7 @@ const DataDeposit = ({ transactionData }) => {
    -

    Data:

    +

    Data

    {outputWithDataDeposit.data}

    @@ -410,7 +410,7 @@ const CreateDelegationId = ({ transactionData }) => {
    -

    Pool Id:

    +

    Pool Id

    {outputWithDataDeposit.pool_id}

    @@ -427,9 +427,9 @@ const DelegateStaking = ({ transactionData }) => {
    -

    Delegation Id:

    +

    Delegation Id

    {outputWithDataDeposit.delegation_id}

    -

    Amount:

    +

    Amount

    {outputWithDataDeposit.amount.decimal}

    @@ -446,9 +446,9 @@ const DelegateWithdraw = ({ transactionData }) => {
    -

    Delegation Id:

    +

    Delegation Id

    {inputWithWithdraw.delegation_id}

    -

    Amount:

    +

    Amount

    {inputWithWithdraw.amount.decimal}

    @@ -465,15 +465,15 @@ const CreateHtlc = ({ transactionData }) => {
    -

    Amount:

    +

    Amount

    {outputWithHtlc.value.amount.decimal}

    -

    Secret Hash:

    +

    Secret Hash

    {outputWithHtlc.htlc.secret_hash.hex}

    -

    Spend Key:

    +

    Spend Key

    {outputWithHtlc.htlc.spend_key}

    -

    Refund Key:

    +

    Refund Key

    {outputWithHtlc.htlc.refund_key}

    -

    Refund Timelock:

    +

    Refund Timelock

    {outputWithHtlc.htlc.refund_timelock.type}:{' '} {outputWithHtlc.htlc.refund_timelock.content} @@ -493,15 +493,15 @@ const SpendHtlc = ({ transactionData }) => {

    -

    Amount:

    +

    Amount

    {inputWithHtlc.utxo.value.amount.decimal}

    -

    Secret Hash:

    +

    Secret Hash

    {inputWithHtlc.utxo.htlc.secret_hash.hex}

    -

    Spend Key:

    +

    Spend Key

    {inputWithHtlc.utxo.htlc.spend_key}

    -

    Refund Key:

    +

    Refund Key

    {inputWithHtlc.utxo.htlc.refund_key}

    -

    Refund Timelock:

    +

    Refund Timelock

    {inputWithHtlc.utxo.htlc.refund_timelock.type}:{' '} {inputWithHtlc.utxo.htlc.refund_timelock.content} @@ -525,9 +525,9 @@ const BridgeRequest = ({ transactionData }) => {

    -

    Token id:

    +

    Token id

    {inputsWithTokens[0]?.utxo?.value?.token_id || 'Unknown'}

    -

    Amount:

    +

    Amount

    {outputsWithTokens[0]?.value?.amount?.decimal || 'Unknown'}

    @@ -544,7 +544,7 @@ const BurnCoin = ({ transactionData }) => {
    -

    Amount:

    +

    Amount

    {outputWithBurnCoin.value.amount.decimal}

    @@ -561,9 +561,9 @@ const TokenMint = ({ transactionData }) => {
    -

    Token id:

    +

    Token id

    {inputWithMint.input.token_id}

    -

    Amount:

    +

    Amount

    {inputWithMint.input.amount.decimal}

    @@ -580,9 +580,9 @@ const TokenUnmint = ({ transactionData }) => {
    -

    Token id:

    +

    Token id

    {inputWithUnmint.input.token_id}

    -

    Amount:

    +

    Amount

    {inputWithUnmint.input.amount.decimal}

    @@ -599,21 +599,21 @@ const TokenMintWithLock = ({ transactionData }) => {
    -

    Destination:

    +

    Destination

    {outputWithLock.destination}

    -

    Amount:

    +

    Amount

    {outputWithLock.value.amount.decimal}

    {outputWithLock.value.token_id && ( <> -

    Token id:

    +

    Token id

    {outputWithLock.value.token_id}

    )} -

    Lock type:

    +

    Lock type

    {outputWithLock.lock.type}

    {outputWithLock.lock.content && ( <> -

    Lock details:

    +

    Lock details

    {JSON.stringify(outputWithLock.lock.content)}

    )} diff --git a/src/components/containers/SignTransaction/InternalTransactionPreview/InternalTransactionPreview.css b/src/components/containers/SignTransaction/InternalTransactionPreview/InternalTransactionPreview.css index e5953524..c6dd24ab 100644 --- a/src/components/containers/SignTransaction/InternalTransactionPreview/InternalTransactionPreview.css +++ b/src/components/containers/SignTransaction/InternalTransactionPreview/InternalTransactionPreview.css @@ -9,8 +9,9 @@ } .preview-section h3 { - margin: 0 0 16px; - font-size: 1.2rem; + margin: 0 0 var(--space-md); + font-size: var(--font-size-xl); + font-weight: 700; display: flex; align-items: center; gap: 8px; @@ -109,18 +110,36 @@ .transactionDetails { display: flex; flex-direction: column; - gap: 16px; } .signTxSection { display: flex; flex-direction: column; - gap: 16px; - padding: 20px; - background: #f8fafc; - border-radius: 8px; + gap: var(--space-3xs); + padding: var(--space-sm) 0; + border-bottom: 1px solid rgba(var(--color-black), 0.06); +} + +.signTxSection:last-child { + border-bottom: none; +} + +.signTxSection h4 { + margin: 0; + font-size: var(--font-size-xs); + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; + color: rgba(var(--color-black), 0.45); +} + +.signTxSection p { + margin: 0; + font-size: var(--font-size-md); + overflow-wrap: break-word; } .signTxAction { - color: rgb(var(--color-primary)); + color: rgb(var(--color-stats-green)); + font-weight: 600; } diff --git a/src/components/containers/SignTransaction/InternalTransactionPreview/InternalTransactionPreview.js b/src/components/containers/SignTransaction/InternalTransactionPreview/InternalTransactionPreview.js index bac28e1a..68456f71 100644 --- a/src/components/containers/SignTransaction/InternalTransactionPreview/InternalTransactionPreview.js +++ b/src/components/containers/SignTransaction/InternalTransactionPreview/InternalTransactionPreview.js @@ -29,7 +29,7 @@ const findRelevantOutput = (inputs, outputs, requiredAddresses) => { const EstimatedChanges = ({ action }) => { return (
    -

    Estimated changes:

    +

    Estimated changes

    {action}

    @@ -41,7 +41,7 @@ const NetworkFee = ({ fee }) => { if (!fee) return '' return (
    -

    Network fee:

    +

    Network fee

    {fee}

    ) @@ -79,15 +79,15 @@ const TransferDetails = ({ transactionData, requiredAddresses }) => {
    -

    Destination:

    +

    Destination

    {relevantOutput.destination}

    {inputWithToken && ( <> -

    Token id:

    +

    Token id

    {tokenId}

    )} -

    Amount:

    +

    Amount

    {relevantOutput.value.amount.decimal}

    @@ -114,14 +114,14 @@ const FreezeTokenDetails = ({ transactionData, unfreeze }) => { return (
    -

    Estimated changes:

    +

    Estimated changes

    You’re approving a one-time request to{' '} {unfreeze ? 'unfreeze' : 'freeze'} token

    -

    Token id:

    +

    Token id

    {inputWithToken.input.token_id}

    @@ -150,11 +150,11 @@ const ChangeTokenMetadata = ({ transactionData }) => {
    -

    Token id:

    +

    Token id

    {inputWithToken.input.token_id}

    -

    New metadata:

    +

    New metadata

    {inputWithToken.input.new_metadata_uri}

    @@ -173,11 +173,11 @@ const ChangeTokenAuthority = ({ transactionData }) => {
    -

    Token id:

    +

    Token id

    {inputWithToken.input.token_id}

    -

    New authority:

    +

    New authority

    {inputWithToken.input.new_authority}

    @@ -197,7 +197,7 @@ const LockTokenSupply = ({ transactionData }) => {
    -

    Token id:

    +

    Token id

    {inputWithToken.input.token_id}

    @@ -220,7 +220,7 @@ const BurnToken = ({ transactionData }) => { {tokenId ? (
    -

    Token id:

    +

    Token id

    {tokenId}

    ) : ( @@ -243,7 +243,7 @@ const ConcludeOrder = ({ transactionData }) => {
    -

    Order ID:

    +

    Order ID

    {inputWithOrderID.input.order_id}

    @@ -261,7 +261,7 @@ const FillOrder = ({ transactionData }) => {
    -

    Order id:

    +

    Order id

    {inputWithOrderID.input.order_id}

    @@ -281,20 +281,20 @@ const CreateOrder = ({ transactionData }) => {
    -

    Ask balance:

    +

    Ask balance

    {outputWithCreateOrder.ask_balance.decimal}

    -

    Ask currency:

    +

    Ask currency

    {outputWithCreateOrder.ask_currency.type}

    -

    Destination:

    +

    Destination

    {outputWithCreateOrder.conclude_destination}

    -

    Give balance:

    +

    Give balance

    {outputWithCreateOrder.give_balance.decimal}

    -

    Give currency:

    +

    Give currency

    Token id: {outputWithCreateOrder.give_currency.token_id}

    type: {outputWithCreateOrder.give_currency.type}

    -

    Initially asked:

    +

    Initially asked

    {outputWithCreateOrder.initially_asked.decimal}

    -

    Initially given:

    +

    Initially given

    {outputWithCreateOrder.initially_given.decimal}

    @@ -313,23 +313,23 @@ const IssueToken = ({ transactionData }) => {
    -

    Authority:

    +

    Authority

    {outputWithCreateOrder.authority}

    -

    Freezable:

    +

    Freezable

    {outputWithCreateOrder.is_freezable ? 'True' : 'False'}

    -

    Metadata:

    +

    Metadata

    Hex: {outputWithCreateOrder.metadata_uri.hex}

    String: {outputWithCreateOrder.metadata_uri.string}

    -

    Number of decimals:

    +

    Number of decimals

    {outputWithCreateOrder.number_of_decimals}

    -

    Token ticker:

    +

    Token ticker

    Hex: {outputWithCreateOrder.token_ticker.hex}

    String: {outputWithCreateOrder.token_ticker.string}

    -

    Supply type:

    +

    Supply type

    {outputWithCreateOrder.total_supply.type}

    {outputWithCreateOrder.total_supply?.amount?.decimal && ( <> -

    Total supply:

    +

    Total supply

    {outputWithCreateOrder.total_supply.amount.decimal}

    )} @@ -350,7 +350,7 @@ const IssueNft = ({ transactionData }) => {
    -

    Destination:

    +

    Destination

    {outputWithIssueNft.destination}

    @@ -369,7 +369,7 @@ const DataDeposit = ({ transactionData }) => {
    -

    Data:

    +

    Data

    {outputWithDataDeposit.data}

    @@ -397,7 +397,7 @@ const CreateDelegationId = ({ transactionData }) => {
    -

    Pool Id:

    +

    Pool Id

    {outputWithCreateDelegation.pool_id}

    @@ -426,9 +426,9 @@ const DelegateStaking = ({ transactionData }) => {
    -

    Delegation Id:

    +

    Delegation Id

    {outputWithStaking.delegation_id}

    -

    Amount:

    +

    Amount

    {outputWithStaking.amount.decimal}

    @@ -456,9 +456,9 @@ const DelegateWithdraw = ({ transactionData }) => {
    -

    Delegation Id:

    +

    Delegation Id

    {inputWithWithdraw.delegation_id}

    -

    Amount:

    +

    Amount

    {inputWithWithdraw.amount.decimal}

    @@ -481,9 +481,9 @@ const BridgeRequest = ({ transactionData }) => {
    -

    Token id:

    +

    Token id

    {inputsWithTokens[0].utxo.value.token_id}

    -

    Amount:

    +

    Amount

    {outputsWithTokens[0].value.amount.decimal}

    From c105967b27bb5f844d8752c8a7caf65900419755 Mon Sep 17 00:00:00 2001 From: owlsua Date: Sun, 30 Aug 2026 15:29:54 +0200 Subject: [PATCH 19/23] Apply the send button class through the CSS module The class is declared in a CSS module, so its name is hashed at build time, but the button passed the plain string. It matched nothing and the button kept its default width. Renamed to camelCase like the rest of the module. --- .../containers/SendTransaction/SendMlTransaction.js | 2 +- .../SendTransaction/SendMlTransaction.module.css | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/containers/SendTransaction/SendMlTransaction.js b/src/components/containers/SendTransaction/SendMlTransaction.js index 41a1c061..8d418bb5 100644 --- a/src/components/containers/SendTransaction/SendMlTransaction.js +++ b/src/components/containers/SendTransaction/SendMlTransaction.js @@ -242,7 +242,7 @@ const SendMlTransaction = ({