From 8e662488b11c0b69e8350a02928e5456cf43b74e Mon Sep 17 00:00:00 2001 From: Andres Ramirez Date: Tue, 8 Jul 2025 13:10:16 -0500 Subject: [PATCH 1/4] feat: added Bridge page --- package-lock.json | 549 +++++++++++++++++++--- package.json | 3 +- public/images/cosmos-logo.png | Bin 0 -> 59066 bytes src/app/bridge/page.tsx | 11 + src/components/Bridge/BridgeCard.tsx | 258 ++++++++++ src/components/Bridge/BridgeDashboard.tsx | 139 ++++++ src/components/Bridge/connections.json | 11 + src/components/ui/icons.tsx | 23 + src/components/ui/select.tsx | 159 +++++++ src/components/ui/sidebar.tsx | 13 +- src/config/chain.ts | 2 + src/services/mutations/ibc.ts | 47 ++ src/services/queries/ibc.ts | 89 ++++ src/utils/format.ts | 52 ++ 14 files changed, 1296 insertions(+), 60 deletions(-) create mode 100644 public/images/cosmos-logo.png create mode 100644 src/app/bridge/page.tsx create mode 100644 src/components/Bridge/BridgeCard.tsx create mode 100644 src/components/Bridge/BridgeDashboard.tsx create mode 100644 src/components/Bridge/connections.json create mode 100644 src/components/ui/select.tsx create mode 100644 src/services/mutations/ibc.ts create mode 100644 src/services/queries/ibc.ts diff --git a/package-lock.json b/package-lock.json index 63e3df6..13961f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,13 @@ "@cosmjs/stargate": "^0.32.3", "@ethereumjs/common": "^4.4.0", "@ethereumjs/vm": "^8.1.1", - "@kiichain/kiijs-evm": "^0.2.0", + "@kiichain/kiijs-evm": "^0.4.0", "@kiichain/kiijs-proto": "^0.1.3", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-collapsible": "^1.1.2", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.6", @@ -33,7 +34,6 @@ "next": "15.1.3", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-google-recaptcha": "^3.1.0", "react-hot-toast": "^2.5.2", "react-icons": "^5.5.0", "react-toastify": "^11.0.5", @@ -53,7 +53,6 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "@types/react-google-recaptcha": "^2.1.9", "@types/webpack": "^5.28.5", "eslint": "^9", "eslint-config-next": "15.1.3", @@ -3230,9 +3229,9 @@ } }, "node_modules/@kiichain/kiijs-evm": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@kiichain/kiijs-evm/-/kiijs-evm-0.2.0.tgz", - "integrity": "sha512-7ehH64tswOIrlpUv/7YKk9sXcqrXP5e1QX6P9IOiIOHGFYDirr+NyTQZCBy0+zuQe0oxQPw1kyU7gWH28nnZBQ==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@kiichain/kiijs-evm/-/kiijs-evm-0.4.0.tgz", + "integrity": "sha512-FIejNxN7bzJCJ4rAaNmW//wal8qoYtONpUPTUX1nej69jfrBmPZdDXNwgVQ9qNGHLB83krPK6UiF+rZCEfkMUA==", "peerDependencies": { "dotenv": "16.4.7", "ethers": "^6.0.0", @@ -4150,6 +4149,11 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", @@ -4630,6 +4634,440 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.5.tgz", + "integrity": "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==" + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", + "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-popper": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", + "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==" + }, "node_modules/@radix-ui/react-separator": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.1.tgz", @@ -4738,6 +5176,37 @@ } } }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-escape-keydown": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", @@ -4771,6 +5240,20 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", @@ -6847,15 +7330,6 @@ "@types/react": "^19.0.0" } }, - "node_modules/@types/react-google-recaptcha": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@types/react-google-recaptcha/-/react-google-recaptcha-2.1.9.tgz", - "integrity": "sha512-nT31LrBDuoSZJN4QuwtQSF3O89FVHC4jLhM+NtKEmVF5R1e8OY0Jo4//x2Yapn2aNHguwgX5doAq8Zo+Ehd0ug==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/readable-stream": { "version": "2.3.15", "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", @@ -11334,14 +11808,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, "node_modules/idb-keyval": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", @@ -13588,6 +14054,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -13759,18 +14226,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-async-script": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/react-async-script/-/react-async-script-1.2.0.tgz", - "integrity": "sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==", - "dependencies": { - "hoist-non-react-statics": "^3.3.0", - "prop-types": "^15.5.0" - }, - "peerDependencies": { - "react": ">=16.4.1" - } - }, "node_modules/react-dom": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", @@ -13783,18 +14238,6 @@ "react": "^19.0.0" } }, - "node_modules/react-google-recaptcha": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz", - "integrity": "sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg==", - "dependencies": { - "prop-types": "^15.5.0", - "react-async-script": "^1.2.0" - }, - "peerDependencies": { - "react": ">=16.4.1" - } - }, "node_modules/react-hot-toast": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", @@ -13825,19 +14268,19 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, "license": "MIT" }, "node_modules/react-remove-scroll": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz", - "integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==", - "license": "MIT", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", "dependencies": { "react-remove-scroll-bar": "^2.3.7", - "react-style-singleton": "^2.2.1", + "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", - "use-sidecar": "^1.1.2" + "use-sidecar": "^1.1.3" }, "engines": { "node": ">=10" diff --git a/package.json b/package.json index 9253d66..621c642 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,13 @@ "@cosmjs/stargate": "^0.32.3", "@ethereumjs/common": "^4.4.0", "@ethereumjs/vm": "^8.1.1", - "@kiichain/kiijs-evm": "^0.2.0", + "@kiichain/kiijs-evm": "^0.4.0", "@kiichain/kiijs-proto": "^0.1.3", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-collapsible": "^1.1.2", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.6", diff --git a/public/images/cosmos-logo.png b/public/images/cosmos-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..53fead1ef044718366a7d3f12b5cb2485463b2c5 GIT binary patch literal 59066 zcmXtgcRZEv|NnKLVXur5GRm$*i58AgM%h~lX`oP9Wu2SM7NH`^LD{RJK{!e&kz<$9 z(K6#G<8V0V{$1Xm@9z%}&bhDgx?b07K3}g}vW=CgAfFT;06@^(%)|}=0{oKzc(~v% z@}{j<;4j|uX0Aa1@QbbeAmHA8N%$oaWM^swDnH9itc^coXlV$*;}rf?FCqY=*5)RL z4wsNW!-rEdPBo1ECFf7q3u`Va-E5k$(h*A#RX8_4UU-do_o?V+nH#RQluE?C&F^0R z-CcRqP=`_%KWY5utM7@Y+OBh%96{H0b?$Esi0v*qDoo=Uxa9TC!%06?+|}{&YgS+bhRro*B9I(_{-_ZEpl4>ZJ-e)QM_H|be;D~s^tY9t9*e8=aPlx4 zW4Zq6Pu$%<)~^vJ$BwS7a%QcvhGISHhWu(YqyZ?F;DyV)cOcIB*KNAbKmVH zCI#-iE0TG1cCKT?>aM%mnY#ec+$jo2&UXeG(Vq_@t2QsdkT) z1OG-varL~hd$X-YGz$Q*k%1eb=eCRfssp)Vk=NfUam_Du&!mj`>;T}`b$D&^m-%t* zqVVV;9ZGclZG3K6 zF9J(7J$pOFDc04SM*+x-u1y-LZQ{fY>-YAjOmv#2Z!cf(*#NiMvv%UDaH~afFME<} z9&s6X^N{MQ4liE+7JPEOeDEnFHj=n1cBoo-N&Ac*FXH`09(W3@F#&(nrnRbSN{}?X z-iepC4**FXNCq{(6!kzjaTjWZAD;U>bA+c&3V^6oI6ior>KOUk_%Qdy-=}$16*LbU zL3Y9iXu>4#((r^nVqjD0xl7r)QGaBJWY!Gw#_Yud@npy~@rOm_9xAJRtAkwNZPwHMVz}~61k`A4kG6#)ljMdK z2@rX2_W%)4c;E@-ckx>-tK4`J4HmMV1{hH+xMX$#l9L(aC0DH>_E77!Fv;z7S<5=_ zPykNGzzq~j_dli!GO{&+S@r<_*iO_50cIkQCNneL@iV9#I@Ip^Vjo-x9$KDvZHvvU zWkj2L|CRti71!vvw?JU;KgW5POOYel`#ho~aPKq`0h{Ha$n2IM=SJ&N;mCWC_b3`3 z&QiHSkpt_0rXkOG0f_|Hqwb2029lrdYe8|1U7I93U;by%o4^JmpYFFibIFvZAwgU~ zk{{9}Y5Ssz+%;;wq9Oo9y1q_cC=PE+gR6J0(V-B0g_At_c#wyM$lr0z4qTV11*lYb zbSfC1;zkv<4v2sR61<1?oqE&#=8b)e6}o`bI$GKiU2qV7dvNX1_=82pE{jz}-nmkJ z1gdL*0;DLk#n-@o6`{xk=@{(rG;l-!T!!@a!hkTip>P9jeCOIn0ng7++9(Jl3+mG|YeBwN)v zZg6SmSph+UjU*K2C#G2jK%TOpsLzmL@2C15G ztF&fM13*d`{Sz8Ny^oMiz}?LFVhjKU2nY~T71u`4a>lDT9REWj!a%j<`Yj%i$^*a7 zpA=dg^qPy}0FdGL-i!c}pvt4Fnd8F1r+DL*b^p^?zk_fV>AzVvyWlK8!?n*IyAEvz zBuls#&sC}%Kx5h@lL3f<0wY3zdxI@mTA~QR!Lv}y)Mq_BqV{^iBZ2GQYx~Tei$_py z#}Rs^YX6tp^b-?ooE|TL09!IlTpPj+idlj<#vxI@C79?lrQC)yp4E7NDa0eGnT#<{MjoGw6v|Ma;hp9Qx zdqCz~&d$t~R{+0!-+z$=6k*7~057VXO~Ti&U#(~cyLNS(s&(RyA`T|}E zl0pkhTk_P`X|m#>TtTA7J*jf321L#Zlfj5wusIluU2533`UwA@?{65!k~apMJw1@3xhAS4cU}RCjUqse z9OXt>n$Edc2J+!S-%1HX|5U`{pNZAI$jP&%;rAyhxwvfPPXiD?ac8!qgZu`F8yeOW z&Wst6`=VAdcW|8yc>iTR5Kklko1}n`-)1Hs7lqq2aElwATG;2>A!rULlJ<_GXqPNS z1n!2@9F|i}59iqLSn+6WYr8)Ej0jxeDNmO2aU5q1PGzj%-*x)w5wVhTmUK(0W7(g!tX=;} z$GXvT`n*v0pBTd>1&lf9lZj^X+XTUb1ng!kSqaGy05;dZPCice1M)k{5yH-V_G_KS zfk1ddU&gokgG4O9v14(1P>^J+T-vfcQ_qVYftGluP1MWEDp|hOqU&j7ii;bwsOhrO zy_}rsRW=YmQ(9}fmtJ8*NZsQG(72GqRkf!N=t7#nxKnL;pyq5vumF*8^?fTz{sLr% z$>RZGpPQ0V7~h-T>?*9#YHaGw0YJGF)a$+FDS~Qu;(+3zB2KSy8QV&#$$-WavdG># z+pqRo77BJw)mTUF3n)=#S2z#$hs|h%cYcGb=TQ8x#Ua3(hXZCjMI6#x@mS+c)J0{6 zTS&%bC#^sCU=%Avaf^`b0iU`sh7LHG{dCz9TGKV>hZ1O0!rJM$=^N6zT*5CML#xuG zqbz@SJdBHTL%5cAAs=KRRVFkXFzM4<=#L}Ek||!3d;C2bquJD^#+N5!IAjIi8KeS` zysOpz3v%Q|!`U&X?O#QW-zLB1!mQlQfuP)h&_9`oPu%nN4z4d}Kz<$(BvDwi-R?Ev z?6=?C@n&|L?6E|(6_4wj`+nSeA{NWz^PPa8!pW_GrW*9V5XkeXLznB=)L3}D7YbCkWYq9?dOjEd~yPeYvD@1_QBYy`xu(d%z&%;4xp*_Znr@8R0_E)-}&5M() zEHZeizFxdMF+VQeNgU@Rb6e8CND35lO8}B!xD{JPfK)I&T-R^ZRcb(2S!6LSt*wum zIUwN1qpKfw_11SiM0jozfQU^Pjovw#$X&)QSV?;8JMQrzFAVDYQ+P4_UW3p5bI4>> zDV|HeKCHvj%xX>VRnBA;eia9C2l;`BSy+AU&9(t9AaR?MILYo;t52ASFU&*v`iyCG z!)bm20zt|js>Zn*x2Igx{qWSlg~9EamwE*g*DOnH5b`7PNKmGhJLd?L&Vk;aig;X% zCbSj@Cht^0&hTO&z}B-Yea?m#EU+10pSDcq>m*DMzBtNLP9)I0Kf7$;xpa^SMtG;# zV4L!_3WzZ zBGc_L?mUt^xyq&taU_cTWDw(qP+` zo{2`>Olm!|;7ff(SanU*00olacd-wk zC)ylSZa9j1eg^X}&~YKybCB!#$wmJ&R7Nj@C+wEe@f!h2&T+OX zja;~jmvk$B5R$_wl3D%K+-x$o4)+uQf#UAkul0BF)c^`~O2EfPr&SmbO2$}Wj>M7d zK2AJeWfY$yk9au%Sv+>2&0;>vNfZ7`5+a)1KxG*{vSj|A}1u2?FQ zmD+AOE9|-LK8L>k3E#+vTU)rmmdvhW=uG$-%40J?c^vMvYAFS-t2ubq2GaFE*%ljw zB`sV9Qr^}Jd%Ekn2~x@HfwtWXy2o422OM7VXmKxj-$CbPa%B$}B^1yvo=pa+_xEPY z30>+#6QK3G(LK%V-?SS>Ogk1C!re}_5zK&3uEpvg0l`Vy4~}j{{%w1Vdru-lJ5eVV znu4Gzc~;oqbm*CB_ZcHc#79zoD}r1)U)}3By_=#?3`kpw`^&<|d!WHVofTq_eym}V z6`%+!23Uurd^iTQxg>z<@s8@o=LmX6!3n#45%S~GO&4As>eE+~k01B?wQsc1R0lTC z)R*W^QG2}!7{Jjc$Vt8ANB24VBNmr-G;~=|ZF)BVsS^LWAmlk5E(%ng!m9bxIS4`Q z#6qss^TXNa8@*d@%ClK{2|I8;+uVz9?vxQx+Jc~)LlW7R8v!9d9TV9<@xp(w&lgcm zj>SYAp`zI+0dPsDxBf2SD)fd2OEmh*Bp10ULIRkyRq^cv0X#v3=PhEZm;mx61y=Aj zMIu+G9a!Xlzs$EfCf!EYo`1QoB_l$FoIO|^i!B_Q6{aZXC1COUl&lXXO>#l5@|3&l zS%oro$NaekNQZpk0Mo`V8gxs8b z(1W(6!o)FLnSFOym0#XmYIVbjk^5HfvVSd)yffzXu)9>K^v1ZR%chu18J z{`>Xuy`uv#i!FRR1FgS+qG1=5z^>8}j~kEoEvqbXi}dChC${MlOD&kxBV=!9g==!#|!Fb*z7~LIXwsAW!vtz#5dkUliA||aH zpq(3xi=OCGI=in1Dc(d?8GuslSqcSTS6<5j$nSNWB~y7gh~h7(x1-Rfo!fOAy0^LQ z%}jofpnXsY*qPvfd5K0wckhPqBA^)m4?DjW(a*u?&ih*#AMhmD4@Vr8->3*u<0gN+ zEDiVhLVt*mjKo(v+S{|NrB+RoS+7N|h)ArSJ&T8X+c96(_V1QQ6oH64GqxC7BOtG( z(E5>=!!VHs=o?E1;WS`|GY0D%Lln*pM$Xc8fEf+OSM|=|{@$SXeN`TTnMGsH9Z#*L zo?POrF2iiZtohi&moI)bqXJ2Be89wbR#-kHuN8w!isviE>OiQ?Z!l7cG>Dp+!3ZOk z4EFNa9%?uv9Ave^`&c68zX8(bz{G?f_?g1V8x&eG$2&6-S{N~JmV|O*& zcS1_;cM73A@WLt}*M4m_S#Gp|`>fbk%jOHpH}jc-S&F{{P97%;iUpw;yC0`2C#*Hy zpr|ka^p{5Pc+{lkh9dAg1b4D^|DyfZqRHTACUP?7AI9qB0+c+B!N~m}{qQ;&3BYr6 zM2-}ot@q&nKMPKYd=k+iK!9$<5c=E?wn%ZHm}123Ehp8l2HL?dIw15s_lJdK_q;}Mf}kMIDOCkhO20W=I6 zuZ!#RJr0<-MtqD^0dbH$8x?p_PcuN>x;C5tF(IZoK43jb}l6fB-Ol=t>85Cz_w5%iYGDqhtx ziGzup`eP}2LWq>a*adou{S^^SE>R%47I}!qk7GqR&jXFD-m2Hu(?t-SKn4zpf-P`* z-r98Aq^|Qs%neEER9o_B{KgnL8?tVEW#{f7zG^KA#6K`u2Ddr6 z2IOxe+*2D7PuHgvPG<{{PZS*ZR2qxiP5WxCgK~9kX;9Gw}9!guZuNcY*K@~>z zAx5+q%=jMD0DbokcrTXgPOx(so;4Fj<#>nR+*1G%_q(bH)2QKA_b5$cUM3UlEW1XA)Qa(kw*H#~n|C-`76dunrFE{bu@LslIv z<&T@(nkCCe(Tco_l{w__p_)c*2s|MPFEjn^Cw`M6pxL@h4)oqF=)LmZ5!|4N&Wf-x zo|Z6aQqp=6V0L|GIwh2`fQW*Z605o8vi^89!ISL~4Mls&3Wtzl*TQFWz{86slxYxh z$|+EHd?fp@-z{N8G%gd5{Legq-UdOKp-wIr?cgE(T&e^8fVF*kngY(DTnY$;7kcGPA#;zgzcpA#PGD7c8Gihu00LK^ zbu|0%I{fg&;|-6krKUJQ`&iONhRCT2mt#qn`Qa_mY^EnKq%ENBhB(Sz==5}e&r6XQ zSqZSfrxh5p7s3+d{tmplT)ZgPcqNO{$PL=alp;@usEK0hq`g3a%NR#Gr0gR61;WpK z{qBDsw*Zi%A4rdpEy6A=RpM+;z)?Wz<^n26!cjbXr7vYao)HECd03;2!YF_`333+D zL4kLh5u4%}S|jvwL;pHW<+1`DK=Zmaz3k@(Cw=ahs$ok>6vI-|VgRPU-tv#WDK%fH+C3 zr8D49%=KH~LoSO0v2z!3Hujs(Hu*1P&PG`BV2yaIdXAcblh@%onv(QW!;^X{NNR$} zQ%N5@Ko45(6xR%do4unM0Ll9d4mlQk2Jd4HpFVs-)$=`^nY1=jhK}Sgnt~)#7;)E6 z1E2K(c-nRg5ZlCf(F1BrFj4Y!Rj>+*5`OUZl99m4dH3Bo5brC^o#wh3NzGF4E^LzS zd4Qv$lE6hj%0T;WlL{9!VNd$Pyv0t&5lDwhb-x67kIMmpcVR?QlBd|uxtt6yA(*?I z6gEcYjBwCW$!0^Q!slvXNi{)|xeNl>4k6F#s5bluZeCQtAy9SG?woR2nY{>AM7zyW zIM--C+dHyBY?l;ne5D2t;wPKchck)ETaZ-lg}!HkKr;i{g@&-tZ&O5)H$pw2QU`S8 zp02?$qRywf79Bm5;TUat)X*gR4Dh@+U;j7fejap02|GU?K<94(GYHoJO<6i3(?lMK ze1T~4K-@i)a@A|8`iLYiY7%k5Z#%s2W2^p@&U zzr1d)B*=VT^dP)7vTG_4dQVBWy6_{?X(l*%!+AEIx}=Y-2F=}Ak3NfE;;ij)=*f-C zz!&mWlEp!&!gTf^pT=$40@{5vK(G-@4wY+tX zfhcdmyc~GV{}?B-JgZmb6%cUK5CtPG4*~?@U|?p_H64=$*Kig9CP|kCEnrdOb>`{a zo(GsxC-UG#+|ue?NBav1jgyzYb1PlnZ@}TKFc?eAVf3i}<)`k0-t#SI&`%640+hm8HrU^r9vFNMnp@R&3;GJL0} z`YN7QHF8)^Xcc95@MZl`F#=l@|3jVYj)lNdz%XsfqG|`!7Svhn8LsCVfn-_x2(#w` zJj%_0s`B7cti<_bThaH9!t=F^hLwHEh!Sx=YEF-5w%ghO3K-yQDn9Y(t2!^K%2Ch- z?~)5ugr_xZ8B4d;_x+Hl&gD??_eCSSyNY4ZH(0iIN z5|%$;fg$pmmOrph9-JTb&bjN+*LuvMpwaZ>9_OGvr1%hdt`kV43=W>OXOh_fgZZNV z6*^~{{)db)F#iVVc>YP`$6tZi2T!U0Gu+ymsR7Ha83=eyQ^RV`_U}WmTx@mhbAMF> zYqGEOC9ET4S@X$a-{m+>sXL;uNEJkX<JExnu-AHLKV1{b!0n!_P5` zBNKU(8Bp}coOY6gVMIKjL+7jRAAyzkj!c#?cxy{zSS?E|35(4$yFQj%3xI7`7#2c~ zVyn}EutBOBs`gI^fz~ymla5f1d$udgFofYQf?rAq-<*`>?=D4*gYF;Pb$i`fftkwdop%zg* z^w@5XMx7QX#(r{PKc2Q64X!;*ByBVj2$E6y{!0`YaLMa$$X0;0D7(Qgd~38@tFpOK zj`**#lWvBM%FMC@1Ew_kr!XRz!dic2sbN^V1;=Ib^lAmvOZ?E899h84}S!o|7hy%HyK z1~$+3H*#ur?MXF(Ipe+J6V-Zf%eCtCeC};=AY~X6%~!-`AA5PeDe}jBo5B)TX;bH*WnwTn)0IHt=FUhTK=jjnvY9d{MYf#eTbuWoO1kwf)Us0qT|U zlGoD(7Iwvb^7lX7g!km9g4(xAd)VBmImeuqZ?uMi(Umw!-@kezT8Zn?cP~xQqY6tj zW8sb924Ti&fO8y3lZT)`^wmMr~> zY*b*r*}=fg@=SjPAaxLrs0du!j8toLVTjqC0Gj&l zoXnk3e6bYz9HLxV`~l7)fm@LCg8@J1XQdLrym(u~88E(JCQ31QPJ|W0vq+s8XoG%> zR56rlPmQa&@yr`GhW{b$7N zmTbUfyc?h&q&$eUaRMn!sW}cIdCv5BcxcJed12M+Ua}#2!B!B+rzICUBii*iR9^%b zUYKO$GSZ#sTfe7r)9GQSVUfuXfLk)vTxZYWC=o1}AoIl8W15_$7K3}%P*}QaL#EnP z)WIQBxZAx7#{1YCJ-_&X6^0PLE3XDkGUT>`?}w8Z`_0i#xEtj(cWJ~k$3D`HBfx=v zj)=h)mX3g-mtG!`e~Dro{NH+ql<+vj$Wn8oxae%X%ZETS$zQ7CmI0(-{ zmG0ULzvtCu=qlv|to^+@ReM6J^y~!mZGr$dFx;$;MpRr5l`O(K+uGYhT33epI}EVZ zzA7f?!S8ann$o#(_G4MMvEG%r(7LE8CXzRP+VMAs9=%ebu#Juf=0H2!31OA1dr44W zx5|QJ(dH4Db=JUOq6Mv;I9|hSa1`z@{UQ6gnpstAdfS8Z@9vA}*U{|onWIy`OF?0| zq10_K{(xh>F%}j*&W-(@D9R1hNMikc!+$C#*em2CU@nvtJ*Zs>3j0n>cw|S;vFn`j z5OxA@RGg4PYhI@sN4k%s@*1^r1B$J5RZ$;t!^>B2|@6deH)6pUOhw`)U`;;&%XwXm8Oy5 zM$Dz3DG|fCSr|hN26gD2@lhuJ$=31TnX7U9wGWSJlfibx#SE#RF?FX@$rZ`|TA?UX zVFSz`Sfwbs7ZQ2)Xhq-s-K2C;(EWb4@v-*yak;p|0^_*yW>A0hcfl_MSt7>*ECo;Y zsFeb#>-`Q5J)#sppna`upChHp{$C-1b%jPmzTx;K7y&$6-%w^DB&UDw=!<~e zamgbjgHQZceT1gR&eaw;7=!inh;Q()W+@TF2f2^g zfr(d862}r?U3e){*rniz^p6M$`72;b!uv3qEz@{@>F){V5q_0g`;9(f2gn(J6AKj4 z2Xxrl^krtaPVm#c;u)s0TSZ^FL^-bnxX%$b+%N*`&Rst}@L}DQEIy;#q;T=2UB3+= zASn{d=tm${7PQ@sCFQ3<-4m3`vJCD&^LnaZLvJH3TeCAL^~dw#w25!Wc|rXm?D;X@ zP5mdn|Fc`uRV#3wWgrt(thO|kFVk|u5vQR*t-Z?{SGKd~IJ#X8UJ@#gtU`?+L^cXV zI*EDiacgHW8?j-hs^ilYN~GM6umg%2t(h577^ zTp7-o$T{43rgrAtMgGq`OLGPNS*P)ywoe^#lrOH}oifN=HP{o2McOKiY+!SIBmO*Y zX)6*oSWPuVs}0x_){AYsIx?*-fa*H*<-DF`s0M0f&YT&JDAU`)rG&H7=nYk3!`3ha zA-^}nZ9D5-;s}&f)}#?MkzF$g=Ou6~Ev_)=ldYCjyMC=?IJqDY;|<~c_$b(C&j=+5 zv6Dvc+o63CZ#wQ5UrZ&q*gbXx2bI@WxDGw+i$L9skAf0I&;6y3a@`q8F!jw5?^^iV zA~(pt zAbdGmZ4#s zXH(SaR~D#kI;byNg?+^3ayO4}C|#vBiOkp?lOWmd0^@ACHdvqA%Wg?K7J`c69^H;} zuM2Ho(Z3w`;_}?-s+C^h|1%+^n{oyLb)nNcJu(?4z0A^Oq|+{FxgoJA&A-taB&7-=S^cZt_+?ixXP^ z>Gz@E-t(8e9QMB88-Jf@j$O9vI&^j*@`UyN@c2tNN^q#F*hFXR(-+^uKV2;9@a%tD z(V?3&Be(bBLow>Qac_tEs;xt({L6apJ;h~YfJ~hPY`b}u-ld$*-uq}-IbH+V_Jbkxu^|yOtpxO+_k-npy zud`#g>e0Wb9`vNn!G+!SN+8q?xhnae{x|Pgc22qXB)OI}S;o(r`lV5+p+<>|S=lRh z?`Nm^m8~1Pna;Xf)Rgc;R260RSbNys-G&=k%kOqXe-HT_C+hsVuPblrLv~<;uaXyK zZfVFcW5#*Gd$>yDz{Aq?I^AAlW?5}mRm8zZ>gJ)0Z~iYoJy#zcI;w;`5tgy|ii4g2 z_KGWOyKftwed%D)9I@*;`(-mKn;XrVsCWoI#(bX-kf3pw9Oi0v(=wa&5Q0&fqYT2Y z_qSQ>xEN?d%sxp6zHVYZ%nQG=3Q^V4xghqQ_jx@R=f^tPPi1coexs~hiW1cObQlv9 z1yK|4o)m>2aUFeP4T7z)udZ5X>P8dy;^4<0F2vtok*9foXQjtyxiGy&@rS#E9_AV6 z3%lr5?GOD^ynQ2S*5eNU28~SX`>pwM^kfBk)zRx#F<1nn{*3D28xozphv|Q4=6w5b zLFqr^qWL`ah%AZc4~HH9T-Ic&qoV3S`}VCvw@ZeA~8|>>Po1CMSkWz)pRAefepB*PpkE!h?MkM+L`2R+lae)||b&oG%Cl2c0EI#zU$0qle6F3P%Er|^cXQU1V^d;m|Na(MEcI7 zhP?RS(?4nP&KnX7PaZY>&3f*jHp2Carhi^n?jt(YUlsJjNtNFHl}^T@*6=8S@!$Jx z93Cb2S#H*-DX`__rhI#Tgt$sr`P3ct=y~Mw>qdu=n3<2&s)G0fK3T1L2z3HO=MQ~* z)lDDeTa3FI;B}ic-0f~ZmWJF@)AET;9%($Lu=sSVYJni&kH1t=f+O7qNv7tT60k>~ za>mQ9dwA>(k!^=2cePew}#q}AccEv=zkBqjvxlVa1c&R1>ESudKWXB~~? z-`C#lng1+sz9ap)#kR$J&{Ey&@Ai7A)O=%qlL4!9%1Ai3U#!ydLP^Dy5rgdkde3mt zN-KMZnQxgKHURj=jf$+lLO&vPFC@#-p4Wjg9$ZqXyU*HuNfQMEk`#K1$@j`boi4 zoys7W7r3SWVEszB7Ff8bvRXRSox-~`^*v%-`*s69>q8BnJghq1v)k-l8x_S;B8c@B6T;Jx9rg2R{$HQOm(0Zfb2qXy(7E$tW#Iz!K*ll_vUVJ~2W6hW;tVuqJ{5d? z!Y5xxAl>%$b%rGtqoHdy_WCauq`E`% z8GOOR{oIH7_eG3~@SnprhPt}c8UgM2fWb&QuYZATkfAqhs^jExsI~~>-1(|&mZbXxcjTd|UYOLqqTM%OpT`r} zYZ{t{w`}d2DqOjt`)$0O9R59i!@m8LSL-^`84PI&;L`eE{`VX-8{<5KYtQsV#m&eC z%jK;kmz1c*VkW-VG6Ee!1x}et6r8@T^|1oynIE2-CKaPG8op~~o3QiqTbuHHkABgh zHBX``2Ar33BBR7#EnzrR0d=uR;sZ*Eq?4OL+K#suuB;68ME`orWZ$3JR01TgKG^06 zj<_nj-MOV+cd>!hrE~oxY)XZl>RNLLH*lXyxK;GcE0#6z@GBB^QFNsg|O*n7O=`H@I1E{<3BlZU{ZPz)et}?svvDL{_-h^-byBPa~DGs5Tle z+$wtSB%gBwYi|(na9V!sFPOra{rVWSWNmFdxI8zue?!FW)!hnnj8WpbfoCN_>1NkR z_ae#Gd*a{8M{-XcJNm;YxC?Gb<&|HrFDh+{CjJF%~^fr zQFe~oPK|GxDG(NOyr!M!NK!GWRSklLAwmyV@k&@$xCVjlUxa)D_ z5!0J8T6Cjke!*Mq7SwI(5FEpl1-yB0b?%2PT6qWRiwLDcG_{!QEjBwHiGK!a%IrfM z!WhS`W;Pe#fL1l1$SZpqd4w8s6u&xu*=p89Ik%!-h&y6S_J>D&@^{w*C+spsbO$}q zZveE8^$GY%oe%Lk+w6q&Z>H59L}Tn0=AtfI$mwu>kFZgMxj^?q?Z@pvW4!2D<2i*< z-HGbd{&trqd$u3`zl`P5y3J)TpdmL7+IC;sCVVGJnh?BY0oI+vN^wJnkT_E!I5F#v zkamPe1xI~*5$G`%KHT$gd(+XNI8|(Ho_x-=oY3aQMU8997KzepC6$Vz!a1W8IntE= z82#%O!>W{Qb4TKMUizTA70eiDxbsdZDE_@X-7t z$T{F^g{C2a^$Y8DS7eu?HA?!Z@{uZ6gN@n+))~wnX!!eHdJ*Q!VU=|)DRN`4+)+>& zxsNTZMZVU5Q}<&P;Rr-&ddWzfD#LcxS^}_qEq0QcO0DuTcxWXV0Y|iIMUur~_l`+Y zKW`9tU(g*zgfE`m2J+(`Lu=J_)$D=o#?Rblz8HS_ZQCXBXZvA?uqFCsV-Z+cn}49^ z@HH#;Kx>C4Pd$gzf-T7{k~tli+G5YKnC5Fc8opc>i4Ti&VXv)%cNX^FOhXcdz;b2iQBG>ace#1t3J*jv9fVoSIXk$r}cjS8dDGOZT{+onk$2VZ?E_I3Sj&e zO^u7MRt`ykI|U=yryY&Qo*lUWYlk@}(bgJanLRegzHXByaJ}UtH9vzX(_Ati$_ZCl z4jM4fkA3+hev-Z8vQ5y*k3#q(H8@oKw4l+ct3`!{SBLN^f7uYw$$>PXzl+bTJi2>) z@)>vWPSQuTtMz4sE{~1Gc@>zBD$^wZ;rWYK-=f`DGJi3I4Uc^d_NZ8y5-FD9RsZMv0+{GBY8Fp{ArbbI@^S3yMxf7C*+GoFa?m&iGPM8#CBg3}P_Q~hk`aO<6j_xFc= zzW14#tY$2!B|zsb=+IwjyJuCzp4`>v+t^^o{&m-FzpV!3ZZ*UW)J@;Oa>(GX!h#|f zgerbjDqf0}$IDO%&DiX*=Bm&3SAKmd{$b)ubO$s8|L)LuSuC+u_8abXxF7q}_v3TVFCjXK^j*{?c~FmUcm zbzabgzm{@`&V0VFHNsk$YD)B+P@W5GTyejUzIc0xCKK0xJfgz3=!+EB=q{Tj$iyr> z8dIWfDzR7J{q?2Jd$AJH3tcRoR_v3+iRs*GR+Y|O2pjnwHnWoeuz|cJ*x$Iq=oFUW z;3+Tf@Xw<~km5#oW=MH<8R0X@7&>1@$hOm&TG?$3TRqqPSm=r)Wx@pun2qQ5Eer2< zzSc{wF%OXRgp2j22R&o`AZzR0kx;KI_Q#jvM&6cW4K-u7EKZaS9Y$D<{<)F=%x&?D z;`d*3SW}CCo_4I?_~jz&s`yf+k?qAN&CIf`a~Lc!ek#9mZ{pkJuaD_N-Vzqf-%tgA z%{L1C*Vv}CIg1aSnKg*T%#2PUHS1Gj(Dp|Ra`}jd1Ceq&w)U@62s%p4@cd4rc0K1_ z;;fu!rPwYVC%O!rw z%s9(|CFeJrn|A1RXujELNly!2U}t+vO_lFpQN}*OL%V#&bDcPkio+gJWFMVdN3(oY z#%HrxeykMG$)WVE!!&^g58b_uDiXiSiah++@o`+gZ@4RTE`xR`E4Wd{Z#wrpORjzH zP7-`kakz=c-`K%zisE>J zx}BI^0&8Ml?%cxhq8Fwqvb!_3o}4f!!fY$7Pf2>JOMv|X<~&&v4wOfsAY&>~E_z&m zU8ZsVjdp1%wutuVfGlj7w}W_Gy8Y~**3|{oyfzQ|VfTJ)HqjusaxUQP zk$?(M0ruA1nRH{Pu+#%rHIgrfl_&y9&Rrfy5Z5N)Cof+Ce%pN|x8;%bU-GV45XKKZ zj%$i}1#59@1;uIDjw$6o_zZGp=h1KJ!mpHO}e?(&u>oLbw!yLq7|0a42 zevSM|oompn-lQggzuv%A9WJU|wxc`YfrsBJWi-7~rK1Oy%SSG*g8NXN9 z?5T!l2S-IfE3!YuFnv#HbPwIYao_#Uj}=Z^3y$$|j0HJj73?iB8FAC_o5|pLadn`I zSzp%>+P7Wc;D#3v0hVmNWlzUcheoJ&&{uv7&{dJINpCRV{7o56XM_a!a6z-?TzTwq z#><^#ckYV}MnBUc`}q3((;hd&D;F^HTcR7~qL&n!$lr~u*c+S#T;~-J8zfTLzf=q!OKi1s`$qH# zuByNA^F8GX7R*X{SPq?5XL!E13Q)fB4?F2AlB{8tSNzbDeeOec+CdL8yJmM*z(Yr) z>~2c^yYWJMbJS&OUC7T0G4ubhZK~?C#m{3OUzR4FcwIFgW|JHNnJg~e_^)+qlUv06 zfqGau?G!~U-fJB3`wxGEEg8m_gR3@OGOf!=+LEAFia?>eD}kbQI9G z3Fa(L^)$v&$zQ&4E{v~K_9g8TtFw5mQ4@cN%z%3E#YU(m_lTvXt7_Y@Mc=vGfG4cA z_PP%*XvO9z-NVr2bL9+GsRKv6{0{YHY{~<>_q(-mUxtGO1wELI?!#?>NQEv@Mlzn^ zyJQjG-+B6{=&eUb{#8w}x~<2XXEc)qYv6x8AZ&uHWZqiW-u4Eo+}qzfE=#yGs;g^+eea%+RevGK^;-xLdVQdO(_ zL;fYJJQ$DcrTY&xKO6vN0i)aJTN@Xq`oj|ZxA;Q8G`RAR)PP=`PISq|#nkXkECjgSu>o zR?yX!*LK_q5XBYB1T^&**xi*{`ovF_dZeQ6pLf#VKTPwk;rcUN$L!<%vd0G@S-{OA zpMm07ANX2fjv&!Oaa3w`>v<>nVAlMEm7|GO6Pa(`^~hFI^yde9(!t}oYU7Kt%Y+u zQDwf#%OY!H3BCI=I&8>~$8BZ)|?YxpYC66!9jaqqB?0C$bq!3u@ zXM5t)UE4l8^;dOM9^}2R15)eeCe7ydjv4bUA$wY={2#pz|?&a{U zY9~2L7BZ}E_x-m=YFp0FyXEcte>8n}JeBYN|9ysIkL;+ZY#C)M&M6HeBYQ?PL}sL{ zb5Keu!l8_iaU3&o%s5IKB70<4_K0`(InFu1>-72l{^{}Pk8`{4>%Ok*eqGP?Qi<>i zVc);US-|*F+{7(;o~5N=@tcCdp!J6!aC+r5((C17xfr9M-%O`?8yxWU%xRCjy|NwL zSCJ+zy7Rvs?i8D@>N8!}WNEIgzDZM~4kf1=iwG+LZbbM-&js$Ay@}ph+JFc6`t^H@ z?^UB~1As=e^)aaNGKH+)lTRFYQg93hKZ8H2 z&iNyZo8vxb;eSZCuPDAifm@VAl(&F!bHRsKJAB8#hTz?lfe1Cph>+EgV?5+N%nECG z9~p|C4qY^Vi51&$Fjd4j1~D4I20S&I9E3Jo%WiUXnqD%4&N>9+RMl^)2N@-2yl7vn z*M33CR$l2CG_6g7)%)^V9BDDtkhpQX1*9FcuufLw0qAn@T^zbz4JAmUZI0L`SfClk153B^R9xcq z>(_M!tS|8zGuExzx1j7I2*-Qa2?{*@602`rPw;|1=1~W9qp_Q`JUJ2x*8dX9*&|yt zupQJBYp(MEX8UIcbllkzdKvHJ_a%mf;dq%Fo}05N&mEVNeb?x_KnA~g`P4{a0d=6; ze%CI4WCZduf~>JG**!L0dlp}?N>cppPvngRY59)1&A1*8f|pydNyDw*j#di|PGaZ2X9=fAV@aQ0Yd^+&J4Dz5w`W;0_r8bi z&kT5vTv5Z^<=Ur^0LS8`8n$qSQtoN0=PbW<4z;3~CywaqkI(j62cPYYr%2gi)k1eR#&rRnvjbP-C-g^FI$_G;7ENyr9 zJbi7nTRV6eBeZ#D;lraI7a(-ZK#u(dpxy|myI!u82hRO!i%0FOfpzNeJ%uZ525+aU zgh|=aN@$*AavAScSQx<@zk&O{F_n5#%ADB8(kq2~xic(kX6n4?KXP=Zgww)lw{;-#ZT~|u%X2%g1IWJUk&!V;ldc-x< z)I3h!=?*w?jGLafEn%_?&MeC-wUaeKFx(ZGmu!wdmCV-iMsTNuU*95wY}e$<@1XDfm5N|=R+EhZ9w(VA0O`^yu4NzYE#SZb&;aEoeZ1ePU4;XgYfdR?WvD=$Ic3(!2-SAulAd-Z${W)ube?b7q{ z=(Gj4D~CcicAUb_ziQBJy`V0bYuSn)BXaR=K}Fx994J|dVm+kF^>9jJABb^)PQwX_?6~BCGEcdW;Ik>xp}FS>NB`G85T6WwoyGA-=^Wmd7@PT!D&O55hvpNofoj1O{6=d!pB=h(Sr_#y;$Y4Y z5(E66>0hJ~vDS0xLN^`xnfS(?nRX$BWj+V)#;bdu0?!_id{#NAJ89yQ6?D2ue7iQ?X#AtF5i?5{(i{ty`5{sHB50J_te?1 zoZNnv@T0Y+tUA5k=bWTp0b23A41?$_Ymi; zw|V*trC>pniUkIy*tp2-@IJm*W>gcly{ydZt~s|FF4O_)N8|a3Ir#BXL)W=WH7_q% zLz42rH_iw5RCsS;05yFySUOApIoq1K+L4X$VxN-yAr2wPFz$~~B(NuV^N%xbqHGQZ zxtgpc9KWk~bvu=bjb3jAc)i=jU|HDW0gSzt8RK~j|D07Kn`k!Yo`&Sxk9~o&ZzZKU z=o!{57B|*ZkPK0)Mse@3pH<*X0+}hkj|1N}fyih?Yy zy@a6mXpT3Oho&VLn8o1=S>D&P&>3sI z3^%5X^PqnCFcU=}DwHwr7Yk5OB!jK(n<~32J5aRl%Tz7|Jnox@>2&1OhJs>2tj_+&B}L?Syar(Bg^?A@iu8``>s2n+c>OmEisyU}VfO+mX1Y0cL^!vE+{nAL z89$Ii#%}KbVBsL8BHVj>?~}OSRkdXc7#$qXM(%BJZf%MUcn=WbMO4HR6{M{sLgx>E zyEOiuJh+#2>RzvHnY)x&^eFH@U-#kC=ZCBY(l(AB6`c7y^DRrNP|Y})dT)C?MRduSxad@Uh;r2_mNO_T&XltniewW!KWf+$ssUAsUo7~H(rqq62| zC?P{}>-i1jY_)N5ap$lqV8UKn25~_!ddh*Flfkwnko;Xxw68Q%|5Q2i?d8F)mmpit zi^n7&TTBs0jP%%ZG+FSu%pj@t8=I%~u7@7We6Ti?<~joQGp;XjaKFuW)Yoptrw85y z0;FjbKq4x44#^`EOxgAhHrYlrp9fy=U=BJS`Q>L811rKBJ7Luvs{O%^c(VJ?rERrgqQuyeXs& zCV_fBn4I?gzbEgvOfsqU2%ztt{YG6S`!OZxF!^t}9Jnb7ZneP!xPV&dq9~ukllV9K zS2wQonY?d~^Z}Sv7WUAI9TDkM8{c)Ldp%22W7{(WeqgQ`2A4)$kC`jSj%hK>3d|?he`v{Uq;fTB8T0*Rt zB1%FF_GSWi_G6t~Nr!6`c3g>#RwtR0Px~pCy~+Vi%2wCQzy*t_(_kA2XEa7|fD|6>Gc@WJ2)Km%Qdo+Jlv3PvK)l8$YJZ zo{5BL3`1d&rQH@|SzHGTraf>a%Jv9GU`)ur@D-asf{HRi&Qo*WM;c2OMy@=%w75zo zGr4X=U;vF0954TA@x~w7=Wp1>_kko3VUd-4x@||kon(58`ks^%!!9_ZP4X&rM0Nt3inwqCOA@ed$n52kq#I9399<{CyEqzjRdG z`QzNKC>v@gb(9NN0ObGIk?+7NNIfz1n*pluAI%X|wmsIAP&_N2ogO7^I?M*9 za5p2N)c<%kZ*|b(=ZTw=$5`SV_oW*gkqi!{>+h|#UNzhM0f+i)`7LJX3RDnCA*|7x zdkUnzaD`lQB{$crxQ^c{?hrA2unNibi?v(=pRcS}DtdmQbW7df8z{)4%eVyqqd{hL zjhzZ*B;!lB;lsZMvP_<-z8x$|D=FA4HR2^J&ayx>=Q*3`7*ifAYnExKyvEzM&37?} zhq-FmW&kdvsiWiIy;ec}0H9C4y`x%7jg`9==ZTQy&V@nSGRJ`(sTL6T1Cy7lhr1o- zhs9e>RUCdCTIy8$-E!un{Am;6K%ru8HwK-SwJR0eOUwDg8H{DS>N$^#MS>)j1tT2b zgq{rGlTV(*YCHDesHhO(hAnv6X{1SeH zr5o&u&j0(k4kbljpTwpCFhVp@ksH1Tj)<`ohTOc}T7?uQOK`7>h5bRg=&TlR26VR_ z_hFzuM3V(@M{%&@JLUTF|6Ltq>DA%p=ocgNBd`xSpeeJ~YS;mt!244YYkozdS;7!N z|HFFRt;C_GVY#T)iDm0~y@rt0iCZJV)z^c7jE-ynK|Uuau@NYccsHm#2X*_2Z%e5; z=T@wd3_0f)!pz0D=-N?qQPA`~7WgdA!cZ1k{N^TBEsPJGDnWf4xKPhwR)$#P<02$-Ixi9W$CLhGLosN6e>46RUs-dz!Iy|9pn% zhSr34OonQXfADsGLG)p3OgINf)my&kMH-&c(JcdYqQ2}mfT9|2^0XXHIOVQr zJEreseeN2F*ii;i6tBKtQ*Uok*}0+s0Vn*g0oq|a$L2CT*n@4#XEDugat{dUF5NP! zYo4S~iGZG3Im!h?{wdVNP53~%7C*q3V`bZB<<98$OdW%@1Q{`HTL~Fh%Sm=QLA@fm z)6Wc`V1@ES*v)TR1!pf~%{QkN_F<^|44_NT!1w4*^C!o3!j!8lgTkSnI0<+>3m6J| zeb>|nmZK$dd`CIr`@+>h?yuhhTC!C_d8{`RN5i352u=gCErV@$Bt!Q<*_{im+^5+5 zyE(wjgc9F?WL}OfYgAYIyV0>6jGl_r(Wr3RHiJHSGOn|sR^IgDMUcj^jw*(WssmOW z7}&3h{n-YB3X$@K2@gu_secY<8K+++(D9yBaJ?C0bO$0?_x#~%$7t+8g6NPI3&YCF3N^(e5VKc!S^&BQ(xuc%*p6)r zMK`_zGxX}py1B-h-P`HSA=g?^(TzkQSd*Gib-Pf*4|C~#7*0r)tZ)CdQ?$aBTTE() zmEtw|?%wSmkuRW8?uLkZ2Mi2K>_gwz3ti8vqc4^y_xiA753?}XD|{u>agj7oIcH6#y*@CDc=GrM-mjhA=@CF$;nv{M7-h=NB6lQ`s8WmX>!?&@hDix1ihRU^ul| zUJqhP z=HN^o3uMaYJ)G_NiW!3m6CHV*0i~7z-L=Hibzos>PnR0dcXbmztmRNQF_c2x+tk04 zOfx{uQ?|Vm-P+e-{i*wI>q8dN_O0}@LKjMR5ubW5FG?hVkY5WHj8!od^jO0;PSrvI z+(sb7`rEfK*1dt?6WOgfEfc%Y;ho5G73VrP42L@p)r!dI$-8ip5dnEPoQQQ*hIMP^ zL;;t{b<;Q3z6m4T4V&g1WNEraW~46H(>p~7dN_T53LA6T@WYw~wB>6~L%$#EEO(hw z{MujZ*9vM zbn+W_TuM74y`+9uWq^IJDaE=!TWxa}`kksnHGB0I#aH?9z!rgeENic7mUqqft0@!@ zz(ntn#BxncKWa9K9z zD-Y(QVbl8O5c%E0fM;Kv9=!Icj=tc3Ei^P;1BG|IaGSVK(hN@(ZixJY{Xp2WfxDyD1P_SF(3^eFqb8a=t6CcZ^z1xIV{Nx0iB z5Ph-{RUm~~2tpLg{{YqQ!SKm&<&cKbj0tc^mv#D6fkUV1GXnB6r@Fm+xud1_KjyEm zA&U-SOm}YdqAg0!dF&SOLl>u^hUzriC(kPs2L%>p)D?1N|V*ee@E=Zy zKtwCdY?3X}m*i=j%_y?W=FQ2;E+RE?SlW`OG{bA|(%NK2*&L|LW)H!OhQ8aDXjH9*Kekk2@G$mJa@!>UP$g;o|FZhj?F#(2^h1ZMIFjybgHRUSWz#-wjOyu zbpUakBisXU3AE#sUD_5e_XaR}C!; zxi?jax~?vM?0w}?o+o;4`TjennR@@>Nif|Z(qdQ@mDJxN;dUIzY47ff=qew>z7TiY$*q?HOwaY>@7I~;3Q_Cju1IOUNdQ#mqNz-} zIE+agV)PvGjgdY-Rjezqjg-I%=x)I7-x;$L!11R42ILgqIn>(AS{hBm(&w->*xfYj zf|FzEz?V`*__Gm8=tnEFpmk|aG&VXwh=pj%<<;~J3B|Z#+sw(mS*@ck zRNGSfD`>`U7dOUd&{hXmKjAu`2E8b1&Qym^%N=;;@?|7FCi2VTdF|_d&C!QrgRk)E$U^o@X@{iYTYtuvf=O4$4Iwwa(@jm z(h#Mb6PhwVC=tnF3;c-(Hl;4IL-=EFe{s|Yj4BQLKUb;MTx*F!g!4W>Xk$#wGS_2N zdUg}bqneYmfgPcVDM$okQzAl#g9z2mMdb)9I7X|b(oAI_S$us1E_&Xt9&JEx?;aIS z3>3P+?tT2{Lwyx3pAkWy#s^EFz`~Lr@V^z}Z#no!cWgBc8~|``?iMdhC14?B6+km-!1Ux+|v8gWm6L&<`TBRl%C-ba_an)j(tF z(EIt6Ibfs`j`5YVHY+lPRTkx|P7~q1`9n?on9%$?y~Iu-P+Mm1Xnm=_J$DO$97d?I zQY#-B7m)!?5w%O@-Z_q}ONKZQG#FFkC3NmH#?%%lxWE084UvM1hh4>?@1i(25XACr z9^oI9yze$9`{b1zmD>I4^}1~ie84SkB(I(vT?WkFC+mk~6lF8cRglwMBkQZq$)+|W zuLBE2!m=!SHx5?RHlhGior`>{3RArlp>q?8-d)dT+>#d@DbjKsfGW*Agu-dFTI8Ro zS~uTEHXVS)M5b`iQ&(tJPzI$bok0%W>x ztm}d!j+>L^LX?utY=x{Z{c+`OdT4X=Kk6i-dEEnFmq2YEyj}3Ig_$D*YTm2=De=oY z^HMLZG1?U?EQpqp(my>?_m)VT-%qXvO)MSXTfTv=ViHymvbGBR$vWnrTqZUjP=!v1 z1JdmdNYykk;Yc&ymEuEPy7!`(U<5bV7BaZ_7>@r(H*3wpv(SR1)9eE;7i zVwv*68QPTa9d8&&%oar1D;4GkVp+I2%!KwnmzQ^k*{<4UnWYJ*6pjh!NC{^s{KbE` zm}0H4dMI+|%H_`(Jsul&oZi3dDljImL(x0iSVjmH42>_b*XVUItnT=GRm+2a?TqWO z?c#z5^gbI`r)c&aovDFhk+ZUQUc0>C0#$snQ8k+Bm;{O~1mD<2JT$z(oB}y8oB&Fa zZIeN0rfNPAzw8MAn|Ow5h%QK>Hull`N90p2z1-3l|6S|wgCvHalVLxj0>r~bX8=l3 zeB4}E2C9bpH^JC`xiU6gm00CdZujvg)>uE>Bcf>l269Nl&x-D-Si2vQ9H@Z?9SW5m za1c{QbE?@f;xGuQVJ8g@{+kh=x<3>UQ!ZZO5Ncne^CeGqLz7|yx}g6dZGXw7Kt{@( z+>_#uXK|XxfE^(IaFo3*M|Q4pJ|Xh}Wx*Wl1$f^I7~5hX9;zGgt5SvWVRdvYiUhSF zt~0PnAc($bf@!VS-x$`aqRsbXLqkUbQ@zeTP%-Y1J8jh)X5Tn!b%1}q7$#aSY_1^9 z(2+X#0}1u#8AD@_seS`v;L|b+A=kn2%;}k6_>#5!tm*wEzI2mB$BKJa^D=h;a$$sG z@OwWb@%qljUYAc1+l#+!&~7j zj!J4ZRYv;taThQ!7$Psj>9L~HQ-{YD7^7>o(ky(Sj45tWZR2S~2|#QRqHM`_FKy)MtlsWTeBt$iw&=pQJ&r4{?|= z9eb}SJyy9NA4ga9n$twAjL(_#NN<5mBHy|CLNUo>c3+b}JVtNs%}qD1HmgA-&qj&T z`R#aU?^``QCvDKIP2a^IhSVa_H43Ekg!RZNH_|TkB%35Om7sExWVwcp2sKVJT6xhO zfx|LjWA-V6pv4XFe|sY}&GF9j9bZ5EDNId;uz^ZOUDABF;}oOcjBe0v-m?G1Yd<`! z34X8UP@LnM{kfiJxS#g?5CjsWpnl>A=9zE?BUAC>!tx)eJ;fIAC`ujJR zuTG>YIq7EC>Qtazit>exBv}Cfq2c)ODff~(#JFWJT$u|sAg z-wNlxnB;-Ajc_vfsGJ(|uz0JmxdIhrec%{?Y8PlF-q)9F-h%9(^7VVg6$OC0D`Ei= zBDxy?wVKKb1GB=j3H~WFQo8Q+chXt890eQWs26aS5yic3?=&yG_E>Ngv5TDXoS)i2 z>@sIz)R*sw3xa8(1nR(GufGqS6qH~MFMTXcaMY6ob-|vpvcC~YcuV*c$T-q?PreXJ z1I-h@r(;&*5v)8?0?P}*V;}Dwmfy>A`HEdehPqUv0L+Q;4`FIh0KCYS&yIl7k}XXz z0$Z+)R*aP5L+bT_F6?XDdh_^I2!%lNwp?mAQEBS0xBWC$^fs~lsObyhucX4tl0)eh zerCt`^2O`8l!cupuEZWzy8Urt9b4DhO}22^gn?a^8(r97-^bXX7~~mS)1^`l~5swBV77hyhdW zukOJv_PThVkpsLoh)msJ1>%XdUd_@vQW53Uqg}d><5w(tOcAyT`pzJM#%|p5CVO{| zB1@-Ew&%@jBVgg5)S4zDb2xVJ)0fEDuLSzDXe$c6RZ;%RP#0d&jzVvUJ|hh;Lamt$ znYl3Jcg+#o#u%j$O12P6Qts^tt)QPj@poJ*{4nh$;MC4*vM`syb^P>AcvIv=B-O{@ zE|CP^;rWtg>^n~e$53MA!HC-VQ!K+QHFz+i-a&|H`Hh{ZmV0cEwLg(^7p8d)g*FFaG z3IKtv7)g*cH0KC+pX^_ha?yH7SLy0+A*KduZ%Oy;R5tDPO2_rM9>jFhHFVN6B{Rj=4Jzzb)WWok+0m) z*@?Uiblf+Ot8Susnncny`QBs;+Mx>TqWh)Itn6hM9P`sv#jn|ecklRy1u8;Fc(oc` zo5G`zi}`wY9K0VB=;h%W_{eEdEXs`WI}}t9+_eG(-AiUfF-d%IK2geSv*}WaQ=el# zB=t}j%HaVCRD_HIL5j1BgLi52grGSMpOYJo?_&%X!Rz`!yxz;P_LkuRC^VrkQF~zS zMfpq9{ZDii{@`&G&`d$nNOxf1t1SniBe>!na{`uaS>bv%{@R!^`dYRgG#1pHcvCBu zQ||qe>i5a4x4uk{av*OStI7pOMpFyupyvx8-~N^V>WM>@ex6|D=lgd0&1e~QBYrxW zbi!$aam$0+B36$T5y5&T9xYx9YHUGew|?2C1XObH6|pKo7|hiwn!i?cv}=>VwdT1R?yXB!c35^yc2F_NNN5Hi#?T)|SyuTjt* zKj!ZVN7gzrfUN~rS~p-<`LMgbuQb3c)AH0NP{W(tAW0DDw0-`C=bUs!QFZV7ZBdkA z!NlIpJ{uG6Mt-3X3ZN{wFcw(`SO8q$c2qfA%W_Sw{30#s9Na>wldQ1YnP%qZw?j!t z;53%CHTl$zkBpbC0C5k83+%a7y}IZ?M4j5Pnd~JG)q_j!SMT&^Q#O&%~EaUFRu2c`1Vk=mZVL#mXs-M`mrzs zwOQOOi5?L&t9Ax$jzIF!UO;7b+IT~7={fEv#o#`{ z+y}!|lI`nerPI^fS#rmUz^}ci^RpkTp01e?RLSlZF7I-(%?6Rb)@0L-{q>O@MT5z~ zL5k_QUejY*x7LFK3+B#r8F3Kk&4euKt4|ZWGmM*7Z$zp0>ja55h>!hF$625PkY$@= z&k=4l5^*rb=K>?rN zOXCsa`L9C8s~fDaPZFk1L`dD-^LE$3iEn7>>2+qNqz7~%kwb$FuzZkbHBTP&#kd*E z{ybclVP~VCLJuhun3-8<^#2(R1WGmtxq$W@G*kczjGW^^N1gr>wY4g)_`l6$tx>;k z6RLqL&oItBL4`02;1zgg_WE88GpE(x+w9uzK6C#x$gQ;Fw&)eR1O~uU3?y2L8EHjG zXQtj>lsFW1?T}|*Q9`+vG}jl!nN=9n%6#C;v9I+g%z-TvUBw| zi21HC0E)j>*l)ZBic4|a8uJgH;%4i(=^{y>Zytd`$CPWWf%+LvGX9IHT2!)pi|AwJ{c;&{f|i-L(HS^7SOMVLit}@&}jOOhY9OWb^~WF8%jD7{l+?n|J@-vbiN zlh_2n%4~km+gg?eW-{c$fOf z3#S_N_M-pGW&q#y@|nqliye;R$dLyx0yYlV?O64c$n}wUaR&y|c>D<-GPatMe$beGW z0$Aq=z)Dx9-p6Du-CRZiQj3-KS?mcbd5>w)@fe?PkuuA||u*l$XXDcyYeFAe|uw zeIOlre;mn&nGoD4xuLPeU3!uvHvOr(R!+W)7(M3Hc;3m=n&rHNRIHgXD-+I}%y!ghd8HRtgt9-+f%xiAU|Mn9 z*P0@Z1Yt_iRV!cEIB-PiD0@VDwfUNydMJ?t+2c1e+c5(i&#KCb`a!J zcHt{NOVRU1%&><4lT<};c)LDwn9gsfe&~?7%dv z1^vzL1?@?Ob}Uza3))d{YIrSzX=XGtLPSCc_*@TACOy=^F)uUTiQoII-)5=${M*+7 zcgs@L{K30;<2U?C&8*A$yd}i0fX5}Yvy+@<=R0X5FF)Wg5w3WoC*`^jj|t9E6#*5s zf@pB>I;96m*rCR>PE~f0$At;QXZE3 z`Q~SJ)$65kVbVFm%<2MT8+I9>Jqk{yKl^3xPk7<1`Nd7e_ze{?qM;R^R#J(9VVJzA!Gxm@Z^EzfruAd=_-w@%9l7 zzUF4O(O_xAzSnbmB;S;?I5Cv6Ku8YFu3M3lmN5-mAf=uOHn4JngT3Tu{R@We)j*&S zG8ss-LA5k8S%%OA5MdRo`QS8GH_OL8t_Hpe!pN%T1qwh8P)Ol_gfoP@7q<^(i+8{D z`tY5_tDu)ij{ez;U_@>-ndW*KbzudryUOi=3jx?Qy1`i*vRz_rTF@*8r`z=5Q7{R} zC!4&yur-Q4O7-`3NvC5PuJ}>PDFO03;d5CbVnf^6j|(%IpKXR%0zQbmGP5#*a}kJobI0rN6ZutcRnD7tUE)yMXAI5XuBn~|#y5;o zd7Xr6pg_^-1$M~pFBV7?lK{eJpmShwL?YSe(HL%49AHO0nH0%w4dN ze}`Sl^Ohmd^DZgk_5Ivyk<5}St80?~4V(`QuNM|zH5zi2??C3C!uHV7S;0Fj+$mKF z7rI=&5Bzf?_t*dQ%JiT7iqBR{dws~4l)W{6dlB7WUH(!&97XN^XljH(GG})F@*N|f z3ySClhlxH`5sQH684yeQWhGyikm0KLxsvdh@FD9=-=RyqwIc~qDd#GTTXz`8D3-U+M<6jnIQ}Izh^Hpt5)$l8XCwYP}K+g!jLiiS5JYXx{$AY7L$X*tpCJV$(4fzhZao_lw$EnM@ML zh(k<}tc3QWf-iNV%d6y(gG+AL3BdspA zU5{)^tnq&@hd=qV9@)3Cp&pki2H?(@Wbt1^iOJVUqN~ECwVpp3*ThM&dfkr9URdUK?3^R(6 z2!}p2GS_P(le5UIXs!lS0)tY)@Y1nR`N<@fi%vgU5}FlMG&Sa7`mw)C!-LZ4BilB#POb*g2>L&rQSF!$5313m=~I|*O$M&2HNW8T~-WsxfuNC6Po@TEry%= z4oC~`f8>H}i3*bmV1_J8UTZMAw>y)TXTE!bBZ6Y7S2nmbi?_Qhe?+pKcqFI4#WiKE zcl$62pX{u-p0f?)%f|&m%Y|eMh90*wu)rfEikteU2;WYAz>a&l_>Bj__i^T1+R9P| zxyE{7R_cK~BV})HW;|qaCDWELES#f|#4Nu933G!sEcqL!6ua%d))y-qmkt_{_zfz2 zSm@SHMD*b&Y~*7VYBiyG%zKGw&kB_@Ky!cQN_ZC_kZNX|r@Fu%lBpKH#>{ zi$B=}03=zypU8SMIF($3TOie&ZKI+$m<@1$Z`s*q)0{>;vU7T}fiJZdc z@7=NyVZRubi;J9T$7ju5NchJ#b7*EHy)Yq-Wgo`QJa)M!5~d7+4r3#?faL%4|1Mgr zBFl|Y#nRWaH!oKWt6N_bDD%h)TqxWE1^bT*pVnWNhurKOouKn+f%Rks<^HP9rE2Hm z=Dvn;`>(w(*kv>Uj}JU~(Di@R59sW)j*@)w|6NA$@>@iNG^3NBiV8W-6j=KcQ>kjx z6%Lj~pGy9BFOEJ%{LqbmKen}T?e(%iapZ}20uQAF7sb!J9oxZghqBkOTmIPRE_jQ` z{lF__wp+s=tQjxfE%&jT$A-SlUV5#5UD`OyTN*@?o^~R6;bCkQ9Q4&kyH1=~;G?pD z<3gnPz5x6B3g9PgYlmgIHY(3-Q2I=LJ93lbbCq(!jiZY<<=bjfbYoQV74O&>y8tH8 zeC;1cKBa8;A7k+!xep=?9Q>eYi(T>vv?$m(>8i!Auxqz~25G-)U8g+cdrgTYmZu$@ zv$0+?0EksKrXMyn97a>-^X`Dz2^hhDi>rzJ=5?1Ja|0?`^*JqVX3?-PkWRMOY7%qb z1cxR5y`!AIl9~L1@~`+k?t)!^TdnOf<8tO+YmfKo?2LbhvYhQW1=5+CTgPbmG)2}=4dJDjwMh#TA(i`;J$8&{fc_7!>p%@^-s8h!o( zb@uju08YCe5c*`)S?EHCx8zhjKLie4k8BUWzNgfj?3qbNki?4MQ*U8x3eyj>eeVYU z7GL^kQb6B$>n^Wjd%q5sRD-LQx9{1GyU%U>OW)3e)LU=B;@av2+S%u5h8+B31%uA| zgB&C$wuS|pLjA#~6=r)LudJcN^)d;K+up2Xc1hoI|F^&Q#tuZo27il?p;7wWRO62w(@Ne%CjTn1x2vdii$GDa*8M7Ayoyq*xaFLu^R%o_RGPGqQ5s3x42moPYj|c zLydz83Y=5Elv9Iqfu(1YiUUvwc?`k#bAUjL1Z|8YlY|s)xm-BrZgZ^4xu+d?eSjJ3 zA4`ggl*|3#h<3Y2aXwTaJ1Nq1AsG9%pK85*s$m;uk&&waDU0K#V!z&}PPCpp#;s$u zW=>QHUmMp2xhBFUrmNx52s}1VAB(3DhAnf4%2%c2%BhH4k#71ee}h7c^5e=wjN$p8xb-7| zhq;5$tLSba5n%@Oqv%CvTw5$cA5h?^99sh$X1z9rGJ=Zk+Y3~9AbA>|TNr^!zt$OY z!YC@wjGR)W>pz7_!YcqtvA^ul90O>RWq)Y?w0+fftP&Ob6Sev2Zj-X+fkbWyvx2IC zJV}o2!rds4f;Dyk(ppwQ*%=oJsI=4qN9G*!L}rbcM`^be3pG{X*}%=7veV3T&n90+ zhaL=2I{R!R{pri<#e+OWVOjrF3;nZ!T7~3qmdV<)fc2vlL(cnel_sB|&@RD6>Iz1L zyEk?rpe&4OwG8KRuE&zkCat1Xh8*wzO4_X~kMY%>0-GNa{$P68w0UTW^&sX;x;IQ zbKy^r`o^j;GNPgeE-X;;;p`|p=xv6(6D~y; z^qG8SP2D?J@vx6z&YKA}0l;?mY@5*H>#kjUrAQ9C)~#VTW)2ke{%H!ttnj{`Tz%KK z@H{@iW0~SCAAHWLt_I55C>#-?~`3e10~K>y*2Z{6an{ ziUWz~Gr6=Hj_VhIffpCYNzy1VoVaJ)Xr%VO)-uv}2?rO|INmP?ckU;5UT*@5c;~3xDOptS2qBf;6ppVT0 zOdVnMwim2+HTOk}Y7m;QMB3}lofiNQYPX+CmN~lNi#^L2v8FX@EHo!`WpkZ}JiW zy)LNMS7v3p<#<%6+4W&F!kol1fA-=`rF`m*#gdYA6>Paf?RINzP?+2q^6{!DL1?8|dbNh=>ti|nRi;I}%3L3J!KRpzyeIMM$IC-X z*^%e()Qh*Oc-3=2pfjCll<^Pizi5zC5bQB5!S`$yQTO++;4XL=e(dJX()m*ZEv}Of z1BNKv--f!49(u9lw;z=GSeI=f%_|Paye*#^R?p=xIrUFfP)0 z>gC^&mH-q>LyKzySS-Q4*;d);#F7_FF{I;h+^`l4eV0F4fCjlMd^?M~m7W-Hc{$Hl zAJcABdj-X^nBr;_lY+q7{S;i++0OF+o5hDX^9a0Cji?rj)TiT4-)OPmV^m(zoOvAj z7Wi^eG?^}Rzk4+y^VHU`$G??z@2C55077Uw%yBT#EIQPWr z%LC)^6?jZ<@R9zUAk=^S$KN;X znNesM5z$aY6s}N4_Dl+;A|n|o>kdWIFjFEYWUn%!xTBOv_Q>c|wm5M*j=RtAec#{j z_qX$z@6Y@FdcR(;=i~XopX8Y!36>L4Gb;8=R8CjuJ-T9jtm&JZF0ROWW~H2uiL|Ev zcwR45cxiR&WIeTqxs7m)U;RD43k#w_q^`QH%BiJtYsWDOwLd~yD?5kQFLX@B`#!SE zOc*x4c|Xvs*Q{aBqtD#RoIf|JlIM^5PYnxS`a0FcOk4Z_Mh{zl)%8HTegmT~W$_S^ zPt92DH*2VHZ;0G{PwO$w+&(E`Q)M&FxtXaOt{o?f4|<6P3iqmIadfxJn1dLe^qmu$ zRw%%4EPa?!p>^}m(|%*kRt9nRE!^I)tgs7^>SjG1LoJjelAqoB4;xuvwij zSdaQv#nOh#=hu6LpK5BhUYAlrbwPvXqJy~uUAGv1Y^~ANF_%o|xSc4BNza69X9t&& zkDptn*xK~aG*oSLQP^9VW5J7mO6I(3N9+}AW1d0h)Ia{3S6l{&;^43sE_zm9;Ztn< z_SYZM^;|s*zX8YlKPYC;g}RRIIMlC;Xm_It)=DTDC7#?8;Gt;3}#b!aH@O9x)I2~{AZqB_a7%EgdQ;Q4wZmCTCItk z$aAjkn%BD9M|?7jD{RXBkw$y z+yqyI($jOnR;eqi6xG12+>Jc#I(cZZoPm<$&0>ehY0y9Ya9(`qgK>5X<~jWHkVOej z?undQY9>_g)j>LwXUKN`W%1WiR)g&j0w;Jx(bE~q&kNs-4TUM?m@m2tg4d$E*9K$L zVwh&;=IICdF$TtAbiI5r^`f08CQJC=Z@*9!2g#jV+mOWFI}HjrWO98*YJy@g1JAI2 z^f-8|#Kfp{D^UQEAS#Cr%=vyU9bm4%5^^D88!<$&D`2K`36{Wz*flrEJbvO9nCF01 z1;Sb(kXTb9_@T`7UnIN-wVv$F0v^Iy%wa31rGxC|Y6;GJa_lK0?EW(_Be1!8WHR!O z$$Pde_vYVUE#@SjxxAS|d9=U8VV*m=p^xDp9-RBNm=AYZ;r$N(H@`k;H(0*NiDc`Q zoD>*Gsb=iJATSONZe%yExkJ*V+w`=SJX+nrsx#mL9D}mSUm@V&VwDUGu^H*b7nfn$ z#(jWrr{(Rf`H!SBp}m)XN^;&Coji__$tSz81uy3!k>&xuG>Kbq$Gy<9&7~@3Hne2= z*eN*zFHnERU|p(~i76}j6AN8;>4gtHp!7Z(B$o6<U zg`^Ou_NlOhaH0PshH;H(hMQ?ez!_{`rB5WhjPLBlqq1(nlD*wFIiumZYD@3^}hK@xZrY1<k?sOpAKui%>pA)9e$!`Id2q#{FFL2JX6GbV4y7bsRgm$Y zgi!p?JU)*iNh*W%oofekAEFpk^&?xz{6Ia?1hLU(QjZ)tVkiR4)CczILK19)V-ra? z(7MS4H_;Z%L4J!GhU|b;vvKMky4PKov2cc&85Y7}_LwyEB)}EaW8l;HZWMe91_4mw z@`($1$qR}=LXIsofhN628#Istcs944n{{AEOj$WR@Iz1oVTxox@h?0q&qLTFnU9cG zklI6I?{BZTuU>8@q}Q=U;tXCZYkpQH#Q6?E$$%%$T+ACL$$$SB?Qt=}8P`>o@EZ%Y zUSkSSo5WyKnx_+gEg9&<@|MgyebGFiurKAU#H)~#<#t&-atam_*n26SdgEX(cH_+FwCy?$Ejd`G>)#I7%TqvKg!Ga_6E_(Y(`YM5q45fm zCakOPH7Sos-}M z4W@q#*3W#RaatJvpT~sj{eGSth$KZg%fXjF z+`NPJs38tCJPK1}D|2lpBN!BwQz8Ttz&=jn;3nKWpV+%r{)S%@%Pfx{lVhOc+VlvQAps+fnHf5ZW%?pf9Q)S?a#7cj{EdvQjDIx#eI*$H^!1ej7 zD0J1hj#t9X+s^TSGUa0#+5vAYPB5$!7#aJDF4=UY7yurS`)V(pAEKUUl;@e5M_kXm znTo5$;(~gPI)pz{b3GFf5KwdD_o?D|-^1__JMByo=nMpKU_q2>ao&M9q=V z{!Hu;T*s&V_$0cWH3$ARU*#ZF2nt`i(S%I4uYOJGyEBlnC+2aKGRQ5<9XF3eV12|F z);sqs(R2F@a9>gnwpxf%#plkTXBzE|!Pqo<_tPVf;T!e9kXxOXTn1aLn(&zz&P!IO zkkfjML{y=&Y{5KSd{UQvk#y~S!rY~v`A9HeVpO$ffdc1|^^UM^kUx$(W4q1ZT!T=4j2-`MH%XU9eo61)d`^gsJyX{z=czgNq$!TGQ&o9V*aYAVRe z{IBo<%Yc~lUugV!hPF5_&E@uF>Y`znbgc`*82rbWFITiX`|{&m5fdl};V(rMJTzw+ zgHiL&`ysle^AtOW9@G~p((gyd-aj!8wE#w`*thl#ey5wDR^rLEbEM$SwZDrdWj$=@ zIor;6yQL-?-?zWt;6E)CoQjp>0G19b7jKkrVYD;L=j(3VxY5mK;*qthSl3Cnw<@EE zl&8Nry>U#7pwf+AGdoR(66>W^SSOUQ^-OLMpzj!bENi_zVc4{ zyRWi7K-J{}RN`@#RKkMKd!Yy1aHcJ|a6qc!k@g^Q=1Rz{$j-e?h=%!IiY|z-5a6DK z&yC8-DqPhRU7L`^6pyN zPeD)Tt~g|E?Uqjy$@i7}+v{{V6@7p$Bi?bNHay6;vPZ4d z32m91s=Ow&_4r+L@YN0$`C5w9&V~H@bM_cS0lFRK$9}Z8UZ8H{%y+XbR7796^^@ZV zeQk>uvI(u3$F7ca(N=maaYat?>ahMewJ)KIhrn}br)xJ&r4&p;d3+dZ?-W7<@yCd1&7v1xt@j_7+$( z_R#LqCLhwjapmgOcRFXXA^+_4lb=yq8?eUoXZXmen=%Vc5nksTLi&L;G=93e_tHPZ z?-HBaVqWMR8P~3wo%K8hsi4^s3(9Mh>!|)jV2N!Ni*N2b^4A4pJ!f<{g%C-&Yq(YE9*z-Vx@Urg+ z?eY1-h(eBWwxwdkRe?#%+Xy?gth3swu=HPl1G}daHS+h1V%AW$e(XL?e*S%!Xr%dh zdiv|h$;*5Xvk5Ivp}clKpGPdmz=jaFp0cQwC5hyFC)-ozehjNgT1V#3kA@LTP7W5F zbrvpYWisfd4R=vD)|T53?mun9VDFm8LJ3nlWb~k#?O+pL!1jg9XHMmg9R-)lwo3I2 z?%Xt8!s0rUvblpsGsHI4gcbM|z%9qtG>GJn#KZg;XX+(u%xXI`Ja>c4*1DK+dUPl* z6FZmLMFrs(FHQ$3iHA6mUX;&E9KUy}x`Z=G0W=S`5O2(F3|>n~2u!wH>NH7Fg?>aQ zE-L8rx?X^U){S`*Ykslo>nH}VIGB8{uQ-LCsC=br0T&0LRo#hXjZD_O+RjzK)WYnn zY+6Ps#lpS#iT;YyB7YTX!lJ<esC8Nr>`cQu+?ny>KvZg$7%VwDnbBTa%l=&3vPI{j4xC>a_d`yLaKa!;={f# zdG^^d?_vH$>Bdzf8$6z55@ITF$CPhW_|%s;c}RH!-D9T0da0~=nsmoe{|O`d?IY?_ zDEGpe67x*>k?KEDryZo385LX16{wPxolrf$+cvAq_Vu$OD7~$o5U!ijjM$n@OktOF z;G{)k%*H!`Ovb8HjBk#8_0A`Q&w%5hVemSq5nsyWGghz*`s3JA3fXA=4Z^i- zTd2_C1pjBGe@P_Rq%pP;XMx?j)I_KD!N*CuNuh>N@?*46H*#otyRMxTXpqEpQXok^ z2J-8RVZ@jtw%N;>uth+QCtSI+o^%CvN`zOT4cb!I^&tQMOWEImND^NRbZm#caJp*| z(#+W`*6d>{6aH-QLfBtJQ!7O3#4slP^ejGXZit_VdEJG1YZdPltKG+Nr_2WKDN2iv znJApV`}-fhHH$#v{I&0;_a8#gQ81hCroS>OMd~y46JL4{+dlt&6tx&#rms?R3C-I$ zoS>Aj5H*rrBb8-LfTXCBjUNsi7HyGLBDbKc;lYJxS6}>n@%6(%U(IKD@<5aF>C35q zVqeNfzew{OJ2PyXDGq`Z0qxs+=kxFNMe~2|j*pMun$(tml5Xjy`kw{qm4dHGY3+}> z7jN@Iib`FbPmm6THzbB{{Cfs1O1=M4F*VlHp*;#{wpKbfIqZKKlkAm{kgl|(xE7!G zB5ieaM^cQC^`YncQW0vkRR4t$=xNyZHGDLoMBCSW&hyHQ%W=4Hd6Ez&b#=q5;Bng~ zG)GvK=VE9@bFv;$dj}x*I6(%xd*ytp92D}7E#q_W3ICi!w@BqrxdC4w7}cIpO(C5WD4a>pdUu+7m+F9!6|87@6#5 zmkSZ=mqS_W)3Mr-4i%GX}akXu9Fn+Ymve#-z;vC+UaP6?y!aqVvar(vPKBOu`D`1a@UzrvrTP5)4 z`F9X1dd&jR_DLqasi+VR>+zU*zPkoO2Xs;-Lnh2Oju@VEXv=4LDKZ|CpsQM%i?be9mP74`r0)d=bVw%bclC zpO~iwT)fyUTn_n8svfNv%^A~tCj2-;q+a!gr>-i?{I$DQ-k+>nnZIM?=ewwnr|-tI zBFM)O7*`8oVR`8JW?daRs{IW_QCr!s3R@Q$TYtg^%pK&afr zbM$>%Q4~zpi4!+oEyk^Tvw_>%jYC6<2{SexB?5~JFi%=`K zwlm&Jo7M?60n_Fm2E=emLxX{#I-{>fH;P0fICel%Ugd?(U_JtMmoK!>D-66xk>p_c zn&aByKsU47j$@|L*p9pqI9Go2c<%ndc9l|KeDtb-=V4UWVFL>VgO!z)tw}MgZhF=W znC&b||6HLPJX}uJo9vC4rBbCYS5Qqr0B^eUuDuZib_$1Li}Qy>Xu(XHCIYTU+>ie;mCT|%Jo)W zM<>+F8tay3A1|Yv&%v4I(*~<27lL!2q^0>lV*JVOhZY+qi5faG?3mmBFb4yMR;TDk zRtdcvtok`#d&ca~3qlNCc053^Dv0_#O{GC*?jPt>QBgljpMxCxu?<^6RF(2h7|h&t zAC!A^a-Jp8{5&5z2oOtapkbbS>&-B>clOI8xwBZpqWd7f)i4QI=x#S@ktZC!U;orZ1H^unOpFPLrE1&T4HR%g4Vb4`i zAb_LNp2%tkrLaUdr%x24Ow+lDHmtM@A%*XnEg&R_3kmv17iyztMV^4s;ys`hLn&uz zp$cocX0G;;OjHNYqM&td?@C1sI_Bl{ZBF#5yngqkgAes8gSB@qG;tr}!07X2#$~*C zAsCXQuhz$z;6)1Z&1aT<)f?FPE95C%GOSPW5s2EKSc{G6~{5v)}|194Z#@`eqV- zu89PRq7v_pL{#eT*kA>muk&vqxaHNLTx}TvduO`BqWT88P4ISvqH6UG89T-qg|&mH zTjw0%)&CCZZ6c{dPJT6NpX5toFVfOt$ak0RAwWLUaHiXA*`X_oA&>u@x?YSpM&kc4 zmmVKqzPE0`2TuLmP(S{Ua*VkS&7U;5CoGTr=bAPsmtcK2TG#a1dF!p2J+UPT{lun( z51fY*F302e#eqnRCT6Spv4S4_`PQ^4RDr{x>f3;qu}U1m4`;QjBlUx8>wd7n^31{( zxQgZ3f|Ao8a6yz-w}(WfrNJeA*k_qFsNM8*R?Rjmk8#HEta_=7_`d9dwrvmZ|LQt+ z|Cnh|j+fm*m6%hr6MD8+5_%B-nZD*=uSOhl`!OcfP{ApW4&yLvDU4Dtg&h?r7a2ip@d@I zqk%J)nv%pKg2bXYFY6L>(?I8BEn1b2c&IzY7K<3D5b__E(PObtjfVh>cKA@1?x z$*vVvkI3&qsjk;X2K@yivw;Wf4~oVNFTg$=BQ&9U_vLrcYcL!xem8%A67Y(O3M;K? zn@pit5lXnOb0AuRaXla2?)n(sO*cy2LRL-LN4rhwN?|I#xYg{RLva}MVz1RDNj~+R z%7qLVJr6Di*nA-|SbLeVIbWC|x&x8|s$^ zGNg-k7(q&7>@%4NHbyG^Z_73YvqpO+^8DpY>iZS*t@)cwnkrN?m0uzCUeJRLYr1<; zWCI`mOYF(OhKG7$EWpl0dLyC1O=o=~dCY*_4=7ra`Wt4+_pE*PQIFPtqYNJcQhDh! zfAv$yA@1gHcnpzO;_}YL7Zx@9_Vmr_eK$Nxf%~Wg{+b-ZUv_!v1#WUH4O)UZTWo0l z+MwA@wrTOJ>g$9#WVgch&{8o5%k%ktJHGSU)0?dVu4`6-l!+M;5IlM#cJ9u(m~nf1`>4yTlb2Nz zH8|d+`yVf1^@oRaS)*n03Fgc^?a4t;lJ0{5>R)MWG3b`3P`6E4j9w5=u(39vZVNv( zyD|$Eaoauehzw@9gSMin)L62DQ4KjtU5V}_j`@)dtb&2B3KU3zK;cTfCO-@M)}Cl)Z5=te-@Ri<}q zZ!t$I#j8;?d{@cNseD<}Iu2y9%B7fmbb*pi6}52!s{9msmiE8a{C)Hy&^{aC?g2YFS+oI$kZdQE3;VwBUBQ2x^tWJfWYF?-t>^CR$9^SC?z zNyXKW82Wh*vI$w{-{&FZV7EQID%BkV2wx@RaWCH6@C0dKPvNFjs1LH9g=n>pXZ5lc zE^*7Gf0QCmrCN%Q4t!yg}R8S4aws|eFk7ShJ`;48odf&tKfXc#; zB^)?k59hJkZmu>ckE*9?Q6%S|`VCXz$G!1YkwXM~ap--PYzM9!~6Pv3+r+FQSBAz5=#7u-`Ia~uRNG4 z+V-g}^yM=0_wS7rvvB;I#&xbdo(+ie>GJ5s8yOH!Pbtrkox-NM|-a2f)2Lvo$%(iYd#kE zd#mb|ehMsY!w9(Vn_Vbxg42+%E`$I?FYPq=0r!$Iegn_N6XqM9Jwy2gI!Vh};-RX8 ze`Mfw>zHR^uPWI*|BY?M!SzV=+xKom1qs8~#Jw3m`81IeV?6%Yg^HnZFPqCY; z%R7($7a5dRpjFS;yUh1=E@_h1A2vV*^4J_`wI7g_2|3Ukv^0J~SzT=^ zNk=!UT}*K%>$Hfc`^po7ya=${lh;2R=+vJk8k4y9*2+WO?BlcUCl=&*H%*lf_I=rc zVg71@pMO&@2Y9ZSo<}7O_vWTFMKb;jD1?{?^rPf4%Wyerj781WJ^s*;SY>^bnPv-_ z#geH!A6ie+R(I%`yPY~PDy*lYzV<_8i<-&i?>x77-F3k~35t|BIZEn2iA=98UHsb= zFsw-zR?9rGTVnRj`82elL-ni^M}n^1?X_38pgFqHlYLvducKx@n~)ctrKd9v?fw(3 zYoyMU_VmwV)_14%bW-0gTXL{6{(Ux$d}@zkXY0>ny=P#|Hvquyb%yBEl%?~YH6ImDX75mv{9W}u{HNBT}VSrP1JxR&szamPLL$~ zD4B2VJjEq0u(|krSN+iiv*!^Jx}pFsR9C_}#^Fk@42k#_ z;@dew{)>?N-%(q54{_n3T62Y~Cc}E^ps@l7vHg7#?P}C$5s@nHp6j4%|^ zfMv|xRKkZ9o&L<{Cj%Jm4AbXBq$j(|#Q(4fJy_M^WR}(iRkUFqJJTT0Wo|hHbIo-r=V^>XR=`%+ZT6lq*g6oIYwSm*p zHP?Q2MaNt)-9Jywk9|Pect?6eLN-X#clHOcb64vh^#wFO@Z!7;n-C~#u2&qdH_z1W z7l=uF^5p(z*g!zsfi!L_nYk&h87~p^>iEX!Q+Io0)$}mR^0Jc?tn)iyH|GO{#D;Z$ z>dvq|;Wl6}#lOCM9>vaZNK|ZL-^I2~dhc|iZdzd7llRuis9z#zU*!hfcd6W#?|?Y^ z=bG+QmAd*sZk7AS`%e_w11L>ujZ(?qn8|ruFT^j)sqxB0h%dzB=#5_=>xBeDgnMuD zQKH`o3sE+D3jz5`1Vp&OP+n>BQo}fYKZl!FGK!Cn&k$esZXI<%c3c@dT7N>>gvotq zp6{Gfg@M9zFk^>;IszE6?N&Rgt;1b_r(`a!G`vsU#R=;v1K|Xzi6&QAF|L7~H5Vb# z^II?nA<0mAWy*c4*K9-6R=8h?+Yvs%f63=CE-pd=V}g8jIVVe`_73NQnCXphL$s^u zg7Vh$RR(#^D4DkR8Hc?+p?S|#vhNSxw{K5)m*HKrHoA6OHe~WKR)K2U{cH&0dbiO= z*U$@D8kkg5&AEu3=Q-4$$xEQkYJCp>^uQ$RMv;4O-ZZG@PSO21*i9!Z7#UEmYdR(< zM>f5^CYlwdNKs1}oj@Xj9h7{y=LjEv@c8j_i~7A;I-yUt2Rxo1qaK`` z{t$j?M1V240&|T)c)|XLri8~3|MX24%H>uJ6)o6G6cn@S*TPJ9&Q$!c16%WFlsw!n zhV{{0qws$rG`&@hWaR=b%VOGBH7%{#hK>nnEB50>$Kl*wg!!Z*R|OPZXRXeiaIpi5 z0?&E3x<@2Ypkn?Hbg!y_XN{7_ol-X*yNV`g z0msKwRAL4(&lL7em_k;+r)(Y3{yEl&)7#r8l|WPEco8yJD2>$1YkQ|Zd&aXIku847 z#@L)iO$*^86vWNEwc@DQd)Xu?fEJ$IrDwtP(YCVjJO^DZw$0#S$676xk&-TSAg~L5 zcSE~EX0m-n#y>sW9z$DS9Aq0Hdy$>I0b!Gb(2;td7 z_D7s=Ke}u<5Y8<_uXw^q1rv`d0@-rf+QhS=TAppz(#H@>i6z z0{f=Fb)0wFF7fUoAv8!FQ*V9SJZL4CwnLinJ*5XR+wV|$bwnghpMzk@p-RwhyB%oL z)A2<07i{^@%3BpAki6 z-xm9QO^6c{gB`2Tbl3@BGjL+4aOUc1jVGZW>oR8cS%l3IMAcD}*(k$F^ zCRcA@9|LlZ7ua*r0`z%R%{4}Cz;esiZDv>45)6SEIs~@fHbcLMNp-eSwbU;th+#Y2 zjHSLV^!q}9s+qB^ea+>pY5U;1t9A-}8E!9J?Bb@P!s$}&*?mxim~*r>uIpkDL+_F# z1mvk+Dc+8%;1O8Z?e)NFR{vo2R-x04}~)z-Iq zGZv{y;=w;|=d+cGF;z1kFQj$$o~2CwFnSJSQrWe~l*O@k@90nZ>L%VFn6=hHvo+^(EVS`C-o#!a1L5EcRTr&qt zKkmcS>e$tjzKq&Mc%0~-l^0Ihm1X-vkj%jO%mJ&*SDgAy+bas8GlxW%MLt*k?0mxw z=xpP=p2jISIfQw|rQ6Zm_2O#o2fD$kuwtW88kHXX+%tM@=0|zwduKT8oh|b_gTfo( z{83XWpmXne(Vfb&61c?F@&c=vE6o+MX_9+Hoi%EU%8^{67$m&Bj@=bxeMl7@6_zM9J zr}7PxcR1ZXdbuN*@0mp&TvnRKk_}dy<=RmRSfjM4O%^eWfDeV_-4Wj-xFAIJ<6WHv zZUqDXS=hmlCqQ(Q`j>Bmz*`s@PxIR5$AhvLcojE}H{A%>hom{515Y_rvGjHe5t#_( zZtL4WY%FvPrSTxd^8I|-yQh4@vK%(N1L02=i>c4CAgnferom*1QqmL4yjxRqo^S^j z!p*8pxlAP``=E)jX91?i443}}c17?<#BnQbt_DQEq~X|P%f0nXZcDKZmRixUfF~zld3>A*^R>q`VRf!#CytPD(q*-irH~o0}(LJqX)}PYdB! zoJ9zA;KuFEzn>T2UPQyik%Rj$Q&$Gnl>u{(HsM<$S4Ge!SegU3s%=F?NKGH`<7D~l zo$tkeX@Dtsv_zCIFHHN?z^=)W=4Nypfk0~*=y06eJTQLRS^cQV7chnX2wBA3O>|zI z2IzYnfxO)-OPt+s2k_@WmxIm}68z?So|<-x*k*_2}m7j>$FTQXRt zJm&zT<2$6vlJ@T^Jx`ZAurZf}Hluh4eF)Sv557+wSga!n@#_l_L#Q)iCdKIQB9=lF zHX+GTgQABwdl`#QUoIzSCE@wo?Yxv7FPL@~*3%vowV@j7NjG7y3{CoEU7HP^KVIf_ z)+VLA0E=5i`8R|ZxfC=W_vCI%ZKW%6mlZWoylHV4w7cg{gYk1+_{B0r>bVL;L~>ExYgILVEoxG+wZ4$4nRhsr!I34 z&rHj9ZWP7Yg0<|>g2lXa8g#;vAwrBClGpdHs0cX9^OE1aTdXhM7!QG2gzcL)xbb9j zf-yS9STB?!4!(jR-D+DMSgIWh-3zdNWl@Nhie)0KIf;`?P&lC7;J>2C23BtLhby8a zu#T^U7o=^$1M_HB-+Xzz4ITn9t}VHcE>bJ{xjHVcCLvLl;GvZ`BmuFQzqXN7v*7>i zq38uiA>Ff82<$eRd%?os1cn@P)2{KAmVC@{_? z4wimGW%-n-3gF7I!H;3IR=K(oBvqXM{&!bND4baV=g=;DRC6pF05An9BjnVwwuRtEAxH05Q_hAfsZ7oZyI!bcy-L#RmF?53cI!59|m-f)pO<25DnZVnMHAuVFsSzyikVNX{{hiKLECPrZxV%zF!2?UOI{; zd@4la@;$0JN;>IrD*M6Zzs!{bI;inBW>DwLQE8m}EB@N?0}ZFH?}zeo9M*%+F^ftt z75y1*arblvEkN)Pw!)xZabfITnm-;?_6(E6KR~!_iNzZ*BZ?9hsMR|sT5XK2;1$l{ z>t6gjk!;~)+&4{qwLu-OhOgS%_GWoBmaOf_$1P)@UQpVrp@21+KE^{CQkrNW8Q_fu>K_&lJlIbAm)G4uTV!CVatPjvTxl@DL7Hp#9Wo>lmYZoJfb{ zK+DcBOoQDPHTX+^SR%xx3~eRL;2uN?fDcyV<)QrHEFf}n7TIG2ykBYxS6l|dhjfU= zQpBngk^gPH4LER;Y_glK&T{MSC%eZ?o!S2dgxhCo9u#NJyk=?SiOlcgCS&8%;`Yw zovDe?x4;OuH;y3OFnHi-7!(9tU#B03D8PSg!H!E}nXuiiI&bKKhZp$w4(OX}Bj8^b0(l9d(iAsv71bI8mg!XRY;Q!+5zh-kyPK{dl|yaeX@?R1l)Se>(<+lHd2)0GGT0r>J?C5ondO;6P%e$_jV` znI*1-JVSV`kKVET)P)niiV$)q6S&uYOCv)%aqMz)sWDR=WsCV(J|O^neP%lGtN=6~ z)VVECgx_umWoz1AYyvVECUo>02=lb0zzxVt?b{AZ(j!?swJJ9-goxVeHVe}HnSWdf zvhX}{vIPB$GBO0`aQGNu7IH;zEIYEYCl1krcBnxr;NmKz#JmobJ)n#@Bz;m80N$?! zE~~+=g1Ibfe<2XVo)noe*_J5P+#h~%k{H5QB}>aAxDv&?cQC@NoIQREFe*aNG6phGS`|mR%%J^JbqO=-`J}wyKC2tqPZArn- zNXx$x{jBZb@%+`FeC7TL!$PA8QxHK+OVzX9&A2s^QJU^f^-K+ znvh&K_pm7MEKc+VO`voSEzBVY<$kVoUMq3pi4ZL;@)CJ66uWKeE1jWN2qA*Qy%FJN zOF~3HSkBv{Ygkk05CPXc%L2CaE0&YBIju)+WIR1y*0TqrDy1Yz zYVY4MMEVyEy4N!@yQzZtOyKwY=E13EjlW-h2~8@2t{k$9*B58ioR4JU7a-8lo=3x% z!tU2GOUMZUw6wM2s8b}h9Qh7F1T<#oNi;cmPTyG^g7*PgO4-HUi;#p0g_)_q3j{O* zIbps2(8oLj*|<0v%1OQ{0z#(2#tIaSMC^IgOjHE@2lV|Tz4|B(Y7AycSHqTX**0iZ3lQuf4m4^+j(DG- zxA8`Ap7Ru2Lt7udTxTN=-hesLmznNRH#^V4ELp-3vaJ|Y$*hwv?4;NB=ZFKqIhBNB z*Kp2$EqV`i>+P10;mab2S+66o3s{Us7iyND;Uf5)CDJH*x{1wC)tpeUb==g{)c!u( zh^0mTSo%~b7|(*opezqc4G-$Ic9#C{|9FmEJHyer7RbxKLJ9MCV>R~0m-8%_sG+Jm z2c|!zeDobd)fM7S*x&_5}0%9D9N(CwOc4g@?e-?~b2d zJ?jN?3s>pG(9Aqo8G*8p>$*FS-|7P3geAj8R0JF;+;zqt2HM`MlY23M{6G@y?xBTq ztS{O%v4-O|Wv5J_$PGjhX{1x~;F@hirGaftcAiy7)rWzP1Q zdFuRpX&Bo$7b+LQ_!w^<0#UV=dV{=vBowez+Sd9mIqW>ci(_+feql++F=<>Hyzl)~ zwO79s4zV+n`{4xj!-E45e$5($!j3Zg@B>5aNdTtWYQr++w!K#&2fNTob5b-h{IRfF zm?H;(dSx%OT$+c#NpnJ}8_(QB;eJBMjaMEcp0vjI4sN40nG~fh#5)8L%wqW@%!niP zqV)yHD!cX*5ym8b?>}M>u0P z?Fy$^sr19ua7V`qL~HritxGJ>Mkq0t-t%1asRec#VDU6Jdl38U7q`! z-Ip(q6C?mPuzH?NBWXdYmTkk|bN%g6@PJiLrO@)*YqFX95U7|i5C^x^De;kA!XiMR zI09vEdyoa^4%|r-o%55nTKsUl$X^5GcnuXK{WMxe&#P?zp)nHjQ#x%P3Xmgp(kH1Jcu@e=Q^{>_fm1&Yj?=f4F|75bR)u@2<};ypT6=P5M_8-uw~w@u6I`N#AS)be1s1JP_FXvC7bc0 zJcL^v66`BJrTDwf0hQ7>XITrgjICig=P@8=Ktm(LFdmD`eaZ!h2Jr7nPhr24#+{$= zTHqi%b=Y_o{~liJgWCX@qy&hQq1Pe*&n`p=0)eS|{_;R>@Htn7JFw)2Z6=&;Cf*)Q z>1L1Gk+JstXf7cs;Oa_aDiW|bDO;ft4WM*K13l5FQch-9YK6`3vG5`qK((UAsxAX7 z0(fRRFi%Sk-1I;zeVXmff5SsQaN)h<*ZUt{!Ebu7GaiDtC5$xfXIu8&+rdLHpoMro zeZ@nR;3fNWXFzmKi?P4J48leD)J4yNgMnwZ1LFu+!#n6{Sa)+Of|1a(Rr~0t6%gPo zRoN8}t$Ia3Twz2LO@5aC6a(a5;r6v;6L?v(8|AT7XcPwD)j6lxmOVz?JOp(!NqrPb z%pF>t7RrMre8Wk8kjnvpMt@EJQ*4|pDuH<6amND`?nDaqZQ!gmjY*qZS6nMkgI3WT z2)_@OU}gpz4EMk@7DfDY`cxzU?pnFNY9bukgQ8vS$$Iz#VH|?P>{?znzDi#4J{H#; zg3)}!4|reVHlTO;*x?OF*a(z+E|Wa|mQg*jusA8oL_meC0Q0$;JbZr)30`iZk@6b{ zIpP=MNG8L}2Bq-31G=JtYrSPLZ+mDOmf*||a z_wT)R_a^GYZ`c?9{L6(uk#`b7EA)!vvN?(pGsU3o1c>g0#M_}pCu?9d(_MCvs_ z$?udfFYZ4~c{&S#9(+4nTjAR|?zsy$0zmE37-gp%Xc-SvbmeBD#!vfP`sDb;2)Gw$ z5-;@(@w0!ib9^tjEQnL1h0`+$Za@YE&PMwrW#ezlK~eONgMS{NKtv#{V6Qw>CS1AW zLE`?)0t9S_@a8mp$e)T_ocEu8p$Tvf_e6gPN6%DL3u7caQGl@c5xJ~i15Z_^S)JSg zfMvHZj&fp`IOte9k0MT37NjPE1nxboZSYe-h4daf#|f4$aFBNp7A8Tyy9zNkSg*T3x2q_a29WZ$k4SE}3vy2J#&MqNUHJ zUwA7cAeKvyFa5Ktx(M9Q)4fh6T6@XO6C z7K|*V_4+*&0Zt%exU2kzyHS>Je}v$CI6YwS zB5y#^3;@l%RxArdk8rZeC+L@+s23mfx$xB)&xO6nmI=a&q!J9j%11yJ#>k>%4lm55 zyN^lV!Gih_O{^U{K^$v&M>qf&fji^3v^v%fr;SSs@_@~PTCPAi{|YyX0@<5= zmr_HJ@J>f)Oo~HY3F%zJZR3HYkopr<4}Qu3-Ypk*y52h+bZ5P>2N8ku))$}xW^>FD%#!} zH+T69Gz^zzkdIUdvi9v?MP80IncrIGE;j=i{S7*muI+XqfCHBO zt}K0^cV6}O{(8Wr(HkTr+KX>mNL6qs168F`t_dSP+>@}h=r@90(j2T31Vb>ZY}=06 zjUq(aN*bC~S=(3ofP`nlDA{?y%E zKxD9{f1Kn)7A^qft`JSgw+8MCADxa4pnNUvY4DmOD0iX{7LkBC*avsu7cknzDVzmW zLM!_}4xfe@r4epcr9c&MJ?Fs%5=2v<7ylXEmWp_d?eGCPZcJ(TBH@1z?SVz|{o$#A zUmBJ09?G9qjKADT71taV%)D!V+;Hs;LE?r&HA~M2i!7*?B2PPa1>X5J8)ID(M2`_$~%Rd z^rDCy;6#@okg?OQ9Mk|=Pj&S&U<(uVgXk)A5YDP1xTVLgP)#pvi-uCLwvXW!@eW$= zs7e2vXE!E}tvQUd6N_802={#~h?0e`$xilvYP#~Urmm)cZZ^W2fPgG&0)m2yfD0-u z5>^2P14>X3MJ>uIE}(r;;0BaNtCb4wTL2YE5EKkmC=k?As0$!Fh{V=~MN7mWizN4* z_LyVjjZVj&AZ;!&3|VPuT!0Cj0Y8JK~?3R3JK{& zY|P@X`e<_37Quq!%#X8>ED*vHnySE=2B^?KX=f||Yk-Aj@Be=)N~1cH6IsIYnN3t+ z%X6tnGMwnVv!X}OVHZSjZj;+WIoG4eo3;r?)8Z5FAN+U;;~eb)rf6_|zb6nzV+2_< z@66-T@Xje$JYB7Gx)vDjokc-e>CAXAJx?-w?G`P%kg$%>VCGgp$#%DUNEmpJj0E8V zCmeF@mRu=efEct|pfvrmlYo>UIz(IJ2cn0&h?Tv z2?5Oh(Z0Td_Vy*PK@%()g`*Mk)H1lDR}-jWur2MvTqeiE0%#|3W8``B+llRmI1J0=E&2^(_E?s7l+;&dPsGyJ76_#DcX~B zz#H_#(>*6!{Gsnqu-gguVj>@2LdrYw?w%Pm&rAx+^8|`@8%3pM`>(4@2~&kcYCMuh z$Hl>*kpJZomnWnakWJLso-5@vq)%_5F?URa`?*h#FAc#sk9N^j`bz<&Ci!Z>nl-C- zi=K>pGDkWVkMB6Q+;Q%2r`k3*5-R1_XDc!nlA;93mA6E)+DOi0P(6jne6Z|xiDsNR zTIT8Y7OHnY{q5K>>cT+5tF*JkjA?Qdr%OH%%22k<)%nP;EIA7lWmN3yn6~ej{@Whw*`z;J(vdvCb6^(f>k5=h zt9P$wZ?-~NzYlpA7rfiI(*w|#XRSWcb!3A!r?B(56xXHo;_I>yhnJX9vRFdSarD>b>~#0P%Gl=zbnUuo<~*}i^Din+zXY;# z$*r5anc#8jl63-;OSa>yN+>#x{p^hkY53ocgRE{CXWZDWdjZ`;GGiqF_f2E=yUXds zOCCw2xc(Xpqi}brre zdt|Zhxo(lQB5URdRSLdLQNPzA5SMCStW zd57#OWawL08E`*k{G+&537IhGD&b}dTS+)83kVjjp{@9uZ?zHJE@)78`L8jL+Y}xB zm7TV2X&E*Bpa|bz05ypALBf^Qb&Erdx}1<)2Ed-i?uG6DN!mWf!hMQi9YsTH#ibw$HwRPvnKQbR{i>`EqHl*k`zVw8lFjxGm+HC@W!>3`n&Qs0yUg48$X0i z1?-G=`xMzDo}> zE5Rsa@uvNUFFr$Yuz)!TUx_$&6?@E4oJCuaB%SD@krvz8?Ssv$W_%xsaC49eAY#7Gv=ATWHENh%PqxEM#!H*X_M7*>s+DD zjut0B6+a&GCc4xi*tD@cM&3>uiY?p?X;~Ju@#roYY;nvqbAtOE;ATPb_;v8{V{QIf z^{3D^;xIH_QIxP&8kh7bmm)LPdf@@s7ex+$a_Ha8e)zv0rUttGtcm$_75`&Cl#DKH z9XuIqAM+Q0b`EH6rn1eneguqt@y~vQhxMFG#W9FETX*8}knVPx3^PLbnkihg_!(6Y zyJjZteXN+o^ncArGOInOaEMUJ1K=xG-=8OrZA6CagTG<9 zaSd~to<9%uah_xtO+RVOj^k0a3M7{QPzcUSpkF?a=-k@=q-dzJ9~t^*f0jJ7HynG< z`tDtt?ePvZ3g~7V&;hSjJm)_hU$NJ}0y5?cC^=J6sb_-kD57%6m`C8vIsjCtvcqZy znu=c#To0XNk~GfU(2CH}ekW9?u8whI1$*yH6DHlz->oF`%dtFm`24Y&UAhC;^F_z9 z>Re+9d`EP3zg3+pmj(Fzv^$HLHemyNJP!{&i+VMSH%uNN`TUZ3e4x$sC08E}10rA) z8Yc0}j?JOlX!QhZIt(>dtBAx=R&uqFMgX3n*XF_!-}E%0DGX6J-wfHDCnf53YDeeH zI5>0qrz;co3zUqjbc}hb;_fR(6s(GDgJ4Ur3)?k}xlC%08Qr9;c59|D>HamMc|MT? z5@;zRwir$SRNakr89?O{D-q+52SbrBcuU_PE$*6y_zUK!eNaEf^G1kqfj_2A6Vd_HRE6^*?`#Zy#vN z)#h|47A*~EbY;#22QB{jHGxmaMT@ZUajKXgLnAXolb~L z(|x(YfPF|yb@m9#vQAd=8$j@$B`eH13(37Sc9@3VtUFL8bo2AJaoCXl;JeVHesE0V z?=@7E#S~%qbkT}b@%9wk7sVOs^k}@;rfb{4Y`9|psY7hX>TYCazIr<lg`&qDqTtx?}tXuFz#*xEl=m`%67XIP#1Kf=MGC$=ze4Ssqa*w}lB zB$qXpEWSF?1k#M23towE%3*8C;&`nKD^PbcN4hZ^U7AoAZzE?dqkc`UXg*4u!+hqz zMD2>wMx#r=B{L1a88@6BZqi{#+wE*%Wz7avd#aF79RO7QXnHiY-IhI}ai+7R#5Qvp z`w!??!={4P2erw8x_iui1&0jqD0=|n!HyRbBK*Mex#V{$062d0Gx)#w)-c@c_-PaT zh9uMukP}YCe24R9UR!mb{+C&=jmcJ`$egcaFVF(R8-A2^y)nzB7cd`QRo}lQ^tRa6 z`uh40u#WbHphgJw%lelfuLv4A)kEaeUqPNzhH={}6N{>>g}%<8I{l#D!k0=pk(~r(5|MAra;Nk;(mc zDj-CAd=K0abphKEl=zoxYuIon@CwFIdKyUI)CE%(+1n??hT)_XYeR^hy!1!N=S1q{ z%#)H-GzHP_y_aA0NWba8nOj!sZFXE_dt zSou7vJ*5|iE;*pa4a)&!IXOJcbgxXx-y>fyWmw&ggahg>3WTe}L-LL-yVv-At)?UM-B2Q*ZVyP~=3o~)`T5zGa| z@s!&60SB?b%3Hm73z?$y$}3jKF+)Qf`QMsLf|(-`^Y}D!daH^2a;yb zrlC25NAoIgiS5TjI?q5*YWqD=Y}wSJt5y>lGIQ88r{}3T;@^vtwAI2fIUTr{u=Np= zR)%`Z7{YnbXxj--Xf)hzu3Uq=R9%VB4SvEcJO?MuQEK{AmOyXwct?_*`V#~ABv9Q# zL-W{BXhr)BaOF6UfPI11afCQDLnu?-Ik-Sxy9Y5fp|!lWwQ41_jDUKK0v6vDF9cO$ zRxdDh2(P}f%m7$kDT!Y$-)1G(V5)mR)3TN_tF(knL!8f{^`2=+6U4<{1qSnBY77H{ zUwhPyX0g?z?JFGQdz1PS5IUOwLS-HHaResPN;*>yeLsA8rqs_3naoL1{!b<7g4cZHz)Vj?g&6#_(D$s) z%Ao22OH+izZbaE_nrfk9jQp*9pb&24Aj17B zw6tr;m$1AEjJblCvo0(!73T71RD{mvh@o(DOA`ZWf=Nclhb3C;x?;rAqZqyo{O`9R z(XhuuXP}l8TH>CB5TmEVT(ADD~bv3!xtt zO2ZXQIT>}RySnM4CIN^qwwKz1&}!kn7M}ELfe=S-xJ#Kt9H5X3kBCXj9vYW^rmOZqvrJ1~f-8)UBQ( zo1lK?!!ZNOzaw+QJ3kiSTT2&oCREk#qO;)CNzG6fr?gs@TD9C)9@XD(zSMx4bby~y zbtt9#@JHfs=c+mlBs=(x?p93pGQfxLgmPnYey)eQUdRv~PT3zr3p|}~q1u|>C z9N3)JxM@T}jJWiS6HXQHSmj%n)a_2sb&>WTa;yB@?VJWkSWTKPK>h8{VmfWh+EgFS S4=K^=XJ4-X&l2~D^#1~Q+fujy literal 0 HcmV?d00001 diff --git a/src/app/bridge/page.tsx b/src/app/bridge/page.tsx new file mode 100644 index 0000000..ec37938 --- /dev/null +++ b/src/app/bridge/page.tsx @@ -0,0 +1,11 @@ +"use client"; + +import BridgeDashboard from "@/components/Bridge/BridgeDashboard"; + +export default function Page() { + return ( +
+ +
+ ); +} diff --git a/src/components/Bridge/BridgeCard.tsx b/src/components/Bridge/BridgeCard.tsx new file mode 100644 index 0000000..b9322a1 --- /dev/null +++ b/src/components/Bridge/BridgeCard.tsx @@ -0,0 +1,258 @@ +import { useTheme } from "@/context/ThemeContext"; +import { useMemo, useState } from "react"; +import { Card, CardContent } from "../ui/card"; +import { Input } from "../ui/input"; +import { Button } from "../ui/button"; +import { Send } from "lucide-react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../ui/select"; +import { IBCToken } from "@/services/queries/ibc"; +import { ethers } from "ethers"; +import { formatBalances } from "@/utils/format"; +import { useSendIBC } from "@/services/mutations/ibc"; +import { useAccount, useWalletClient } from "wagmi"; +import { toast } from "sonner"; + +export interface IConnection { + id: string; + name: string; + iconUrl: string; + chainId: string; + status: "active" | "inactive"; + channel: string; + prefix: string; +} + +interface BridgeCardProps { + connection: IConnection; + balances: IBCToken[] | null; +} + +export default function BridgeCard({ connection, balances }: BridgeCardProps) { + const { theme } = useTheme(); + const [selectedToken, setSelectedToken] = useState(""); + const [address, setAddress] = useState(""); + const [amount, setAmount] = useState(""); + const sendIBCMutation = useSendIBC(); + const { data: walletClient } = useWalletClient(); + + const { address: connectedAddress } = useAccount(); + + const handleSend = async () => { + if (!amount || !address || !selectedTokenInfo || parseFloat(amount) <= 0) + return; + + const regex = new RegExp(`^${connection.prefix}1[0-9a-z]{38}$`); + const isValid = regex.test(address); + + if (!address || !isValid) { + toast.error(`Address must have prefix ${connection.prefix}`); + return; + } + + sendIBCMutation.mutate({ + amount, + denom: selectedTokenInfo.base, + ibc_channel: connection.channel, + toAddress: address, + walletClient, + exponent: selectedTokenInfo?.exponent, + }); + + setSelectedToken(""); + setAmount(""); + setAddress(""); + }; + + const selectedTokenInfo = useMemo(() => { + return balances?.find((t) => t.denom === selectedToken); + }, [selectedToken, balances]); + + return ( + + + {/* Header */} +
+
+ {`${connection.name} +
+ +
+

+ {connection.name} +

+

+ {connection.chainId} +

+
+
+
+ + Active + +
+
+ + {/* Token selector */} +
+ + + + {/* Balance */} + {selectedToken && ( +

+ {`Balance: ${formatBalances( + ethers.formatUnits( + selectedTokenInfo?.amount || 0, + selectedTokenInfo?.exponent || 18 + ) + )} ${selectedToken}`} +

+ )} +
+ + {/* Address input */} +
+ + setAddress(e.target.value)} + style={{ + backgroundColor: theme.bgColor, + borderColor: "transparent", + color: theme.primaryTextColor, + }} + className="border-0 placeholder:text-gray-500" + /> +
+ + {/* Amount input */} +
+ + setAmount(e.target.value)} + style={{ + backgroundColor: theme.bgColor, + borderColor: "transparent", + color: theme.primaryTextColor, + MozAppearance: "textfield", + }} + className="border-0 placeholder:text-gray-500 [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" + /> +
+ + {/* Send button */} + +
+
+ ); +} diff --git a/src/components/Bridge/BridgeDashboard.tsx b/src/components/Bridge/BridgeDashboard.tsx new file mode 100644 index 0000000..6caf7f0 --- /dev/null +++ b/src/components/Bridge/BridgeDashboard.tsx @@ -0,0 +1,139 @@ +import { useTheme } from "@/context/ThemeContext"; +import { Card, CardContent } from "@/components/ui/card"; +import { StatCard } from "../StatCard"; +import BridgeCard from "./BridgeCard"; +import { useAccount, useBalance, useWalletClient } from "wagmi"; +import { + IBCToken, + useCosmosTokens, + useQueryIBCAssetList, +} from "@/services/queries/ibc"; +import rawConnections from "./connections.json"; +import type { IConnection } from "./BridgeCard"; +import { useMemo } from "react"; + +export default function BridgeDashboard() { + const connections = rawConnections as IConnection[]; + + const { theme } = useTheme(); + const { data: walletClient } = useWalletClient(); + + const { address } = useAccount(); + const { data: balance } = useBalance({ address }); + const { data: ercTokens } = useCosmosTokens(walletClient); + const { data: ibcAssets } = useQueryIBCAssetList(); + + const assetMap: Record = { + "0x1E643c8fDc9bA4B99AE598e9b0Ed98fe3A2319f9": "atom", + }; + + const combinedBalances = useMemo(() => { + if (!ercTokens || !balance || !ibcAssets) return []; + + const nativeCoin: IBCToken = { + amount: balance.value.toString(), + denom: "kii", + exponent: 18, + base: "akii", + name: "KII", + }; + + const mappedErc = ercTokens + .map((token) => { + const denom = assetMap[token.contractAddress]; + if (!denom) return null; + + const assetInfo = ibcAssets.find((a) => a.denom == denom); + if (!assetInfo) return null; + + const balanceInfo: IBCToken = { + amount: token.amount.toString(), + denom: denom, + exponent: assetInfo.exponent, + base: assetInfo.base, + name: assetInfo.name, + }; + + return balanceInfo; + }) + .filter(Boolean) as IBCToken[]; + + return [...mappedErc, nativeCoin]; + }, [ercTokens, balance, ibcAssets]); + + return ( +
+ {/* Header */} +
+
+

+ IBC Bridges +

+
+

+ Transfer tokens between KiiChain and other Cosmos chains +

+
+ + {/* Stats cards */} +
+ +
+ + {/* Bridge Cards */} +
+ {connections.map((connection) => ( + + ))} +
+ + {/* Info Section */} + + +
+
+ i +
+
+

+ About IBC Transfers +

+

+ Inter-Blockchain Communication (IBC) allows secure token + transfers between different Cosmos chains. Transfers typically + take 1-3 minutes to complete and require a small fee on both + source and destination chains. +

+
+
+
+
+
+ ); +} diff --git a/src/components/Bridge/connections.json b/src/components/Bridge/connections.json new file mode 100644 index 0000000..088c53f --- /dev/null +++ b/src/components/Bridge/connections.json @@ -0,0 +1,11 @@ +[ + { + "id": "cosmoshub", + "name": "Cosmos Hub Testnet", + "iconUrl": "/images/cosmos-logo.png", + "chainId": "provider", + "status": "active", + "channel": "channel-3", + "prefix": "cosmos" + } +] diff --git a/src/components/ui/icons.tsx b/src/components/ui/icons.tsx index 9e2dd80..fb5a7c4 100644 --- a/src/components/ui/icons.tsx +++ b/src/components/ui/icons.tsx @@ -440,3 +440,26 @@ export function ArrowDownIcon(props: React.SVGProps) { ); } + +export function TransferArrowsIcon(props: React.SVGProps) { + return ( + + + + + + ); +} diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 0000000..6e637f7 --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,159 @@ +"use client" + +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx index 2d8cc97..d9510a8 100644 --- a/src/components/ui/sidebar.tsx +++ b/src/components/ui/sidebar.tsx @@ -18,6 +18,7 @@ import { StakingIcon, GovernanceIcon, SmartContractIaIcon, + TransferArrowsIcon, } from "./icons"; import { useIsMobile } from "@/hooks/use-mobile"; @@ -69,6 +70,7 @@ const Icons = { StakingIcon, GovernanceIcon, SmartContractIaIcon, + TransferArrowsIcon, }; const SidebarProvider = React.forwardRef< @@ -786,16 +788,16 @@ const menuItems = [ label: "Blocks", href: "/blocks", }, + { + icon: , + label: "Bridge", + href: "/bridge", + }, { icon: , label: "Uptime", href: "/uptime", }, - // { - // icon: , - // label: "Supply", - // href: "/supply", - // }, { icon: , label: "Parameters", @@ -811,7 +813,6 @@ const menuItems = [ label: "Faucet", href: "/faucet", }, - { icon: , label: "Deploy Smart Contracts", diff --git a/src/config/chain.ts b/src/config/chain.ts index 61ad16b..0f4ab21 100644 --- a/src/config/chain.ts +++ b/src/config/chain.ts @@ -8,6 +8,8 @@ export const CHAIN_RPC_ENDPOINT = "https://rpc.uno.sentry.testnet.v3.kiivalidator.com"; export const CHAIN_JSON_RPC_ENDPOINT = "https://json-rpc.uno.sentry.testnet.v3.kiivalidator.com/"; +export const CHAIN_ASSET_LIST_URL = + "https://raw.githubusercontent.com/KiiChain/testnets/main/testnet_oro/assetlist.json"; export const TESTNET_ORO_EVM = kiiEvm.TESTNET_ORO_EVM; export const KIICHAIN_BASE_DENOM = kiiEvm.KIICHAIN_BASE_DENOM; diff --git a/src/services/mutations/ibc.ts b/src/services/mutations/ibc.ts new file mode 100644 index 0000000..beaca0b --- /dev/null +++ b/src/services/mutations/ibc.ts @@ -0,0 +1,47 @@ +import { SendIBCTokens } from "@/utils/format"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { toast } from "sonner"; + +// useSendIBC is the wrapper to the SendIBC Token function +export function useSendIBC() { + const queryClient = useQueryClient(); + + type props = { + toAddress: string; + ibc_channel: string; + amount: string; + denom: string; + exponent: number; + walletClient: any; + }; + + return useMutation({ + mutationFn: (data: props) => + SendIBCTokens( + data.toAddress, + data.ibc_channel, + data.amount, + "IBC Transfer", + data.walletClient, + data.exponent, + data.denom + ), + + onSuccess: (data, variables) => { + // validate if the transfer was successfully + if (data.status != 1) { + toast.error("Transaction has failed, please try later"); + return; + } + + toast.success("IBC Transfer Successfully Completed"); + + // Invalidate all user-related queries + queryClient.invalidateQueries({ queryKey: ["user"], exact: false }); + }, + + onError: ({ message }) => { + toast.error(`Error validating task on CosmosHub:, ${message}`); + }, + }); +} diff --git a/src/services/queries/ibc.ts b/src/services/queries/ibc.ts new file mode 100644 index 0000000..4d4ecaa --- /dev/null +++ b/src/services/queries/ibc.ts @@ -0,0 +1,89 @@ +import { ethers } from "ethers"; +import { useQuery } from "wagmi/query"; +import * as kiiEvm from "@kiichain/kiijs-evm"; +import { CHAIN_ASSET_LIST_URL } from "@/config/chain"; + +interface EvmBalance { + contractAddress: string; + amount: bigint; +} + +export interface IBCToken { + amount: number | string; + denom: string; + exponent: number; + base: string; + name: string; +} + +// useCosmosTokens retrieves the user tokens on its cosmos-side account using the Bank precompile +export const useCosmosTokens = (walletClient: any) => { + return useQuery({ + queryKey: ["cosmosTokens"], + queryFn: async (): Promise => { + try { + if (!walletClient) throw new Error("Wallet not connected"); + + const ethersProvider = new ethers.BrowserProvider( + walletClient.transport + ); + const signer = await ethersProvider.getSigner(); + const address = await signer.getAddress(); + + const bankPrecompile = kiiEvm.getBankPrecompileEthersV6Contract(signer); + const rawBalances = await bankPrecompile.balances(address); + + const parsed: EvmBalance[] = rawBalances.map( + ([contractAddress, amount]: [string, bigint]) => ({ + contractAddress, + amount, + }) + ); + + return parsed; + } catch (error) { + console.error("Error fetching Cosmos tokens:", error); + return []; + } + }, + enabled: !!walletClient, + staleTime: 300000, + }); +}; + +// useQueryAssetList queries the Kiichain assetlist and returns the IBC assets +export const useQueryIBCAssetList = () => { + return useQuery({ + queryKey: ["assetList"], + queryFn: async (): Promise => { + try { + const res = await fetch(CHAIN_ASSET_LIST_URL); + const json = await res.json(); + + const ibcAssets = json.assets.filter((a: any) => + a.base.startsWith("ibc/") + ); + + const mappedBalances: IBCToken[] = ibcAssets.map((asset: any) => { + const exponent = + asset.denom_units.find((d: any) => d.denom === asset.display) + ?.exponent ?? 6; + + return { + denom: asset.display, + name: asset.symbol, + exponent, + base: asset.base, + }; + }); + + return mappedBalances; + } catch (error) { + console.error("Error loading asset list:", error); + return []; + } + }, + enabled: true, + staleTime: 5 * 60 * 1000, + }); +}; diff --git a/src/utils/format.ts b/src/utils/format.ts index 5f7a935..22b58e5 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -1,3 +1,6 @@ +import * as kiiEvm from "@kiichain/kiijs-evm"; +import { ethers } from "ethers"; + export const formatAmount = (amount: string): string => { const num = parseFloat(amount) / 1_000_000_000_000_000_000; return num.toLocaleString(undefined, { @@ -5,3 +8,52 @@ export const formatAmount = (amount: string): string => { maximumFractionDigits: 2, }); }; + +// SendIBCTokens sends native tokens via IBC to the provided channel +export async function SendIBCTokens( + toAddress: string, + channel: string, + amount: string, + memo: string, + walletClient: any, + exponent: number, + denom: string +) { + const PORT = "transfer"; + const convertedAmount = ethers.parseUnits(amount, exponent); + + try { + if (!walletClient) throw new Error("Wallet not connected"); + + const ethersProvider = new ethers.BrowserProvider(walletClient.transport); + const signer = await ethersProvider.getSigner(); + + const ibcPrecompile = kiiEvm.getIBCPrecompileEthersV6Contract(signer); + + const tx = await ibcPrecompile.transferWithDefaultTimeout( + toAddress, + PORT, + channel, + denom, + convertedAmount, + memo + ); + + return await tx.wait(); + } catch (error) { + console.error("Error trying to make an IBC transfer:", error); + throw error; + } +} + +// formatBalances formats the input quantity as 123,456.78 +export const formatBalances = (amount: string | number): string => { + const parsed = typeof amount === "string" ? parseFloat(amount) : amount; + + if (isNaN(parsed)) return "0"; + + return parsed.toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); +}; From c35954d0b190481236d679add4a03f5ccf36da82 Mon Sep 17 00:00:00 2001 From: Andres Ramirez Date: Tue, 8 Jul 2025 15:42:43 -0500 Subject: [PATCH 2/4] feat: added IBC transfer and Token scanning --- src/components/Bridge/BridgeCard.tsx | 16 ++++++++--- src/components/Bridge/BridgeDashboard.tsx | 24 ++++++++--------- src/services/mutations/ibc.ts | 8 ++++-- src/services/queries/ibc.ts | 33 +++++++++++++++++------ src/utils/format.ts | 1 + 5 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/components/Bridge/BridgeCard.tsx b/src/components/Bridge/BridgeCard.tsx index b9322a1..3b7e971 100644 --- a/src/components/Bridge/BridgeCard.tsx +++ b/src/components/Bridge/BridgeCard.tsx @@ -55,18 +55,26 @@ export default function BridgeCard({ connection, balances }: BridgeCardProps) { return; } - sendIBCMutation.mutate({ + const amountToSend = ethers.parseUnits(amount, selectedTokenInfo.exponent); + const available = BigInt(selectedTokenInfo.amount); + + if (amountToSend > available) { + toast.error("Insufficient funds to send"); + return; + } + + const mutData = { amount, denom: selectedTokenInfo.base, ibc_channel: connection.channel, toAddress: address, walletClient, exponent: selectedTokenInfo?.exponent, - }); + }; + + await sendIBCMutation.mutate(mutData); - setSelectedToken(""); setAmount(""); - setAddress(""); }; const selectedTokenInfo = useMemo(() => { diff --git a/src/components/Bridge/BridgeDashboard.tsx b/src/components/Bridge/BridgeDashboard.tsx index 6caf7f0..9f65905 100644 --- a/src/components/Bridge/BridgeDashboard.tsx +++ b/src/components/Bridge/BridgeDashboard.tsx @@ -23,10 +23,6 @@ export default function BridgeDashboard() { const { data: ercTokens } = useCosmosTokens(walletClient); const { data: ibcAssets } = useQueryIBCAssetList(); - const assetMap: Record = { - "0x1E643c8fDc9bA4B99AE598e9b0Ed98fe3A2319f9": "atom", - }; - const combinedBalances = useMemo(() => { if (!ercTokens || !balance || !ibcAssets) return []; @@ -40,18 +36,22 @@ export default function BridgeDashboard() { const mappedErc = ercTokens .map((token) => { - const denom = assetMap[token.contractAddress]; - if (!denom) return null; + const matchingAsset = ibcAssets.find((a) => { + return ( + a.base.startsWith("ibc/") && + a.base.slice(-40).toLowerCase() === + token.contractAddress.toLowerCase().slice(2) + ); + }); - const assetInfo = ibcAssets.find((a) => a.denom == denom); - if (!assetInfo) return null; + if (!matchingAsset) return null; const balanceInfo: IBCToken = { amount: token.amount.toString(), - denom: denom, - exponent: assetInfo.exponent, - base: assetInfo.base, - name: assetInfo.name, + denom: matchingAsset.denom, + exponent: matchingAsset.exponent, + base: matchingAsset.base, + name: matchingAsset.name, }; return balanceInfo; diff --git a/src/services/mutations/ibc.ts b/src/services/mutations/ibc.ts index beaca0b..4a6a5ec 100644 --- a/src/services/mutations/ibc.ts +++ b/src/services/mutations/ibc.ts @@ -12,6 +12,7 @@ export function useSendIBC() { amount: string; denom: string; exponent: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any walletClient: any; }; @@ -27,7 +28,7 @@ export function useSendIBC() { data.denom ), - onSuccess: (data, variables) => { + onSuccess: (data) => { // validate if the transfer was successfully if (data.status != 1) { toast.error("Transaction has failed, please try later"); @@ -37,7 +38,10 @@ export function useSendIBC() { toast.success("IBC Transfer Successfully Completed"); // Invalidate all user-related queries - queryClient.invalidateQueries({ queryKey: ["user"], exact: false }); + queryClient.invalidateQueries({ + queryKey: ["user", "cosmosTokens"], + exact: false, + }); }, onError: ({ message }) => { diff --git a/src/services/queries/ibc.ts b/src/services/queries/ibc.ts index 4d4ecaa..e1d7fd0 100644 --- a/src/services/queries/ibc.ts +++ b/src/services/queries/ibc.ts @@ -16,10 +16,28 @@ export interface IBCToken { name: string; } +interface assetList { + chain_name: string; + assets: asset[]; +} + +interface asset { + description: string; + denom_units: { + denom: string; + exponent: number; + }[]; + base: string; + name: string; + display: string; + symbol: string; +} + // useCosmosTokens retrieves the user tokens on its cosmos-side account using the Bank precompile +// eslint-disable-next-line @typescript-eslint/no-explicit-any export const useCosmosTokens = (walletClient: any) => { - return useQuery({ - queryKey: ["cosmosTokens"], + return useQuery({ + queryKey: ["user", "cosmosTokens"], queryFn: async (): Promise => { try { if (!walletClient) throw new Error("Wallet not connected"); @@ -58,15 +76,13 @@ export const useQueryIBCAssetList = () => { queryFn: async (): Promise => { try { const res = await fetch(CHAIN_ASSET_LIST_URL); - const json = await res.json(); + const json: assetList = await res.json(); - const ibcAssets = json.assets.filter((a: any) => - a.base.startsWith("ibc/") - ); + const ibcAssets = json.assets.filter((a) => a.base.startsWith("ibc/")); - const mappedBalances: IBCToken[] = ibcAssets.map((asset: any) => { + const mappedBalances: IBCToken[] = ibcAssets.map((asset) => { const exponent = - asset.denom_units.find((d: any) => d.denom === asset.display) + asset.denom_units.find((d) => d.denom === asset.display) ?.exponent ?? 6; return { @@ -74,6 +90,7 @@ export const useQueryIBCAssetList = () => { name: asset.symbol, exponent, base: asset.base, + amount: "", }; }); diff --git a/src/utils/format.ts b/src/utils/format.ts index 22b58e5..73e913e 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -15,6 +15,7 @@ export async function SendIBCTokens( channel: string, amount: string, memo: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any walletClient: any, exponent: number, denom: string From a80457a2e106437b4a9935b712e06143108d6259 Mon Sep 17 00:00:00 2001 From: Andres Ramirez Date: Tue, 8 Jul 2025 16:10:26 -0500 Subject: [PATCH 3/4] feat: added chain explorer --- src/components/Bridge/BridgeCard.tsx | 16 ++++++++++++++++ src/components/Bridge/connections.json | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/components/Bridge/BridgeCard.tsx b/src/components/Bridge/BridgeCard.tsx index 3b7e971..6e47e1c 100644 --- a/src/components/Bridge/BridgeCard.tsx +++ b/src/components/Bridge/BridgeCard.tsx @@ -26,6 +26,7 @@ export interface IConnection { status: "active" | "inactive"; channel: string; prefix: string; + explorer: string; } interface BridgeCardProps { @@ -260,6 +261,21 @@ export default function BridgeCard({ connection, balances }: BridgeCardProps) { )} + + {/* Show explorer */} +
+

+ {`Want to verify balances on ${connection.name}? `} + + View on Explorer + +

+
); diff --git a/src/components/Bridge/connections.json b/src/components/Bridge/connections.json index 088c53f..48e9444 100644 --- a/src/components/Bridge/connections.json +++ b/src/components/Bridge/connections.json @@ -6,6 +6,7 @@ "chainId": "provider", "status": "active", "channel": "channel-3", - "prefix": "cosmos" + "prefix": "cosmos", + "explorer": "https://explorer.polypore.xyz/provider" } ] From 88196bfa4c148fe3b05aca7ee410890b09223a76 Mon Sep 17 00:00:00 2001 From: Andres Ramirez Date: Tue, 8 Jul 2025 16:15:39 -0500 Subject: [PATCH 4/4] chore: move ibc connections to config file --- src/components/Bridge/BridgeDashboard.tsx | 4 ++-- src/components/Bridge/connections.json | 12 ------------ src/config/chain.ts | 13 +++++++++++++ 3 files changed, 15 insertions(+), 14 deletions(-) delete mode 100644 src/components/Bridge/connections.json diff --git a/src/components/Bridge/BridgeDashboard.tsx b/src/components/Bridge/BridgeDashboard.tsx index 9f65905..7c42474 100644 --- a/src/components/Bridge/BridgeDashboard.tsx +++ b/src/components/Bridge/BridgeDashboard.tsx @@ -8,12 +8,12 @@ import { useCosmosTokens, useQueryIBCAssetList, } from "@/services/queries/ibc"; -import rawConnections from "./connections.json"; import type { IConnection } from "./BridgeCard"; import { useMemo } from "react"; +import { IBC_CONNECTIONS } from "@/config/chain"; export default function BridgeDashboard() { - const connections = rawConnections as IConnection[]; + const connections = IBC_CONNECTIONS as IConnection[]; const { theme } = useTheme(); const { data: walletClient } = useWalletClient(); diff --git a/src/components/Bridge/connections.json b/src/components/Bridge/connections.json deleted file mode 100644 index 48e9444..0000000 --- a/src/components/Bridge/connections.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "id": "cosmoshub", - "name": "Cosmos Hub Testnet", - "iconUrl": "/images/cosmos-logo.png", - "chainId": "provider", - "status": "active", - "channel": "channel-3", - "prefix": "cosmos", - "explorer": "https://explorer.polypore.xyz/provider" - } -] diff --git a/src/config/chain.ts b/src/config/chain.ts index 0f4ab21..02bfd34 100644 --- a/src/config/chain.ts +++ b/src/config/chain.ts @@ -15,3 +15,16 @@ export const TESTNET_ORO_EVM = kiiEvm.TESTNET_ORO_EVM; export const KIICHAIN_BASE_DENOM = kiiEvm.KIICHAIN_BASE_DENOM; export const KIICHAIN_SYMBOL = kiiEvm.TESTNET_ORO_EVM.nativeCurrency.symbol; export const KIICHAIN_ORO_DENOM = kiiEvm.ORO_DENOM; + +export const IBC_CONNECTIONS = [ + { + id: "cosmoshub", + name: "Cosmos Hub Testnet", + iconUrl: "/images/cosmos-logo.png", + chainId: "provider", + status: "active", + channel: "channel-3", + prefix: "cosmos", + explorer: "https://explorer.polypore.xyz/provider", + }, +];