diff --git a/.github/workflows/deploy-wallet-at-merge-to-main.yml b/.github/workflows/deploy-wallet-at-merge-to-main.yml index ba42c97d7..6c9c0065b 100644 --- a/.github/workflows/deploy-wallet-at-merge-to-main.yml +++ b/.github/workflows/deploy-wallet-at-merge-to-main.yml @@ -4,7 +4,7 @@ on: branches: - main paths: - - "apps/namada-interface/**" + - "apps/namadillo/**" - ".github/workflows/**" env: CI: false @@ -32,7 +32,7 @@ jobs: version: "v0.10.3" - name: build the site - working-directory: ./apps/namada-interface + working-directory: ./apps/namadillo run: yarn build env: NAMADA_INTERFACE_NAMADA_ALIAS: "Namada Devnet" @@ -43,7 +43,7 @@ jobs: if: false uses: nwtgck/actions-netlify@v1.2.3 with: - publish-dir: "./apps/namada-interface/build" + publish-dir: "./apps/namadillo/dist" production-branch: main github-token: ${{ secrets.GITHUB_TOKEN }} deploy-message: "Merged PR ${{ github.event.number }} to main" diff --git a/.github/workflows/deploy-wallet-at-pr.yml b/.github/workflows/deploy-wallet-at-pr.yml index 60288fd31..6dcc7eea7 100644 --- a/.github/workflows/deploy-wallet-at-pr.yml +++ b/.github/workflows/deploy-wallet-at-pr.yml @@ -2,7 +2,7 @@ name: Deploy wallet preview to netlify at PR and pushes to it on: pull_request: paths: - - "apps/namada-interface/**" + - "apps/namadillo/**" - "apps/extension/**" - "packages/**" - ".github/workflows/**" @@ -134,7 +134,7 @@ jobs: run: rustup target add wasm32-unknown-unknown - name: build the site - working-directory: ./apps/namada-interface + working-directory: ./apps/namadillo run: yarn build env: NAMADA_INTERFACE_NAMADA_ALIAS: "Namada Devnet" @@ -143,7 +143,7 @@ jobs: - name: Deploy to Netlify uses: nwtgck/actions-netlify@v1.2.3 with: - publish-dir: "./apps/namada-interface/build" + publish-dir: "./apps/namadillo/dist" production-branch: main github-token: ${{ secrets.GITHUB_TOKEN }} deploy-message: "deploy ${{ github.event.number }} at creating a PR" @@ -240,13 +240,12 @@ jobs: path: ./apps/extension/build/firefox/artifact/* E2E-tests: - needs: - [build-interface, build-extension-chrome] + needs: [build-interface, build-extension-chrome] timeout-minutes: 60 runs-on: ubuntu-latest defaults: run: - working-directory: ./apps/namada-interface + working-directory: ./apps/namadillo steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/release-wallet.yml b/.github/workflows/release-wallet.yml index 19863d861..341aeead4 100644 --- a/.github/workflows/release-wallet.yml +++ b/.github/workflows/release-wallet.yml @@ -83,7 +83,7 @@ jobs: run: sudo apt-get install -y protobuf-compiler - name: Build the interface - working-directory: ./apps/namada-interface + working-directory: ./apps/namadillo run: yarn build env: NAMADA_INTERFACE_NAMADA_ALIAS: ${{ inputs.NAMADA_INTERFACE_NAMADA_ALIAS }} @@ -99,8 +99,8 @@ jobs: - uses: actions/upload-artifact@v3 with: - name: namada-interface - path: ./apps/namada-interface/build + name: namadillo + path: ./apps/namadillo/dist build-extension-chrome: needs: setup @@ -196,8 +196,8 @@ jobs: - name: Download interface build uses: actions/download-artifact@v3 with: - name: namada-interface - path: ./namada-interface + name: namadillo + path: ./namadillo - name: Download Chrome extension build uses: actions/download-artifact@v3 @@ -220,7 +220,7 @@ jobs: - name: Deploy interface to Netlify uses: nwtgck/actions-netlify@v1.2.3 with: - publish-dir: ./namada-interface + publish-dir: ./namadillo production-branch: main github-token: ${{ secrets.GITHUB_TOKEN }} deploy-message: "Deployed release ${{ needs.setup.outputs.VERSION }}" diff --git a/apps/extension/package.json b/apps/extension/package.json index 2e67dac5d..aab31ed2e 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -48,8 +48,8 @@ "framer-motion": "6.2.4", "io-ts": "^2.2.21", "js-sha256": "^0.10.1", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-icons": "^4.12.0", "react-router-dom": "^6.0.0", "typescript": "^5.1.3", diff --git a/apps/extension/src/App/Accounts/AddAccount.tsx b/apps/extension/src/App/Accounts/AddAccount.tsx index f54407dd6..d4c4c0d06 100644 --- a/apps/extension/src/App/Accounts/AddAccount.tsx +++ b/apps/extension/src/App/Accounts/AddAccount.tsx @@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom"; import { Ledger, makeBip44Path } from "@heliax/namada-sdk/web"; import { chains } from "@namada/chains"; -import { ActionButton, Input, Toggle } from "@namada/components"; +import { ActionButton, Input, ToggleButton } from "@namada/components"; import { AccountType, DerivedAccount } from "@namada/types"; import { TopLevelRoute } from "App/types"; @@ -371,9 +371,9 @@ const AddAccount: React.FC = ({ {parentAccountType !== AccountType.Ledger && (
- Transparent  - setIsTransparent(!isTransparent)} + setIsTransparent(!isTransparent)} checked={isTransparent} />  Shielded diff --git a/apps/namada-interface/package.json b/apps/namada-interface/package.json index c2dd1ab7f..365ddbcc6 100644 --- a/apps/namada-interface/package.json +++ b/apps/namada-interface/package.json @@ -1,7 +1,7 @@ { "name": "@namada/namada-interface", "version": "0.2.1", - "description": "Namada Browser Extension", + "description": "Namada Interface", "repository": "https://github.com/anoma/namada-interface/", "author": "Heliax Dev ", "license": "MIT", @@ -48,9 +48,6 @@ "dev:local": "NODE_ENV=development NAMADA_INTERFACE_LOCAL=\"true\" yarn dev", "dev:proxy": "NAMADA_INTERFACE_PROXY=true && ./scripts/start-proxies.sh && yarn dev:local", "build": "NODE_ENV=production && yarn wasm:build && webpack-cli", - "lint": "eslint src --ext .ts,.tsx", - "lint:fix": "yarn lint -- --fix", - "lint:ci": "yarn lint --max-warnings 0", "test": "yarn wasm:build:test && yarn jest", "test:watch": "yarn wasm:build:test && yarn jest --watchAll=true", "test:coverage": "yarn wasm:build:test && yarn test --coverage", diff --git a/apps/namada-interface/src/slices/settings.ts b/apps/namada-interface/src/slices/settings.ts index 090be08d0..a12f8e646 100644 --- a/apps/namada-interface/src/slices/settings.ts +++ b/apps/namada-interface/src/slices/settings.ts @@ -1,40 +1,61 @@ import { ChainKey } from "@namada/types"; -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; - -import { atom } from "jotai"; - -const SETTINGS_ACTIONS_BASE = "settings"; - -export type SettingsState = { - connectedChains: string[]; +import { CurrencyType } from "@namada/utils"; +import { Getter, Setter, atom } from "jotai"; +import { atomWithStorage } from "jotai/utils"; + +type SettingsStorage = { + fiat: CurrencyType; + hideBalances: boolean; + rpcUrl: string; + chainId: string; }; -const initialState: SettingsState = { - connectedChains: [], -}; - -const settingsSlice = createSlice({ - name: SETTINGS_ACTIONS_BASE, - initialState, - reducers: { - setIsConnected: (state, action: PayloadAction) => { - state.connectedChains = state.connectedChains.includes(action.payload) - ? state.connectedChains - : [...state.connectedChains, action.payload]; - }, - }, +export const namadaExtensionConnectedAtom = atom(false); + +export const namadilloSettingsAtom = atomWithStorage( + "namadillo:settings", + { + fiat: "usd", + hideBalances: false, + rpcUrl: process.env.NAMADA_INTERFACE_NAMADA_URL || "", + chainId: process.env.NAMADA_INTERFACE_NAMADA_CHAIN_ID || "", + } +); + +const changeSettings = + (key: keyof SettingsStorage) => + (get: Getter, set: Setter, value: T) => { + const settings = get(namadilloSettingsAtom); + set(namadilloSettingsAtom, { ...settings, [key]: value }); + }; + +export const selectedCurrencyAtom = atom( + (get) => get(namadilloSettingsAtom).fiat, + changeSettings("fiat") +); + +export const hideBalancesAtom = atom( + (get) => get(namadilloSettingsAtom).hideBalances, + changeSettings("hideBalances") +); + +export const rpcUrlAtom = atom( + (get) => get(namadilloSettingsAtom).rpcUrl, + changeSettings("rpcUrl") +); + +export const chainIdAtom = atom( + (get) => get(namadilloSettingsAtom).chainId, + changeSettings("chainId") +); + +export const connectedChainsAtom = atom([]); +export const addConnectedChainAtom = atom(null, (get, set, chain: ChainKey) => { + const connectedChains = get(connectedChainsAtom); + set( + connectedChainsAtom, + connectedChains.includes(chain) ? connectedChains : ( + [...connectedChains, chain] + ) + ); }); - -const { actions, reducer } = settingsSlice; - -export const { setIsConnected } = actions; - -export default reducer; - -//////////////////////////////////////////////////////////////////////////////// -// JOTAI -//////////////////////////////////////////////////////////////////////////////// - -const namadaExtensionConnectedAtom = atom(false); - -export { namadaExtensionConnectedAtom }; diff --git a/apps/namada-interface/src/types/environment.d.ts b/apps/namada-interface/src/types/environment.d.ts index f84eb7753..62f6b3009 100644 --- a/apps/namada-interface/src/types/environment.d.ts +++ b/apps/namada-interface/src/types/environment.d.ts @@ -26,5 +26,3 @@ declare global { } } } - -export { }; diff --git a/apps/namadillo/.env.sample b/apps/namadillo/.env.sample new file mode 100644 index 000000000..8c878755c --- /dev/null +++ b/apps/namadillo/.env.sample @@ -0,0 +1,10 @@ +# Specify the following if you wish to override the defaults defined in @anoma/chains: + +# NAMADA +NAMADA_INTERFACE_NAMADA_ALIAS=Namada Testnet +NAMADA_INTERFACE_NAMADA_TOKEN=tnam1qxvg64psvhwumv3mwrrjfcz0h3t3274hwggyzcee +NAMADA_INTERFACE_NAMADA_CHAIN_ID=namada-1.5.32ccad5356012a7 +NAMADA_INTERFACE_NAMADA_URL=http://127.0.0.1:26657 +NAMADA_INTERFACE_NAMADA_BECH32_PREFIX=atest +NAMADA_INTERFACE_NO_INDEXER=true + diff --git a/apps/namadillo/.eslintignore b/apps/namadillo/.eslintignore new file mode 100644 index 000000000..06d792df7 --- /dev/null +++ b/apps/namadillo/.eslintignore @@ -0,0 +1,2 @@ +*.css +*.svg diff --git a/apps/namadillo/.eslintrc.cjs b/apps/namadillo/.eslintrc.cjs new file mode 100644 index 000000000..5809ec666 --- /dev/null +++ b/apps/namadillo/.eslintrc.cjs @@ -0,0 +1,3 @@ +module.exports = { + extends: require.resolve("@namada/config/eslint/react.js"), +}; diff --git a/apps/namadillo/.gitignore b/apps/namadillo/.gitignore new file mode 100644 index 000000000..320fb3dfb --- /dev/null +++ b/apps/namadillo/.gitignore @@ -0,0 +1,10 @@ +# dependencies +/node_modules +/.pnp +.pnp.js +/build +/dist +.env +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/apps/namadillo/.release-it.cjs b/apps/namadillo/.release-it.cjs new file mode 100644 index 000000000..9261789f6 --- /dev/null +++ b/apps/namadillo/.release-it.cjs @@ -0,0 +1,7 @@ +const baseConfig = require("../../.release-it.base.cjs"); + +const config = { + ...baseConfig, +}; + +module.exports = config; diff --git a/apps/namadillo/.vscode/settings.json b/apps/namadillo/.vscode/settings.json new file mode 100644 index 000000000..7ef664f1d --- /dev/null +++ b/apps/namadillo/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "workbench.colorCustomizations": { + "[monokai]": { + "symbolIcon.namespaceForeground": "#ff0000" + }, + "activityBar.background": "#007d3b", + "activityBar.activeForeground": "1e5388", + "titleBar.activeBackground": "#007d3b", + "titleBar.activeForeground": "#fff", + "activityBar.foreground": "#fff", + }, + "window.title": "anoma-cli" +} \ No newline at end of file diff --git a/apps/namadillo/README.md b/apps/namadillo/README.md new file mode 100644 index 000000000..fc012e485 --- /dev/null +++ b/apps/namadillo/README.md @@ -0,0 +1,68 @@ +# Namadillo + +This is the React app for `namadillo`, the web client which integrates with the Namada `extension`. + +## Table of Contents + +- [Usage](#usage) +- [Configuration](#configuration) + +### Usage + +```bash +# Install dependencies +yarn + +# Build wasm-dependencies (for using SDK Query) +yarn wasm:build + +# Build wasm-dependencies with debugging enabled +yarn wasm:build:dev + +# Start app in development mode +yarn dev:local + +# If you are running chains locally, it is recommended that you instead proxy RPC requests: +yarn dev:proxy + +# Build production release: +yarn build + +# Run ESLint +yarn lint + +# Run ESLint fix +yarn lint:fix + +# Run tests +yarn test +``` + +[ [Table of Contents](#table-of-contents) ] + +### Configuration + +Configuration is done by creating a `.env` file, based on [.env.sample](./.env.sample), and specifying the values you wish to override. + +The following is an example of configuring the interface and extension to connect to testnets: + +```bash +# NAMADA +NAMADA_INTERFACE_NAMADA_ALIAS=Namada Testnet +NAMADA_INTERFACE_NAMADA_CHAIN_ID=public-testnet-14.5d79b6958580 +NAMADA_INTERFACE_NAMADA_URL=https://proxy.heliax.click/public-testnet-14.5d79b6958580/ + +# COSMOS +NAMADA_INTERFACE_COSMOS_ALIAS=Cosmos Testnet +NAMADA_INTERFACE_COSMOS_CHAIN_ID=theta-testnet-001 +NAMADA_INTERFACE_COSMOS_URL=https://rpc.sentry-01.theta-testnet.polypore.xyz + +# ETH +NAMADA_INTERFACE_ETH_ALIAS=Eth Testnet +NAMADA_INTERFACE_ETH_CHAIN_ID=0x7A69 +NAMADA_INTERFACE_ETH_URL=https://rpc.ankr.com/eth_goerli +``` + +For more details on setting up your local environment for integration between the interface and the extension, see the [README.md](../../README.md) at the root of this repo. + +[ [Table of Contents](#table-of-contents) ] diff --git a/apps/namadillo/e2e-tests/hello.spec.ts b/apps/namadillo/e2e-tests/hello.spec.ts new file mode 100644 index 000000000..78373e16f --- /dev/null +++ b/apps/namadillo/e2e-tests/hello.spec.ts @@ -0,0 +1,5 @@ +import { test } from "@playwright/test"; + +test.skip("hello world", () => { + console.log("This is a placeholder test so CI is not complaining!"); +}); diff --git a/apps/namadillo/index.html b/apps/namadillo/index.html new file mode 100644 index 000000000..5522f7df4 --- /dev/null +++ b/apps/namadillo/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + Namadillo + + + + +
+ + + diff --git a/apps/namadillo/jest.config.ts b/apps/namadillo/jest.config.ts new file mode 100644 index 000000000..af4a6b323 --- /dev/null +++ b/apps/namadillo/jest.config.ts @@ -0,0 +1,10 @@ +import type { Config } from "@jest/types"; +// Sync object +const config: Config.InitialOptions = { + verbose: true, + transform: { + "^.+\\.tsx?$": "ts-jest", + }, + modulePathIgnorePatterns: ["e2e-tests"], +}; +export default config; diff --git a/apps/namadillo/package.json b/apps/namadillo/package.json new file mode 100644 index 000000000..c459e2657 --- /dev/null +++ b/apps/namadillo/package.json @@ -0,0 +1,114 @@ +{ + "name": "@namada/namadillo", + "version": "0.2.1", + "description": "Namadillo", + "repository": "https://github.com/anoma/namada-interface/", + "author": "Heliax Dev ", + "license": "MIT", + "private": true, + "dependencies": { + "@types/invariant": "^2.2.37", + "@types/react-paginate": "^7.1.2", + "bignumber.js": "^9.1.1", + "clsx": "^2.1.1", + "crypto-browserify": "^3.12.0", + "ethers": "^6.7.1", + "fp-ts": "^2.16.1", + "framer-motion": "^11.0.28", + "invariant": "^2.2.4", + "jotai": "^2.6.3", + "lodash.debounce": "^4.0.8", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "react-icons": "^5.1.0", + "react-paginate": "^8.2.0", + "react-router-dom": "^6.0.0", + "react-scripts": "5.0.1", + "styled-components": "^5.3.3", + "tailwind-merge": "^2.3.0", + "typescript": "^5.1.3", + "web-vitals": "^2.1.4" + }, + "scripts": { + "bump": "yarn workspace namada run bump --target apps/namadillo", + "release": "release-it --verbose --ci", + "release:dry-run": "release-it --verbose --dry-run --ci", + "release:no-npm": "release-it --verbose --no-npm.publish --ci", + "start:proxy": "node ./scripts/startProxies.js", + "dev": "vite", + "dev:local": "NODE_ENV=development NAMADA_INTERFACE_LOCAL=\"true\" yarn dev", + "dev:proxy": "NAMADA_INTERFACE_PROXY=true && ./scripts/start-proxies.sh && yarn dev:local", + "dev:old": "NODE_ENV=development webpack-dev-server", + "build": "NODE_ENV=production && yarn wasm:build && vite build", + "build:old": "NODE_ENV=production && yarn wasm:build && webpack-cli", + "lint": "eslint src --ext .ts,.tsx", + "lint:fix": "yarn lint -- --fix", + "lint:ci": "yarn lint --max-warnings 0", + "test": "yarn wasm:build:test && yarn jest", + "test:watch": "yarn wasm:build:test && yarn jest --watchAll=true", + "test:coverage": "yarn wasm:build:test && yarn test --coverage", + "test:ci": "jest", + "e2e-test": "PLAYWRIGHT_BASE_URL=http://localhost:3000 yarn playwright test", + "e2e-test:headed": "PLAYWRIGHT_BASE_URL=http://localhost:3000 yarn playwright test --project=chromium --headed", + "wasm:build": "node ./scripts/build.js --release", + "wasm:build:multicore": "node ./scripts/build.js --release --multicore", + "wasm:build:dev": "node ./scripts/build.js", + "wasm:build:dev:multicore": "node ./scripts/build.js --multicore", + "wasm:build:test": "./scripts/build-test.sh" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@playwright/test": "^1.24.1", + "@svgr/webpack": "^6.5.1", + "@testing-library/jest-dom": "^5.16.2", + "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^29.4.0", + "@types/lodash.debounce": "^4.0.9", + "@types/node": "^16.11.25", + "@types/react": "^17.0.39", + "@types/react-dom": "^17.0.11", + "@types/styled-components": "^5.1.22", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.16", + "css-loader": "^6.7.3", + "dotenv": "^16.0.3", + "eslint": "^8.49.0", + "eslint-config-prettier": "^8.8.0", + "eslint-import-resolver-typescript": "^2.5.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-react": "^7.33.0", + "eslint-plugin-react-hooks": "^4.6.0", + "history": "^5.3.0", + "html-webpack-plugin": "^5.5.0", + "jest": "^29.4.1", + "jest-fetch-mock": "^3.0.3", + "local-cors-proxy": "^1.1.0", + "postcss": "^8.4.32", + "postcss-loader": "^7.3.3", + "style-loader": "^3.3.1", + "tailwindcss": "^3.4.0", + "ts-jest": "^29.0.5", + "ts-loader": "^9.4.2", + "ts-node": "^10.9.1", + "tsconfig-paths-webpack-plugin": "^4.1.0", + "typescript-plugin-styled-components": "^2.0.0", + "vite": "^5.2.11", + "vite-plugin-node-polyfills": "^0.22.0", + "vite-tsconfig-paths": "^4.3.2", + "webpack-bundle-analyzer": "^4.10.1", + "webpack-cli": "^5.0.1", + "webpack-dev-server": "^4.11.1" + } +} diff --git a/apps/namadillo/playwright.config.ts b/apps/namadillo/playwright.config.ts new file mode 100644 index 000000000..89c532231 --- /dev/null +++ b/apps/namadillo/playwright.config.ts @@ -0,0 +1,73 @@ +import type { PlaywrightTestConfig } from "@playwright/test"; +import { devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: "./e2e-tests", + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + }, + }, + + { + name: "firefox", + use: { + ...devices["Desktop Firefox"], + }, + }, + + { + name: "webkit", + use: { + ...devices["Desktop Safari"], + }, + }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // port: 3000, + // }, +}; + +export default config; diff --git a/apps/namadillo/postcss.config.cjs b/apps/namadillo/postcss.config.cjs new file mode 100644 index 000000000..6887c8262 --- /dev/null +++ b/apps/namadillo/postcss.config.cjs @@ -0,0 +1,8 @@ +module.exports = { + plugins: { + "postcss-import": {}, + "tailwindcss/nesting": {}, + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/apps/namadillo/public/_redirects b/apps/namadillo/public/_redirects new file mode 100644 index 000000000..f8243379a --- /dev/null +++ b/apps/namadillo/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 \ No newline at end of file diff --git a/apps/namadillo/public/apple-touch-icon.png b/apps/namadillo/public/apple-touch-icon.png new file mode 100644 index 000000000..2e7be2b2e Binary files /dev/null and b/apps/namadillo/public/apple-touch-icon.png differ diff --git a/apps/namadillo/public/favicon-16x16.png b/apps/namadillo/public/favicon-16x16.png new file mode 100644 index 000000000..7ad518403 Binary files /dev/null and b/apps/namadillo/public/favicon-16x16.png differ diff --git a/apps/namadillo/public/favicon-32x32.png b/apps/namadillo/public/favicon-32x32.png new file mode 100644 index 000000000..26885f3c0 Binary files /dev/null and b/apps/namadillo/public/favicon-32x32.png differ diff --git a/apps/namadillo/public/index.html b/apps/namadillo/public/index.html new file mode 100644 index 000000000..d791824e8 --- /dev/null +++ b/apps/namadillo/public/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + Namada Interface + + + + +
+ + diff --git a/apps/namadillo/public/manifest.json b/apps/namadillo/public/manifest.json new file mode 100644 index 000000000..6408efb19 --- /dev/null +++ b/apps/namadillo/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "Namadillo", + "name": "Namadillo", + "icons": [ + { + "src": "apple-touch-icon.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "favicon-16x16.png", + "type": "image/png", + "sizes": "16x16" + }, + { + "src": "favicon-32x32.png", + "type": "image/png", + "sizes": "32x32" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/apps/namadillo/public/robots.txt b/apps/namadillo/public/robots.txt new file mode 100644 index 000000000..e9e57dc4d --- /dev/null +++ b/apps/namadillo/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/apps/namadillo/scripts/build-test.sh b/apps/namadillo/scripts/build-test.sh new file mode 100755 index 000000000..4c5ab9a04 --- /dev/null +++ b/apps/namadillo/scripts/build-test.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +SCRIPT_DIR=$(cd "$(dirname "$0")"; pwd -P) +PACKAGES_PATH="../../../packages" + +cd "${SCRIPT_DIR}/${PACKAGES_PATH}/crypto" && yarn wasm:build:node:dev +cd "${SCRIPT_DIR}/${PACKAGES_PATH}/shared" && yarn wasm:build:node:dev diff --git a/apps/namadillo/scripts/build.js b/apps/namadillo/scripts/build.js new file mode 100644 index 000000000..65d2405ee --- /dev/null +++ b/apps/namadillo/scripts/build.js @@ -0,0 +1,19 @@ +const { spawnSync } = require("child_process"); + +const args = process.argv.filter((arg) => arg.match(/--\w+/)); +const strippedArgs = new Set(args.map((arg) => arg.replace("--", ""))); + +const isRelease = strippedArgs.has("release"); +const isMulticore = strippedArgs.has("multicore"); +const taskShared = `wasm:build${!isRelease ? ":dev" : ""}${ + isMulticore ? ":multicore" : "" +}`; +const taskCrypto = `wasm:build${!isRelease ? ":dev" : ""}`; + +spawnSync("yarn", ["workspace", "@namada/crypto", "run", taskCrypto], { + stdio: "inherit", +}); + +spawnSync("yarn", ["workspace", "@namada/shared", "run", taskShared], { + stdio: "inherit", +}); diff --git a/apps/namadillo/scripts/start-proxies.sh b/apps/namadillo/scripts/start-proxies.sh new file mode 100755 index 000000000..25bd0da52 --- /dev/null +++ b/apps/namadillo/scripts/start-proxies.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +SCRIPT_DIR=$(cd "$(dirname "$0")"; pwd -P) + +node $SCRIPT_DIR/startProxies.js& + diff --git a/apps/namadillo/scripts/startProxies.js b/apps/namadillo/scripts/startProxies.js new file mode 100644 index 000000000..361592dd2 --- /dev/null +++ b/apps/namadillo/scripts/startProxies.js @@ -0,0 +1,47 @@ +const { exec } = require("child_process"); +require("dotenv").config(); + +const { + NAMADA_INTERFACE_NAMADA_ALIAS = "Namada", + NAMADA_INTERFACE_NAMADA_URL, + NAMADA_INTERFACE_COSMOS_ALIAS = "Cosmos", + NAMADA_INTERFACE_COSMOS_URL, + NAMADA_INTERFACE_ETH_ALIAS = "Ethereum", + NAMADA_INTERFACE_ETH_URL, +} = process.env; + +const proxyConfigs = [ + { + alias: NAMADA_INTERFACE_NAMADA_ALIAS, + url: NAMADA_INTERFACE_NAMADA_URL, + proxyPort: 8010, + }, + { + alias: NAMADA_INTERFACE_COSMOS_ALIAS, + url: NAMADA_INTERFACE_COSMOS_URL, + proxyPort: 8011, + }, + { + alias: NAMADA_INTERFACE_ETH_ALIAS, + url: NAMADA_INTERFACE_ETH_URL, + proxyPort: 8012, + }, +]; + +proxyConfigs.forEach(({ alias, url, proxyPort }) => { + if (url) { + console.log(`Starting local-cors-proxy for ${alias}`); + console.log(`-> ${url} proxied to http://localhost:${proxyPort}/proxy\n`); + + exec( + `lcp --proxyUrl ${url} --port ${proxyPort}`, + (error, stdout, stderr) => { + console.log(stdout); + console.log(stderr); + if (error !== null) { + console.log(`exec error: ${error}`); + } + } + ); + } +}); diff --git a/apps/namadillo/src/App/AccountOverview/AccountOverview.tsx b/apps/namadillo/src/App/AccountOverview/AccountOverview.tsx new file mode 100644 index 000000000..9d64a1b93 --- /dev/null +++ b/apps/namadillo/src/App/AccountOverview/AccountOverview.tsx @@ -0,0 +1,122 @@ +import { + ActionButton, + Heading, + SkeletonLoading, + Stack, +} from "@namada/components"; +import { useUntilIntegrationAttached } from "@namada/integrations"; +import { FiatCurrency } from "App/Common/FiatCurrency"; +import { Intro } from "App/Common/Intro"; +import { NamCurrency } from "App/Common/NamCurrency"; +import { PageWithSidebar } from "App/Common/PageWithSidebar"; +import GovernanceRoutes from "App/Governance/routes"; +import MainnetRoadmap from "App/Sidebars/MainnetRoadmap"; +import StakingRoutes from "App/Staking/routes"; +import clsx from "clsx"; +import { useAtomValue } from "jotai"; +import { useNavigate } from "react-router-dom"; +import { defaultAccountAtom, totalNamBalanceAtom } from "slices/accounts"; +import { chainAtom } from "slices/chain"; + +export const AccountOverview = (): JSX.Element => { + const navigate = useNavigate(); + const chain = useAtomValue(chainAtom); + const account = useAtomValue(defaultAccountAtom); + const totalBalance = useAtomValue(totalNamBalanceAtom); + const extensionAttachStatus = useUntilIntegrationAttached(chain); + const currentExtensionAttachStatus = + extensionAttachStatus[chain.extension.id]; + + const hasExtensionInstalled = + currentExtensionAttachStatus === "attached" || + currentExtensionAttachStatus === "pending"; + + const isConnected = account !== undefined; + + return ( + +
+ {!isConnected && ( +
+ +
+ )} + + {isConnected && ( + + {totalBalance.isSuccess && ( +
+ + NAM Balance + + + +
+ )} + {totalBalance.isPending && ( + + )} +
+ + Stake & Vote Now + + + navigate(StakingRoutes.overview().url)} + size="sm" + color="primary" + borderRadius="sm" + > + Stake + + navigate(GovernanceRoutes.index())} + color="primary" + outlined + className="uppercase hover:text-rblack before:border before:border-yellow" + borderRadius="sm" + hoverColor="primary" + > + Governance + + +
+
+ )} +
+ {isConnected && ( + + )} +
+ ); +}; diff --git a/apps/namadillo/src/App/AccountOverview/index.ts b/apps/namadillo/src/App/AccountOverview/index.ts new file mode 100644 index 000000000..6fbc7b631 --- /dev/null +++ b/apps/namadillo/src/App/AccountOverview/index.ts @@ -0,0 +1 @@ +export { AccountOverview } from "./AccountOverview"; diff --git a/apps/namadillo/src/App/App.tsx b/apps/namadillo/src/App/App.tsx new file mode 100644 index 000000000..df9f8b0a1 --- /dev/null +++ b/apps/namadillo/src/App/App.tsx @@ -0,0 +1,48 @@ +import { useUntilIntegrationAttached } from "@namada/integrations"; +import { Container } from "App/Common/Container"; +import { Toasts } from "App/Common/Toast"; +import { TopNavigation } from "App/Common/TopNavigation"; +import { AnimatePresence } from "framer-motion"; +import { createBrowserHistory } from "history"; +import { useOnChainChanged } from "hooks/useOnChainChanged"; +import { useOnNamadaExtensionAttached } from "hooks/useOnNamadaExtensionAttached"; +import { useOnNamadaExtensionConnected } from "hooks/useOnNamadaExtensionConnected"; +import { useTransactionService } from "hooks/useTransactionService"; +import { useAtomValue } from "jotai"; +import { Outlet } from "react-router-dom"; +import { chainAtom } from "slices/chain"; +import { Navigation } from "./Common/Navigation"; + +export const history = createBrowserHistory({ window }); + +export function App(): JSX.Element { + useOnNamadaExtensionAttached(); + useOnNamadaExtensionConnected(); + useOnChainChanged(); + useTransactionService(); + + const chain = useAtomValue(chainAtom); + const extensionAttachStatus = useUntilIntegrationAttached(chain); + const currentExtensionAttachStatus = + extensionAttachStatus[chain.extension.id]; + const extensionReady = + currentExtensionAttachStatus === "attached" || + currentExtensionAttachStatus === "detached"; + + return ( + <> + + {extensionReady && ( + } + header={} + > + + + + + )} + + ); +} diff --git a/apps/namadillo/src/App/AppRoutes.tsx b/apps/namadillo/src/App/AppRoutes.tsx new file mode 100644 index 000000000..6293be1d5 --- /dev/null +++ b/apps/namadillo/src/App/AppRoutes.tsx @@ -0,0 +1,48 @@ +import { Router } from "@remix-run/router"; +import { + Route, + createBrowserRouter, + createRoutesFromElements, +} from "react-router-dom"; +import { AccountOverview } from "./AccountOverview"; +import { App } from "./App"; +import { AnimatedTransition } from "./Common/AnimatedTransition"; +import { Governance } from "./Governance"; +import { Staking } from "./Staking"; + +import GovernanceRoutes from "./Governance/routes"; +import StakingRoutes from "./Staking/routes"; + +export const getRouter = (): Router => { + return createBrowserRouter( + createRoutesFromElements( + }> + + + + } + /> + + + + } + /> + + + + } + /> + + ) + ); +}; diff --git a/apps/namadillo/src/App/Common/ActiveAccount.tsx b/apps/namadillo/src/App/Common/ActiveAccount.tsx new file mode 100644 index 000000000..be0aeba3f --- /dev/null +++ b/apps/namadillo/src/App/Common/ActiveAccount.tsx @@ -0,0 +1,32 @@ +import { CopyToClipboardControl } from "@namada/components"; +import clsx from "clsx"; +import { useAtomValue } from "jotai"; +import { defaultAccountAtom } from "slices/accounts"; + +export const ActiveAccount = (): JSX.Element => { + const account = useAtomValue(defaultAccountAtom); + + if (!account) { + return <>; + } + + return ( +
+ + + + {account.alias} + + + +
+ ); +}; diff --git a/apps/namadillo/src/App/Common/AnimatedTransition.tsx b/apps/namadillo/src/App/Common/AnimatedTransition.tsx new file mode 100644 index 000000000..6ddba8964 --- /dev/null +++ b/apps/namadillo/src/App/Common/AnimatedTransition.tsx @@ -0,0 +1,20 @@ +import { motion } from "framer-motion"; + +export const AnimatedTransition = (props: { + children: React.ReactNode; + elementKey: string; +}): JSX.Element => { + const { children, elementKey } = props; + return ( + + {children} + + ); +}; diff --git a/apps/namadillo/src/App/Common/BurgerButton.tsx b/apps/namadillo/src/App/Common/BurgerButton.tsx new file mode 100644 index 000000000..1e9975880 --- /dev/null +++ b/apps/namadillo/src/App/Common/BurgerButton.tsx @@ -0,0 +1,61 @@ +import clsx from "clsx"; +import { useEffect, useRef } from "react"; +import { useLocation } from "react-router-dom"; +type BurgerButtonProps = { + open: boolean; + onClick: () => void; +}; + +export const BurgerButton = ({ + open, + onClick, +}: BurgerButtonProps): JSX.Element => { + const buttonRef = useRef(null); + const location = useLocation(); + + useEffect(() => { + if (open) { + const toggle = (): void => onClick(); + document.addEventListener("click", toggle); + return () => document.removeEventListener("click", toggle); + } + }, [open]); + + useEffect(() => { + if (open) { + onClick(); + } + }, [location]); + + return ( + + ); +}; diff --git a/apps/namadillo/src/App/Common/ConnectBanner.tsx b/apps/namadillo/src/App/Common/ConnectBanner.tsx new file mode 100644 index 000000000..bfe3efbc1 --- /dev/null +++ b/apps/namadillo/src/App/Common/ConnectBanner.tsx @@ -0,0 +1,20 @@ +import { Panel } from "@namada/components"; +import { ConnectExtensionButton } from "App/Common/ConnectExtensionButton"; +import { useAtomValue } from "jotai"; +import { chainAtom } from "slices/chain"; + +type ConnectBannerProps = { + text: string; +}; + +export const ConnectBanner = ({ text }: ConnectBannerProps): JSX.Element => { + const chain = useAtomValue(chainAtom); + return ( + +
+
{text}
+ +
+
+ ); +}; diff --git a/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx b/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx new file mode 100644 index 000000000..d6ccd8c53 --- /dev/null +++ b/apps/namadillo/src/App/Common/ConnectExtensionButton.tsx @@ -0,0 +1,55 @@ +import { ActionButton } from "@namada/components"; +import { useUntilIntegrationAttached } from "@namada/integrations"; +import { Chain } from "@namada/types"; +import { ConnectStatus, useExtensionConnect } from "hooks/useExtensionConnect"; + +type Props = { + chain: Chain; +}; + +export const ConnectExtensionButton = ({ chain }: Props): JSX.Element => { + const extensionAttachStatus = useUntilIntegrationAttached(chain); + const { connect, connectionStatus } = useExtensionConnect(chain); + + const currentExtensionAttachStatus = + extensionAttachStatus[chain.extension.id]; + + const hasExtensionInstalled = + currentExtensionAttachStatus === "attached" || + currentExtensionAttachStatus === "pending"; + + const handleDownloadExtension = (): void => { + window.open( + "https://namada.net/extension", + "_blank", + "noopener,noreferrer" + ); + }; + + return ( + <> + {hasExtensionInstalled && + connectionStatus !== ConnectStatus.CONNECTED && ( + + Connect Extension + + )} + + {!hasExtensionInstalled && ( + handleDownloadExtension()} + color="primary" + size="sm" + borderRadius="sm" + > + Download Extension + + )} + + ); +}; diff --git a/apps/namadillo/src/App/Common/Container.tsx b/apps/namadillo/src/App/Common/Container.tsx new file mode 100644 index 000000000..9c7b2657b --- /dev/null +++ b/apps/namadillo/src/App/Common/Container.tsx @@ -0,0 +1,60 @@ +import clsx from "clsx"; +import { useState } from "react"; +import { BurgerButton } from "./BurgerButton"; + +type ContainerProps = { + header: JSX.Element; + navigation: JSX.Element; + children: JSX.Element; +} & React.ComponentPropsWithoutRef<"div">; + +export const Container = ({ + header, + navigation, + children, + ...props +}: ContainerProps): JSX.Element => { + const [displayNavigation, setDisplayNavigation] = useState(false); + + return ( +
+
+ + + Namadillo + +
+ {header} + + setDisplayNavigation(!displayNavigation)} + /> + +
+
+
+ +
{children}
+
+
+ ); +}; diff --git a/apps/namadillo/src/App/Common/CurrencySelector.tsx b/apps/namadillo/src/App/Common/CurrencySelector.tsx new file mode 100644 index 000000000..12af835ea --- /dev/null +++ b/apps/namadillo/src/App/Common/CurrencySelector.tsx @@ -0,0 +1,51 @@ +import { StyledSelectBox } from "@namada/components"; +import { CurrencyInfoListItem } from "@namada/utils"; +import clsx from "clsx"; + +type CurrencySelectorProps = { + value: string; + onChange: (value: string) => void; + currencies: CurrencyInfoListItem[]; +}; + +export const CurrencySelector = ({ + value, + onChange, + currencies, +}: CurrencySelectorProps): JSX.Element => { + const getCurrencySymbol = (symbol: string): React.ReactNode => ( + + {symbol} + + ); + + return ( +
+ onChange(e.target.value)} + options={currencies.map((currency) => ({ + id: currency.id, + value: ( + <> + {getCurrencySymbol(currency.sign)}{" "} + {currency.plural} + + ), + ariaLabel: currency.plural, + }))} + /> +
+ ); +}; diff --git a/apps/namadillo/src/App/Common/FiatCurrency.tsx b/apps/namadillo/src/App/Common/FiatCurrency.tsx new file mode 100644 index 000000000..21a8c93e5 --- /dev/null +++ b/apps/namadillo/src/App/Common/FiatCurrency.tsx @@ -0,0 +1,29 @@ +import { Currency, CurrencyProps } from "@namada/components"; +import BigNumber from "bignumber.js"; +import { useAtomValue } from "jotai"; +import { selectedCurrencyRateAtom } from "slices/exchangeRates"; +import { hideBalancesAtom, selectedCurrencyAtom } from "slices/settings"; + +type FiatCurrencyProps = { + amountInNam: BigNumber; +} & Omit< + CurrencyProps, + "amount" | "currency" | "currencyPosition" | "spaceAroundSign" +>; + +export const FiatCurrency = ({ + amountInNam, + ...props +}: FiatCurrencyProps): JSX.Element => { + const hideBalances = useAtomValue(hideBalancesAtom); + const selectedFiatCurrency = useAtomValue(selectedCurrencyAtom); + const selectedCurrencyRate = useAtomValue(selectedCurrencyRateAtom); + return ( + + ); +}; diff --git a/apps/namadillo/src/App/Common/FormattedPaginator.module.css b/apps/namadillo/src/App/Common/FormattedPaginator.module.css new file mode 100644 index 000000000..85b9f50c2 --- /dev/null +++ b/apps/namadillo/src/App/Common/FormattedPaginator.module.css @@ -0,0 +1,47 @@ +.paginator { + @apply flex justify-center uppercase items-center py-2 select-none text-xs; + + :global .disabled { + @apply pointer-events-none opacity-50; + } + + :global li { + } + + :global .previous { + @apply mr-1.5; + } + + :global .next { + @apply ml-1.5; + } + + :global .next a, + :global .previous a { + @apply w-6 h-6 rounded-full flex items-center justify-center bg-neutral-800; + } + + :global li:not(.next, .previous) { + @apply bg-neutral-800 flex; + + :global a { + @apply px-1.5 h-6 flex items-center justify-center; + } + } + + :global a:hover { + @apply hover:text-cyan; + } + + :global li:nth-child(2) { + @apply rounded-s-lg pl-2; + } + + :global li:nth-last-child(2) { + @apply rounded-e-lg pr-2; + } + + :global .selected { + @apply text-yellow; + } +} diff --git a/apps/namadillo/src/App/Common/FormattedPaginator.tsx b/apps/namadillo/src/App/Common/FormattedPaginator.tsx new file mode 100644 index 000000000..675eadc1d --- /dev/null +++ b/apps/namadillo/src/App/Common/FormattedPaginator.tsx @@ -0,0 +1,23 @@ +import clsx from "clsx"; +import { FaChevronLeft, FaChevronRight } from "react-icons/fa6"; +import ReactPaginate, { ReactPaginateProps } from "react-paginate"; +import styles from "./FormattedPaginator.module.css"; + +type FormattedPaginatorProps = ReactPaginateProps; + +export const FormattedPaginator = ( + props: FormattedPaginatorProps +): JSX.Element => { + return ( + } + previousLabel={} + renderOnZeroPageCount={null} + {...props} + /> + ); +}; + +export default FormattedPaginator; diff --git a/apps/namadillo/src/App/Common/Info.tsx b/apps/namadillo/src/App/Common/Info.tsx new file mode 100644 index 000000000..40352f1e5 --- /dev/null +++ b/apps/namadillo/src/App/Common/Info.tsx @@ -0,0 +1,27 @@ +import clsx from "clsx"; +import React from "react"; +import { GoInfo } from "react-icons/go"; +import { twMerge } from "tailwind-merge"; + +type InfoProps = React.ComponentPropsWithRef<"span">; + +export const Info = (props: InfoProps): JSX.Element => { + const { className, children, ...rest } = props; + return ( + + + + + ); +}; diff --git a/apps/namadillo/src/App/Common/Intro.tsx b/apps/namadillo/src/App/Common/Intro.tsx new file mode 100644 index 000000000..08af4335f --- /dev/null +++ b/apps/namadillo/src/App/Common/Intro.tsx @@ -0,0 +1,32 @@ +import { ActionButton, Image } from "@namada/components"; +import { Chain } from "@namada/types"; +import { ConnectExtensionButton } from "App/Common/ConnectExtensionButton"; +import clsx from "clsx"; + +type IntroProps = { + chain: Chain; + hasExtensionInstalled: boolean; +}; + +export const Intro = ({ chain }: IntroProps): JSX.Element => { + return ( +
+
+ +

+ Your Gateway to Shielded Multichain +

+
+
+ + + Help + +
+
+ ); +}; diff --git a/apps/namadillo/src/App/Common/ModalContainer.tsx b/apps/namadillo/src/App/Common/ModalContainer.tsx new file mode 100644 index 000000000..1bd085589 --- /dev/null +++ b/apps/namadillo/src/App/Common/ModalContainer.tsx @@ -0,0 +1,28 @@ +import { IoClose } from "react-icons/io5"; + +type ModalContainerProps = { + header: React.ReactNode; + onClose: () => void; + children: React.ReactNode; +}; + +export const ModalContainer = ({ + header, + onClose, + children, +}: ModalContainerProps): JSX.Element => { + return ( +
+ + + +
+ {header} +
+
{children}
+
+ ); +}; diff --git a/apps/namadillo/src/App/Common/NamCurrency.tsx b/apps/namadillo/src/App/Common/NamCurrency.tsx new file mode 100644 index 000000000..cd1ca5cc6 --- /dev/null +++ b/apps/namadillo/src/App/Common/NamCurrency.tsx @@ -0,0 +1,24 @@ +import { Currency, CurrencyProps } from "@namada/components"; +import { useAtomValue } from "jotai"; +import { hideBalancesAtom } from "slices/settings"; + +type NamCurrencyProps = Omit< + CurrencyProps, + "currency" | "currencyPosition" | "spaceAroundSign" +> & { forceBalanceDisplay?: boolean }; + +export const NamCurrency = ({ + forceBalanceDisplay = false, + ...props +}: NamCurrencyProps): JSX.Element => { + const hideBalances = useAtomValue(hideBalancesAtom); + return ( + + ); +}; diff --git a/apps/namadillo/src/App/Common/Navigation.tsx b/apps/namadillo/src/App/Common/Navigation.tsx new file mode 100644 index 000000000..2f1ceafcc --- /dev/null +++ b/apps/namadillo/src/App/Common/Navigation.tsx @@ -0,0 +1,87 @@ +import { SidebarMenuItem } from "App/Common/SidebarMenuItem"; +import GovernanceRoutes from "App/Governance/routes"; +import { MASPIcon } from "App/Icons/MASPIcon"; +import { SwapIcon } from "App/Icons/SwapIcon"; +import { AiFillHome } from "react-icons/ai"; +import { BsDiscord, BsTwitterX } from "react-icons/bs"; +import { FaVoteYea } from "react-icons/fa"; +import { GoStack } from "react-icons/go"; +import { IoSwapHorizontal } from "react-icons/io5"; +import { DISCORD_URL, TWITTER_URL } from "urls"; + +import StakingRoutes from "App/Staking/routes"; + +export const Navigation = (): JSX.Element => { + return ( +
+
    +
  • + + + Overview + +
  • +
  • + + + Staking + +
  • +
  • + + + Governance + +
  • +
  • + + + Transfer + +
  • +
  • + + + + + MASP + +
  • +
  • + + + + + Swap + +
  • +
+ +
+ ); +}; diff --git a/apps/namadillo/src/App/Common/PageWithSidebar.tsx b/apps/namadillo/src/App/Common/PageWithSidebar.tsx new file mode 100644 index 000000000..2ba3c86f0 --- /dev/null +++ b/apps/namadillo/src/App/Common/PageWithSidebar.tsx @@ -0,0 +1,11 @@ +type PageWithSidebar = { + children: React.ReactNode; +}; + +export const PageWithSidebar = ({ children }: PageWithSidebar): JSX.Element => { + return ( +
+ {children} +
+ ); +}; diff --git a/apps/namadillo/src/App/Common/Search.tsx b/apps/namadillo/src/App/Common/Search.tsx new file mode 100644 index 000000000..6d670d2c4 --- /dev/null +++ b/apps/namadillo/src/App/Common/Search.tsx @@ -0,0 +1,52 @@ +import { Input } from "@namada/components"; +import clsx from "clsx"; +import debounce from "lodash.debounce"; +import { useRef, useState } from "react"; +import { FaSearch } from "react-icons/fa"; +import { twMerge } from "tailwind-merge"; + +type SearchProps = Omit< + React.ComponentProps, + "onChange" | "onFocus" | "onBlur" | "type" +> & { + onChange?: (search: string) => void; +}; + +export const Search = ({ + onChange, + className, + ...rest +}: SearchProps): JSX.Element => { + const [displaySearchIcon, setDisplaySearchIcon] = useState(true); + const debouncedSearch = useRef( + debounce((value: string) => onChange?.(value), 300) + ); + + return ( +
+ + + + debouncedSearch.current(e.target.value)} + className={twMerge( + "w-full [&_input]:text-sm [&_input]:text-neutral-400 [&_input]:border-neutral-400 [&_input]:py-[11px]", + "[&_input]:pl-4 [&_input]:rounded-sm [&_input]:placeholder:text-center", + "[&_input]:placeholder:text-neutral-400/50 [&_input]:placeholder:text-left [&_input]:placeholder:pl-6", + className + )} + onFocus={() => setDisplaySearchIcon(false)} + onBlur={(e) => + e.target.value.length === 0 && setDisplaySearchIcon(true) + } + {...rest} + /> +
+ ); +}; diff --git a/apps/namadillo/src/App/Common/SidebarMenuItem.tsx b/apps/namadillo/src/App/Common/SidebarMenuItem.tsx new file mode 100644 index 000000000..aa080184f --- /dev/null +++ b/apps/namadillo/src/App/Common/SidebarMenuItem.tsx @@ -0,0 +1,34 @@ +import clsx from "clsx"; +import { NavLink } from "react-router-dom"; + +type Props = { + url?: string; + children: React.ReactNode; +}; + +export const SidebarMenuItem = ({ url, children }: Props): JSX.Element => { + const className = clsx( + "flex items-center gap-5 text-lg text-white", + "transition-colors duration-300 ease-out-quad hover:text-cyan", + { + "!text-neutral-500 pointer-events-none select-none": !url, + } + ); + + if (!url) { + return {children}; + } + + return ( + + clsx(className, { + "text-yellow font-bold": isActive, + }) + } + > + {children} + + ); +}; diff --git a/apps/namadillo/src/App/Common/TableRowLoading.tsx b/apps/namadillo/src/App/Common/TableRowLoading.tsx new file mode 100644 index 000000000..088b83121 --- /dev/null +++ b/apps/namadillo/src/App/Common/TableRowLoading.tsx @@ -0,0 +1,24 @@ +import { SkeletonLoading, Stack } from "@namada/components"; +import clsx from "clsx"; + +type TableRowLoadingProps = { count: number }; + +export const TableRowLoading = ({ + count = 3, +}: TableRowLoadingProps): JSX.Element => { + return ( + + {[...Array(count).keys()].map((_, idx) => ( + + ))} + + ); +}; diff --git a/apps/namadillo/src/App/Common/Toast.tsx b/apps/namadillo/src/App/Common/Toast.tsx new file mode 100644 index 000000000..da88ccec5 --- /dev/null +++ b/apps/namadillo/src/App/Common/Toast.tsx @@ -0,0 +1,93 @@ +import { Stack } from "@namada/components"; +import clsx from "clsx"; +import { AnimatePresence, motion } from "framer-motion"; +import { useAtomValue, useSetAtom } from "jotai"; +import { useCallback, useEffect } from "react"; +import { FaCheck, FaTimes } from "react-icons/fa"; +import { FaRegHourglassHalf, FaXmark } from "react-icons/fa6"; +import { + dismissToastNotificationAtom, + toastNotificationsAtom, +} from "slices/notifications"; +import { ToastNotification } from "types/notifications"; + +export const Toasts = (): JSX.Element => { + const dismiss = useSetAtom(dismissToastNotificationAtom); + const notifications = useAtomValue(toastNotificationsAtom); + const onClose = useCallback((notification: ToastNotification): void => { + dismiss(notification.id); + }, []); + + return ( +
+ + + {notifications.map((n) => ( + + ))} + + +
+ ); +}; + +type ToastProps = { + notification: ToastNotification; + onClose: (notification: ToastNotification) => void; +}; + +const Toast = ({ notification, onClose }: ToastProps): JSX.Element => { + useEffect(() => { + notification.timeout && + setTimeout(() => { + onClose(notification); + }, notification.timeout); + }, []); + + return ( + + + {notification.type === "success" && ( + + + + )} + {notification.type === "error" && ( + + + + )} + {notification.type === "pending" && ( + + + + )} + +
+ {notification.title} +

{notification.description}

+
+ onClose(notification)} + className={clsx( + "absolute right-1 top-1 p-1.5 flex items-center", + "justify-center cursor-pointer" + )} + > + + +
+ ); +}; diff --git a/apps/namadillo/src/App/Common/TopNavigation.tsx b/apps/namadillo/src/App/Common/TopNavigation.tsx new file mode 100644 index 000000000..abc4481f7 --- /dev/null +++ b/apps/namadillo/src/App/Common/TopNavigation.tsx @@ -0,0 +1,43 @@ +import { ToggleButton } from "@namada/components"; +import { Chain } from "@namada/types"; +import { ActiveAccount } from "App/Common/ActiveAccount"; +import { ConnectExtensionButton } from "App/Common/ConnectExtensionButton"; +import { ConnectStatus, useExtensionConnect } from "hooks/useExtensionConnect"; +import { useAtom } from "jotai"; +import { IoSettingsOutline } from "react-icons/io5"; +import { Link } from "react-router-dom"; +import { hideBalancesAtom } from "slices/settings"; + +type Props = { + chain: Chain; +}; + +export const TopNavigation = ({ chain }: Props): JSX.Element => { + const { connectionStatus } = useExtensionConnect(chain); + const [hideBalances, setHideBalances] = useAtom(hideBalancesAtom); + + return ( + <> + {connectionStatus !== ConnectStatus.CONNECTED && ( + + + + )} + + {connectionStatus === ConnectStatus.CONNECTED && ( +
+ setHideBalances(!hideBalances)} + containerProps={{ className: "hidden text-white md:flex" }} + /> + + + + +
+ )} + + ); +}; diff --git a/apps/namadillo/src/App/Common/TransactionFees.tsx b/apps/namadillo/src/App/Common/TransactionFees.tsx new file mode 100644 index 000000000..98c196a7a --- /dev/null +++ b/apps/namadillo/src/App/Common/TransactionFees.tsx @@ -0,0 +1,27 @@ +import clsx from "clsx"; +import { useGasEstimate } from "hooks/useGasEstimate"; +import { NamCurrency } from "./NamCurrency"; +type TransactionFeesProps = { + numberOfTransactions: number; + className?: string; +}; + +export const TransactionFees = ({ + numberOfTransactions, + className, +}: TransactionFeesProps): JSX.Element => { + const { calculateMinGasRequired } = useGasEstimate(); + const minimumGas = calculateMinGasRequired(numberOfTransactions); + + if (!minimumGas || minimumGas.eq(0)) return <>; + return ( +
+ Transaction fee:{" "} + +
+ ); +}; diff --git a/apps/namadillo/src/App/Governance/AllProposalsTable.tsx b/apps/namadillo/src/App/Governance/AllProposalsTable.tsx new file mode 100644 index 000000000..9037c03df --- /dev/null +++ b/apps/namadillo/src/App/Governance/AllProposalsTable.tsx @@ -0,0 +1,282 @@ +import { useAtomValue } from "jotai"; +import { useState } from "react"; +import { GoCheckCircleFill, GoInfo } from "react-icons/go"; +import { useNavigate } from "react-router-dom"; + +import { + Stack, + StyledSelectBox, + StyledTable, + TableRow, +} from "@namada/components"; +import { Proposal, isProposalStatus, proposalStatuses } from "@namada/types"; +import { Search } from "App/Common/Search"; +import clsx from "clsx"; +import { allProposalsFamily, proposalFamily } from "slices/proposals"; +import { showProposalStatus, showProposalTypeString } from "utils"; +import { StatusLabel, TypeLabel } from "./ProposalLabels"; +import GovernanceRoutes from "./routes"; + +const Table: React.FC< + { + proposalIds: bigint[]; + } & ExtensionConnectedProps +> = (props) => { + const navigate = useNavigate(); + + const headers = [ + "ID", + "Title", + "Type", + "Status", + "", + { children: "Voting end on UTC", className: "text-right" }, + "", + ]; + + const renderRow = (id: bigint, index: number): TableRow => ({ + key: id.toString(), + className: clsx( + "group/proposals cursor-pointer text-xs [&_td]:py-4", + "[&_td:first-child]:rounded-s-md [&_td:last-child]:rounded-e-md" + ), + onClick: () => { + navigate(GovernanceRoutes.proposal(id).url); + }, + cells: [ + // ID +
+ #{id.toString()} +
, + + // Title + , + + // Type + <Type key="type" id={id} index={index} />, + + // Status + <Status key="status" id={id} />, + + // Voted + props.isExtensionConnected && props.votedProposalIds.includes(id) && ( + <GoCheckCircleFill key="voted" className="text-cyan text-lg" /> + ), + + // Voting end on + <VotingEnd key="voting-end" id={id} />, + + // Info + <i + key="info" + className="flex justify-center w-6 text-lg group-hover/proposals:text-cyan" + > + <GoInfo /> + </i>, + ], + }); + + return ( + <div className="h-[490px] flex flex-col"> + <StyledTable + tableProps={{ className: "w-full text-xs [&_td]:px-2 [&_th]:px-2" }} + headProps={{ className: "text-xs" }} + id="all-proposals-table" + headers={headers} + rows={props.proposalIds.map(renderRow)} + containerClassName="dark-scrollbar" + /> + </div> + ); +}; + +type ExtensionConnectedProps = + | { isExtensionConnected: true; votedProposalIds: bigint[] } + | { isExtensionConnected: false }; + +const statusFilters = [ + "all", + "pending", + "ongoing", + "passed", + "rejected", +] as const; +type StatusFilter = (typeof statusFilters)[number]; + +const isStatusFilter = (str: string): str is StatusFilter => + statusFilters.includes(str as StatusFilter); + +const typeFilters = ["all", "default", "pgf_steward", "pgf_payment"] as const; +type TypeFilter = (typeof typeFilters)[number]; + +const isTypeFilter = (str: string): str is TypeFilter => + typeFilters.includes(str as TypeFilter); + +export const AllProposalsTable: React.FC<ExtensionConnectedProps> = (props) => { + const [selectedStatus, setSelectedStatus] = useState<StatusFilter>("all"); + const [selectedType, setSelectedType] = useState<TypeFilter>("all"); + const [search, setSearch] = useState<string | undefined>(); + + const maybeStatus = + isProposalStatus(selectedStatus) ? selectedStatus : undefined; + const maybeType = selectedType === "all" ? undefined : selectedType; + + // TODO: just query IDs, don't query full proposals. + // This should be better for loading table rows in parallel. + const proposals = useAtomValue( + allProposalsFamily({ + status: maybeStatus, + type: maybeType, + search, + }) + ); + + return ( + <Stack gap={4}> + <div className="flex gap-2 items-end"> + <TableSelect<StatusFilter> + id="proposal-status-select" + value={selectedStatus} + defaultValue={selectedStatus} + options={[ + { + id: "all" as const, + value: <TableSelectOption>All</TableSelectOption>, + ariaLabel: "all", + }, + ...proposalStatuses.map((status) => ({ + id: status, + value: ( + <TableSelectOption> + {showProposalStatus(status)} + </TableSelectOption> + ), + ariaLabel: status, + })), + ]} + label="Proposal Status" + onChange={(e) => { + const value = e.target.value; + if (!isStatusFilter(value)) { + throw new Error(`unknown status filter value, got ${value}`); + } + setSelectedStatus(value); + }} + /> + + <TableSelect<TypeFilter> + value={selectedType} + defaultValue={selectedType} + options={[ + { + id: "all", + value: <TableSelectOption>All</TableSelectOption>, + ariaLabel: "", + }, + ...(["default", "pgf_steward", "pgf_payment"] as const).map( + (type) => ({ + id: type, + value: ( + <TableSelectOption> + {showProposalTypeString(type)} + </TableSelectOption> + ), + ariaLabel: type, + }) + ), + ]} + id="proposal-type-select" + label="Proposal Type" + onChange={(e) => { + const value = e.target.value; + if (!isTypeFilter(value)) { + throw new Error(`unknown type filter value, got ${value}`); + } + setSelectedType(value); + }} + /> + + <Search + placeholder="Search by title or ID" + className={clsx( + "[&_input]:py-2 [&_input]:rounded-md [&_input]:border-[#5C5C5C]", + "w-64 [&_input]:leading-normal [&_input]:h-9" + )} + onChange={setSearch} + /> + </div> + + <Table + {...props} + proposalIds={proposals.isSuccess ? proposals.data.map((p) => p.id) : []} + /> + </Stack> + ); +}; + +const TableSelectOption: React.FC<{ + children?: React.ReactNode; +}> = ({ children }) => <span className="col-span-full">{children}</span>; + +type TableSelectProps<T extends string> = Omit< + React.ComponentProps<typeof StyledSelectBox<T>>, + "containerProps" | "listItemProps" +> & { label: string }; + +const TableSelect = <T extends string>( + props: TableSelectProps<T> +): JSX.Element => ( + <div className="flex flex-col"> + <div className="text-xs text-[#8A8A8A] pl-5 pb-1">{props.label}</div> + + <StyledSelectBox<T> + containerProps={{ + className: clsx( + "bg-[#1B1B1B] text-sm text-[#656565] rounded-md w-48 px-5 py-2", + "border-r-[12px] border-transparent h-9" + ), + }} + listItemProps={{ + className: "w-32", + }} + {...props} + /> + </div> +); + +const useWithProposal = ( + id: bigint, + render: (proposal: Proposal) => JSX.Element | null +): JSX.Element | null => { + const proposal = useAtomValue(proposalFamily(id)); + + if (proposal.isPending || proposal.isError) { + return null; + } else { + return render(proposal.data); + } +}; + +type CellProps = { id: bigint }; + +const Title: React.FC<CellProps> = ({ id }) => + useWithProposal(id, (proposal) => <>{proposal.content.title}</>); + +const Type: React.FC<{ index: number } & CellProps> = ({ id, index }) => + useWithProposal(id, (proposal) => ( + <TypeLabel + color={index % 2 === 0 ? "dark" : "light"} + proposalType={proposal.proposalType} + className="w-full text-center" + /> + )); + +const Status: React.FC<CellProps> = ({ id }) => + useWithProposal(id, (proposal) => ( + <StatusLabel status={proposal.status} className="ml-auto" /> + )); + +const VotingEnd: React.FC<CellProps> = ({ id }) => + useWithProposal(id, (proposal) => ( + <div className="text-right">Epoch {proposal.endEpoch.toString()}</div> + )); diff --git a/apps/namadillo/src/App/Governance/Governance.tsx b/apps/namadillo/src/App/Governance/Governance.tsx new file mode 100644 index 000000000..613d91414 --- /dev/null +++ b/apps/namadillo/src/App/Governance/Governance.tsx @@ -0,0 +1,41 @@ +import { useSanitizedLocation } from "@namada/hooks"; +import { useEffect } from "react"; +import { Route, Routes, useNavigate } from "react-router-dom"; +import { GovernanceOverview } from "./GovernanceOverview"; +import { ProposalAndVote } from "./ProposalAndVote"; +import { SubmitVote } from "./SubmitVote"; +import { ViewJson } from "./ViewJson"; +import GovernanceRoutes from "./routes"; + +export const Governance: React.FC = () => { + const navigate = useNavigate(); + const location = useSanitizedLocation(); + + // from outside this view we just navigate here + // this view decides what is the default view + useEffect(() => { + if (location.pathname === GovernanceRoutes.index()) { + navigate(GovernanceRoutes.overview().url); + } + }, [location.pathname]); + + return ( + <main className="w-full"> + <Routes> + <Route + path={`${GovernanceRoutes.overview()}`} + element={<GovernanceOverview />} + /> + <Route + path={`${GovernanceRoutes.proposal()}`} + element={<ProposalAndVote />} + /> + <Route + path={`${GovernanceRoutes.submitVote()}`} + element={<SubmitVote />} + /> + <Route path={`${GovernanceRoutes.viewJson()}`} element={<ViewJson />} /> + </Routes> + </main> + ); +}; diff --git a/apps/namadillo/src/App/Governance/GovernanceOverview.tsx b/apps/namadillo/src/App/Governance/GovernanceOverview.tsx new file mode 100644 index 000000000..057a9a963 --- /dev/null +++ b/apps/namadillo/src/App/Governance/GovernanceOverview.tsx @@ -0,0 +1,85 @@ +import { Panel, SkeletonLoading } from "@namada/components"; +import { ConnectBanner } from "App/Common/ConnectBanner"; +import { PageWithSidebar } from "App/Common/PageWithSidebar"; +import { useAtomValue } from "jotai"; +import { allProposalsAtom, votedProposalIdsAtom } from "slices/proposals"; +import { namadaExtensionConnectedAtom } from "slices/settings"; +import { + atomsAreFetching, + atomsAreLoaded, + useNotifyOnAtomError, +} from "store/utils"; +import { AllProposalsTable } from "./AllProposalsTable"; +import { LiveGovernanceProposals } from "./LiveGovernanceProposals"; +import { ProposalsSummary } from "./ProposalsSummary"; +import { UpcomingProposals } from "./UpcomingProposals"; + +export const GovernanceOverview: React.FC = () => { + const isConnected = useAtomValue(namadaExtensionConnectedAtom); + const allProposals = useAtomValue(allProposalsAtom); + const votedProposalIds = useAtomValue(votedProposalIdsAtom); + + // TODO: is there a better way than this to show that votedProposalIdsAtom + // is dependent on isConnected? + const extensionAtoms = isConnected ? [votedProposalIds] : []; + + useNotifyOnAtomError( + [allProposals, ...extensionAtoms], + [allProposals.isError, votedProposalIds.isError] + ); + + return ( + <PageWithSidebar> + <div className="flex flex-col gap-1.5"> + {!isConnected && ( + <ConnectBanner text="To vote please connect your account" /> + )} + <Panel title="Live Governance Proposals"> + {atomsAreFetching(allProposals, ...extensionAtoms) && ( + <SkeletonLoading height="150px" width="100%" /> + )} + {isConnected && atomsAreLoaded(allProposals, ...extensionAtoms) && ( + <LiveGovernanceProposals + allProposals={allProposals.data!} + isExtensionConnected={true} + votedProposalIds={votedProposalIds.data!} + /> + )} + {!isConnected && atomsAreLoaded(allProposals) && ( + <LiveGovernanceProposals + allProposals={allProposals.data!} + isExtensionConnected={false} + /> + )} + </Panel> + <Panel title="Upcoming Proposals"> + {atomsAreFetching(allProposals) && ( + <SkeletonLoading height="150px" width="100%" /> + )} + {atomsAreLoaded(allProposals) && ( + <UpcomingProposals allProposals={allProposals.data!} /> + )} + </Panel> + <Panel title="All Proposals"> + {isConnected && atomsAreLoaded(...extensionAtoms) && ( + <AllProposalsTable + isExtensionConnected={true} + votedProposalIds={votedProposalIds.data!} + /> + )} + {!isConnected && <AllProposalsTable isExtensionConnected={false} />} + </Panel> + </div> + <aside className="flex flex-col gap-2 mt-1.5 lg:mt-0"> + <Panel> + {atomsAreFetching(allProposals) && ( + <SkeletonLoading height="150px" width="100%" /> + )} + {atomsAreLoaded(allProposals) && ( + <ProposalsSummary allProposals={allProposals.data!} /> + )} + </Panel> + </aside> + </PageWithSidebar> + ); +}; diff --git a/apps/namadillo/src/App/Governance/LiveGovernanceProposals.tsx b/apps/namadillo/src/App/Governance/LiveGovernanceProposals.tsx new file mode 100644 index 000000000..1174119df --- /dev/null +++ b/apps/namadillo/src/App/Governance/LiveGovernanceProposals.tsx @@ -0,0 +1,122 @@ +import { ActionButton, SegmentedBar, Stack } from "@namada/components"; +import BigNumber from "bignumber.js"; +import { GoInfo } from "react-icons/go"; +import GovernanceRoutes from "./routes"; + +import { Proposal, voteTypes } from "@namada/types"; +import clsx from "clsx"; +import { useNavigate } from "react-router-dom"; +import { StatusLabel, TypeLabel, VotedLabel } from "./ProposalLabels"; +import { colors } from "./types"; + +const ProposalListItem: React.FC<{ + proposal: Proposal; + voted?: boolean; +}> = ({ proposal, voted }) => { + const { status } = proposal; + + const navigate = useNavigate(); + + const zeroVotes = BigNumber.sum( + ...voteTypes.map((voteType) => proposal[voteType]) + ).isEqualTo(0); + + const barData = + zeroVotes ? + [{ value: 1, color: "#3A3A3A" }] + : voteTypes.map((voteType) => ({ + value: proposal[voteType], + color: colors[voteType], + })); + + const onVote = (e: React.MouseEvent): void => { + e.stopPropagation(); + navigate(GovernanceRoutes.submitVote(proposal.id).url); + }; + + return ( + <Stack + as="li" + gap={3} + onClick={() => navigate(GovernanceRoutes.proposal(proposal.id).url)} + className={clsx( + "group/proposal cursor-pointer text-sm", + "rounded-md bg-[#191919] p-4" + )} + > + <div className="flex items-center justify-between gap-4"> + <StatusLabel className="text-[10px] min-w-38" status={status} /> + <div className="text-xs text-neutral-400"> + Voting End on epoch {proposal.endEpoch.toString()} + </div> + </div> + <div className="flex items-center justify-between gap-4"> + <div className="min-w-[6ch]">#{proposal.id.toString()}</div> + <div className="flex-1">{proposal.content.title}</div> + <TypeLabel proposalType={proposal.proposalType} color="dark" /> + {typeof voted !== "undefined" && ( + <div> + {voted ? + <VotedLabel className="text-[10px]" /> + : <ActionButton + className="uppercase py-1.5" + size="xs" + color="white" + borderRadius="sm" + onClick={onVote} + > + Vote + </ActionButton> + } + </div> + )} + <i + className={clsx( + "flex justify-center w-10 text-md text-center", + "group-hover/proposal:text-cyan" + )} + > + <GoInfo /> + </i> + </div> + + <SegmentedBar data={barData} /> + </Stack> + ); +}; + +type LiveGovernanceProposalsProps = ( + | { isExtensionConnected: true; votedProposalIds: bigint[] } + | { isExtensionConnected: false } +) & { + allProposals: Proposal[]; +}; + +export const LiveGovernanceProposals: React.FC<LiveGovernanceProposalsProps> = ( + props +) => { + const liveProposals = props.allProposals.filter( + (proposal) => proposal.status === "ongoing" + ); + + return ( + <div className="max-h-[490px] flex flex-col"> + <Stack + gap={4} + as="ul" + className="dark-scrollbar overscroll-contain overflow-x-auto" + > + {liveProposals.map((proposal, index) => { + const voted = + props.isExtensionConnected ? + props.votedProposalIds.includes(proposal.id) + : undefined; + + return ( + <ProposalListItem proposal={proposal} voted={voted} key={index} /> + ); + })} + </Stack> + </div> + ); +}; diff --git a/apps/namadillo/src/App/Governance/ProposalAndVote.tsx b/apps/namadillo/src/App/Governance/ProposalAndVote.tsx new file mode 100644 index 000000000..888ed4d20 --- /dev/null +++ b/apps/namadillo/src/App/Governance/ProposalAndVote.tsx @@ -0,0 +1,94 @@ +import { Panel, SkeletonLoading } from "@namada/components"; +import { useSanitizedParams } from "@namada/hooks"; +import { useAtomValue } from "jotai"; + +import { ProposalDiscord } from "App/Sidebars/ProposalDiscord"; +import { proposalFamily, proposalVotedFamily } from "slices/proposals"; +import { namadaExtensionConnectedAtom } from "slices/settings"; +import { + atomsAreFetching, + atomsAreLoaded, + useNotifyOnAtomError, +} from "store/utils"; +import { ProposalDescription } from "./ProposalDescription"; +import { ProposalHeader } from "./ProposalHeader"; +import { ProposalStatusSummary } from "./ProposalStatusSummary"; +import { VoteHelpText } from "./VoteHelpText"; +import { VoteInfoCards } from "./VoteInfoCards"; + +export const ProposalAndVote: React.FC = () => { + const { proposalId: proposalIdString = "" } = useSanitizedParams(); + + // TODO: handle NaN case + const proposalId = BigInt(Number.parseInt(proposalIdString)); + + const isConnected = useAtomValue(namadaExtensionConnectedAtom); + const proposal = useAtomValue(proposalFamily(proposalId)); + const voted = useAtomValue(proposalVotedFamily(proposalId)); + + // TODO: is there a better way than this to show that voted is dependent on + // isConnected? + const extensionAtoms = isConnected ? [voted] : []; + + useNotifyOnAtomError( + [proposal, ...extensionAtoms], + [proposal.isError, voted.isError] + ); + + return ( + <div className="flex flex-col md:grid md:grid-cols-[auto_270px] gap-2"> + <div className="flex flex-col gap-1.5"> + <Panel className="px-3"> + {atomsAreFetching(proposal, ...extensionAtoms) && ( + <SkeletonLoading height="150px" width="100%" /> + )} + <div className="px-12"> + {isConnected && atomsAreLoaded(proposal, ...extensionAtoms) && ( + <ProposalHeader + proposal={proposal.data!} + isExtensionConnected={true} + voted={voted.data!} + /> + )} + {!isConnected && atomsAreLoaded(proposal) && ( + <ProposalHeader + proposal={proposal.data!} + isExtensionConnected={false} + /> + )} + </div> + </Panel> + <Panel title="Description"> + {atomsAreFetching(proposal) && ( + <SkeletonLoading height="150px" width="100%" /> + )} + {atomsAreLoaded(proposal) && ( + <ProposalDescription proposal={proposal.data!} /> + )} + </Panel> + <Panel className="py-6 px-7"> + {atomsAreFetching(proposal) && ( + <SkeletonLoading height="150px" width="100%" /> + )} + {atomsAreLoaded(proposal) && ( + <VoteInfoCards proposal={proposal.data!} /> + )} + </Panel> + <Panel className="py-6"> + <VoteHelpText /> + </Panel> + </div> + <aside className="flex flex-col gap-2"> + <Panel className="@container" title="Proposal Status"> + {atomsAreFetching(proposal) && ( + <ProposalStatusSummary loading={true} /> + )} + {atomsAreLoaded(proposal) && ( + <ProposalStatusSummary loading={false} proposal={proposal.data!} /> + )} + </Panel> + <ProposalDiscord /> + </aside> + </div> + ); +}; diff --git a/apps/namadillo/src/App/Governance/ProposalDescription.tsx b/apps/namadillo/src/App/Governance/ProposalDescription.tsx new file mode 100644 index 000000000..f201d775c --- /dev/null +++ b/apps/namadillo/src/App/Governance/ProposalDescription.tsx @@ -0,0 +1,52 @@ +import { Stack } from "@namada/components"; +import { useState } from "react"; + +import { Proposal } from "@namada/types"; + +export const ProposalDescription: React.FC<{ + proposal: Proposal; +}> = ({ proposal }) => { + const [expanded, setExpanded] = useState(false); + + const { abstract, ...details } = proposal.content; + + const formattedDetails = Object.entries(details).map(([key, value]) => { + const spacedKey = key.replaceAll("-", " "); + const capitalizedKey = + spacedKey.charAt(0).toUpperCase() + spacedKey.slice(1); + + return [capitalizedKey, value]; + }); + + return ( + <Stack className="text-sm px-8 -mt-3" gap={4}> + <section>{abstract}</section> + + {expanded && + formattedDetails.map(([key, value], i) => ( + <section key={i}> + <h3 className="text-[#8A8A8A]">{key}</h3> + <p>{value}</p> + </section> + ))} + + <a + className="block text-center cursor-pointer after:content-['_+']" + onClick={() => setExpanded(!expanded)} + > + Show {expanded ? "Less" : "More"} + </a> + </Stack> + ); + + // return ( + // <> + // {Object.entries(content).map(([key, value]) => ( + // <section className="text-sm px-8" key={key}> + // <h3>{key}</h3> + // <p>{value}</p> + // </section> + // ))} + // </> + //); +}; diff --git a/apps/namadillo/src/App/Governance/ProposalHeader.tsx b/apps/namadillo/src/App/Governance/ProposalHeader.tsx new file mode 100644 index 000000000..a278323b8 --- /dev/null +++ b/apps/namadillo/src/App/Governance/ProposalHeader.tsx @@ -0,0 +1,168 @@ +import { ActionButton, ProgressBar, Stack } from "@namada/components"; +import { Proposal } from "@namada/types"; +import { formatEpoch } from "@namada/utils"; +import clsx from "clsx"; +import { useAtomValue } from "jotai"; +import { FaChevronLeft } from "react-icons/fa"; +import { SiWebassembly } from "react-icons/si"; +import { VscJson } from "react-icons/vsc"; +import { Link, useNavigate } from "react-router-dom"; +import { currentEpochAtom } from "slices/proposals"; +import { StatusLabel, TypeLabel, VotedLabel } from "./ProposalLabels"; +import GovernanceRoutes from "./routes"; + +const WasmButton: React.FC<{ + wasmCode?: Uint8Array; + proposalId: bigint; +}> = ({ wasmCode, proposalId }) => { + const href = + typeof wasmCode === "undefined" ? undefined : ( + URL.createObjectURL( + new Blob([wasmCode], { type: "application/octet-stream" }) + ) + ); + + const filename = `proposal-${proposalId.toString()}.wasm`; + + return ( + <ActionButton + className="px-3 py-2" + color="white" + size="xs" + outlined + borderRadius="sm" + disabled={typeof wasmCode === "undefined"} + as="a" + download={filename} + href={href} + > + <span className="flex text-xs items-center justify-between gap-2"> + <SiWebassembly /> + WASM + </span> + </ActionButton> + ); +}; + +type ProposalHeaderProps = ( + | { isExtensionConnected: true; voted: boolean } + | { isExtensionConnected: false } +) & { + proposal: Proposal; +}; + +export const ProposalHeader: React.FC<ProposalHeaderProps> = (props) => { + const { proposal, isExtensionConnected } = props; + const { status } = proposal; + + const navigate = useNavigate(); + const voteButtonDisabled = + !isExtensionConnected || props.voted || status !== "ongoing"; + const { startEpoch, endEpoch } = proposal; + const currentEpoch = useAtomValue(currentEpochAtom); + + if (!currentEpoch.isSuccess) { + return null; + } + + const totalEpochs = endEpoch - startEpoch; + const relativeCurrentEpoch = currentEpoch.data - startEpoch; + + const { type, data } = proposal.proposalType; + const wasmCode = type === "default" ? data : undefined; + + return ( + <> + <div className="flex mb-5"> + <div className="w-full"> + <div className="text-xxs text-neutral-500"> + <Link + className="transition-colors hover:text-white" + to={GovernanceRoutes.index()} + > + Governance + </Link>{" "} + /  + <Link + className="transition-colors hover:text-white" + to={GovernanceRoutes.proposal(proposal.id).url} + > + Proposal #{proposal.id.toString()} + </Link> + </div> + <Link + to={GovernanceRoutes.index()} + className={clsx( + "inline-flex items-center text-xxs gap-1", + "text-neutral-200 bg-neutral-900 my-2 px-2 py-0.5 rounded-sm", + "hover:text-yellow" + )} + > + <i className="text-[0.8em]"> + <FaChevronLeft /> + </i>{" "} + Back + </Link> + <Stack gap={2.5}> + <TypeLabel proposalType={proposal.proposalType} /> + <div className="text-xl"> + #{proposal.id.toString()} {proposal.content.title} + </div> + </Stack> + </div> + <Stack gap={2}> + <ActionButton + className="px-3 py-2" + color="white" + size="xs" + outlined + borderRadius="sm" + onClick={() => navigate(GovernanceRoutes.viewJson(proposal.id).url)} + > + <span className="flex text-xs justify-between gap-2"> + <VscJson /> + JSON + </span> + </ActionButton> + <WasmButton wasmCode={wasmCode} proposalId={proposal.id} /> + </Stack> + </div> + <hr className="border-neutral-900 w-full mb-4" /> + <div className="flex gap-2 mb-4"> + <StatusLabel status={status} className="text-xs min-w-42" /> + {isExtensionConnected && props.voted && ( + <VotedLabel className="text-xs min-w-22" /> + )} + </div> + <div className="flex gap-10 bg-neutral-900 mb-9 px-5 py-3 -mx-3 rounded-md"> + <div className="w-full grid grid-cols-2 text-xs"> + <span>Progress</span> + <div className="col-span-2 mt-3 mb-2"> + <ProgressBar + value={{ value: relativeCurrentEpoch, color: "#11DFDF" }} + total={{ value: totalEpochs, color: "#3A3A3A" }} + /> + </div> + <div className="col-start-1">{formatEpoch(startEpoch)}</div> + <div className="text-right">{formatEpoch(endEpoch)}</div> + </div> + {isExtensionConnected && ( + <div className="w-32 flex items-center justify-center"> + <ActionButton + size="sm" + borderRadius="sm" + className="py-2" + color="white" + disabled={voteButtonDisabled} + onClick={() => + navigate(GovernanceRoutes.submitVote(proposal.id).url) + } + > + Vote + </ActionButton> + </div> + )} + </div> + </> + ); +}; diff --git a/apps/namadillo/src/App/Governance/ProposalLabels.tsx b/apps/namadillo/src/App/Governance/ProposalLabels.tsx new file mode 100644 index 000000000..15bc7486a --- /dev/null +++ b/apps/namadillo/src/App/Governance/ProposalLabels.tsx @@ -0,0 +1,55 @@ +import { InsetLabel, RoundedLabel } from "@namada/components"; +import { ProposalStatus, ProposalType } from "@namada/types"; +import { assertNever } from "@namada/utils"; +import { GoCheckCircleFill } from "react-icons/go"; +import { twMerge } from "tailwind-merge"; +import { showProposalStatus, showProposalTypeString } from "utils"; + +export const StatusLabel: React.FC< + { + status: ProposalStatus; + } & React.ComponentProps<typeof RoundedLabel> +> = ({ status, className, ...rest }) => { + const statusClassName = + status === "pending" ? "text-upcoming" + : status === "ongoing" ? "text-intermediate" + : status === "passed" ? "text-success" + : status === "rejected" ? "text-fail" + : assertNever(status); + + return ( + <RoundedLabel className={twMerge(className, statusClassName)} {...rest}> + {showProposalStatus(status)} + </RoundedLabel> + ); +}; + +export const VotedLabel: React.FC<React.ComponentProps<"div">> = ({ + className, + ...props +}) => { + return ( + <RoundedLabel + {...props} + className={twMerge( + "flex gap-1 text-cyan py-1 px-2 items-center", + className + )} + > + <div className="grow">Voted</div> + <i className="inline-flex text-lg"> + <GoCheckCircleFill /> + </i> + </RoundedLabel> + ); +}; + +export const TypeLabel: React.FC< + { + proposalType: ProposalType; + } & React.ComponentProps<typeof InsetLabel> +> = ({ proposalType, ...rest }) => ( + <InsetLabel className="text-xs leading-[1.65em]" {...rest}> + {showProposalTypeString(proposalType.type)} + </InsetLabel> +); diff --git a/apps/namadillo/src/App/Governance/ProposalStatusSummary.tsx b/apps/namadillo/src/App/Governance/ProposalStatusSummary.tsx new file mode 100644 index 000000000..ac49ae22d --- /dev/null +++ b/apps/namadillo/src/App/Governance/ProposalStatusSummary.tsx @@ -0,0 +1,234 @@ +import BigNumber from "bignumber.js"; +import clsx from "clsx"; +import { motion } from "framer-motion"; +import { useState } from "react"; + +import { PieChart, PieChartData, Stack } from "@namada/components"; +import { formatPercentage } from "@namada/utils"; + +import { Proposal, TallyType, VoteType, voteTypes } from "@namada/types"; +import { AnimatePresence } from "framer-motion"; +import { colors } from "./types"; + +// TODO: is this a good enough way to represent rational numbers? +const quorumMap: Record<TallyType, BigNumber> = { + "two-thirds": BigNumber(2).dividedBy(3), + "one-half-over-one-third": BigNumber(1).dividedBy(3), + "less-one-half-over-one-third-nay": BigNumber(1).dividedBy(3), +}; + +const StatusListItem: React.FC<{ + color: string; + leftContent: string; + rightContent: string; + rightSubContent: string; + selected: boolean; +}> = ({ color, leftContent, rightContent, rightSubContent, selected }) => { + return ( + <li + className={clsx( + "rounded-sm leading-tight py-1 px-3 flex justify-between", + "border-transparent border-2", + "transition-all ease-out-quad duration-100" + )} + style={{ + color, + borderColor: selected ? color : "#1B1B1B", + backgroundColor: + selected ? `rgb(from ${color} r g b / 0.1)` : "#1B1B1B", + }} + > + <div className="uppercase text-sm">{leftContent}</div> + <div className="text-right"> + <div className="text-sm">{rightContent}</div> + <div className="text-xxs">{rightSubContent}</div> + </div> + </li> + ); +}; + +const Layout: React.FC<{ + pieChartData?: PieChartData[]; + percentages: Record<VoteType, string>; + amounts: Record<VoteType, string>; + turnout: string; + quorum: string; + pieChartMiddle?: React.ReactNode; + hoveredVoteType?: VoteType; + onMouseEnter?: (data: PieChartData, index: number) => void; + onMouseLeave?: () => void; +}> = ({ + pieChartData, + percentages, + amounts, + turnout, + quorum, + pieChartMiddle, + hoveredVoteType, + onMouseEnter, + onMouseLeave, +}) => ( + <Stack className="@sm:flex-row" gap={4}> + <PieChart + id="proposal-status-pie-chart" + data={pieChartData || [{ value: 1, color: "#1B1B1B" }]} + segmentMargin={0} + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} + > + {pieChartMiddle} + </PieChart> + + <div className="contents w-full @sm:block"> + <Stack as="ul" gap={1}> + {voteTypes.map((voteType, i) => ( + <StatusListItem + key={i} + color={colors[voteType]} + leftContent={voteType} + rightContent={percentages[voteType]} + rightSubContent={amounts[voteType]} + selected={hoveredVoteType === voteType} + /> + ))} + </Stack> + + <div className="@sm:mt-6"> + <h3 className="text-[#A3A3A3] text-xs">Turnout / Quorum</h3> + <p className="text-xl"> + {turnout} / {quorum} + </p> + </div> + </div> + </Stack> +); + +type ProposalStatusSummaryProps = + | { + loading: false; + proposal: Proposal; + } + | { loading: true }; + +export const ProposalStatusSummary: React.FC<ProposalStatusSummaryProps> = ( + props +) => { + if (props.loading) { + return ( + <Layout + percentages={{ + yay: "%", + nay: "%", + abstain: "%", + }} + amounts={{ + yay: "NAM", + nay: "NAM", + abstain: "NAM", + }} + turnout="%" + quorum="%" + pieChartMiddle={ + <div className="text-sm text-[#B0B0B0] animate-pulse"> + Syncing Data + </div> + } + /> + ); + } else { + return <Loaded proposal={props.proposal} />; + } +}; + +const Loaded: React.FC<{ + proposal: Proposal; +}> = ({ proposal }) => { + const { yay, nay, abstain, totalVotingPower } = proposal; + + const [hoveredVoteType, setHoveredVoteType] = useState< + VoteType | undefined + >(); + + const yayNayAbstainSummedPower = BigNumber.sum(yay, nay, abstain); + + const zeroVotes = yayNayAbstainSummedPower.isEqualTo(0); + + const votedProportion = + totalVotingPower.isEqualTo(0) ? + BigNumber(0) + : yayNayAbstainSummedPower.dividedBy(totalVotingPower); + + const quorum = quorumMap[proposal.tallyType]; + + const percentageString = (value: BigNumber): string => { + const voteProportion = + zeroVotes ? BigNumber(0) : value.dividedBy(yayNayAbstainSummedPower); + + return formatPercentage(voteProportion, 2); + }; + + const data: PieChartData[] | undefined = + zeroVotes ? undefined : ( + voteTypes.map((voteType) => ({ + value: proposal[voteType], + color: colors[voteType], + })) + ); + + const handleMouseEnter = (_data: PieChartData, index: number): void => { + if (!zeroVotes) { + setHoveredVoteType(voteTypes[index]); + } + }; + + const handleMouseLeave = (): void => setHoveredVoteType(undefined); + + const pieChartMiddle = ( + <AnimatePresence> + {typeof hoveredVoteType !== "undefined" && ( + <motion.article + className="leading-tight" + style={{ color: colors[hoveredVoteType] }} + transition={{ duration: 0.1 }} + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + exit={{ opacity: 0 }} + > + <div className="uppercase text-sm">{hoveredVoteType}</div> + <div className="text-3xl"> + {percentageString(proposal[hoveredVoteType])} + </div> + </motion.article> + )} + </AnimatePresence> + ); + + const percentages = { + yay: percentageString(yay), + nay: percentageString(nay), + abstain: percentageString(abstain), + }; + + const amountString = (voteType: VoteType): string => + proposal[voteType].toString() + " NAM"; + + const amounts = { + yay: amountString("yay"), + nay: amountString("nay"), + abstain: amountString("abstain"), + }; + + return ( + <Layout + pieChartData={data} + percentages={percentages} + amounts={amounts} + turnout={formatPercentage(votedProportion, 2)} + quorum={formatPercentage(quorum, 2)} + pieChartMiddle={pieChartMiddle} + hoveredVoteType={hoveredVoteType} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} + /> + ); +}; diff --git a/apps/namadillo/src/App/Governance/ProposalsSummary.tsx b/apps/namadillo/src/App/Governance/ProposalsSummary.tsx new file mode 100644 index 000000000..564f7a3bd --- /dev/null +++ b/apps/namadillo/src/App/Governance/ProposalsSummary.tsx @@ -0,0 +1,36 @@ +import { Stack } from "@namada/components"; +import { Proposal } from "@namada/types"; + +const SummaryCard: React.FC<{ + title: string; + content: React.ReactNode; +}> = ({ title, content }) => ( + <Stack gap={2} className="rounded bg-[#1b1b1b] p-4"> + <div className="text-xs">{title}</div> + <div className="text-2xl">{content}</div> + </Stack> +); + +export const ProposalsSummary: React.FC<{ + allProposals: Proposal[]; +}> = ({ allProposals }) => { + const total = allProposals.length; + const ongoing = allProposals.filter( + ({ status }) => status === "ongoing" + ).length; + const passed = allProposals.filter( + ({ status }) => status === "passed" + ).length; + const rejected = allProposals.filter( + ({ status }) => status === "rejected" + ).length; + + return ( + <Stack gap={4}> + <SummaryCard title="Total Proposals" content={total} /> + <SummaryCard title="Proposals in Voting Period" content={ongoing} /> + <SummaryCard title="Passed" content={passed} /> + <SummaryCard title="Rejected" content={rejected} /> + </Stack> + ); +}; diff --git a/apps/namadillo/src/App/Governance/SubmitVote.tsx b/apps/namadillo/src/App/Governance/SubmitVote.tsx new file mode 100644 index 000000000..4caeecec0 --- /dev/null +++ b/apps/namadillo/src/App/Governance/SubmitVote.tsx @@ -0,0 +1,145 @@ +import { + ActionButton, + Modal, + SkeletonLoading, + Stack, + TickedRadioList, +} from "@namada/components"; +import { useSanitizedParams } from "@namada/hooks"; +import { VoteType, isVoteType, voteTypes } from "@namada/types"; +import { TransactionFees } from "App/Common/TransactionFees"; +import clsx from "clsx"; +import invariant from "invariant"; +import { useAtomValue, useSetAtom } from "jotai"; +import { useEffect, useState } from "react"; +import { IoClose } from "react-icons/io5"; +import { useNavigate } from "react-router-dom"; +import { dispatchToastNotificationAtom } from "slices/notifications"; +import { performVoteAtom, proposalFamily } from "slices/proposals"; +import GovernanceRoutes from "./routes"; + +export const SubmitVote: React.FC = () => { + const navigate = useNavigate(); + const { mutate: performVote, isSuccess } = useAtomValue(performVoteAtom); + const dispatchNotification = useSetAtom(dispatchToastNotificationAtom); + + useEffect(() => { + if (isSuccess) { + dispatchSuccessNotification(); + onCloseModal(); + } + }, [isSuccess]); + + const [selectedVoteType, setSelectedVoteType] = useState<VoteType>(); + + const { proposalId: proposalIdString = "" } = useSanitizedParams(); + // TODO: validate we got a number + const proposalId = BigInt(Number.parseInt(proposalIdString)); + const proposalQueryResult = useAtomValue(proposalFamily(proposalId)); + + if (Number.isNaN(proposalId)) { + navigate(GovernanceRoutes.overview().url); + return null; + } + + const proposal = + proposalQueryResult.isSuccess ? proposalQueryResult.data : null; + + const onCloseModal = (): void => navigate(-1); + + const dispatchSuccessNotification = (): void => { + dispatchNotification({ + id: "proposal-voted", + type: "pending", + title: "Governance transaction in progress", + description: `You've voted ${selectedVoteType} for the proposal + #${proposalId}. Your transaction is being procesed.`, + }); + }; + + const onSubmit = (e: React.FormEvent): void => { + e.preventDefault(); + invariant(!Number.isNaN(proposalId), "Proposal ID is not a number"); + invariant( + typeof selectedVoteType !== "undefined", + "There is no selected vote type" + ); + performVote({ + proposalId, + vote: selectedVoteType, + }); + }; + + const handleSelectVoteType = (voteType: string): void => { + if (!isVoteType(voteType)) { + throw new Error(`unexpected vote type, got ${voteType}`); + } + setSelectedVoteType(voteType); + }; + + return ( + <Modal onClose={onCloseModal}> + <div className="relative py-9 px-8 bg-neutral-800 min-w-[540px] rounded-md text-white"> + <i + className={clsx( + "cursor-pointer text-white absolute right-8 top-8 text-3xl", + "hover:text-yellow transition-colors" + )} + onClick={onCloseModal} + > + <IoClose /> + </i> + <h1 className="text-xl font-medium mb-4">Vote</h1> + {proposalQueryResult.isLoading && ( + <Stack gap={4}> + <SkeletonLoading + className="bg-neutral-700" + width="100%" + height="30px" + /> + <SkeletonLoading + className="bg-neutral-700" + width="100%" + height="200px" + /> + </Stack> + )} + {proposalQueryResult.isSuccess && proposal && ( + <Stack gap={4} full as="form" onSubmit={onSubmit}> + <div> + #{proposal.id.toString()} {proposal.content.title} + </div> + <Stack gap={2}> + <TickedRadioList<VoteType> + options={voteTypes.map((voteType) => ({ + text: voteType.charAt(0).toUpperCase() + voteType.slice(1), + value: voteType, + }))} + id="vote-type-radio" + value={selectedVoteType} + onChange={handleSelectVoteType} + /> + + {voteTypes.map((voteType, i) => ( + <div key={i}>{}</div> + ))} + </Stack> + <footer> + <TransactionFees + className="flex justify-between" + numberOfTransactions={1} + /> + </footer> + <ActionButton + type="submit" + borderRadius="sm" + disabled={typeof selectedVoteType === "undefined"} + > + Confirm + </ActionButton> + </Stack> + )} + </div> + </Modal> + ); +}; diff --git a/apps/namadillo/src/App/Governance/UpcomingProposals.tsx b/apps/namadillo/src/App/Governance/UpcomingProposals.tsx new file mode 100644 index 000000000..7e603697f --- /dev/null +++ b/apps/namadillo/src/App/Governance/UpcomingProposals.tsx @@ -0,0 +1,68 @@ +import { SegmentedBar, Stack } from "@namada/components"; +import GovernanceRoutes from "./routes"; + +import { Proposal } from "@namada/types"; +import clsx from "clsx"; +import { useNavigate } from "react-router-dom"; +import { StatusLabel, TypeLabel } from "./ProposalLabels"; + +const ProposalListItem: React.FC<{ + proposal: Proposal; +}> = ({ proposal }) => { + const navigate = useNavigate(); + + const barData = [ + { + value: 100, + color: "#3A3A3A", + }, + ]; + + return ( + <Stack + as="li" + gap={3} + onClick={() => navigate(GovernanceRoutes.proposal(proposal.id).url)} + className={clsx( + "group/proposal cursor-pointer text-sm", + "rounded-md bg-[#191919] p-4" + )} + > + <div className="flex items-center justify-between gap-4"> + <StatusLabel + className="text-[10px] min-w-38" + status={proposal.status} + /> + </div> + <div className="flex items-center justify-between gap-4"> + <div className="min-w-[6ch]">#{proposal.id.toString()}</div> + <div className="flex-1">{proposal.content.title}</div> + <TypeLabel proposalType={proposal.proposalType} color="dark" /> + </div> + + <SegmentedBar data={barData} /> + </Stack> + ); +}; + +export const UpcomingProposals: React.FC<{ + allProposals: Proposal[]; +}> = ({ allProposals }) => { + const upcomingProposals = allProposals.filter( + (proposal) => proposal.status === "pending" + ); + + return ( + <div className="max-h-[490px] flex flex-col"> + <Stack + gap={4} + as="ul" + className="dark-scrollbar overscroll-contain overflow-x-auto" + > + {upcomingProposals.map((proposal, index) => ( + <ProposalListItem proposal={proposal} key={index} /> + ))} + </Stack> + </div> + ); +}; diff --git a/apps/namadillo/src/App/Governance/ViewJson.tsx b/apps/namadillo/src/App/Governance/ViewJson.tsx new file mode 100644 index 000000000..dddd291f5 --- /dev/null +++ b/apps/namadillo/src/App/Governance/ViewJson.tsx @@ -0,0 +1,187 @@ +import { useSanitizedParams } from "@namada/hooks"; +import { useAtomValue } from "jotai"; +import { useNavigate } from "react-router-dom"; + +import { Modal } from "@namada/components"; +import { PgfTarget, Proposal } from "@namada/types"; +import { assertNever, copyToClipboard } from "@namada/utils"; +import { ModalContainer } from "App/Common/ModalContainer"; +import clsx from "clsx"; +import { useState } from "react"; +import { GoCheck, GoCopy } from "react-icons/go"; +import { proposalFamily } from "slices/proposals"; +import GovernanceRoutes from "./routes"; + +type DefaultData = Uint8Array | undefined; + +type PgfStewardData = { + add?: string; + remove: string[]; +}; + +// TODO: add IBC target +type PgfTargetJson = { + internal: { + target: string; + amount: string; + }; +}; + +type PgfPaymentData = { + continuous: PgfTargetJson[]; + retro: PgfTargetJson[]; +}; + +type DataJson = DefaultData | PgfStewardData | PgfPaymentData; + +type ProposalJson = { + proposal: { + id: bigint; + content: { [key: string]: string | undefined }; + author: string; + voting_start_epoch: bigint; + voting_end_epoch: bigint; + activation_epoch: bigint; + }; + data: DataJson; +}; + +const formatPgfTarget = (value: PgfTarget): PgfTargetJson => ({ + internal: { + target: value.internal.target, + amount: value.internal.amount.toString(), + }, +}); + +const formatData = (proposal: Proposal): DataJson => { + switch (proposal.proposalType.type) { + case "default": + return proposal.proposalType.data; + + case "pgf_steward": + const addRemove = proposal.proposalType.data; + + // deliberately specify keys to ensure no unwanted data is printed + return { + add: addRemove.add, + remove: addRemove.remove, + }; + + case "pgf_payment": + const pgfActions = proposal.proposalType.data; + + const continuous = [ + ...pgfActions.continuous.add, + ...pgfActions.continuous.remove, + ].map(formatPgfTarget); + + const retro = pgfActions.retro.map(formatPgfTarget); + + // deliberately specify keys to ensure no unwanted data is printed + return { continuous, retro }; + + default: + return assertNever(proposal.proposalType); + } +}; + +const getProposalJsonString = (proposal: Proposal): string => { + const proposalJson: ProposalJson = { + proposal: { + id: proposal.id, + content: proposal.content, + author: proposal.author, + voting_start_epoch: proposal.startEpoch, + voting_end_epoch: proposal.endEpoch, + activation_epoch: proposal.graceEpoch, + }, + data: formatData(proposal), + }; + + const stringified = JSON.stringify( + proposalJson, + (_, value) => { + // TODO: This is not technically safe to cast BigInt to Number, but in + // practice is probably fine. Still, we should consider replacing this + // code. + if (typeof value === "bigint") { + return Number(value); + } + + // Stop JSON.stringify from spacing out WASM data. + // This adds double quotes we need to go back and remove later. + if (value instanceof Object.getPrototypeOf(Uint8Array)) { + return "[" + Array.from(value).join(", ") + "]"; + } + + return value; + }, + 2 + ); + + const { type, data } = proposal.proposalType; + + if (type === "default" && typeof data !== "undefined") { + // remove double quotes around WASM data + return stringified.replace(/("data": )"(\[.*\])"(\n}$)/, "$1$2$3"); + } else { + return stringified; + } +}; + +export const ViewJson: React.FC = () => { + const navigate = useNavigate(); + const [copied, setCopied] = useState(false); + + const { proposalId: proposalIdString = "" } = useSanitizedParams(); + // TODO: validate we got a number + const proposalId = BigInt(Number.parseInt(proposalIdString)); + const proposalQueryResult = useAtomValue(proposalFamily(proposalId)); + + if (Number.isNaN(proposalId) || !proposalQueryResult.isSuccess) { + navigate(GovernanceRoutes.overview().url); + return null; + } + + const proposal = proposalQueryResult.data; + + const jsonString = getProposalJsonString(proposal); + + const onCloseModal = (): void => + navigate(GovernanceRoutes.proposal(proposal.id).url); + + const onCopy = (): void => { + if (!copied) { + setCopied(true); + copyToClipboard(jsonString); + setTimeout(() => setCopied(false), 2000); + } + }; + + return ( + <Modal onClose={onCloseModal}> + <ModalContainer header={null} onClose={onCloseModal}> + <i + className={clsx( + "border border-current rounded-sm p-1 absolute", + "text-lg top-6 right-17 text-white transition-colors", + { "hover:text-yellow cursor-pointer": !copied } + )} + onClick={onCopy} + > + {copied ? + <GoCheck /> + : <GoCopy />} + </i> + <pre + className={clsx( + "overflow-x-auto dark-scrollbar whitespace-pre-wrap", + "px-8 pt-4 mt-8 h-[95%]" + )} + > + {jsonString} + </pre> + </ModalContainer> + </Modal> + ); +}; diff --git a/apps/namadillo/src/App/Governance/VoteHelpText.tsx b/apps/namadillo/src/App/Governance/VoteHelpText.tsx new file mode 100644 index 000000000..e68c1a4a0 --- /dev/null +++ b/apps/namadillo/src/App/Governance/VoteHelpText.tsx @@ -0,0 +1,45 @@ +import { Stack } from "@namada/components"; + +const Section: React.FC<{ + header: string; + children?: React.ReactNode; +}> = ({ header, children }) => ( + <Stack as="section" className="text-sm" gap={4}> + <h3 className="text-base">{header}</h3> + {children} + </Stack> +); + +export const VoteHelpText: React.FC = () => ( + <Stack className="p-4" gap={8}> + <Section header="Governance Votes:"> + <p> + The following items summarize the voting options and what they mean for + this proposal: + </p> + <p> + YES - This person is your preferred candidate for AADAO’s Oversight Co + mmittee. + </p> + <p> + ABSTAIN - This person is not your preferred candidate (please remember + to vote Yes on your preferred candidate’s proposal), or you wish to + contribute to the quorum but you formally decline to vote either for or + against the proposal. + </p> + <p> + NO - No votes on this proposal will not have any impact on the election. + </p> + </Section> + + <Section header="Election results:"> + <p> + While we understand that the three proposals may not “pass” or meet the + quorum, we plan to tally the final “Yes” votes for each candidate, + considering the number of ATOMs (not the number of wallets + voting).Results will be deemed final for this election cycle, at the end + of the voting period of all three proposals. + </p> + </Section> + </Stack> +); diff --git a/apps/namadillo/src/App/Governance/VoteInfoCards.tsx b/apps/namadillo/src/App/Governance/VoteInfoCards.tsx new file mode 100644 index 000000000..8d53c319d --- /dev/null +++ b/apps/namadillo/src/App/Governance/VoteInfoCards.tsx @@ -0,0 +1,116 @@ +import { twMerge } from "tailwind-merge"; + +import { AddRemove, PgfActions, Proposal } from "@namada/types"; + +import { formatEpoch } from "@namada/utils"; + +const InfoCard: React.FC< + { + title: string; + content: React.ReactNode; + } & React.ComponentProps<"div"> +> = ({ title, content, className, ...rest }) => ( + <div + className={twMerge("bg-[#1B1B1B] rounded-sm px-3 py-2", className)} + {...rest} + > + <div className="text-xs text-[#8A8A8A]">{title}</div> + <div className="text-sm">{content}</div> + </div> +); + +const PgfStewardInfoCards: React.FC<{ + addRemove: AddRemove; +}> = ({ addRemove }) => { + return ( + <> + <InfoCard + title="Add" + className="col-span-3" + content={<span key={`info-card-add`}>{addRemove.add}</span>} + /> + <InfoCard + title="Remove" + className="col-span-3" + content={addRemove.remove.map((address) => ( + <span key={`info-card-remove-${address}`}>{address}</span> + ))} + /> + </> + ); +}; + +const PgfPaymentInfoCards: React.FC<{ + pgfActions: PgfActions; +}> = ({ pgfActions }) => { + return ( + <> + <InfoCard + title="Continuous Add" + className="col-span-full" + content={pgfActions.continuous.add.map( + ({ internal: { amount, target } }) => ( + <span key={`info-card-continuous-add-${target}`}> + {target} {amount.toString()} NAM + </span> + ) + )} + /> + <InfoCard + title="Continuous Remove" + className="col-span-full" + content={pgfActions.continuous.remove.map( + ({ internal: { amount, target } }) => ( + <span key={`info-card-continuous-remove-${target}`}> + {target} {amount.toString()} NAM + </span> + ) + )} + /> + <InfoCard + title="Retro" + className="col-span-full" + content={pgfActions.retro.map(({ internal: { amount, target } }) => ( + <span key={`info-card-retro-${target}`}> + {target} {amount.toString()} NAM + </span> + ))} + /> + </> + ); +}; + +export const VoteInfoCards: React.FC<{ + proposal: Proposal; +}> = ({ proposal }) => { + return ( + <div className="grid grid-cols-6 gap-2 m-4"> + <InfoCard + title="Voting Start" + content={formatEpoch(proposal.startEpoch)} + className="col-span-2" + /> + <InfoCard + title="Voting End" + content={formatEpoch(proposal.endEpoch)} + className="col-span-2" + /> + <InfoCard + title="Grace Epoch" + content={formatEpoch(proposal.graceEpoch)} + className="col-span-2" + /> + <InfoCard + title="Proposer" + content={proposal.author} + className="col-span-full" + /> + {proposal.proposalType.type === "pgf_steward" && ( + <PgfStewardInfoCards addRemove={proposal.proposalType.data} /> + )} + {proposal.proposalType.type === "pgf_payment" && ( + <PgfPaymentInfoCards pgfActions={proposal.proposalType.data} /> + )} + </div> + ); +}; diff --git a/apps/namadillo/src/App/Governance/index.ts b/apps/namadillo/src/App/Governance/index.ts new file mode 100644 index 000000000..27f9db425 --- /dev/null +++ b/apps/namadillo/src/App/Governance/index.ts @@ -0,0 +1 @@ +export { Governance } from "./Governance"; diff --git a/apps/namadillo/src/App/Governance/routes.ts b/apps/namadillo/src/App/Governance/routes.ts new file mode 100644 index 000000000..01b27ecf6 --- /dev/null +++ b/apps/namadillo/src/App/Governance/routes.ts @@ -0,0 +1,27 @@ +import { createRouteOutput, RouteOutput } from "utils/routes"; + +export const index = (): string => `/governance`; + +const routeOutput = createRouteOutput(index); + +export const overview = (): RouteOutput => routeOutput(`/overview`); + +export const proposal = (proposalId?: bigint): RouteOutput => + routeOutput(`/proposal/${proposalIdString(proposalId)}`); + +export const submitVote = (proposalId?: bigint): RouteOutput => + routeOutput(`/submit-vote/${proposalIdString(proposalId)}`); + +export const viewJson = (proposalId?: bigint): RouteOutput => + routeOutput(`/json/${proposalIdString(proposalId)}`); + +const proposalIdString = (proposalId?: bigint): string => + typeof proposalId === "undefined" ? ":proposalId" : proposalId.toString(); + +export default { + index, + overview, + proposal, + submitVote, + viewJson, +}; diff --git a/apps/namadillo/src/App/Governance/types.ts b/apps/namadillo/src/App/Governance/types.ts new file mode 100644 index 000000000..452ee787a --- /dev/null +++ b/apps/namadillo/src/App/Governance/types.ts @@ -0,0 +1,7 @@ +import { VoteType } from "@namada/types"; + +export const colors: Record<VoteType, string> = { + yay: "#15DD89", + nay: "#DD1599", + abstain: "#8a8a8a", +}; diff --git a/apps/namadillo/src/App/Icons/MASPIcon.tsx b/apps/namadillo/src/App/Icons/MASPIcon.tsx new file mode 100644 index 000000000..4e5df77f6 --- /dev/null +++ b/apps/namadillo/src/App/Icons/MASPIcon.tsx @@ -0,0 +1,19 @@ +export const MASPIcon = (): JSX.Element => { + return ( + <svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <g opacity="0.25"> + <circle + cx="8.3418" + cy="8.46289" + r="6.5" + stroke="white" + strokeWidth="2" + /> + <circle cx="8.51367" cy="5.78711" r="1.39062" fill="white" /> + <circle cx="4.74475" cy="8.46252" r="1.28479" fill="white" /> + <circle cx="7.69434" cy="10.8086" r="1.23438" fill="white" /> + <circle cx="11.6127" cy="8.23474" r="1.34021" fill="white" /> + </g> + </svg> + ); +}; diff --git a/apps/namadillo/src/App/Icons/SwapIcon.tsx b/apps/namadillo/src/App/Icons/SwapIcon.tsx new file mode 100644 index 000000000..90d778c2e --- /dev/null +++ b/apps/namadillo/src/App/Icons/SwapIcon.tsx @@ -0,0 +1,22 @@ +export const SwapIcon = (): JSX.Element => { + return ( + <svg viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <g opacity="0.25"> + <circle + cx="4.96143" + cy="5.57031" + r="4.375" + fill="white" + stroke="black" + /> + <circle + cx="9.83643" + cy="10.4453" + r="4.375" + fill="white" + stroke="black" + /> + </g> + </svg> + ); +}; diff --git a/apps/namadillo/src/App/Sidebars/MainnetRoadmap.tsx b/apps/namadillo/src/App/Sidebars/MainnetRoadmap.tsx new file mode 100644 index 000000000..da8f838ca --- /dev/null +++ b/apps/namadillo/src/App/Sidebars/MainnetRoadmap.tsx @@ -0,0 +1,84 @@ +import { ActionButton, Image } from "@namada/components"; +import clsx from "clsx"; +import { FaCircleCheck } from "react-icons/fa6"; + +const MainnetRoadmap = (): JSX.Element => { + const renderPhase = ( + phaseNumber: string, + name: React.ReactNode, + className: string, + done = false + ): JSX.Element => { + return ( + <li + className={clsx( + "flex flex-col items-center text-center uppercase text-yellow my-2", + className + )} + > + <i className="w-0.5 h-3.5 bg-yellow mb-1.5" /> + <span className="block text-[13px] mb-1">Phase {phaseNumber}</span> + <span className="leading-normal">{name}</span> + {done && ( + <i className="my-1"> + <FaCircleCheck /> + </i> + )} + </li> + ); + }; + + return ( + <div className="flex flex-col items-center w-[240px] py-10 px-6"> + <Image styleOverrides={{ width: "50px" }} imageName="LogoMinimal" /> + <h2 className="text-yellow text-center uppercase text-xl leading-6 font-medium mt-2.5"> + Namada Mainnet Roadmap + </h2> + <ul> + {renderPhase( + "1", + <> + Proof of Stake + <br /> + Governance + </>, + "opacity-100", + true + )} + {renderPhase( + "2", + <> + Proof of Stake Rewards + <br /> + PGF Inflation + </>, + "opacity-25" + )} + {renderPhase( + "3", + <> + MASP Enabled + <br /> + IBC Transfers + </>, + "opacity-25" + )} + {renderPhase("4", "Shielded Rewards", "opacity-25")} + {renderPhase("5", "NAM Transfers", "opacity-25")} + </ul> + <ActionButton + className="max-w-40 mt-6" + href="https://namada.net/mainnet-launch" + target="_blank" + color="primary" + size="sm" + outlined + borderRadius="sm" + > + Learn about Mainnet phases + </ActionButton> + </div> + ); +}; + +export default MainnetRoadmap; diff --git a/apps/namadillo/src/App/Sidebars/ProposalDiscord.tsx b/apps/namadillo/src/App/Sidebars/ProposalDiscord.tsx new file mode 100644 index 000000000..e852c8554 --- /dev/null +++ b/apps/namadillo/src/App/Sidebars/ProposalDiscord.tsx @@ -0,0 +1,28 @@ +import { ActionButton, Panel, Stack } from "@namada/components"; +import { BsDiscord } from "react-icons/bs"; +import { DISCORD_URL } from "urls"; + +export const ProposalDiscord: React.FC = () => { + return ( + <Panel className="text-center text-black bg-[#E9E9E9] px-8 pt-10 pb-14"> + <Stack gap={6}> + <BsDiscord className="m-auto text-[90px] leading-none -mb-3" /> + <p className="text-[18px] leading-tight"> + Join proposal discussions on discord + </p> + <ActionButton + size="xs" + borderRadius="sm" + color="black" + className="text-white" + hoverColor="secondary" + as="a" + href={DISCORD_URL} + target="_blank" + > + Discord + </ActionButton> + </Stack> + </Panel> + ); +}; diff --git a/apps/namadillo/src/App/Sidebars/ValidatorDiversification.tsx b/apps/namadillo/src/App/Sidebars/ValidatorDiversification.tsx new file mode 100644 index 000000000..29e251d39 --- /dev/null +++ b/apps/namadillo/src/App/Sidebars/ValidatorDiversification.tsx @@ -0,0 +1,26 @@ +import { ActionButton } from "@namada/components"; +import clsx from "clsx"; +import stakingInfo from "./assets/staking-info.png"; + +// TODO: Define correct URL +export const ValidatorDiversification = (): JSX.Element => { + return ( + <div + className={clsx( + "flex flex-col items-center text-[18px] py-8 px-2 text-center text-yellow", + "gap-7 leading-tight" + )} + > + <img src={stakingInfo} className="w-full max-w-42 mx-auto" /> + <p>When staking consider diversifying Across multiple validators.</p> + <ActionButton + className="max-w-44" + href="https://namada.net" + borderRadius="sm" + size="xs" + > + Learn about CPos + </ActionButton> + </div> + ); +}; diff --git a/apps/namadillo/src/App/Sidebars/YourStakingDistribution.tsx b/apps/namadillo/src/App/Sidebars/YourStakingDistribution.tsx new file mode 100644 index 000000000..aff4f8d37 --- /dev/null +++ b/apps/namadillo/src/App/Sidebars/YourStakingDistribution.tsx @@ -0,0 +1,102 @@ +import { PieChart, PieChartData } from "@namada/components"; +import BigNumber from "bignumber.js"; +import { AnimatePresence, motion } from "framer-motion"; +import { useState } from "react"; +import { MyValidator } from "slices/validators"; + +type YourStakingDistributionProps = { + myValidators: MyValidator[]; +}; + +export const YourStakingDistribution = ({ + myValidators, +}: YourStakingDistributionProps): JSX.Element => { + const [displayedValidator, setDisplayedValidator] = useState< + MyValidator | undefined + >(); + + const totalAmount = myValidators.reduce( + (previous, current) => previous.plus(current.stakedAmount || 0), + new BigNumber(0) + ); + + const getColor = (index: number): string => { + const baseColors = ["#FFFF00", "#DD1599", "#15DD89", "#00FFFF"]; + if (index < baseColors.length) return baseColors[index]; + + const rangeMin = 20; + const rangeMax = 320; + const step = 10; + + const rangeLength = (rangeMax - rangeMin) / step; + const colorIdx = index % rangeLength; + const color = `hsl(${colorIdx * step}, 100%, 50%)`; + return color; + }; + + const getFormattedPercentage = (myValidator: MyValidator): string => { + if (!myValidator.stakedAmount) return "0%"; + return ( + myValidator.stakedAmount + .dividedBy(totalAmount) + .multipliedBy(100) + .toFormat(2) + "%" + ); + }; + + const data: PieChartData[] = myValidators.map((myValidator, index) => ({ + value: myValidator.stakedAmount || 0, + color: getColor(index), + })); + + return ( + <article className="@sm:grid @sm:grid-cols-[270px_auto] @sm:gap-8"> + <PieChart + id="your-staking-distribution" + data={data} + segmentMargin={0} + strokeWidth={8} + onMouseLeave={() => setDisplayedValidator(undefined)} + onMouseEnter={(_data: PieChartData, index: number) => { + setDisplayedValidator(myValidators[index]); + }} + > + <div className="max-w-[75%] mx-auto leading-tight"> + <AnimatePresence> + {displayedValidator === undefined && ( + <motion.span + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + exit={{ opacity: 0 }} + > + Your Staking Distribution + </motion.span> + )} + {displayedValidator && ( + <motion.div + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + exit={{ opacity: 0 }} + > + {displayedValidator.validator.alias} + <span className="block text-neutral-500 text-sm"> + {getFormattedPercentage(displayedValidator)} + </span> + </motion.div> + )} + </AnimatePresence> + </div> + </PieChart> + <ul className="flex flex-col gap-2 mt-4 @sm:mt-2"> + {myValidators.map((myValidator) => ( + <li key={`staking-distribution-${myValidator.uuid}`}> + <div className="grid grid-cols-[auto_max-content] text-sm justify-between"> + <span>{myValidator.validator.alias}</span> + <span>{getFormattedPercentage(myValidator)}</span> + </div> + </li> + ))} + </ul> + </article> + ); +}; diff --git a/apps/namadillo/src/App/Sidebars/assets/staking-info.png b/apps/namadillo/src/App/Sidebars/assets/staking-info.png new file mode 100644 index 000000000..5f15c2306 Binary files /dev/null and b/apps/namadillo/src/App/Sidebars/assets/staking-info.png differ diff --git a/apps/namadillo/src/App/Staking/AllValidatorsTable.tsx b/apps/namadillo/src/App/Staking/AllValidatorsTable.tsx new file mode 100644 index 000000000..98ee78977 --- /dev/null +++ b/apps/namadillo/src/App/Staking/AllValidatorsTable.tsx @@ -0,0 +1,125 @@ +import { ActionButton, TableRow } from "@namada/components"; +import { formatPercentage, shortenAddress } from "@namada/utils"; +import { Search } from "App/Common/Search"; +import { TableRowLoading } from "App/Common/TableRowLoading"; +import BigNumber from "bignumber.js"; +import { useValidatorFilter } from "hooks/useValidatorFilter"; +import { useAtomValue } from "jotai"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { namadaExtensionConnectedAtom } from "slices/settings"; +import { Validator, allValidatorsAtom } from "slices/validators"; +import { useNotifyOnAtomError } from "store/utils"; +import { ValidatorsTable } from "./ValidatorsTable"; +import StakingRoutes from "./routes"; + +type AllValidatorsProps = { + resultsPerPage?: number; + initialPage?: number; +}; + +export const AllValidatorsTable = ({ + resultsPerPage = 100, + initialPage = 0, +}: AllValidatorsProps): JSX.Element => { + const validators = useAtomValue(allValidatorsAtom); + const isConnected = useAtomValue(namadaExtensionConnectedAtom); + const navigate = useNavigate(); + const [filter, setFilter] = useState(""); + const filteredValidators = useValidatorFilter({ + validators: validators.isSuccess ? validators.data : [], + myValidatorsAddresses: [], + searchTerm: filter, + onlyMyValidators: false, + }); + + useNotifyOnAtomError([validators], [validators.isError]); + + if (validators.isError) return <>Error!</>; + + const headers = [ + "", + "Validator", + "Address", + <div key={`all-validators-voting-power`} className="text-right"> + Voting Power + </div>, + <div key={`all-validators-comission`} className="text-right"> + Commission + </div>, + ]; + + const renderRow = (validator: Validator): TableRow => ({ + className: "[&_td:first-child]:pr-0", + cells: [ + // Thumbnail: + <img + key={`validator-image-${validator.address}`} + src={validator.imageUrl} + className="w-8 rounded-full aspect-square" + />, + // Alias: + <strong + key={`validator-alias-${validator.address}`} + className="font-medium" + > + {validator.alias} + </strong>, + // Address: + shortenAddress(validator.address, 8, 8), + // Voting Power: + <div + className="flex flex-col text-right" + key={`validator-voting-power-${validator.address}`} + > + {validator.votingPowerInNAM && ( + <span>{validator.votingPowerInNAM?.toString()} NAM</span> + )} + <span className="text-neutral-600 text-sm"> + {formatPercentage(BigNumber(validator.votingPowerPercentage || 0))} + </span> + </div>, + // Commission: + <div key={`comission-${validator.address}`} className="text-right"> + {formatPercentage(BigNumber(validator.commission))} + </div>, + ], + }); + + if (validators.isLoading) { + return <TableRowLoading count={2} />; + } + + return ( + <div className="min-h-[450px] flex flex-col"> + <div className="grid grid-cols-[40%_max-content] justify-between mb-5"> + <Search + onChange={(value: string) => setFilter(value)} + placeholder="Search Validator" + /> + {isConnected && ( + <ActionButton + size="sm" + color="primary" + borderRadius="sm" + onClick={() => navigate(StakingRoutes.incrementBonding().url)} + > + Stake + </ActionButton> + )} + </div> + {validators.data && ( + <div className="flex flex-col h-[490px] overflow-hidden"> + <ValidatorsTable + id="all-validators" + validatorList={filteredValidators} + headers={headers} + initialPage={initialPage} + resultsPerPage={resultsPerPage} + renderRow={renderRow} + /> + </div> + )} + </div> + ); +}; diff --git a/apps/namadillo/src/App/Staking/BondingAmountOverview.tsx b/apps/namadillo/src/App/Staking/BondingAmountOverview.tsx new file mode 100644 index 000000000..08fae211b --- /dev/null +++ b/apps/namadillo/src/App/Staking/BondingAmountOverview.tsx @@ -0,0 +1,58 @@ +import { Panel, Stack } from "@namada/components"; +import { FiatCurrency } from "App/Common/FiatCurrency"; +import { NamCurrency } from "App/Common/NamCurrency"; +import BigNumber from "bignumber.js"; +import clsx from "clsx"; + +type BondingAmountOverviewProps = { + title: string; + amountInNam: BigNumber | number; + updatedAmountInNam?: BigNumber | number; + amountToDelegate?: BigNumber; + additionalText?: React.ReactNode; + extraContent?: React.ReactNode; + updatedValueClassList?: string; +}; + +export const BondingAmountOverview = ({ + title, + amountInNam, + updatedAmountInNam = 0, + additionalText, + extraContent, + amountToDelegate, + updatedValueClassList = "", +}: BondingAmountOverviewProps): JSX.Element => { + const hasUpdatedValue = + updatedAmountInNam && !new BigNumber(updatedAmountInNam).eq(amountInNam); + const namToDisplay = hasUpdatedValue ? updatedAmountInNam : amountInNam; + + return ( + <Panel className="relative w-full rounded-md"> + <Stack gap={2} className="leading-none"> + <h3 className="text-sm">{title}</h3> + <div className="flex items-center"> + <NamCurrency + amount={namToDisplay} + className={clsx("text-2xl", { + [updatedValueClassList]: hasUpdatedValue, + })} + currencySignClassName="text-lg" + /> + {amountToDelegate && amountToDelegate.gt(0) && ( + <span className="text-success text-md font-light mt-1.5 ml-3"> + (+ + <NamCurrency amount={amountToDelegate} /> Re-Delegate) + </span> + )} + </div> + <FiatCurrency + className="text-base text-neutral-400" + amountInNam={new BigNumber(namToDisplay)} + /> + {additionalText && <p className="text-[10px]">{additionalText}</p>} + {extraContent} + </Stack> + </Panel> + ); +}; diff --git a/apps/namadillo/src/App/Staking/IncrementBonding.tsx b/apps/namadillo/src/App/Staking/IncrementBonding.tsx new file mode 100644 index 000000000..2953be988 --- /dev/null +++ b/apps/namadillo/src/App/Staking/IncrementBonding.tsx @@ -0,0 +1,242 @@ +import { ActionButton, Alert, Modal, Panel } from "@namada/components"; +import { BondProps } from "@namada/types"; +import { shortenAddress } from "@namada/utils"; +import { Info } from "App/Common/Info"; +import { ModalContainer } from "App/Common/ModalContainer"; +import { NamCurrency } from "App/Common/NamCurrency"; +import { TableRowLoading } from "App/Common/TableRowLoading"; +import { TransactionFees } from "App/Common/TransactionFees"; +import { useStakeModule } from "hooks/useStakeModule"; +import { useValidatorFilter } from "hooks/useValidatorFilter"; +import { useValidatorSorting } from "hooks/useValidatorSorting"; +import invariant from "invariant"; +import { useAtomValue, useSetAtom } from "jotai"; +import { TransactionPair, prepareTxs } from "lib/query"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { defaultAccountAtom, totalNamBalanceAtom } from "slices/accounts"; +import { GAS_LIMIT, minimumGasPriceAtom } from "slices/fees"; +import { dispatchToastNotificationAtom } from "slices/notifications"; +import { createBondTxAtom } from "slices/staking"; +import { dispatchTransactionsAtom } from "slices/transactions"; +import { allValidatorsAtom } from "slices/validators"; +import { BondingAmountOverview } from "./BondingAmountOverview"; +import { IncrementBondingTable } from "./IncrementBondingTable"; +import { ValidatorFilterNav } from "./ValidatorFilterNav"; +import StakingRoutes from "./routes"; + +const IncrementBonding = (): JSX.Element => { + const [filter, setFilter] = useState<string>(""); + const [onlyMyValidators, setOnlyMyValidators] = useState(false); + const navigate = useNavigate(); + const totalNamBalance = useAtomValue(totalNamBalanceAtom); + const gasPrice = useAtomValue(minimumGasPriceAtom); + const account = useAtomValue(defaultAccountAtom); + const validators = useAtomValue(allValidatorsAtom); + const dispatchTransactions = useSetAtom(dispatchTransactionsAtom); + const dispatchNotification = useSetAtom(dispatchToastNotificationAtom); + const resultsPerPage = 100; + const [seed, setSeed] = useState(Math.random()); + + const { + mutate: createBondTransaction, + isPending: isPerformingBond, + isSuccess, + data: bondTransactionData, + } = useAtomValue(createBondTxAtom); + + const { + myValidators, + totalUpdatedAmount, + totalStakedAmount, + totalNamAfterStaking, + stakedAmountByAddress, + updatedAmountByAddress, + onChangeValidatorAmount, + parseUpdatedAmounts, + } = useStakeModule({ account }); + + const filteredValidators = useValidatorFilter({ + validators: validators.isSuccess ? validators.data : [], + myValidatorsAddresses: Array.from( + new Set([ + ...Object.keys(stakedAmountByAddress), + ...Object.keys(updatedAmountByAddress), + ]) + ), + searchTerm: filter, + onlyMyValidators, + }); + + const sortedValidators = useValidatorSorting({ + validators: filteredValidators, + updatedAmountByAddress, + seed, + }); + + const onCloseModal = (): void => navigate(StakingRoutes.overview().url); + + const onSubmit = (e: React.FormEvent): void => { + e.preventDefault(); + invariant( + account, + "Extension is not connected or you don't have an account" + ); + invariant(gasPrice.data, "Gas price loading is still pending"); + createBondTransaction({ + changes: parseUpdatedAmounts(), + account, + gasConfig: { + gasPrice: gasPrice.data!, + gasLimit: GAS_LIMIT, + }, + }); + }; + + const dispatchPendingNotification = (): void => { + dispatchNotification({ + id: "staking-new", + title: "Staking transaction in progress", + description: ( + <> + The staking transaction of <NamCurrency amount={totalUpdatedAmount} />{" "} + is being processed + </> + ), + type: "pending", + }); + }; + + const dispatchBondingTransactions = ( + transactions: TransactionPair<BondProps>[] + ): void => { + dispatchTransactions( + prepareTxs<BondProps>(transactions, (props: BondProps) => { + const validatorAddress = shortenAddress(props.validator, 12, 8); + return { + success: { + title: "Staking transaction succeeded", + text: `Your staking transaction of ${props.amount} NAM to ${validatorAddress} has been succeeded`, + }, + error: { + title: "Staking transaction failed", + text: `Your staking transaction to ${validatorAddress} has failed.`, + }, + }; + }) + ); + }; + + useEffect(() => { + if (isSuccess) { + dispatchPendingNotification(); + bondTransactionData && dispatchBondingTransactions(bondTransactionData); + onCloseModal(); + } + }, [isSuccess]); + + const errorMessage = ((): string => { + if (totalNamBalance.isPending) return "Loading..."; + if (totalNamBalance.data!.lt(totalUpdatedAmount)) return "Invalid amount"; + return ""; + })(); + + return ( + <Modal onClose={onCloseModal}> + <ModalContainer + header={ + <span className="flex items-center gap-3"> + Select Validators to delegate your NAM{" "} + <Info> + Enter staking values across multiple validators. The total amount + should be less than the total NAM available in your account. + Please leave a small amount for transaction fees. + </Info> + </span> + } + onClose={onCloseModal} + > + <form + onSubmit={onSubmit} + className="grid grid-rows-[max-content_auto_max-content] gap-2 h-full" + > + <div className="grid grid-cols-[2fr_1fr_1fr] gap-1.5"> + <BondingAmountOverview + title="Available to Stake" + amountInNam={totalNamBalance.data ?? 0} + updatedAmountInNam={totalNamAfterStaking} + extraContent={ + <> + {totalNamAfterStaking.lt(GAS_LIMIT.multipliedBy(2)) && ( + <Alert + type="warning" + className="absolute py-3 right-2 top-4 max-w-[50%] text-xs rounded-sm" + > + We recommend leaving a small amount of NAM to cover fees + </Alert> + )} + </> + } + /> + <BondingAmountOverview + title="Current Stake" + amountInNam={totalStakedAmount} + /> + <BondingAmountOverview + title="Increased Stake" + updatedAmountInNam={totalUpdatedAmount} + updatedValueClassList="text-yellow" + amountInNam={0} + /> + </div> + <Panel className="grid grid-rows-[max-content_auto] w-full relative overflow-hidden"> + {validators.isSuccess && ( + <ValidatorFilterNav + validators={validators.data} + updatedAmountByAddress={updatedAmountByAddress} + stakedAmountByAddress={stakedAmountByAddress} + onChangeSearch={(value: string) => setFilter(value)} + onlyMyValidators={onlyMyValidators} + onFilterByMyValidators={setOnlyMyValidators} + onRandomize={() => setSeed(Math.random())} + /> + )} + {(validators.isLoading || myValidators.isLoading) && ( + <div className="mt-3"> + <TableRowLoading count={2} /> + </div> + )} + {validators.isSuccess && myValidators.isSuccess && ( + <IncrementBondingTable + resultsPerPage={resultsPerPage} + validators={sortedValidators} + onChangeValidatorAmount={onChangeValidatorAmount} + updatedAmountByAddress={updatedAmountByAddress} + stakedAmountByAddress={stakedAmountByAddress} + /> + )} + </Panel> + <div className="relative"> + <ActionButton + type="submit" + size="sm" + borderRadius="sm" + className="mt-2 w-1/4 mx-auto" + disabled={ + !!errorMessage || isPerformingBond || totalUpdatedAmount.eq(0) + } + > + {isPerformingBond ? "Processing..." : errorMessage || "Stake"} + </ActionButton> + <TransactionFees + className="absolute right-4 top-1/2 -translate-y-1/2" + numberOfTransactions={Object.keys(updatedAmountByAddress).length} + /> + </div> + </form> + </ModalContainer> + </Modal> + ); +}; + +export default IncrementBonding; diff --git a/apps/namadillo/src/App/Staking/IncrementBondingTable.tsx b/apps/namadillo/src/App/Staking/IncrementBondingTable.tsx new file mode 100644 index 000000000..04ebccbe2 --- /dev/null +++ b/apps/namadillo/src/App/Staking/IncrementBondingTable.tsx @@ -0,0 +1,143 @@ +import { AmountInput, TableRow } from "@namada/components"; +import { formatPercentage } from "@namada/utils"; +import { NamCurrency } from "App/Common/NamCurrency"; +import BigNumber from "bignumber.js"; +import clsx from "clsx"; +import { Validator } from "slices/validators"; +import { twMerge } from "tailwind-merge"; +import { ValidatorName } from "./ValidatorName"; +import { ValidatorsTable } from "./ValidatorsTable"; + +type IncrementBondingTableProps = { + validators: Validator[]; + updatedAmountByAddress: Record<string, BigNumber>; + stakedAmountByAddress: Record<string, BigNumber>; + onChangeValidatorAmount: (validator: Validator, amount?: BigNumber) => void; + resultsPerPage?: number; +}; + +export const IncrementBondingTable = ({ + validators, + updatedAmountByAddress, + stakedAmountByAddress, + onChangeValidatorAmount, + resultsPerPage = 100, +}: IncrementBondingTableProps): JSX.Element => { + const headers = [ + { children: "Validator", sortable: true }, + "Amount to Stake", + { + children: ( + <div className="leading-tight"> + Stake{" "} + <small className="block text-xs text-neutral-500">New Stake</small> + </div> + ), + className: "text-right", + }, + { children: "Voting Power", className: "text-right" }, + { children: "Commission", className: "text-right" }, + ]; + + const renderRow = (validator: Validator): TableRow => { + const stakedAmount = + stakedAmountByAddress[validator.address] ?? new BigNumber(0); + const amountToStake = + updatedAmountByAddress[validator.address] ?? new BigNumber(0); + const hasStakedAmount = stakedAmount.gt(0); + const hasNewAmounts = amountToStake.gt(0); + + const newRow = { + className: "", + cells: [ + // Validator Alias + Avatar + <ValidatorName + key={`increment-bonding-alias-${validator.address}`} + validator={validator} + hasStake={hasStakedAmount} + />, + + // Amount Text input + <div + key={`increment-bonding-new-amounts-${validator.address}`} + className="relative" + > + <AmountInput + placeholder="Select to increase stake" + className={twMerge( + clsx("[&_input]:border-neutral-500 [&_input]:py-2 [&>div]:my-0", { + "[&_input]:border-yellow [&_input]:bg-yellow-950": + hasNewAmounts, + }) + )} + value={updatedAmountByAddress[validator.address]} + onChange={(e) => onChangeValidatorAmount(validator, e.target.value)} + data-validator-input={validator.address} + /> + <span + className={clsx( + "absolute flex items-center right-2 top-[0.6em]", + "text-neutral-500 text-sm" + )} + > + NAM + </span> + </div>, + + // Current Stake / New Stake + <div + key={`increment-bonding-current-stake`} + className="text-right leading-tight min-w-[12ch]" + > + <span className="block"> + <NamCurrency amount={stakedAmount ?? 0} /> + </span> + {hasNewAmounts && ( + <span + className={clsx("text-neutral-500 text-sm", { + "text-yellow": hasNewAmounts, + })} + > + <NamCurrency amount={amountToStake.plus(stakedAmount)} /> + </span> + )} + </div>, + + // Voting Power + <div + className="flex flex-col text-right leading-tight" + key={`validator-voting-power-${validator.address}`} + > + {validator.votingPowerInNAM && ( + <span>{validator.votingPowerInNAM?.toString()} NAM</span> + )} + <span className="text-neutral-600 text-sm"> + {formatPercentage(BigNumber(validator.votingPowerPercentage || 0))} + </span> + </div>, + + // Commission + <div + key={`comission-${validator.uuid}`} + className="text-right leading-tight" + > + {formatPercentage(validator.commission)} + </div>, + ], + }; + + return newRow; + }; + + return ( + <ValidatorsTable + id="increment-bonding-table" + tableClassName="flex-1 overflow-auto mt-2" + validatorList={validators} + updatedAmountByAddress={updatedAmountByAddress} + headers={headers} + renderRow={renderRow} + resultsPerPage={resultsPerPage} + /> + ); +}; diff --git a/apps/namadillo/src/App/Staking/MyValidatorsFilter.tsx b/apps/namadillo/src/App/Staking/MyValidatorsFilter.tsx new file mode 100644 index 000000000..378a0964f --- /dev/null +++ b/apps/namadillo/src/App/Staking/MyValidatorsFilter.tsx @@ -0,0 +1,46 @@ +import { ActionButton, Stack } from "@namada/components"; +import clsx from "clsx"; + +type FilterOptions = "all" | "my-validators"; + +type MyValidatorsFilterProps = { + onChange: (type: FilterOptions) => void; + value: FilterOptions; +}; + +export const MyValidatorsFilter = ({ + onChange, + value, +}: MyValidatorsFilterProps): JSX.Element => { + const selectedClassList = "[&_i]:!bg-white !text-black"; + return ( + <Stack gap={1.5} direction="horizontal" as="ul"> + <li> + <ActionButton + type="button" + color="white" + size="sm" + borderRadius="sm" + outlined={value !== "all"} + className={clsx({ [selectedClassList]: value === "all" })} + onClick={() => onChange("all")} + > + All + </ActionButton> + </li> + <li> + <ActionButton + className={clsx({ [selectedClassList]: value === "my-validators" })} + type="button" + color="white" + size="sm" + borderRadius="sm" + outlined={value !== "my-validators"} + onClick={() => onChange("my-validators")} + > + Your Validators + </ActionButton> + </li> + </Stack> + ); +}; diff --git a/apps/namadillo/src/App/Staking/MyValidatorsTable.tsx b/apps/namadillo/src/App/Staking/MyValidatorsTable.tsx new file mode 100644 index 000000000..2cfd6d804 --- /dev/null +++ b/apps/namadillo/src/App/Staking/MyValidatorsTable.tsx @@ -0,0 +1,133 @@ +import { ActionButton, TableRow } from "@namada/components"; +import { formatPercentage, shortenAddress } from "@namada/utils"; +import { FiatCurrency } from "App/Common/FiatCurrency"; +import { NamCurrency } from "App/Common/NamCurrency"; +import BigNumber from "bignumber.js"; +import { useAtomValue } from "jotai"; +import { useNavigate } from "react-router-dom"; +import { MyValidator, Validator, myValidatorsAtom } from "slices/validators"; +import { ValidatorsTable } from "./ValidatorsTable"; +import StakingRoutes from "./routes"; + +export const MyValidatorsTable = (): JSX.Element => { + const navigate = useNavigate(); + const myValidators = useAtomValue(myValidatorsAtom); + const myValidatorsObj: Record<string, MyValidator> = + myValidators.isSuccess ? + myValidators.data.reduce( + (acc: Record<string, MyValidator>, current: MyValidator) => { + return { ...acc, [current.validator.address]: current }; + }, + {} + ) + : {}; + + const head = [ + "", + "My Validators", + "Address", + <div key="my-validators-vp" className="text-right"> + Voting Power + </div>, + <div key="my-validators-staked-amount" className="text-right"> + Staked Amount + </div>, + <div key="my-validators-comission" className="text-right"> + Commission + </div>, + ]; + + const renderRow = (validator: Validator): TableRow => { + const stakedAmount = myValidatorsObj[validator.address].stakedAmount; + return { + className: "", + cells: [ + <img + key={`validator-image-${validator.address}`} + src={validator.imageUrl} + className="rounded-full aspect-square max-w-8" + />, + <strong + className="font-medium" + key={`my-validator-alias-${validator.address}`} + > + {validator.alias} + </strong>, + shortenAddress(validator.address, 8, 6), + <div + className="flex flex-col text-right leading-tight" + key={`my-validator-voting-power-${validator.address}`} + > + {validator.votingPowerInNAM && ( + <span>{validator.votingPowerInNAM?.toString()} NAM</span> + )} + <span className="text-neutral-600 text-sm"> + {formatPercentage(BigNumber(validator.votingPowerPercentage || 0))} + </span> + </div>, + <div + key={`my-validator-currency-${validator.address}`} + className="text-right leading-tight" + > + <NamCurrency amount={stakedAmount || new BigNumber(0)} /> + <FiatCurrency + amountInNam={stakedAmount || new BigNumber(0)} + className="block text-sm text-neutral-600" + /> + </div>, + <div + key={`comission-${validator.address}`} + className="text-right leading-tight" + > + {formatPercentage(validator.commission)} + </div>, + ], + }; + }; + + return ( + <> + <nav className="absolute top-6 right-4 flex gap-2 flex-1 z-50"> + <ActionButton + className="basis-[content] py-1" + color="primary" + size="md" + borderRadius="sm" + onClick={() => navigate(StakingRoutes.incrementBonding().url)} + > + Stake + </ActionButton> + <ActionButton + className="basis-[content] py-1" + color="white" + size="md" + borderRadius="sm" + onClick={() => navigate(StakingRoutes.redelegateBonding().url)} + > + Re-delegate + </ActionButton> + <ActionButton + className="basis-[content] py-1" + color="white" + size="md" + outlined + borderRadius="sm" + onClick={() => navigate(StakingRoutes.unstake().url)} + > + Unstake + </ActionButton> + </nav> + <ValidatorsTable + id="my-validators" + tableClassName="mt-2" + validatorList={ + myValidators.isSuccess ? + myValidators.data.map((v: MyValidator) => v.validator) + : [] + } + headers={head} + renderRow={renderRow} + /> + </> + ); +}; diff --git a/apps/namadillo/src/App/Staking/QuickAccessList.tsx b/apps/namadillo/src/App/Staking/QuickAccessList.tsx new file mode 100644 index 000000000..38dd8f735 --- /dev/null +++ b/apps/namadillo/src/App/Staking/QuickAccessList.tsx @@ -0,0 +1,61 @@ +import BigNumber from "bignumber.js"; +import clsx from "clsx"; +import { useMemo } from "react"; +import { Validator } from "slices/validators"; +import { ValidatorThumb } from "./ValidatorThumb"; + +type QuickAccessListProps = { + updatedAmountByAddress: Record<string, BigNumber>; + stakedAmountByAddress: Record<string, BigNumber>; + validators: Validator[]; + onClick: (validator: Validator) => void; +}; + +export const QuickAccessList = ({ + validators, + stakedAmountByAddress, + updatedAmountByAddress, + onClick, +}: QuickAccessListProps): JSX.Element => { + const displayedValidators = useMemo(() => { + const validatorAddresses = Object.keys(stakedAmountByAddress).concat( + Object.keys(updatedAmountByAddress) + ); + + return validators + .filter((validator: Validator) => + validatorAddresses.includes(validator.address) + ) + .sort((v1, _v2) => + Object.keys(stakedAmountByAddress).includes(v1.address) ? -1 : 1 + ); + }, [stakedAmountByAddress, updatedAmountByAddress]); // My validators will never change + + return ( + <ul className="flex items-center whitespace-nowrap gap-1"> + {displayedValidators.map((validator: Validator) => ( + <li + key={`quick-access-val-${validator.address}`} + onClick={() => onClick(validator)} + > + <ValidatorThumb + className={clsx( + "w-10 transition-colors duration-150 ease-out-quad", + "border-2 border-transparent cursor-pointer", + { + "border-yellow": Object.keys(updatedAmountByAddress).includes( + validator.address + ), + } + )} + hasStake={Object.keys(stakedAmountByAddress).includes( + validator.address + )} + imageUrl={validator.imageUrl} + alt={validator.alias} + /> + </li> + ))} + </ul> + ); +}; diff --git a/apps/namadillo/src/App/Staking/ReDelegate.tsx b/apps/namadillo/src/App/Staking/ReDelegate.tsx new file mode 100644 index 000000000..f0c2a6d2f --- /dev/null +++ b/apps/namadillo/src/App/Staking/ReDelegate.tsx @@ -0,0 +1,231 @@ +import { ActionButton, Alert, Modal, Panel } from "@namada/components"; +import { RedelegateMsgValue } from "@namada/types"; +import { shortenAddress } from "@namada/utils"; +import { Info } from "App/Common/Info"; +import { ModalContainer } from "App/Common/ModalContainer"; +import BigNumber from "bignumber.js"; +import clsx from "clsx"; +import { useGasEstimate } from "hooks/useGasEstimate"; +import { useStakeModule } from "hooks/useStakeModule"; +import invariant from "invariant"; +import { useAtomValue, useSetAtom } from "jotai"; +import { TransactionPair, prepareTxs } from "lib/query"; +import { getAmountDistribution } from "lib/staking"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { defaultAccountAtom } from "slices/accounts"; +import { GAS_LIMIT } from "slices/fees"; +import { dispatchToastNotificationAtom } from "slices/notifications"; +import { createReDelegateTxAtom } from "slices/staking"; +import { dispatchTransactionsAtom } from "slices/transactions"; +import { Validator, allValidatorsAtom } from "slices/validators"; +import { twMerge } from "tailwind-merge"; +import { BondingAmountOverview } from "./BondingAmountOverview"; +import { ReDelegateAssignStake } from "./ReDelegateAssignStake"; +import { ReDelegateRemoveStake } from "./ReDelegateRemoveStake"; +import StakingRoutes from "./routes"; + +export const ReDelegate = (): JSX.Element => { + const [step, setStep] = useState<"remove" | "assign">("remove"); + const [amountsToAssignByAddress, setAmountToAssignByAddress] = useState< + Record<string, BigNumber> + >({}); + + const { gasPrice } = useGasEstimate(); + const navigate = useNavigate(); + const dispatchNotification = useSetAtom(dispatchToastNotificationAtom); + const dispatchTransactions = useSetAtom(dispatchTransactionsAtom); + const account = useAtomValue(defaultAccountAtom); + const validators = useAtomValue(allValidatorsAtom); + const { + totalStakedAmount, + totalUpdatedAmount: totalToRedelegate, + stakedAmountByAddress, + updatedAmountByAddress: amountsRemovedByAddress, + onChangeValidatorAmount, + myValidators, + } = useStakeModule({ account }); + + const { + mutate: createRedelegateTx, + isPending: isCreatingTx, + data: redelegateTxData, + isSuccess, + } = useAtomValue(createReDelegateTxAtom); + + useEffect(() => { + if (isSuccess) { + dispatchPendingNotification(); + redelegateTxData && dispatchReDelegateTransactions(redelegateTxData); + onCloseModal(); + } + }, [isSuccess]); + + const onCloseModal = (): void => navigate(StakingRoutes.overview().url); + + const onAssignAmount = ( + validator: Validator, + amount: BigNumber | undefined + ): void => { + setAmountToAssignByAddress((amounts) => { + if (amount === undefined) { + delete amounts[validator.address]; + return { ...amounts }; + } + + return { + ...amounts, + [validator.address]: amount, + }; + }); + }; + + const dispatchPendingNotification = (): void => { + dispatchNotification({ + id: "staking-redelegate", + title: "Staking re-delegation in progress", + description: <>The re-delegation transaction is being processed</>, + type: "pending", + }); + }; + + const dispatchReDelegateTransactions = ( + transactions: TransactionPair<RedelegateMsgValue>[] + ): void => { + dispatchTransactions( + prepareTxs<RedelegateMsgValue>( + transactions, + (props: RedelegateMsgValue) => { + const sourceAddress = shortenAddress(props.sourceValidator, 12, 8); + const destAddress = shortenAddress(props.destinationValidator, 12, 8); + return { + success: { + title: "Re-delegate succeeded", + text: `Your re-delegate transaction of ${props.amount} NAM from ${sourceAddress} to ${destAddress} has succeeded`, + }, + error: { + title: "Staking transaction failed", + text: `Your staking transaction of ${props.amount} NAM from ${sourceAddress} to ${destAddress} has failed.`, + }, + }; + } + ) + ); + }; + + const onSubmit = (e: React.FormEvent): void => { + e.preventDefault(); + invariant(account, `Extension is connected but you don't have an account`); + invariant(gasPrice, "Gas price loading is still pending"); + const redelegationChanges = getAmountDistribution( + amountsRemovedByAddress, + amountsToAssignByAddress + ); + createRedelegateTx({ + changes: redelegationChanges, + gasConfig: { + gasPrice: gasPrice, + gasLimit: GAS_LIMIT, + }, + account, + }); + }; + + const totalAssignedAmounts = BigNumber.sum( + new BigNumber(0), + ...Object.values(amountsToAssignByAddress) + ); + + const stepTitle = { + remove: "Step 1 - Remove NAM from current Validators", + assign: "Step 2 - Assign Re-delegating NAM", + }; + + const totalUpdatedAmount = totalToRedelegate.minus(totalAssignedAmounts); + + return ( + <Modal onClose={onCloseModal}> + <ModalContainer + header={ + <span className="flex items-center gap-4"> + {stepTitle[step]} + <Info> + You can edit the amounts between validators. You can't + increase or reduce the total staked amount, so pay attention to + the correct distribution. + </Info> + </span> + } + onClose={onCloseModal} + > + <form + onSubmit={onSubmit} + className="grid grid-rows-[max-content_auto_max-content] gap-2 h-full" + > + <div className="grid grid-cols-[2fr_1fr_1fr] gap-1.5"> + <BondingAmountOverview + title="Available to re-delegate" + amountInNam={0} + updatedAmountInNam={totalUpdatedAmount} + updatedValueClassList={twMerge( + clsx("text-yellow", { + "text-fail": totalUpdatedAmount.lt(0), + }) + )} + extraContent={ + <> + <Alert + type="warning" + className="absolute py-3 right-3 top-4 max-w-[50%] text-xs rounded-sm" + > + To proceed all re-delegated value must be assigned + </Alert> + </> + } + /> + <BondingAmountOverview + title="Current Stake" + amountInNam={totalStakedAmount} + /> + <Panel className="flex items-center h-full justify-center"> + <ActionButton + type="button" + className="w-32 mx-auto" + color="white" + size="sm" + borderRadius="sm" + onClick={() => navigate(StakingRoutes.unstake().url)} + > + Unstake + </ActionButton> + </Panel> + </div> + + {step === "remove" && ( + <ReDelegateRemoveStake + onChangeValidatorAmount={onChangeValidatorAmount} + amountsRemovedByAddress={amountsRemovedByAddress} + stakedAmountByAddress={stakedAmountByAddress} + onProceed={() => setStep("assign")} + /> + )} + + {step === "assign" && + validators.isSuccess && + myValidators.isSuccess && ( + <ReDelegateAssignStake + validators={validators.data} + amountsRemovedByAddress={amountsRemovedByAddress} + assignedAmountsByAddress={amountsToAssignByAddress} + stakedAmountByAddress={stakedAmountByAddress} + totalToRedelegate={totalToRedelegate} + totalAssignedAmounts={totalAssignedAmounts} + onChangeAssignedAmount={onAssignAmount} + isPerformingRedelegation={isCreatingTx} + /> + )} + </form> + </ModalContainer> + </Modal> + ); +}; diff --git a/apps/namadillo/src/App/Staking/ReDelegateAssignStake.tsx b/apps/namadillo/src/App/Staking/ReDelegateAssignStake.tsx new file mode 100644 index 000000000..2b0dea6ba --- /dev/null +++ b/apps/namadillo/src/App/Staking/ReDelegateAssignStake.tsx @@ -0,0 +1,144 @@ +import { ActionButton, Panel } from "@namada/components"; +import { NamCurrency } from "App/Common/NamCurrency"; +import { TransactionFees } from "App/Common/TransactionFees"; +import BigNumber from "bignumber.js"; +import clsx from "clsx"; +import { useValidatorFilter } from "hooks/useValidatorFilter"; +import { useValidatorSorting } from "hooks/useValidatorSorting"; +import { useState } from "react"; +import { Validator } from "slices/validators"; +import { twMerge } from "tailwind-merge"; +import { ReDelegateTable } from "./ReDelegateTable"; +import { ValidatorFilterNav } from "./ValidatorFilterNav"; + +type ReDelegateAssignStakeProps = { + validators: Validator[]; + amountsRemovedByAddress: Record<string, BigNumber>; + assignedAmountsByAddress: Record<string, BigNumber>; + stakedAmountByAddress: Record<string, BigNumber>; + totalAssignedAmounts: BigNumber; + totalToRedelegate: BigNumber; + onChangeAssignedAmount: ( + validator: Validator, + amount: BigNumber | undefined + ) => void; + isPerformingRedelegation: boolean; +}; + +export const ReDelegateAssignStake = ({ + validators, + stakedAmountByAddress, + amountsRemovedByAddress, + totalToRedelegate, + totalAssignedAmounts, + assignedAmountsByAddress, + onChangeAssignedAmount, + isPerformingRedelegation, +}: ReDelegateAssignStakeProps): JSX.Element => { + const [filter, setFilter] = useState<string>(""); + const [onlyMyValidators, setOnlyMyValidators] = useState(false); + const [seed, setSeed] = useState(Math.random()); + + const isAssigningValid = totalAssignedAmounts.lte(totalToRedelegate); + const hasUpdatedAmounts = Object.keys(assignedAmountsByAddress).length > 0; + const hasInvalidDistribution = + !isAssigningValid || !totalAssignedAmounts.minus(totalToRedelegate).eq(0); + + // TODO: this is just an estimate, but we should calculate it in a better way + const numberOfTransactions = Math.max( + Object.keys(assignedAmountsByAddress).length, + Object.keys(amountsRemovedByAddress).length + ); + + const filteredValidators = useValidatorFilter({ + validators, + myValidatorsAddresses: Array.from( + new Set([ + ...Object.keys(stakedAmountByAddress), + ...Object.keys(assignedAmountsByAddress), + ]) + ), + searchTerm: filter, + onlyMyValidators, + }); + + const sortedValidators = useValidatorSorting({ + validators: filteredValidators, + updatedAmountByAddress: assignedAmountsByAddress, + seed, + }); + + const renderNewTotalStakeAmount = (validator: Validator): JSX.Element => { + const stakedAmount = + stakedAmountByAddress[validator.address] ?? new BigNumber(0); + + const amountRemoved = + amountsRemovedByAddress[validator.address] ?? new BigNumber(0); + + const updatedAmount = + assignedAmountsByAddress[validator.address] ?? new BigNumber(0); + + const newAmount = stakedAmount.minus(amountRemoved).plus(updatedAmount); + const hasUpdatedAmount = updatedAmount ? updatedAmount.gt(0) : false; + return ( + <> + {hasUpdatedAmount && ( + <span + className={twMerge( + clsx("text-neutral-500 text-sm", { + "text-orange": newAmount?.lte(stakedAmount), + "text-success": newAmount?.gt(stakedAmount), + "text-fail": !isAssigningValid, + }) + )} + > + <NamCurrency amount={newAmount} /> + </span> + )} + </> + ); + }; + + return ( + <> + <Panel className="grid grid-rows-[max-content_auto] overflow-hidden w-full rounded-md relative"> + <ValidatorFilterNav + validators={validators} + updatedAmountByAddress={assignedAmountsByAddress} + stakedAmountByAddress={stakedAmountByAddress} + onChangeSearch={(value: string) => setFilter(value)} + onlyMyValidators={onlyMyValidators} + onFilterByMyValidators={setOnlyMyValidators} + onRandomize={() => setSeed(Math.random())} + /> + {Object.keys(stakedAmountByAddress).length > 0 && ( + <ReDelegateTable + validators={sortedValidators} + updatedAmountByAddress={assignedAmountsByAddress} + stakedAmountByAddress={stakedAmountByAddress} + onChangeValidatorAmount={onChangeAssignedAmount} + renderInfoColumn={renderNewTotalStakeAmount} + /> + )} + </Panel> + <div className="relative"> + <ActionButton + type="submit" + size="sm" + color="white" + borderRadius="sm" + className="mt-2 w-1/4 mx-auto" + disabled={hasInvalidDistribution || isPerformingRedelegation} + > + {hasInvalidDistribution && hasUpdatedAmounts ? + "Invalid distribution" + : "Re-Delegate"} + </ActionButton> + <TransactionFees + className="absolute right-4 top-1/2 -translate-y-1/2" + numberOfTransactions={numberOfTransactions} + /> + </div> + </> + ); +}; diff --git a/apps/namadillo/src/App/Staking/ReDelegateRemoveStake.tsx b/apps/namadillo/src/App/Staking/ReDelegateRemoveStake.tsx new file mode 100644 index 000000000..231c2d5e2 --- /dev/null +++ b/apps/namadillo/src/App/Staking/ReDelegateRemoveStake.tsx @@ -0,0 +1,161 @@ +import { ActionButton, Panel } from "@namada/components"; +import { NamCurrency } from "App/Common/NamCurrency"; +import { TableRowLoading } from "App/Common/TableRowLoading"; +import BigNumber from "bignumber.js"; +import clsx from "clsx"; +import invariant from "invariant"; +import { useAtomValue } from "jotai"; +import { useMemo } from "react"; +import { MyValidator, Validator, myValidatorsAtom } from "slices/validators"; +import { twMerge } from "tailwind-merge"; +import { ReDelegateTable } from "./ReDelegateTable"; + +type ReDelegateRemoveStakeProps = { + onChangeValidatorAmount: ( + validator: Validator, + amount: BigNumber | undefined + ) => void; + amountsRemovedByAddress: Record<string, BigNumber>; + stakedAmountByAddress: Record<string, BigNumber>; + onProceed: () => void; +}; + +export const ReDelegateRemoveStake = ({ + onChangeValidatorAmount, + amountsRemovedByAddress, + stakedAmountByAddress, + onProceed, +}: ReDelegateRemoveStakeProps): JSX.Element => { + const myValidators = useAtomValue(myValidatorsAtom); + + const validators = useMemo(() => { + if (!myValidators.isSuccess) return []; + return myValidators.data.map((mv: MyValidator) => mv.validator); + }, [myValidators.data]); + + const renderInfoColumn = (validator: Validator): JSX.Element => { + const stakedAmount = stakedAmountByAddress[validator.address] ?? 0; + const updatedAmounts = amountsRemovedByAddress[validator.address]; + const hasNewAmounts = updatedAmounts ? updatedAmounts.gt(0) : false; + return ( + <> + {hasNewAmounts && ( + <span + className={twMerge( + clsx("text-neutral-500 text-sm", { + "text-orange": updatedAmounts?.lte(stakedAmount), + "text-fail": updatedAmounts?.gt(stakedAmount), + }) + )} + > + <NamCurrency amount={stakedAmount.minus(updatedAmounts || 0)} /> + </span> + )} + </> + ); + }; + + const onReDelegateAll = (): void => { + invariant(myValidators.isSuccess, "My Validators are not loaded"); + myValidators.data!.forEach((mv: MyValidator) => { + onChangeValidatorAmount( + mv.validator, + mv.stakedAmount ?? new BigNumber(0) + ); + }); + }; + + const onClear = (): void => { + invariant(myValidators.isSuccess, "My validators are not loaded"); + myValidators.data!.forEach((mv: MyValidator) => { + if (stakedAmountByAddress[mv.validator.address]) { + onChangeValidatorAmount(mv.validator, new BigNumber(0)); + } + }); + }; + + const hasZeroUpdatedAmounts = + Object.keys(amountsRemovedByAddress).length > 0 ? + BigNumber.sum(...Object.values(amountsRemovedByAddress)).eq(0) + : true; + + const hasValidUpdatedAmounts = useMemo( + () => + Object.keys(amountsRemovedByAddress).reduce( + (prev: boolean, address: string): boolean => { + if (!prev) return false; + return ( + amountsRemovedByAddress[address].lte( + stakedAmountByAddress[address] ?? new BigNumber(0) + ) && amountsRemovedByAddress[address].gte(0) + ); + }, + true + ), + [amountsRemovedByAddress] + ); + + const validationMessage = + !hasValidUpdatedAmounts ? "Invalid amount distribution" : undefined; + + return ( + <> + <Panel className="grid grid-rows-[max-content_auto] overflow-hidden w-full rounded-md relative"> + {myValidators.isSuccess && ( + <div className="flex gap-2"> + <ActionButton + type="button" + className="w-auto" + outlined + size="sm" + borderRadius="sm" + color="secondary" + onClick={onReDelegateAll} + > + Re-delegate all + </ActionButton> + <ActionButton + type="button" + className="w-auto" + outlined + size="sm" + borderRadius="sm" + color="white" + onClick={onClear} + > + Clear + </ActionButton> + </div> + )} + {myValidators.isLoading && ( + <nav className="mt-3"> + <TableRowLoading count={2} /> + </nav> + )} + {myValidators.isSuccess && + Object.keys(stakedAmountByAddress).length > 0 && ( + <ReDelegateTable + validators={validators} + onChangeValidatorAmount={onChangeValidatorAmount} + updatedAmountByAddress={amountsRemovedByAddress} + stakedAmountByAddress={stakedAmountByAddress} + renderInfoColumn={renderInfoColumn} + /> + )} + </Panel> + <div className="relative"> + <ActionButton + type="button" + size="sm" + color="secondary" + borderRadius="sm" + className="mt-2 w-1/4 mx-auto" + disabled={hasZeroUpdatedAmounts || !hasValidUpdatedAmounts} + onClick={onProceed} + > + {validationMessage ?? "Assign new Stake"} + </ActionButton> + </div> + </> + ); +}; diff --git a/apps/namadillo/src/App/Staking/ReDelegateTable.tsx b/apps/namadillo/src/App/Staking/ReDelegateTable.tsx new file mode 100644 index 000000000..50fc57de3 --- /dev/null +++ b/apps/namadillo/src/App/Staking/ReDelegateTable.tsx @@ -0,0 +1,134 @@ +import { AmountInput, TableRow } from "@namada/components"; +import { formatPercentage } from "@namada/utils"; +import { NamCurrency } from "App/Common/NamCurrency"; +import BigNumber from "bignumber.js"; +import clsx from "clsx"; +import { Validator } from "slices/validators"; +import { twMerge } from "tailwind-merge"; +import { ValidatorName } from "./ValidatorName"; +import { ValidatorsTable } from "./ValidatorsTable"; + +type IncrementBondingTableProps = { + validators: Validator[]; + updatedAmountByAddress: Record<string, BigNumber | undefined>; + stakedAmountByAddress: Record<string, BigNumber>; + renderInfoColumn: (validator: Validator) => React.ReactNode; + onChangeValidatorAmount: ( + validator: Validator, + amount: BigNumber | undefined + ) => void; +}; + +export const ReDelegateTable = ({ + validators, + updatedAmountByAddress, + stakedAmountByAddress, + renderInfoColumn, + onChangeValidatorAmount, +}: IncrementBondingTableProps): JSX.Element => { + const headers = [ + { children: "Validator", sortable: true }, + "Amount to Re-delegate", + { + children: ( + <div className="leading-tight"> + Stake{" "} + <small className="block text-xs text-neutral-500"> + New total stake + </small> + </div> + ), + className: "text-right", + }, + { children: "Voting Power", className: "text-right" }, + { children: "Commission", className: "text-right" }, + ]; + + const renderRow = (validator: Validator): TableRow => { + const stakedAmount = + stakedAmountByAddress[validator.address] ?? new BigNumber(0); + const updatedAmounts = updatedAmountByAddress[validator.address]; + const hasStakedAmount = stakedAmount ? stakedAmount.gt(0) : false; + const hasNewAmounts = updatedAmounts ? updatedAmounts.gt(0) : false; + + return { + className: "", + cells: [ + // Validator Alias + Avatar + <ValidatorName + key={`increment-bonding-alias-${validator.address}`} + validator={validator} + hasStake={hasStakedAmount} + />, + + // Amount Text input + <div + key={`increment-bonding-new-amounts-${validator.address}`} + className="relative" + > + <AmountInput + value={updatedAmountByAddress[validator.address]} + onChange={(e) => onChangeValidatorAmount(validator, e.target.value)} + placeholder="Select to enter stake" + className={twMerge( + clsx( + "[&_input]:border-neutral-500 [&_input]:py-2.5 [&>div]:my-0", + { + "[&_input]:border-yellow [&_input]:bg-yellow-950": + hasNewAmounts, + } + ) + )} + /> + {hasNewAmounts && ( + <span className="absolute h-full flex items-center right-2 top-0 text-neutral-500 text-sm"> + NAM + </span> + )} + </div>, + + <div + key={`increment-bonding-current-stake`} + className="text-right leading-tight" + > + <span className="block"> + <NamCurrency amount={stakedAmount} /> + </span> + {renderInfoColumn(validator)} + </div>, + + // Voting Power + <div + className="flex flex-col text-right leading-tight" + key={`validator-voting-power-${validator.address}`} + > + {validator.votingPowerInNAM && ( + <span>{validator.votingPowerInNAM?.toString()} NAM</span> + )} + <span className="text-neutral-600 text-sm"> + {formatPercentage(BigNumber(validator.votingPowerPercentage || 0))} + </span> + </div>, + + // Commission + <div + key={`comission-${validator.uuid}`} + className="text-right leading-tight" + > + {formatPercentage(validator.commission)} + </div>, + ], + }; + }; + + return ( + <ValidatorsTable + id="increment-bonding-table" + tableClassName="mt-2" + validatorList={validators} + updatedAmountByAddress={updatedAmountByAddress} + headers={headers} + renderRow={renderRow} + /> + ); +}; diff --git a/apps/namadillo/src/App/Staking/Staking.tsx b/apps/namadillo/src/App/Staking/Staking.tsx new file mode 100644 index 000000000..4b49f43ac --- /dev/null +++ b/apps/namadillo/src/App/Staking/Staking.tsx @@ -0,0 +1,41 @@ +import { useAtomValue } from "jotai"; +import { loadable } from "jotai/utils"; +import { Route, Routes } from "react-router-dom"; +import { isRevealPkNeededAtom, minimumGasPriceAtom } from "slices/fees"; +import IncrementBonding from "./IncrementBonding"; +import { ReDelegate } from "./ReDelegate"; +import { StakingOverview } from "./StakingOverview"; +import Unstake from "./Unstake"; +import StakingRoutes from "./routes"; + +// This is the parent view for all staking related views. Most of the +// staking specific functions are defined here and passed down as props. +// This contains the main vies in staking: +// * StakingOverview - displaying an overview of the users staking and validators +// * ValidatorDetails - as the name says +// * NewStakingStakingPosition - rendered in modal on top of other content +// this is for creating new staking positions +// * UnstakePositions - rendered in modal on top of other content, for unstaking +export const Staking = (): JSX.Element => { + useAtomValue(minimumGasPriceAtom); + useAtomValue(loadable(isRevealPkNeededAtom)); + + return ( + <main className="w-full"> + <Routes> + <Route path="/*" element={<StakingOverview />} /> + </Routes> + <Routes> + <Route + path={`${StakingRoutes.incrementBonding()}`} + element={<IncrementBonding />} + /> + <Route path={`${StakingRoutes.unstake()}`} element={<Unstake />} /> + <Route + path={`${StakingRoutes.redelegateBonding()}`} + element={<ReDelegate />} + /> + </Routes> + </main> + ); +}; diff --git a/apps/namadillo/src/App/Staking/StakingOverview.tsx b/apps/namadillo/src/App/Staking/StakingOverview.tsx new file mode 100644 index 000000000..e844c3246 --- /dev/null +++ b/apps/namadillo/src/App/Staking/StakingOverview.tsx @@ -0,0 +1,62 @@ +import { Panel } from "@namada/components"; +import { ConnectBanner } from "App/Common/ConnectBanner"; +import { PageWithSidebar } from "App/Common/PageWithSidebar"; +import { ValidatorDiversification } from "App/Sidebars/ValidatorDiversification"; +import { YourStakingDistribution } from "App/Sidebars/YourStakingDistribution"; +import { useAtomValue } from "jotai"; +import { useEffect } from "react"; +import { balancesAtom, transparentAccountsAtom } from "slices/accounts"; +import { namadaExtensionConnectedAtom } from "slices/settings"; +import { myValidatorsAtom } from "slices/validators"; +import { AllValidatorsTable } from "./AllValidatorsTable"; +import { MyValidatorsTable } from "./MyValidatorsTable"; +import { StakingSummary } from "./StakingSummary"; + +// This is the default view for the staking. it displays all the relevant +// staking information of the user and allows unstake the active staking +// positions directly from here. +// * Unstaking happens by calling a callback that triggers a modal +// view in the parent +// * user can also navigate to sibling view for validator details +export const StakingOverview = (): JSX.Element => { + const isConnected = useAtomValue(namadaExtensionConnectedAtom); + const accounts = useAtomValue(transparentAccountsAtom); + const myValidators = useAtomValue(myValidatorsAtom); + const hasStaking = isConnected && myValidators.data?.length > 0; + useAtomValue(balancesAtom); + + useEffect(() => { + if (isConnected && accounts.length > 0) { + myValidators.refetch(); + } + }, [isConnected, accounts]); + + return ( + <PageWithSidebar> + <div className="flex flex-col gap-2"> + {!isConnected && ( + <ConnectBanner text="To stake please connect your account" /> + )} + {isConnected && <StakingSummary />} + {hasStaking && ( + <Panel title="My Validators" className="relative"> + <MyValidatorsTable /> + </Panel> + )} + <Panel className="relative pb-6" title="All Validators"> + <AllValidatorsTable /> + </Panel> + </div> + <aside className="w-full mt-2 flex flex-col sm:flex-row lg:mt-0 lg:flex-col gap-2"> + {hasStaking && ( + <Panel className="w-full @container"> + <YourStakingDistribution myValidators={myValidators.data} /> + </Panel> + )} + <Panel> + <ValidatorDiversification /> + </Panel> + </aside> + </PageWithSidebar> + ); +}; diff --git a/apps/namadillo/src/App/Staking/StakingSummary.tsx b/apps/namadillo/src/App/Staking/StakingSummary.tsx new file mode 100644 index 000000000..728fcf231 --- /dev/null +++ b/apps/namadillo/src/App/Staking/StakingSummary.tsx @@ -0,0 +1,146 @@ +import { + ActionButton, + AmountSummaryCard, + Heading, + Image, + Panel, + PieChart, + PieChartData, + SkeletonLoading, +} from "@namada/components"; +import { FiatCurrency } from "App/Common/FiatCurrency"; +import { NamCurrency } from "App/Common/NamCurrency"; +import { useAtomValue } from "jotai"; +import { GoStack } from "react-icons/go"; +import { useNavigate } from "react-router-dom"; +import { totalNamBalanceAtom } from "slices/accounts"; +import { getStakingTotalAtom } from "slices/staking"; +import StakingRoutes from "./routes"; + +export const StakingSummary = (): JSX.Element => { + const navigate = useNavigate(); + const totalStakedBalance = useAtomValue(getStakingTotalAtom); + const availableBalance = useAtomValue(totalNamBalanceAtom); + + const getPiechartData = (): Array<PieChartData> => { + if (!totalStakedBalance.isSuccess || !availableBalance.isSuccess) { + return []; + } + + const totalStaked = totalStakedBalance.data; + if (totalStaked.totalUnbonded.eq(0) && totalStaked.totalBonded.eq(0)) { + return [{ value: 1, color: "#2F2F2F" }]; + } + + return [ + { value: availableBalance.data, color: "#ffffff" }, + { value: totalStaked.totalBonded, color: "#ffff00" }, + { value: totalStaked.totalUnbonded, color: "#DD1599" }, + ]; + }; + + // TODO: implement total staking rewards + return ( + <ul className="flex flex-col sm:grid sm:grid-cols-[1.25fr_1fr_1fr] gap-2"> + <Panel as="li" className="flex items-center"> + {totalStakedBalance.isPending && ( + <SkeletonLoading + height="auto" + width="80%" + className="rounded-full aspect-square mx-auto border-neutral-800 border-[22px] bg-transparent" + /> + )} + {totalStakedBalance.isSuccess && ( + <PieChart + id="total-staked-balance" + className="xl:max-w-[85%] mx-auto" + data={getPiechartData()} + strokeWidth={7} + > + <div className="flex flex-col gap-1 leading-tight"> + <Heading className="text-sm text-neutral-500" level="h3"> + Total Staked Balance + </Heading> + <NamCurrency + amount={totalStakedBalance.data.totalBonded} + className="text-2xl" + currencySignClassName="block mb-1 text-xs ml-1" + /> + <FiatCurrency + amountInNam={totalStakedBalance.data.totalBonded} + className="text-neutral-500 text-sm" + /> + </div> + </PieChart> + )} + </Panel> + <Panel as="li" className="border border-yellow"> + <AmountSummaryCard + logoElement={<Image imageName="LogoMinimal" />} + title={ + <> + Available NAM + <br /> + to Stake + </> + } + isLoading={totalStakedBalance.isPending || availableBalance.isPending} + mainAmount={ + <NamCurrency + amount={availableBalance.data ?? 0} + className="block leading-none" + currencySignClassName="block mb-3 mt-0.5 text-sm" + /> + } + alternativeAmount={ + totalStakedBalance.isSuccess && ( + <FiatCurrency + amountInNam={totalStakedBalance.data.totalUnbonded} + /> + ) + } + callToAction={ + <ActionButton + className="px-8" + borderRadius="sm" + size="xs" + color="primary" + onClick={() => navigate(StakingRoutes.incrementBonding().url)} + > + Stake + </ActionButton> + } + /> + </Panel> + <Panel as="li" className="opacity-60 pointer-events-none select-none"> + <AmountSummaryCard + logoElement={ + <i className="text-4xl"> + <GoStack /> + </i> + } + title="Staking Rewards will be enabled in phase 2" + mainAmount={ + <NamCurrency + amount={0} + className="block leading-none" + currencySignClassName="block mb-3 mt-0.5 text-sm" + /> + } + alternativeAmount="$0" + callToAction={ + <ActionButton + className="px-8" + borderRadius="sm" + size="xs" + color="white" + disabled + > + Claim + </ActionButton> + } + /> + </Panel> + </ul> + ); +}; diff --git a/apps/namadillo/src/App/Staking/Unstake.tsx b/apps/namadillo/src/App/Staking/Unstake.tsx new file mode 100644 index 000000000..2ff67023d --- /dev/null +++ b/apps/namadillo/src/App/Staking/Unstake.tsx @@ -0,0 +1,241 @@ +import { ActionButton, Alert, Modal, Panel, Stack } from "@namada/components"; +import { UnbondProps } from "@namada/types"; +import { shortenAddress } from "@namada/utils"; +import { Info } from "App/Common/Info"; +import { ModalContainer } from "App/Common/ModalContainer"; +import { NamCurrency } from "App/Common/NamCurrency"; +import { TableRowLoading } from "App/Common/TableRowLoading"; +import { TransactionFees } from "App/Common/TransactionFees"; +import BigNumber from "bignumber.js"; +import clsx from "clsx"; +import { useStakeModule } from "hooks/useStakeModule"; +import invariant from "invariant"; +import { useAtomValue, useSetAtom } from "jotai"; +import { TransactionPair, prepareTxs } from "lib/query"; +import { FormEvent, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { defaultAccountAtom } from "slices/accounts"; +import { GAS_LIMIT, minimumGasPriceAtom } from "slices/fees"; +import { dispatchToastNotificationAtom } from "slices/notifications"; +import { createUnbondTxAtom } from "slices/staking"; +import { dispatchTransactionsAtom } from "slices/transactions"; +import { MyValidator, myValidatorsAtom } from "slices/validators"; +import { BondingAmountOverview } from "./BondingAmountOverview"; +import { UnstakeBondingTable } from "./UnstakeBondingTable"; +import StakingRoutes from "./routes"; + +const Unstake = (): JSX.Element => { + const navigate = useNavigate(); + const account = useAtomValue(defaultAccountAtom); + const validators = useAtomValue(myValidatorsAtom); + const dispatchNotification = useSetAtom(dispatchToastNotificationAtom); + const dispatchTransactions = useSetAtom(dispatchTransactionsAtom); + const minimumGasPrice = useAtomValue(minimumGasPriceAtom); + const { + mutate: createUnbondTx, + isPending: isPerformingUnbond, + data: unbondTransactionData, + isSuccess, + } = useAtomValue(createUnbondTxAtom); + + const { + parseUpdatedAmounts, + totalStakedAmount, + stakedAmountByAddress, + updatedAmountByAddress, + totalUpdatedAmount, + onChangeValidatorAmount, + } = useStakeModule({ account }); + + const onCloseModal = (): void => navigate(StakingRoutes.overview().url); + + const onUnbondAll = (): void => { + if (!validators.isSuccess) return; + validators.data.forEach((myValidator: MyValidator) => + onChangeValidatorAmount( + myValidator.validator, + myValidator.stakedAmount || new BigNumber(0) + ) + ); + }; + + const onSubmit = (e: FormEvent): void => { + e.preventDefault(); + invariant( + account, + "Extension is not connected or you don't have an account" + ); + invariant(minimumGasPrice.isSuccess, "Gas price loading is still pending"); + createUnbondTx({ + changes: parseUpdatedAmounts(), + account, + gasConfig: { + gasPrice: minimumGasPrice.data!, + gasLimit: GAS_LIMIT, + }, + }); + }; + + const dispatchPendingNotification = (): void => { + dispatchNotification({ + id: "unstaking", + title: "Unstake transaction in progress", + description: ( + <> + You've unstaked  + <NamCurrency amount={totalUpdatedAmount} /> and the transaction is + being processed + </> + ), + type: "pending", + }); + }; + + const dispatchUnbondingTransactions = ( + transactions: TransactionPair<UnbondProps>[] + ): void => { + dispatchTransactions( + prepareTxs<UnbondProps>(transactions, (props: UnbondProps) => { + const validatorAddress = shortenAddress(props.validator, 12, 8); + return { + success: { + title: "Unstake transaction succeeded", + text: `You've removed ${props.amount} NAM from validator ${validatorAddress}`, + }, + error: { + title: "Unstake transaction failed", + text: `Your request to unstake ${props.amount} NAM from ${validatorAddress} has failed.`, + }, + }; + }) + ); + }; + + useEffect(() => { + if (isSuccess) { + dispatchPendingNotification(); + unbondTransactionData && + dispatchUnbondingTransactions(unbondTransactionData); + onCloseModal(); + } + }, [isSuccess]); + + const validationMessage = ((): string => { + if (totalStakedAmount.lt(totalUpdatedAmount)) return "Invalid amount"; + return ""; + })(); + + return ( + <Modal onClose={onCloseModal}> + <ModalContainer + header={ + <span className="flex items-center gap-4"> + Select amount to unstake + <Info> + To unstake, type the amount of NAM you wish to remove from a + validator. Please pay attention to the unbonding period, it might + take a few days before the amount to be available. + </Info> + </span> + } + onClose={onCloseModal} + > + <form + className="grid grid-rows-[max-content_auto_max-content] h-full gap-2" + onSubmit={onSubmit} + > + <div className="grid grid-cols-[2fr_1fr_1fr] gap-1.5"> + <BondingAmountOverview + title="Amount of NAM to Unstake" + amountInNam={0} + updatedAmountInNam={totalUpdatedAmount} + updatedValueClassList="text-pink" + extraContent={ + <> + <Alert + type="removal" + className="absolute py-3 right-3 top-4 max-w-[50%] text-xs rounded-sm" + > + <ul className="list-disc pl-4"> + <li className="mb-1"> + You will not receive staking rewards + </li> + <li>It will take 21 days for the amount to be liquid</li> + </ul> + </Alert> + </> + } + /> + <BondingAmountOverview + title="Current Stake" + amountInNam={totalStakedAmount} + /> + <Panel className="rounded-md"> + <Stack gap={2} className="leading-none"> + <h3 className="text-sm">Unbonding period</h3> + <div className="text-xl">21 Days</div> + <p className="text-xs"> + Once this period has elapsed, you may access your assets in + the main dashboard + </p> + </Stack> + </Panel> + </div> + <Panel + className={clsx( + "grid grid-rows-[max-content_auto_max-content] overflow-hidden", + "relative w-full rounded-md flex-1" + )} + > + {validators.data && ( + <div> + <ActionButton + type="button" + className="inline-flex w-auto leading-none px-4 py-3 mb-4" + color="magenta" + borderRadius="sm" + outlined + onClick={onUnbondAll} + > + Unbond All + </ActionButton> + </div> + )} + {validators.isLoading && <TableRowLoading count={2} />} + {validators.isSuccess && ( + <UnstakeBondingTable + myValidators={validators.data} + onChangeValidatorAmount={onChangeValidatorAmount} + updatedAmountByAddress={updatedAmountByAddress} + stakedAmountByAddress={stakedAmountByAddress} + /> + )} + </Panel> + <div className="relative"> + <ActionButton + size="sm" + color="white" + borderRadius="sm" + className="mt-2 w-1/4 mx-auto" + disabled={ + !!validationMessage || + isPerformingUnbond || + totalUpdatedAmount.eq(0) + } + > + {isPerformingUnbond ? + "Processing..." + : validationMessage || "Unstake"} + </ActionButton> + <TransactionFees + className="absolute right-4 top-1/2 -translate-y-1/2" + numberOfTransactions={Object.keys(updatedAmountByAddress).length} + /> + </div> + </form> + </ModalContainer> + </Modal> + ); +}; + +export default Unstake; diff --git a/apps/namadillo/src/App/Staking/UnstakeBondingTable.tsx b/apps/namadillo/src/App/Staking/UnstakeBondingTable.tsx new file mode 100644 index 000000000..9972e4a65 --- /dev/null +++ b/apps/namadillo/src/App/Staking/UnstakeBondingTable.tsx @@ -0,0 +1,131 @@ +import { AmountInput, TableRow } from "@namada/components"; +import { formatPercentage } from "@namada/utils"; +import { NamCurrency } from "App/Common/NamCurrency"; +import BigNumber from "bignumber.js"; +import clsx from "clsx"; +import { MyValidator, Validator } from "slices/validators"; +import { twMerge } from "tailwind-merge"; +import { ValidatorName } from "./ValidatorName"; +import { ValidatorsTable } from "./ValidatorsTable"; + +type UnstakeBondingTableProps = { + myValidators: MyValidator[]; + updatedAmountByAddress: Record<string, BigNumber>; + stakedAmountByAddress: Record<string, BigNumber>; + onChangeValidatorAmount: (validator: Validator, amount: BigNumber) => void; +}; + +export const UnstakeBondingTable = ({ + myValidators, + updatedAmountByAddress, + stakedAmountByAddress, + onChangeValidatorAmount, +}: UnstakeBondingTableProps): JSX.Element => { + const headers = [ + { children: "Validator", sortable: true }, + "Amount to Unstake", + <div key={`unstake-new-total`} className="text-right"> + <span className="block">Stake</span> + <small className="text-xs text-neutral-500 block">New total Stake</small> + </div>, + <div key={`unstake-voting-power`} className="text-right"> + Voting Power + </div>, + <div key={`unstake-commission`} className="text-right"> + Commission + </div>, + ]; + + const renderRow = (validator: Validator): TableRow => { + const stakedAmount = + stakedAmountByAddress[validator.address] ?? new BigNumber(0); + + const amountToUnstake = + updatedAmountByAddress[validator.address] ?? new BigNumber(0); + + const hasNewAmounts = amountToUnstake.gt(0); + + return { + className: "", + cells: [ + // Validator Alias + Avatar + <ValidatorName + key={`validator-name-${validator.address}`} + validator={validator} + hasStake={true} + />, + + // Amount Text input + <div + key={`increment-bonding-new-amounts-${validator.address}`} + className="relative" + > + <AmountInput + placeholder="Select to increase stake" + value={amountToUnstake.eq(0) ? undefined : amountToUnstake} + onChange={(e) => + onChangeValidatorAmount( + validator, + e.target.value || new BigNumber(0) + ) + } + className={twMerge( + clsx( + "[&_input]:border-neutral-500 [&_input]:py-2.5 [&>div]:my-0", + { + "[&_input]:!border-pink [&_input]:text-pink": hasNewAmounts, + } + ) + )} + /> + </div>, + + <div + key={`increment-bonding-new-totals-${validator.address}`} + className="text-right leading-tight" + > + <span className="block text-white"> + <NamCurrency amount={stakedAmount} /> + </span> + {hasNewAmounts && ( + <span className="text-orange text-sm"> + = + <NamCurrency amount={stakedAmount.minus(amountToUnstake)} /> + </span> + )} + </div>, + + // Voting Power + <div + className="flex flex-col text-right leading-tight" + key={`validator-voting-power-${validator.address}`} + > + {validator.votingPowerInNAM && ( + <span>{validator.votingPowerInNAM?.toString()} NAM</span> + )} + <span className="text-neutral-600 text-sm"> + {formatPercentage(BigNumber(validator.votingPowerPercentage || 0))} + </span> + </div>, + + // Commission + <div + key={`comission-${validator.uuid}`} + className="text-right leading-tight" + > + {formatPercentage(validator.commission)} + </div>, + ], + }; + }; + + return ( + <ValidatorsTable + id="increment-bonding-table" + tableClassName="mt-2" + validatorList={myValidators.map((mv) => mv.validator)} + headers={headers} + renderRow={renderRow} + /> + ); +}; diff --git a/apps/namadillo/src/App/Staking/ValidatorFilterNav.tsx b/apps/namadillo/src/App/Staking/ValidatorFilterNav.tsx new file mode 100644 index 000000000..d635aff8e --- /dev/null +++ b/apps/namadillo/src/App/Staking/ValidatorFilterNav.tsx @@ -0,0 +1,76 @@ +import { ActionButton, Stack } from "@namada/components"; +import { Search } from "App/Common/Search"; +import BigNumber from "bignumber.js"; +import { Validator } from "slices/validators"; +import { MyValidatorsFilter } from "./MyValidatorsFilter"; +import { QuickAccessList } from "./QuickAccessList"; + +type Props = { + validators: Validator[]; + stakedAmountByAddress: Record<string, BigNumber>; + updatedAmountByAddress: Record<string, BigNumber>; + onChangeSearch: (searchStr: string) => void; + onFilterByMyValidators: (showMyValidators: boolean) => void; + onRandomize?: () => void; + onlyMyValidators: boolean; +}; + +export const ValidatorFilterNav = ({ + validators, + stakedAmountByAddress, + updatedAmountByAddress, + onChangeSearch, + onFilterByMyValidators, + onRandomize, + onlyMyValidators, +}: Props): JSX.Element => { + // this is super ugly :( + const focusOnValidator = (validator: Validator): void => { + setTimeout(() => { + const input = document.querySelector( + `[data-validator-input="${validator.address}"]` + ); + if (input) { + (input as HTMLInputElement).focus(); + } + }, 100); + }; + + return ( + <Stack direction="horizontal" gap={2} className="w-full items-center mb-2"> + <div className="w-[300px]"> + <Search + onChange={(value: string) => onChangeSearch(value)} + placeholder="Search Validator" + /> + </div> + <MyValidatorsFilter + value={onlyMyValidators ? "my-validators" : "all"} + onChange={(filter: string) => + onFilterByMyValidators(filter === "my-validators") + } + /> + <QuickAccessList + validators={validators} + onClick={(validator: Validator) => { + onFilterByMyValidators(true); + focusOnValidator(validator); + }} + updatedAmountByAddress={updatedAmountByAddress} + stakedAmountByAddress={stakedAmountByAddress} + /> + {onRandomize && ( + <ActionButton + type="button" + onClick={onRandomize} + color="white" + className="ml-auto w-auto px-8" + size="sm" + borderRadius="sm" + > + Randomise + </ActionButton> + )} + </Stack> + ); +}; diff --git a/apps/namadillo/src/App/Staking/ValidatorInfoPanel.tsx b/apps/namadillo/src/App/Staking/ValidatorInfoPanel.tsx new file mode 100644 index 000000000..72edd6290 --- /dev/null +++ b/apps/namadillo/src/App/Staking/ValidatorInfoPanel.tsx @@ -0,0 +1,96 @@ +import { formatPercentage, shortenAddress } from "@namada/utils"; +import BigNumber from "bignumber.js"; +import clsx from "clsx"; +import { useAtomValue } from "jotai"; +import { GoGlobe } from "react-icons/go"; +import { IoClose } from "react-icons/io5"; +import { chainAtom } from "slices/chain"; +import { Validator } from "slices/validators"; +import { twMerge } from "tailwind-merge"; +import { ValidatorName } from "./ValidatorName"; + +type ValidatorInfoPanel = { + validator: Validator; + onClose: () => void; +} & React.ComponentPropsWithRef<"div">; + +export const ValidatorInfoPanel = ({ + validator, + onClose, + ...props +}: ValidatorInfoPanel): JSX.Element => { + const chain = useAtomValue(chainAtom); + const { + className: articleClassName, + onClick: articleClick, + ...articleProps + } = props; + + return ( + <article + className={twMerge( + "absolute w-[340px] min-h-[400px] bg-rblack border border-yellow", + "text-sm rounded-md px-5 pt-11 pb-6 z-50", + articleClassName + )} + onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { + e.stopPropagation(); + articleClick && articleClick(e); + }} + {...articleProps} + > + <header + className={clsx( + "flex justify-between pb-3 border-b border-neutral-700", + "mb-3" + )} + > + <ValidatorName validator={validator} showAddress={false} /> + {validator.homepageUrl && ( + <a + href={validator.homepageUrl} + target="_blank" + rel="nofollow noreferrer" + className="flex items-center text-xs gap-1 hover:text-cyan" + > + Web <GoGlobe /> + </a> + )} + </header> + <p>{shortenAddress(validator.address, 20, 8)}</p> + {validator.description && ( + <p className="bg-neutral-700 px-2.5 py-3 mt-3 rounded-sm"> + {validator.description} + </p> + )} + <dl + className={clsx( + "grid gap-y-1.5 grid-cols-2 justify-between mt-4 [&_dd]:text-right", + "border-b border-neutral-700 pb-5" + )} + > + <dt>Chain</dt> + <dd>{chain.alias}</dd> + <dt>Commission</dt> + <dd>{formatPercentage(validator.commission)}</dd> + <dt>Expected APR (%)</dt> + <dd>{formatPercentage(new BigNumber(validator.expectedApr))}</dd> + <dt>Voting Power</dt> + <dd> + {formatPercentage( + new BigNumber(validator.votingPowerPercentage ?? 0) + )} + </dd> + <dt>Unbonding Period</dt> + <dd>{validator.unbondingPeriod}</dd> + </dl> + + <i + onClick={onClose} + className="absolute text-xl top-2.5 right-3 cursor-pointer hover:text-cyan" + > + <IoClose /> + </i> + </article> + ); +}; diff --git a/apps/namadillo/src/App/Staking/ValidatorName.tsx b/apps/namadillo/src/App/Staking/ValidatorName.tsx new file mode 100644 index 000000000..2851890c2 --- /dev/null +++ b/apps/namadillo/src/App/Staking/ValidatorName.tsx @@ -0,0 +1,35 @@ +import { shortenAddress } from "@namada/utils"; +import { Validator } from "slices/validators"; +import { ValidatorThumb } from "./ValidatorThumb"; + +type ValidatorNameProps = { + validator: Validator; + showAddress?: boolean; + hasStake?: boolean; +}; + +export const ValidatorName = ({ + validator, + showAddress = true, + hasStake, +}: ValidatorNameProps): JSX.Element => { + return ( + <div className="flex items-center gap-4"> + <aside> + <ValidatorThumb + imageUrl={validator.imageUrl} + alt={validator.alias} + hasStake={hasStake} + /> + </aside> + <span className="leading-tight"> + <strong className="font-medium">{validator.alias}</strong> + {showAddress && ( + <small className="block text-xs text-neutral-500"> + {shortenAddress(validator.address, 6, 12)} + </small> + )} + </span> + </div> + ); +}; diff --git a/apps/namadillo/src/App/Staking/ValidatorThumb.tsx b/apps/namadillo/src/App/Staking/ValidatorThumb.tsx new file mode 100644 index 000000000..beac1e94d --- /dev/null +++ b/apps/namadillo/src/App/Staking/ValidatorThumb.tsx @@ -0,0 +1,41 @@ +import clsx from "clsx"; +import { PiStackBold } from "react-icons/pi"; +import { twMerge } from "tailwind-merge"; + +type ValidatorThumbProps = { + className?: string; + imageUrl?: string; + alt: string; + hasStake?: boolean; +}; + +export const ValidatorThumb = ({ + className, + imageUrl, + alt, + hasStake = false, +}: ValidatorThumbProps): JSX.Element => { + return ( + <span className="relative"> + <img + src={imageUrl} + alt={alt} + title={alt} + className={twMerge( + "rounded-full aspect-square w-8 bg-neutral-900", + className + )} + /> + {hasStake && ( + <i + className={clsx( + "absolute -top-0.5 -right-1 rounded-full bg-yellow", + "flex items-center justify-center text-black text-[10px] p-0.5" + )} + > + <PiStackBold /> + </i> + )} + </span> + ); +}; diff --git a/apps/namadillo/src/App/Staking/ValidatorsTable.tsx b/apps/namadillo/src/App/Staking/ValidatorsTable.tsx new file mode 100644 index 000000000..f2dbbf970 --- /dev/null +++ b/apps/namadillo/src/App/Staking/ValidatorsTable.tsx @@ -0,0 +1,172 @@ +import { StyledTable, TableHeader, TableRow } from "@namada/components"; +import { FormattedPaginator } from "App/Common/FormattedPaginator"; +import BigNumber from "bignumber.js"; +import clsx from "clsx"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { GoInfo } from "react-icons/go"; +import { Validator } from "slices/validators"; +import { twMerge } from "tailwind-merge"; +import { ValidatorInfoPanel } from "./ValidatorInfoPanel"; + +type ValidatorsTableProps = { + id: string; + headers: (TableHeader | React.ReactNode)[]; + validatorList: Validator[]; + renderRow: (validator: Validator) => TableRow; + resultsPerPage?: number; + initialPage?: number; + tableClassName?: string; + updatedAmountByAddress?: Record<string, BigNumber | undefined>; +}; + +export const ValidatorsTable = ({ + id, + headers, + renderRow, + validatorList, + resultsPerPage = 100, + initialPage = 0, + tableClassName, + updatedAmountByAddress, +}: ValidatorsTableProps): JSX.Element => { + const containerRef = useRef<HTMLDivElement>(null); + const [page, setPage] = useState(initialPage); + const [rows, setRows] = useState<TableRow[]>([]); + const [selectedValidator, setSelectedValidator] = useState< + Validator | undefined + >(); + + const updatesTracker = useRef<typeof updatedAmountByAddress>( + updatedAmountByAddress || {} + ); + + const paginatedValidators = validatorList.slice( + page * resultsPerPage, + page * resultsPerPage + resultsPerPage + ); + + const pageCount = Math.ceil(validatorList.length / resultsPerPage); + + useEffect(() => { + setPage(0); + }, [validatorList]); + + useEffect(() => { + const onCloseInfo = (): void => setSelectedValidator(undefined); + document.documentElement.addEventListener("click", onCloseInfo); + return () => { + document.documentElement.removeEventListener("click", onCloseInfo); + }; + }, []); + + // Update all validators + useEffect(() => { + setRows(paginatedValidators.map(mapValidatorRow)); + }, [renderRow, page, validatorList]); + + // Only updates the addresses contained in updatedAmountByAddress that have changed + useEffect(() => { + if (!updatedAmountByAddress) return; + setRows((rows) => { + const newRows = [...rows]; + paginatedValidators.forEach((validator, idx) => { + const skipUpdate = + (!updatesTracker.current![validator.address] && + !updatedAmountByAddress[validator.address]) || + updatesTracker.current![validator.address] === + updatedAmountByAddress[validator.address]; + + if (skipUpdate) return; + newRows[idx] = mapValidatorRow(validator); + }); + updatesTracker.current = { ...updatedAmountByAddress }; + return newRows; + }); + }, [updatedAmountByAddress]); + + const mapValidatorRow = (validator: Validator): TableRow => { + const row = renderRow(validator); + row.cells.push( + <i + onClick={(e) => { + e.stopPropagation(); + setSelectedValidator(validator); + }} + className={clsx( + "cursor-pointer flex justify-end relative", + "hover:text-cyan active:top-px" + )} + > + <GoInfo /> + </i> + ); + return row; + }; + + const scrollTop = useCallback((): void => { + const container = containerRef.current!.querySelector(".table-container"); + if (container) { + container.scrollTo({ top: 0, left: 0 }); + } + }, []); + + const styledTable = useMemo(() => { + return ( + <StyledTable + id={id} + headers={headers.concat("")} + rows={rows} + containerClassName="table-container flex-1 dark-scrollbar overscroll-contain" + tableProps={{ + className: twMerge( + "w-full flex-1 [&_td]:px-1 [&_th]:px-1 [&_td:first-child]:pl-4 [&_td]:h-[64px]", + "[&_td]:font-normal [&_td:last-child]:pr-4 [&_th:first-child]:pl-4 [&_th:last-child]:pr-4", + "[&_td:first-child]:rounded-s-md [&_td:last-child]:rounded-e-md", + tableClassName + ), + }} + headProps={{ className: "text-neutral-500" }} + /> + ); + }, [rows, updatedAmountByAddress, tableClassName]); + + const pagination = useMemo(() => { + return ( + <FormattedPaginator + pageRangeDisplayed={3} + pageCount={pageCount} + onPageChange={({ selected }) => { + setPage(selected); + scrollTop(); + }} + /> + ); + }, [page, validatorList]); + + if (rows.length === 0) { + return ( + <div className="flex items-center justify-center text-sm py-4 text-neutral-200"> + No results were found + </div> + ); + } + + return ( + <div + ref={containerRef} + className={clsx( + "grid grid-rows-[auto_max-content] flex-1 overflow-hidden w-full gap-2" + )} + > + {styledTable} + {pagination} + {selectedValidator && ( + <ValidatorInfoPanel + className="h-full right-0 top-0" + validator={selectedValidator} + onClose={() => setSelectedValidator(undefined)} + /> + )} + </div> + ); +}; diff --git a/apps/namadillo/src/App/Staking/index.ts b/apps/namadillo/src/App/Staking/index.ts new file mode 100644 index 000000000..0c9e03239 --- /dev/null +++ b/apps/namadillo/src/App/Staking/index.ts @@ -0,0 +1 @@ +export { Staking } from "./Staking"; diff --git a/apps/namadillo/src/App/Staking/routes.ts b/apps/namadillo/src/App/Staking/routes.ts new file mode 100644 index 000000000..6a0f2143e --- /dev/null +++ b/apps/namadillo/src/App/Staking/routes.ts @@ -0,0 +1,36 @@ +import { RouteOutput, createRouteOutput } from "utils/routes"; + +export const index = (): string => `/staking`; + +const routeOutput = createRouteOutput(index); + +export const overview = (): RouteOutput => routeOutput(`/`); + +export const manage = (): RouteOutput => routeOutput("/manage"); + +export const incrementBonding = (): RouteOutput => + routeOutput("/bonding/increment"); + +export const redelegateBonding = (): RouteOutput => + routeOutput("/bonding/redelegate"); + +export const validatorDetails = (id: string | number): RouteOutput => + routeOutput(`/validator-details/${id}`); + +export const validatorDetailsOwner = ( + validatorId: string | number, + ownerId: string | number +): RouteOutput => routeOutput(`${validatorDetails(validatorId)}/${ownerId}`); + +export const unstake = (): RouteOutput => routeOutput("/bonding/unstake"); + +export default { + index, + manage, + overview, + validatorDetails, + validatorDetailsOwner, + incrementBonding, + redelegateBonding, + unstake, +}; diff --git a/apps/namadillo/src/custom.d.ts b/apps/namadillo/src/custom.d.ts new file mode 100644 index 000000000..bec8758ff --- /dev/null +++ b/apps/namadillo/src/custom.d.ts @@ -0,0 +1,8 @@ +declare module "*.svg" { + import React from "react"; + export const ReactComponent: React.FunctionComponent< + React.SVGProps<SVGSVGElement> + >; + const src: string; + export default src; +} diff --git a/apps/namadillo/src/hooks/index.ts b/apps/namadillo/src/hooks/index.ts new file mode 100644 index 000000000..1cc763ef0 --- /dev/null +++ b/apps/namadillo/src/hooks/index.ts @@ -0,0 +1 @@ +export * from "./useSdk"; diff --git a/apps/namadillo/src/hooks/useExtensionConnect.ts b/apps/namadillo/src/hooks/useExtensionConnect.ts new file mode 100644 index 000000000..dc99bd13a --- /dev/null +++ b/apps/namadillo/src/hooks/useExtensionConnect.ts @@ -0,0 +1,76 @@ +import { useIntegrationConnection } from "@namada/integrations"; +import { Chain } from "@namada/types"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; +import { useEffect, useState } from "react"; +import { addAccountsAtom, balancesAtom } from "slices/accounts"; +import { + addConnectedChainAtom, + namadaExtensionConnectedAtom, +} from "slices/settings"; + +export enum ConnectStatus { + IDLE, + CONNECTING, + CONNECTED, + ERROR, +} + +type UseConnectOutput = { + connectionStatus: ConnectStatus; + connect: () => Promise<void>; +}; + +export const useExtensionConnect = (chain: Chain): UseConnectOutput => { + const addAccounts = useSetAtom(addAccountsAtom); + const addConnectedChain = useSetAtom(addConnectedChainAtom); + useAtomValue(balancesAtom); + + const [extensionConnected, setExtensionConnected] = useAtom( + namadaExtensionConnectedAtom + ); + + const [connectionStatus, setConnectionStatus] = useState<ConnectStatus>( + extensionConnected ? ConnectStatus.CONNECTED : ConnectStatus.IDLE + ); + + const [integration, isConnectingToExtension, withConnection] = + useIntegrationConnection(chain.id); + + useEffect(() => { + if (isConnectingToExtension) { + setConnectionStatus(ConnectStatus.CONNECTING); + } + }, [isConnectingToExtension]); + + useEffect(() => { + if (extensionConnected) { + setConnectionStatus(ConnectStatus.CONNECTED); + addConnectedChain(chain.id); + } + }, [extensionConnected]); + + const handleConnectExtension = async (): Promise<void> => { + if (extensionConnected) return; + + withConnection( + async () => { + const accounts = await integration?.accounts(); + if (accounts) { + addAccounts(accounts); + addConnectedChain(chain.id); + } + setConnectionStatus(ConnectStatus.CONNECTED); + setExtensionConnected(true); + }, + async () => { + setConnectionStatus(ConnectStatus.ERROR); + setExtensionConnected(false); + } + ); + }; + + return { + connect: handleConnectExtension, + connectionStatus, + }; +}; diff --git a/apps/namadillo/src/hooks/useGasEstimate.ts b/apps/namadillo/src/hooks/useGasEstimate.ts new file mode 100644 index 000000000..8303d9c8c --- /dev/null +++ b/apps/namadillo/src/hooks/useGasEstimate.ts @@ -0,0 +1,25 @@ +import BigNumber from "bignumber.js"; +import { useAtomValue } from "jotai"; +import { minimumGasPriceAtom } from "slices/fees"; + +type useGasEstimateReturn = { + gasPrice: BigNumber | undefined; + calculateMinGasRequired: ( + numberOfTransactions?: number, + pkRevealNeeded?: number + ) => BigNumber | undefined; +}; + +export const useGasEstimate = (): useGasEstimateReturn => { + const gas = useAtomValue(minimumGasPriceAtom); + return { + gasPrice: gas.data, + calculateMinGasRequired: ( + numberOfTransactions: number = 1, + pkRevealNeeded: number = numberOfTransactions + ): BigNumber | undefined => { + if (!gas.isSuccess) return undefined; + return gas.data.multipliedBy(numberOfTransactions + pkRevealNeeded); + }, + }; +}; diff --git a/apps/namadillo/src/hooks/useOnChainChanged.ts b/apps/namadillo/src/hooks/useOnChainChanged.ts new file mode 100644 index 000000000..dc1b607cd --- /dev/null +++ b/apps/namadillo/src/hooks/useOnChainChanged.ts @@ -0,0 +1,13 @@ +import { useEffectSkipFirstRender } from "@namada/hooks"; +import { useAtomValue, useSetAtom } from "jotai"; +import { chainAtom } from "slices/chain"; +import { isRevealPkNeededAtom, minimumGasPriceAtom } from "slices/fees"; + +export const useOnChainChanged = (): void => { + const chain = useAtomValue(chainAtom); + useAtomValue(minimumGasPriceAtom); + const refreshPublicKeys = useSetAtom(isRevealPkNeededAtom); + useEffectSkipFirstRender(() => { + refreshPublicKeys(); + }, [chain]); +}; diff --git a/apps/namadillo/src/hooks/useOnNamadaExtensionAttached.ts b/apps/namadillo/src/hooks/useOnNamadaExtensionAttached.ts new file mode 100644 index 000000000..8484d779b --- /dev/null +++ b/apps/namadillo/src/hooks/useOnNamadaExtensionAttached.ts @@ -0,0 +1,25 @@ +import { useEffectSkipFirstRender } from "@namada/hooks"; +import { + Namada, + useIntegration, + useUntilIntegrationAttached, +} from "@namada/integrations"; +import { useAtomValue, useSetAtom } from "jotai"; +import { chainAtom } from "slices/chain"; +import { namadaExtensionConnectedAtom } from "slices/settings"; + +export const useOnNamadaExtensionAttached = (): void => { + const setNamadaExtensionConnected = useSetAtom(namadaExtensionConnectedAtom); + const chain = useAtomValue(chainAtom); // should always be namada + const { namada: attachStatus } = useUntilIntegrationAttached(chain); + const integration = useIntegration("namada") as Namada; + + useEffectSkipFirstRender(() => { + (async () => { + if (attachStatus === "attached") { + const connected = !!(await integration.isConnected()); + setNamadaExtensionConnected(connected); + } + })(); + }, [attachStatus]); +}; diff --git a/apps/namadillo/src/hooks/useOnNamadaExtensionConnected.ts b/apps/namadillo/src/hooks/useOnNamadaExtensionConnected.ts new file mode 100644 index 000000000..36c44da69 --- /dev/null +++ b/apps/namadillo/src/hooks/useOnNamadaExtensionConnected.ts @@ -0,0 +1,17 @@ +import { useAtomValue, useSetAtom } from "jotai"; +import { useEffect } from "react"; +import { fetchAccountsAtom, fetchDefaultAccountAtom } from "slices/accounts"; +import { namadaExtensionConnectedAtom } from "slices/settings"; + +export const useOnNamadaExtensionConnected = (): void => { + const connected = useAtomValue(namadaExtensionConnectedAtom); + const fetchAccounts = useSetAtom(fetchAccountsAtom); + const fetchDefaultAccount = useSetAtom(fetchDefaultAccountAtom); + + useEffect(() => { + if (connected) { + fetchAccounts(); + fetchDefaultAccount(); + } + }, [connected]); +}; diff --git a/apps/namadillo/src/hooks/useSdk.tsx b/apps/namadillo/src/hooks/useSdk.tsx new file mode 100644 index 000000000..f4c3d9146 --- /dev/null +++ b/apps/namadillo/src/hooks/useSdk.tsx @@ -0,0 +1,49 @@ +import initSdk from "@heliax/namada-sdk/inline-init"; +import { Sdk, getSdk } from "@heliax/namada-sdk/web"; +import { createContext, useContext, useEffect, useState } from "react"; + +const { + NAMADA_INTERFACE_NAMADA_URL: rpcUrl = "http://localhost:27657", + NAMADA_INTERFACE_NAMADA_TOKEN: + token = "tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e", +} = process.env; + +export const SdkContext = createContext<Sdk | null>(null); + +const initializeSdk = async (): Promise<Sdk> => { + const { cryptoMemory } = await initSdk(); + const sdk = getSdk(cryptoMemory, rpcUrl, "", token); + return sdk; +}; + +// Global instance of initialized SDK +const sdkInstance = initializeSdk(); + +// Helper to access SDK instance +export const getSdkInstance = async (): Promise<Sdk> => sdkInstance; + +export const SdkProvider: React.FC = ({ children }) => { + const [sdk, setSdk] = useState<Sdk>(); + + useEffect(() => { + getSdkInstance().then((sdk) => setSdk(sdk)); + }, []); + + return ( + <> + {sdk ? + <SdkContext.Provider value={sdk}> {children} </SdkContext.Provider> + : null} + </> + ); +}; + +export const useSdk = (): Sdk => { + const sdkContext = useContext(SdkContext); + + if (!sdkContext) { + throw new Error("sdkContext has to be used within <SdkContext.Provider>"); + } + + return sdkContext; +}; diff --git a/apps/namadillo/src/hooks/useStakeModule.ts b/apps/namadillo/src/hooks/useStakeModule.ts new file mode 100644 index 000000000..f3f853e55 --- /dev/null +++ b/apps/namadillo/src/hooks/useStakeModule.ts @@ -0,0 +1,95 @@ +import { Account } from "@namada/types"; +import BigNumber from "bignumber.js"; +import { useAtomValue } from "jotai"; +import { useEffect, useState } from "react"; +import { totalNamBalanceAtom } from "slices/accounts"; +import { Validator, myValidatorsAtom } from "slices/validators"; +import { ChangeInStakingPosition } from "types/staking"; +import { ValidatorAddress } from "types/validators"; + +type UseStakeModuleProps = { + account: Account | undefined; +}; + +//eslint-disable-next-line +export const useStakeModule = ({ account }: UseStakeModuleProps) => { + const totalNam = useAtomValue(totalNamBalanceAtom).data || BigNumber(0); + const myValidators = useAtomValue(myValidatorsAtom); + + const [stakedAmountByAddress, setStakedAmountsByAddress] = useState< + Record<ValidatorAddress, BigNumber> + >({}); + + const [updatedAmountByAddress, setUpdatedAmountByAddress] = useState< + Record<ValidatorAddress, BigNumber> + >({}); + + const totalUpdatedAmount = BigNumber.sum( + 0, + ...Object.values(updatedAmountByAddress) + ); + + const totalStakedAmount = BigNumber.sum( + 0, + ...Object.values(stakedAmountByAddress) + ); + + const totalNamAfterStaking = BigNumber.max( + totalNam.minus(totalUpdatedAmount), + 0 + ); + + const totalAmountToDelegate = + totalUpdatedAmount.lt(0) ? totalUpdatedAmount.abs() : new BigNumber(0); + + const onChangeValidatorAmount = ( + validator: Validator, + amount: BigNumber | undefined + ): void => { + setUpdatedAmountByAddress((obj: Record<ValidatorAddress, BigNumber>) => { + const newObj = { + ...obj, + }; + + if (!amount) { + if (newObj.hasOwnProperty(validator.address)) { + delete newObj[validator.address]; + } + } else { + newObj[validator.address] = amount; + } + + return newObj; + }); + }; + + const parseUpdatedAmounts = (): ChangeInStakingPosition[] => { + return Object.keys(updatedAmountByAddress).map((validatorAddress) => ({ + validatorId: validatorAddress, + amount: updatedAmountByAddress[validatorAddress], + })); + }; + + useEffect(() => { + if (!myValidators.isSuccess || !account) return; + + const stakedAmounts: Record<ValidatorAddress, BigNumber> = {}; + for (const myValidator of myValidators.data) { + stakedAmounts[myValidator.validator.address] = + myValidator.stakedAmount || new BigNumber(0); + } + setStakedAmountsByAddress(stakedAmounts); + }, [myValidators, account]); + + return { + totalUpdatedAmount, + totalNamAfterStaking, + totalStakedAmount, + totalAmountToDelegate, + parseUpdatedAmounts, + myValidators, + stakedAmountByAddress, + updatedAmountByAddress, + onChangeValidatorAmount, + }; +}; diff --git a/apps/namadillo/src/hooks/useTransactionService.ts b/apps/namadillo/src/hooks/useTransactionService.ts new file mode 100644 index 000000000..966ce197b --- /dev/null +++ b/apps/namadillo/src/hooks/useTransactionService.ts @@ -0,0 +1,66 @@ +import { useAtomValue, useSetAtom } from "jotai"; +import { + PreparedTransaction, + TransactionNotification, + broadcastTx, +} from "lib/query"; +import { useEffect } from "react"; +import { + dispatchToastNotificationAtom, + filterToastNotificationsAtom, +} from "slices/notifications"; +import { + clearTransactionQueueAtom, + transactionsAtom, +} from "slices/transactions"; + +export const useTransactionService = (): void => { + const transactions = useAtomValue(transactionsAtom); + const clearTransactions = useSetAtom(clearTransactionQueueAtom); + const dispatchNotification = useSetAtom(dispatchToastNotificationAtom); + const filterNotifications = useSetAtom(filterToastNotificationsAtom); + + const handleNotification = ( + id: string, + notification: TransactionNotification, + type: "error" | "success" + ): void => { + dispatchNotification({ + id, + type, + title: notification[type]?.title || "", + description: notification[type]?.text || "", + timeout: 5000, + }); + }; + + const handleTransaction = async ( + transaction: PreparedTransaction + ): Promise<void> => { + const transactionId = transaction.encodedTx.hash(); + let notificationType: "success" | "error" = "success"; + + try { + await broadcastTx(transaction); + // TODO: this is dismissing all notifications, we should dismiss by some criterea + filterNotifications((notification) => notification.type !== "pending"); + } catch (err) { + notificationType = "error"; + } + + if (transaction.notification) { + handleNotification( + transactionId, + transaction.notification, + notificationType + ); + } + }; + + useEffect(() => { + if (transactions.length > 0) { + transactions.forEach((transaction) => handleTransaction(transaction)); + clearTransactions(); + } + }, [transactions]); +}; diff --git a/apps/namadillo/src/hooks/useValidatorFilter.tsx b/apps/namadillo/src/hooks/useValidatorFilter.tsx new file mode 100644 index 000000000..a4b320df7 --- /dev/null +++ b/apps/namadillo/src/hooks/useValidatorFilter.tsx @@ -0,0 +1,35 @@ +import { useMemo } from "react"; +import { Validator } from "slices/validators"; + +type Props = { + validators: Validator[]; + myValidatorsAddresses: string[]; + searchTerm: string; + onlyMyValidators: boolean; +}; + +const filterValidators = (validator: Validator, search: string): boolean => { + if (!search) return true; + const preparedSearch = search.toLowerCase().trim(); + return ( + validator.address.toLowerCase().indexOf(preparedSearch) > -1 || + validator.alias.toLowerCase().indexOf(preparedSearch) > -1 + ); +}; + +export const useValidatorFilter = ({ + validators, + myValidatorsAddresses, + searchTerm, + onlyMyValidators, +}: Props): Validator[] => { + return useMemo(() => { + return validators.filter((v) => { + const keep = filterValidators(v, searchTerm); + if (keep && onlyMyValidators) { + return myValidatorsAddresses.indexOf(v.address) >= 0; + } + return keep; + }); + }, [validators, searchTerm, onlyMyValidators]); +}; diff --git a/apps/namadillo/src/hooks/useValidatorSorting.ts b/apps/namadillo/src/hooks/useValidatorSorting.ts new file mode 100644 index 000000000..550d9d81f --- /dev/null +++ b/apps/namadillo/src/hooks/useValidatorSorting.ts @@ -0,0 +1,31 @@ +import BigNumber from "bignumber.js"; +import { useMemo } from "react"; +import { Validator } from "slices/validators"; + +type useValidatorSortingProps = { + validators: Validator[]; + seed: number; + updatedAmountByAddress: Record<string, BigNumber>; +}; + +export const useValidatorSorting = ({ + validators, + seed, + updatedAmountByAddress, +}: useValidatorSortingProps): Validator[] => { + return useMemo(() => { + return validators + .map((v) => ({ value: v, sort: Math.random() })) + .sort((validator1, validator2) => { + const v1IsUpdated = !!updatedAmountByAddress[validator1.value.address]; + const v2IsUpdated = !!updatedAmountByAddress[validator2.value.address]; + + // Then addresses that were updated (increased / decreased staking) + if (v1IsUpdated && !v2IsUpdated) return -1; + if (!v1IsUpdated && v2IsUpdated) return 1; + + return validator1.sort - validator2.sort; + }) + .map(({ value }) => value); + }, [validators, seed]); +}; diff --git a/apps/namadillo/src/index.tsx b/apps/namadillo/src/index.tsx new file mode 100644 index 000000000..aeea2e443 --- /dev/null +++ b/apps/namadillo/src/index.tsx @@ -0,0 +1,34 @@ +import { init as initShared } from "@namada/shared/src/init-inline"; +import { SdkProvider } from "hooks/useSdk"; +import React from "react"; +import { createRoot } from "react-dom/client"; +import { RouterProvider } from "react-router-dom"; +import { getRouter } from "./App/AppRoutes"; +import reportWebVitals from "./reportWebVitals"; +import { ExtensionEventsProvider, IntegrationsProvider } from "./services"; + +import "@namada/components/src/base.css"; +import "./tailwind.css"; + +// TODO: we could show the loading screen while initShared is pending +const container = document.getElementById("root"); +if (container) { + const root = createRoot(container); + initShared().then(() => { + root.render( + <React.StrictMode> + <IntegrationsProvider> + <SdkProvider> + <ExtensionEventsProvider> + <RouterProvider router={getRouter()} /> + </ExtensionEventsProvider> + </SdkProvider> + </IntegrationsProvider> + </React.StrictMode> + ); + }); +} +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/apps/namadillo/src/lib/query.ts b/apps/namadillo/src/lib/query.ts new file mode 100644 index 000000000..db488485c --- /dev/null +++ b/apps/namadillo/src/lib/query.ts @@ -0,0 +1,195 @@ +import { EncodedTx } from "@heliax/namada-sdk/web"; +import { getIntegration } from "@namada/integrations"; +import { + Account, + Chain, + Signer, + WrapperTxMsgValue, + WrapperTxProps, +} from "@namada/types"; +import { getSdkInstance } from "hooks"; +import invariant from "invariant"; +import { GasConfig } from "types/fees"; + +const { + NAMADA_INTERFACE_NAMADA_TOKEN: + nativeToken = "tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e", +} = process.env; + +export type TransactionPair<T> = { + encodedTxData: EncodedTxData<T>; + signedTx: Uint8Array; +}; + +export type EncodedTxData<T> = { + type: string; + encodedTx: EncodedTx; + meta?: { + props: T; + }; +}; + +export type TransactionNotification = { + success?: { title: string; text: string }; + error?: { title: string; text: string }; +}; + +export type PreparedTransaction = { + encodedTx: EncodedTx; + signedTx: Uint8Array; + notification?: TransactionNotification; +}; + +export const revealPublicKeyType = "revealPublicKey"; + +const getTxProps = ( + account: Account, + gasConfig: GasConfig, + chain: Chain +): WrapperTxMsgValue => { + const address = nativeToken; + invariant(!!address, "Invalid currency address"); + invariant( + !!account.publicKey, + "Account doesn't contain a publicKey attached to it" + ); + + return { + token: address!, + feeAmount: gasConfig.gasPrice, + gasLimit: gasConfig.gasLimit, + chainId: chain.chainId, + publicKey: account.publicKey!, + memo: "", + }; +}; + +/** + * Builds an array of encoded transactions based on the provided query properties. + * Each transaction is processed through the provided transaction function `txFn`. + * @param {T[]} queryProps - An array of properties used to build transactions. + * @param {(WrapperTxProps, T) => Promise<EncodedTx>} txFn - Function to build each transaction. + */ +export const buildTxArray = async <T>( + account: Account, + gasConfig: GasConfig, + chain: Chain, + queryProps: T[], + txFn: (wrapperTxProps: WrapperTxProps, props: T) => Promise<EncodedTx> +): Promise<EncodedTxData<T>[]> => { + const { tx, rpc } = await getSdkInstance(); + const wrapperTxProps = getTxProps(account, gasConfig, chain); + const txArray: EncodedTxData<T>[] = []; + + // Determine if RevealPK is needed: + const pk = await rpc.queryPublicKey(account.address); + if (!pk) { + const revealPkTx = await tx.buildRevealPk(wrapperTxProps, account.address); + txArray.push({ type: revealPublicKeyType, encodedTx: revealPkTx }); + } + + const encodedTxs = await Promise.all( + queryProps.map((props) => txFn.apply(tx, [wrapperTxProps, props])) + ); + + const typedEncodedTxs = encodedTxs.map((encodedTx, index) => ({ + encodedTx, + type: txFn.name, + meta: { + props: queryProps[index], + }, + })); + + txArray.push(...typedEncodedTxs); + return txArray; +}; + +/** + * Asynchronously signs an array of encoded transactions using Namada extension. + */ +export const signTxArray = async <T>( + chain: Chain, + typedEncodedTxs: EncodedTxData<T>[], + owner: string +): Promise<Uint8Array[]> => { + const integration = getIntegration(chain.id); + const signingClient = integration.signer() as Signer; + try { + // TODO: after rejecting, it keeps awaiting forever + const signedTxs = await signingClient.sign( + owner, + typedEncodedTxs.map(({ encodedTx }) => encodedTx.tx) + ); + + if (!signedTxs) { + throw new Error("Signing failed"); + } + + return signedTxs; + } catch (err) { + throw new Error("Signing failed"); + } +}; + +/** + * Builds an array of **transaction pairs**. Each transaction pair consists of a signed + * transaction and its corresponding encoded transaction data. + * + * Encoded transaction data includes the transaction itself along with additional metadata + * that holds the initial values used for its creation. + */ +export const buildTxPair = async <T>( + account: Account, + gasConfig: GasConfig, + chain: Chain, + queryProps: T[], + txFn: (wrapperTxProps: WrapperTxProps, props: T) => Promise<EncodedTx>, + owner: string +): Promise<TransactionPair<T>[]> => { + const encodedTxs = await buildTxArray<T>( + account, + gasConfig, + chain, + queryProps, + txFn + ); + const signedTxs = await signTxArray<T>(chain, encodedTxs, owner); + return signedTxs.map( + (tx, index): TransactionPair<T> => ({ + signedTx: tx, + encodedTxData: encodedTxs[index], + }) + ); +}; + +export const prepareTxs = <T>( + transactions: TransactionPair<T>[], + buildNotification?: (props: T) => TransactionNotification +): PreparedTransaction[] => { + return transactions.map((pair: TransactionPair<T>): PreparedTransaction => { + if (pair.encodedTxData.type === revealPublicKeyType) { + return { + encodedTx: pair.encodedTxData.encodedTx, + signedTx: pair.signedTx, + }; + } + return { + encodedTx: pair.encodedTxData.encodedTx, + signedTx: pair.signedTx, + notification: + buildNotification && pair.encodedTxData.meta?.props ? + buildNotification(pair.encodedTxData.meta.props) + : undefined, + }; + }); +}; + +export const broadcastTx = async ( + transaction: PreparedTransaction +): Promise<void> => { + const { rpc } = await getSdkInstance(); + await rpc.broadcastTx({ + wrapperTxMsg: transaction.encodedTx.txMsg, + tx: transaction.signedTx, + }); +}; diff --git a/apps/namadillo/src/lib/staking.ts b/apps/namadillo/src/lib/staking.ts new file mode 100644 index 000000000..83a1a269b --- /dev/null +++ b/apps/namadillo/src/lib/staking.ts @@ -0,0 +1,145 @@ +import BigNumber from "bignumber.js"; +import { RedelegateChange } from "types/staking"; + +export const getReducedAmounts = ( + stakedAmounts: Record<string, BigNumber>, + updatedAmounts: Record<string, BigNumber> +): Record<string, BigNumber> => { + const reducedAmounts: Record<string, BigNumber> = {}; + for (const address in updatedAmounts) { + if (!stakedAmounts.hasOwnProperty(address)) continue; + if (stakedAmounts[address].gt(updatedAmounts[address])) { + reducedAmounts[address] = stakedAmounts[address].minus( + updatedAmounts[address] + ); + } + } + return reducedAmounts; +}; + +export const getIncrementedAmounts = ( + stakedAmounts: Record<string, BigNumber>, + updatedAmounts: Record<string, BigNumber> +): Record<string, BigNumber> => { + const incrementedAmounts: Record<string, BigNumber> = {}; + for (const address in updatedAmounts) { + if ( + !stakedAmounts[address] || + updatedAmounts[address].gt(stakedAmounts[address]) + ) { + incrementedAmounts[address] = updatedAmounts[address].minus( + stakedAmounts[address] || 0 + ); + } + } + return incrementedAmounts; +}; + +export const getPendingToDistributeAmount = ( + stakedAmounts: Record<string, BigNumber>, + updatedAmounts: Record<string, BigNumber> +): BigNumber => { + const incrementedAmounts = Object.values( + getIncrementedAmounts(stakedAmounts, updatedAmounts) + ); + const decreasedAmounts = Object.values( + getReducedAmounts(stakedAmounts, updatedAmounts) + ); + + const incrementedTotal = + incrementedAmounts.length ? + BigNumber.sum(...incrementedAmounts) + : BigNumber(0); + + const decreasedTotal = + decreasedAmounts.length ? BigNumber.sum(...decreasedAmounts) : BigNumber(0); + + return decreasedTotal.minus(incrementedTotal); +}; + +export const buildRedelegateChange = ( + sourceValidator: string, + destinationValidator: string, + amount: BigNumber +): RedelegateChange => ({ sourceValidator, destinationValidator, amount }); + +export const getAmountDistribution = ( + reducedAmounts: Record<string, BigNumber>, + increasedAmounts: Record<string, BigNumber> +): RedelegateChange[] => { + const redelegateChanges: RedelegateChange[] = []; + + // If two addresses are present in both objects, fix the assigned amounts + for (const address in reducedAmounts) { + if (increasedAmounts.hasOwnProperty(address)) { + const diff = reducedAmounts[address].minus(increasedAmounts[address]); + if (diff.eq(0)) { + delete reducedAmounts[address]; + delete increasedAmounts[address]; + continue; + } + + if (diff.gt(0)) { + delete increasedAmounts[address]; + reducedAmounts[address] = diff; + continue; + } + + if (diff.lt(0)) { + delete reducedAmounts[address]; + increasedAmounts[address] = diff.abs(); + } + } + } + + const addressesToReduce = Object.keys(reducedAmounts).sort( + (address1: string, address2: string) => + reducedAmounts[address1].gt(reducedAmounts[address2]) ? -1 : 1 + ); + + const addressesToIncrement = Object.keys(increasedAmounts).sort( + (address1: string, address2: string) => + increasedAmounts[address1].gt(increasedAmounts[address2]) ? 1 : -1 + ); + + for (const addressToReduce of addressesToReduce) { + let totalToReduce = reducedAmounts[addressToReduce]; + let shift = false; + + for (const addressToIncrement of addressesToIncrement) { + const increment = increasedAmounts[addressToIncrement]; + const redelegate = buildRedelegateChange.bind( + null, + addressToReduce, + addressToIncrement + ); + + // redelegating a partial of the current source address to another + if (totalToReduce.gt(increment)) { + totalToReduce = totalToReduce.minus(increment); + redelegateChanges.push(redelegate(increment)); + shift = true; + continue; + } + + // we're just decreasing the same amount left + if (totalToReduce.eq(increment)) { + redelegateChanges.push(redelegate(increment)); + shift = true; + break; + } + + // redelegating all we have from the current source address, but we need more. + redelegateChanges.push(redelegate(totalToReduce)); + increasedAmounts[addressToIncrement] = + increasedAmounts[addressToIncrement].minus(totalToReduce); + break; + } + + if (shift) { + addressesToIncrement.shift(); + } + } + + return redelegateChanges; +}; diff --git a/apps/namadillo/src/lib/tests/staking.test.ts b/apps/namadillo/src/lib/tests/staking.test.ts new file mode 100644 index 000000000..1070a25fc --- /dev/null +++ b/apps/namadillo/src/lib/tests/staking.test.ts @@ -0,0 +1,78 @@ +import BigNumber from "bignumber.js"; +import { getAmountDistribution } from "../staking"; + +describe("Testing lib/staking functions", () => { + // We're assuming that increments is always equal decrements amount + + // Regular test case + const reducedAmounts1 = { + a: BigNumber(20), + b: BigNumber(100), + }; + + const assignedAmounts1 = { + c: BigNumber(25), + d: BigNumber(95), + }; + + // Distributing to same validator + const reducedAmounts2 = { + a: BigNumber(20), + b: BigNumber(100), + c: BigNumber(20), + }; + + const assignedAmounts2 = { + a: BigNumber(10), + c: BigNumber(110), + d: BigNumber(20), + }; + + // Zeoring an address + const reducedAmounts3 = { a: BigNumber(100) }; + const assignedAmounts3 = { b: BigNumber(100) }; + + it("should calculate the correct distribution", () => { + // More than one distribution + const distribution1 = getAmountDistribution( + reducedAmounts1, + assignedAmounts1 + ); + expect(distribution1[0].sourceValidator).toBe("b"); + expect(distribution1[0].destinationValidator).toBe("c"); + expect(distribution1[0].amount.toString()).toBe("25"); + + expect(distribution1[1].sourceValidator).toBe("b"); + expect(distribution1[1].destinationValidator).toBe("d"); + expect(distribution1[1].amount.toString()).toBe("75"); + + expect(distribution1[2].sourceValidator).toBe("a"); + expect(distribution1[2].destinationValidator).toBe("d"); + expect(distribution1[2].amount.toString()).toBe("20"); + + // Distributing to the same validator because of reasons + const distribution2 = getAmountDistribution( + reducedAmounts2, + assignedAmounts2 + ); + expect(distribution2.length).toBe(3); + expect(distribution2[0].sourceValidator).toBe("b"); + expect(distribution2[0].destinationValidator).toBe("d"); + expect(distribution2[0].amount.toString()).toBe("20"); + expect(distribution2[1].sourceValidator).toBe("b"); + expect(distribution2[1].destinationValidator).toBe("c"); + expect(distribution2[1].amount.toString()).toBe("80"); + expect(distribution2[2].sourceValidator).toBe("a"); + expect(distribution2[2].destinationValidator).toBe("c"); + expect(distribution2[2].amount.toString()).toBe("10"); + + // Only one distribution + const distribution3 = getAmountDistribution( + reducedAmounts3, + assignedAmounts3 + ); + expect(distribution3[0].sourceValidator).toBe("a"); + expect(distribution3[0].destinationValidator).toBe("b"); + expect(distribution3[0].amount.toString()).toBe("100"); + }); +}); diff --git a/apps/namadillo/src/react-app.env.d.ts b/apps/namadillo/src/react-app.env.d.ts new file mode 100644 index 000000000..6431bc5fc --- /dev/null +++ b/apps/namadillo/src/react-app.env.d.ts @@ -0,0 +1 @@ +/// <reference types="react-scripts" /> diff --git a/apps/namadillo/src/reportWebVitals.ts b/apps/namadillo/src/reportWebVitals.ts new file mode 100644 index 000000000..41784a366 --- /dev/null +++ b/apps/namadillo/src/reportWebVitals.ts @@ -0,0 +1,15 @@ +import { ReportHandler } from "web-vitals"; + +const reportWebVitals = (onPerfEntry?: ReportHandler): void => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/apps/namadillo/src/services/extensionEvents/handlers/index.ts b/apps/namadillo/src/services/extensionEvents/handlers/index.ts new file mode 100644 index 000000000..279a6115c --- /dev/null +++ b/apps/namadillo/src/services/extensionEvents/handlers/index.ts @@ -0,0 +1 @@ +export * from "./namada"; diff --git a/apps/namadillo/src/services/extensionEvents/handlers/namada.ts b/apps/namadillo/src/services/extensionEvents/handlers/namada.ts new file mode 100644 index 000000000..98f39ab52 --- /dev/null +++ b/apps/namadillo/src/services/extensionEvents/handlers/namada.ts @@ -0,0 +1,11 @@ +import { Namada } from "@namada/integrations"; + +export const NamadaConnectionRevokedHandler = + ( + integration: Namada, + setNamadaExtensionConnected: (connected: boolean) => void + ) => + async () => { + const connected = !!(await integration.isConnected()); + setNamadaExtensionConnected(connected); + }; diff --git a/apps/namadillo/src/services/extensionEvents/index.ts b/apps/namadillo/src/services/extensionEvents/index.ts new file mode 100644 index 000000000..1a8f850f5 --- /dev/null +++ b/apps/namadillo/src/services/extensionEvents/index.ts @@ -0,0 +1 @@ +export * from "./provider"; diff --git a/apps/namadillo/src/services/extensionEvents/provider.tsx b/apps/namadillo/src/services/extensionEvents/provider.tsx new file mode 100644 index 000000000..ac0098045 --- /dev/null +++ b/apps/namadillo/src/services/extensionEvents/provider.tsx @@ -0,0 +1,40 @@ +import { useEventListenerOnce } from "@namada/hooks"; +import { Namada, useIntegration } from "@namada/integrations"; +import { Events } from "@namada/types"; +import { useAtomValue, useSetAtom } from "jotai"; +import { createContext } from "react"; +import { balancesAtom, fetchDefaultAccountAtom } from "slices/accounts"; +import { namadaExtensionConnectedAtom } from "slices/settings"; +import { NamadaConnectionRevokedHandler } from "./handlers"; + +export const ExtensionEventsContext = createContext({}); + +export const ExtensionEventsProvider: React.FC = (props): JSX.Element => { + const namadaIntegration = useIntegration("namada"); + const fetchDefaultAccount = useSetAtom(fetchDefaultAccountAtom); + const balances = useAtomValue(balancesAtom); + const setNamadaExtensionConnected = useSetAtom(namadaExtensionConnectedAtom); + + // Instantiate handlers: + const namadaConnectionRevokedHandler = NamadaConnectionRevokedHandler( + namadaIntegration as Namada, + setNamadaExtensionConnected + ); + + // Register handlers: + useEventListenerOnce(Events.AccountChanged, async () => { + await fetchDefaultAccount(); + balances.refetch(); + }); + + useEventListenerOnce( + Events.ConnectionRevoked, + namadaConnectionRevokedHandler + ); + + return ( + <ExtensionEventsContext.Provider value={{}}> + {props.children} + </ExtensionEventsContext.Provider> + ); +}; diff --git a/apps/namadillo/src/services/index.ts b/apps/namadillo/src/services/index.ts new file mode 100644 index 000000000..fc69b4fc5 --- /dev/null +++ b/apps/namadillo/src/services/index.ts @@ -0,0 +1,2 @@ +export * from "./integrations"; +export * from "./extensionEvents"; diff --git a/apps/namadillo/src/services/integrations/index.ts b/apps/namadillo/src/services/integrations/index.ts new file mode 100644 index 000000000..1a8f850f5 --- /dev/null +++ b/apps/namadillo/src/services/integrations/index.ts @@ -0,0 +1 @@ +export * from "./provider"; diff --git a/apps/namadillo/src/services/integrations/provider.tsx b/apps/namadillo/src/services/integrations/provider.tsx new file mode 100644 index 000000000..c5c99fa8e --- /dev/null +++ b/apps/namadillo/src/services/integrations/provider.tsx @@ -0,0 +1,9 @@ +import { IntegrationsContext, integrations } from "@namada/integrations"; + +export const IntegrationsProvider: React.FC = (props): JSX.Element => { + return ( + <IntegrationsContext.Provider value={integrations}> + {props.children} + </IntegrationsContext.Provider> + ); +}; diff --git a/apps/namadillo/src/setupTests.ts b/apps/namadillo/src/setupTests.ts new file mode 100644 index 000000000..1dd407a63 --- /dev/null +++ b/apps/namadillo/src/setupTests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import "@testing-library/jest-dom"; diff --git a/apps/namadillo/src/slices/accounts.ts b/apps/namadillo/src/slices/accounts.ts new file mode 100644 index 000000000..96ab2d092 --- /dev/null +++ b/apps/namadillo/src/slices/accounts.ts @@ -0,0 +1,199 @@ +import { getIntegration } from "@namada/integrations"; +import { + Account as AccountDetails, + ChainKey, + CosmosTokenType, + TokenBalances, + TokenType, +} from "@namada/types"; +import BigNumber from "bignumber.js"; +import { getSdkInstance } from "hooks"; +import { atom } from "jotai"; +import { atomWithQuery } from "jotai-tanstack-query"; + +const { + NAMADA_INTERFACE_NAMADA_TOKEN: + tokenAddress = "tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e", +} = process.env; + +type Address = string; +type Details = AccountDetails; + +export type Balance = Partial<Record<TokenType, BigNumber>>; +export type Account = { details: Details; balance: Balance }; + +export type AccountsState = { + derived: Record<ChainKey, Record<Address, Account>>; +}; + +export const accountsAtom = atom<readonly AccountDetails[]>([]); + +export const addAccountsAtom = atom( + null, + (_get, set, accounts: readonly AccountDetails[]) => { + set(accountsAtom, accounts); + } +); + +export const transparentAccountsAtom = atom<readonly AccountDetails[]>((get) => + get(accountsAtom).filter((account) => !account.isShielded) +); + +export const shieldedAccountsAtom = atom<readonly AccountDetails[]>((get) => + get(accountsAtom).filter((account) => account.isShielded) +); + +export const refreshAccountsAtom = atom(null, (_get, set) => + set(accountsAtom, []) +); + +export const fetchAccountsAtom = atom( + (get) => get(accountsAtom), + async (_get, set) => { + const namada = getIntegration("namada"); + const result = await namada.accounts(); + if (typeof result === "undefined") { + throw new Error("accounts was undefined!"); + } + set(accountsAtom, result); + } +); + +export const defaultAccountAtom = atom<AccountDetails | undefined>(undefined); + +export const fetchDefaultAccountAtom = atom( + (get) => get(defaultAccountAtom), + async (_get, set) => { + const namada = getIntegration("namada"); + const result = await namada.defaultAccount(); + if (typeof result === "undefined") { + throw new Error("account was undefined!"); + } + set(defaultAccountAtom, result); + } +); + +export const totalNamBalanceAtom = atomWithQuery<BigNumber>((get) => { + const balances = get(balancesAtom); + return { + enabled: balances.isSuccess, + queryKey: ["totalNamBalance", balances.dataUpdatedAt], + queryFn: () => { + return Object.values(balances.data!).reduce( + (prev, current) => prev.plus(current["NAM"] || 0), + new BigNumber(0) + ); + }, + }; +}); + +export const balancesAtom = atomWithQuery<Record<Address, Balance>>((get) => { + const token = tokenAddress; + //const shieldedAccounts = get(shieldedAccountsAtom); + const transparentAccounts = get(transparentAccountsAtom); + + return { + enabled: !!token && transparentAccounts.length > 0, + queryKey: ["balances", token], + queryFn: async () => { + // We query the balances for the transparent accounts first as it's faster + const transparentBalances = await Promise.all( + queryBalance(transparentAccounts, token) + ); + + let balances = {}; + transparentBalances.forEach(([address, balance]) => { + balances = { ...balances, [address]: balance }; + }); + + // await namada.sync(); + // TODO: enable the following code on phase 3 + // + // const shieldedBalances = await Promise.all( + // queryBalance(shieldedAccounts, token) + // ); + // + // shieldedBalances.forEach(([address, balance]) => { + // balances = { ...balances, [address]: balance }; + // }); + + return balances; + }, + }; +}); + +const queryBalance = ( + accounts: readonly AccountDetails[], + token: string +): Promise<[string, TokenBalances]>[] => { + return accounts.map(async (account): Promise<[string, TokenBalances]> => { + const { rpc } = await getSdkInstance(); + const tokens = [token]; + + const balances = (await rpc.queryBalance(account.address, tokens)).map( + ([token, amount]) => { + return { + token, + amount, + }; + } + ); + + // TODO: This is for testing + return [account.address, { NAM: BigNumber(balances[0].amount) }]; + }); +}; + +const keplrAccountsAtom = (() => { + const base = atom(new Promise<readonly AccountDetails[]>(() => {})); + + return atom( + (get) => get(base), + (_get, set) => + set( + base, + (async () => { + const keplr = getIntegration("cosmos"); + const accounts = await keplr.accounts(); + + if (typeof accounts === "undefined") { + throw new Error("Keplr accounts was undefined"); + } + + return accounts; + })() + ) + ); +})(); + +const keplrBalancesAtom = (() => { + const base = atom(new Promise<TokenBalances<CosmosTokenType>>(() => {})); + + return atom( + (get) => get(base), + (get, set) => + set( + base, + (async () => { + const accounts = await get(keplrAccountsAtom); + const keplr = getIntegration("cosmos"); + const supportedChainId = keplr.chain.chainId; + + // TODO: support querying balances for multiple chains + const supportedAccount = accounts.find( + ({ chainId }) => chainId === supportedChainId + ); + + if (typeof supportedAccount === "undefined") { + throw new Error( + `no Keplr account for chain ID ${supportedChainId}` + ); + } + + return await keplr.queryBalances(supportedAccount.address); + })() + ) + ); +})(); + +export { keplrAccountsAtom, keplrBalancesAtom }; diff --git a/apps/namadillo/src/slices/chain.ts b/apps/namadillo/src/slices/chain.ts new file mode 100644 index 000000000..2e0bd27a3 --- /dev/null +++ b/apps/namadillo/src/slices/chain.ts @@ -0,0 +1,5 @@ +import { chains } from "@namada/chains"; +import { Chain } from "@namada/types"; +import { atom } from "jotai"; + +export const chainAtom = atom<Chain>((_get) => chains.namada); diff --git a/apps/namadillo/src/slices/exchangeRates.ts b/apps/namadillo/src/slices/exchangeRates.ts new file mode 100644 index 000000000..a2d281fd5 --- /dev/null +++ b/apps/namadillo/src/slices/exchangeRates.ts @@ -0,0 +1,24 @@ +import { CurrencyType } from "@namada/utils"; +import { atom } from "jotai"; +import { selectedCurrencyAtom } from "./settings"; + +type SupportedCurrencies = "nam"; + +export type ExchangeRateTable = Record< + SupportedCurrencies, + Record<CurrencyType, number> +>; + +export const exchangeRateAtom = atom<ExchangeRateTable>({ + nam: { + usd: 0, + eur: 0, + jpy: 0, + }, +}); + +export const selectedCurrencyRateAtom = atom((get) => { + const exchangeRates = get(exchangeRateAtom); + const activeCurrency = get(selectedCurrencyAtom); + return exchangeRates["nam"][activeCurrency]; +}); diff --git a/apps/namadillo/src/slices/fees.ts b/apps/namadillo/src/slices/fees.ts new file mode 100644 index 000000000..a26fb4b32 --- /dev/null +++ b/apps/namadillo/src/slices/fees.ts @@ -0,0 +1,76 @@ +import BigNumber from "bignumber.js"; +import { getSdkInstance } from "hooks"; +import invariant from "invariant"; +import { atom } from "jotai"; +import { atomWithQuery } from "jotai-tanstack-query"; +import { accountsAtom } from "slices/accounts"; +import { chainAtom } from "slices/chain"; + +// TODO: remove harcoding of gas limit +export const GAS_LIMIT = new BigNumber(20_000); + +const { + NAMADA_INTERFACE_NAMADA_TOKEN: + nativeToken = "tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e", +} = process.env; + +export const minimumGasPriceAtom = atomWithQuery<BigNumber>((get) => { + const chain = get(chainAtom); + return { + queryKey: ["minimum-gas-price-" + chain.chainId], + queryFn: async () => { + const { rpc } = await getSdkInstance(); + // TODO: Can nativeToken ever be undefined? + invariant(!!nativeToken, "Native token is undefined"); + const result = (await rpc.queryGasCosts()) as [string, string][]; + const nativeTokenCost = result.find(([token]) => token === nativeToken); + invariant(!!nativeTokenCost, "Error querying minimum gas price"); + const asBigNumber = new BigNumber(nativeTokenCost![1]); + invariant( + !asBigNumber.isNaN(), + "Error converting minimum gas price to BigNumber" + ); + + return asBigNumber; + }, + }; +}); + +export const isRevealPkNeededAtom = (() => { + type RevealPkNeededMap = { [address: string]: boolean }; + + const base = atom(new Promise<RevealPkNeededMap>(() => {})); + + return atom( + async (get) => { + const map = await get(base); + + return (address: string): boolean => { + if (!(address in map)) { + throw new Error("address not found in public key map"); + } + return map[address]; + }; + }, + (get, set) => + set( + base, + (async (): Promise<RevealPkNeededMap> => { + const accounts = get(accountsAtom); + const transparentAccounts = accounts.filter( + (account) => !account.isShielded + ); + const { rpc } = await getSdkInstance(); + + const entries = await Promise.all( + transparentAccounts.map(async ({ address }) => { + const publicKey = await rpc.queryPublicKey(address); + return [address, !publicKey]; + }) + ); + + return Object.fromEntries(entries); + })() + ) + ); +})(); diff --git a/apps/namadillo/src/slices/notifications.ts b/apps/namadillo/src/slices/notifications.ts new file mode 100644 index 000000000..24339bbc2 --- /dev/null +++ b/apps/namadillo/src/slices/notifications.ts @@ -0,0 +1,38 @@ +import { atom } from "jotai"; +import { + ToastNotification, + ToastNotificationEntryFilter, +} from "types/notifications"; + +const toastNotificationsBaseAtom = atom<ToastNotification[]>([]); + +export const toastNotificationsAtom = atom<ToastNotification[]>((get) => + get(toastNotificationsBaseAtom) +); + +export const dispatchToastNotificationAtom = atom( + null, + (get, set, data: ToastNotification) => { + const notifications = get(toastNotificationsBaseAtom); + set(toastNotificationsBaseAtom, [...notifications, { ...data }]); + } +); + +export const dismissToastNotificationAtom = atom( + null, + (get, set, id: string) => { + const notifications = get(toastNotificationsBaseAtom); + set( + toastNotificationsBaseAtom, + notifications.filter((n) => n.id !== id) + ); + } +); + +export const filterToastNotificationsAtom = atom( + null, + (get, set, filterFn: ToastNotificationEntryFilter) => { + const notifications = get(toastNotificationsBaseAtom); + set(toastNotificationsBaseAtom, notifications.filter(filterFn)); + } +); diff --git a/apps/namadillo/src/slices/proposals/atoms.ts b/apps/namadillo/src/slices/proposals/atoms.ts new file mode 100644 index 000000000..ed333e44b --- /dev/null +++ b/apps/namadillo/src/slices/proposals/atoms.ts @@ -0,0 +1,123 @@ +import { atomWithMutation, atomWithQuery } from "jotai-tanstack-query"; +import { atomFamily } from "jotai/utils"; + +import { ProposalStatus, ProposalTypeString, VoteType } from "@namada/types"; +import { transparentAccountsAtom } from "slices/accounts"; +import { chainAtom } from "slices/chain"; +import { + fetchAllProposals, + fetchCurrentEpoch, + fetchProposalById, + fetchProposalCode, + fetchProposalCounter, + fetchProposalVoted, + fetchProposalVotes, + fetchVotedProposalIds, + performVote, +} from "./functions"; + +export const currentEpochAtom = atomWithQuery((get) => ({ + queryKey: ["current-epoch"], + queryFn: () => fetchCurrentEpoch(get(chainAtom)), +})); + +export const proposalCounterAtom = atomWithQuery((get) => ({ + queryKey: ["proposal-counter"], + queryFn: () => fetchProposalCounter(get(chainAtom)), +})); + +export const proposalFamily = atomFamily((id: bigint) => + atomWithQuery((get) => ({ + queryKey: ["proposal", id.toString()], + queryFn: () => fetchProposalById(get(chainAtom), id), + })) +); + +export const proposalVotesFamily = atomFamily((id: bigint) => + atomWithQuery((get) => ({ + queryKey: ["proposal-votes", id.toString()], + queryFn: () => fetchProposalVotes(get(chainAtom), id), + })) +); + +export const proposalVotedFamily = atomFamily((id: bigint) => + atomWithQuery((get) => ({ + queryKey: ["proposal-voted", id.toString()], + queryFn: async () => { + const [account] = get(transparentAccountsAtom); + if (typeof account === "undefined") { + throw new Error("no account found"); + } + return await fetchProposalVoted(get(chainAtom), id, account); + }, + })) +); + +export const allProposalsAtom = atomWithQuery((get) => ({ + queryKey: ["all-proposals"], + queryFn: () => fetchAllProposals(get(chainAtom)), +})); + +// TODO: this is a bad way to filter/search +export const allProposalsFamily = atomFamily( + (options?: { + status?: ProposalStatus; + type?: ProposalTypeString; + search?: string; + }) => + atomWithQuery((get) => ({ + queryKey: [ + "all-proposals", + options?.status, + options?.type, + options?.search, + ], + queryFn: () => + fetchAllProposals( + get(chainAtom), + options?.status, + options?.type, + options?.search + ), + })), + (a, b) => + a?.status === b?.status && a?.type === b?.type && a?.search === b?.search +); + +export const votedProposalIdsAtom = atomWithQuery((get) => ({ + queryKey: ["voted-proposal-ids"], + queryFn: async () => { + const [account] = get(transparentAccountsAtom); + if (typeof account === "undefined") { + throw new Error("no account found"); + } + return await fetchVotedProposalIds(get(chainAtom), account); + }, +})); + +export const proposalCodeFamily = atomFamily((id: bigint) => + atomWithQuery((get) => ({ + queryKey: ["proposal-code", id.toString()], + queryFn: () => fetchProposalCode(get(chainAtom), id), + })) +); + +type PerformVoteArgs = { + proposalId: bigint; + vote: VoteType; +}; +export const performVoteAtom = atomWithMutation((get) => { + return { + mutationKey: ["voting"], + mutationFn: async ({ proposalId, vote }: PerformVoteArgs) => { + const chain = get(chainAtom); + const [account] = get(transparentAccountsAtom); + + if (typeof account === "undefined") { + throw new Error("no account"); + } + + performVote(proposalId, vote, account, chain); + }, + }; +}); diff --git a/apps/namadillo/src/slices/proposals/functions.ts b/apps/namadillo/src/slices/proposals/functions.ts new file mode 100644 index 000000000..4b47f2ce2 --- /dev/null +++ b/apps/namadillo/src/slices/proposals/functions.ts @@ -0,0 +1,517 @@ +import { deserialize } from "@dao-xyz/borsh"; +import { EncodedTx } from "@heliax/namada-sdk/web"; +import { getIntegration } from "@namada/integrations"; +import { Proposal as ProposalSchema, Query } from "@namada/shared"; +import { + Account, + AddRemove, + Chain, + DelegatorVote, + PgfActions, + Proposal, + ProposalStatus, + ProposalType, + ProposalTypeString, + ValidatorVote, + Vote, + VoteType, + isTallyType, + isVoteType, +} from "@namada/types"; +import BigNumber from "bignumber.js"; +import * as E from "fp-ts/Either"; +import * as t from "io-ts"; + +import { getSdkInstance } from "hooks"; + +const { + NAMADA_INTERFACE_NAMADA_TOKEN: + nativeToken = "tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e", + NAMADA_INTERFACE_NO_INDEXER, +} = process.env; + +export const fetchCurrentEpoch = async (chain: Chain): Promise<bigint> => { + const { rpc } = chain; + const query = new Query(rpc); + return await query.query_epoch(); +}; + +export const fetchProposalCounter = async (chain: Chain): Promise<bigint> => { + const { rpc } = chain; + const query = new Query(rpc); + const proposalCounter: string = await query.query_proposal_counter(); + return BigInt(proposalCounter); +}; + +// TODO: this function is way too big +const decodeProposalType = async ( + typeString: string, + data: string | undefined, + chain: Chain, + proposalId: bigint +): Promise<ProposalType> => { + switch (typeString) { + case "default": + const wasmCode = + typeof data === "undefined" ? undefined : ( + await fetchProposalCode(chain, proposalId) + ); + + return { type: "default", data: wasmCode }; + + case "pgf_steward": + if (typeof data === "undefined") { + throw new Error("data was undefined for pgf_steward proposal"); + } + + const addRemoveSchema = t.array( + t.union([t.type({ Add: t.string }), t.type({ Remove: t.string })]) + ); + + const addRemoveDecoded = addRemoveSchema.decode(JSON.parse(data)); + + if (E.isLeft(addRemoveDecoded)) { + throw new Error("pgf_steward data is not valid"); + } + + const addRemove = addRemoveDecoded.right.reduce<AddRemove>( + (acc, curr) => { + if ("Add" in curr) { + if ("add" in acc) { + throw new Error( + "found multiple add addresses in PgfSteward proposal" + ); + } + return { ...acc, add: curr.Add }; + } else { + return { ...acc, remove: [...acc.remove, curr.Remove] }; + } + }, + { remove: [] } + ); + + return { type: "pgf_steward", data: addRemove }; + + case "pgf_payment": + if (typeof data === "undefined") { + throw new Error("data was undefined for pgf_payment proposal"); + } + + // TODO: add IBC target + const pgfTargetSchema = t.type({ + Internal: t.type({ + target: t.string, + amount: t.string, + }), + }); + + const pgfActionsSchema = t.array( + t.union([ + t.type({ + Continuous: t.union([ + t.type({ Add: pgfTargetSchema }), + t.type({ Remove: pgfTargetSchema }), + ]), + }), + t.type({ + Retro: pgfTargetSchema, + }), + ]) + ); + + const pgfActionsDecoded = pgfActionsSchema.decode(JSON.parse(data)); + + if (E.isLeft(pgfActionsDecoded)) { + throw new Error("pgf_payment data is not valid"); + } + + const pgfActions = pgfActionsDecoded.right.reduce<PgfActions>( + (acc, curr) => { + if ("Continuous" in curr) { + const continuous = curr.Continuous; + const [ + { + Internal: { target, amount }, + }, + key, + ] = + "Add" in continuous ? + [continuous["Add"], "add" as const] + : [continuous["Remove"], "remove" as const]; + + const amountAsBigNumber = BigNumber(amount); + + if (amountAsBigNumber.isNaN()) { + throw new Error( + `couldn't parse amount to BigNumber, got ${amount}` + ); + } + + return { + ...acc, + continuous: { + ...acc.continuous, + [key]: [ + ...acc.continuous[key], + { internal: { target, amount: amountAsBigNumber } }, + ], + }, + }; + } else { + const { target, amount } = curr.Retro.Internal; + const amountAsBigNumber = BigNumber(amount); + + if (amountAsBigNumber.isNaN()) { + throw new Error( + `couldn't parse amount to BigNumber, got ${amount}` + ); + } + + return { + ...acc, + retro: [ + ...acc.retro, + { internal: { amount: amountAsBigNumber, target } }, + ], + }; + } + }, + { continuous: { add: [], remove: [] }, retro: [] } + ); + + return { type: "pgf_payment", data: pgfActions }; + + default: + throw new Error(`unknown proposal type string, got ${typeString}`); + } +}; + +export const fetchProposalById = async ( + chain: Chain, + id: bigint +): Promise<Proposal> => { + const { rpc } = chain; + const query = new Query(rpc); + const proposalUint8Array = await query.query_proposal_by_id(id); + const deserialized = deserialize(proposalUint8Array, ProposalSchema); + + const content = JSON.parse(deserialized.content); + + const tallyType = deserialized.tallyType; + + if (!isTallyType(tallyType)) { + throw new Error(`unknown tally type, got ${tallyType}`); + } + + const proposalType = await decodeProposalType( + deserialized.proposalType, + deserialized.data, + chain, + id + ); + + const { startEpoch, endEpoch } = deserialized; + + const fetchedStatus = await fetchProposalStatus( + chain, + id, + startEpoch, + endEpoch + ); + + return { + ...deserialized, + ...fetchedStatus, + content, + proposalType, + tallyType, + }; +}; + +export const fetchAllProposals = async ( + chain: Chain, + status?: ProposalStatus, + type?: ProposalTypeString, + search?: string +): Promise<Proposal[]> => { + const proposalCounter = await fetchProposalCounter(chain); + + const proposals: Promise<Proposal>[] = []; + for (let id = BigInt(0); id < proposalCounter; id++) { + proposals.push(fetchProposalById(chain, id)); + } + + const resolvedProposals = await Promise.all(proposals); + + const statusFiltered = + typeof status === "undefined" ? resolvedProposals : ( + resolvedProposals.filter((p) => p.status === status) + ); + + const typeFiltered = + typeof type === "undefined" ? statusFiltered : ( + statusFiltered.filter((p) => p.proposalType.type === type) + ); + + const searchFiltered = + typeof search === "undefined" ? typeFiltered : ( + typeFiltered.filter( + (p) => + p.content.title?.toLowerCase().includes(search.toLowerCase()) || + p.id.toString().includes(search) + ) + ); + + return Array(30).fill(searchFiltered).flat(); +}; + +// TODO: this function is way too big +export const fetchProposalVotes = async ( + chain: Chain, + id: bigint +): Promise<Vote[]> => { + if (NAMADA_INTERFACE_NO_INDEXER) { + return []; + } + + const { endEpoch } = await fetchProposalById(chain, id); + const currentEpoch = await fetchCurrentEpoch(chain); + + const epoch = endEpoch < currentEpoch ? endEpoch : currentEpoch; + + const { rpc } = chain; + const query = new Query(rpc); + const [validatorVotesResult, delegatorVotesResult]: [ + [string, string, string][], // validator votes [address, vote, voting power] + [string, string, [string, string][]][], // delegator votes [address, vote] + ] = await query.query_proposal_votes(id, epoch); + + const validatorVotes: ValidatorVote[] = validatorVotesResult.map( + ([address, voteType, votingPower]) => { + if (!isVoteType(voteType)) { + throw new Error(`unknown vote type, got ${voteType}`); + } + + const votingPowerAsBigNumber = BigNumber(votingPower); + + if (votingPowerAsBigNumber.isNaN()) { + throw new Error("unable to parse voting power as big number"); + } + + return { + address, + voteType, + isValidator: true, + votingPower: votingPowerAsBigNumber, + }; + } + ); + + const delegatorVotes: DelegatorVote[] = delegatorVotesResult.map( + ([address, voteType, votingPower]) => { + if (!isVoteType(voteType)) { + throw new Error(`unknown vote type, got ${voteType}`); + } + + const votingPowerAsBigNumber: [string, BigNumber][] = votingPower.map( + ([validatorAddress, amount]) => { + const amountAsBigNumber = BigNumber(amount); + if (amountAsBigNumber.isNaN()) { + throw new Error("unable to parse amount as big number"); + } + + return [validatorAddress, amountAsBigNumber]; + } + ); + + return { + address, + voteType, + isValidator: false, + votingPower: votingPowerAsBigNumber, + } as const; + } + ); + + return [...validatorVotes, ...delegatorVotes]; +}; + +export const fetchProposalStatus = async ( + chain: Chain, + id: bigint, + startEpoch: bigint, + endEpoch: bigint +): Promise< + { + [VT in VoteType]: BigNumber; + } & { + totalVotingPower: BigNumber; + status: ProposalStatus; + } +> => { + const currentEpoch = await fetchCurrentEpoch(chain); + + const epoch = endEpoch < currentEpoch ? endEpoch : currentEpoch; + + if (NAMADA_INTERFACE_NO_INDEXER) { + const status: ProposalStatus = + startEpoch > currentEpoch ? "pending" + : endEpoch > currentEpoch ? "ongoing" + : id % BigInt(2) === BigInt(0) ? "passed" + : "rejected"; + + return { + status, + yay: BigNumber(300), + nay: BigNumber(250), + abstain: BigNumber(125), + totalVotingPower: BigNumber(1000), + }; + } + + const { rpc } = chain; + const query = new Query(rpc); + + const [passed, yayPower, nayPower, abstainPower, totalVotingPower]: [ + boolean, + string, + string, + string, + string, + ] = await query.query_proposal_result(id, epoch); + + const yayPowerAsBigNumber = BigNumber(yayPower); + if (yayPowerAsBigNumber.isNaN()) { + throw new Error("couldn't parse yay power as BigNumber"); + } + + const nayPowerAsBigNumber = BigNumber(nayPower); + if (nayPowerAsBigNumber.isNaN()) { + throw new Error("couldn't parse nay power as BigNumber"); + } + + const abstainPowerAsBigNumber = BigNumber(abstainPower); + if (abstainPowerAsBigNumber.isNaN()) { + throw new Error("couldn't parse abstain power as BigNumber"); + } + + const totalVotingPowerAsBigNumber = BigNumber(totalVotingPower); + if (totalVotingPowerAsBigNumber.isNaN()) { + throw new Error("couldn't parse total voting power as BigNumber"); + } + + const status: ProposalStatus = + startEpoch > currentEpoch ? "pending" + : endEpoch > currentEpoch ? "ongoing" + : passed ? "passed" + : "rejected"; + + return { + status, + yay: yayPowerAsBigNumber, + nay: nayPowerAsBigNumber, + abstain: abstainPowerAsBigNumber, + totalVotingPower: totalVotingPowerAsBigNumber, + }; +}; + +export const fetchProposalVoted = async ( + chain: Chain, + id: bigint, + account: Account +): Promise<boolean> => { + const votes = await fetchProposalVotes(chain, id); + return votes.some((vote) => vote.address === account.address); +}; + +export const fetchVotedProposalIds = async ( + chain: Chain, + account: Account +): Promise<bigint[]> => { + const proposalCounter = await fetchProposalCounter(chain); + + const proposalIds: bigint[] = []; + for (let id = BigInt(0); id < proposalCounter; id++) { + const voted = await fetchProposalVoted(chain, id, account); + if (voted) { + proposalIds.push(id); + } + } + + return proposalIds; +}; + +export const fetchProposalCode = async ( + chain: Chain, + id: bigint +): Promise<Uint8Array> => { + const { rpc } = chain; + const query = new Query(rpc); + return await query.query_proposal_code(id); +}; + +export const performVote = async ( + proposalId: bigint, + vote: VoteType, + account: Account, + chain: Chain +): Promise<void> => { + const signingClient = getIntegration("namada").signer(); + + if (typeof signingClient === "undefined") { + throw new Error("no signer"); + } + + const publicKey = account.publicKey; + if (typeof publicKey === "undefined") { + throw new Error("no public key on account"); + } + const { tx, rpc } = await getSdkInstance(); + const signer = account.address; + + const proposalProps = { + signer, + proposalId, + vote, + }; + + const wrapperTxProps = { + token: nativeToken, + feeAmount: BigNumber(0), + gasLimit: BigNumber(20_000), + chainId: chain.chainId, + publicKey, + memo: "", + }; + + const txArray: EncodedTx[] = []; + + // RevealPK if needed + const pk = await rpc.queryPublicKey(account.address); + if (!pk) { + const revealPkTx = await tx.buildRevealPk(wrapperTxProps, account.address); + // Add to txArray to sign & broadcast below: + txArray.push(revealPkTx); + } + + const encodedTx = await tx.buildVoteProposal(wrapperTxProps, proposalProps); + txArray.push(encodedTx); + + try { + const signedTx = await signingClient.sign( + signer, + txArray.map(({ tx }) => tx) + ); + if (!signedTx) { + throw new Error("Signing failed"); + } + txArray.forEach(async (tx, i) => { + await rpc.broadcastTx({ + wrapperTxMsg: tx.txMsg, + tx: signedTx[i], + }); + }); + } catch (e) { + console.error(e); + } +}; diff --git a/apps/namadillo/src/slices/proposals/index.ts b/apps/namadillo/src/slices/proposals/index.ts new file mode 100644 index 000000000..4e0d46d9a --- /dev/null +++ b/apps/namadillo/src/slices/proposals/index.ts @@ -0,0 +1 @@ +export * from "./atoms"; diff --git a/apps/namadillo/src/slices/settings.ts b/apps/namadillo/src/slices/settings.ts new file mode 100644 index 000000000..a12f8e646 --- /dev/null +++ b/apps/namadillo/src/slices/settings.ts @@ -0,0 +1,61 @@ +import { ChainKey } from "@namada/types"; +import { CurrencyType } from "@namada/utils"; +import { Getter, Setter, atom } from "jotai"; +import { atomWithStorage } from "jotai/utils"; + +type SettingsStorage = { + fiat: CurrencyType; + hideBalances: boolean; + rpcUrl: string; + chainId: string; +}; + +export const namadaExtensionConnectedAtom = atom(false); + +export const namadilloSettingsAtom = atomWithStorage<SettingsStorage>( + "namadillo:settings", + { + fiat: "usd", + hideBalances: false, + rpcUrl: process.env.NAMADA_INTERFACE_NAMADA_URL || "", + chainId: process.env.NAMADA_INTERFACE_NAMADA_CHAIN_ID || "", + } +); + +const changeSettings = + <T>(key: keyof SettingsStorage) => + (get: Getter, set: Setter, value: T) => { + const settings = get(namadilloSettingsAtom); + set(namadilloSettingsAtom, { ...settings, [key]: value }); + }; + +export const selectedCurrencyAtom = atom( + (get) => get(namadilloSettingsAtom).fiat, + changeSettings<CurrencyType>("fiat") +); + +export const hideBalancesAtom = atom( + (get) => get(namadilloSettingsAtom).hideBalances, + changeSettings<boolean>("hideBalances") +); + +export const rpcUrlAtom = atom( + (get) => get(namadilloSettingsAtom).rpcUrl, + changeSettings<string>("rpcUrl") +); + +export const chainIdAtom = atom( + (get) => get(namadilloSettingsAtom).chainId, + changeSettings<string>("chainId") +); + +export const connectedChainsAtom = atom<ChainKey[]>([]); +export const addConnectedChainAtom = atom(null, (get, set, chain: ChainKey) => { + const connectedChains = get(connectedChainsAtom); + set( + connectedChainsAtom, + connectedChains.includes(chain) ? connectedChains : ( + [...connectedChains, chain] + ) + ); +}); diff --git a/apps/namadillo/src/slices/staking.ts b/apps/namadillo/src/slices/staking.ts new file mode 100644 index 000000000..c56786b1a --- /dev/null +++ b/apps/namadillo/src/slices/staking.ts @@ -0,0 +1,200 @@ +import { Account, BondProps, RedelegateProps } from "@namada/types"; +import BigNumber from "bignumber.js"; +import { invariant } from "framer-motion"; +import { getSdkInstance } from "hooks"; +import { atomWithMutation, atomWithQuery } from "jotai-tanstack-query"; +import { TransactionPair, buildTxPair } from "lib/query"; +import { GasConfig } from "types/fees"; +import { ChangeInStakingPosition, RedelegateChange } from "types/staking"; +import { chainAtom } from "./chain"; +import { MyValidator, myValidatorsAtom } from "./validators"; + +const { + NAMADA_INTERFACE_NAMADA_TOKEN: + nativeToken = "tnam1qxgfw7myv4dh0qna4hq0xdg6lx77fzl7dcem8h7e", +} = process.env; + +type StakingTotals = { + totalBonded: BigNumber; + totalUnbonded: BigNumber; + totalWithdrawable: BigNumber; +}; + +type ChangeInStakingProps = { + account: Account; + changes: ChangeInStakingPosition[]; + gasConfig: GasConfig; +}; + +type RedelegateChangesProps = { + account: Account; + changes: RedelegateChange[]; + gasConfig: GasConfig; +}; + +export const getStakingTotalAtom = atomWithQuery<StakingTotals>((get) => { + const myValidators = get(myValidatorsAtom); + return { + enabled: myValidators.isSuccess, + queryKey: ["staking-totals", myValidators.dataUpdatedAt], + queryFn: async () => { + const totalBonded = myValidators.data.reduce( + (acc: BigNumber, validator: MyValidator) => + acc.plus(validator.stakedAmount ?? 0), + new BigNumber(0) + ); + + const totalUnbonded = myValidators.data.reduce( + (acc: BigNumber, validator: MyValidator) => + acc.plus(validator.unbondedAmount ?? 0), + new BigNumber(0) + ); + + const totalWithdrawable = myValidators.data.reduce( + (acc: BigNumber, validator: MyValidator) => + acc.plus(validator.withdrawableAmount ?? 0), + new BigNumber(0) + ); + + return { totalBonded, totalUnbonded, totalWithdrawable }; + }, + }; +}); + +const getStakingChangesParams = ( + account: Account, + changes: ChangeInStakingPosition[] +): BondProps[] => { + const address = nativeToken; + invariant(!!address, "Invalid currency address"); + return changes.map((change) => ({ + source: account.address, + validator: change.validatorId, + amount: change.amount, + nativeToken: address!, + })); +}; + +const getRedelegateChangeParams = ( + account: Account, + changes: RedelegateChange[] +): RedelegateProps[] => { + return changes.map((change: RedelegateChange) => ({ + owner: account.address, + ...change, + })); +}; + +export const createBondTxAtom = atomWithMutation((get) => { + return { + mutationKey: ["create-bonding-tx"], + mutationFn: async ({ + changes, + gasConfig, + account, + }: ChangeInStakingProps): Promise< + TransactionPair<BondProps>[] | undefined + > => { + try { + const chain = get(chainAtom); + const { tx } = await getSdkInstance(); + const bondProps = getStakingChangesParams(account, changes); + const transactionPairs = await buildTxPair( + account, + gasConfig, + chain, + bondProps, + tx.buildBond, + bondProps[0].source + ); + return transactionPairs; + } catch (err) { + console.error(err); + } + }, + }; +}); + +export const createUnbondTxAtom = atomWithMutation((get) => { + return { + mutationKey: ["creat-unbonding-tx"], + mutationFn: async ({ + changes, + gasConfig, + account, + }: ChangeInStakingProps) => { + try { + const chain = get(chainAtom); + const { tx } = await getSdkInstance(); + const unbondProps = getStakingChangesParams(account, changes); + const transactionPairs = await buildTxPair( + account, + gasConfig, + chain, + unbondProps, + tx.buildUnbond, + unbondProps[0].source + ); + return transactionPairs; + } catch (err) { + console.error(err); + } + }, + }; +}); + +export const createReDelegateTxAtom = atomWithMutation((get) => { + return { + mutationKey: ["create-redelegate-tx"], + mutationFn: async ({ + changes, + gasConfig, + account, + }: RedelegateChangesProps) => { + try { + const chain = get(chainAtom); + const { tx } = await getSdkInstance(); + const redelegateProps = getRedelegateChangeParams(account, changes); + const transactionPairs = await buildTxPair( + account, + gasConfig, + chain, + redelegateProps, + tx.buildRedelegate, + redelegateProps[0].owner + ); + return transactionPairs; + } catch (err) { + console.error(err); + } + }, + }; +}); + +export const createWithdrawTxAtom = atomWithMutation((get) => { + return { + mutationKey: ["withdraw"], + mutationFn: async ({ + changes, + gasConfig, + account, + }: ChangeInStakingProps) => { + try { + const chain = get(chainAtom); + const { tx } = await getSdkInstance(); + const withdrawProps = getStakingChangesParams(account, changes); + const transactionPairs = await buildTxPair( + account, + gasConfig, + chain, + withdrawProps, + tx.buildWithdraw, + withdrawProps[0].source + ); + return transactionPairs; + } catch (err) { + console.error(err); + } + }, + }; +}); diff --git a/apps/namadillo/src/slices/transactions.ts b/apps/namadillo/src/slices/transactions.ts new file mode 100644 index 000000000..73fed9833 --- /dev/null +++ b/apps/namadillo/src/slices/transactions.ts @@ -0,0 +1,21 @@ +import { atom } from "jotai"; +import { PreparedTransaction } from "lib/query"; + +export type DispatchTransaction = (txs: PreparedTransaction[]) => Promise<void>; + +const transactionsBaseAtom = atom<PreparedTransaction[]>([]); +export const transactionsAtom = atom<PreparedTransaction[]>((get) => + get(transactionsBaseAtom) +); + +export const dispatchTransactionsAtom = atom( + null, + (get, set, transactions: PreparedTransaction[]) => { + const queuedTransactions = get(transactionsAtom); + set(transactionsBaseAtom, [...queuedTransactions, ...transactions]); + } +); + +export const clearTransactionQueueAtom = atom(null, (_get, set) => { + set(transactionsBaseAtom, []); +}); diff --git a/apps/namadillo/src/slices/validators.ts b/apps/namadillo/src/slices/validators.ts new file mode 100644 index 000000000..ee96c9c4c --- /dev/null +++ b/apps/namadillo/src/slices/validators.ts @@ -0,0 +1,119 @@ +import { Query } from "@namada/shared"; +import BigNumber from "bignumber.js"; +import { atomWithQuery } from "jotai-tanstack-query"; +import { transparentAccountsAtom } from "./accounts"; +import { chainAtom } from "./chain"; + +type Unique = { + uuid: string; +}; + +export type Validator = Unique & { + alias: string; + address: string; + description?: string; + homepageUrl: string; + expectedApr: number; + unbondingPeriod: string; + votingPowerInNAM?: BigNumber; + votingPowerPercentage?: number; + commission: BigNumber; + imageUrl?: string; +}; + +export type MyValidator = Unique & { + stakingStatus: string; + stakedAmount?: BigNumber; + unbondedAmount?: BigNumber; + withdrawableAmount?: BigNumber; + validator: Validator; +}; + +// TODO: Change this after the indexer is created +const toValidator = (address: string): Validator => ({ + uuid: address, + alias: "<Validator Name>", + description: "Lorem ipsum dolor sit amet", + address, + homepageUrl: "http://namada.net", + expectedApr: 0.1127, + unbondingPeriod: "21 days", + votingPowerInNAM: BigNumber("70000000"), + votingPowerPercentage: 0.06, + commission: new BigNumber(0), // TODO: implement commission + imageUrl: "https://loremflickr.com/200/200", +}); + +export const allValidatorsAtom = atomWithQuery((get) => ({ + queryKey: ["all-validators"], + queryFn: async () => { + const { rpc } = get(chainAtom); + const query = new Query(rpc); + const queryResult = + (await query.query_all_validator_addresses()) as string[]; + return queryResult.map(toValidator); + }, +})); + +// eslint-disable-next-line +export const myValidatorsAtom = atomWithQuery((get) => { + const accounts = get(transparentAccountsAtom); + const ids = accounts.map((account) => account.address).join("-"); + return { + queryKey: ["my-validators", ids], + queryFn: async () => { + const { rpc } = get(chainAtom); + const addresses = accounts.map((account) => account.address); + const query = new Query(rpc); + const myValidatorsRes = await query.query_my_validators(addresses); + console.log({ myValidatorsRes }); + return myValidatorsRes.reduce(toMyValidators, []); + }, + }; +}); + +const toMyValidators = ( + acc: MyValidator[], + // TODO: omg + [_, validator, stake, unbonded, withdrawable]: [ + string, + string, + string, + string, + string, + ] +): MyValidator[] => { + const index = acc.findIndex((myValidator) => myValidator.uuid === validator); + const v = acc[index]; + const sliceFn = + index == -1 ? + (arr: MyValidator[]) => arr + : (arr: MyValidator[], idx: number) => [ + ...arr.slice(0, idx), + ...arr.slice(idx + 1), + ]; + + const stakedAmount = new BigNumber(stake).plus( + new BigNumber(v?.stakedAmount || 0) + ); + + const unbondedAmount = new BigNumber(unbonded).plus( + new BigNumber(v?.unbondedAmount || 0) + ); + + const withdrawableAmount = new BigNumber(withdrawable).plus( + new BigNumber(v?.withdrawableAmount || 0) + ); + + return [ + ...sliceFn(acc, index), + { + uuid: validator, + stakingStatus: "Bonded", + stakedAmount, + unbondedAmount, + withdrawableAmount, + validator: toValidator(validator), + }, + ]; +}; diff --git a/apps/namadillo/src/store/utils.ts b/apps/namadillo/src/store/utils.ts new file mode 100644 index 000000000..6a437ebbe --- /dev/null +++ b/apps/namadillo/src/store/utils.ts @@ -0,0 +1,42 @@ +import { useSetAtom } from "jotai"; +import { AtomWithQueryResult } from "jotai-tanstack-query"; +import { useEffect } from "react"; +import { + dismissToastNotificationAtom, + dispatchToastNotificationAtom, +} from "slices/notifications"; +import { ToastNotification } from "types/notifications"; + +export const atomsAreFetching = (...args: AtomWithQueryResult[]): boolean => { + return args.reduce((prev, current) => prev || current.isLoading, false); +}; + +export const atomsAreLoaded = (...args: AtomWithQueryResult[]): boolean => { + return args.reduce((prev, current) => prev && current.isSuccess, true); +}; + +export const atomsAreError = (...args: AtomWithQueryResult[]): boolean => { + return args.reduce((prev, current) => prev || current.isError, false); +}; + +export const useNotifyOnAtomError = ( + atoms: AtomWithQueryResult[], + deps: React.DependencyList +): void => { + const dispatchNotification = useSetAtom(dispatchToastNotificationAtom); + const dismissNotification = useSetAtom(dismissToastNotificationAtom); + + const toast: ToastNotification = { + id: "something-went-wrong", + title: "Something went wrong", + description: "Try checking your network settings and refreshing the page", + type: "error", + }; + + useEffect(() => { + if (atomsAreError(...atoms)) { + dismissNotification(toast.id); + dispatchNotification(toast); + } + }, deps); +}; diff --git a/apps/namadillo/src/tailwind.css b/apps/namadillo/src/tailwind.css new file mode 100644 index 000000000..0988d1559 --- /dev/null +++ b/apps/namadillo/src/tailwind.css @@ -0,0 +1,59 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + @apply bg-gray; + @apply pb-1; +} + +.dark-scrollbar { + @apply pr-2; +} + +.dark-scrollbar::-webkit-scrollbar { + @apply w-1; +} + +.dark-scrollbar::-webkit-scrollbar-track { + @apply bg-black; +} + +.dark-scrollbar::-webkit-scrollbar-thumb { + @apply bg-neutral-300/90 rounded-md transition-colors duration-300 ease-out; +} + +.app-loader { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 10; + + &::after { + content: ""; + position: absolute; + width: 32px; + height: 32px; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + border: 4px solid transparent; + border-top-color: yellow; + border-radius: 50%; + animation: button-loading-spinner 1s ease infinite; + + @keyframes button-loading-spinner { + from { + transform: rotate(0turn); + } + + to { + transform: rotate(1turn); + } + } + } +} diff --git a/apps/namadillo/src/types/environment.d.ts b/apps/namadillo/src/types/environment.d.ts new file mode 100644 index 000000000..e52f44c5c --- /dev/null +++ b/apps/namadillo/src/types/environment.d.ts @@ -0,0 +1,31 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + NODE_ENV: "development" | "production"; + NAMADA_INTERFACE_LOCAL?: "true" | "false"; + + NAMADA_INTERFACE_NAMADA_ALIAS?: string; + NAMADA_INTERFACE_NAMADA_CHAIN_ID?: string; + NAMADA_INTERFACE_NAMADA_URL?: string; + NAMADA_INTERFACE_NAMADA_BECH32_PREFIX?: string; + NAMADA_INTERFACE_NAMADA_FAUCET_ADDRESS?: string; + NAMADA_INTERFACE_NAMADA_FAUCET_LIMIT?: string; + NAMADA_INTERFACE_NAMADA_TOKEN?: string; + + NAMADA_INTERFACE_COSMOS_ALIAS?: string; + NAMADA_INTERFACE_COSMOS_CHAIN_ID?: string; + NAMADA_INTERFACE_COSMOS_CHAIN_URL?: string; + + NAMADA_INTERFACE_OSMOSIS_ALIAS?: string; + NAMADA_INTERFACE_OSMOSIS_CHAIN_ID?: string; + NAMADA_INTERFACE_OSMOSIS_URL?: string; + + // CoinGecko + NAMADA_INTERFACE_API_URL?: string; + NAMADA_INTERFACE_API_KEY?: string; + NAMADA_INTERFACE_API_TTL?: string; + } + } +} + +export { }; diff --git a/apps/namadillo/src/types/events.ts b/apps/namadillo/src/types/events.ts new file mode 100644 index 000000000..e69de29bb diff --git a/apps/namadillo/src/types/fees.ts b/apps/namadillo/src/types/fees.ts new file mode 100644 index 000000000..d6754df09 --- /dev/null +++ b/apps/namadillo/src/types/fees.ts @@ -0,0 +1,6 @@ +import BigNumber from "bignumber.js"; + +export type GasConfig = { + gasLimit: BigNumber; + gasPrice: BigNumber; +}; diff --git a/apps/namadillo/src/types/notifications.ts b/apps/namadillo/src/types/notifications.ts new file mode 100644 index 000000000..046e4b727 --- /dev/null +++ b/apps/namadillo/src/types/notifications.ts @@ -0,0 +1,11 @@ +export type ToastNotification = { + id: string; + type: "pending" | "success" | "error"; + title: React.ReactNode; + description: React.ReactNode; + timeout?: number; +}; + +export type ToastNotificationEntryFilter = ( + notification: ToastNotification +) => boolean; diff --git a/apps/namadillo/src/types/staking.ts b/apps/namadillo/src/types/staking.ts new file mode 100644 index 000000000..7ee261e18 --- /dev/null +++ b/apps/namadillo/src/types/staking.ts @@ -0,0 +1,13 @@ +import BigNumber from "bignumber.js"; +import { ValidatorAddress } from "./validators"; + +export type ChangeInStakingPosition = { + validatorId: ValidatorAddress; + amount: BigNumber; +}; + +export type RedelegateChange = { + sourceValidator: string; + destinationValidator: string; + amount: BigNumber; +}; diff --git a/apps/namadillo/src/types/validators.ts b/apps/namadillo/src/types/validators.ts new file mode 100644 index 000000000..59a7133bb --- /dev/null +++ b/apps/namadillo/src/types/validators.ts @@ -0,0 +1 @@ +export type ValidatorAddress = string; diff --git a/apps/namadillo/src/urls.ts b/apps/namadillo/src/urls.ts new file mode 100644 index 000000000..71eba56b5 --- /dev/null +++ b/apps/namadillo/src/urls.ts @@ -0,0 +1,2 @@ +export const DISCORD_URL = "https://discord.com/invite/namada"; +export const TWITTER_URL = "https://twitter.com/namada"; diff --git a/apps/namadillo/src/utils/index.ts b/apps/namadillo/src/utils/index.ts new file mode 100644 index 000000000..c0b34a38b --- /dev/null +++ b/apps/namadillo/src/utils/index.ts @@ -0,0 +1,22 @@ +import { ProposalStatus, ProposalTypeString } from "@namada/types"; + +export const showProposalStatus = (status: ProposalStatus): string => { + const statusText: Record<ProposalStatus, string> = { + pending: "Upcoming", + ongoing: "Ongoing", + passed: "Passed", + rejected: "Rejected", + }; + + return statusText[status]; +}; + +export const showProposalTypeString = (type: ProposalTypeString): string => { + const typeText: Record<ProposalTypeString, string> = { + default: "Default", + pgf_steward: "PGF Steward", + pgf_payment: "PGF Payment", + }; + + return typeText[type]; +}; diff --git a/apps/namadillo/src/utils/routes.ts b/apps/namadillo/src/utils/routes.ts new file mode 100644 index 000000000..8350a6a6b --- /dev/null +++ b/apps/namadillo/src/utils/routes.ts @@ -0,0 +1,11 @@ +export type RouteOutput = { + url: string; + toString: () => string; +}; + +export const createRouteOutput = + (index: () => string) => + (route: string): RouteOutput => ({ + url: `${index()}${route}`, + toString: () => route, + }); diff --git a/apps/namadillo/tailwind.config.cjs b/apps/namadillo/tailwind.config.cjs new file mode 100644 index 000000000..bd5c2433b --- /dev/null +++ b/apps/namadillo/tailwind.config.cjs @@ -0,0 +1,12 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./src/**/*.{js,jsx,ts,tsx}", + "../../packages/components/src/**/*.{js,ts,jsx,tsx}", + ], + presets: [require("@namada/components/src/theme.js")], + theme: { + extend: {}, + }, + plugins: [require("@tailwindcss/container-queries")], +}; diff --git a/apps/namadillo/tsconfig.json b/apps/namadillo/tsconfig.json new file mode 100644 index 000000000..c602a34a5 --- /dev/null +++ b/apps/namadillo/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./src", + "allowJs": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["dom", "dom.iterable", "esnext"], + "module": "esnext", + "moduleResolution": "node", + "noEmit": false, + "noFallthroughCasesInSwitch": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "es2015" + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules"], + "typeRoots": ["./node_modules/@types"] +} diff --git a/apps/namadillo/tsconfig.webpack.json b/apps/namadillo/tsconfig.webpack.json new file mode 100644 index 000000000..12b6f0f18 --- /dev/null +++ b/apps/namadillo/tsconfig.webpack.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig", + "exclude": [ + "**/*.test.ts", + "./node_modules/**/*.tsx", + "node_modules/**/*.ts", + "node_modules" + ] +} diff --git a/apps/namadillo/vite.config.js b/apps/namadillo/vite.config.js new file mode 100644 index 000000000..b01835215 --- /dev/null +++ b/apps/namadillo/vite.config.js @@ -0,0 +1,47 @@ +/* eslint-disable */ +import react from "@vitejs/plugin-react"; +import { defineConfig, loadEnv } from "vite"; +import checker from "vite-plugin-checker"; +import { nodePolyfills } from "vite-plugin-node-polyfills"; +import tsconfigPaths from "vite-tsconfig-paths"; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ""); + const filteredEnv = Object.keys(env).reduce((acc, current) => { + if (current.startsWith("NAMADA_INTERFACE_")) { + return { + ...acc, + [current]: env[current], + }; + } + return acc; + }, {}); + + return { + plugins: [ + react(), + tsconfigPaths(), + nodePolyfills({ + protocolImports: true, + }), + checker({ + typescript: true, + eslint: { lintCommand: 'eslint "./src/**/*.{ts,tsx}"' }, + }), + ], + define: { + "process.env": filteredEnv, + }, + optimizeDeps: { + esbuildOptions: { + // Node.js global to browser globalThis + define: { + global: "globalThis", + }, + }, + }, + build: { + commonjsOptions: { transformMixedEsModules: true }, // Change + }, + }; +}); diff --git a/apps/namadillo/webpack.config.js b/apps/namadillo/webpack.config.js new file mode 100644 index 000000000..f9b22e3eb --- /dev/null +++ b/apps/namadillo/webpack.config.js @@ -0,0 +1,189 @@ +const BundleAnalyzerPlugin = + require("webpack-bundle-analyzer").BundleAnalyzerPlugin; +const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); +const webpack = require("webpack"); +const { resolve, join } = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const CopyPlugin = require("copy-webpack-plugin"); +const { getProcessEnv } = require("@namada/config/webpack.js"); + +// Load environment variables +require("dotenv").config({ path: resolve(__dirname, ".env") }); + +const { NODE_ENV, BUNDLE_ANALYZE } = process.env; + +const createStyledComponentsTransformer = + require("typescript-plugin-styled-components").default; + +const copyPatterns = [ + { + from: "../../packages/shared/src/shared/shared_bg.wasm", + to: "./shared.namada.wasm", + }, + { + from: "../../packages/crypto/src/crypto/crypto_bg.wasm", + to: "./crypto.namada.wasm", + }, + + { + from: "./public/*.png", + to: "./assets/[name].png", + }, + { + from: "./public/manifest.json", + to: "./manifest.json", + }, + { + from: "./public/_redirects", + to: "./", + }, +]; + +const analyzePlugins = + BUNDLE_ANALYZE === "true" ? [new BundleAnalyzerPlugin()] : []; + +const plugins = [ + ...analyzePlugins, + new CopyPlugin({ + patterns: copyPatterns, + }), + new webpack.ProvidePlugin({ + Buffer: ["buffer", "Buffer"], + }), + new HtmlWebpackPlugin({ + template: resolve("./public/index.html"), + }), + // Provide environment variables to interface: + new webpack.DefinePlugin({ + process: { + env: JSON.stringify( + getProcessEnv("NAMADA_INTERFACE", [ + "TARGET", + "NODE_ENV", + "npm_package_version", + ]) + ), + }, + }), +]; + +module.exports = { + mode: NODE_ENV, + target: "web", + devtool: "eval-source-map", + entry: { + interface: "./src", + }, + output: { + publicPath: "/", + path: resolve(__dirname, `./build/`), + filename: "[name].bundle.js", + }, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: "ts-loader", + exclude: /node_modules/, + options: { + configFile: resolve(__dirname, "tsconfig.webpack.json"), + getCustomTransformers: (program) => ({ + before: [ + createStyledComponentsTransformer(program, { + setComponentId: true, + setDisplayName: true, + minify: true, + }), + ], + }), + }, + }, + { + test: /\.css$/i, + use: ["style-loader", "css-loader", "postcss-loader"], + }, + { + test: /\.png$/i, + use: [ + { + loader: "file-loader", + }, + ], + }, + { + test: /\.wasm$/, + type: "asset/inline", + exclude: /node_modules/, + }, + { + test: /\.svg$/, + use: [ + { + loader: require.resolve("@svgr/webpack"), + options: { + prettier: false, + svgo: false, + svgoConfig: { + plugins: [{ removeViewBox: false }], + }, + titleProp: true, + ref: true, + }, + }, + { + loader: require.resolve("file-loader"), + options: { + name: "assets/[name].[hash].[ext]", + }, + }, + ], + issuer: { + and: [/\.(ts|tsx|md)$/], + }, + }, + ], + }, + resolve: { + alias: { + // Use styled-components from namada-interface, not packages/components + "styled-components": resolve( + __dirname, + "node_modules", + "styled-components" + ), + }, + extensions: [".tsx", ".ts", ".js"], + modules: [resolve(__dirname, "src/"), "node_modules"], + fallback: { + buffer: require.resolve("buffer/"), + crypto: require.resolve("crypto-browserify"), + }, + plugins: [ + new TsconfigPathsPlugin({ + configFile: "tsconfig.webpack.json", + }), + ], + }, + performance: { + hints: "warning", + maxAssetSize: 200000, + maxEntrypointSize: 400000, + assetFilter: function(assetFilename) { + assetFilename.endsWith(".wasm"); + }, + }, + plugins, + devServer: { + static: { + directory: join(__dirname, "public"), + }, + compress: false, + port: 3000, + hot: true, + historyApiFallback: true, + }, + stats: { + // We want to ignore wasm-bindgen-rayon circular dependency warning + warningsFilter: [/dependency between chunks.+wasm-bindgen-rayon/], + }, +}; diff --git a/package.json b/package.json index 56ff84f75..81661f81a 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "version": "0.2.4", "private": true, "workspaces": [ - "apps/*", + "apps/namadillo", + "apps/extension", + "apps/faucet", "packages/*", "e2e" ], @@ -35,7 +37,15 @@ "release-it": "^17.0.1", "rimraf": "^5.0.5", "stream-browserify": "^3.0.0", + "vite-plugin-checker": "^0.6.4", "wsrun": "^5.2.4" }, - "packageManager": "yarn@4.0.2" + "packageManager": "yarn@4.0.2", + "dependencies": { + "@tailwindcss/container-queries": "^0.1.1", + "@tanstack/query-core": "^5.32.0", + "invariant": "^2.2.4", + "jotai-tanstack-query": "^0.8.5", + "wonka": "^6.3.4" + } } diff --git a/packages/components/package.json b/packages/components/package.json index 3b43e926a..787ad46c1 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -19,8 +19,9 @@ "lint:ci": "yarn lint --max-warnings 0" }, "dependencies": { + "@namada/utils": "0.2.1", + "bignumber.js": "^9.1.1", "clsx": "^2.0.0", - "react": "^17.0.2", "react-icons": "^4.12.0", "styled-components": "^5.3.5", "tailwind-merge": "^2.1.0", @@ -39,6 +40,7 @@ }, "peerDependencies": { "postcss": "^8.4.32", + "react": "^18.0.0", "tailwindcss": "^3.3.6" }, "files": [ diff --git a/packages/components/src/ActionButton.tsx b/packages/components/src/ActionButton.tsx index c8eeb9586..cc4a965e4 100644 --- a/packages/components/src/ActionButton.tsx +++ b/packages/components/src/ActionButton.tsx @@ -14,10 +14,12 @@ const actionButton = tv({ primary: "bg-yellow hover:text-yellow", secondary: "bg-cyan hover:text-cyan", black: "bg-black text-yellow hover:text-black", + white: "bg-white text-black hover:text-white", + magenta: "bg-pink text-white", }, size: { - xs: "px-4 py-1.5 text-sm", + xs: "px-4 py-1.5 text-sm leading-none", sm: "px-6 py-2.5 text-sm", md: "px-8 py-3 text-base", lg: "px-8 py-[0.825em] text-lg", @@ -25,13 +27,16 @@ const actionButton = tv({ }, borderRadius: { - sm: "rounded-sm", - md: "rounded-md", - lg: "rounded-lg", + sm: "rounded-sm before:rounded-sm", + md: "rounded-md before:rounded-md", + lg: "rounded-lg before:rounded-lg", }, outlined: { - true: "bg-transparent border border-current", + true: clsx( + "bg-transparent before:border before:border-current before:absolute", + "before:left-0 before:top-0 before:w-full before:h-full before:z-[1000]" + ), }, disabled: { @@ -48,18 +53,22 @@ const actionButton = tv({ color: "primary", class: "text-yellow", }, - { outlined: true, color: "secondary", class: "text-cyan", }, - + { outlined: true, color: "magenta", class: "text-pink bg-pink-800" }, { outlined: true, color: "black", class: "text-black", }, + { + outlined: true, + color: "white", + class: "text-white", + }, ], defaultVariants: { @@ -81,6 +90,8 @@ const actionButtonHover = tv({ primary: "bg-yellow", secondary: "bg-cyan", black: "bg-black", + white: "bg-white", + magenta: "bg-pink-900", }, disabled: { true: "group-hover:translate-y-[calc(100%+2px)]", diff --git a/packages/components/src/Alert.tsx b/packages/components/src/Alert.tsx index 001626157..5864f5b47 100644 --- a/packages/components/src/Alert.tsx +++ b/packages/components/src/Alert.tsx @@ -10,6 +10,7 @@ const alert = tv({ type: { success: "", error: { base: "text-red-500" }, + removal: { base: "bg-pink-800 text-pink" }, warning: { base: "bg-yellow-900 text-yellow" }, info: { base: "bg-neutral-900 text-white border-0", @@ -42,7 +43,7 @@ export const Alert = ({ aria-labelledby={title} > {title && <strong className={alertClass.title()}>{title}</strong>} - <div className="text-sm leading-[1.15em]">{children}</div> + <div className="leading-[1.15em]">{children}</div> </div> ); }; diff --git a/packages/components/src/AmountInput.tsx b/packages/components/src/AmountInput.tsx index fb4638c33..c7acfad1e 100644 --- a/packages/components/src/AmountInput.tsx +++ b/packages/components/src/AmountInput.tsx @@ -1,19 +1,16 @@ import BigNumber from "bignumber.js"; -import { ChangeEventHandler, ComponentProps, useState } from "react"; +import { ChangeEventHandler, useState } from "react"; import { Result } from "@namada/utils"; -import { Input } from "./Input"; +import { Input, InputProps } from "./Input"; -type BigNumberElement = Omit<HTMLInputElement, "value"> & { - value?: BigNumber; +export type BigNumberElement = Omit<HTMLInputElement, "value"> & { + value?: BigNumber | undefined; }; -type Props = Omit< - ComponentProps<typeof Input>, - "value" | "onChange" | "min" | "max" -> & { - value?: BigNumber; +type Props = Omit<InputProps, "value" | "onChange" | "min" | "max"> & { + value?: BigNumber | undefined; onChange?: ChangeEventHandler<BigNumberElement>; maxDecimalPlaces?: number; min?: string | number | BigNumber; @@ -64,14 +61,14 @@ export const AmountInput: React.FC<Props> = ({ error, ...rest }) => { - const [inputString, setInputString] = useState<string>(); + const [inputString, setInputString] = useState<string | undefined>(); const [lastKnownValue, setLastKnownValue] = useState<BigNumber>(); const [validationError, setValidationError] = useState<string>(); const valueChanged = - value === undefined || lastKnownValue === undefined - ? value !== lastKnownValue - : !value.isEqualTo(lastKnownValue); + value === undefined || lastKnownValue === undefined ? + value !== lastKnownValue + : !value.isEqualTo(lastKnownValue); if (valueChanged) { setLastKnownValue(value); @@ -87,9 +84,17 @@ export const AmountInput: React.FC<Props> = ({ const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => { const stringValue = event.target.value; - setInputString(stringValue); + if (stringValue === undefined || stringValue === "") { + onChange?.({ + ...event, + target: { ...event.target, value: undefined }, + currentTarget: { ...event.currentTarget, value: undefined }, + }); + return; + } + const validateResult = validate(stringValue, { min, max, @@ -98,12 +103,12 @@ export const AmountInput: React.FC<Props> = ({ const asBigNumber = validateResult.ok ? validateResult.value : undefined; const error = - validateResult.ok || stringValue === "" - ? "" - : errorMessages[validateResult.error]; + validateResult.ok || stringValue === "" ? + "" + : errorMessages[validateResult.error]; setValidationError(error); - setLastKnownValue(asBigNumber); + event.currentTarget.setCustomValidity(error); onChange?.({ ...event, target: { ...event.target, value: asBigNumber }, diff --git a/packages/components/src/AmountSummaryCard.tsx b/packages/components/src/AmountSummaryCard.tsx new file mode 100644 index 000000000..e7205e183 --- /dev/null +++ b/packages/components/src/AmountSummaryCard.tsx @@ -0,0 +1,84 @@ +import clsx from "clsx"; +import { createElement } from "react"; +import { twMerge } from "tailwind-merge"; +import { SkeletonLoading } from "./SkeletonLoading"; +import { Stack } from "./Stack"; + +type AmountSummaryCardProps = { + as?: keyof JSX.IntrinsicElements; + logoElement?: React.ReactNode; + title?: React.ReactNode; + mainAmount?: React.ReactNode; + alternativeAmount?: React.ReactNode; + extra?: React.ReactNode; + callToAction: React.ReactNode; + className?: string; + isLoading?: boolean; +}; + +export const AmountSummaryCard = ({ + as = "div", + logoElement, + title, + mainAmount, + alternativeAmount, + extra, + callToAction, + className = "", + isLoading = false, +}: AmountSummaryCardProps): JSX.Element => { + return createElement( + as, + { + className: twMerge( + clsx( + "flex flex-col gap-8 justify-between rounded-sm", + "pt-5 pb-6 px-4 h-full" + ), + className + ), + }, + <> + <header className="flex flex-col gap-3"> + {logoElement && <i className="block w-9 mx-auto">{logoElement}</i>} + {title && ( + <div + className={clsx( + "text-white text-sm text-center font-medium", + "leading-4 max-w-[150px] mx-auto" + )} + > + {title} + </div> + )} + </header> + + {isLoading && ( + <Stack gap={2.5} className="h-[76px] items-center"> + <SkeletonLoading height="26px" width="100px" /> + <SkeletonLoading height="16px" width="50px" /> + </Stack> + )} + + {!isLoading && ( + <div className="text-center flex-1"> + {mainAmount && ( + <strong className="block text-[22px] text-white font-medium"> + {mainAmount} + </strong> + )} + {alternativeAmount && ( + <span className="block text-base text-neutral-500 font-medium"> + {alternativeAmount} + </span> + )} + {extra} + </div> + )} + + <footer className="flex flex-col items-center mx-auto"> + {callToAction} + </footer> + </> + ); +}; diff --git a/packages/components/src/Checkbox.tsx b/packages/components/src/Checkbox.tsx index c3e7d0364..2c12350d6 100644 --- a/packages/components/src/Checkbox.tsx +++ b/packages/components/src/Checkbox.tsx @@ -1,5 +1,6 @@ import clsx from "clsx"; import { GoCheck } from "react-icons/go"; +import { twMerge } from "tailwind-merge"; import { tv, type VariantProps } from "tailwind-variants"; const checkbox = tv({ @@ -28,11 +29,14 @@ const checkbox = tv({ }, }); -type CheckboxProps = React.ComponentPropsWithRef<"input"> & +type CheckboxProps = { + checkedClassName?: string; +} & React.ComponentPropsWithRef<"input"> & VariantProps<typeof checkbox>; export const Checkbox = ({ className, + checkedClassName, ...props }: CheckboxProps): JSX.Element => { const checkboxClasses = checkbox({ @@ -40,7 +44,12 @@ export const Checkbox = ({ }); return ( - <label className={checkboxClasses.label({ class: className })}> + <label + className={twMerge( + checkboxClasses.label({ class: className }), + props.checked && checkedClassName + )} + > <input className={checkboxClasses.checkbox()} type="checkbox" diff --git a/packages/components/src/CopyToClipboardControl.tsx b/packages/components/src/CopyToClipboardControl.tsx index d3670bca4..115009870 100644 --- a/packages/components/src/CopyToClipboardControl.tsx +++ b/packages/components/src/CopyToClipboardControl.tsx @@ -2,15 +2,18 @@ import { copyToClipboard } from "@namada/utils"; import { motion } from "framer-motion"; import { useState } from "react"; import { GoCheckCircle, GoCopy } from "react-icons/go"; +import { twMerge } from "tailwind-merge"; type CopyToClipboardControlIcon = { value: string; className?: string; + children?: React.ReactNode; }; export const CopyToClipboardControl = ({ value, className, + children, }: CopyToClipboardControlIcon): JSX.Element => { const [copied, setCopied] = useState(false); @@ -31,12 +34,16 @@ export const CopyToClipboardControl = ({ return ( <div - className={className} + className={twMerge( + "relative flex items-center gap-2 active:top-px", + className + )} role="button" aria-labelledby="Copy to clipboard" onClick={onClick} > - {copied ? ( + {children} + {copied ? <motion.div initial={{ opacity: 0, scale: 0.5 }} animate={{ opacity: 1, scale: 1 }} @@ -44,9 +51,7 @@ export const CopyToClipboardControl = ({ > <GoCheckCircle /> </motion.div> - ) : ( - <GoCopy /> - )} + : <GoCopy />} </div> ); }; diff --git a/packages/components/src/Currency.tsx b/packages/components/src/Currency.tsx new file mode 100644 index 000000000..55bbd9fa4 --- /dev/null +++ b/packages/components/src/Currency.tsx @@ -0,0 +1,75 @@ +import { KnownCurrencies } from "@namada/utils"; +import BigNumber from "bignumber.js"; + +export type CurrencyProps = { + amount: number | BigNumber; + hideBalances?: boolean; + currency: keyof typeof KnownCurrencies; + separator?: "." | "," | ""; + spaceAroundSign?: boolean; + currencyPosition?: "left" | "right"; + currencySignClassName?: string; + baseAmountClassName?: string; + fractionClassName?: string; +} & React.ComponentPropsWithoutRef<"span">; + +export const Currency = ({ + amount, + currency, + hideBalances = false, + currencyPosition = "left", + separator = ".", + className = "", + spaceAroundSign = false, + currencySignClassName = "", + baseAmountClassName = "", + fractionClassName = "", + ...containerRest +}: CurrencyProps): JSX.Element => { + const currencyObj = KnownCurrencies[currency]; + const amountParts = BigNumber(amount).toString().split("."); + const baseAmount = hideBalances ? "✳✳✳✳" : amountParts[0] || "0"; + const fraction = + amountParts.length > 1 && !hideBalances ? amountParts[1] : ""; + + const currencyHtml = ( + <span className={currencySignClassName}> + {spaceAroundSign && currencyPosition === "right" ? " " : null} + {currencyObj.sign} + {spaceAroundSign && currencyPosition === "left" ? " " : null} + </span> + ); + + const baseAmountHtml = ( + <span className={baseAmountClassName}>{baseAmount}</span> + ); + + const fractionHtml = fraction && ( + <span className={fractionClassName}>{fraction}</span> + ); + + const amountText = + BigNumber(baseAmount).eq(1) ? currencyObj.singular : currencyObj.plural; + + const centsText = + BigNumber(fraction).gt(0) ? ` and ${fraction} ${currencyObj.fraction}` : ""; + + const screenReaderText = `${baseAmount} ${amountText}${centsText}`; + + return ( + <span className={className} {...containerRest}> + <span aria-hidden="true"> + {currencyPosition === "left" && currencyHtml} + {baseAmountHtml} + {fractionHtml ? + <> + {separator} + {fractionHtml} + </> + : null} + {currencyPosition === "right" && currencyHtml} + </span> + <span className="sr-only">{screenReaderText}</span> + </span> + ); +}; diff --git a/packages/components/src/Input.tsx b/packages/components/src/Input.tsx index d74d27cc7..c724efb12 100644 --- a/packages/components/src/Input.tsx +++ b/packages/components/src/Input.tsx @@ -10,7 +10,7 @@ const inputClassList = tv({ slots: { field: clsx( "bg-black border rounded-sm text-white text-base font-medium leading-[1.25]", - "py-5 px-4 w-full transition-all duration-150 ease-out focus:outline-0 active:outline-0", + "py-5 px-4 w-full transition-all duration-150 ease-out focus:outline-0 active:outline-0 focus:outline-0", "placeholder:text-neutral-700 placeholder:transition-opacity placeholder:duration-150 placeholder:ease-out", "hover:placeholder:opacity-70 focus:placeholder:opacity-0 select:bg-neutral-600", "[&[readonly]]:select-none [&[readonly]]:pointer-events-none" @@ -18,7 +18,7 @@ const inputClassList = tv({ label: "text-white text-sm font-medium [&_p]:pb-1", labelText: "pl-1.5", error: "text-red-500 hidden text-xs font-normal pl-1.5", - inputWrapper: "flex mt-2 mb-1 relative", + inputWrapper: "flex relative", icon: clsx( "flex items-center cursor-pointer h-full absolute right-4 top-0 text-2xl", "text-yellow [&_path]:stroke-yellow [&_rect]:stroke-yellow" @@ -29,9 +29,15 @@ const inputClassList = tv({ hasError: { true: { field: "focus:!border-red-500", + inputWrapper: "mb-1", error: "block", }, }, + hasLabel: { + true: { + inputWrapper: "mt-2", + }, + }, hasIcon: { true: { field: "pr-12", @@ -66,7 +72,7 @@ type InputVariants = | "Textarea" | "ReadOnlyCopy"; -type InputProps = { +export type InputProps = { label?: string | React.ReactNode; error?: string | React.ReactNode; sensitive?: boolean; @@ -108,7 +114,9 @@ export const Input = ({ aria-labelledby={passwordShown ? "Hide password" : "Display password"} onClick={() => setPasswordShown(!passwordShown)} > - {passwordShown ? <GoEye /> : <GoEyeClosed />} + {passwordShown ? + <GoEye /> + : <GoEyeClosed />} </span> ); }, @@ -142,6 +150,7 @@ export const Input = ({ const classes = inputClassList({ hasError: !!error, hasHint: !!hint, + hasLabel: !!label, hasIcon: iconMap.hasOwnProperty(variant), isSensitive: sensitive, theme: theme || "neutral", @@ -166,11 +175,9 @@ export const Input = ({ > {label && <span className={classes.labelText()}>{label}</span>} <div className={classes.inputWrapper()}> - {sensitive ? ( + {sensitive ? <ContentMasker color={theme}>{element}</ContentMasker> - ) : ( - element - )} + : element} {!hideIcon && icon} {children} </div> diff --git a/packages/components/src/InsetLabel.tsx b/packages/components/src/InsetLabel.tsx new file mode 100644 index 000000000..ef18c1955 --- /dev/null +++ b/packages/components/src/InsetLabel.tsx @@ -0,0 +1,23 @@ +import { twMerge } from "tailwind-merge"; +import { tv, type VariantProps } from "tailwind-variants"; + +const insetLabel = tv({ + base: "rounded-sm w-fit px-4 py-1 text-neutral-450", + variants: { + color: { + dark: "bg-rblack", + light: "bg-[#1B1B1B]", + }, + }, + defaultVariants: { + color: "light", + }, +}); + +export const InsetLabel: React.FC< + React.ComponentProps<"div"> & VariantProps<typeof insetLabel> +> = ({ children, className, color, ...rest }) => ( + <div className={twMerge(insetLabel({ color }), className)} {...rest}> + {children} + </div> +); diff --git a/packages/components/src/Modal.tsx b/packages/components/src/Modal.tsx index 55e67707b..042d22696 100644 --- a/packages/components/src/Modal.tsx +++ b/packages/components/src/Modal.tsx @@ -1,63 +1,56 @@ -import { default as ReactModal } from "react-modal"; -import { tv } from "tailwind-variants"; +import clsx from "clsx"; +import { useEffect } from "react"; +import { twMerge } from "tailwind-merge"; -type Props = { +type ModalProps = { children: JSX.Element; - isOpen: boolean; - title?: string; - onBackdropClick?: () => void; -}; + onClose: () => void; +} & React.ComponentPropsWithoutRef<"div">; -const modal = tv({ - slots: { - container: "flex flex-col items-center w-full bg-neutral-900", - header: "flex justify-center items-center text-neutral-200", - content: "flex items-center justify-center w-full bg-neutral-900", - }, -}); +export const Modal = ({ + onClose, + children, + className = "", + ...props +}: ModalProps): JSX.Element => { + useEffect(() => { + const onKeyPress = (event: KeyboardEvent): void => { + if (event.key === "Escape") { + onClose(); + } + }; -export const Modal = (props: Props): JSX.Element => { - const { children, isOpen, title, onBackdropClick } = props; + document.addEventListener("keydown", onKeyPress); + return () => { + document.removeEventListener("keydown", onKeyPress); + }; + }, []); - const { container, header, content } = modal(); + useEffect(() => { + const classList = ["max-h-[100svh]", "overflow-hidden"]; + document.body.classList.add(...classList); + return () => { + document.body.classList.remove(...classList); + }; + }, []); return ( - <ReactModal - shouldCloseOnOverlayClick={onBackdropClick !== undefined} - onRequestClose={() => { - onBackdropClick && onBackdropClick(); - }} - style={{ - overlay: { - backgroundColor: "rgba(0, 0, 0, 0.75)", - display: "flex", - justifyContent: "center", - alignItems: "center", - zIndex: "1100", - }, - content: { - top: "undefined", - left: "undefined", - right: "undefined", - bottom: "undefined", - zIndex: "1100", - display: "flex", - justifyContent: "center", - width: "80%", - maxWidth: "640px", - height: "80%", - padding: 0, - }, - }} - isOpen={isOpen} - ariaHideApp={false} - > - <div className={container()}> - <header className={header()}> - <h3>{title}</h3> - </header> - <div className={content()}>{children}</div> + <> + <div + onClick={onClose} + className="fixed top-0 left-0 w-full h-full cursor-pointer backdrop-blur-lg z-[1000] bg-rblack/50" + /> + <div + {...props} + className={twMerge( + clsx( + "fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-20 z-[1001]", + className + ) + )} + > + {children} </div> - </ReactModal> + </> ); }; diff --git a/packages/components/src/Panel.tsx b/packages/components/src/Panel.tsx index b89387917..60b047439 100644 --- a/packages/components/src/Panel.tsx +++ b/packages/components/src/Panel.tsx @@ -1,17 +1,38 @@ -import clsx from "clsx"; +import React from "react"; +import { twMerge } from "tailwind-merge"; -type PanelProps = { +type PanelProps<T extends keyof JSX.IntrinsicElements> = { children: React.ReactNode; -}; + as?: T; + hierarchy?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + title?: React.ReactNode; +} & React.ComponentPropsWithoutRef<T>; -export const Panel = ({ children }: PanelProps): JSX.Element => { - return ( - <div - className={clsx( - "relative text-white rounded-lg bg-gray w-[540px] min-h-[640px]" - )} - > +export const Panel = <T extends keyof JSX.IntrinsicElements = "div">({ + children, + as, + hierarchy = "h3", + title, + className, + ...props +}: PanelProps<T>): JSX.Element => { + return React.createElement( + as || "div", + { + className: twMerge( + "rounded-sm bg-rblack px-4 py-5 text-white font-medium", + className + ), + ...props, + }, + <> + {title && + React.createElement( + hierarchy, + { className: "relative z-20 mb-8" }, + title + )} {children} - </div> + </> ); }; diff --git a/packages/components/src/PieChart.tsx b/packages/components/src/PieChart.tsx new file mode 100644 index 000000000..7a3f42a81 --- /dev/null +++ b/packages/components/src/PieChart.tsx @@ -0,0 +1,108 @@ +import BigNumber from "bignumber.js"; +import clsx from "clsx"; +import { twMerge } from "tailwind-merge"; + +export type PieChartData = { + value: number | BigNumber; + color: string; +}; + +type PieChartProps = Omit< + React.ComponentPropsWithRef<"svg">, + "onMouseEnter" +> & { + id: string; + data: PieChartData[]; + children?: React.ReactNode; + radius?: number; + strokeWidth?: number; + segmentMargin?: number; + contentProps?: React.ComponentPropsWithoutRef<"div">; + onMouseEnter?: (data: PieChartData, index: number) => void; + onMouseLeave?: () => void; +}; + +export const PieChart = ({ + id, + data, + children, + strokeWidth = 10, + radius = 50, + segmentMargin = 2.5, + contentProps = {}, + onMouseEnter, + onMouseLeave, + ...svgProps +}: PieChartProps): JSX.Element => { + const length = 2 * Math.PI * (radius - strokeWidth); + const indexedData = data.map((dataItem, index) => ({ + ...dataItem, + originalIndex: index, // needed to preserve index when some values are 0 + })); + const filteredData = indexedData.filter((el) => + new BigNumber(el.value).gt(0) + ); + const margin = filteredData.length > 1 ? segmentMargin : 0; + const { className: contentPropsClassName, ...contentPropsRest } = + contentProps; + + const total = filteredData.reduce((acc, entry) => { + return acc.plus(entry.value); + }, new BigNumber(0)); + + const percentages = filteredData.map((entry) => + new BigNumber(entry.value).div(total).toNumber() + ); + + let offset = 0; + const renderData = (): React.ReactNode => + filteredData.map((dataItem, index) => { + const segmentLength = Math.abs(length * percentages[index] - margin); + const path = ( + <circle + fill="none" + key={`pie-chart-${id}-${index}`} + cx={radius} + cy={radius} + strokeWidth={strokeWidth} + strokeDashoffset={offset} + strokeDasharray={`${segmentLength} ${length}`} + r={radius - strokeWidth} + stroke={dataItem.color} + onMouseEnter={ + onMouseEnter ? + () => onMouseEnter(dataItem, dataItem.originalIndex) + : undefined + } + onMouseLeave={onMouseLeave ? () => onMouseLeave() : undefined} + /> + ); + offset -= segmentLength + margin; + return path; + }); + + return ( + <div className="relative w-full"> + <svg + viewBox={`0 0 ${radius * 2} ${radius * 2}`} + width="100%" + height="100%" + {...svgProps} + > + {renderData()} + </svg> + <div + className={twMerge( + clsx( + "absolute top-0 left-0 text-center w-full h-full", + "flex items-center justify-center pointer-events-none" + ), + contentPropsClassName + )} + {...contentPropsRest} + > + {children} + </div> + </div> + ); +}; diff --git a/packages/components/src/ProgressBar.tsx b/packages/components/src/ProgressBar.tsx new file mode 100644 index 000000000..29483ba9e --- /dev/null +++ b/packages/components/src/ProgressBar.tsx @@ -0,0 +1,23 @@ +import BigNumber from "bignumber.js"; + +import { SegmentedBar, SegmentedBarData } from "./SegmentedBar"; + +export const ProgressBar: React.FC< + { + value: SegmentedBarData; + total: SegmentedBarData; + } & Omit<React.ComponentProps<typeof SegmentedBar>, "data"> +> = ({ value, total, ...rest }) => { + const data = [ + value, + { + ...total, + value: BigNumber.max( + 0, + BigNumber(total.value.toString()).minus(value.value.toString()) + ), + }, + ]; + + return <SegmentedBar data={data} {...rest} />; +}; diff --git a/packages/components/src/RoundedLabel.tsx b/packages/components/src/RoundedLabel.tsx new file mode 100644 index 000000000..97f0aa538 --- /dev/null +++ b/packages/components/src/RoundedLabel.tsx @@ -0,0 +1,17 @@ +import { twMerge } from "tailwind-merge"; + +export const RoundedLabel: React.FC<React.ComponentProps<"div">> = ({ + children, + className, + ...rest +}) => ( + <div + className={twMerge( + "uppercase rounded-2xl border text-center px-2 py-1", + className + )} + {...rest} + > + {children} + </div> +); diff --git a/packages/components/src/SegmentedBar.tsx b/packages/components/src/SegmentedBar.tsx new file mode 100644 index 000000000..299bdfb3f --- /dev/null +++ b/packages/components/src/SegmentedBar.tsx @@ -0,0 +1,43 @@ +import BigNumber from "bignumber.js"; +import { twMerge } from "tailwind-merge"; + +import { formatPercentage } from "@namada/utils"; + +export type SegmentedBarData = { + value: number | BigNumber | bigint; + color: string; +}; + +export const SegmentedBar: React.FC< + { + data: SegmentedBarData[]; + } & React.ComponentProps<"div"> +> = ({ data, className, ...rest }) => { + if (data.length === 0) { + return null; + } + + const percentage = (value: number | BigNumber | bigint): string => { + const total = data.reduce( + (acc, curr) => acc.plus(BigNumber(curr.value.toString())), + BigNumber(0) + ); + + return formatPercentage(BigNumber(value.toString()).dividedBy(total), 2); + }; + + const init = data.slice(0, -1); + const last = data[data.length - 1]; + + return ( + <div className={twMerge("w-full h-[6px] flex", className)} {...rest}> + {init.map(({ value, color }, index) => ( + <div + key={index} + style={{ flexBasis: percentage(value), backgroundColor: color }} + ></div> + ))} + <div className="grow" style={{ backgroundColor: last.color }}></div> + </div> + ); +}; diff --git a/packages/components/src/SkeletonLoading.tsx b/packages/components/src/SkeletonLoading.tsx new file mode 100644 index 000000000..cd132c5fc --- /dev/null +++ b/packages/components/src/SkeletonLoading.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { twMerge } from "tailwind-merge"; + +type SkeletonLoadingProps = { + height: string; + width: string; +} & React.ComponentPropsWithoutRef<"span">; + +export const SkeletonLoading = ({ + height, + width, + ...props +}: SkeletonLoadingProps): JSX.Element => { + const { className, ...rest } = props; + return ( + <span + className={twMerge( + "bg-neutral-800 animate-pulse block rounded-[2px]", + className + )} + style={{ width, height }} + {...rest} + /> + ); +}; diff --git a/packages/components/src/StyledSelectBox.tsx b/packages/components/src/StyledSelectBox.tsx new file mode 100644 index 000000000..9e56f5e17 --- /dev/null +++ b/packages/components/src/StyledSelectBox.tsx @@ -0,0 +1,148 @@ +import clsx from "clsx"; +import { useEffect, useState } from "react"; +import { twMerge } from "tailwind-merge"; + +export type SelectBoxOption<T extends string> = { + id: T; + value: React.ReactNode; + ariaLabel: string; +}; + +export type SelectBoxProps<T extends string> = { + id: string; + value: T; + displayArrow?: boolean; + listContainerProps?: React.ComponentPropsWithoutRef<"ul">; + containerProps?: React.ComponentPropsWithoutRef<"div">; + listItemProps?: React.ComponentPropsWithoutRef<"li">; + defaultValue: React.ReactNode; + options: SelectBoxOption<T>[]; +} & React.ComponentPropsWithRef<"input">; + +export const StyledSelectBox = <T extends string = string>({ + id, + options, + value, + onChange, + containerProps = {}, + listItemProps = {}, + listContainerProps = {}, + displayArrow = true, + defaultValue, + ...props +}: SelectBoxProps<T>): JSX.Element => { + const [selected, setSelected] = useState<SelectBoxOption<T> | null>(null); + const [open, setOpen] = useState(false); + + useEffect(() => { + const defaultSelected = options.find((option) => option.id === value); + if (defaultSelected) { + setSelected(defaultSelected); + } + }, []); + + useEffect(() => { + const close = (): void => setOpen(false); + if (open) { + document.documentElement.addEventListener("click", close); + } + return () => { + document.documentElement.removeEventListener("click", close); + }; + }, [open]); + + const getElementId = (optionId: string): string => `radio-${id}-${optionId}`; + + const { className: containerClassList, ...otherContainerProps } = + containerProps; + + const { className: listContainerClassList, ...otherListContainerProps } = + listContainerProps; + + const { className: listItemClassList, ...otherListItemProps } = listItemProps; + + return ( + <div className="relative" onClick={(e) => e.stopPropagation()}> + <fieldset + role="radiogroup" + className="absolute pointer-events-none invisible" + > + {options.map((option: SelectBoxOption<T>) => ( + <input + key={`radio-${id}-${option.id}`} + id={getElementId(option.id)} + type="radio" + value={option.id} + checked={value === option.id} + aria-label={option.ariaLabel} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { + setSelected(option); + if (onChange) onChange(e); + }} + {...props} + /> + ))} + </fieldset> + <div> + <div + className={twMerge( + "group inline-flex items-center relative select-none", + "font-medium cursor-pointer pr-4 active:top-px", + clsx({ + "opacity-50": open, + }), + containerClassList + )} + role="button" + aria-label="Open Navigation" + onClick={() => setOpen(!open)} + {...otherContainerProps} + > + {selected ? selected.value : defaultValue} + {displayArrow && ( + <i + className={clsx( + "absolute right-0 rotate-45 border-r border-b w-[5px] h-[5px] border-current", + "origin-top", + { "rotate-[-135deg] translate-y-[0.35em]": open } + )} + /> + )} + </div> + <ul + className={twMerge( + "hidden absolute top-[2.5em] left-1/2 -translate-x-1/2 flex-col hidden", + "rounded-sm pt-2 pb-3 px-5 cursor-pointer bg-rblack z-[9999]", + clsx({ flex: open }), + listContainerClassList + )} + {...otherListContainerProps} + > + {options.map((option, idx) => ( + <li + key={`selectbox-item-${id}-${option.id}`} + className={twMerge( + clsx("group/item", { + "border-b border-current": idx < options.length - 1, + }), + listItemClassList + )} + onClick={() => setOpen(false)} + {...otherListItemProps} + > + <label + className={twMerge( + "grid grid-cols-[30px_max-content] cursor-pointer py-2", + "group-hover/item:text-cyan transition-color duration-150" + )} + htmlFor={getElementId(option.id)} + > + {option.value} + </label> + </li> + ))} + </ul> + </div> + </div> + ); +}; diff --git a/packages/components/src/StyledTable.tsx b/packages/components/src/StyledTable.tsx new file mode 100644 index 000000000..3be2975aa --- /dev/null +++ b/packages/components/src/StyledTable.tsx @@ -0,0 +1,62 @@ +import { ReactNode, useMemo } from "react"; +import { twMerge } from "tailwind-merge"; +import { StyledTableHead, TableHeader } from "./StyledTableHead"; +import { StyledTableRow, TableRow } from "./StyledTableRow"; + +type StyledTableProps = { + id: string; + sortable?: boolean; + containerClassName?: string; + tableProps?: React.ComponentPropsWithoutRef<"table">; + headProps?: React.ComponentPropsWithoutRef<"thead">; + headClassName?: string; + className?: string; + headers?: (TableHeader | ReactNode)[]; + rows?: TableRow[]; +}; + +export const StyledTable = ({ + id, + containerClassName, + tableProps = {}, + headProps = {}, + headers = [], + rows = [], +}: StyledTableProps): JSX.Element => { + const { className: tableClassName, ...otherTableProps } = tableProps; + + const tableHeaders = useMemo( + () => + headers && ( + <StyledTableHead id={id} headers={headers} headProps={headProps} /> + ), + [] + ); + + return ( + <div + className={twMerge( + "overscroll-contain max-w-full overflow-x-auto", + containerClassName + )} + > + <table + style={{ minWidth: (headers?.length || 0) * 100 + "px" }} + className={twMerge("border-spacing-4", tableClassName)} + {...otherTableProps} + > + {tableHeaders} + <tbody className="text-white text-base font-medium"> + {rows.map((row, index) => ( + <StyledTableRow + key={row.key ?? `table-tr-${id}-${index}`} + id={id} + index={index} + row={row} + /> + ))} + </tbody> + </table> + </div> + ); +}; diff --git a/packages/components/src/StyledTableHead.tsx b/packages/components/src/StyledTableHead.tsx new file mode 100644 index 000000000..af1053cd9 --- /dev/null +++ b/packages/components/src/StyledTableHead.tsx @@ -0,0 +1,67 @@ +import { ReactNode } from "react"; +import { twMerge } from "tailwind-merge"; + +export type TableHeader = { + children: ReactNode; + sortable?: boolean; +} & React.ComponentPropsWithoutRef<"th">; + +type StyledTableHeadProps = { + id: string; + headers: (TableHeader | ReactNode)[]; + headProps?: React.ComponentPropsWithoutRef<"thead">; +}; + +const checkIsTableHeader = ( + tableHeaderEl: TableHeader | ReactNode +): tableHeaderEl is TableHeader => + tableHeaderEl !== undefined && !!(tableHeaderEl as TableHeader).children; + +const renderTableHeaderElement = ( + id: string, + h: TableHeader | ReactNode, + index: number +): ReactNode => { + const key = `table-th-${id}-${index}`; + const baseClassName = `px-6 py-2`; + + if (checkIsTableHeader(h)) { + const { className: additionalClassName, children, ...props } = h; + return ( + <th + key={key} + className={twMerge(baseClassName, additionalClassName)} + {...props} + > + {children} + </th> + ); + } + + return ( + <th key={key} className={baseClassName}> + {h} + </th> + ); +}; + +export const StyledTableHead = ({ + id, + headProps = {}, + headers, +}: StyledTableHeadProps): JSX.Element => { + const { className: headClassName, ...otherHeadProps } = headProps; + return ( + <thead + className={twMerge( + "text-sm text-neutral-400 font-medium text-left", + headClassName + )} + {...otherHeadProps} + > + <tr> + {headers.map((h, index) => renderTableHeaderElement(id, h, index))} + </tr> + </thead> + ); +}; diff --git a/packages/components/src/StyledTableRow.tsx b/packages/components/src/StyledTableRow.tsx new file mode 100644 index 000000000..e8bac6103 --- /dev/null +++ b/packages/components/src/StyledTableRow.tsx @@ -0,0 +1,59 @@ +import clsx from "clsx"; +import { ReactNode, useMemo } from "react"; +import { twMerge } from "tailwind-merge"; + +type StyledTableRowProps = { + id: string; + index: number; + row: TableRow; +}; + +export type TableRow = { + cells: (ReactNode | CustomTableCell)[]; + key?: React.Key; +} & React.ComponentPropsWithoutRef<"tr">; + +export type CustomTableCell = { + render: () => ReactNode; +}; + +const checkIsCustomTableCell = ( + tableCellEl: CustomTableCell | ReactNode +): tableCellEl is CustomTableCell => + tableCellEl !== undefined && + tableCellEl !== null && + !!(tableCellEl as CustomTableCell).render; + +export const StyledTableRow = ({ + id, + row, + index, +}: StyledTableRowProps): JSX.Element => { + const { cells, className: rowsClassName, key: _key, ...props } = row; + const tableRows = useMemo( + () => ( + <tr + className={twMerge( + clsx("group/row", { + "bg-neutral-900": index % 2 === 0, + "bg-rblack": index % 2, + }), + rowsClassName + )} + {...props} + > + {cells.map((cell, cellId) => ( + <td + className="min-h-[78px] px-6 align-middle" + key={`table-td-${id}-${index}-${cellId}`} + > + {checkIsCustomTableCell(cell) ? cell.render() : cell} + </td> + ))} + </tr> + ), + [row] + ); + + return tableRows; +}; diff --git a/packages/components/src/TickedRadioList.tsx b/packages/components/src/TickedRadioList.tsx new file mode 100644 index 000000000..d4ef7bd6c --- /dev/null +++ b/packages/components/src/TickedRadioList.tsx @@ -0,0 +1,68 @@ +import { Stack } from "@namada/components"; +import { GoCheckCircle } from "react-icons/go"; + +import clsx from "clsx"; +import { tv } from "tailwind-variants"; + +type TickedRadioListElement<T extends string> = { + text: string; + value: T; +}; + +type TickedRadioListProps<T extends string> = { + options: Array<TickedRadioListElement<T>>; + id: string; + value: T | undefined; + onChange: (value: string) => void; +}; + +const tickedRadioList = tv({ + slots: { + fieldset: "flex flex-col", + label: clsx( + "w-full bg-black text-white text-base text-center rounded-sm py-3.5 px-5 border border-black", + "cursor-pointer font-medium flex items-center justify-center hover:text-yellow transition-colors" + ), + input: "hidden", + icon: "absolute right-12 text-neutral-450 text-xl text-yellow", + }, + variants: { + checked: { + true: { + label: "border-yellow", + }, + }, + }, +}); + +export const TickedRadioList = <T extends string = string>({ + options, + id, + value, + onChange, +}: TickedRadioListProps<T>): JSX.Element => { + const { fieldset, label, input, icon } = tickedRadioList(); + + return ( + <Stack as="fieldset" className={fieldset()} gap={3}> + {options.map((option, idx) => { + const checked = value === option.value; + + return ( + <label key={idx} className={label({ checked })}> + {option.text} + {checked && <GoCheckCircle strokeWidth={1} className={icon()} />} + <input + className={input()} + type="radio" + name={id} + value={option.value} + checked={checked} + onChange={(e) => onChange(e.target.value)} + ></input> + </label> + ); + })} + </Stack> + ); +}; diff --git a/packages/components/src/Toggle/index.ts b/packages/components/src/Toggle/index.ts deleted file mode 100644 index 07a13e0c8..000000000 --- a/packages/components/src/Toggle/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as Toggle } from "./toggle"; diff --git a/packages/components/src/Toggle/toggle.components.ts b/packages/components/src/Toggle/toggle.components.ts deleted file mode 100644 index 91396b083..000000000 --- a/packages/components/src/Toggle/toggle.components.ts +++ /dev/null @@ -1,75 +0,0 @@ -import styled from "styled-components"; -import { ColorMode, DesignConfiguration } from "@namada/utils"; - -const COMPONENT_WIDTH_PIXELS = 46; -const CIRCLE_DIAMETER_PIXELS = 20; -const BORDER_PIXELS = 2; - -const transition = "all 0.3s ease-in-out"; - -enum ComponentColor { - CircleBackground, - ToggleBackground, -} - -const getColor = ( - color: ComponentColor, - theme: DesignConfiguration -): string => { - const { colorMode } = theme.themeConfigurations; - - const colorMap: Record<ColorMode, Record<ComponentColor, string>> = { - light: { - [ComponentColor.CircleBackground]: theme.colors.secondary.main, - [ComponentColor.ToggleBackground]: theme.colors.utility1.main20, - }, - dark: { - [ComponentColor.CircleBackground]: theme.colors.primary.main, - [ComponentColor.ToggleBackground]: theme.colors.utility1.main60, - }, - }; - - return colorMap[colorMode][color]; -}; - -export const ToggleContainer = styled.button<{ - checked: boolean; - isLoading?: boolean; -}>` - display: flex; - align-items: center; - width: ${COMPONENT_WIDTH_PIXELS}px; - height: ${CIRCLE_DIAMETER_PIXELS + BORDER_PIXELS + BORDER_PIXELS}px; - padding: 0; - padding-left: ${(props) => - props.checked - ? `${BORDER_PIXELS - 1}px` - : `${ - COMPONENT_WIDTH_PIXELS - CIRCLE_DIAMETER_PIXELS - 1 - BORDER_PIXELS - }px`}; - border: 1px solid - ${(props) => getColor(ComponentColor.ToggleBackground, props.theme)}; - border-radius: 999px; - background-color: ${(props) => - getColor(ComponentColor.ToggleBackground, props.theme)}; - /* TODO: Make this work for all toggles, not just theme selection */ - transition: ${transition}; - cursor: pointer; -`; - -export const ToggleCircle = styled.div<{ - checked: boolean; -}>` - display: flex; - justify-content: center; - align-items: center; - width: ${CIRCLE_DIAMETER_PIXELS}px; - max-width: ${CIRCLE_DIAMETER_PIXELS}px; - height: ${CIRCLE_DIAMETER_PIXELS}px; - border: none; - border-radius: 50%; - background-color: ${(props) => - getColor(ComponentColor.CircleBackground, props.theme)}; - box-sizing: border-box; - transition: ${transition}; -`; diff --git a/packages/components/src/Toggle/toggle.tsx b/packages/components/src/Toggle/toggle.tsx deleted file mode 100644 index b41d2aa9a..000000000 --- a/packages/components/src/Toggle/toggle.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { ToggleCircle, ToggleContainer } from "./toggle.components"; - -export type ToggleProps = { - // indicates whether it is enabled/disabled. checked = enabled - checked: boolean; - // cb for when clicked - onClick: () => void; - // custom element to be placed in circle when enabled - circleElementEnabled?: JSX.Element; - // custom element to be placed in circle when disabled - circleElementDisabled?: JSX.Element; - testId?: string; -}; - -/** - * a component to indicate true/false - */ -const Toggle: React.FunctionComponent<ToggleProps> = (props: ToggleProps) => { - const { - checked, - onClick, - circleElementEnabled, - circleElementDisabled, - testId, - } = props; - const circleElement = checked ? circleElementEnabled : circleElementDisabled; - // TODO: animate the change of circleElement - return ( - <ToggleContainer - role="switch" - aria-checked={checked} - checked={checked} - onClick={() => { - onClick(); - }} - data-testid={`Toggle${testId ? `testId-${testId}` : ""}`} - > - <ToggleCircle checked={checked}>{circleElement}</ToggleCircle> - </ToggleContainer> - ); -}; - -export default Toggle; diff --git a/packages/components/src/ToggleButton.tsx b/packages/components/src/ToggleButton.tsx index 657a1538a..416fd1406 100644 --- a/packages/components/src/ToggleButton.tsx +++ b/packages/components/src/ToggleButton.tsx @@ -1,37 +1,68 @@ import clsx from "clsx"; +import { twMerge } from "tailwind-merge"; +import { tv } from "tailwind-variants"; type Props = { checked: boolean; onChange: () => void; label: string; + containerProps?: React.ComponentPropsWithoutRef<"label">; }; +const toggleButtonClassList = tv({ + slots: { + container: clsx( + "group flex gap-6 items-center cursor-pointer text-sm relative", + "[&>span]:relative active:[&>span]:top-px" + ), + checkbox: clsx("invisible absolute pointer-events-none"), + toggleContainer: clsx( + "relative rounded-3xl bg-rblack p-1 h-5 w-10 cursor-pointer", + "transition-all duration-100 ease-out-quad", + "[&~span]:top-px" + ), + toggleIndicator: clsx( + "absolute left-0.5 top-0.5 h-[calc(100%-4px)] bg-yellow", + "aspect-square rounded-full transition-all duration-200 ease-out-quad" + ), + }, + variants: { + checked: { + true: { + toggleIndicator: "translate-x-5", + }, + }, + }, +}); + export const ToggleButton = ({ checked, onChange, label, + containerProps = {}, }: Props): JSX.Element => { + const { container, checkbox, toggleContainer, toggleIndicator } = + toggleButtonClassList({ checked }); + + const { className: containerClassName, ...containerPropsRest } = + containerProps; + return ( - <div - className={clsx( - "group relative rounded-3xl p-1 h-5 w-10 cursor-pointer", - "transition-all duration-150 ease-out-quad", - { "bg-yellow": checked, "bg-neutral-950": !checked } - )} - data-role="button" - aria-label={label} - role="switch" - onClick={onChange} + <label + className={twMerge(container(), containerClassName)} + {...containerPropsRest} > - <i - className={clsx( - "absolute left-1.5 top-0.5 h-[calc(100%-4px)]", - "aspect-square rounded-full transition-all duration-300 ease-out-quad", - "group-hover:bg-v-hover", - { "translate-x-full": checked }, - { "bg-neutral-950": checked, "bg-neutral-800": !checked } - )} + <span>{label}</span> + <input + aria-label={label} + type="checkbox" + className={checkbox()} + onChange={onChange} + checked={checked} /> - </div> + <div className={clsx(toggleContainer())} data-role="button"> + <i className={toggleIndicator()} /> + </div> + </label> ); }; diff --git a/packages/components/src/base.css b/packages/components/src/base.css index 89c943a4a..5e129260d 100644 --- a/packages/components/src/base.css +++ b/packages/components/src/base.css @@ -45,12 +45,10 @@ code { --ease-in-out-circ: cubic-bezier(0, 0.55, 0.45, 1); } -@layer utilities { - .text-balance { - text-wrap: balance; - } - - .unset { - all: unset; - } +.text-balance { + text-wrap: balance; +} + +.unset { + all: unset; } diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index e32378db4..391ccb2a3 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -2,17 +2,20 @@ export * from "./Accordion"; export * from "./ActionButton"; export * from "./Alert"; export * from "./AmountInput"; +export * from "./AmountSummaryCard"; export * from "./Badge"; export * from "./Checkbox"; export * from "./Container"; export * from "./ContentMasker"; export * from "./CopyToClipboardControl"; +export * from "./Currency"; export * from "./DropdownMenu"; export * from "./FeedbackButton"; export * from "./Heading"; export * from "./Icon"; export * from "./Image"; export * from "./Input"; +export * from "./InsetLabel"; export * from "./KeyListItem"; export * from "./LifecycleExecutionWrapper"; export * from "./LinkButton"; @@ -20,15 +23,24 @@ export * from "./Loading"; export * from "./Modal"; export * from "./NavigationContainer"; export * from "./Panel"; +export * from "./PieChart"; +export * from "./ProgressBar"; export * from "./ProgressIndicator"; export * from "./RadioGroup"; +export * from "./RoundedLabel"; export * from "./SeedPhraseInstructions"; +export * from "./SegmentedBar"; export * from "./Select"; +export * from "./SkeletonLoading"; export * from "./Stack"; +export * from "./StyledSelectBox"; +export * from "./StyledTable"; +export * from "./StyledTableHead"; +export * from "./StyledTableRow"; export * from "./Table"; export * from "./TabsGroup"; export * from "./Text"; -export * from "./Toggle"; +export * from "./TickedRadioList"; export * from "./ToggleButton"; export * from "./Tooltip"; export * from "./ViewKeys"; diff --git a/packages/components/src/theme.js b/packages/components/src/theme.js index aecafc27c..a6969bf76 100644 --- a/packages/components/src/theme.js +++ b/packages/components/src/theme.js @@ -4,11 +4,13 @@ module.exports = { colors: { success: "#15DD89", intermediate: "#F48708", + orange: "#FF8A00", fail: "#DD1539", white: "#fff", black: "#141414", gray: "#292929", rblack: "#000000", + upcoming: "#7DA0A8", yellow: { DEFAULT: "#FFFF00", @@ -38,13 +40,27 @@ module.exports = { 900: "#001F1F", 950: "#000303", }, - + pink: { + DEFAULT: "#DD1599", + 50: "#F8B2E0", + 100: "#F69FD8", + 200: "#F27AC9", + 300: "#EF55BA", + 400: "#EB30AB", + 500: "#DD1599", + 600: "#AA1076", + 700: "#770B52", + 800: "#2b0d20", + 900: "#10020B", + 950: "#000000", + }, neutral: { 50: "#fafafa", 100: "#f5f5f5", 200: "#e5e5e5", 300: "#d4d4d4", 400: "#999999", + 450: "#838383", 500: "#787878", 600: "#545454", 700: "#404040", diff --git a/packages/crypto/src/init-inline.ts b/packages/crypto/src/init-inline.ts index 15b75fb1c..da14138c0 100644 --- a/packages/crypto/src/init-inline.ts +++ b/packages/crypto/src/init-inline.ts @@ -1,4 +1,6 @@ import initWasm, { InitOutput } from "./crypto/crypto"; -import wasm from "./crypto/crypto_bg.wasm"; + +// @ts-expect-error https://vitejs.dev/guide/features#fetching-the-module-in-node-js +import wasm from "./crypto/crypto_bg.wasm?url"; export const init: () => Promise<InitOutput> = async () => await initWasm(wasm); diff --git a/packages/hooks/package.json b/packages/hooks/package.json index c949fd4b2..285bd74e4 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -19,7 +19,10 @@ "lint:ci": "yarn lint --max-warnings 0" }, "dependencies": { - "react": "^17.0.2", + "@namada/chains": "0.2.1", + "@namada/types": "0.2.1", + "@namada/utils": "0.2.1", + "isomorphic-dompurify": "^2.6.0", "typescript": "^5.1.3" }, "devDependencies": { @@ -31,6 +34,9 @@ "eslint-plugin-react-hooks": "^4.6.0", "rimraf": "^5.0.5" }, + "peerDependencies": { + "react": "^18.0.0" + }, "files": [ "dist" ], diff --git a/packages/hooks/src/useSanitizedLocation.ts b/packages/hooks/src/useSanitizedLocation.ts index e6ce22058..4f29e055e 100644 --- a/packages/hooks/src/useSanitizedLocation.ts +++ b/packages/hooks/src/useSanitizedLocation.ts @@ -1,5 +1,5 @@ -import { useLocation, Location } from "react-router-dom"; -import { sanitize } from "dompurify"; +import { sanitize } from "isomorphic-dompurify"; +import { Location, useLocation } from "react-router-dom"; export const useSanitizedLocation = (): Location => { const location = useLocation(); @@ -7,6 +7,6 @@ export const useSanitizedLocation = (): Location => { ...location, pathname: sanitize(location.pathname), search: sanitize(location.search), - hash: sanitize(location.hash) + hash: sanitize(location.hash), }; }; diff --git a/packages/hooks/src/useSanitizedParams.ts b/packages/hooks/src/useSanitizedParams.ts index 6849f3309..0032f2960 100644 --- a/packages/hooks/src/useSanitizedParams.ts +++ b/packages/hooks/src/useSanitizedParams.ts @@ -1,18 +1,18 @@ -import { useParams, Params } from "react-router-dom"; -import { sanitize } from "dompurify"; +import { sanitize } from "isomorphic-dompurify"; +import { Params, useParams } from "react-router-dom"; type ParamsT<ParamsOrKey> = Readonly< [ParamsOrKey] extends [string] ? Params<ParamsOrKey> : Partial<ParamsOrKey> -> +>; export function useSanitizedParams< - ParamsOrKey extends string | Record<string, string | undefined> = string -> (): ParamsT<ParamsOrKey> { + ParamsOrKey extends string | Record<string, string | undefined> = string, +>(): ParamsT<ParamsOrKey> { const params = useParams<ParamsOrKey>(); return Object.entries(params).reduce( (acc, [key, value]) => ({ ...acc, - [key]: typeof value === 'string' ? sanitize(value) : value + [key]: typeof value === "string" ? sanitize(value) : value, }), {} as ParamsT<ParamsOrKey> ); diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 9c88544c4..8064730a6 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -11,6 +11,10 @@ "import": "./dist/web/sdk/src/initWeb.js", "types": "./dist/web/sdk/src/initWeb.d.ts" }, + "./inline-init": { + "import": "./dist/inline/sdk/src/initInline.js", + "types": "./dist/inline/sdk/src/initInline.d.ts" + }, "./node": { "require": "./dist/node/sdk/src/indexNode.js", "types": "./dist/node/sdk/src/indexNode.d.ts" diff --git a/packages/sdk/src/initInline.ts b/packages/sdk/src/initInline.ts new file mode 100644 index 000000000..4351a9232 --- /dev/null +++ b/packages/sdk/src/initInline.ts @@ -0,0 +1,35 @@ +// We have to use relative imports here othewise ts-patch is getting confused and produces wrong paths after compialtion +import { init as initCrypto } from "../../crypto/src/init-inline"; +import { init as initShared } from "../../shared/src/init-inline"; +import { initThreadPool } from "../../shared/src/init-thread-pool"; + +/** + * Initialize the SDK memory + * @async + * @returns + + - The SDK crypto memory + */ +export default async function init(): Promise<{ + cryptoMemory: WebAssembly.Memory; +}> { + // Load and initialize shared wasm + await initShared(); + // Load and initialize crypto wasm + const { memory: cryptoMemory } = await initCrypto(); + return { cryptoMemory }; +} + +/** + * Initialize the SDK memory, with multicore support. + * If you built wasm without multicore support, this will work as regular init. + * @async + * @returns - The SDK crypto memory + */ +export async function initMulticore(): Promise<{ + cryptoMemory: WebAssembly.Memory; +}> { + const res = await init(); + await initThreadPool(navigator.hardwareConcurrency); + return res; +} diff --git a/packages/sdk/src/rpc/rpc.ts b/packages/sdk/src/rpc/rpc.ts index 1e2e9191d..3a2ec5d85 100644 --- a/packages/sdk/src/rpc/rpc.ts +++ b/packages/sdk/src/rpc/rpc.ts @@ -1,7 +1,4 @@ -import { deserialize } from "@dao-xyz/borsh"; import { - Proposal, - Proposals, Query as QueryWasm, Sdk as SdkWasm, TransferToEthereum, @@ -74,17 +71,6 @@ export class Rpc { return await this.query.query_all_validator_addresses(); } - /** - * Query Proposals - * @async - * @returns List of the proposals - */ - async queryProposals(): Promise<Proposal[]> { - const serializedProposals = await this.query.query_proposals(); - const { proposals } = deserialize(serializedProposals, Proposals); - return proposals; - } - /** * Query total delegations * @async diff --git a/packages/sdk/src/tests/data.ts b/packages/sdk/src/tests/data.ts index d71bcd889..d89e3dc73 100644 --- a/packages/sdk/src/tests/data.ts +++ b/packages/sdk/src/tests/data.ts @@ -23,7 +23,7 @@ export const ACCOUNT_2 = { export const SHIELDED_ACCOUNT = { paymentAddress: - "znam1qpwsnm860tukw9e95q7gh68lskv7pult902z00rdnj8lnxzs4ksrapu7n323qd87cvl6edcf5cxwd", + "znam1t5y7e7n6l9n3wfdq8j973lu9n8s086et6snmcmvu3lues59d5qlg085u25grflkr87ktwuhvd4y", spendingKey: "zsknam1qdjyja2zqqqqpqpxsekqx3tg8qz727cyqqhujj7c74xglmzk5lk0e2gej2s2e6c4vthhegtlxuulu4g4d3dqfc08fg0xunw4q22xdhac46t7empalaeq3czx7nfx456w7dqdm3tldwu83jjcc9dees3cefpkv23jnra5c3cwsy092ahrtpm7zv34sj8dy72zwpzwjja69pw6lacdd655j0zaxdjk6ej387njquecyqm2hzv3kps76wrv2y5xk634kkcc05tdrvwyhhq9p5gcy", viewingKey: diff --git a/packages/sdk/tsconfig.inline.json b/packages/sdk/tsconfig.inline.json new file mode 100644 index 000000000..85ecc392c --- /dev/null +++ b/packages/sdk/tsconfig.inline.json @@ -0,0 +1,27 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "src/initInline.ts", + "../shared/src/shared/**/*", + "../crypto/src/crypto/**/*" + ], + "exclude": [ + "**/*.test.ts", + "node_modules", + "scripts", + "../shared/src/shared/**/*.node.*" + ], + "compilerOptions": { + "declaration": true, + "outDir": "dist/inline", + "module": "esnext", + "moduleResolution": "bundler", + "plugins": [ + // Transform paths in output .js files + { "transform": "typescript-transform-paths" }, + + // Transform paths in output .d.ts files (Include this line if you output declarations files) + { "transform": "typescript-transform-paths", "afterDeclarations": true } + ] + } +} diff --git a/packages/shared/.gitignore b/packages/shared/.gitignore index 3d6277c1a..2311971e3 100644 --- a/packages/shared/.gitignore +++ b/packages/shared/.gitignore @@ -1,2 +1,3 @@ src/shared +lib/**/*.js dist diff --git a/packages/shared/lib/Cargo.lock b/packages/shared/lib/Cargo.lock index bbbf9cbad..1875d7422 100644 --- a/packages/shared/lib/Cargo.lock +++ b/packages/shared/lib/Cargo.lock @@ -12,6 +12,21 @@ dependencies = [ "regex", ] +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aead" version = "0.5.2" @@ -19,29 +34,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array 0.14.7", + "generic-array", ] [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -53,9 +86,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "ark-bls12-381" @@ -78,7 +111,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -93,8 +126,8 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "num-bigint 0.4.3", - "num-traits", + "num-bigint", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "paste", "rustc_version 0.3.3", "zeroize", @@ -116,8 +149,8 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ - "num-bigint 0.4.3", - "num-traits", + "num-bigint", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "quote", "syn 1.0.109", ] @@ -138,7 +171,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.8.5", ] @@ -150,19 +183,19 @@ checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] @@ -178,21 +211,35 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.65", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] [[package]] name = "base16ct" @@ -202,9 +249,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base58" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" [[package]] name = "base64" @@ -214,15 +261,21 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.3" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.0.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bech32" @@ -254,7 +307,7 @@ dependencies = [ "pairing", "rand_core 0.6.4", "rayon", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -268,12 +321,12 @@ dependencies = [ [[package]] name = "bip0039" -version = "0.10.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef0f0152ec5cf17f49a5866afaa3439816207fd4f0a224c0211ffaf5e278426" +checksum = "568b6890865156d9043af490d4c4081c385dd68ea10acd6ca15733d511e6b51c" dependencies = [ "hmac 0.12.1", - "pbkdf2 0.10.1", + "pbkdf2 0.12.2", "rand 0.8.5", "sha2 0.10.8", "unicode-normalization", @@ -291,6 +344,9 @@ name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] [[package]] name = "bitvec" @@ -304,11 +360,20 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "blake2b_simd" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", "arrayvec", @@ -317,9 +382,9 @@ dependencies = [ [[package]] name = "blake2s_simd" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637f448b9e61dfadbdcbae9a885fadee1f3eaffb1f8d3c1965d3ade8bdfd44f" +checksum = "94230421e395b9920d23df13ea5d77a20e1725331f90fbbf6df6040b33f756ae" dependencies = [ "arrayref", "arrayvec", @@ -327,15 +392,16 @@ dependencies = [ ] [[package]] -name = "block-buffer" -version = "0.7.3" +name = "blake3" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array 0.12.4", + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", ] [[package]] @@ -344,7 +410,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -353,16 +419,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.7", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", + "generic-array", ] [[package]] @@ -375,46 +432,91 @@ dependencies = [ "group", "pairing", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] name = "borsh" -version = "1.2.1" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive 0.10.3", + "hashbrown 0.13.2", +] + +[[package]] +name = "borsh" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9897ef0f1bd2362169de6d7e436ea2237dc1085d7d1e4db75f4be34d86f309d1" +checksum = "dbe5b10e214954177fb1dc9fbd20a1a2608fe99e6c832033bdc7cea287a20d77" dependencies = [ - "borsh-derive", + "borsh-derive 1.5.0", "cfg_aliases", ] [[package]] name = "borsh-derive" -version = "1.2.1" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478b41ff04256c5c8330f3dfdaaae2a5cc976a8e75088bafa4625b0d0208de8c" +checksum = "d7a8646f94ab393e43e8b35a2558b1624bed28b97ee09c5d15456e3c9463f46d" dependencies = [ "once_cell", - "proc-macro-crate 2.0.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", "syn_derive", ] +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "borsh-ext" version = "1.2.0" source = "git+https://github.com/heliaxdev/borsh-ext?tag=v1.2.0#a62fee3e847e512cad9ac0f1fd5a900e5db9ba37" dependencies = [ - "borsh", + "borsh 1.5.0", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ "sha2 0.10.8", "tinyvec", @@ -422,9 +524,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-slice-cast" @@ -432,54 +534,48 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" dependencies = [ "serde", ] [[package]] name = "camino" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.3" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" -version = "0.15.4" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.18", + "semver 1.0.23", "serde", "serde_json", "thiserror", @@ -496,9 +592,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" [[package]] name = "cfg-if" @@ -538,17 +634,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", - "num-traits", - "time 0.1.45", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen", - "winapi", + "windows-targets 0.52.5", ] [[package]] @@ -614,11 +709,11 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" dependencies = [ - "base64 0.21.3", + "base64 0.21.7", "bech32 0.9.1", "bs58", "digest 0.10.7", - "generic-array 0.14.7", + "generic-array", "hex", "ripemd", "serde", @@ -638,17 +733,36 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "const-hex" +version = "1.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ff96486ccc291d36a958107caf2c0af8c78c0af7d31ae2f35ce055130de1a6" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_panic" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b" [[package]] name = "constant_time_eq" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "core-foundation" @@ -668,55 +782,46 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -726,13 +831,13 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array 0.14.7", + "generic-array", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -742,28 +847,18 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.7", + "generic-array", "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -dependencies = [ - "generic-array 0.12.4", - "subtle 1.0.0", -] - [[package]] name = "crypto-mac" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.7", - "subtle 2.4.1", + "generic-array", + "subtle", ] [[package]] @@ -795,54 +890,28 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.3" +name = "data-encoding" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.39", -] +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] -name = "darling_macro" -version = "0.20.3" +name = "der" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ - "darling_core", - "quote", - "syn 2.0.39", + "const-oid", + "zeroize", ] [[package]] -name = "data-encoding" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" - -[[package]] -name = "der" -version = "0.7.8" +name = "deranged" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ - "const-oid", - "zeroize", + "powerfmt", ] [[package]] @@ -873,22 +942,13 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -900,7 +960,7 @@ dependencies = [ "block-buffer 0.10.4", "const-oid", "crypto-common", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -931,7 +991,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] @@ -940,11 +1000,31 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "duration-str" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c1a2e028bbf7921549873b291ddc0cfe08b673d9489da81ac28898cd5a0e6e0" +dependencies = [ + "chrono", + "rust_decimal", + "serde", + "thiserror", + "time", + "winnow 0.6.8", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest 0.10.7", @@ -965,21 +1045,6 @@ dependencies = [ "signature", ] -[[package]] -name = "ed25519-consensus" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758e2a0cd8a6cdf483e1d369e7d081647e00b88d8953e34d8f2cbba05ae28368" -dependencies = [ - "curve25519-dalek-ng", - "hex", - "rand_core 0.6.4", - "serde", - "sha2 0.9.9", - "thiserror", - "zeroize", -] - [[package]] name = "ed25519-consensus" version = "2.1.0" @@ -989,52 +1054,54 @@ dependencies = [ "curve25519-dalek-ng", "hex", "rand_core 0.6.4", + "serde", "sha2 0.9.9", + "thiserror", "zeroize", ] [[package]] name = "either" -version = "1.8.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "elliptic-curve" -version = "0.13.5" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", "digest 0.10.7", "ff", - "generic-array 0.14.7", + "generic-array", "group", "pkcs8", "rand_core 0.6.4", "sec1", "serdect", - "subtle 2.4.1", + "subtle", "zeroize", ] [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] [[package]] name = "enr" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf56acd72bb22d2824e66ae8e9e5ada4d0de17a69c7fd35569dde2ada8ec9116" +checksum = "2a3d8dc56e02f954cac8eb489772c552c473346fc34f67412bb6244fd647f7e4" dependencies = [ - "base64 0.13.1", + "base64 0.21.7", "bytes", "hex", "k256", @@ -1054,23 +1121,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -1092,7 +1148,7 @@ dependencies = [ "sha2 0.10.8", "sha3", "thiserror", - "uuid", + "uuid 0.8.2", ] [[package]] @@ -1192,9 +1248,9 @@ dependencies = [ [[package]] name = "ethers-addressbook" -version = "2.0.8" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcb6ffefc230d8c42874c51b28dc11dbb8de50b27a8fdf92648439d6baa68dc" +checksum = "5495afd16b4faa556c3bba1f21b98b4983e53c1755022377051a975c3b021759" dependencies = [ "ethers-core", "once_cell", @@ -1204,17 +1260,16 @@ dependencies = [ [[package]] name = "ethers-contract" -version = "2.0.8" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4719a44c3d37ab07c6dea99ab174068d8c35e441b60b6c20ce4e48357273e8" +checksum = "6fceafa3578c836eeb874af87abacfb041f92b4da0a78a5edd042564b8ecdaaa" dependencies = [ + "const-hex", "ethers-contract-abigen", "ethers-contract-derive", "ethers-core", "ethers-providers", - "ethers-signers", "futures-util", - "hex", "once_cell", "pin-project", "serde", @@ -1224,58 +1279,58 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" -version = "2.0.8" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "155ea1b84d169d231317ed86e307af6f2bed6b40dd17e5e94bc84da21cadb21c" +checksum = "04ba01fbc2331a38c429eb95d4a570166781f14290ef9fdb144278a90b5a739b" dependencies = [ "Inflector", + "const-hex", "dunce", "ethers-core", "eyre", - "hex", - "prettyplease 0.2.9", + "prettyplease", "proc-macro2", "quote", "regex", "serde", "serde_json", - "syn 2.0.39", - "toml 0.7.6", + "syn 2.0.65", + "toml 0.8.13", "walkdir", ] [[package]] name = "ethers-contract-derive" -version = "2.0.8" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8567ff196c4a37c1a8c90ec73bda0ad2062e191e4f0a6dc4d943e2ec4830fc88" +checksum = "87689dcabc0051cde10caaade298f9e9093d65f6125c14575db3fd8c669a168f" dependencies = [ "Inflector", + "const-hex", "ethers-contract-abigen", "ethers-core", - "hex", "proc-macro2", "quote", "serde_json", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] name = "ethers-core" -version = "2.0.8" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60ca2514feb98918a0a31de7e1983c29f2267ebf61b2dc5d4294f91e5b866623" +checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" dependencies = [ "arrayvec", "bytes", "cargo_metadata", "chrono", + "const-hex", "elliptic-curve", "ethabi", - "generic-array 0.14.7", - "hex", + "generic-array", "k256", - "num_enum 0.6.1", + "num_enum", "once_cell", "open-fastrlp", "rand 0.8.5", @@ -1283,7 +1338,7 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.39", + "syn 2.0.65", "tempfile", "thiserror", "tiny-keccak", @@ -1292,13 +1347,14 @@ dependencies = [ [[package]] name = "ethers-etherscan" -version = "2.0.8" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22b3a8269d3df0ed6364bc05b4735b95f4bf830ce3aef87d5e760fb0e93e5b91" +checksum = "e79e5973c26d4baf0ce55520bd732314328cabe53193286671b47144145b9649" dependencies = [ + "chrono", "ethers-core", "reqwest", - "semver 1.0.18", + "semver 1.0.23", "serde", "serde_json", "thiserror", @@ -1307,15 +1363,14 @@ dependencies = [ [[package]] name = "ethers-middleware" -version = "2.0.8" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c339aad74ae5c451d27e0e49c7a3c7d22620b119b4f9291d7aa21f72d7f366" +checksum = "48f9fdf09aec667c099909d91908d5eaf9be1bd0e2500ba4172c1d28bfaa43de" dependencies = [ "async-trait", "auto_impl", "ethers-contract", "ethers-core", - "ethers-etherscan", "ethers-providers", "ethers-signers", "futures-channel", @@ -1334,23 +1389,24 @@ dependencies = [ [[package]] name = "ethers-providers" -version = "2.0.8" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b411b119f1cf0efb69e2190883dee731251882bb21270f893ee9513b3a697c48" +checksum = "6434c9a33891f1effc9c75472e12666db2fa5a0fec4b29af6221680a6fe83ab2" dependencies = [ "async-trait", "auto_impl", - "base64 0.21.3", + "base64 0.21.7", "bytes", + "const-hex", "enr", "ethers-core", "futures-core", "futures-timer", "futures-util", "hashers", - "hex", - "http", + "http 0.2.12", "instant", + "jsonwebtoken", "once_cell", "pin-project", "reqwest", @@ -1369,17 +1425,17 @@ dependencies = [ [[package]] name = "ethers-signers" -version = "2.0.8" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4864d387456a9c09a1157fa10e1528b29d90f1d859443acf06a1b23365fb518c" +checksum = "228875491c782ad851773b652dd8ecac62cda8571d3bc32a5853644dd26766c2" dependencies = [ "async-trait", "coins-bip32", "coins-bip39", + "const-hex", "elliptic-curve", "eth-keystore", "ethers-core", - "hex", "rand 0.8.5", "sha2 0.10.8", "thiserror", @@ -1388,28 +1444,19 @@ dependencies = [ [[package]] name = "eyre" -version = "0.6.8" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "fastrand" -version = "1.9.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "ff" @@ -1419,7 +1466,7 @@ checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "bitvec", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -1446,7 +1493,6 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" dependencies = [ - "eyre", "paste", ] @@ -1473,9 +1519,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -1489,9 +1535,9 @@ dependencies = [ "cbc", "cipher", "libm", - "num-bigint 0.4.3", + "num-bigint", "num-integer", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1502,9 +1548,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1517,9 +1563,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1527,15 +1573,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1544,9 +1590,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-locks" @@ -1560,32 +1606,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers", "send_wrapper 0.4.0", @@ -1593,9 +1639,9 @@ dependencies = [ [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1618,15 +1664,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1653,9 +1690,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -1664,6 +1701,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + [[package]] name = "gloo-timers" version = "0.2.6" @@ -1678,9 +1721,9 @@ dependencies = [ [[package]] name = "gloo-utils" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" dependencies = [ "js-sys", "serde", @@ -1698,22 +1741,22 @@ dependencies = [ "ff", "memuse", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] name = "h2" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 1.9.3", + "http 0.2.12", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1728,9 +1771,18 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashers" @@ -1747,11 +1799,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1759,23 +1817,13 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hmac" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" -dependencies = [ - "crypto-mac 0.7.0", - "digest 0.8.1", -] - [[package]] name = "hmac" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" dependencies = [ - "crypto-mac 0.8.0", + "crypto-mac", "digest 0.9.0", ] @@ -1788,17 +1836,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "hmac-drbg" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" -dependencies = [ - "digest 0.8.1", - "generic-array 0.12.4", - "hmac 0.7.1", -] - [[package]] name = "hmac-sha512" version = "0.1.9" @@ -1807,9 +1844,20 @@ checksum = "77e806677ce663d0a199541030c816847b36e8dc095f70dae4a4f4ad63da5383" [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1818,12 +1866,12 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", "pin-project-lite", ] @@ -1841,16 +1889,16 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "httparse", "httpdate", @@ -1878,16 +1926,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -1901,9 +1949,9 @@ dependencies = [ [[package]] name = "ibc" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "429b6aca6624a9364878e28c90311438c2621a8270942d80732b2651ac38ac74" +checksum = "b9298a8de81eea8d496672e47f13ab1ae5145b3b554ec3951222197697e1cf82" dependencies = [ "ibc-apps", "ibc-clients", @@ -1913,11 +1961,44 @@ dependencies = [ "ibc-primitives", ] +[[package]] +name = "ibc-app-nft-transfer" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e4d7728ae132ecb49286f225d0ab6ad56b2a15af47e019da45ad23fd789d76f" +dependencies = [ + "ibc-app-nft-transfer-types", + "ibc-core", + "serde-json-wasm", +] + +[[package]] +name = "ibc-app-nft-transfer-types" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333b2fcc0226d150e996fddd3fd2041460c7ed7e7594b65077026c7e38587329" +dependencies = [ + "base64 0.21.7", + "borsh 0.10.3", + "derive_more", + "displaydoc", + "http 1.1.0", + "ibc-app-transfer-types", + "ibc-core", + "ibc-proto", + "mime", + "parity-scale-codec", + "scale-info", + "schemars", + "serde", + "serde-json-wasm", +] + [[package]] name = "ibc-app-transfer" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b177b343385d9654d99be4709b5ed1574d41f91dfa4044b2d26d688be4179d7c" +checksum = "fdf138e322daa7b757b66a8c9a5bcac00773136f4b939b6cfb43bb95576ba59d" dependencies = [ "ibc-app-transfer-types", "ibc-core", @@ -1926,80 +2007,99 @@ dependencies = [ [[package]] name = "ibc-app-transfer-types" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95f92a3eda225e5c86e7bb6501c95986583ac541c4369d3c528349d81390f947" +checksum = "4e8777875777e43f3c18a340ac5ea2223dc87a67b60d99e451142eac3b7b7a46" dependencies = [ + "borsh 0.10.3", "derive_more", "displaydoc", "ibc-core", "ibc-proto", + "parity-scale-codec", "primitive-types", + "scale-info", + "schemars", "serde", "uint", ] [[package]] name = "ibc-apps" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4be40d55ed2dea9f2d05b902a3586f20850c723e4bdbfc4fb0ebe7a66ca5e40" +checksum = "6ac849a9d4f6097cc81405b00428fd73b04ce5290d806ce7f5c8afd42fbfb9bd" dependencies = [ + "ibc-app-nft-transfer", "ibc-app-transfer", ] [[package]] name = "ibc-client-tendermint" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119aa5873214228bf69bded3f20022b9ae1bc35b6841d295afcd73e53db05ccf" +checksum = "804dcd81f62608c453e72f669b2df986eb49d4b4381deac2a70bd33ee94cef7f" dependencies = [ + "derive_more", "ibc-client-tendermint-types", "ibc-core-client", "ibc-core-commitment-types", "ibc-core-handler-types", "ibc-core-host", "ibc-primitives", - "prost 0.12.1", "serde", - "tendermint", + "tendermint 0.36.0", "tendermint-light-client-verifier", ] [[package]] name = "ibc-client-tendermint-types" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f21679016931b332b295a761e65cc122dc6fbfb98444148b681ad3aaa474665" +checksum = "54b41444137be02cabc484079443f447d23e746fabd9ac6fd5d99faac0b30a5f" dependencies = [ - "bytes", "displaydoc", "ibc-core-client-types", "ibc-core-commitment-types", "ibc-core-host-types", "ibc-primitives", "ibc-proto", - "prost 0.12.1", "serde", - "tendermint", + "tendermint 0.36.0", "tendermint-light-client-verifier", - "tendermint-proto", + "tendermint-proto 0.36.0", +] + +[[package]] +name = "ibc-client-wasm-types" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc05b707ee957b1272877606379647ae1e5897316a3406b6a93e1885ee99e68" +dependencies = [ + "base64 0.21.7", + "displaydoc", + "ibc-core-client", + "ibc-core-host-types", + "ibc-primitives", + "ibc-proto", + "serde", ] [[package]] name = "ibc-clients" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c660323e93107a136aa3dbc412b7fa2eafd315c2fe71184096a43800f8ca5" +checksum = "675754a0a5f2f70f71445338fa4d8b49d3c84ce1cf4748ca6c98bf6ede9c4bfc" dependencies = [ "ibc-client-tendermint", + "ibc-client-wasm-types", ] [[package]] name = "ibc-core" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "100d9d0aa67432c5078a8a1c818e3fc990a193be6d35ed0abeda5b340d16c1da" +checksum = "5cb69c4ee05d367fa321acf67ec00d3e9f8ecfef013accdb05889db32ff2de3f" dependencies = [ "ibc-core-channel", "ibc-core-client", @@ -2008,14 +2108,15 @@ dependencies = [ "ibc-core-handler", "ibc-core-host", "ibc-core-router", + "ibc-derive", "ibc-primitives", ] [[package]] name = "ibc-core-channel" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebaa37629ac029f914dfe552ab5dad01ddb240ec885ed0ae68221cbea4e9bfc" +checksum = "c5dcc1c14a92f01e556d72d834f842bb655043a7e122ebb8aabdb5e9df600aa8" dependencies = [ "ibc-core-channel-types", "ibc-core-client", @@ -2025,15 +2126,15 @@ dependencies = [ "ibc-core-host", "ibc-core-router", "ibc-primitives", - "prost 0.12.1", ] [[package]] name = "ibc-core-channel-types" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2ba72c56c411b1e0ce6dc3f5e1fa1de9e6c84891f425b7be8a9e1705964378" +checksum = "4c2744ad32ae7360caefb80f495800f883f5e5687cfbd74ff82a444b59a47af7" dependencies = [ + "borsh 0.10.3", "derive_more", "displaydoc", "ibc-core-client-types", @@ -2042,18 +2143,20 @@ dependencies = [ "ibc-core-host-types", "ibc-primitives", "ibc-proto", - "prost 0.12.1", + "parity-scale-codec", + "scale-info", + "schemars", "serde", "sha2 0.10.8", "subtle-encoding", - "tendermint", + "tendermint 0.36.0", ] [[package]] name = "ibc-core-client" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c4fac8e05201795073dee8c93d5afe9dfeac9aec2412b4a2b0c5f0d1e1d725" +checksum = "80071ac73dd4f3436bf1aef03c9b1715a4db0914f738904c281449dc05a0a9cf" dependencies = [ "ibc-core-client-context", "ibc-core-client-types", @@ -2061,14 +2164,13 @@ dependencies = [ "ibc-core-handler-types", "ibc-core-host", "ibc-primitives", - "prost 0.12.1", ] [[package]] name = "ibc-core-client-context" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b82abd9489021730d59ab2d00e9438d3711e8e78ecba4d083b64f833301682b" +checksum = "1e9f751b62cad4195be5347646151020fd27c2924f10d82d6301a675fac544dd" dependencies = [ "derive_more", "displaydoc", @@ -2076,67 +2178,71 @@ dependencies = [ "ibc-core-commitment-types", "ibc-core-handler-types", "ibc-core-host-types", - "ibc-derive", "ibc-primitives", - "prost 0.12.1", "subtle-encoding", - "tendermint", + "tendermint 0.36.0", ] [[package]] name = "ibc-core-client-types" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafdbf6db5dab4c8ad610b6940e23b4f8abd0a6ac5e8e2801415a95defd4a583" +checksum = "423e9e9a70b78fabc94c51dae76800459e126f891ae0987e88ac5e12c36e24de" dependencies = [ + "borsh 0.10.3", "derive_more", "displaydoc", "ibc-core-commitment-types", "ibc-core-host-types", "ibc-primitives", "ibc-proto", - "prost 0.12.1", + "parity-scale-codec", + "scale-info", + "schemars", "serde", "subtle-encoding", - "tendermint", + "tendermint 0.36.0", ] [[package]] name = "ibc-core-commitment-types" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed4256b0216fc49024bac7e01c61b9bb055e31914ffe9ce6f468d7ce496a9357" +checksum = "c1b323c91e58ea7b573e01b8e76d13f146c97c245ada0aab3070576d54288f30" dependencies = [ + "borsh 0.10.3", "derive_more", "displaydoc", "ibc-primitives", "ibc-proto", "ics23", - "prost 0.12.1", + "parity-scale-codec", + "scale-info", + "schemars", "serde", "subtle-encoding", ] [[package]] name = "ibc-core-connection" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e237b70b9ba0177a4e59ac9048fffac2ac44c334703cc0ae403ad221450850" +checksum = "c31271364789ccfc12c25afa21b47274d7e07bf49b1f728fd66cfa6c29daaf4a" dependencies = [ "ibc-core-client", "ibc-core-connection-types", "ibc-core-handler-types", "ibc-core-host", "ibc-primitives", - "prost 0.12.1", ] [[package]] name = "ibc-core-connection-types" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca841416fa29626423917099092f3698ae2735074cb3fe42936ddf6b2ccbf2f7" +checksum = "52d302a36925469589a911dab66654f390b87e98608d07515e47f5c7e51dffe7" dependencies = [ + "borsh 0.10.3", "derive_more", "displaydoc", "ibc-core-client-types", @@ -2144,17 +2250,19 @@ dependencies = [ "ibc-core-host-types", "ibc-primitives", "ibc-proto", - "prost 0.12.1", + "parity-scale-codec", + "scale-info", + "schemars", "serde", "subtle-encoding", - "tendermint", + "tendermint 0.36.0", ] [[package]] name = "ibc-core-handler" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47e5e5a006aa0fc87ec3f5fb1e0ef6dd5aeea5079fa927d799d526c44329987" +checksum = "4523b9f77d3a1a391a3d63737760be44b23bc9b09ed297883a34654383b9b0e2" dependencies = [ "ibc-core-channel", "ibc-core-client", @@ -2168,10 +2276,11 @@ dependencies = [ [[package]] name = "ibc-core-handler-types" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3d59a8a5eb2069530c42783b4fef63472a89e0e9242334351df1bb58aaf542" +checksum = "73b229a92aa8b06ab9ccde6e1b3dad597deb97cb40e3bb6a631799b4cd2ad124" dependencies = [ + "borsh 0.10.3", "derive_more", "displaydoc", "ibc-core-channel-types", @@ -2182,17 +2291,19 @@ dependencies = [ "ibc-core-router-types", "ibc-primitives", "ibc-proto", - "prost 0.12.1", + "parity-scale-codec", + "scale-info", + "schemars", "serde", "subtle-encoding", - "tendermint", + "tendermint 0.36.0", ] [[package]] name = "ibc-core-host" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aa63c895c0e5a75e42fe859b8fd4250c12bfa8b9c6b114f94c927ecfad38a03" +checksum = "7ed7421f285225b78f3d020df6126b61f94b9eb370a83e42fbd4e9c8b04162fa" dependencies = [ "derive_more", "displaydoc", @@ -2204,15 +2315,14 @@ dependencies = [ "ibc-core-handler-types", "ibc-core-host-types", "ibc-primitives", - "prost 0.12.1", "subtle-encoding", ] [[package]] name = "ibc-core-host-cosmos" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a325862af6c20b0df3d27c072a2d802a7232dc1666214d738cdfbd9a9c99720" +checksum = "5b25b5b45cf47b1e73211a83adf3a753f1acdba887cd3bc5357d6f547d4a3e8c" dependencies = [ "derive_more", "displaydoc", @@ -2226,30 +2336,33 @@ dependencies = [ "ibc-core-host-types", "ibc-primitives", "ibc-proto", - "prost 0.12.1", "serde", "sha2 0.10.8", "subtle-encoding", - "tendermint", + "tendermint 0.36.0", ] [[package]] name = "ibc-core-host-types" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616955da310febbe93c0569a2feebd9f57cafed3eee5a56b0c3bb953a75f6089" +checksum = "939178939d33e5af1aca19608b019233d753f3734b828d66f40152b382f3db53" dependencies = [ + "borsh 0.10.3", "derive_more", "displaydoc", "ibc-primitives", + "parity-scale-codec", + "scale-info", + "schemars", "serde", ] [[package]] name = "ibc-core-router" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31fe115da73e0616bdb44400fa6b11ca251648d070c4ff419d58e27804d30243" +checksum = "328e6db6d3aa7126278c46c3dff779aa961952a63d031d3ddf4c202f4a3faad4" dependencies = [ "derive_more", "displaydoc", @@ -2257,99 +2370,104 @@ dependencies = [ "ibc-core-host-types", "ibc-core-router-types", "ibc-primitives", - "prost 0.12.1", "subtle-encoding", ] [[package]] name = "ibc-core-router-types" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d1fbb0bbbdeafa7ac989ba1693ed46d22e0e3eb0bdae478544e31157a4fdba6" +checksum = "4175b57087b28759364572683b335ec4fe63a6f938f1a5d0c383a6297a032155" dependencies = [ + "borsh 0.10.3", "derive_more", "displaydoc", "ibc-core-host-types", "ibc-primitives", "ibc-proto", - "ics23", - "prost 0.12.1", + "parity-scale-codec", + "scale-info", + "schemars", "serde", "subtle-encoding", - "tendermint", + "tendermint 0.36.0", ] [[package]] name = "ibc-derive" -version = "0.4.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df07bf5bc1e65e291506b7497633e07967e49b36a8db10cda77a8fd686eb4548" +checksum = "23d961d2194fd5229961835d2eb78091906ef8afbaaa55bce7ad41bf3ead8aa9" dependencies = [ - "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] name = "ibc-primitives" -version = "0.48.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5edea4685267fd68514c87e7aa3a62712340c4cff6903f088a9ab571428a08a" +checksum = "2b3340c4908f1a1a36863270ac976e0295fbd1911cbc4609ab406967fd8ccc04" dependencies = [ + "borsh 0.10.3", "derive_more", "displaydoc", "ibc-proto", - "prost 0.12.1", + "parity-scale-codec", + "prost", + "scale-info", + "schemars", "serde", - "tendermint", - "time 0.3.21", + "tendermint 0.36.0", + "time", ] [[package]] name = "ibc-proto" -version = "0.38.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93cbf4cbe9e5113cc7c70f3208a7029b2205c629502cbb2ae7ea0a09a97d3005" +checksum = "66080040d5a4800d52966d55b055400f86b79c34b854b935bef03c87aacda62a" dependencies = [ - "base64 0.21.3", + "base64 0.22.1", + "borsh 0.10.3", "bytes", "flex-error", "ics23", - "prost 0.12.1", + "informalsystems-pbjson", + "parity-scale-codec", + "prost", + "scale-info", + "schemars", "serde", "subtle-encoding", - "tendermint-proto", + "tendermint-proto 0.36.0", ] [[package]] name = "ics23" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "661e2d6f79952a65bc92b1c81f639ebd37228dae6ff412a5aba7d474bdc4b957" +checksum = "dc3b8be84e7285c73b88effdc3294b552277d6b0ec728ee016c861b7b9a2c19c" dependencies = [ "anyhow", + "blake2", + "blake3", "bytes", "hex", "informalsystems-pbjson", - "prost 0.12.1", + "prost", "ripemd", "serde", "sha2 0.10.8", "sha3", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -2371,7 +2489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" dependencies = [ "integer-sqrt", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "uint", ] @@ -2424,7 +2542,7 @@ name = "index-set" version = "0.8.0" source = "git+https://github.com/heliaxdev/index-set?tag=v0.8.1#b0d928f83cf0d465ccda299d131e8df2859b5184" dependencies = [ - "borsh", + "borsh 1.5.0", "serde", ] @@ -2440,21 +2558,32 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.2.4" +source = "git+https://github.com/heliaxdev/indexmap?tag=2.2.4-heliax-1#b5b5b547bd6ab04bbb16e060326a50ddaeb6c909" +dependencies = [ + "borsh 1.5.0", + "equivalent", + "hashbrown 0.14.5", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.5", ] [[package]] name = "informalsystems-pbjson" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eecd90f87bea412eac91c6ef94f6b1e390128290898cbe14f2b926787ae1fb" +checksum = "9aa4a0980c8379295100d70854354e78df2ee1c6ca0f96ffe89afeb3140e3a3d" dependencies = [ - "base64 0.13.1", + "base64 0.21.7", "serde", ] @@ -2464,14 +2593,14 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -2482,59 +2611,62 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" dependencies = [ - "num-traits", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +dependencies = [ + "base64 0.21.7", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "jubjub" version = "0.10.0" @@ -2546,14 +2678,14 @@ dependencies = [ "ff", "group", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] name = "k256" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ "cfg-if", "ecdsa", @@ -2566,13 +2698,33 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] +[[package]] +name = "konst" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50a0ba6de5f7af397afff922f22c149ff605c766cd3269cf6c1cd5e466dbe3b9" +dependencies = [ + "const_panic", + "konst_kernel", + "typewit", +] + +[[package]] +name = "konst_kernel" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0a455a1719220fd6adf756088e1c69a85bf14b6a9e24537a5cc04f503edb2b" +dependencies = [ + "typewit", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -2587,43 +2739,37 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] -name = "libsecp256k1" -version = "0.3.5" +name = "libredox" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "arrayref", - "crunchy", - "digest 0.8.1", - "hmac-drbg", - "rand 0.7.3", - "sha2 0.8.2", - "subtle 2.4.1", - "typenum", + "bitflags 2.5.0", + "libc", ] [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -2631,30 +2777,27 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "masp_note_encryption" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=30492323d98b0531fd18b6285cd94afcaa4066d2#30492323d98b0531fd18b6285cd94afcaa4066d2" +source = "git+https://github.com/anoma/masp?rev=6fc4692841a2241633792c429cc66b42023e5bf3#6fc4692841a2241633792c429cc66b42023e5bf3" dependencies = [ - "borsh", + "borsh 1.5.0", "chacha20", "chacha20poly1305", "cipher", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] name = "masp_primitives" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=30492323d98b0531fd18b6285cd94afcaa4066d2#30492323d98b0531fd18b6285cd94afcaa4066d2" +source = "git+https://github.com/anoma/masp?rev=6fc4692841a2241633792c429cc66b42023e5bf3#6fc4692841a2241633792c429cc66b42023e5bf3" dependencies = [ "aes", "bip0039", @@ -2662,7 +2805,7 @@ dependencies = [ "blake2b_simd", "blake2s_simd", "bls12_381", - "borsh", + "borsh 1.5.0", "byteorder", "ff", "fpe", @@ -2674,24 +2817,24 @@ dependencies = [ "masp_note_encryption", "memuse", "nonempty", - "num-traits", + "num-traits 0.2.19 (git+https://github.com/heliaxdev/num-traits?rev=db259754d33f193f02cbb65520d9ac00614be2c4)", "rand 0.8.5", "rand_core 0.6.4", "sha2 0.10.8", - "subtle 2.4.1", + "subtle", "zcash_encoding", ] [[package]] name = "masp_proofs" version = "1.0.0" -source = "git+https://github.com/anoma/masp?rev=30492323d98b0531fd18b6285cd94afcaa4066d2#30492323d98b0531fd18b6285cd94afcaa4066d2" +source = "git+https://github.com/anoma/masp?rev=6fc4692841a2241633792c429cc66b42023e5bf3#6fc4692841a2241633792c429cc66b42023e5bf3" dependencies = [ "bellman", "blake2b_simd", "bls12_381", "directories", - "getrandom 0.2.9", + "getrandom 0.2.15", "group", "itertools 0.11.0", "jubjub", @@ -2704,18 +2847,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" - -[[package]] -name = "memoffset" -version = "0.9.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memuse" @@ -2735,11 +2869,20 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "miniz_oxide" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +dependencies = [ + "adler", +] + [[package]] name = "mio" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -2748,18 +2891,18 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" [[package]] name = "namada" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ "async-trait", "bimap", - "borsh", + "borsh 1.5.0", "borsh-ext", "circular-queue", "clru", @@ -2770,12 +2913,14 @@ dependencies = [ "ethers", "eyre", "futures", - "itertools 0.10.5", + "itertools 0.12.1", + "konst", "masp_primitives", "masp_proofs", "namada_account", "namada_core", "namada_ethereum_bridge", + "namada_events", "namada_gas", "namada_governance", "namada_ibc", @@ -2790,13 +2935,12 @@ dependencies = [ "namada_tx_env", "namada_vote_ext", "namada_vp_env", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "num256", "orion", "owo-colors", - "parse_duration", "paste", - "prost 0.12.1", + "prost", "rand 0.8.5", "rand_core 0.6.4", "regex", @@ -2805,6 +2949,7 @@ dependencies = [ "serde_json", "sha2 0.9.9", "slip10_ed25519", + "smooth-operator", "tendermint-rpc", "thiserror", "tiny-bip39 0.8.2 (git+https://github.com/anoma/tiny-bip39.git?rev=bf0f6d8713589b83af7a917366ec31f5275c0e57)", @@ -2812,6 +2957,7 @@ dependencies = [ "tokio", "toml 0.5.11", "tracing", + "uint", "wasmparser", "wasmtimer", "wat", @@ -2820,27 +2966,37 @@ dependencies = [ [[package]] name = "namada_account" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ - "borsh", + "borsh 1.5.0", "namada_core", "namada_macros", "namada_storage", "serde", ] +[[package]] +name = "namada_controller" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" +dependencies = [ + "namada_core", + "smooth-operator", + "thiserror", +] + [[package]] name = "namada_core" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ "bech32 0.8.1", - "borsh", + "borsh 1.5.0", "borsh-ext", "chrono", "data-encoding", - "ed25519-consensus 1.2.1", + "ed25519-consensus", "ethabi", "ethbridge-structs", "eyre", @@ -2848,24 +3004,26 @@ dependencies = [ "ics23", "impl-num-traits", "index-set", + "indexmap 2.2.4", "k256", "masp_primitives", "namada_macros", "num-integer", - "num-rational 0.4.1", - "num-traits", + "num-rational", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "num256", - "num_enum 0.7.1", + "num_enum", "primitive-types", - "prost-types 0.12.1", + "prost-types", "rand 0.8.5", "rand_core 0.6.4", "serde", "serde_json", "sha2 0.9.9", + "smooth-operator", "sparse-merkle-tree", - "tendermint", - "tendermint-proto", + "tendermint 0.36.0", + "tendermint-proto 0.36.0", "thiserror", "tiny-keccak", "tracing", @@ -2875,15 +3033,17 @@ dependencies = [ [[package]] name = "namada_ethereum_bridge" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ - "borsh", + "borsh 1.5.0", "ethabi", "ethers", "eyre", - "itertools 0.10.5", + "itertools 0.12.1", + "konst", "namada_core", + "namada_events", "namada_macros", "namada_parameters", "namada_proof_of_stake", @@ -2895,20 +3055,34 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "tendermint", - "tendermint-proto", - "tendermint-rpc", + "tendermint 0.36.0", + "tendermint-proto 0.36.0", + "thiserror", + "tracing", +] + +[[package]] +name = "namada_events" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" +dependencies = [ + "borsh 1.5.0", + "namada_core", + "namada_macros", + "serde", + "serde_json", "thiserror", "tracing", ] [[package]] name = "namada_gas" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ - "borsh", + "borsh 1.5.0", "namada_core", + "namada_events", "namada_macros", "serde", "thiserror", @@ -2916,41 +3090,50 @@ dependencies = [ [[package]] name = "namada_governance" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ - "borsh", - "itertools 0.10.5", + "borsh 1.5.0", + "itertools 0.12.1", + "konst", "namada_core", + "namada_events", "namada_macros", "namada_parameters", "namada_storage", "namada_trans_token", "serde", "serde_json", + "smooth-operator", "thiserror", "tracing", ] [[package]] name = "namada_ibc" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ - "borsh", + "borsh 1.5.0", "ibc", "ibc-derive", "ics23", + "konst", "masp_primitives", "namada_core", + "namada_events", "namada_gas", "namada_governance", + "namada_macros", "namada_parameters", "namada_state", "namada_storage", "namada_token", + "namada_tx", "primitive-types", - "prost 0.12.1", + "prost", + "serde", + "serde_json", "sha2 0.9.9", "thiserror", "tracing", @@ -2958,12 +3141,10 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ "data-encoding", - "lazy_static", - "paste", "proc-macro2", "quote", "sha2 0.9.9", @@ -2972,25 +3153,25 @@ dependencies = [ [[package]] name = "namada_merkle_tree" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ - "borsh", + "borsh 1.5.0", "eyre", "ics23", "namada_core", "namada_macros", - "prost 0.12.1", + "prost", "sparse-merkle-tree", "thiserror", ] [[package]] name = "namada_parameters" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ - "borsh", + "borsh 1.5.0", "namada_core", "namada_macros", "namada_storage", @@ -2999,57 +3180,63 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ - "borsh", + "borsh 1.5.0", "data-encoding", - "derivative", + "konst", "namada_account", + "namada_controller", "namada_core", + "namada_events", "namada_governance", "namada_macros", "namada_parameters", "namada_storage", "namada_trans_token", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell", "serde", + "smooth-operator", "thiserror", "tracing", ] [[package]] name = "namada_replay_protection" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ "namada_core", ] [[package]] name = "namada_sdk" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ "async-trait", "bimap", - "borsh", + "borsh 1.5.0", "borsh-ext", "circular-queue", "data-encoding", "derivation-path", + "duration-str", "ethbridge-bridge-contract", "ethers", "eyre", "futures", - "itertools 0.10.5", + "itertools 0.12.1", "lazy_static", "masp_primitives", "masp_proofs", "namada_account", "namada_core", "namada_ethereum_bridge", + "namada_events", + "namada_gas", "namada_governance", "namada_ibc", "namada_macros", @@ -3060,13 +3247,13 @@ dependencies = [ "namada_token", "namada_tx", "namada_vote_ext", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "num256", "orion", "owo-colors", - "parse_duration", "paste", - "prost 0.12.1", + "patricia_tree", + "prost", "rand 0.8.5", "rand_core 0.6.4", "regex", @@ -3075,7 +3262,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "slip10_ed25519", - "tendermint-config", + "smooth-operator", + "tendermint-config 0.36.0", "tendermint-rpc", "thiserror", "tiny-bip39 0.8.2 (git+https://github.com/anoma/tiny-bip39.git?rev=bf0f6d8713589b83af7a917366ec31f5275c0e57)", @@ -3089,38 +3277,43 @@ dependencies = [ [[package]] name = "namada_shielded_token" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ - "borsh", + "borsh 1.5.0", "masp_primitives", + "namada_controller", "namada_core", "namada_parameters", "namada_storage", "namada_trans_token", + "namada_tx", "rayon", "serde", + "smooth-operator", "tracing", ] [[package]] name = "namada_state" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ - "borsh", + "borsh 1.5.0", "ics23", - "itertools 0.10.5", + "itertools 0.12.1", "namada_core", + "namada_events", "namada_gas", "namada_macros", "namada_merkle_tree", "namada_parameters", "namada_replay_protection", "namada_storage", - "namada_trans_token", "namada_tx", + "patricia_tree", "sha2 0.9.9", + "smooth-operator", "sparse-merkle-tree", "thiserror", "tiny-keccak", @@ -3129,29 +3322,29 @@ dependencies = [ [[package]] name = "namada_storage" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ - "borsh", - "itertools 0.10.5", + "borsh 1.5.0", + "itertools 0.12.1", "namada_core", - "namada_gas", "namada_macros", "namada_merkle_tree", "namada_replay_protection", - "namada_tx", "regex", "serde", + "smooth-operator", "thiserror", "tracing", ] [[package]] name = "namada_token" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ "namada_core", + "namada_events", "namada_shielded_token", "namada_storage", "namada_trans_token", @@ -3159,29 +3352,34 @@ dependencies = [ [[package]] name = "namada_trans_token" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ + "konst", "namada_core", + "namada_events", "namada_storage", ] [[package]] name = "namada_tx" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ "ark-bls12-381", - "borsh", + "bitflags 2.5.0", + "borsh 1.5.0", "data-encoding", + "konst", "masp_primitives", "namada_core", + "namada_events", "namada_gas", "namada_macros", "num-derive", - "num-traits", - "prost 0.12.1", - "prost-types 0.12.1", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "prost", + "prost-types", "serde", "serde_json", "sha2 0.9.9", @@ -3191,19 +3389,20 @@ dependencies = [ [[package]] name = "namada_tx_env" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ "namada_core", + "namada_events", "namada_storage", ] [[package]] name = "namada_vote_ext" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ - "borsh", + "borsh 1.5.0", "namada_core", "namada_macros", "namada_tx", @@ -3212,14 +3411,17 @@ dependencies = [ [[package]] name = "namada_vp_env" -version = "0.32.0" -source = "git+https://github.com/anoma/namada#5e0b1620f7f1bf68f9d7ebcef1a624eb60dba7cd" +version = "0.36.0" +source = "git+https://github.com/anoma/namada#1ecca97bda74f75859ef1aed30a404c75ded75cb" dependencies = [ "derivative", "masp_primitives", "namada_core", + "namada_events", + "namada_ibc", "namada_storage", "namada_tx", + "smooth-operator", "thiserror", ] @@ -3249,72 +3451,42 @@ checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" [[package]] name = "num" -version = "0.2.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ - "num-bigint 0.2.6", - "num-complex 0.2.4", + "num-bigint", + "num-complex", "num-integer", "num-iter", - "num-rational 0.2.4", - "num-traits", -] - -[[package]] -name = "num" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" -dependencies = [ - "num-bigint 0.4.3", - "num-complex 0.4.4", - "num-integer", - "num-iter", - "num-rational 0.4.1", - "num-traits", + "num-rational", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-bigint" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-complex" -version = "0.2.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ - "autocfg", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "num-complex" -version = "0.4.4" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" -dependencies = [ - "num-traits", -] +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-derive" @@ -3329,54 +3501,49 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-rational" -version = "0.2.4" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", - "num-bigint 0.2.6", + "num-bigint", "num-integer", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "num-rational" -version = "0.4.1" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "num-bigint 0.4.3", - "num-integer", - "num-traits", + "libm", ] [[package]] name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +version = "0.2.19" +source = "git+https://github.com/heliaxdev/num-traits?rev=db259754d33f193f02cbb65520d9ac00614be2c4#db259754d33f193f02cbb65520d9ac00614be2c4" dependencies = [ "autocfg", ] @@ -3388,9 +3555,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" dependencies = [ "lazy_static", - "num 0.4.1", + "num", "num-derive", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_derive", ] @@ -3407,63 +3574,45 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" -dependencies = [ - "num_enum_derive 0.6.1", -] - -[[package]] -name = "num_enum" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" dependencies = [ - "num_enum_derive 0.7.1", + "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.6.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] -name = "num_enum_derive" -version = "0.7.1" +name = "object" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ - "proc-macro-crate 2.0.1", - "proc-macro2", - "quote", - "syn 2.0.39", + "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "opaque-debug" -version = "0.2.3" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "open-fastrlp" @@ -3513,7 +3662,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] @@ -3524,9 +3673,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -3541,8 +3690,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", - "getrandom 0.2.9", - "subtle 2.4.1", + "getrandom 0.2.15", + "subtle", "zeroize", ] @@ -3563,9 +3712,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.5.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ddb756ca205bd108aee3c62c6d3c994e1df84a59b9d6d4a5ea42ee1fd5a9a28" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arrayvec", "bitvec", @@ -3577,11 +3726,11 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.1.4" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 1.0.109", @@ -3589,9 +3738,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -3599,37 +3748,26 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "smallvec", - "windows-targets 0.48.0", -] - -[[package]] -name = "parse_duration" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d" -dependencies = [ - "lazy_static", - "num 0.2.1", - "regex", + "windows-targets 0.52.5", ] [[package]] name = "password-hash" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -3642,32 +3780,31 @@ dependencies = [ "group", "rand 0.8.5", "static_assertions", - "subtle 2.4.1", + "subtle", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "pbkdf2" -version = "0.4.0" +name = "patricia_tree" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +checksum = "31f2f4539bffe53fc4b4da301df49d114b845b077bd5727b7fe2bd9d8df2ae68" dependencies = [ - "crypto-mac 0.8.0", + "bitflags 2.5.0", ] [[package]] name = "pbkdf2" -version = "0.10.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" dependencies = [ - "digest 0.10.7", - "password-hash", + "crypto-mac", ] [[package]] @@ -3687,13 +3824,14 @@ checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", "hmac 0.12.1", + "password-hash", ] [[package]] name = "peg" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" +checksum = "8a625d12ad770914cbf7eff6f9314c3ef803bfe364a1b20bc36ddf56673e71e5" dependencies = [ "peg-macros", "peg-runtime", @@ -3701,9 +3839,9 @@ dependencies = [ [[package]] name = "peg-macros" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" +checksum = "f241d42067ed3ab6a4fece1db720838e1418f36d868585a27931f95d6bc03582" dependencies = [ "peg-runtime", "proc-macro2", @@ -3712,21 +3850,30 @@ dependencies = [ [[package]] name = "peg-runtime" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" +checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" + +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.3" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", @@ -3735,12 +3882,12 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.0.0", + "indexmap 2.2.6", ] [[package]] @@ -3755,29 +3902,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.0" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.0" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -3808,34 +3955,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", - "opaque-debug 0.3.0", + "opaque-debug", "universal-hash", ] [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "prettyplease" -version = "0.1.25" +name = "ppv-lite86" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" -dependencies = [ - "proc-macro2", - "syn 1.0.109", -] +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.2.9" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] @@ -3854,22 +3997,20 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "once_cell", - "toml_edit 0.19.14", + "toml 0.5.11", ] [[package]] name = "proc-macro-crate" -version = "2.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_datetime", - "toml_edit 0.20.2", + "toml_edit 0.21.1", ] [[package]] @@ -3881,7 +4022,6 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", "version_check", ] @@ -3898,104 +4038,87 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" dependencies = [ "unicode-ident", ] [[package]] -name = "prost" -version = "0.11.9" +name = "proptest" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ - "bytes", - "prost-derive 0.11.9", + "bitflags 2.5.0", + "lazy_static", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_xorshift", + "regex-syntax", + "unarray", ] [[package]] name = "prost" -version = "0.12.1" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fdd22f3b9c31b53c060df4a0613a1c7f062d4115a2b984dd15b1858f7e340d" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.1", + "prost-derive", ] [[package]] name = "prost-build" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", - "heck", - "itertools 0.10.5", - "lazy_static", + "heck 0.5.0", + "itertools 0.12.1", "log", "multimap", + "once_cell", "petgraph", - "prettyplease 0.1.25", - "prost 0.11.9", - "prost-types 0.11.9", + "prettyplease", + "prost", + "prost-types", "regex", - "syn 1.0.109", + "syn 2.0.65", "tempfile", - "which", ] [[package]] name = "prost-derive" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", - "syn 1.0.109", -] - -[[package]] -name = "prost-derive" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" -dependencies = [ - "anyhow", - "itertools 0.11.0", - "proc-macro2", - "quote", - "syn 2.0.39", -] - -[[package]] -name = "prost-types" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" -dependencies = [ - "prost 0.11.9", + "syn 2.0.65", ] [[package]] name = "prost-types" -version = "0.12.1" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e081b29f63d83a4bc75cfc9f3fe424f9156cf92d8a4f0c9407cce9a1b67327cf" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ - "prost 0.12.1", + "prost", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -4065,7 +4188,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.15", ] [[package]] @@ -4077,6 +4200,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rayon" version = "1.5.3" @@ -4091,14 +4223,13 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", + "wasm_sync", ] [[package]] @@ -4134,38 +4265,29 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "getrandom 0.2.9", - "redox_syscall 0.2.16", + "getrandom 0.2.15", + "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -4186,9 +4308,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" @@ -4196,13 +4318,13 @@ version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64 0.21.3", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "hyper", "hyper-tls", @@ -4237,7 +4359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34618a20b14cebebc63f8b1c5228116e7379765e8f1b7a9f66b5920babd8595" dependencies = [ "js-sys", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror", "tokio", "wasm-bindgen", @@ -4252,7 +4374,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac 0.12.1", - "subtle 2.4.1", + "subtle", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", ] [[package]] @@ -4286,6 +4423,22 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rust_decimal" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +dependencies = [ + "arrayvec", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -4313,21 +4466,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.18", + "semver 1.0.23", ] [[package]] name = "rustix" -version = "0.37.23" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4336,20 +4488,20 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.3", + "base64 0.21.7", ] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "salsa20" @@ -4371,9 +4523,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.9.0" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0a159d0c45c12b20c5a844feb1fe4bea86e28f17b92a5f0c42193634d3782" +checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" dependencies = [ "cfg-if", "derive_more", @@ -4383,11 +4535,11 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.9.0" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "912e55f6d20e0e80d63733872b40e1227c0bce1e1ab81ba67d696339bfd7fd29" +checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 1.0.109", @@ -4402,6 +4554,30 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "schemars" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0218ceea14babe24a4a5836f86ade86c1effbc198164e619194cb5069187e29" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed5a1ccce8ff962e31a165d41f6e2a2dd1245099dc4d594f5574a86cd90f4d3" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.65", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -4434,20 +4610,20 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", - "generic-array 0.14.7", + "generic-array", "pkcs8", "serdect", - "subtle 2.4.1", + "subtle", "zeroize", ] [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -4456,9 +4632,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -4475,9 +4651,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] @@ -4505,47 +4681,58 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] [[package]] name = "serde-json-wasm" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c37d03f3b0f6b5f77c11af1e7c772de1c9af83e50bef7bb6069601900ba67b" +checksum = "f05da0d153dd4595bdffd5099dc0e9ce425b205ee648eb93437ff7302af8c9a5" dependencies = [ "serde", ] [[package]] name = "serde_bytes" -version = "0.11.10" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c5113243e4a3a1c96587342d067f3e6b0f50790b6cf40d2868eb647a3eef0e" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -4554,20 +4741,20 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.13" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0a21fba416426ac927b1691996e82079f8b6156e920c85345f135b2e9ba2de" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -4594,18 +4781,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", -] - [[package]] name = "sha2" version = "0.9.9" @@ -4616,7 +4791,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -4647,7 +4822,7 @@ dependencies = [ "async-trait", "chrono", "console_error_panic_hook", - "getrandom 0.2.9", + "getrandom 0.2.15", "gloo-utils", "hex", "js-sys", @@ -4657,7 +4832,7 @@ dependencies = [ "rexie", "serde", "serde_json", - "tendermint-config", + "tendermint-config 0.34.1", "thiserror", "tiny-bip39 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen", @@ -4670,23 +4845,35 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", ] +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", + "time", +] + [[package]] name = "slab" version = "0.4.9" @@ -4707,92 +4894,99 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smooth-operator" +version = "0.6.0" +source = "git+https://github.com/heliaxdev/smooth-operator?tag=v0.6.0#1e9e2382dd6c053f54418db836f7f03143fcd2f3" +dependencies = [ + "smooth-operator-impl", +] + +[[package]] +name = "smooth-operator-impl" +version = "0.6.0" +source = "git+https://github.com/heliaxdev/smooth-operator?tag=v0.6.0#1e9e2382dd6c053f54418db836f7f03143fcd2f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] [[package]] name = "socket2" -version = "0.4.9" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=515687fe7884cb365067ac86c66ac3613de176bb#515687fe7884cb365067ac86c66ac3613de176bb" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=bab8cb96872db22cc9a139b2d3dfc4e00521d097#bab8cb96872db22cc9a139b2d3dfc4e00521d097" dependencies = [ - "borsh", + "borsh 1.5.0", "cfg-if", "ics23", + "itertools 0.12.1", "sha2 0.9.9", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] -[[package]] -name = "spmc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a8428da277a8e3a15271d79943e80ccc2ef254e78813a166a08d65e4c3ece5" - [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strum" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.25.2" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - -[[package]] -name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "subtle-encoding" @@ -4822,9 +5016,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" dependencies = [ "proc-macro2", "quote", @@ -4840,7 +5034,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] @@ -4878,35 +5072,62 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.6.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ - "autocfg", "cfg-if", "fastrand", - "redox_syscall 0.3.5", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tendermint" -version = "0.34.0" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc2294fa667c8b548ee27a9ba59115472d0a09c2ba255771092a7f1dcf03a789" +checksum = "15ab8f0a25d0d2ad49ac615da054d6a76aa6603ff95f7d18bafdd34450a1a04b" dependencies = [ "bytes", "digest 0.10.7", "ed25519", - "ed25519-consensus 2.1.0", + "ed25519-consensus", + "flex-error", + "futures", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell", + "prost", + "prost-types", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.10.8", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.34.1", + "time", + "zeroize", +] + +[[package]] +name = "tendermint" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b50aae6ec24c3429149ad59b5b8d3374d7804d4c7d6125ceb97cb53907fb68d" +dependencies = [ + "bytes", + "digest 0.10.7", + "ed25519", + "ed25519-consensus", "flex-error", "futures", "k256", - "num-traits", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell", - "prost 0.12.1", - "prost-types 0.12.1", + "prost", + "prost-types", "ripemd", "serde", "serde_bytes", @@ -4914,123 +5135,147 @@ dependencies = [ "serde_repr", "sha2 0.10.8", "signature", - "subtle 2.4.1", + "subtle", "subtle-encoding", - "tendermint-proto", - "time 0.3.21", + "tendermint-proto 0.36.0", + "time", "zeroize", ] [[package]] name = "tendermint-config" -version = "0.34.0" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a25dbe8b953e80f3d61789fbdb83bf9ad6c0ef16df5ca6546f49912542cc137" +checksum = "e1a02da769166e2052cd537b1a97c78017632c2d9e19266367b27e73910434fc" dependencies = [ "flex-error", "serde", "serde_json", - "tendermint", + "tendermint 0.34.1", "toml 0.5.11", "url", ] +[[package]] +name = "tendermint-config" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e07b383dc8780ebbec04cfb603f3fdaba6ea6663d8dd861425b1ffa7761fe90d" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint 0.36.0", + "toml 0.8.13", + "url", +] + [[package]] name = "tendermint-light-client-verifier" -version = "0.34.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74994da9de4b1144837a367ca2c60c650f5526a7c1a54760a3020959b522e474" +checksum = "4216e487165e5dbd7af79952eaa0d5f06c5bde861eb76c690acd7f2d2a19395c" dependencies = [ "derive_more", "flex-error", "serde", - "tendermint", - "time 0.3.21", + "tendermint 0.36.0", + "time", ] [[package]] name = "tendermint-proto" -version = "0.34.0" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc728a4f9e891d71adf66af6ecaece146f9c7a11312288a3107b3e1d6979aaf" +checksum = "b797dd3d2beaaee91d2f065e7bdf239dc8d80bba4a183a288bc1279dd5a69a1e" dependencies = [ "bytes", "flex-error", "num-derive", - "num-traits", - "prost 0.12.1", - "prost-types 0.12.1", + "num-traits 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", + "prost", + "prost-types", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f193d04afde6592c20fd70788a10b8cb3823091c07456db70d8a93f5fb99c1" +dependencies = [ + "bytes", + "flex-error", + "prost", + "prost-types", "serde", "serde_bytes", "subtle-encoding", - "time 0.3.21", + "time", ] [[package]] name = "tendermint-rpc" -version = "0.34.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbf0a4753b46a190f367337e0163d0b552a2674a6bac54e74f9f2cdcde2969b" +checksum = "21e3c231a3632cab53f92ad4161c730c468c08cfe4f0aa5a6735b53b390aecbd" dependencies = [ "async-trait", "bytes", "flex-error", - "getrandom 0.2.9", + "getrandom 0.2.15", "peg", "pin-project", - "semver 1.0.18", + "rand 0.8.5", + "semver 1.0.23", "serde", "serde_bytes", "serde_json", - "subtle 2.4.1", + "subtle", "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", + "tendermint 0.36.0", + "tendermint-config 0.36.0", + "tendermint-proto 0.36.0", "thiserror", - "time 0.3.21", + "time", "url", - "uuid", + "uuid 1.8.0", "walkdir", ] [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.21" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", "serde", "time-core", "time-macros", @@ -5038,16 +5283,17 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -5091,14 +5337,13 @@ dependencies = [ [[package]] name = "tiny-hderive" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b874a4992538d4b2f4fbbac11b9419d685f4b39bdc3fed95b04e07bfd76040" +source = "git+https://github.com/heliaxdev/tiny-hderive.git?rev=173ae03abed0cd25d88a5a13efac00af96b75b87#173ae03abed0cd25d88a5a13efac00af96b75b87" dependencies = [ "base58", - "hmac 0.7.1", - "libsecp256k1", + "hmac 0.12.1", + "k256", "memzero", - "sha2 0.8.2", + "sha2 0.10.8", ] [[package]] @@ -5127,11 +5372,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.2" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", "mio", @@ -5146,13 +5391,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] @@ -5167,16 +5412,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -5190,60 +5434,60 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.6" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.19.14", + "toml_edit 0.22.13", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.0.0", - "serde", - "serde_spanned", + "indexmap 2.2.6", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.6", + "serde", + "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.8", ] [[package]] name = "tonic-build" -version = "0.8.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" +checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" dependencies = [ - "prettyplease 0.1.25", + "prettyplease", "proc-macro2", "prost-build", "quote", - "syn 1.0.109", + "syn 2.0.65", ] [[package]] @@ -5254,11 +5498,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5266,20 +5509,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] @@ -5296,15 +5539,30 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "typewit" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "c6fb9ae6a3cafaf0a5d14c2302ca525f9ae8e07a0f0e6949de88d882c37a6e24" +dependencies = [ + "typewit_proc_macros", +] + +[[package]] +name = "typewit_proc_macros" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" [[package]] name = "ucd-trie" @@ -5324,32 +5582,38 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode-xid" @@ -5364,14 +5628,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", - "subtle 2.4.1", + "subtle", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -5384,10 +5654,16 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.15", "serde", ] +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" + [[package]] name = "vcpkg" version = "0.2.15" @@ -5402,9 +5678,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -5425,12 +5701,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -5439,9 +5709,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -5449,24 +5719,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -5476,9 +5746,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5486,40 +5756,40 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-rayon" -version = "1.0.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df87c67450805c305d3ae44a3ac537b0253d029153c25afc3ecd2edc36ccafb1" +checksum = "a9beda8dfdfaf2e0ec0b47e130a0794d18188fba4da8a2155dcc3bbeb7e0d454" dependencies = [ + "crossbeam-channel", "js-sys", - "rayon", - "spmc", + "rayon-core", "wasm-bindgen", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-bindgen-test" -version = "0.3.36" +version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e636f3a428ff62b3742ebc3c70e254dfe12b8c2b469d688ea59cdd4abcf502" +checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b" dependencies = [ "console_error_panic_hook", "js-sys", @@ -5531,12 +5801,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.36" +version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18c1fad2f7c4958e7bcce014fa212f59a65d5e3721d0f77e6c0b27ede936ba3" +checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" dependencies = [ "proc-macro2", "quote", + "syn 2.0.65", ] [[package]] @@ -5548,6 +5819,17 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm_sync" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff360cade7fec41ff0e9d2cda57fe58258c5f16def0e21302394659e6bbb0ea" +dependencies = [ + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.107.0" @@ -5555,7 +5837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" dependencies = [ "indexmap 1.9.3", - "semver 1.0.18", + "semver 1.0.23", ] [[package]] @@ -5595,25 +5877,14 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] - [[package]] name = "winapi" version = "0.3.9" @@ -5632,11 +5903,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -5646,12 +5917,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.52.5", ] [[package]] @@ -5660,7 +5931,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.5", ] [[package]] @@ -5669,128 +5940,144 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] [[package]] name = "winnow" -version = "0.5.15" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] @@ -5842,11 +6129,31 @@ dependencies = [ "nonempty", ] +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] @@ -5859,5 +6166,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.65", ] diff --git a/packages/shared/lib/Cargo.toml b/packages/shared/lib/Cargo.toml index ced09c11c..8a7990539 100644 --- a/packages/shared/lib/Cargo.toml +++ b/packages/shared/lib/Cargo.toml @@ -24,7 +24,7 @@ chrono = "0.4.22" getrandom = { version = "0.2.7", features = ["js"] } gloo-utils = { version = "0.1.5", features = ["serde"] } js-sys = "0.3.60" -namada = { git = "https://github.com/anoma/namada", version = "0.32.0", default-features = false, features = ["namada-sdk"] } +namada = { git = "https://github.com/anoma/namada", version = "0.36.0", default-features = false, features = ["namada-sdk"] } rand = "0.8.5" rexie = "0.5" serde = "^1.0.181" @@ -73,7 +73,7 @@ wasm-opt = false [package.metadata.wasm-pack.profile.dev.wasm-bindgen] omit-default-module-path = true # We set it to false as it checks if return type from setTimout is a number which is not true in the nodejs environment -debug-js-glue = false +debug-js-glue = false [package.metadata.wasm-pack.profile.release.wasm-bindgen] omit-default-module-path = true diff --git a/packages/shared/lib/rust-toolchain.toml b/packages/shared/lib/rust-toolchain.toml index 0862f9716..dd8fd91b6 100644 --- a/packages/shared/lib/rust-toolchain.toml +++ b/packages/shared/lib/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2023-06-01" +channel = "nightly-2024-02-09" components = ["rustc", "cargo", "rust-std", "rust-docs", "rls", "rust-src", "rust-analysis"] targets = ['wasm32-unknown-unknown'] diff --git a/packages/shared/lib/src/query.rs b/packages/shared/lib/src/query.rs index 554d41491..490451515 100644 --- a/packages/shared/lib/src/query.rs +++ b/packages/shared/lib/src/query.rs @@ -3,7 +3,8 @@ use namada::address::Address; use namada::core::borsh::BorshSerialize; use namada::eth_bridge_pool::TransferToEthereum; use namada::governance::storage::keys as governance_storage; -use namada::governance::utils::{compute_proposal_result, ProposalVotes, TallyVote, VotePower}; +use namada::governance::utils::{compute_proposal_result, ProposalVotes, + VotePower, TallyType, TallyResult}; use namada::governance::{ProposalType, ProposalVote}; use namada::ledger::eth_bridge::bridge_pool::query_signed_bridge_pool; use namada::ledger::parameters::storage; @@ -18,12 +19,13 @@ use namada::sdk::masp_primitives::zip32::ExtendedFullViewingKey; use namada::sdk::rpc::{ format_denominated_amount, get_public_key_at, get_token_balance, get_total_staked_tokens, query_epoch, query_native_token, query_proposal_by_id, query_proposal_votes, - query_storage_value, + query_storage_value, is_steward }; use namada::token; use namada::uint::I256; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap}; use std::str::FromStr; +use namada::core::collections::{HashSet, HashMap}; use wasm_bindgen::prelude::*; use crate::rpc_client::HttpClient; @@ -51,10 +53,10 @@ impl Query { /// # Errors /// /// Returns an error if the RPC call fails - pub async fn query_epoch(&self) -> Result<JsValue, JsError> { + pub async fn query_epoch(&self) -> Result<u64, JsError> { let epoch = RPC.shell().epoch(&self.client).await?; - to_js_result(epoch) + Ok(epoch.0) } /// Gets all active validator addresses @@ -119,7 +121,7 @@ impl Query { let validators = RPC .vp() .pos() - .delegation_validators(&self.client, &address) + .delegation_validators(&self.client, &address, &None) .await?; validators_per_address.insert(address, validators); @@ -181,7 +183,7 @@ impl Query { let validators = RPC .vp() .pos() - .delegation_validators(&self.client, &address) + .delegation_validators(&self.client, &address, &None) .await?; validators_per_address.insert(address, validators); @@ -391,78 +393,281 @@ impl Query { to_js_result(result) } - /// Returns a list of all proposals - pub async fn query_proposals(&self) -> Result<Uint8Array, JsError> { - let last_proposal_id_key = governance_storage::get_counter_key(); - let last_proposal_id = - query_storage_value::<HttpClient, u64>(&self.client, &last_proposal_id_key) + pub async fn query_total_staked_tokens( + &self, + epoch: u64 + ) -> Result<JsValue, JsError> { + let total_staked_tokens = + get_total_staked_tokens(&self.client, Epoch(epoch)) + .await?; + + to_js_result(total_staked_tokens) + } + + pub async fn query_proposal_counter(&self) -> Result<JsValue, JsError> { + let proposal_counter_key = governance_storage::get_counter_key(); + let proposal_counter = + query_storage_value::<HttpClient, u64>(&self.client, &proposal_counter_key) .await .unwrap(); - let from_id = if last_proposal_id > 10 { - last_proposal_id - 10 - } else { - 0 - }; + to_js_result(proposal_counter) + } - let mut proposals: Vec<ProposalInfo> = vec![]; - let epoch = RPC.shell().epoch(&self.client).await?; + pub async fn query_proposal_by_id(&self, id: u64) -> Result<Uint8Array, JsError> { + let proposal = query_proposal_by_id(&self.client, id) + .await + .unwrap() + .expect("Proposal should be written to storage."); - for id in from_id..last_proposal_id { - let proposal = query_proposal_by_id(&self.client, id) - .await - .unwrap() - .expect("Proposal should be written to storage."); - let votes = compute_proposal_votes(&self.client, id, proposal.voting_end_epoch).await; - let total_voting_power = - get_total_staked_tokens(&self.client, proposal.voting_end_epoch) - .await - .unwrap(); - //TODO: for now we assume that interface does not support steward accounts - let tally_type = proposal.get_tally_type(false); - - let proposal_type = match proposal.r#type { - ProposalType::PGFSteward(_) => "pgf_steward", - ProposalType::PGFPayment(_) => "pgf_payment", - ProposalType::Default(_) => "default", - }; - let status = - if proposal.voting_start_epoch <= epoch && proposal.voting_end_epoch >= epoch { - "ongoing" - } else if proposal.voting_end_epoch < epoch { - "finished" - } else { - "upcoming" - }; + let content = serde_json::to_string(&proposal.content)?; - let content = serde_json::to_string(&proposal.content)?; - - let proposal_result = compute_proposal_result(votes, total_voting_power, tally_type); - - let proposal_info = ProposalInfo { - id: proposal.id.to_string(), - proposal_type: proposal_type.to_string(), - author: proposal.author.to_string(), - start_epoch: proposal.voting_start_epoch.0, - end_epoch: proposal.voting_end_epoch.0, - grace_epoch: proposal.grace_epoch.0, - content, - status: status.to_string(), - result: proposal_result.result.to_string(), - total_voting_power: proposal_result.total_voting_power.to_string_native(), - total_yay_power: proposal_result.total_yay_power.to_string_native(), - total_nay_power: proposal_result.total_nay_power.to_string_native(), - }; - - proposals.push(proposal_info); - } + let is_steward = is_steward(&self.client, &proposal.author).await; + let tally_type = proposal.get_tally_type(is_steward); + let tally_type_string = match tally_type { + TallyType::TwoThirds => "two-thirds", + TallyType::OneHalfOverOneThird => "one-half-over-one-third", + TallyType::LessOneHalfOverOneThirdNay => "less-one-half-over-one-third-nay" + }; + + let (proposal_type, data) = match proposal.r#type { + ProposalType::Default => { + ("default", None) + }, + ProposalType::DefaultWithWasm(hash) => { + ("default", Some(hash.to_string())) + }, + ProposalType::PGFSteward(data) => { + let data_string = serde_json::to_string(&data)?; + ("pgf_steward", Some(data_string)) + }, + ProposalType::PGFPayment(data) => { + let data_string = serde_json::to_string(&data)?; + ("pgf_payment", Some(data_string)) + } + }; + + let proposal_info = ProposalInfo { + id: proposal.id, + author: proposal.author.to_string(), + start_epoch: proposal.voting_start_epoch.0, + end_epoch: proposal.voting_end_epoch.0, + grace_epoch: proposal.activation_epoch.0, + content, + tally_type: String::from(tally_type_string), + proposal_type: String::from(proposal_type), + data + }; let mut writer = vec![]; - BorshSerialize::serialize(&proposals, &mut writer)?; + BorshSerialize::serialize(&proposal_info, &mut writer)?; Ok(Uint8Array::from(writer.as_slice())) } + pub async fn query_proposal_votes( + &self, + proposal_id: u64, + epoch: u64 + ) -> Result<JsValue, JsError> { + + let votes = compute_proposal_votes(&self.client, proposal_id, Epoch(epoch)).await; + + //let result = query_proposal_votes(&self.client, id).await?; + + //let votes: Vec<(Address, String, bool)> = result + // .into_iter() + // .map(|vote| { + // let data = match vote.data { + // ProposalVote::Yay => "yay", + // ProposalVote::Nay => "nay", + // ProposalVote::Abstain => "abstain" + // }; + // let is_validator = vote.is_validator(); + // ( + // vote.delegator, + // String::from(data), + // is_validator + // ) + // }) + // .collect(); + + let validator_votes: Vec<(Address, String, token::Amount)> = Vec::from_iter( + votes.validators_vote.iter().map(|(address, vote)| { + let vote = match vote { + ProposalVote::Yay => "yay", + ProposalVote::Nay => "nay", + ProposalVote::Abstain => "abstain", + }; + + let voting_power = votes.validator_voting_power.get(address) + .expect("validator has voting power entry") + .clone(); + + (address.clone(), String::from(vote), voting_power) + }) + ); + + //let validator_voting_power: Vec<(Address, token::Amount)> = + // votes.validator_voting_power.into_iter().collect(); + + let delegator_votes: Vec<(Address, String, Vec<(Address, token::Amount)>)> = + Vec::from_iter( + votes.delegators_vote.iter().map(|(address, vote)| { + let vote = match vote { + ProposalVote::Yay => "yay", + ProposalVote::Nay => "nay", + ProposalVote::Abstain => "abstain", + }; + + let voting_power = votes.delegator_voting_power.get(address) + .expect("delegator has voting power entry") + .clone() + .into_iter() + .collect(); + + (address.clone(), String::from(vote), voting_power) + }) + ); + + //let validator_voting_power: Vec<(Address, Amount)> = Vec::from_iter( + // votes.validator_voting_power.iter().map(|(address, voting_power)| { + // let vote = match vote { + // TallyVote::OnChain(ProposalVote::Yay) => "yay", + // TallyVote::OnChain(ProposalVote::Nay) => "nay", + // TallyVote::OnChain(ProposalVote::Abstain) => "abstain", + // TallyVote::Offline(_) => panic!("received offline tally") + // }; + // (address.clone(), String::from(vote)) + // }) + //); + + to_js_result(( + validator_votes, + delegator_votes + )) + } + + pub async fn query_proposal_result( + &self, + proposal_id: u64, + epoch: u64 + ) -> Result<JsValue, JsError> { + let epoch = Epoch(epoch); + + let votes = compute_proposal_votes(&self.client, proposal_id, epoch).await; + + let total_voting_power = + get_total_staked_tokens(&self.client, epoch).await?; + + let proposal = query_proposal_by_id(&self.client, proposal_id) + .await + .unwrap() + .expect("Proposal should be written to storage."); + let is_steward = is_steward(&self.client, &proposal.author).await; + let tally_type = proposal.get_tally_type(is_steward); + + let proposal_result = + compute_proposal_result(votes, total_voting_power, tally_type) + .expect("could compute proposal result"); + + let passed = match proposal_result.result { + TallyResult::Passed => true, + TallyResult::Rejected => false, + }; + + to_js_result(( + passed, + proposal_result.total_yay_power, + proposal_result.total_nay_power, + proposal_result.total_abstain_power, + proposal_result.total_voting_power, + )) + } + + pub async fn query_proposal_code(&self, proposal_id: u64) -> Result<Uint8Array, JsError> { + let proposal_code_key = + governance_storage::get_proposal_code_key(proposal_id); + let code = query_storage_value::<HttpClient, Vec<u8>>( + &self.client, + &proposal_code_key + ).await?; + + Ok(Uint8Array::from(code.as_slice())) + } + + ///// Returns a list of all proposals + //pub async fn query_proposals(&self) -> Result<Uint8Array, JsError> { + // let last_proposal_id_key = governance_storage::get_counter_key(); + // let last_proposal_id = + // query_storage_value::<HttpClient, u64>(&self.client, &last_proposal_id_key) + // .await + // .unwrap(); + + // let from_id = if last_proposal_id > 10 { + // last_proposal_id - 10 + // } else { + // 0 + // }; + + // let mut proposals: Vec<ProposalInfo> = vec![]; + // let epoch = RPC.shell().epoch(&self.client).await?; + + // for id in from_id..last_proposal_id { + // let proposal = query_proposal_by_id(&self.client, id) + // .await + // .unwrap() + // .expect("Proposal should be written to storage."); + // let votes = compute_proposal_votes(&self.client, id, proposal.voting_end_epoch).await; + // let total_voting_power = + // get_total_staked_tokens(&self.client, proposal.voting_end_epoch) + // .await + // .unwrap(); + // //TODO: for now we assume that interface does not support steward accounts + // let tally_type = proposal.get_tally_type(false); + + // let proposal_type = match proposal.r#type { + // ProposalType::PGFSteward(_) => "pgf_steward", + // ProposalType::PGFPayment(_) => "pgf_payment", + // ProposalType::Default(_) => "default", + // }; + // let status = + // if proposal.voting_start_epoch <= epoch && proposal.voting_end_epoch >= epoch { + // "ongoing" + // } else if proposal.voting_end_epoch < epoch { + // "finished" + // } else { + // "upcoming" + // }; + + // let content = serde_json::to_string(&proposal.content)?; + + // let proposal_result = compute_proposal_result(votes, total_voting_power, tally_type); + + // let proposal_info = ProposalInfo { + // id: proposal.id.to_string(), + // proposal_type: proposal_type.to_string(), + // author: proposal.author.to_string(), + // start_epoch: proposal.voting_start_epoch.0, + // end_epoch: proposal.voting_end_epoch.0, + // grace_epoch: proposal.grace_epoch.0, + // content, + // status: status.to_string(), + // result: proposal_result.result.to_string(), + // total_voting_power: proposal_result.total_voting_power.to_string_native(), + // total_yay_power: proposal_result.total_yay_power.to_string_native(), + // total_nay_power: proposal_result.total_nay_power.to_string_native(), + // }; + + // proposals.push(proposal_info); + // } + + // let mut writer = vec![]; + // BorshSerialize::serialize(&proposals, &mut writer)?; + + // Ok(Uint8Array::from(writer.as_slice())) + //} + /// Returns a list of all delegations for given addresses and epoch /// /// # Arguments @@ -552,13 +757,20 @@ pub async fn compute_proposal_votes( proposal_id: u64, epoch: Epoch, ) -> ProposalVotes { - let votes = query_proposal_votes(client, proposal_id).await.unwrap(); + let votes = query_proposal_votes(client, proposal_id) + .await + .unwrap(); - let mut validators_vote: HashMap<Address, TallyVote> = HashMap::default(); - let mut validator_voting_power: HashMap<Address, VotePower> = HashMap::default(); - let mut delegators_vote: HashMap<Address, TallyVote> = HashMap::default(); - let mut delegator_voting_power: HashMap<Address, HashMap<Address, VotePower>> = + let mut validators_vote: HashMap<Address, ProposalVote> = + HashMap::default(); + let mut validator_voting_power: HashMap<Address, VotePower> = + HashMap::default(); + let mut delegators_vote: HashMap<Address, ProposalVote> = HashMap::default(); + let mut delegator_voting_power: HashMap< + Address, + HashMap<Address, VotePower>, + > = HashMap::default(); for vote in votes { if vote.is_validator() { @@ -570,7 +782,7 @@ pub async fn compute_proposal_votes( .expect("Validator stake should be present") .unwrap_or_default(); - validators_vote.insert(vote.validator.clone(), vote.data.into()); + validators_vote.insert(vote.validator.clone(), vote.data); validator_voting_power.insert(vote.validator, validator_stake); } else { let delegator_stake = RPC @@ -580,11 +792,11 @@ pub async fn compute_proposal_votes( .await .expect("Delegator stake should be present"); - delegators_vote.insert(vote.delegator.clone(), vote.data.into()); - delegator_voting_power - .entry(vote.delegator.clone()) - .or_default() - .insert(vote.validator, delegator_stake); + delegators_vote.insert(vote.delegator.clone(), vote.data); + delegator_voting_power + .entry(vote.delegator.clone()) + .or_default() + .insert(vote.validator, delegator_stake); } } diff --git a/packages/shared/lib/src/rpc_client.rs b/packages/shared/lib/src/rpc_client.rs index 9ab08d8e0..681e2eb27 100644 --- a/packages/shared/lib/src/rpc_client.rs +++ b/packages/shared/lib/src/rpc_client.rs @@ -119,6 +119,7 @@ impl Client for HttpClient { data: response.value, info: response.info, proof: response.proof, + height: response.height.into(), }), Code::Err(code) => Err(RpcError::new(&format!("Error code {}", code))), } diff --git a/packages/shared/lib/src/rpc_client.js b/packages/shared/lib/src/rpc_client.ts similarity index 76% rename from packages/shared/lib/src/rpc_client.js rename to packages/shared/lib/src/rpc_client.ts index bc96cb6f6..77f7a0189 100644 --- a/packages/shared/lib/src/rpc_client.js +++ b/packages/shared/lib/src/rpc_client.ts @@ -3,12 +3,10 @@ * Small wrapper for fetch to make it easier to pass props * Called wasmFetch to avoid naming conflict */ -async function wasmFetch(url, method, body) { +export async function wasmFetch(url: string, method: string, body: string) { const res = await fetch(url, { method, body, }); return res; } - -module.exports = { wasmFetch }; diff --git a/packages/shared/lib/src/sdk/masp/masp.node.js b/packages/shared/lib/src/sdk/masp/masp.node.js index 78d096eea..f46e969f5 100644 --- a/packages/shared/lib/src/sdk/masp/masp.node.js +++ b/packages/shared/lib/src/sdk/masp/masp.node.js @@ -1,22 +1,30 @@ const fs = require("node:fs"); -export function writeFileSync(path, ui8a) { +function writeFileSync(path, ui8a) { fs.writeFileSync(path, Buffer.from(ui8a)); } -export function readFileSync(path) { +function readFileSync(path) { const buffer = fs.readFileSync(path).buffer; return buffer; } -export function renameSync(pathA, pathB) { +function renameSync(pathA, pathB) { fs.renameSync(pathA, pathB); } -export function unlinkSync(path) { +function unlinkSync(path) { fs.unlinkSync(path); } -export function existsSync(path) { +function existsSync(path) { return fs.existsSync(path); } + +module.exports = { + writeFileSync, + readFileSync, + renameSync, + unlinkSync, + existsSync, +}; diff --git a/packages/shared/lib/src/sdk/mod.rs b/packages/shared/lib/src/sdk/mod.rs index 995c4078f..0a881433c 100644 --- a/packages/shared/lib/src/sdk/mod.rs +++ b/packages/shared/lib/src/sdk/mod.rs @@ -124,7 +124,7 @@ impl Sdk { } #[cfg(feature = "web")] - pub async fn load_masp_params(&mut self, _db_name: JsValue) -> Result<(), JsValue> { + pub async fn load_masp_params(&self, _db_name: JsValue) -> Result<(), JsValue> { // _dn_name is not used in the web version for a time being let params = get_masp_params().await?; let params_iter = js_sys::try_iter(¶ms)?.ok_or_else(|| "Can't iterate over JsValue")?; @@ -144,7 +144,7 @@ impl Sdk { } #[cfg(feature = "nodejs")] - pub async fn load_masp_params(&mut self, context_dir: JsValue) -> Result<(), JsValue> { + pub async fn load_masp_params(&self, context_dir: JsValue) -> Result<(), JsValue> { let context_dir = context_dir.as_string().unwrap(); let mut shielded = self.namada.shielded_mut().await; @@ -153,44 +153,39 @@ impl Sdk { Ok(()) } - pub async fn add_spending_key(&mut self, xsk: String, alias: String) { + pub async fn add_spending_key(&self, xsk: String, alias: String) { let mut wallet = self.namada.wallet_mut().await; wallet::add_spending_key(&mut wallet, xsk, alias) } - pub async fn add_viewing_key(&mut self, xvk: String, alias: String) { + pub async fn add_viewing_key(&self, xvk: String, alias: String) { let mut wallet = self.namada.wallet_mut().await; wallet::add_viewing_key(&mut wallet, xvk, alias) } - pub async fn add_payment_address(&mut self, pa: String, alias: String) { + pub async fn add_payment_address(&self, pa: String, alias: String) { let mut wallet = self.namada.wallet_mut().await; wallet::add_payment_address(&mut wallet, pa, alias) } - pub async fn add_default_payment_address(&mut self, xvk: String, alias: String) { + pub async fn add_default_payment_address(&self, xvk: String, alias: String) { let mut wallet = self.namada.wallet_mut().await; wallet::add_default_payment_address(&mut wallet, xvk, alias) } - pub async fn add_keypair( - &mut self, - secret_key: String, - alias: String, - password: Option<String>, - ) { + pub async fn add_keypair(&self, secret_key: String, alias: String, password: Option<String>) { let mut wallet = self.namada.wallet_mut().await; wallet::add_keypair(&mut wallet, secret_key, alias, password) } - pub async fn save_wallet(&mut self) -> Result<(), JsValue> { + pub async fn save_wallet(&self) -> Result<(), JsValue> { let wallet = self.namada.wallet_mut().await; wallet.save().map_err(JsError::from)?; Ok(()) } - pub async fn load_wallet(&mut self) -> Result<(), JsValue> { + pub async fn load_wallet(&self) -> Result<(), JsValue> { let mut wallet = self.namada.wallet_mut().await; wallet.load().map_err(JsError::from)?; @@ -198,7 +193,7 @@ impl Sdk { } pub async fn sign_tx( - &mut self, + &self, built_tx: BuiltTx, private_key: Option<String>, chain_id: Option<String>, @@ -252,7 +247,7 @@ impl Sdk { } // Broadcast Tx - pub async fn process_tx(&mut self, tx_bytes: &[u8], tx_msg: &[u8]) -> Result<JsValue, JsError> { + pub async fn process_tx(&self, tx_bytes: &[u8], tx_msg: &[u8]) -> Result<JsValue, JsError> { let args = tx::tx_args_from_slice(tx_msg)?; let tx = Tx::try_from_slice(tx_bytes)?; @@ -264,7 +259,7 @@ impl Sdk { /// Build transaction for specified type, return bytes to client pub async fn build_tx( - &mut self, + &self, tx_type: TxType, specific_msg: &[u8], tx_msg: &[u8], @@ -342,7 +337,7 @@ impl Sdk { } pub async fn build_transfer( - &mut self, + &self, transfer_msg: &[u8], tx_msg: &[u8], _gas_payer: Option<String>, @@ -379,7 +374,7 @@ impl Sdk { } pub async fn build_ibc_transfer( - &mut self, + &self, ibc_transfer_msg: &[u8], tx_msg: &[u8], _gas_payer: Option<String>, @@ -391,7 +386,7 @@ impl Sdk { } pub async fn build_eth_bridge_transfer( - &mut self, + &self, eth_bridge_transfer_msg: &[u8], tx_msg: &[u8], _gas_payer: Option<String>, @@ -403,7 +398,7 @@ impl Sdk { } pub async fn build_vote_proposal( - &mut self, + &self, vote_proposal_msg: &[u8], tx_msg: &[u8], _gas_payer: Option<String>, @@ -418,7 +413,7 @@ impl Sdk { } pub async fn build_bond( - &mut self, + &self, bond_msg: &[u8], tx_msg: &[u8], _gas_payer: Option<String>, @@ -430,7 +425,7 @@ impl Sdk { } pub async fn build_unbond( - &mut self, + &self, unbond_msg: &[u8], tx_msg: &[u8], _gas_payer: Option<String>, @@ -442,7 +437,7 @@ impl Sdk { } pub async fn build_withdraw( - &mut self, + &self, withdraw_msg: &[u8], tx_msg: &[u8], _gas_payer: Option<String>, @@ -454,7 +449,7 @@ impl Sdk { } pub async fn build_redelegate( - &mut self, + &self, redelegate_msg: &[u8], tx_msg: &[u8], _gas_payer: Option<String>, @@ -466,7 +461,7 @@ impl Sdk { } pub async fn build_reveal_pk( - &mut self, + &self, tx_msg: &[u8], _gas_payer: String, ) -> Result<BuiltTx, JsError> { @@ -480,7 +475,7 @@ impl Sdk { // Helper function to reveal public key pub async fn reveal_pk( - &mut self, + &self, signing_key: String, tx_msg: &[u8], chain_id: Option<String>, @@ -494,7 +489,7 @@ impl Sdk { .expect("No public key provided"); let address = Address::from(pk); - if is_reveal_pk_needed(self.namada.client(), &address, false).await? { + if is_reveal_pk_needed(self.namada.client(), &address).await? { let built_tx = self.build_reveal_pk(tx_msg, String::from("")).await?; // Conversion from JsValue so we can use self.sign_tx let tx_bytes = diff --git a/packages/shared/lib/src/sdk/mod.js b/packages/shared/lib/src/sdk/mod.ts similarity index 74% rename from packages/shared/lib/src/sdk/mod.js rename to packages/shared/lib/src/sdk/mod.ts index f9ed7acb7..fe460f988 100644 --- a/packages/shared/lib/src/sdk/mod.js +++ b/packages/shared/lib/src/sdk/mod.ts @@ -1,7 +1,6 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ const PREFIX = "Namada::SDK"; -async function hasMaspParams() { +export async function hasMaspParams(): Promise<boolean> { return ( (await has("masp-spend.params")) && (await has("masp-output.params")) && @@ -9,7 +8,7 @@ async function hasMaspParams() { ); } -async function fetchAndStoreMaspParams() { +export async function fetchAndStoreMaspParams(): Promise<[void, void, void]> { return Promise.all([ fetchAndStore("masp-spend.params"), fetchAndStore("masp-output.params"), @@ -17,7 +16,7 @@ async function fetchAndStoreMaspParams() { ]); } -async function getMaspParams() { +export async function getMaspParams(): Promise<[unknown, unknown, unknown]> { return Promise.all([ get("masp-spend.params"), get("masp-output.params"), @@ -25,12 +24,12 @@ async function getMaspParams() { ]); } -async function fetchAndStore(params) { +export async function fetchAndStore(params: string): Promise<void> { const data = await fetchParams(params); await set(params, data); } -async function fetchParams(params) { +export async function fetchParams(params: string): Promise<Uint8Array> { const path = process.env.NAMADA_INTERFACE_MASP_PARAMS_PATH || "https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup/"; @@ -40,7 +39,7 @@ async function fetchParams(params) { .then((ab) => new Uint8Array(ab)); } -function getDB() { +function getDB(): Promise<IDBDatabase> { return new Promise((resolve, reject) => { const request = indexedDB.open(PREFIX); request.onerror = (event) => { @@ -49,7 +48,8 @@ function getDB() { }; request.onupgradeneeded = (event) => { - const db = event.target.result; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const db = (event.target as any).result; db.createObjectStore(PREFIX, { keyPath: "key" }); }; @@ -60,7 +60,7 @@ function getDB() { }); } -async function get(key) { +export async function get(key: string): Promise<unknown> { const tx = (await getDB()).transaction(PREFIX, "readonly"); const store = tx.objectStore(PREFIX); @@ -81,7 +81,7 @@ async function get(key) { }); } -async function has(key) { +export async function has(key: string): Promise<boolean> { const tx = (await getDB()).transaction(PREFIX, "readonly"); const store = tx.objectStore(PREFIX); @@ -93,13 +93,14 @@ async function has(key) { reject(event.target); }; request.onsuccess = (e) => { - const cursor = e.target.result; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const cursor = (e.target as any).result; resolve(!!cursor); }; }); } -async function set(key, data) { +export async function set(key: string, data: unknown): Promise<void> { const tx = (await getDB()).transaction(PREFIX, "readwrite"); const store = tx.objectStore(PREFIX); @@ -118,12 +119,3 @@ async function set(key, data) { }; }); } - -module.exports = { - PREFIX, - has, - set, - hasMaspParams, - fetchAndStoreMaspParams, - getMaspParams, -}; diff --git a/packages/shared/lib/src/sdk/signature.rs b/packages/shared/lib/src/sdk/signature.rs index 6347f457d..a6933b7de 100644 --- a/packages/shared/lib/src/sdk/signature.rs +++ b/packages/shared/lib/src/sdk/signature.rs @@ -1,7 +1,7 @@ use namada::core::borsh::{BorshDeserialize, BorshSerialize}; use namada::{ key::common::{PublicKey, Signature}, - tx::{CompressedSignature, Section, Signer, Tx}, + tx::{CompressedAuthorization, Section, Signer, Tx}, }; use std::collections::BTreeMap; use wasm_bindgen::JsError; @@ -38,11 +38,11 @@ pub fn construct_signature_section( ) -> Result<Section, JsError> { let signatures = BTreeMap::from([(0, Signature::try_from_slice(signature)?)]); - let compressed_signature = CompressedSignature { + let compressed_signature = CompressedAuthorization { targets: sec_indices.to_vec(), signer: Signer::PubKeys(vec![PublicKey::try_from_slice(pubkey)?]), signatures, }; - Ok(Section::Signature(compressed_signature.expand(&tx))) + Ok(Section::Authorization(compressed_signature.expand(&tx))) } diff --git a/packages/shared/lib/src/sdk/tx.rs b/packages/shared/lib/src/sdk/tx.rs index 0627911a2..a45f96f71 100644 --- a/packages/shared/lib/src/sdk/tx.rs +++ b/packages/shared/lib/src/sdk/tx.rs @@ -11,7 +11,7 @@ use namada::{ ethereum_events::EthAddress, key::common::PublicKey, masp::{ExtendedSpendingKey, PaymentAddress, TransferSource, TransferTarget}, - sdk::args::{self, InputAmount}, + sdk::args::{self, InputAmount, TxExpiration}, token::{Amount, DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}, }; use wasm_bindgen::JsError; @@ -300,15 +300,13 @@ pub fn vote_proposal_tx_args( vote, } = vote_proposal_msg; let tx = tx_msg_into_args(tx_msg)?; - let voter = Address::from_str(&signer)?; + let voter_address = Address::from_str(&signer)?; let args = args::VoteProposal { tx, - proposal_id: Some(proposal_id), - is_offline: false, + proposal_id, vote, - voter, - proposal_data: None, + voter_address, tx_code_path: PathBuf::from("tx_vote_proposal.wasm"), }; @@ -448,6 +446,7 @@ pub fn ibc_transfer_tx_args( timeout_height, timeout_sec_offset, tx_code_path: PathBuf::from("tx_ibc.wasm"), + refund_target: None, }; Ok(args) @@ -537,7 +536,7 @@ fn tx_msg_into_args(tx_msg: &[u8]) -> Result<args::Tx, JsError> { chain_id, public_key, disposable_signing_key, - fee_unshield, + fee_unshield: _fee_unshield, memo, } = tx_msg; @@ -561,12 +560,14 @@ fn tx_msg_into_args(tx_msg: &[u8]) -> Result<args::Tx, JsError> { _ => vec![], }; - let fee_unshield = match fee_unshield { - Some(v) => Some(TransferSource::ExtendedSpendingKey( - ExtendedSpendingKey::from_str(&v)?, - )), - _ => None, - }; + // Support for fee_unshield was removed in 0.36.0 of namada + // Keeping this as a reminder + // let fee_unshield = match fee_unshield { + // Some(v) => Some(TransferSource::ExtendedSpendingKey( + // ExtendedSpendingKey::from_str(&v)? + // )), + // _ => None, + // }; // Ledger address is not used in the SDK. // We can leave it as whatever as long as it's valid url. @@ -586,11 +587,10 @@ fn tx_msg_into_args(tx_msg: &[u8]) -> Result<args::Tx, JsError> { initialized_account_alias: None, fee_amount: Some(fee_input_amount), fee_token: token.clone(), - fee_unshield, gas_limit: GasLimit::from_str(&gas_limit).expect("Gas limit to be valid"), wrapper_fee_payer: None, output_folder: None, - expiration: None, + expiration: TxExpiration::Default, chain_id: Some(ChainId(String::from(chain_id))), signatures: vec![], signing_keys, diff --git a/packages/shared/lib/src/types/masp.rs b/packages/shared/lib/src/types/masp.rs index 2e2da3a04..4a2e30f91 100644 --- a/packages/shared/lib/src/types/masp.rs +++ b/packages/shared/lib/src/types/masp.rs @@ -78,16 +78,6 @@ impl PaymentAddress { Ok(PaymentAddress(payment_address)) } - /// Returns a pinned or non-pinned PaymentAddress - pub fn pinned(&self, pin: bool) -> PaymentAddress { - PaymentAddress(self.0.pinned(pin)) - } - - /// Determine whether this PaymentAddress is pinned - pub fn is_pinned(&self) -> bool { - self.0.is_pinned() - } - /// Retrieve PaymentAddress hash pub fn hash(&self) -> String { self.0.hash() @@ -179,29 +169,12 @@ mod tests { let hash = payment_address.hash(); let expected_address = - "znam1qpjvwgnqt4p3yh6k3daatr0yjw5a4a6t20p5sjfvg8508ew387msz46hev5tfun8h067qfqh3s64t"; + "znam1vnrjyczagvf9745t002cmeyn48d0wj6ncdyyjtzpare7t5flkuq4w47t9z60yeam7hszgyhdw2j"; - let expected_hash = "DBF7C3440E0C0B81EBBB95AD26DA6D875C19BC45"; + let expected_hash = "4E11B97D220F336CF36A14E8DDFE15ED34BC489D"; assert!(address.starts_with("znam")); assert_eq!(address, expected_address); assert_eq!(hash, expected_hash); } - - #[wasm_bindgen_test] - fn can_pin_a_payment_address() { - let encoded_payment_address: &[u8] = &[ - 100, 199, 34, 96, 93, 67, 18, 95, 86, 139, 123, 213, 141, 228, 147, 169, 218, 247, 75, - 83, 195, 72, 73, 44, 65, 232, 243, 229, 209, 63, 183, 1, 87, 87, 203, 40, 180, 242, - 103, 187, 245, 224, 36, - ]; - let payment_address = PaymentAddress::new(encoded_payment_address) - .expect("Instantiating PaymentAddress struct should not fail!"); - - let is_pinned = payment_address.is_pinned(); - assert!(!is_pinned); - - let payment_address = payment_address.pinned(true); - assert!(payment_address.is_pinned()); - } } diff --git a/packages/shared/lib/src/types/query.rs b/packages/shared/lib/src/types/query.rs index 08b7f77b0..086b18d46 100644 --- a/packages/shared/lib/src/types/query.rs +++ b/packages/shared/lib/src/types/query.rs @@ -3,16 +3,13 @@ use namada::core::borsh::BorshSerialize; #[derive(BorshSerialize)] #[borsh(crate = "namada::core::borsh")] pub struct ProposalInfo { - pub id: String, - pub proposal_type: String, + pub id: u64, + pub content: String, pub author: String, pub start_epoch: u64, pub end_epoch: u64, pub grace_epoch: u64, - pub content: String, - pub status: String, - pub result: String, - pub total_voting_power: String, - pub total_yay_power: String, - pub total_nay_power: String, + pub tally_type: String, + pub proposal_type: String, + pub data: Option<String> } diff --git a/packages/shared/package.json b/packages/shared/package.json index 2db1e2478..c9345b3ad 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -8,20 +8,21 @@ "author": "Heliax Dev <info@heliax.dev>", "license": "MIT", "scripts": { - "prepublish": "yarn && yarn build", - "build": "yarn wasm:build && tsc --build", + "prepublish": "yarn wasm:build && tsc --build", "release": "yarn prepublish && release-it --verbose --ci", "release:dry-run": "yarn prepublish && release-it --verbose --dry-run --ci", "release:no-npm": "yarn prepublish && release-it --verbose --no-npm.publish --ci", - "wasm:build": "node ./scripts/build.js --release", - "wasm:build:multicore": "node ./scripts/build.js --release --multicore", - "wasm:build:dev": "node ./scripts/build.js", - "wasm:build:dev:multicore": "node ./scripts/build.js --multicore", - "wasm:build:node": "node ./scripts/build.js --target nodejs --release", - "wasm:build:node:multicore": "node ./scripts/build.js --target nodejs --release --multicore", - "wasm:build:node:dev": "node ./scripts/build.js --target nodejs", - "wasm:build:node:dev:multicore": "node ./scripts/build.js --target node --multicore", - "test-wasm:ci": "cd ./lib && wasm-pack test --node -- --features dev,web --profile dev" + "wasm:ts:node": "tsc -p tsconfig.node.json", + "wasm:ts:web": "tsc -p tsconfig.web.json", + "wasm:build": "yarn wasm:ts:web && node ./scripts/build.js --release", + "wasm:build:multicore": "yarn wasm:ts:web && node ./scripts/build.js --release --multicore", + "wasm:build:dev": "yarn wasm:ts:web && node ./scripts/build.js", + "wasm:build:dev:multicore": "yarn wasm:ts:web && node ./scripts/build.js --multicore", + "wasm:build:node": "yarn wasm:ts:node && node ./scripts/build.js --target nodejs --release", + "test-wasm:ci": "yarn wasm:ts:node && cd ./lib && wasm-pack test --node -- --features nodejs", + "wasm:build:node:multicore": "yarn wasm:ts:node && node ./scripts/build.js --target nodejs --release --multicore", + "wasm:build:node:dev": "yarn wasm:ts:node && node ./scripts/build.js --target nodejs", + "wasm:build:node:dev:multicore": "yarn wasm:ts:node && node ./scripts/build.js --target node --multicore" }, "dependencies": { "@dao-xyz/borsh": "^5.1.5", diff --git a/packages/shared/src/borsh-schemas.ts b/packages/shared/src/borsh-schemas.ts index c7312b5b1..0249a785b 100644 --- a/packages/shared/src/borsh-schemas.ts +++ b/packages/shared/src/borsh-schemas.ts @@ -1,4 +1,4 @@ -import { BinaryReader, BinaryWriter, field, vec } from "@dao-xyz/borsh"; +import { BinaryReader, BinaryWriter, field, option, vec } from "@dao-xyz/borsh"; import BigNumber from "bignumber.js"; export const BigNumberSerializer = { @@ -12,11 +12,11 @@ export const BigNumberSerializer = { }; export class Proposal { - @field({ type: "string" }) - id!: string; + @field({ type: "u64" }) + id!: bigint; @field({ type: "string" }) - proposalType!: "pgf_steward" | "pgf_payment" | "default"; + content!: string; @field({ type: "string" }) author!: string; @@ -31,22 +31,13 @@ export class Proposal { graceEpoch!: bigint; @field({ type: "string" }) - contentJSON!: string; - - @field({ type: "string" }) - status!: "ongoing" | "finished" | "upcoming"; + tallyType!: string; @field({ type: "string" }) - result!: "passed" | "rejected"; - - @field({ type: BigNumberSerializer }) - totalVotingPower!: BigNumber; - - @field({ type: BigNumberSerializer }) - totalYayPower!: BigNumber; + proposalType!: string; - @field({ type: BigNumberSerializer }) - totalNayPower!: BigNumber; + @field({ type: option("string") }) + data?: string; constructor(data: Proposal) { Object.assign(this, data); diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 706a1c4da..91f8c899c 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,5 +1,3 @@ -import { deserialize } from "@dao-xyz/borsh"; - import { Proposal, Proposals } from "./borsh-schemas"; import { Query as RustQuery } from "./shared/shared"; export * from "./shared/shared"; @@ -43,7 +41,6 @@ const promiseWithTimeout = //Fallbacks for rust panics export class Query extends RustQuery { - private _query_proposals = super.query_proposals.bind(this); query_balance = super.query_balance.bind(this); query_epoch = promiseWithTimeout(super.query_epoch.bind(this)); query_all_validator_addresses = promiseWithTimeout( @@ -54,12 +51,6 @@ export class Query extends RustQuery { ); query_total_bonds = promiseWithTimeout(super.query_total_bonds.bind(this)); delegators_votes = promiseWithTimeout(super.delegators_votes.bind(this)); - queryProposals = async (): Promise<Proposal[]> => { - const fn = this._query_proposals; - const serializedProposals = await fn(); - const { proposals } = deserialize(serializedProposals, Proposals); - return proposals; - }; get_total_delegations = promiseWithTimeout( super.get_total_delegations.bind(this) ); diff --git a/packages/shared/src/init-inline.ts b/packages/shared/src/init-inline.ts index 50fd84972..c74930b1d 100644 --- a/packages/shared/src/init-inline.ts +++ b/packages/shared/src/init-inline.ts @@ -1,4 +1,5 @@ import initWasm, { InitOutput } from "./shared/shared"; -import wasm from "./shared/shared_bg.wasm"; +//@ts-expect-error https://vitejs.dev/guide/features#fetching-the-module-in-node-js +import wasm from "./shared/shared_bg.wasm?url"; export const init: () => Promise<InitOutput> = async () => await initWasm(wasm); diff --git a/packages/shared/tsconfig.node.json b/packages/shared/tsconfig.node.json new file mode 100644 index 000000000..2dac094b0 --- /dev/null +++ b/packages/shared/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "include": ["lib/src/**/*.ts"], + "compilerOptions": { + "esModuleInterop": true, + "module": "node16", + "moduleResolution": "node16", + "allowJs": false, + "skipLibCheck": true, + "noEmit": false, + "declaration": false + } +} diff --git a/packages/shared/tsconfig.web.json b/packages/shared/tsconfig.web.json new file mode 100644 index 000000000..f47196d74 --- /dev/null +++ b/packages/shared/tsconfig.web.json @@ -0,0 +1,12 @@ +{ + "include": ["lib/src/**/*.ts"], + "compilerOptions": { + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "allowJs": false, + "skipLibCheck": true, + "noEmit": false, + "declaration": false + } +} diff --git a/packages/types/src/events.ts b/packages/types/src/events.ts index fe97f2283..b4047c294 100644 --- a/packages/types/src/events.ts +++ b/packages/types/src/events.ts @@ -4,11 +4,6 @@ export enum Events { AccountChanged = "namada-account-changed", NetworkChanged = "namada-network-changed", - TxStarted = "namada-tx-started", - TxCompleted = "namada-tx-completed", - UpdatedBalances = "namada-updated-balances", - UpdatedStaking = "namada-updated-staking", - ProposalsUpdated = "namada-proposals-updated", ExtensionLocked = "namada-extension-locked", ConnectionRevoked = "namada-connection-revoked", } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index ae832d43b..ba73cea1e 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -2,6 +2,7 @@ export * from "./account"; export * from "./chain"; export * from "./events"; export * from "./namada"; +export * from "./proposals"; export * from "./signer"; export * from "./tokens"; export * from "./tx"; diff --git a/packages/types/src/proposals.ts b/packages/types/src/proposals.ts new file mode 100644 index 000000000..04276615d --- /dev/null +++ b/packages/types/src/proposals.ts @@ -0,0 +1,121 @@ +import BigNumber from "bignumber.js"; + +export const proposalStatuses = [ + "pending", + "ongoing", + "passed", + "rejected", +] as const; + +export type ProposalStatus = (typeof proposalStatuses)[number]; + +export const isProposalStatus = (str: string): str is ProposalStatus => + proposalStatuses.includes(str as ProposalStatus); + +export type Proposal = { + id: bigint; + author: string; + content: { [key: string]: string | undefined }; + startEpoch: bigint; + endEpoch: bigint; + graceEpoch: bigint; + proposalType: ProposalType; + tallyType: TallyType; + status: ProposalStatus; + totalVotingPower: BigNumber; +} & { + [VT in VoteType]: BigNumber; +}; + +export type ProposalWithExtraInfo = { + proposal: Proposal; +}; + +type ProposalStatusCommonProperties = { + [VT in VoteType]: BigNumber; +} & { + totalVotingPower: BigNumber; +}; + +export type Pending = { + status: "pending"; +} & ProposalStatusCommonProperties; + +export type Ongoing = { + status: "ongoing"; +} & ProposalStatusCommonProperties; + +export type Finished = { + status: "finished"; + passed: boolean; +} & ProposalStatusCommonProperties; + +export type AddRemove = { + add?: string; + remove: string[]; +}; + +// TODO: add IBC target +export type PgfTarget = { + internal: { + amount: BigNumber; + target: string; + }; +}; + +export type PgfActions = { + continuous: { + add: PgfTarget[]; + remove: PgfTarget[]; + }; + retro: PgfTarget[]; +}; + +export type Default = { type: "default"; data?: Uint8Array }; +export type PgfSteward = { type: "pgf_steward"; data: AddRemove }; +export type PgfPayment = { type: "pgf_payment"; data: PgfActions }; +export type ProposalType = Default | PgfSteward | PgfPayment; + +export type ProposalTypeString = ProposalType["type"]; + +export const voteTypes = ["yay", "nay", "abstain"] as const; +export type VoteType = (typeof voteTypes)[number]; + +export const isVoteType = (str: string): str is VoteType => + voteTypes.includes(str as VoteType); + +export type Votes = Record<VoteType, BigNumber>; + +type VoteCommonProperties = { + address: string; + voteType: VoteType; +}; + +export type ValidatorVote = { + isValidator: true; + votingPower: BigNumber; +} & VoteCommonProperties; + +export const isValidatorVote = (vote: Vote): vote is ValidatorVote => + vote.isValidator; + +export type DelegatorVote = { + isValidator: false; + votingPower: [string, BigNumber][]; +} & VoteCommonProperties; + +export const isDelegatorVote = (vote: Vote): vote is DelegatorVote => + !vote.isValidator; + +export type Vote = DelegatorVote | ValidatorVote; + +export const tallyTypes = [ + "two-thirds", + "one-half-over-one-third", + "less-one-half-over-one-third-nay", +] as const; + +export type TallyType = (typeof tallyTypes)[number]; + +export const isTallyType = (tallyType: string): tallyType is TallyType => + tallyTypes.includes(tallyType as TallyType); diff --git a/packages/utils/src/currencies.ts b/packages/utils/src/currencies.ts new file mode 100644 index 000000000..f6466e6f5 --- /dev/null +++ b/packages/utils/src/currencies.ts @@ -0,0 +1,55 @@ +export type CurrencyInfo = { + sign: string; + singular: string; + plural: string; + fraction: string; + fiat: boolean; +}; + +export type CurrencyInfoListItem = { + id: string; +} & CurrencyInfo; + +export const KnownCurrencies: Record<string, CurrencyInfo> = { + usd: { + sign: "$", + singular: "US Dollar", + plural: "US Dollars", + fraction: "cents", + fiat: true, + }, + eur: { + sign: "€", + singular: "Euro", + plural: "Euros", + fraction: "cents", + fiat: true, + }, + nam: { + sign: "NAM", + singular: "NAM", + plural: "NAM", + fraction: "cents", + fiat: false, + }, + jpy: { + sign: "¥", + singular: "Yen", + plural: "Yen", + fraction: "cents", + fiat: true, + }, +}; + +export const CurrencyList: CurrencyInfoListItem[] = Object.keys( + KnownCurrencies +).map((currency) => ({ + id: currency, + ...KnownCurrencies[currency], +})); + +export const FiatCurrencyList = CurrencyList.filter( + (currency) => currency.fiat +); + +export type CurrencyType = keyof typeof KnownCurrencies; diff --git a/packages/utils/src/helpers/index.ts b/packages/utils/src/helpers/index.ts index 0bf4ca7d6..44b2fb540 100644 --- a/packages/utils/src/helpers/index.ts +++ b/packages/utils/src/helpers/index.ts @@ -209,8 +209,20 @@ export function paramsToUrl( return url; } -export const formatPercentage = (bigNumber: BigNumber): string => - bigNumber.multipliedBy(100).toString() + "%"; +export const formatPercentage = ( + bigNumber: BigNumber, + decimalPlaces?: number +): string => { + const percentage = bigNumber.multipliedBy(100); + const rounded = + typeof decimalPlaces === "undefined" ? percentage : ( + percentage.decimalPlaces(decimalPlaces) + ); + return rounded.toString() + "%"; +}; + +export const formatEpoch = (epoch: bigint): string => + `Epoch ${epoch.toString()}`; /** * Applies a function to a value that is possibly undefined. diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index b46343641..b3df192f2 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,3 +1,4 @@ export * from "./async"; +export * from "./currencies"; export * from "./helpers"; export * from "./theme"; diff --git a/storybook/.storybook/main.ts b/storybook/.storybook/main.ts index fda20ead5..241e731ce 100644 --- a/storybook/.storybook/main.ts +++ b/storybook/.storybook/main.ts @@ -1,6 +1,7 @@ import type { StorybookConfig } from "@storybook/react-vite"; import { dirname, join } from "path"; +import { mergeConfig } from "vite"; /** * This function is used to resolve the absolute path of a package. @@ -25,5 +26,10 @@ const config: StorybookConfig = { docs: { autodocs: "tag", }, + async viteFinal(config, { configType }) { + return mergeConfig(config, { + define: { "process.env": {} }, + }); + }, }; export default config; diff --git a/storybook/.storybook/preview.tsx b/storybook/.storybook/preview.tsx index 09828aba3..03702bd26 100644 --- a/storybook/.storybook/preview.tsx +++ b/storybook/.storybook/preview.tsx @@ -1,4 +1,5 @@ import type { Preview } from "@storybook/react"; +import { themes } from "@storybook/theming"; import { StoryWrapper } from "../src/StoryWrapper"; import "../src/styles/tailwind-output.css"; @@ -15,6 +16,9 @@ const preview: Preview = { ], parameters: { actions: { argTypesRegex: "^on[A-Z].*" }, + docs: { + theme: themes.dark, + }, controls: { matchers: { color: /(background|color)$/i, diff --git a/storybook/.yarn/install-state.gz b/storybook/.yarn/install-state.gz index 1926e6a0f..1e6317d14 100644 Binary files a/storybook/.yarn/install-state.gz and b/storybook/.yarn/install-state.gz differ diff --git a/storybook/public/images/cosmostation.png b/storybook/public/images/cosmostation.png new file mode 100644 index 000000000..02ffa1816 Binary files /dev/null and b/storybook/public/images/cosmostation.png differ diff --git a/storybook/public/images/everstake.png b/storybook/public/images/everstake.png new file mode 100644 index 000000000..d13b44c55 Binary files /dev/null and b/storybook/public/images/everstake.png differ diff --git a/storybook/src/components/Cheatsheet.tsx b/storybook/src/components/Cheatsheet.tsx index 4c502a7f7..005c7dcd5 100644 --- a/storybook/src/components/Cheatsheet.tsx +++ b/storybook/src/components/Cheatsheet.tsx @@ -1,4 +1,4 @@ -import { Heading, Panel, Stack } from "@namada/components"; +import { Heading, Stack } from "@namada/components"; import { GoArrowLeft, GoCheckCircle, @@ -10,6 +10,7 @@ import { GoUnlock, } from "react-icons/go"; import { Color } from "./Color"; +import { Panel } from "./Panel"; import { Section } from "./Section"; export const CheatSheet = (): JSX.Element => { diff --git a/storybook/src/components/Panel.tsx b/storybook/src/components/Panel.tsx new file mode 100644 index 000000000..b89387917 --- /dev/null +++ b/storybook/src/components/Panel.tsx @@ -0,0 +1,17 @@ +import clsx from "clsx"; + +type PanelProps = { + children: React.ReactNode; +}; + +export const Panel = ({ children }: PanelProps): JSX.Element => { + return ( + <div + className={clsx( + "relative text-white rounded-lg bg-gray w-[540px] min-h-[640px]" + )} + > + {children} + </div> + ); +}; diff --git a/storybook/src/stories/ActionButton.stories.ts b/storybook/src/stories/ActionButton.stories.ts index 1a461844a..9030dd471 100644 --- a/storybook/src/stories/ActionButton.stories.ts +++ b/storybook/src/stories/ActionButton.stories.ts @@ -10,11 +10,11 @@ const meta: Meta<typeof ActionButton> = { control: { type: "select" }, }, color: { - options: ["primary", "secondary", "black"], + options: ["primary", "secondary", "black", "white"], control: { type: "select" }, }, hoverColor: { - options: ["primary", "secondary", "black"], + options: ["primary", "secondary", "black", "white"], control: { type: "select" }, }, borderRadius: { diff --git a/storybook/src/stories/AmountSummaryCard.stories.tsx b/storybook/src/stories/AmountSummaryCard.stories.tsx new file mode 100644 index 000000000..f0c7e0a38 --- /dev/null +++ b/storybook/src/stories/AmountSummaryCard.stories.tsx @@ -0,0 +1,31 @@ +import { ActionButton, AmountSummaryCard, Image } from "@namada/components"; +import type { StoryObj } from "@storybook/react"; + +export default { + title: "Components/AmountSummaryCard", + component: AmountSummaryCard, + argTypes: {}, +}; + +type Story = StoryObj<typeof AmountSummaryCard>; + +export const Default: Story = { + args: { + as: "div", + logoElement: <Image imageName="LogoMinimal" />, + title: "Available NAM to Stake", + mainAmount: "315 NAM", + alternativeAmount: "$100.34", + className: "bg-rblack border-yellow border", + callToAction: ( + <ActionButton + className="px-8" + borderRadius="sm" + size="xs" + color="primary" + > + Stake + </ActionButton> + ), + }, +}; diff --git a/storybook/src/stories/Currency.stories.tsx b/storybook/src/stories/Currency.stories.tsx new file mode 100644 index 000000000..d52d00bff --- /dev/null +++ b/storybook/src/stories/Currency.stories.tsx @@ -0,0 +1,25 @@ +import { Currency } from "@namada/components"; // Adjust the import path to where your Box component is located +import { Meta, StoryObj } from "@storybook/react"; + +export default { + title: "Components/Currency", + component: Currency, + argTypes: {}, +} as Meta; + +type Story = StoryObj<typeof Currency>; + +export const Default: Story = { + args: { + currency: "nam", + amount: 1000.56, + spaceAroundSign: true, + separator: "", + currencyPosition: "right", + currencySignClassName: "text-xl font-light", + baseAmountClassName: "text-3xl", + fractionClassName: "text-sm opacity-20", + className: "font-medium", + onClick: () => alert("You can use events"), + }, +}; diff --git a/storybook/src/stories/Panel.stories.tsx b/storybook/src/stories/Panel.stories.tsx new file mode 100644 index 000000000..733591ba4 --- /dev/null +++ b/storybook/src/stories/Panel.stories.tsx @@ -0,0 +1,37 @@ +import { Panel } from "@namada/components"; // Adjust the import path to where your Box component is located +import { Meta, StoryObj } from "@storybook/react"; + +export default { + title: "Components/Panel", + component: Panel, + argTypes: { + hierarchy: { + control: { + type: "select", + options: ["h1", "h2", "h3", "h4", "h5", "h6"], + }, + }, + title: { control: "text" }, + children: { control: "text" }, + }, +} as Meta; + +type Story = StoryObj<typeof Panel>; + +export const Default: Story = { + args: { + title: "Default Title", + children: "This is the default content of the Panel.", + }, +}; + +export const HiddenContent: Story = { + args: { + ...Default.args, + children: ( + <p> + This is some custom content, including a <strong>strong tag</strong>. + </p> + ), + }, +}; diff --git a/storybook/src/stories/PieChart.stories.tsx b/storybook/src/stories/PieChart.stories.tsx new file mode 100644 index 000000000..85e2aeed5 --- /dev/null +++ b/storybook/src/stories/PieChart.stories.tsx @@ -0,0 +1,33 @@ +import { PieChart } from "@namada/components"; +import type { StoryFn, StoryObj } from "@storybook/react"; + +export default { + title: "Components/PieChart", + component: PieChart, + argTypes: {}, +}; + +type Story = StoryObj<typeof PieChart>; + +export const Default: Story = { + args: { + data: [ + { value: 100.0, color: "#ffff00" }, + { value: 12, color: "#f0f0f0" }, + { value: 24, color: "#ff00ff" }, + ], + }, + decorators: [ + (Story: StoryFn, context) => { + const children = ( + <div className="text-white"> + <div className="text-2xl">Total Stake Balance</div> + <div className="text-4xl"> + XX <strong>NAM</strong> + </div> + </div> + ); + return <Story args={{ ...context.args, children }} />; + }, + ], +}; diff --git a/storybook/src/stories/StyledSelectBox.stories.tsx b/storybook/src/stories/StyledSelectBox.stories.tsx new file mode 100644 index 000000000..4a92e05ca --- /dev/null +++ b/storybook/src/stories/StyledSelectBox.stories.tsx @@ -0,0 +1,55 @@ +import { StyledSelectBox } from "@namada/components"; +import { useArgs } from "@storybook/preview-api"; +import type { StoryFn, StoryObj } from "@storybook/react"; + +export default { + title: "Components/StyledSelectBox", + component: StyledSelectBox, + argTypes: {}, +}; + +type Story = StoryObj<typeof StyledSelectBox>; + +const getCurrencySymbol = (symbol: string): React.ReactNode => ( + <span className="mr-2 w-6 h-6 rounded-full bg-yellow text-black flex justify-center items-center"> + {symbol} + </span> +); + +export const Default: Story = { + args: { + id: "currency", + options: [ + { + id: "usd", + value: <>{getCurrencySymbol("$")} USD</>, + ariaLabel: "US Dollar", + }, + { + id: "eur", + value: <>{getCurrencySymbol("€")} EUR</>, + ariaLabel: "Euros", + }, + ], + name: "default-select", + value: "usd", + onChange: (e: React.ChangeEvent<HTMLInputElement>) => { + console.log(e.target.value); + }, + }, + decorators: [ + (Story: StoryFn, context) => { + const [, setArgs] = useArgs<typeof context.args>(); + + const onChange = (e: React.ChangeEvent<HTMLInputElement>): void => { + setArgs({ value: e.target.value }); + }; + + return ( + <div className="text-white"> + <Story args={{ ...context.args, onChange }} /> + </div> + ); + }, + ], +}; diff --git a/storybook/src/stories/StyledTable.stories.tsx b/storybook/src/stories/StyledTable.stories.tsx new file mode 100644 index 000000000..dbc81d649 --- /dev/null +++ b/storybook/src/stories/StyledTable.stories.tsx @@ -0,0 +1,117 @@ +import { StyledTable } from "@namada/components"; +import type { StoryObj } from "@storybook/react"; +import cosmostationLogo from "../../public/images/cosmostation.png"; +import everstakeLogo from "../../public/images/everstake.png"; + +export default { + title: "Components/StyledTable", + component: StyledTable, + argTypes: {}, +}; + +type Story = StoryObj<typeof StyledTable>; + +export const Default: Story = { + args: { + tableProps: { className: "w-full" }, + headers: [ + "Validator", + "Address", + <div key="voting-power" className="text-right"> + Voting Power + </div>, + "Comission", + ], + rows: [ + { + cells: [ + "Cosmostation", + "tnamf5jdn...fhth57", + <div className="text-right" key={`row-7000`}> + 7,000,000 NAM + </div>, + "2.0%", + ], + className: + "cursor-pointer transition-all duration-150 hover:text-yellow", + }, + { + cells: [ + "ZK Validator", + "tnamf5jdn...fhth57", + <div className="text-right" key={`row-7000`}> + 7,000,000 NAM + </div>, + , + "2.0%", + ], + className: + "cursor-pointer transition-all duration-150 hover:text-yellow", + }, + ], + }, +}; + +export const WithEmptyTitles: Story = { + args: { + headers: ["", "Validator", "Address", "Voting Power", "Comission", ""], + rows: [ + { + cells: [ + <img src={cosmostationLogo} key="cosmostation-logo" />, + "Cosmostation", + "tnamf5jdn...fhth57", + "7,000,000 NAM", + "340 NAM", + "2.0%", + ">", + ], + }, + { + cells: [ + <img src={everstakeLogo} key="everstake-logo" />, + "Everstake", + "tnamf5jdn...fhth57", + "7,000,000 NAM", + "340 NAM", + "2.0%", + ">", + ], + }, + ], + }, +}; + +export const WithEvents: Story = { + args: { + headers: ["", "Validator", "Address", "Voting Power", "Comission", ""], + rows: [ + { + cells: [ + <img src={cosmostationLogo} key="cosmostation-logo" />, + "Cosmostation", + "tnamf5jdn...fhth57", + "7,000,000 NAM", + "340 NAM", + "2.0%", + ">", + ], + className: "cursor-pointer", + onClick: () => alert("clicked on Cosmostation"), + }, + { + cells: [ + <img src={everstakeLogo} key="everstake-logo" />, + "Everstake", + "tnamf5jdn...fhth57", + "7,000,000 NAM", + "340 NAM", + "2.0%", + ">", + ], + className: "cursor-pointer", + onClick: () => alert("clicked on Everstake"), + }, + ], + }, +}; diff --git a/storybook/src/stories/TickedRadioList.stories.tsx b/storybook/src/stories/TickedRadioList.stories.tsx new file mode 100644 index 000000000..4b4509ef2 --- /dev/null +++ b/storybook/src/stories/TickedRadioList.stories.tsx @@ -0,0 +1,48 @@ +import { TickedRadioList } from "@namada/components"; +import type { StoryObj } from "@storybook/react"; + +export default { + title: "Components/TickedRadioList", + component: TickedRadioList, + argTypes: {}, +}; + +type Story = StoryObj<typeof TickedRadioList>; + +export const Default: Story = { + args: { + id: "currency-list", + value: "USD", + options: [ + { + text: "USD ($)", + value: "USD", + }, + { + text: "EUR (€)", + value: "EUR", + }, + { + text: "GBP (£)", + value: "GBP", + }, + { + text: "PLN (zł)", + value: "PLN", + }, + { + text: "BRL (R$)", + value: "BRL", + }, + { + text: "PHP (₱)", + value: "PHP", + }, + { + text: "JPY (¥)", + value: "JPY", + }, + ], + onChange: () => {}, + }, +}; diff --git a/storybook/src/stories/ToggleButton.stories.tsx b/storybook/src/stories/ToggleButton.stories.tsx new file mode 100644 index 000000000..ce053b7eb --- /dev/null +++ b/storybook/src/stories/ToggleButton.stories.tsx @@ -0,0 +1,18 @@ +import { ToggleButton } from "@namada/components"; // Adjust the import path to where your Box component is located +import { Meta, StoryObj } from "@storybook/react"; + +export default { + title: "Components/ToggleButton", + component: ToggleButton, + argTypes: {}, +} as Meta; + +type Story = StoryObj<typeof ToggleButton>; + +export const Default: Story = { + args: { + label: "Lorem ipsum", + onChange: () => console.log("On change event fired"), + checked: true, + }, +}; diff --git a/tsconfig.base.json b/tsconfig.base.json index 76e219556..e04804ed3 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -33,6 +33,9 @@ "@heliax/namada-sdk/web": ["../../../packages/sdk/src/indexWeb.ts"], "@heliax/namada-sdk/web-init": ["../../../packages/sdk/src/initWeb.ts"], + "@heliax/namada-sdk/inline-init": [ + "../../../packages/sdk/src/initInline.ts" + ], "@heliax/namada-sdk/node": ["../../../packages/sdk/src/indexNode.ts"], "@heliax/namada-sdk/node-init": ["../../../packages/sdk/src/initNode.ts"] diff --git a/yarn.lock b/yarn.lock index c74e1ec30..0dbcfa045 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,10 +12,10 @@ __metadata: languageName: node linkType: hard -"@adraffy/ens-normalize@npm:1.9.2": - version: 1.9.2 - resolution: "@adraffy/ens-normalize@npm:1.9.2" - checksum: 70aca9af28c8d707f6194d3a717582ce8b87087e9c395013f409b2e97b6918a177287bc3dd55c38955f2aa1d6ca604f94e66bfe9aa5651d8aa9979c0896786ee +"@adraffy/ens-normalize@npm:1.10.1": + version: 1.10.1 + resolution: "@adraffy/ens-normalize@npm:1.10.1" + checksum: fdd647604e8fac6204921888aaf5a6bc65eabf0d2921bc5f93b64d01f4bc33ead167c1445f7de05468d05cd92ac31b74c68d2be840c62b79d73693308f885c06 languageName: node linkType: hard @@ -123,6 +123,16 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.24.1, @babel/code-frame@npm:^7.24.2": + version: 7.24.2 + resolution: "@babel/code-frame@npm:7.24.2" + dependencies: + "@babel/highlight": "npm:^7.24.2" + picocolors: "npm:^1.0.0" + checksum: d1d4cba89475ab6aab7a88242e1fd73b15ecb9f30c109b69752956434d10a26a52cbd37727c4eca104b6d45227bd1dfce39a6a6f4a14c9b2f07f871e968cf406 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.13.11, @babel/compat-data@npm:^7.16.8, @babel/compat-data@npm:^7.17.0, @babel/compat-data@npm:^7.17.7": version: 7.17.7 resolution: "@babel/compat-data@npm:7.17.7" @@ -250,6 +260,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.23.5": + version: 7.24.4 + resolution: "@babel/core@npm:7.24.4" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.24.2" + "@babel/generator": "npm:^7.24.4" + "@babel/helper-compilation-targets": "npm:^7.23.6" + "@babel/helper-module-transforms": "npm:^7.23.3" + "@babel/helpers": "npm:^7.24.4" + "@babel/parser": "npm:^7.24.4" + "@babel/template": "npm:^7.24.0" + "@babel/traverse": "npm:^7.24.1" + "@babel/types": "npm:^7.24.0" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: fc136966583e64d6f84f4a676368de6ab4583aa87f867186068655b30ef67f21f8e65a88c6d446a7efd219ad7ffb9185c82e8a90183ee033f6f47b5026641e16 + languageName: node + linkType: hard + "@babel/core@npm:^7.23.7": version: 7.23.7 resolution: "@babel/core@npm:7.23.7" @@ -366,6 +399,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.24.1, @babel/generator@npm:^7.24.4": + version: 7.24.4 + resolution: "@babel/generator@npm:7.24.4" + dependencies: + "@babel/types": "npm:^7.24.0" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^2.5.1" + checksum: 67a1b2f7cc985aaaa11b01e8ddd4fffa4f285837bc7a209738eb8203aa34bdafeb8507ed75fd883ddbabd641a036ca0a8d984e760f28ad4a9d60bff29d0a60bb + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.16.0, @babel/helper-annotate-as-pure@npm:^7.16.7": version: 7.16.7 resolution: "@babel/helper-annotate-as-pure@npm:7.16.7" @@ -1387,6 +1432,17 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.24.4": + version: 7.24.4 + resolution: "@babel/helpers@npm:7.24.4" + dependencies: + "@babel/template": "npm:^7.24.0" + "@babel/traverse": "npm:^7.24.1" + "@babel/types": "npm:^7.24.0" + checksum: 747ef62b7fe87de31a2f3c19ff337a86cbb79be2f6c18af63133b614ab5a8f6da5b06ae4b06fb0e71271cb6a27efec6f8b6c9f44c60b8a18777832dc7929e6c5 + languageName: node + linkType: hard + "@babel/highlight@npm:^7.16.7": version: 7.16.10 resolution: "@babel/highlight@npm:7.16.10" @@ -1431,6 +1487,18 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.24.2": + version: 7.24.2 + resolution: "@babel/highlight@npm:7.24.2" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.22.20" + chalk: "npm:^2.4.2" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 98ce00321daedeed33a4ed9362dc089a70375ff1b3b91228b9f05e6591d387a81a8cba68886e207861b8871efa0bc997ceabdd9c90f6cce3ee1b2f7f941b42db + languageName: node + linkType: hard + "@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.16.7, @babel/parser@npm:^7.17.3, @babel/parser@npm:^7.17.7": version: 7.17.7 resolution: "@babel/parser@npm:7.17.7" @@ -1494,6 +1562,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.24.1, @babel/parser@npm:^7.24.4": + version: 7.24.4 + resolution: "@babel/parser@npm:7.24.4" + bin: + parser: ./bin/babel-parser.js + checksum: 8381e1efead5069cb7ed2abc3a583f4a86289b2f376c75cecc69f59a8eb36df18274b1886cecf2f97a6a0dff5334b27330f58535be9b3e4e26102cc50e12eac8 + languageName: node + linkType: hard + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.16.7": version: 7.16.7 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.16.7" @@ -3575,6 +3652,28 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-jsx-self@npm:^7.23.3": + version: 7.24.1 + resolution: "@babel/plugin-transform-react-jsx-self@npm:7.24.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: ea362ff94b535c753f560eb1f5e063dc72bbbca17ed58837a949a7b289d5eacc7b0a28296d1932c94429b168d6040cdee5484a59b9e3c021f169e0ee137e6a27 + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-source@npm:^7.23.3": + version: 7.24.1 + resolution: "@babel/plugin-transform-react-jsx-source@npm:7.24.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.24.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: ea8e3263c0dc51fbc97c156cc647150a757cc56de10781287353d0ce9b2dcd6b6d93d573c0142d7daf5d6fb554c74fa1971ae60764924ea711161d8458739b63 + languageName: node + linkType: hard + "@babel/plugin-transform-react-jsx@npm:^7.16.7": version: 7.17.3 resolution: "@babel/plugin-transform-react-jsx@npm:7.17.3" @@ -4584,15 +4683,6 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.15.4": - version: 7.17.8 - resolution: "@babel/runtime@npm:7.17.8" - dependencies: - regenerator-runtime: "npm:^0.13.4" - checksum: e06384a648b9b8be6a20fd6185b3d96701cd70c2fd43cec71bda8755d16cd087a8e985a408d9d56a36d4cc07e36167745ce63457f33487199328e800a6c64d48 - languageName: node - linkType: hard - "@babel/runtime@npm:^7.23.5": version: 7.23.6 resolution: "@babel/runtime@npm:7.23.6" @@ -4602,6 +4692,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.24.1": + version: 7.24.5 + resolution: "@babel/runtime@npm:7.24.5" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 05730e43e8ba6550eae9fd4fb5e7d9d3cb91140379425abcb2a1ff9cebad518a280d82c4c4b0f57ada26a863106ac54a748d90c775790c0e2cd0ddd85ccdf346 + languageName: node + linkType: hard + "@babel/template@npm:^7.16.7, @babel/template@npm:^7.3.3": version: 7.16.7 resolution: "@babel/template@npm:7.16.7" @@ -4765,6 +4864,24 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.24.1": + version: 7.24.1 + resolution: "@babel/traverse@npm:7.24.1" + dependencies: + "@babel/code-frame": "npm:^7.24.1" + "@babel/generator": "npm:^7.24.1" + "@babel/helper-environment-visitor": "npm:^7.22.20" + "@babel/helper-function-name": "npm:^7.23.0" + "@babel/helper-hoist-variables": "npm:^7.22.5" + "@babel/helper-split-export-declaration": "npm:^7.22.6" + "@babel/parser": "npm:^7.24.1" + "@babel/types": "npm:^7.24.0" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: c087b918f6823776537ba246136c70e7ce0719fc05361ebcbfd16f4e6f2f6f1f8f4f9167f1d9b675f27d12074839605189cc9d689de20b89a85e7c140f23daab + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.6, @babel/types@npm:^7.16.0, @babel/types@npm:^7.16.7, @babel/types@npm:^7.16.8, @babel/types@npm:^7.17.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": version: 7.17.0 resolution: "@babel/types@npm:7.17.0" @@ -5830,6 +5947,167 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/aix-ppc64@npm:0.20.2" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/android-arm64@npm:0.20.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/android-arm@npm:0.20.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/android-x64@npm:0.20.2" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/darwin-arm64@npm:0.20.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/darwin-x64@npm:0.20.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/freebsd-arm64@npm:0.20.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/freebsd-x64@npm:0.20.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-arm64@npm:0.20.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-arm@npm:0.20.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-ia32@npm:0.20.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-loong64@npm:0.20.2" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-mips64el@npm:0.20.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-ppc64@npm:0.20.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-riscv64@npm:0.20.2" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-s390x@npm:0.20.2" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-x64@npm:0.20.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/netbsd-x64@npm:0.20.2" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/openbsd-x64@npm:0.20.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/sunos-x64@npm:0.20.2" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/win32-arm64@npm:0.20.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/win32-ia32@npm:0.20.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/win32-x64@npm:0.20.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -7597,6 +7875,17 @@ __metadata: languageName: node linkType: hard +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" + dependencies: + "@jridgewell/set-array": "npm:^1.2.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb + languageName: node + linkType: hard + "@jridgewell/resolve-uri@npm:^3.0.3": version: 3.0.5 resolution: "@jridgewell/resolve-uri@npm:3.0.5" @@ -7618,6 +7907,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 + languageName: node + linkType: hard + "@jridgewell/source-map@npm:^0.3.3": version: 0.3.5 resolution: "@jridgewell/source-map@npm:0.3.5" @@ -7635,7 +7931,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.14": +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15": version: 1.4.15 resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" checksum: 0c6b5ae663087558039052a626d2d7ed5208da36cfd707dcc5cea4a07cfc918248403dcb5989a8f7afaf245ce0573b7cc6fd94c4a30453bd10e44d9363940ba5 @@ -7692,7 +7988,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.20": +"@jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: @@ -7859,7 +8155,7 @@ __metadata: languageName: node linkType: hard -"@namada/chains@workspace:packages/chains": +"@namada/chains@npm:0.2.1, @namada/chains@workspace:packages/chains": version: 0.0.0-use.local resolution: "@namada/chains@workspace:packages/chains" dependencies: @@ -7877,16 +8173,17 @@ __metadata: version: 0.0.0-use.local resolution: "@namada/components@workspace:packages/components" dependencies: + "@namada/utils": "npm:0.2.1" "@types/react": "npm:^17.0.39" "@types/styled-components": "npm:^5.1.26" autoprefixer: "npm:^10.4.16" + bignumber.js: "npm:^9.1.1" clsx: "npm:^2.0.0" eslint: "npm:^8.49.0" eslint-config-prettier: "npm:^8.8.0" eslint-plugin-import: "npm:^2.27.5" eslint-plugin-react: "npm:^7.33.0" eslint-plugin-react-hooks: "npm:^4.6.0" - react: "npm:^17.0.2" react-icons: "npm:^4.12.0" styled-components: "npm:^5.3.5" tailwind-merge: "npm:^2.1.0" @@ -7894,6 +8191,7 @@ __metadata: typescript: "npm:^5.1.3" peerDependencies: postcss: ^8.4.32 + react: ^18.0.0 tailwindcss: ^3.3.6 languageName: unknown linkType: soft @@ -7997,8 +8295,8 @@ __metadata: postcss: "npm:^8.4.32" postcss-loader: "npm:^7.3.3" postcss-preset-env: "npm:^9.3.0" - react: "npm:^17.0.2" - react-dom: "npm:^17.0.2" + react: "npm:^18.0.0" + react-dom: "npm:^18.0.0" react-icons: "npm:^4.12.0" react-router-dom: "npm:^6.0.0" remove-files-webpack-plugin: "npm:^1.5.0" @@ -8065,15 +8363,20 @@ __metadata: version: 0.0.0-use.local resolution: "@namada/hooks@workspace:packages/hooks" dependencies: + "@namada/chains": "npm:0.2.1" + "@namada/types": "npm:0.2.1" + "@namada/utils": "npm:0.2.1" "@types/react": "npm:^17.0.39" eslint: "npm:^8.49.0" eslint-config-prettier: "npm:^8.8.0" eslint-plugin-import: "npm:^2.27.5" eslint-plugin-react: "npm:^7.33.0" eslint-plugin-react-hooks: "npm:^4.6.0" - react: "npm:^17.0.2" + isomorphic-dompurify: "npm:^2.6.0" rimraf: "npm:^5.0.5" typescript: "npm:^5.1.3" + peerDependencies: + react: ^18.0.0 languageName: unknown linkType: soft @@ -8104,33 +8407,29 @@ __metadata: languageName: unknown linkType: soft -"@namada/namada-interface@workspace:apps/namada-interface": +"@namada/namadillo@workspace:apps/namadillo": version: 0.0.0-use.local - resolution: "@namada/namada-interface@workspace:apps/namada-interface" + resolution: "@namada/namadillo@workspace:apps/namadillo" dependencies: "@playwright/test": "npm:^1.24.1" - "@reduxjs/toolkit": "npm:^1.8.0" "@svgr/webpack": "npm:^6.5.1" "@testing-library/jest-dom": "npm:^5.16.2" "@testing-library/react": "npm:^12.1.3" "@testing-library/user-event": "npm:^13.5.0" - "@types/dompurify": "npm:^3.0.2" + "@types/invariant": "npm:^2.2.37" "@types/jest": "npm:^29.4.0" + "@types/lodash.debounce": "npm:^4.0.9" "@types/node": "npm:^16.11.25" "@types/react": "npm:^17.0.39" "@types/react-dom": "npm:^17.0.11" - "@types/react-modal": "npm:^3.13.1" "@types/react-paginate": "npm:^7.1.2" - "@types/react-qr-reader": "npm:^2.1.4" - "@types/redux-mock-store": "npm:^1.0.3" "@types/styled-components": "npm:^5.1.22" + "@vitejs/plugin-react": "npm:^4.2.1" autoprefixer: "npm:^10.4.16" - babel-plugin-styled-components: "npm:^2.0.3" bignumber.js: "npm:^9.1.1" - buffer: "npm:^6.0.3" + clsx: "npm:^2.1.1" crypto-browserify: "npm:^3.12.0" css-loader: "npm:^6.7.3" - dompurify: "npm:^3.0.2" dotenv: "npm:^16.0.3" eslint: "npm:^8.49.0" eslint-config-prettier: "npm:^8.8.0" @@ -8140,34 +8439,26 @@ __metadata: eslint-plugin-react-hooks: "npm:^4.6.0" ethers: "npm:^6.7.1" fp-ts: "npm:^2.16.1" - framer-motion: "npm:^6.2.8" + framer-motion: "npm:^11.0.28" history: "npm:^5.3.0" html-webpack-plugin: "npm:^5.5.0" + invariant: "npm:^2.2.4" jest: "npm:^29.4.1" jest-fetch-mock: "npm:^3.0.3" jotai: "npm:^2.6.3" - jotai-redux: "npm:^0.2.1" local-cors-proxy: "npm:^1.1.0" - next-qrcode: "npm:^2.0.0" - path: "npm:^0.12.7" + lodash.debounce: "npm:^4.0.8" postcss: "npm:^8.4.32" postcss-loader: "npm:^7.3.3" - prettier: "npm:^2.5.1" - react: "npm:^17.0.2" - react-dom: "npm:^17.0.2" - react-modal: "npm:^3.15.1" + react: "npm:^18.0.0" + react-dom: "npm:^18.0.0" + react-icons: "npm:^5.1.0" react-paginate: "npm:^8.2.0" - react-qr-reader: "npm:2.1.2" - react-redux: "npm:^7.2.6" react-router-dom: "npm:^6.0.0" - react-scripts: "npm:5.0.0" - redux-mock-store: "npm:^1.5.4" - redux-persist: "npm:^6.0.0" - redux-persist-transform-encrypt: "npm:^3.0.1" - redux-thunk: "npm:^2.4.1" - stream: "npm:^0.0.2" + react-scripts: "npm:5.0.1" style-loader: "npm:^3.3.1" styled-components: "npm:^5.3.3" + tailwind-merge: "npm:^2.3.0" tailwindcss: "npm:^3.4.0" ts-jest: "npm:^29.0.5" ts-loader: "npm:^9.4.2" @@ -8175,6 +8466,9 @@ __metadata: tsconfig-paths-webpack-plugin: "npm:^4.1.0" typescript: "npm:^5.1.3" typescript-plugin-styled-components: "npm:^2.0.0" + vite: "npm:^5.2.11" + vite-plugin-node-polyfills: "npm:^0.22.0" + vite-tsconfig-paths: "npm:^4.3.2" web-vitals: "npm:^2.1.4" webpack-bundle-analyzer: "npm:^4.10.1" webpack-cli: "npm:^5.0.1" @@ -8223,7 +8517,7 @@ __metadata: languageName: unknown linkType: soft -"@namada/types@workspace:packages/types": +"@namada/types@npm:0.2.1, @namada/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@namada/types@workspace:packages/types" dependencies: @@ -8244,7 +8538,7 @@ __metadata: languageName: unknown linkType: soft -"@namada/utils@workspace:packages/utils": +"@namada/utils@npm:0.2.1, @namada/utils@workspace:packages/utils": version: 0.0.0-use.local resolution: "@namada/utils@workspace:packages/utils" dependencies: @@ -8279,10 +8573,19 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.1.2": - version: 1.1.2 - resolution: "@noble/hashes@npm:1.1.2" - checksum: 452a197522dabd163cf5297fe7b768fabba73072a198752074da6fce7c1438c7f614b27891391e9f6b118842656a4da4c0fc04e464ba1e15f306291d05dd106a +"@noble/curves@npm:1.2.0": + version: 1.2.0 + resolution: "@noble/curves@npm:1.2.0" + dependencies: + "@noble/hashes": "npm:1.3.2" + checksum: 0bac7d1bbfb3c2286910b02598addd33243cb97c3f36f987ecc927a4be8d7d88e0fcb12b0f0ef8a044e7307d1844dd5c49bb724bfa0a79c8ec50ba60768c97f6 + languageName: node + linkType: hard + +"@noble/hashes@npm:1.3.2": + version: 1.3.2 + resolution: "@noble/hashes@npm:1.3.2" + checksum: 2482cce3bce6a596626f94ca296e21378e7a5d4c09597cbc46e65ffacc3d64c8df73111f2265444e36a3168208628258bbbaccba2ef24f65f58b2417638a20e7 languageName: node linkType: hard @@ -8293,13 +8596,6 @@ __metadata: languageName: node linkType: hard -"@noble/secp256k1@npm:1.7.1": - version: 1.7.1 - resolution: "@noble/secp256k1@npm:1.7.1" - checksum: 48091801d39daba75520012027d0ff0b1719338d96033890cfe0d287ad75af00d82769c0194a06e7e4fbd816ae3f204f4a59c9e26f0ad16b429f7e9b5403ccd5 - languageName: node - linkType: hard - "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -8706,26 +9002,6 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:^1.8.0": - version: 1.8.0 - resolution: "@reduxjs/toolkit@npm:1.8.0" - dependencies: - immer: "npm:^9.0.7" - redux: "npm:^4.1.2" - redux-thunk: "npm:^2.4.1" - reselect: "npm:^4.1.5" - peerDependencies: - react: ^16.9.0 || ^17.0.0 || 18.0.0-beta - react-redux: ^7.2.1 || ^8.0.0-beta - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - checksum: ad23c9cb9d9156c100732fa81b134126db2eeb1decc8f993b93e8c39f4696af90431d4abdc2d6cf7cf32da880c1a848caae87d088e9bc29f047d15805e37f311 - languageName: node - linkType: hard - "@release-it/conventional-changelog@npm:^8.0.1": version: 8.0.1 resolution: "@release-it/conventional-changelog@npm:8.0.1" @@ -8764,6 +9040,22 @@ __metadata: languageName: node linkType: hard +"@rollup/plugin-inject@npm:^5.0.5": + version: 5.0.5 + resolution: "@rollup/plugin-inject@npm:5.0.5" + dependencies: + "@rollup/pluginutils": "npm:^5.0.1" + estree-walker: "npm:^2.0.2" + magic-string: "npm:^0.30.3" + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 22d10cf44fa56a6683d5ac4df24a9003379b3dcaae9897f5c30c844afc2ebca83cfaa5557f13a1399b1c8a0d312c3217bcacd508b7ebc4b2cbee401bd1ec8be2 + languageName: node + linkType: hard + "@rollup/plugin-node-resolve@npm:^11.2.1": version: 11.2.1 resolution: "@rollup/plugin-node-resolve@npm:11.2.1" @@ -8805,6 +9097,134 @@ __metadata: languageName: node linkType: hard +"@rollup/pluginutils@npm:^5.0.1": + version: 5.1.0 + resolution: "@rollup/pluginutils@npm:5.1.0" + dependencies: + "@types/estree": "npm:^1.0.0" + estree-walker: "npm:^2.0.2" + picomatch: "npm:^2.3.1" + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: c7bed15711f942d6fdd3470fef4105b73991f99a478605e13d41888963330a6f9e32be37e6ddb13f012bc7673ff5e54f06f59fd47109436c1c513986a8a7612d + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.16.4" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-android-arm64@npm:4.16.4" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-darwin-arm64@npm:4.16.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-darwin-x64@npm:4.16.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.16.4" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.16.4" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.16.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.16.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.16.4" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.16.4" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.16.4" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.16.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.16.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.16.4" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.16.4" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.16.4": + version: 4.16.4 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.16.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rushstack/eslint-patch@npm:^1.1.0": version: 1.1.1 resolution: "@rushstack/eslint-patch@npm:1.1.1" @@ -9400,6 +9820,22 @@ __metadata: languageName: node linkType: hard +"@tailwindcss/container-queries@npm:^0.1.1": + version: 0.1.1 + resolution: "@tailwindcss/container-queries@npm:0.1.1" + peerDependencies: + tailwindcss: ">=3.2.0" + checksum: 336546ddcc60280723f2a92e311cac0acb8b05624c6519675d4b11ae13ed01dba5e622705a9be69cd66ca2c8019032551176546e6920d9634750165ac4c15d8e + languageName: node + linkType: hard + +"@tanstack/query-core@npm:^5.32.0": + version: 5.32.0 + resolution: "@tanstack/query-core@npm:5.32.0" + checksum: 58e7b053579a23dba36189a1fd87261819b31b344648f693fd613c722260066e11ad8c82dc07f5e5af2e276bde1b9f6a1dd6da3bf93cdd3583d128a2b373fce0 + languageName: node + linkType: hard + "@testing-library/dom@npm:^8.0.0": version: 8.11.3 resolution: "@testing-library/dom@npm:8.11.3" @@ -9543,6 +9979,19 @@ __metadata: languageName: node linkType: hard +"@types/babel__core@npm:^7.20.5": + version: 7.20.5 + resolution: "@types/babel__core@npm:7.20.5" + dependencies: + "@babel/parser": "npm:^7.20.7" + "@babel/types": "npm:^7.20.7" + "@types/babel__generator": "npm:*" + "@types/babel__template": "npm:*" + "@types/babel__traverse": "npm:*" + checksum: bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff + languageName: node + linkType: hard + "@types/babel__generator@npm:*": version: 7.6.4 resolution: "@types/babel__generator@npm:7.6.4" @@ -9673,6 +10122,15 @@ __metadata: languageName: node linkType: hard +"@types/dompurify@npm:^3.0.5": + version: 3.0.5 + resolution: "@types/dompurify@npm:3.0.5" + dependencies: + "@types/trusted-types": "npm:*" + checksum: a34dcc4498ca250815ccf9aecbe82df96ba5db247d0440cf266a876757d47c52519c240db3475e794d7deb0d6b1af23328e02879be368ad0e26b20c0f0865dba + languageName: node + linkType: hard + "@types/eslint-scope@npm:^3.7.3": version: 3.7.3 resolution: "@types/eslint-scope@npm:3.7.3" @@ -9717,7 +10175,7 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5": +"@types/estree@npm:1.0.5, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5": version: 1.0.5 resolution: "@types/estree@npm:1.0.5" checksum: b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d @@ -9786,7 +10244,7 @@ __metadata: languageName: node linkType: hard -"@types/hoist-non-react-statics@npm:*, @types/hoist-non-react-statics@npm:^3.3.0": +"@types/hoist-non-react-statics@npm:*": version: 3.3.1 resolution: "@types/hoist-non-react-statics@npm:3.3.1" dependencies: @@ -9833,6 +10291,13 @@ __metadata: languageName: node linkType: hard +"@types/invariant@npm:^2.2.37": + version: 2.2.37 + resolution: "@types/invariant@npm:2.2.37" + checksum: f57ed8445036ebda8bc93804f088c2a13050bbeef4e4bc6ed531a70e2869250dbe59413f2a9ed7d8f3efa960f191e8dfca9d25414d63cbf604d348428f8c5b75 + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -10007,6 +10472,22 @@ __metadata: languageName: node linkType: hard +"@types/lodash.debounce@npm:^4.0.9": + version: 4.0.9 + resolution: "@types/lodash.debounce@npm:4.0.9" + dependencies: + "@types/lodash": "npm:*" + checksum: 9fbb24e5e52616faf60ba5c82d8c6517f4b86fc6e9ab353b4c56c0760f63d9bf53af3f2d8f6c37efa48090359fb96dba1087d497758511f6c40677002191d042 + languageName: node + linkType: hard + +"@types/lodash@npm:*": + version: 4.17.0 + resolution: "@types/lodash@npm:4.17.0" + checksum: 4c5b41c9a6c41e2c05d08499e96f7940bcf194dcfa84356235b630da920c2a5e05f193618cea76006719bec61c76617dff02defa9d29934f9f6a76a49291bd8f + languageName: node + linkType: hard + "@types/long@npm:^4.0.1": version: 4.0.2 resolution: "@types/long@npm:4.0.2" @@ -10207,15 +10688,6 @@ __metadata: languageName: node linkType: hard -"@types/react-modal@npm:^3.13.1": - version: 3.13.1 - resolution: "@types/react-modal@npm:3.13.1" - dependencies: - "@types/react": "npm:*" - checksum: 58b39870509d65ecd5998365bf2e59062da29db6c525135db50e8eab4e7ecf8d81ee331e34d16a889a47b49a8e2888252b66f2365817ae613ccc32f2ddea983a - languageName: node - linkType: hard - "@types/react-paginate@npm:^7.1.2": version: 7.1.2 resolution: "@types/react-paginate@npm:7.1.2" @@ -10225,27 +10697,6 @@ __metadata: languageName: node linkType: hard -"@types/react-qr-reader@npm:^2.1.4": - version: 2.1.4 - resolution: "@types/react-qr-reader@npm:2.1.4" - dependencies: - "@types/react": "npm:*" - checksum: baa954efd22b9fd7882e56d4596aec6106dbbfdbae2d257e39b26265e199cebb59903667b542217f26e9f0b44a2febabae88c8a98c9e1487c2131fb6956e2fc3 - languageName: node - linkType: hard - -"@types/react-redux@npm:^7.1.20": - version: 7.1.23 - resolution: "@types/react-redux@npm:7.1.23" - dependencies: - "@types/hoist-non-react-statics": "npm:^3.3.0" - "@types/react": "npm:*" - hoist-non-react-statics: "npm:^3.3.0" - redux: "npm:^4.0.0" - checksum: db8919f92022fe067c8303aff1a4e0486be69b48cf547b60e41a9cb5c56da0c1e91eae6a34892bf5532e47e1bf58fe02ec995bdecebea3f8e79e52c1357958a1 - languageName: node - linkType: hard - "@types/react@npm:*, @types/react@npm:^17.0.39": version: 17.0.40 resolution: "@types/react@npm:17.0.40" @@ -10257,15 +10708,6 @@ __metadata: languageName: node linkType: hard -"@types/redux-mock-store@npm:^1.0.3": - version: 1.0.3 - resolution: "@types/redux-mock-store@npm:1.0.3" - dependencies: - redux: "npm:^4.0.5" - checksum: 5027e4ecd654efc8b98418c41e9286c257db54206943ee2f541d751f6579e740b733597d58d1a79df133da0693f8c34772e6bed85c5ef6d9ae8636d342d7df88 - languageName: node - linkType: hard - "@types/resolve@npm:1.17.1": version: 1.17.1 resolution: "@types/resolve@npm:1.17.1" @@ -10804,6 +11246,21 @@ __metadata: languageName: node linkType: hard +"@vitejs/plugin-react@npm:^4.2.1": + version: 4.2.1 + resolution: "@vitejs/plugin-react@npm:4.2.1" + dependencies: + "@babel/core": "npm:^7.23.5" + "@babel/plugin-transform-react-jsx-self": "npm:^7.23.3" + "@babel/plugin-transform-react-jsx-source": "npm:^7.23.3" + "@types/babel__core": "npm:^7.20.5" + react-refresh: "npm:^0.14.0" + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + checksum: de1eec44d703f32e5b58e776328ca20793657fe991835d15b290230b19a2a08be5d31501d424279ae13ecfed28044c117b69d746891c8d9b92c69e8a8907e989 + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.11.1": version: 1.11.1 resolution: "@webassemblyjs/ast@npm:1.11.1" @@ -11738,7 +12195,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.1, ansi-escapes@npm:^4.3.2": +"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.1, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -12165,6 +12622,19 @@ __metadata: languageName: node linkType: hard +"assert@npm:^2.0.0": + version: 2.1.0 + resolution: "assert@npm:2.1.0" + dependencies: + call-bind: "npm:^1.0.2" + is-nan: "npm:^1.3.2" + object-is: "npm:^1.1.5" + object.assign: "npm:^4.1.4" + util: "npm:^0.12.5" + checksum: 7271a5da883c256a1fa690677bf1dd9d6aa882139f2bed1cd15da4f9e7459683e1da8e32a203d6cc6767e5e0f730c77a9532a87b896b4b0af0dd535f668775f0 + languageName: node + linkType: hard + "ast-types-flow@npm:^0.0.7": version: 0.0.7 resolution: "ast-types-flow@npm:0.0.7" @@ -12286,6 +12756,15 @@ __metadata: languageName: node linkType: hard +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: "npm:^1.0.0" + checksum: d07226ef4f87daa01bd0fe80f8f310982e345f372926da2e5296aecc25c41cab440916bbaa4c5e1034b453af3392f67df5961124e4b586df1e99793a1374bdb2 + languageName: node + linkType: hard + "aws-sign2@npm:~0.7.0": version: 0.7.0 resolution: "aws-sign2@npm:0.7.0" @@ -12722,7 +13201,7 @@ __metadata: languageName: node linkType: hard -"babel-plugin-styled-components@npm:>= 1.12.0, babel-plugin-styled-components@npm:^2.0.3": +"babel-plugin-styled-components@npm:>= 1.12.0": version: 2.0.6 resolution: "babel-plugin-styled-components@npm:2.0.6" dependencies: @@ -13198,6 +13677,15 @@ __metadata: languageName: node linkType: hard +"browser-resolve@npm:^2.0.0": + version: 2.0.0 + resolution: "browser-resolve@npm:2.0.0" + dependencies: + resolve: "npm:^1.17.0" + checksum: 06c43adf3cb1939825ab9a4ac355b23272820ee421a20d04f62e0dabd9ea305e497b97f3ac027f87d53c366483aafe8673bbe1aaa5e41cd69eeafa65ac5fda6e + languageName: node + linkType: hard + "browserify-aes@npm:^1.0.0, browserify-aes@npm:^1.0.4": version: 1.2.0 resolution: "browserify-aes@npm:1.2.0" @@ -13262,6 +13750,15 @@ __metadata: languageName: node linkType: hard +"browserify-zlib@npm:^0.2.0": + version: 0.2.0 + resolution: "browserify-zlib@npm:0.2.0" + dependencies: + pako: "npm:~1.0.5" + checksum: 9ab10b6dc732c6c5ec8ebcbe5cb7fe1467f97402c9b2140113f47b5f187b9438f93a8e065d8baf8b929323c18324fbf1105af479ee86d9d36cab7d7ef3424ad9 + languageName: node + linkType: hard + "browserslist@npm:^4.0.0, browserslist@npm:^4.14.5, browserslist@npm:^4.16.6, browserslist@npm:^4.17.5, browserslist@npm:^4.18.1, browserslist@npm:^4.19.1, browserslist@npm:^4.19.3, browserslist@npm:^4.20.2": version: 4.20.2 resolution: "browserslist@npm:4.20.2" @@ -13400,7 +13897,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.2.1, buffer@npm:^5.5.0": +"buffer@npm:^5.2.1, buffer@npm:^5.5.0, buffer@npm:^5.7.1": version: 5.7.1 resolution: "buffer@npm:5.7.1" dependencies: @@ -13434,6 +13931,13 @@ __metadata: languageName: node linkType: hard +"builtin-status-codes@npm:^3.0.0": + version: 3.0.0 + resolution: "builtin-status-codes@npm:3.0.0" + checksum: c37bbba11a34c4431e56bd681b175512e99147defbe2358318d8152b3a01df7bf25e0305873947e5b350073d5ef41a364a22b37e48f1fb6d2fe6d5286a0f348c + languageName: node + linkType: hard + "bundle-name@npm:^3.0.0": version: 3.0.0 resolution: "bundle-name@npm:3.0.0" @@ -13692,7 +14196,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": +"chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -13808,7 +14312,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.4.0": +"chokidar@npm:^3.4.0, chokidar@npm:^3.5.1": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -13996,17 +14500,6 @@ __metadata: languageName: node linkType: hard -"cliui@npm:^6.0.0": - version: 6.0.0 - resolution: "cliui@npm:6.0.0" - dependencies: - string-width: "npm:^4.2.0" - strip-ansi: "npm:^6.0.0" - wrap-ansi: "npm:^6.2.0" - checksum: 35229b1bb48647e882104cac374c9a18e34bbf0bace0e2cf03000326b6ca3050d6b59545d91e17bfe3705f4a0e2988787aa5cde6331bf5cbbf0164732cef6492 - languageName: node - linkType: hard - "cliui@npm:^7.0.2": version: 7.0.4 resolution: "cliui@npm:7.0.4" @@ -14054,6 +14547,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + "co-body@npm:^6.0.0": version: 6.1.0 resolution: "co-body@npm:6.1.0" @@ -14275,7 +14775,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^8.3.0": +"commander@npm:^8.0.0, commander@npm:^8.3.0": version: 8.3.0 resolution: "commander@npm:8.3.0" checksum: 8b043bb8322ea1c39664a1598a95e0495bfe4ca2fad0d84a92d7d1d8d213e2a155b441d2470c8e08de7c4a28cf2bc6e169211c49e1b21d9f7edc6ae4d9356060 @@ -14452,6 +14952,20 @@ __metadata: languageName: node linkType: hard +"console-browserify@npm:^1.1.0": + version: 1.2.0 + resolution: "console-browserify@npm:1.2.0" + checksum: 89b99a53b7d6cee54e1e64fa6b1f7ac24b844b4019c5d39db298637e55c1f4ffa5c165457ad984864de1379df2c8e1886cbbdac85d9dbb6876a9f26c3106f226 + languageName: node + linkType: hard + +"constants-browserify@npm:^1.0.0": + version: 1.0.0 + resolution: "constants-browserify@npm:1.0.0" + checksum: ab49b1d59a433ed77c964d90d19e08b2f77213fb823da4729c0baead55e3c597f8f97ebccfdfc47bd896d43854a117d114c849a6f659d9986420e97da0f83ac5 + languageName: node + linkType: hard + "content-disposition@npm:0.5.4, content-disposition@npm:~0.5.2": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" @@ -14946,7 +15460,7 @@ __metadata: languageName: node linkType: hard -"create-require@npm:^1.1.0": +"create-require@npm:^1.1.0, create-require@npm:^1.1.1": version: 1.1.1 resolution: "create-require@npm:1.1.1" checksum: 157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 @@ -15002,7 +15516,7 @@ __metadata: languageName: node linkType: hard -"crypto-browserify@npm:^3.12.0": +"crypto-browserify@npm:^3.11.0, crypto-browserify@npm:^3.12.0": version: 3.12.0 resolution: "crypto-browserify@npm:3.12.0" dependencies: @@ -15021,13 +15535,6 @@ __metadata: languageName: node linkType: hard -"crypto-js@npm:3.1.9-1": - version: 3.1.9-1 - resolution: "crypto-js@npm:3.1.9-1" - checksum: f68af7121c35ae4f50030f3f33f9268a104b0ded727e31c7303bee72bde2e6131d27df72ae778a7565b93ac828ba30bd6b0c1c4acb5d5c5590001090ac2f818f - languageName: node - linkType: hard - "crypto-js@npm:^4.1.1": version: 4.1.1 resolution: "crypto-js@npm:4.1.1" @@ -15456,6 +15963,15 @@ __metadata: languageName: node linkType: hard +"cssstyle@npm:^4.0.1": + version: 4.0.1 + resolution: "cssstyle@npm:4.0.1" + dependencies: + rrweb-cssom: "npm:^0.6.0" + checksum: cadf9a8b23e11f4c6d63f21291096a0b0be868bd4ab9c799daa2c5b18330e39e5281605f01da906e901b42f742df0f3b3645af6465e83377ff7d15a88ee432a0 + languageName: node + linkType: hard + "csstype@npm:^3.0.2": version: 3.0.11 resolution: "csstype@npm:3.0.11" @@ -15539,6 +16055,16 @@ __metadata: languageName: node linkType: hard +"data-urls@npm:^5.0.0": + version: 5.0.0 + resolution: "data-urls@npm:5.0.0" + dependencies: + whatwg-mimetype: "npm:^4.0.0" + whatwg-url: "npm:^14.0.0" + checksum: 1b894d7d41c861f3a4ed2ae9b1c3f0909d4575ada02e36d3d3bc584bdd84278e20709070c79c3b3bff7ac98598cb191eb3e86a89a79ea4ee1ef360e1694f92ad + languageName: node + linkType: hard + "debounce@npm:1.2.1, debounce@npm:^1.2.1": version: 1.2.1 resolution: "debounce@npm:1.2.1" @@ -15616,6 +16142,13 @@ __metadata: languageName: node linkType: hard +"decimal.js@npm:^10.4.3": + version: 10.4.3 + resolution: "decimal.js@npm:10.4.3" + checksum: 6d60206689ff0911f0ce968d40f163304a6c1bc739927758e6efc7921cfa630130388966f16bf6ef6b838cb33679fbe8e7a78a2f3c478afce841fd55ac8fb8ee + languageName: node + linkType: hard + "decode-uri-component@npm:^0.2.0": version: 0.2.0 resolution: "decode-uri-component@npm:0.2.0" @@ -15811,6 +16344,17 @@ __metadata: languageName: node linkType: hard +"define-properties@npm:^1.2.1": + version: 1.2.1 + resolution: "define-properties@npm:1.2.1" + dependencies: + define-data-property: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.0" + object-keys: "npm:^1.1.1" + checksum: 88a152319ffe1396ccc6ded510a3896e77efac7a1bfbaa174a7b00414a1747377e0bb525d303794a47cf30e805c2ec84e575758512c6e44a993076d29fd4e6c3 + languageName: node + linkType: hard + "defined@npm:^1.0.0": version: 1.0.0 resolution: "defined@npm:1.0.0" @@ -16028,13 +16572,6 @@ __metadata: languageName: node linkType: hard -"dijkstrajs@npm:^1.0.1": - version: 1.0.2 - resolution: "dijkstrajs@npm:1.0.2" - checksum: 323e10c46a163a2b8ff6a357f967cff4392981d351de9077d131469d02edfe9cd38ce079bbb58c8cf99e6925a75a3b22269d1886381a8eab1daf0d2e39191728 - languageName: node - linkType: hard - "dir-glob@npm:^2.0.0": version: 2.2.2 resolution: "dir-glob@npm:2.2.2" @@ -16181,6 +16718,13 @@ __metadata: languageName: node linkType: hard +"domain-browser@npm:^4.22.0": + version: 4.23.0 + resolution: "domain-browser@npm:4.23.0" + checksum: dfcc6ba070a2c968a4d922e7d99ef440d1076812af0d983404aadf64729f746bb4a0ad2c5e73ccd5d9cf41bc79037f2a1e4a915bdf33d07e0d77f487b635b5b2 + languageName: node + linkType: hard + "domelementtype@npm:1": version: 1.3.1 resolution: "domelementtype@npm:1.3.1" @@ -16254,6 +16798,13 @@ __metadata: languageName: node linkType: hard +"dompurify@npm:^3.1.0": + version: 3.1.0 + resolution: "dompurify@npm:3.1.0" + checksum: 8adbcc6de954bd0486ae033489c6b52f5aece356963734b82b6d28f9f6ba79a846c4d23085679c0423e81fd8ac8f804dc8c2dfcc0e03e86db2cf0da3e33ad481 + languageName: node + linkType: hard + "domutils@npm:^1.7.0": version: 1.7.0 resolution: "domutils@npm:1.7.0" @@ -16439,13 +16990,6 @@ __metadata: languageName: node linkType: hard -"emitter-component@npm:^1.1.1": - version: 1.1.1 - resolution: "emitter-component@npm:1.1.1" - checksum: dfe379e5444c7313b3dfad4e62cc0ac4d3be523ab527fe5b0e18649088d9012f2247b53af7b7dc6a3d7de958c0bad6f211117bc579895ccd2d623882a9ceb7a3 - languageName: node - linkType: hard - "emittery@npm:^0.10.2": version: 0.10.2 resolution: "emittery@npm:0.10.2" @@ -16502,13 +17046,6 @@ __metadata: languageName: node linkType: hard -"encode-utf8@npm:^1.0.3": - version: 1.0.3 - resolution: "encode-utf8@npm:1.0.3" - checksum: 6b3458b73e868113d31099d7508514a5c627d8e16d1e0542d1b4e3652299b8f1f590c468e2b9dcdf1b4021ee961f31839d0be9d70a7f2a8a043c63b63c9b3a88 - languageName: node - linkType: hard - "encodeurl@npm:^1.0.2, encodeurl@npm:~1.0.2": version: 1.0.2 resolution: "encodeurl@npm:1.0.2" @@ -16859,6 +17396,86 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.20.1": + version: 0.20.2 + resolution: "esbuild@npm:0.20.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.20.2" + "@esbuild/android-arm": "npm:0.20.2" + "@esbuild/android-arm64": "npm:0.20.2" + "@esbuild/android-x64": "npm:0.20.2" + "@esbuild/darwin-arm64": "npm:0.20.2" + "@esbuild/darwin-x64": "npm:0.20.2" + "@esbuild/freebsd-arm64": "npm:0.20.2" + "@esbuild/freebsd-x64": "npm:0.20.2" + "@esbuild/linux-arm": "npm:0.20.2" + "@esbuild/linux-arm64": "npm:0.20.2" + "@esbuild/linux-ia32": "npm:0.20.2" + "@esbuild/linux-loong64": "npm:0.20.2" + "@esbuild/linux-mips64el": "npm:0.20.2" + "@esbuild/linux-ppc64": "npm:0.20.2" + "@esbuild/linux-riscv64": "npm:0.20.2" + "@esbuild/linux-s390x": "npm:0.20.2" + "@esbuild/linux-x64": "npm:0.20.2" + "@esbuild/netbsd-x64": "npm:0.20.2" + "@esbuild/openbsd-x64": "npm:0.20.2" + "@esbuild/sunos-x64": "npm:0.20.2" + "@esbuild/win32-arm64": "npm:0.20.2" + "@esbuild/win32-ia32": "npm:0.20.2" + "@esbuild/win32-x64": "npm:0.20.2" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 66398f9fb2c65e456a3e649747b39af8a001e47963b25e86d9c09d2a48d61aa641b27da0ce5cad63df95ad246105e1d83e7fee0e1e22a0663def73b1c5101112 + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -16956,9 +17573,9 @@ __metadata: languageName: node linkType: hard -"eslint-config-react-app@npm:^7.0.0": - version: 7.0.0 - resolution: "eslint-config-react-app@npm:7.0.0" +"eslint-config-react-app@npm:^7.0.1": + version: 7.0.1 + resolution: "eslint-config-react-app@npm:7.0.1" dependencies: "@babel/core": "npm:^7.16.0" "@babel/eslint-parser": "npm:^7.16.3" @@ -16976,7 +17593,7 @@ __metadata: eslint-plugin-testing-library: "npm:^5.0.1" peerDependencies: eslint: ^8.0.0 - checksum: 0b343633673c1fbb8d11ea7dcc303e9753249bf0edfb3154daad407376e0a75da3176cff724b5f2beca24542a06762a58aa51dbf6a3c3f56b2959f3a13ff4dc4 + checksum: be290ec0cd5a2c0bb0b85cb1645e8734769cae77f101cd453631d77a60fa4894ee8b5b1e080ee8c21e01af0d0fc22367a2882931a549691b5ab801abb985cbba languageName: node linkType: hard @@ -17615,6 +18232,13 @@ __metadata: languageName: node linkType: hard +"estree-walker@npm:^2.0.2": + version: 2.0.2 + resolution: "estree-walker@npm:2.0.2" + checksum: 53a6c54e2019b8c914dc395890153ffdc2322781acf4bd7d1a32d7aedc1710807bdcd866ac133903d5629ec601fbb50abe8c2e5553c7f5a0afdd9b6af6c945af + languageName: node + linkType: hard + "esutils@npm:^2.0.2": version: 2.0.3 resolution: "esutils@npm:2.0.3" @@ -17639,17 +18263,17 @@ __metadata: linkType: hard "ethers@npm:^6.7.1": - version: 6.7.1 - resolution: "ethers@npm:6.7.1" + version: 6.12.1 + resolution: "ethers@npm:6.12.1" dependencies: - "@adraffy/ens-normalize": "npm:1.9.2" - "@noble/hashes": "npm:1.1.2" - "@noble/secp256k1": "npm:1.7.1" + "@adraffy/ens-normalize": "npm:1.10.1" + "@noble/curves": "npm:1.2.0" + "@noble/hashes": "npm:1.3.2" "@types/node": "npm:18.15.13" aes-js: "npm:4.0.0-beta.5" tslib: "npm:2.4.0" ws: "npm:8.5.0" - checksum: 1f5a4a024f865637bbc2c6d3383558ff9b62bf87a30d3159e3cc96d7b91e39e42483875edeff4ff0e30f3bea50d286598e2587d1652a0fcec0f8a8ad054a6d7f + checksum: 7686e1efdb0a831578f35d69188783c225de5a6fbb1b422327bc45cee04d49a2707e73c9342a6a5eb2870ce35668c71372737439ec3993d31d83f4a0e2446cc7 languageName: node linkType: hard @@ -17689,7 +18313,7 @@ __metadata: languageName: node linkType: hard -"events@npm:^3.2.0, events@npm:^3.3.0": +"events@npm:^3.0.0, events@npm:^3.2.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" checksum: d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 @@ -17807,13 +18431,6 @@ __metadata: languageName: node linkType: hard -"exenv@npm:^1.2.0": - version: 1.2.2 - resolution: "exenv@npm:1.2.2" - checksum: 4e96b355a6b9b9547237288ca779dd673b2e698458b409e88b50df09feb7c85ef94c07354b6b87bc3ed0193a94009a6f7a3c71956da12f45911c0d0f5aa3caa0 - languageName: node - linkType: hard - "exit@npm:^0.1.2": version: 0.1.2 resolution: "exit@npm:0.1.2" @@ -18119,7 +18736,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2": +"fast-glob@npm:^3.2.7, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" dependencies: @@ -18649,23 +19266,23 @@ __metadata: languageName: node linkType: hard -"framer-motion@npm:^6.2.8": - version: 6.2.8 - resolution: "framer-motion@npm:6.2.8" +"framer-motion@npm:^11.0.28": + version: 11.1.7 + resolution: "framer-motion@npm:11.1.7" dependencies: - "@emotion/is-prop-valid": "npm:^0.8.2" - framesync: "npm:6.0.1" - hey-listen: "npm:^1.0.8" - popmotion: "npm:11.0.3" - style-value-types: "npm:5.0.0" - tslib: "npm:^2.1.0" + tslib: "npm:^2.4.0" peerDependencies: - react: ">=16.8 || ^17.0.0 || ^18.0.0" - react-dom: ">=16.8 || ^17.0.0 || ^18.0.0" - dependenciesMeta: + "@emotion/is-prop-valid": "*" + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: "@emotion/is-prop-valid": optional: true - checksum: 148ee1be89f214f3d4d2d0ce5592b850a5577974673a4efe8528f50ff6722e9855cf8e4fdc32036981a0d55c95d474e1ac23380634da1f2fb481b7243da66454 + react: + optional: true + react-dom: + optional: true + checksum: 33ab19674170b88df2ceed655d95851e23ff270a0239a2bc84f464f6ae9632868d2c1b440e1be0187cd99cedd88ed22fab1e6234f67c40a2d215bd99c15dad31 languageName: node linkType: hard @@ -18721,6 +19338,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.1.0": + version: 11.2.0 + resolution: "fs-extra@npm:11.2.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: d77a9a9efe60532d2e790e938c81a02c1b24904ef7a3efb3990b835514465ba720e99a6ea56fd5e2db53b4695319b644d76d5a0e9988a2beef80aa7b1da63398 + languageName: node + linkType: hard + "fs-extra@npm:^8.1.0": version: 8.1.0 resolution: "fs-extra@npm:8.1.0" @@ -18812,6 +19440,16 @@ __metadata: languageName: node linkType: hard +"fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin<compat/fsevents>, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin<compat/fsevents>": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin<compat/fsevents>::version=2.3.2&hash=df0bf1" @@ -18821,6 +19459,15 @@ __metadata: languageName: node linkType: hard +"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin<compat/fsevents>": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin<compat/fsevents>::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + "function-bind@npm:^1.1.1": version: 1.1.1 resolution: "function-bind@npm:1.1.1" @@ -19607,6 +20254,15 @@ __metadata: languageName: node linkType: hard +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + "has-yarn@npm:^3.0.0": version: 3.0.0 resolution: "has-yarn@npm:3.0.0" @@ -19689,7 +20345,7 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2": +"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.3.0": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -19753,6 +20409,15 @@ __metadata: languageName: node linkType: hard +"html-encoding-sniffer@npm:^4.0.0": + version: 4.0.0 + resolution: "html-encoding-sniffer@npm:4.0.0" + dependencies: + whatwg-encoding: "npm:^3.1.1" + checksum: 523398055dc61ac9b34718a719cb4aa691e4166f29187e211e1607de63dc25ac7af52ca7c9aead0c4b3c0415ffecb17326396e1202e2e86ff4bca4c0ee4c6140 + languageName: node + linkType: hard + "html-entities@npm:^2.1.0, html-entities@npm:^2.3.2": version: 2.3.2 resolution: "html-entities@npm:2.3.2" @@ -20015,6 +20680,13 @@ __metadata: languageName: node linkType: hard +"https-browserify@npm:^1.0.0": + version: 1.0.0 + resolution: "https-browserify@npm:1.0.0" + checksum: e17b6943bc24ea9b9a7da5714645d808670af75a425f29baffc3284962626efdc1eb3aa9bbffaa6e64028a6ad98af5b09fabcb454a8f918fb686abfdc9e9b8ae + languageName: node + linkType: hard + "https-proxy-agent@npm:^5.0.0": version: 5.0.0 resolution: "https-proxy-agent@npm:5.0.0" @@ -20353,6 +21025,15 @@ __metadata: languageName: node linkType: hard +"invariant@npm:^2.2.4": + version: 2.2.4 + resolution: "invariant@npm:2.2.4" + dependencies: + loose-envify: "npm:^1.0.0" + checksum: 5af133a917c0bcf65e84e7f23e779e7abc1cd49cb7fdc62d00d1de74b0d8c1b5ee74ac7766099fb3be1b05b26dfc67bab76a17030d2fe7ea2eef867434362dfc + languageName: node + linkType: hard + "invert-kv@npm:^3.0.0": version: 3.0.1 resolution: "invert-kv@npm:3.0.1" @@ -20707,6 +21388,16 @@ __metadata: languageName: node linkType: hard +"is-nan@npm:^1.3.2": + version: 1.3.2 + resolution: "is-nan@npm:1.3.2" + dependencies: + call-bind: "npm:^1.0.0" + define-properties: "npm:^1.1.3" + checksum: 8bfb286f85763f9c2e28ea32e9127702fe980ffd15fa5d63ade3be7786559e6e21355d3625dd364c769c033c5aedf0a2ed3d4025d336abf1b9241e3d9eddc5b0 + languageName: node + linkType: hard + "is-negative-zero@npm:^2.0.1, is-negative-zero@npm:^2.0.2": version: 2.0.2 resolution: "is-negative-zero@npm:2.0.2" @@ -20928,6 +21619,15 @@ __metadata: languageName: node linkType: hard +"is-typed-array@npm:^1.1.3": + version: 1.1.13 + resolution: "is-typed-array@npm:1.1.13" + dependencies: + which-typed-array: "npm:^1.1.14" + checksum: fa5cb97d4a80e52c2cc8ed3778e39f175a1a2ae4ddf3adae3187d69586a1fd57cfa0b095db31f66aa90331e9e3da79184cea9c6abdcd1abc722dc3c3edd51cca + languageName: node + linkType: hard + "is-typedarray@npm:^1.0.0, is-typedarray@npm:~1.0.0": version: 1.0.0 resolution: "is-typedarray@npm:1.0.0" @@ -21030,6 +21730,24 @@ __metadata: languageName: node linkType: hard +"isomorphic-dompurify@npm:^2.6.0": + version: 2.7.0 + resolution: "isomorphic-dompurify@npm:2.7.0" + dependencies: + "@types/dompurify": "npm:^3.0.5" + dompurify: "npm:^3.1.0" + jsdom: "npm:^24.0.0" + checksum: 56c38021ae7d4a44a78b12cfbcad74f078fd85d9758f81ee7c7f9b145a9ac2e341228bfcdb78278549e68672e1977e4ccbfa062f9962cecc25b41e7fa1a2be6d + languageName: node + linkType: hard + +"isomorphic-timers-promises@npm:^1.0.1": + version: 1.0.1 + resolution: "isomorphic-timers-promises@npm:1.0.1" + checksum: 3b4761d0012ebe6b6382246079fc667f3513f36fe4042638f2bfb7db1557e4f1acd33a9c9907706c04270890ec6434120f132f3f300161a42a7dd8628926c8a4 + languageName: node + linkType: hard + "isomorphic-ws@npm:^4.0.1": version: 4.0.1 resolution: "isomorphic-ws@npm:4.0.1" @@ -24100,12 +24818,14 @@ __metadata: languageName: node linkType: hard -"jotai-redux@npm:^0.2.1": - version: 0.2.1 - resolution: "jotai-redux@npm:0.2.1" +"jotai-tanstack-query@npm:^0.8.5": + version: 0.8.5 + resolution: "jotai-tanstack-query@npm:0.8.5" peerDependencies: - jotai: ">=1.11.0" - checksum: 36c4696fa7c983b10f125e3851267c74b8a728cf766bfb8bcb16d75ed74068fc52b2a1f08ae280293b5a40556db80d7ac5e6472fbdc1adbff754137d1ccfb8fa + "@tanstack/query-core": "*" + jotai: ">=2.0.0" + wonka: ^6.3.4 + checksum: 761a9ed4b98b7c7899b11ffd94d29e1f3d8844b15f8c4175743e30a3ab99bd1ab672983c6b8b490c51fa166688c8f8575e9a9bf7b69be78c1a6dbc1907f1934a languageName: node linkType: hard @@ -24419,6 +25139,40 @@ __metadata: languageName: node linkType: hard +"jsdom@npm:^24.0.0": + version: 24.0.0 + resolution: "jsdom@npm:24.0.0" + dependencies: + cssstyle: "npm:^4.0.1" + data-urls: "npm:^5.0.0" + decimal.js: "npm:^10.4.3" + form-data: "npm:^4.0.0" + html-encoding-sniffer: "npm:^4.0.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.2" + is-potential-custom-element-name: "npm:^1.0.1" + nwsapi: "npm:^2.2.7" + parse5: "npm:^7.1.2" + rrweb-cssom: "npm:^0.6.0" + saxes: "npm:^6.0.0" + symbol-tree: "npm:^3.2.4" + tough-cookie: "npm:^4.1.3" + w3c-xmlserializer: "npm:^5.0.0" + webidl-conversions: "npm:^7.0.0" + whatwg-encoding: "npm:^3.1.1" + whatwg-mimetype: "npm:^4.0.0" + whatwg-url: "npm:^14.0.0" + ws: "npm:^8.16.0" + xml-name-validator: "npm:^5.0.0" + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + checksum: 7b35043d7af39ad6dcaef0fa5679d8c8a94c6c9b6cc4a79222b7c9987d57ab7150c50856684ae56b473ab28c7d82aec0fb7ca19dcbd4c3f46683c807d717a3af + languageName: node + linkType: hard + "jsesc@npm:^2.5.1": version: 2.5.2 resolution: "jsesc@npm:2.5.2" @@ -24639,13 +25393,6 @@ __metadata: languageName: node linkType: hard -"jsqr@npm:^1.1.1": - version: 1.4.0 - resolution: "jsqr@npm:1.4.0" - checksum: 69fbfe4c866a04c97b377901a166544a583bfc76b838c275efa9af058d64e5612267079b1e96ea7b6434385803571b1c6a97a43c85f4373e8afa4f4034fc916c - languageName: node - linkType: hard - "jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.2.1": version: 3.2.1 resolution: "jsx-ast-utils@npm:3.2.1" @@ -25353,6 +26100,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.3": + version: 0.30.10 + resolution: "magic-string@npm:0.30.10" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.4.15" + checksum: aa9ca17eae571a19bce92c8221193b6f93ee8511abb10f085e55ffd398db8e4c089a208d9eac559deee96a08b7b24d636ea4ab92f09c6cf42a7d1af51f7fd62b + languageName: node + linkType: hard + "make-dir@npm:^2.1.0": version: 2.1.0 resolution: "make-dir@npm:2.1.0" @@ -26028,11 +26784,15 @@ __metadata: resolution: "namada@workspace:." dependencies: "@release-it/conventional-changelog": "npm:^8.0.1" + "@tailwindcss/container-queries": "npm:^0.1.1" + "@tanstack/query-core": "npm:^5.32.0" "@typescript-eslint/eslint-plugin": "npm:^6.13.0" "@typescript-eslint/parser": "npm:^6.13.0" eslint: "npm:^8.49.0" git-commit-msg-linter: "npm:^5.0.4" husky: "npm:^8.0.3" + invariant: "npm:^2.2.4" + jotai-tanstack-query: "npm:^0.8.5" jsdoc-to-markdown: "npm:^8.0.1" lint-staged: "npm:^15.2.0" prettier: "npm:^3.1.0" @@ -26040,6 +26800,8 @@ __metadata: release-it: "npm:^17.0.1" rimraf: "npm:^5.0.5" stream-browserify: "npm:^3.0.0" + vite-plugin-checker: "npm:^0.6.4" + wonka: "npm:^6.3.4" wsrun: "npm:^5.2.4" languageName: unknown linkType: soft @@ -26126,19 +26888,6 @@ __metadata: languageName: node linkType: hard -"next-qrcode@npm:^2.0.0": - version: 2.0.0 - resolution: "next-qrcode@npm:2.0.0" - dependencies: - qrcode: "npm:^1.4.4" - peerDependencies: - react: ^17.0.2 - react-dom: ^17.0.2 - react-scripts: ^4.0.3 - checksum: 5986862f7eec544bf53503384515c9167450724064b7a5fb1ca2071d6d1e3c8b62473bc6f8d476e35e2de792b33aaa3f0c604957a8c129c579f021cb81e7cd06 - languageName: node - linkType: hard - "nice-try@npm:^1.0.4": version: 1.0.5 resolution: "nice-try@npm:1.0.5" @@ -26289,6 +27038,41 @@ __metadata: languageName: node linkType: hard +"node-stdlib-browser@npm:^1.2.0": + version: 1.2.0 + resolution: "node-stdlib-browser@npm:1.2.0" + dependencies: + assert: "npm:^2.0.0" + browser-resolve: "npm:^2.0.0" + browserify-zlib: "npm:^0.2.0" + buffer: "npm:^5.7.1" + console-browserify: "npm:^1.1.0" + constants-browserify: "npm:^1.0.0" + create-require: "npm:^1.1.1" + crypto-browserify: "npm:^3.11.0" + domain-browser: "npm:^4.22.0" + events: "npm:^3.0.0" + https-browserify: "npm:^1.0.0" + isomorphic-timers-promises: "npm:^1.0.1" + os-browserify: "npm:^0.3.0" + path-browserify: "npm:^1.0.1" + pkg-dir: "npm:^5.0.0" + process: "npm:^0.11.10" + punycode: "npm:^1.4.1" + querystring-es3: "npm:^0.2.1" + readable-stream: "npm:^3.6.0" + stream-browserify: "npm:^3.0.0" + stream-http: "npm:^3.2.0" + string_decoder: "npm:^1.0.0" + timers-browserify: "npm:^2.0.4" + tty-browserify: "npm:0.0.1" + url: "npm:^0.11.0" + util: "npm:^0.12.4" + vm-browserify: "npm:^1.0.1" + checksum: 4da239ebabcba68e09b2620aaae02dd589045b101441beb90988bc60f1af3d286e9fab0c334503eaf74986e583923e7648a8fa081edc4981e4d738636773f32e + languageName: node + linkType: hard + "nopt@npm:^7.0.0": version: 7.2.0 resolution: "nopt@npm:7.2.0" @@ -26408,6 +27192,13 @@ __metadata: languageName: node linkType: hard +"nwsapi@npm:^2.2.7": + version: 2.2.9 + resolution: "nwsapi@npm:2.2.9" + checksum: e6ebbaedf44d1c1e13f7193e5129c8da1b2e8064862b70458ab9bd9e9640b8ad035a0e48d509e787527ecdddea74d5a02798420cd971264a4e03c2b173fadac8 + languageName: node + linkType: hard + "oauth-sign@npm:~0.9.0": version: 0.9.0 resolution: "oauth-sign@npm:0.9.0" @@ -26474,6 +27265,16 @@ __metadata: languageName: node linkType: hard +"object-is@npm:^1.1.5": + version: 1.1.6 + resolution: "object-is@npm:1.1.6" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + checksum: 506af444c4dce7f8e31f34fc549e2fb8152d6b9c4a30c6e62852badd7f520b579c679af433e7a072f9d78eb7808d230dc12e1cf58da9154dfbf8813099ea0fe0 + languageName: node + linkType: hard + "object-keys@npm:^1.0.12, object-keys@npm:^1.1.1": version: 1.1.1 resolution: "object-keys@npm:1.1.1" @@ -26801,6 +27602,13 @@ __metadata: languageName: node linkType: hard +"os-browserify@npm:^0.3.0": + version: 0.3.0 + resolution: "os-browserify@npm:0.3.0" + checksum: 6ff32cb1efe2bc6930ad0fd4c50e30c38010aee909eba8d65be60af55efd6cbb48f0287e3649b4e3f3a63dce5a667b23c187c4293a75e557f0d5489d735bcf52 + languageName: node + linkType: hard + "os-homedir@npm:^1.0.0, os-homedir@npm:^1.0.1": version: 1.0.2 resolution: "os-homedir@npm:1.0.2" @@ -27047,7 +27855,7 @@ __metadata: languageName: node linkType: hard -"pako@npm:1.0.11, pako@npm:~1.0.2": +"pako@npm:1.0.11, pako@npm:~1.0.2, pako@npm:~1.0.5": version: 1.0.11 resolution: "pako@npm:1.0.11" checksum: 86dd99d8b34c3930345b8bbeb5e1cd8a05f608eeb40967b293f72fe469d0e9c88b783a8777e4cc7dc7c91ce54c5e93d88ff4b4f060e6ff18408fd21030d9ffbe @@ -27174,6 +27982,15 @@ __metadata: languageName: node linkType: hard +"parse5@npm:^7.1.2": + version: 7.1.2 + resolution: "parse5@npm:7.1.2" + dependencies: + entities: "npm:^4.4.0" + checksum: 297d7af8224f4b5cb7f6617ecdae98eeaed7f8cbd78956c42785e230505d5a4f07cef352af10d3006fa5c1544b76b57784d3a22d861ae071bbc460c649482bf4 + languageName: node + linkType: hard + "parseurl@npm:^1.3.2, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" @@ -27191,6 +28008,13 @@ __metadata: languageName: node linkType: hard +"path-browserify@npm:^1.0.1": + version: 1.0.1 + resolution: "path-browserify@npm:1.0.1" + checksum: 8b8c3fd5c66bd340272180590ae4ff139769e9ab79522e2eb82e3d571a89b8117c04147f65ad066dccfb42fcad902e5b7d794b3d35e0fd840491a8ddbedf8c66 + languageName: node + linkType: hard + "path-exists@npm:^3.0.0": version: 3.0.0 resolution: "path-exists@npm:3.0.0" @@ -27294,16 +28118,6 @@ __metadata: languageName: node linkType: hard -"path@npm:^0.12.7": - version: 0.12.7 - resolution: "path@npm:0.12.7" - dependencies: - process: "npm:^0.11.1" - util: "npm:^0.10.3" - checksum: f795ce5438a988a590c7b6dfd450ec9baa1c391a8be4c2dea48baa6e0f5b199e56cd83b8c9ebf3991b81bea58236d2c32bdafe2c17a2e70c3a2e4c69891ade59 - languageName: node - linkType: hard - "pause-stream@npm:0.0.11": version: 0.0.11 resolution: "pause-stream@npm:0.0.11" @@ -27468,6 +28282,15 @@ __metadata: languageName: node linkType: hard +"pkg-dir@npm:^5.0.0": + version: 5.0.0 + resolution: "pkg-dir@npm:5.0.0" + dependencies: + find-up: "npm:^5.0.0" + checksum: 793a496d685dc55bbbdbbb22d884535c3b29241e48e3e8d37e448113a71b9e42f5481a61fdc672d7322de12fbb2c584dd3a68bf89b18fffce5c48a390f911bc5 + languageName: node + linkType: hard + "pkg-up@npm:^3.1.0": version: 3.1.0 resolution: "pkg-up@npm:3.1.0" @@ -27486,13 +28309,6 @@ __metadata: languageName: node linkType: hard -"pngjs@npm:^5.0.0": - version: 5.0.0 - resolution: "pngjs@npm:5.0.0" - checksum: c074d8a94fb75e2defa8021e85356bf7849688af7d8ce9995b7394d57cd1a777b272cfb7c4bce08b8d10e71e708e7717c81fd553a413f21840c548ec9d4893c6 - languageName: node - linkType: hard - "popmotion@npm:11.0.3": version: 11.0.3 resolution: "popmotion@npm:11.0.3" @@ -27516,6 +28332,13 @@ __metadata: languageName: node linkType: hard +"possible-typed-array-names@npm:^1.0.0": + version: 1.0.0 + resolution: "possible-typed-array-names@npm:1.0.0" + checksum: d9aa22d31f4f7680e20269db76791b41c3a32c01a373e25f8a4813b4d45f7456bfc2b6d68f752dc4aab0e0bb0721cb3d76fb678c9101cb7a16316664bc2c73fd + languageName: node + linkType: hard + "postcss-attribute-case-insensitive@npm:^5.0.0": version: 5.0.0 resolution: "postcss-attribute-case-insensitive@npm:5.0.0" @@ -28794,6 +29617,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.38": + version: 8.4.38 + resolution: "postcss@npm:8.4.38" + dependencies: + nanoid: "npm:^3.3.7" + picocolors: "npm:^1.0.0" + source-map-js: "npm:^1.2.0" + checksum: 955407b8f70cf0c14acf35dab3615899a2a60a26718a63c848cf3c29f2467b0533991b985a2b994430d890bd7ec2b1963e36352b0774a19143b5f591540f7c06 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -28825,15 +29659,6 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^2.5.1": - version: 2.6.0 - resolution: "prettier@npm:2.6.0" - bin: - prettier: bin-prettier.js - checksum: aa29412e52be673e1e63ceb17eb6bbbec5a2d6fd126f66c31a935a2d35463f8ba28bc54b20cb1e89712cb3928ef60d27f53174e99a5222fbd414ba654929d693 - languageName: node - linkType: hard - "prettier@npm:^3.1.0": version: 3.1.0 resolution: "prettier@npm:3.1.0" @@ -28981,7 +29806,7 @@ __metadata: languageName: node linkType: hard -"process@npm:^0.11.1, process@npm:^0.11.10": +"process@npm:^0.11.10": version: 0.11.10 resolution: "process@npm:0.11.10" checksum: 40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3 @@ -29054,7 +29879,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15, prop-types@npm:^15.5.8, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -29232,6 +30057,13 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^1.4.1": + version: 1.4.1 + resolution: "punycode@npm:1.4.1" + checksum: 354b743320518aef36f77013be6e15da4db24c2b4f62c5f1eb0529a6ed02fbaf1cb52925785f6ab85a962f2b590d9cd5ad730b70da72b5f180e2556b8bd3ca08 + languageName: node + linkType: hard + "punycode@npm:^2.1.0, punycode@npm:^2.1.1": version: 2.1.1 resolution: "punycode@npm:2.1.1" @@ -29239,6 +30071,13 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^2.3.1": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + "pupa@npm:^3.1.0": version: 3.1.0 resolution: "pupa@npm:3.1.0" @@ -29317,20 +30156,6 @@ __metadata: languageName: node linkType: hard -"qrcode@npm:^1.4.4": - version: 1.5.0 - resolution: "qrcode@npm:1.5.0" - dependencies: - dijkstrajs: "npm:^1.0.1" - encode-utf8: "npm:^1.0.3" - pngjs: "npm:^5.0.0" - yargs: "npm:^15.3.1" - bin: - qrcode: bin/qrcode - checksum: bd3c74fa00d729a32a5b161ef51dafacf1973a2936a234b1b780a53639d0290397ca36fa01ffd10f55b448e7baf9320bc3c47b08090d0aac0fc2dec7f094f4a9 - languageName: node - linkType: hard - "qs@npm:6.11.0": version: 6.11.0 resolution: "qs@npm:6.11.0" @@ -29347,6 +30172,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.11.2": + version: 6.12.1 + resolution: "qs@npm:6.12.1" + dependencies: + side-channel: "npm:^1.0.6" + checksum: 439e6d7c6583e7c69f2cab2c39c55b97db7ce576e4c7c469082b938b7fc8746e8d547baacb69b4cd2b6666484776c3f4840ad7163a4c5326300b0afa0acdd84b + languageName: node + linkType: hard + "qs@npm:^6.4.0": version: 6.11.2 resolution: "qs@npm:6.11.2" @@ -29372,6 +30206,13 @@ __metadata: languageName: node linkType: hard +"querystring-es3@npm:^0.2.1": + version: 0.2.1 + resolution: "querystring-es3@npm:0.2.1" + checksum: 476938c1adb45c141f024fccd2ffd919a3746e79ed444d00e670aad68532977b793889648980e7ca7ff5ffc7bfece623118d0fbadcaf217495eeb7059ae51580 + languageName: node + linkType: hard + "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -29515,9 +30356,9 @@ __metadata: languageName: node linkType: hard -"react-dev-utils@npm:^12.0.0": - version: 12.0.0 - resolution: "react-dev-utils@npm:12.0.0" +"react-dev-utils@npm:^12.0.1": + version: 12.0.1 + resolution: "react-dev-utils@npm:12.0.1" dependencies: "@babel/code-frame": "npm:^7.16.0" address: "npm:^1.1.2" @@ -29538,12 +30379,12 @@ __metadata: open: "npm:^8.4.0" pkg-up: "npm:^3.1.0" prompts: "npm:^2.4.2" - react-error-overlay: "npm:^6.0.10" + react-error-overlay: "npm:^6.0.11" recursive-readdir: "npm:^2.2.2" shell-quote: "npm:^1.7.3" strip-ansi: "npm:^6.0.1" text-table: "npm:^0.2.0" - checksum: 0eb8e1a959a96cd0bba112d9baeaeedfd66b9e25ee93ee3d7eb0902407fbb666ccf3bc6a3604b0e7aad3a3f2e3f9f8fbfc8717753a8f8dc18d0090895a8c440c + checksum: 94bc4ee5014290ca47a025e53ab2205c5dc0299670724d46a0b1bacbdd48904827b5ae410842d0a3a92481509097ae032e4a9dc7ca70db437c726eaba6411e82 languageName: node linkType: hard @@ -29560,10 +30401,22 @@ __metadata: languageName: node linkType: hard -"react-error-overlay@npm:^6.0.10": - version: 6.0.10 - resolution: "react-error-overlay@npm:6.0.10" - checksum: 123dc3a6d079a190f94bb4138b5b970e9f592a614f411afd8045a9eab6107d1c750da3a756b19f012b53cd318bc2287cbe6a7ba683ba1d904821d9f7e5135472 +"react-dom@npm:^18.0.0": + version: 18.3.0 + resolution: "react-dom@npm:18.3.0" + dependencies: + loose-envify: "npm:^1.1.0" + scheduler: "npm:^0.23.1" + peerDependencies: + react: ^18.3.0 + checksum: 5072767a5d67e242579e5ed46094bf5665385fcfc50584e818273ba668f768348bfd9101841fa3986635635b1238a7a5b2d28b73b134ebbe58a415311afd60d4 + languageName: node + linkType: hard + +"react-error-overlay@npm:^6.0.11": + version: 6.0.11 + resolution: "react-error-overlay@npm:6.0.11" + checksum: 8fc93942976e0c704274aec87dbc8e21f62a2cc78d1c93f9bcfff9f7494b00c60f7a2f0bd48d832bcd3190627c0255a1df907373f61f820371373a65ec4b2d64 languageName: node linkType: hard @@ -29576,6 +30429,15 @@ __metadata: languageName: node linkType: hard +"react-icons@npm:^5.1.0": + version: 5.1.0 + resolution: "react-icons@npm:5.1.0" + peerDependencies: + react: "*" + checksum: f01648bbf37854510a568c1ac4aeb1c23f734f2ff3a9e94d624bc039ff6291d83ea83281a1380e8105b3f1819bb359a6f34326a5cefbfbced1024b4b81493e01 + languageName: node + linkType: hard + "react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -29583,7 +30445,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^17.0.1, react-is@npm:^17.0.2": +"react-is@npm:^17.0.1": version: 17.0.2 resolution: "react-is@npm:17.0.2" checksum: 2bdb6b93fbb1820b024b496042cce405c57e2f85e777c9aabd55f9b26d145408f9f74f5934676ffdc46f3dcff656d78413a6e43968e7b3f92eea35b3052e9053 @@ -29597,28 +30459,6 @@ __metadata: languageName: node linkType: hard -"react-lifecycles-compat@npm:^3.0.0": - version: 3.0.4 - resolution: "react-lifecycles-compat@npm:3.0.4" - checksum: 1d0df3c85af79df720524780f00c064d53a9dd1899d785eddb7264b378026979acbddb58a4b7e06e7d0d12aa1494fd5754562ee55d32907b15601068dae82c27 - languageName: node - linkType: hard - -"react-modal@npm:^3.15.1": - version: 3.15.1 - resolution: "react-modal@npm:3.15.1" - dependencies: - exenv: "npm:^1.2.0" - prop-types: "npm:^15.7.2" - react-lifecycles-compat: "npm:^3.0.0" - warning: "npm:^4.0.3" - peerDependencies: - react: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 - react-dom: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 - checksum: 90711c29557b9b5b13728ef65d586929c9ca2f3e3b6875d688a5ac2ed4bd3227e1ec136778b9c8236989730f942915d97c7b47f26ef1c101345bfd41197781aa - languageName: node - linkType: hard - "react-paginate@npm:^8.2.0": version: 8.2.0 resolution: "react-paginate@npm:8.2.0" @@ -29630,41 +30470,6 @@ __metadata: languageName: node linkType: hard -"react-qr-reader@npm:2.1.2": - version: 2.1.2 - resolution: "react-qr-reader@npm:2.1.2" - dependencies: - jsqr: "npm:^1.1.1" - prop-types: "npm:^15.5.8" - webrtc-adapter: "npm:^6.4.0" - peerDependencies: - react: ~16 - react-dom: ~16 - checksum: d3518a5520009d51a34e536058000b1a6f1d4ac8bc104888699ce4821ad08806e817e59c8d81449bbb82b987c130249ca639c95deb95116d2933c5f5f1522f40 - languageName: node - linkType: hard - -"react-redux@npm:^7.2.6": - version: 7.2.6 - resolution: "react-redux@npm:7.2.6" - dependencies: - "@babel/runtime": "npm:^7.15.4" - "@types/react-redux": "npm:^7.1.20" - hoist-non-react-statics: "npm:^3.3.2" - loose-envify: "npm:^1.4.0" - prop-types: "npm:^15.7.2" - react-is: "npm:^17.0.2" - peerDependencies: - react: ^16.8.3 || ^17 - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - checksum: f2329642ef6ea95ad9384de08529ded1f836a82098a19f4f09f6afef612d9d5785b4e8aef4f9c9292d3f93812b979f94cd9b8d3aa8747e0a4a10e2600771c3f5 - languageName: node - linkType: hard - "react-refresh@npm:^0.11.0": version: 0.11.0 resolution: "react-refresh@npm:0.11.0" @@ -29672,6 +30477,13 @@ __metadata: languageName: node linkType: hard +"react-refresh@npm:^0.14.0": + version: 0.14.1 + resolution: "react-refresh@npm:0.14.1" + checksum: 2ec3be9be8a66595968bce983dd1a3ac9b31baa61cb693ab0fc6ea1944fda39265cc17da9acc257e3f3aa7e7786a396f1bc5e18a1f8f55edbdc03f12c7e56596 + languageName: node + linkType: hard + "react-router-dom@npm:^6.0.0": version: 6.8.2 resolution: "react-router-dom@npm:6.8.2" @@ -29696,9 +30508,9 @@ __metadata: languageName: node linkType: hard -"react-scripts@npm:5.0.0": - version: 5.0.0 - resolution: "react-scripts@npm:5.0.0" +"react-scripts@npm:5.0.1": + version: 5.0.1 + resolution: "react-scripts@npm:5.0.1" dependencies: "@babel/core": "npm:^7.16.0" "@pmmmwh/react-refresh-webpack-plugin": "npm:^0.5.3" @@ -29716,7 +30528,7 @@ __metadata: dotenv: "npm:^10.0.0" dotenv-expand: "npm:^5.1.0" eslint: "npm:^8.3.0" - eslint-config-react-app: "npm:^7.0.0" + eslint-config-react-app: "npm:^7.0.1" eslint-webpack-plugin: "npm:^3.1.1" file-loader: "npm:^6.2.0" fs-extra: "npm:^10.0.0" @@ -29734,7 +30546,7 @@ __metadata: postcss-preset-env: "npm:^7.0.1" prompts: "npm:^2.4.2" react-app-polyfill: "npm:^3.0.0" - react-dev-utils: "npm:^12.0.0" + react-dev-utils: "npm:^12.0.1" react-refresh: "npm:^0.11.0" resolve: "npm:^1.20.0" resolve-url-loader: "npm:^4.0.0" @@ -29759,7 +30571,7 @@ __metadata: optional: true bin: react-scripts: bin/react-scripts.js - checksum: 7fb82563704d38f28c6a8ccf3ed67e5bbbae6e56703ea261285fd4520af6ef7606c2351e062c0b86c638c2bb3acf2b2cca8ba1b659b9cde0ffa4dc13f3f41c75 + checksum: 1776e7139261019eb4a2adece8fb997913040c6b4e9170902ffed95c3ff311ded623189bb1582ecddb3a5a15d6afd871fb68dbed72080d50f635e31c4ff5fff5 languageName: node linkType: hard @@ -29773,6 +30585,15 @@ __metadata: languageName: node linkType: hard +"react@npm:^18.0.0": + version: 18.3.0 + resolution: "react@npm:18.3.0" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: ad87bbfdb0c5466148c657da18b0d5458e835389fc591d59840f0e6ec797a004073a01c8cdbff1767a8774c7219054a56f74dacd67bdbb849f1314e427999268 + languageName: node + linkType: hard + "read-cache@npm:^1.0.0": version: 1.0.0 resolution: "read-cache@npm:1.0.0" @@ -29989,64 +30810,6 @@ __metadata: languageName: node linkType: hard -"redux-mock-store@npm:^1.5.4": - version: 1.5.4 - resolution: "redux-mock-store@npm:1.5.4" - dependencies: - lodash.isplainobject: "npm:^4.0.6" - checksum: 674fc7a68ffdb9a9ad50c9f987a2711387b1fc348a8ed3d07b7abd4d2560762347a9565cf9080e786d9f66012ff47f90bef244623c62136511d9b4f2411fb658 - languageName: node - linkType: hard - -"redux-persist-transform-encrypt@npm:^3.0.1": - version: 3.0.1 - resolution: "redux-persist-transform-encrypt@npm:3.0.1" - dependencies: - crypto-js: "npm:3.1.9-1" - json-stringify-safe: "npm:^5.0.1" - peerDependencies: - redux: ">3.0.0" - redux-persist: ^6.x.x - checksum: a01e628123365b2450ab9a8133706c4b6da382ff56385ca06c09d0a9bc64fa158483878be536787838be26683e1f36c8ad3efa47dd24f321da5dffe7eb895a8b - languageName: node - linkType: hard - -"redux-persist@npm:^6.0.0": - version: 6.0.0 - resolution: "redux-persist@npm:6.0.0" - peerDependencies: - redux: ">4.0.0" - checksum: 8242d265ab8d28bbc95cf2dc2a05b869eb67aa309b1ed08163c926f3af56dd8eb1ea62118286083461b8ef2024d3b349fd264e5a62a70eb2e74d068c832d5bf2 - languageName: node - linkType: hard - -"redux-thunk@npm:^2.4.1": - version: 2.4.1 - resolution: "redux-thunk@npm:2.4.1" - peerDependencies: - redux: ^4 - checksum: 1127090b488c6b368397ed885415553735433b2971bd7d7aee77da398bddcac1c6dbddb0ebef1761d9c2bd59e610877824fad432ade5a4f75132e5bb37387ee7 - languageName: node - linkType: hard - -"redux@npm:^4.0.0, redux@npm:^4.1.2": - version: 4.1.2 - resolution: "redux@npm:4.1.2" - dependencies: - "@babel/runtime": "npm:^7.9.2" - checksum: 3c6af9724bb39ead9d6a7e2bd422f898136d44e0ff3abe8ccafd6be8a6e219ae4f8bea0b154173e42da829979394944ec47a91eea961997eb95d31af60597ebe - languageName: node - linkType: hard - -"redux@npm:^4.0.5": - version: 4.2.0 - resolution: "redux@npm:4.2.0" - dependencies: - "@babel/runtime": "npm:^7.9.2" - checksum: 6b8b543499c9b8aa6afa01ef68950f4b2ea68d803381ac65797b1a5a7e39ba88ee3650c2a5a1dd500c78ad022de45cd5ed4a5f41fe7d51db8b07d12fbe84d146 - languageName: node - linkType: hard - "regenerate-unicode-properties@npm:^10.0.1": version: 10.0.1 resolution: "regenerate-unicode-properties@npm:10.0.1" @@ -30408,13 +31171,6 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^4.1.5": - version: 4.1.5 - resolution: "reselect@npm:4.1.5" - checksum: 1737bc8ee7da3c07761ec84fd44281604144210ef35f7704e93170260303c2ba2f9afaa165a0421d8ef578a41c5204dedc18572f27df45cd65da6bd8d5716b12 - languageName: node - linkType: hard - "resolve-alpn@npm:^1.2.0": version: 1.2.1 resolution: "resolve-alpn@npm:1.2.1" @@ -30490,7 +31246,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.6, resolve@npm:^1.1.7, resolve@npm:^1.22.2": +"resolve@npm:^1.1.6, resolve@npm:^1.1.7, resolve@npm:^1.17.0, resolve@npm:^1.22.2": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -30565,7 +31321,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.1.7#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin<compat/resolve>": +"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.1.7#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin<compat/resolve>": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>::version=1.22.8&hash=c3c19d" dependencies: @@ -30768,12 +31524,73 @@ __metadata: languageName: node linkType: hard -"rtcpeerconnection-shim@npm:^1.2.14": - version: 1.2.15 - resolution: "rtcpeerconnection-shim@npm:1.2.15" - dependencies: - sdp: "npm:^2.6.0" - checksum: efa4d9ba66146e2e03b6fb34bd1ceb77bf14f2fb2ee8c75c2c8f6a2494ffacc37c214caf5d4a3dcc761ba0b5bfd20fa61fa0820447f64e24b1e959f725ff7410 +"rollup@npm:^4.13.0": + version: 4.16.4 + resolution: "rollup@npm:4.16.4" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.16.4" + "@rollup/rollup-android-arm64": "npm:4.16.4" + "@rollup/rollup-darwin-arm64": "npm:4.16.4" + "@rollup/rollup-darwin-x64": "npm:4.16.4" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.16.4" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.16.4" + "@rollup/rollup-linux-arm64-gnu": "npm:4.16.4" + "@rollup/rollup-linux-arm64-musl": "npm:4.16.4" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.16.4" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.16.4" + "@rollup/rollup-linux-s390x-gnu": "npm:4.16.4" + "@rollup/rollup-linux-x64-gnu": "npm:4.16.4" + "@rollup/rollup-linux-x64-musl": "npm:4.16.4" + "@rollup/rollup-win32-arm64-msvc": "npm:4.16.4" + "@rollup/rollup-win32-ia32-msvc": "npm:4.16.4" + "@rollup/rollup-win32-x64-msvc": "npm:4.16.4" + "@types/estree": "npm:1.0.5" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-powerpc64le-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: f88017e8a599b055c555fe9b9dc2eee3def3067701600492a2dc2ed3ba78c3f0b1d7927f9ed934afef936167a73447121e8f7fbc4804b73f6c181e2d7f52e853 + languageName: node + linkType: hard + +"rrweb-cssom@npm:^0.6.0": + version: 0.6.0 + resolution: "rrweb-cssom@npm:0.6.0" + checksum: 3d9d90d53c2349ea9c8509c2690df5a4ef930c9cf8242aeb9425d4046f09d712bb01047e00da0e1c1dab5db35740b3d78fd45c3e7272f75d3724a563f27c30a3 languageName: node linkType: hard @@ -30943,6 +31760,15 @@ __metadata: languageName: node linkType: hard +"scheduler@npm:^0.23.1": + version: 0.23.1 + resolution: "scheduler@npm:0.23.1" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: cfda827a445fb57192e05275040eccc7c5e2749b98f15559520c7f6539d89d75633bb8b6c1cedf56ca0546630b72d0958bf00b63e2b8f9296e87d0d9d2d50e35 + languageName: node + linkType: hard + "schema-utils@npm:2.7.0": version: 2.7.0 resolution: "schema-utils@npm:2.7.0" @@ -30999,13 +31825,6 @@ __metadata: languageName: node linkType: hard -"sdp@npm:^2.6.0, sdp@npm:^2.9.0": - version: 2.12.0 - resolution: "sdp@npm:2.12.0" - checksum: 1a2ffdc20d79711175f89e87a6ce8db9b4757e694bed9760e5f919eed5925c9fb43ea63c5fd38f428a3edd45baae826318153fdc1db590a504eed7a809a23e32 - languageName: node - linkType: hard - "secretjs@npm:^0.17.0": version: 0.17.5 resolution: "secretjs@npm:0.17.5" @@ -31160,6 +31979,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.5.0": + version: 7.6.2 + resolution: "semver@npm:7.6.2" + bin: + semver: bin/semver.js + checksum: 97d3441e97ace8be4b1976433d1c32658f6afaff09f143e52c593bae7eef33de19e3e369c88bd985ce1042c6f441c80c6803078d1de2a9988080b66684cbb30c + languageName: node + linkType: hard + "semver@npm:^7.6.0": version: 7.6.0 resolution: "semver@npm:7.6.0" @@ -31324,7 +32152,7 @@ __metadata: languageName: node linkType: hard -"setimmediate@npm:^1.0.5": +"setimmediate@npm:^1.0.4, setimmediate@npm:^1.0.5": version: 1.0.5 resolution: "setimmediate@npm:1.0.5" checksum: 5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49 @@ -31678,6 +32506,13 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:^1.2.0": + version: 1.2.0 + resolution: "source-map-js@npm:1.2.0" + checksum: 7e5f896ac10a3a50fe2898e5009c58ff0dc102dcb056ed27a354623a0ece8954d4b2649e1a1b2b52ef2e161d26f8859c7710350930751640e71e374fe2d321a4 + languageName: node + linkType: hard + "source-map-loader@npm:^3.0.0": version: 3.0.1 resolution: "source-map-loader@npm:3.0.1" @@ -32014,6 +32849,18 @@ __metadata: languageName: node linkType: hard +"stream-http@npm:^3.2.0": + version: 3.2.0 + resolution: "stream-http@npm:3.2.0" + dependencies: + builtin-status-codes: "npm:^3.0.0" + inherits: "npm:^2.0.4" + readable-stream: "npm:^3.6.0" + xtend: "npm:^4.0.2" + checksum: f128fb8076d60cd548f229554b6a1a70c08a04b7b2afd4dbe7811d20f27f7d4112562eb8bce86d72a8691df3b50573228afcf1271e55e81f981536c67498bc41 + languageName: node + linkType: hard + "stream-to-array@npm:~2.3.0": version: 2.3.0 resolution: "stream-to-array@npm:2.3.0" @@ -32041,15 +32888,6 @@ __metadata: languageName: node linkType: hard -"stream@npm:^0.0.2": - version: 0.0.2 - resolution: "stream@npm:0.0.2" - dependencies: - emitter-component: "npm:^1.1.1" - checksum: 2b2a196218afcd61fa48366318cdbc4a496d7141ec21f616e5f75290428daff9d0e1ac109a39e63c6d07f1187db055ca2b04e188232cca21595b85f282d7ad28 - languageName: node - linkType: hard - "streamx@npm:^2.15.0": version: 2.15.1 resolution: "streamx@npm:2.15.1" @@ -32267,7 +33105,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": +"string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: @@ -32686,6 +33524,15 @@ __metadata: languageName: node linkType: hard +"tailwind-merge@npm:^2.3.0": + version: 2.3.0 + resolution: "tailwind-merge@npm:2.3.0" + dependencies: + "@babel/runtime": "npm:^7.24.1" + checksum: 5ea308e23c3ab1cf4c3f35f0a471753f4d3ed232d63dd7c09151a74428737321902203d90e9f0cb76ea5c3978e71b0adbc503dc455e56cda967a7674ae4b94b5 + languageName: node + linkType: hard + "tailwind-variants@npm:^0.1.18": version: 0.1.19 resolution: "tailwind-variants@npm:0.1.19" @@ -33144,6 +33991,15 @@ __metadata: languageName: node linkType: hard +"timers-browserify@npm:^2.0.4": + version: 2.0.12 + resolution: "timers-browserify@npm:2.0.12" + dependencies: + setimmediate: "npm:^1.0.4" + checksum: 98e84db1a685bc8827c117a8bc62aac811ad56a995d07938fc7ed8cdc5bf3777bfe2d4e5da868847194e771aac3749a20f6cdd22091300fe889a76fe214a4641 + languageName: node + linkType: hard + "timsort@npm:^0.3.0": version: 0.3.0 resolution: "timsort@npm:0.3.0" @@ -33161,6 +34017,13 @@ __metadata: languageName: node linkType: hard +"tiny-invariant@npm:^1.1.0": + version: 1.3.3 + resolution: "tiny-invariant@npm:1.3.3" + checksum: 65af4a07324b591a059b35269cd696aba21bef2107f29b9f5894d83cc143159a204b299553435b03874ebb5b94d019afa8b8eff241c8a4cfee95872c2e1c1c4a + languageName: node + linkType: hard + "titleize@npm:^3.0.0": version: 3.0.0 resolution: "titleize@npm:3.0.0" @@ -33253,6 +34116,18 @@ __metadata: languageName: node linkType: hard +"tough-cookie@npm:^4.1.3": + version: 4.1.3 + resolution: "tough-cookie@npm:4.1.3" + dependencies: + psl: "npm:^1.1.33" + punycode: "npm:^2.1.1" + universalify: "npm:^0.2.0" + url-parse: "npm:^1.5.3" + checksum: 4fc0433a0cba370d57c4b240f30440c848906dee3180bb6e85033143c2726d322e7e4614abb51d42d111ebec119c4876ed8d7247d4113563033eebbc1739c831 + languageName: node + linkType: hard + "tough-cookie@npm:~2.5.0": version: 2.5.0 resolution: "tough-cookie@npm:2.5.0" @@ -33290,6 +34165,15 @@ __metadata: languageName: node linkType: hard +"tr46@npm:^5.0.0": + version: 5.0.0 + resolution: "tr46@npm:5.0.0" + dependencies: + punycode: "npm:^2.3.1" + checksum: 1521b6e7bbc8adc825c4561480f9fe48eb2276c81335eed9fa610aa4c44a48a3221f78b10e5f18b875769eb3413e30efbf209ed556a17a42aa8d690df44b7bee + languageName: node + linkType: hard + "tr46@npm:~0.0.3": version: 0.0.3 resolution: "tr46@npm:0.0.3" @@ -33571,6 +34455,20 @@ __metadata: languageName: node linkType: hard +"tsconfck@npm:^3.0.3": + version: 3.0.3 + resolution: "tsconfck@npm:3.0.3" + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + bin: + tsconfck: bin/tsconfck.js + checksum: d45009230c4caa5fc765bdded96f3b8703a7cdd44a1d63024914b0fb1c4dabf9e94d28cc9f9edccaef9baa7b99adc963502d34943d82fcb07b92e1161ee03c56 + languageName: node + linkType: hard + "tsconfig-paths-webpack-plugin@npm:^4.1.0": version: 4.1.0 resolution: "tsconfig-paths-webpack-plugin@npm:4.1.0" @@ -33663,6 +34561,13 @@ __metadata: languageName: node linkType: hard +"tty-browserify@npm:0.0.1": + version: 0.0.1 + resolution: "tty-browserify@npm:0.0.1" + checksum: 5e34883388eb5f556234dae75b08e069b9e62de12bd6d87687f7817f5569430a6dfef550b51dbc961715ae0cd0eb5a059e6e3fc34dc127ea164aa0f9b5bb033d + languageName: node + linkType: hard + "tunnel-agent@npm:^0.6.0": version: 0.6.0 resolution: "tunnel-agent@npm:0.6.0" @@ -34273,6 +35178,16 @@ __metadata: languageName: node linkType: hard +"url@npm:^0.11.0": + version: 0.11.3 + resolution: "url@npm:0.11.3" + dependencies: + punycode: "npm:^1.4.1" + qs: "npm:^6.11.2" + checksum: 7546b878ee7927cfc62ca21dbe2dc395cf70e889c3488b2815bf2c63355cb3c7db555128176a01b0af6cccf265667b6fd0b4806de00cb71c143c53986c08c602 + languageName: node + linkType: hard + "user-home@npm:^2.0.0": version: 2.0.0 resolution: "user-home@npm:2.0.0" @@ -34311,12 +35226,16 @@ __metadata: languageName: node linkType: hard -"util@npm:^0.10.3": - version: 0.10.4 - resolution: "util@npm:0.10.4" +"util@npm:^0.12.4, util@npm:^0.12.5": + version: 0.12.5 + resolution: "util@npm:0.12.5" dependencies: - inherits: "npm:2.0.3" - checksum: d29f6893e406b63b088ce9924da03201df89b31490d4d011f1c07a386ea4b3dbe907464c274023c237da470258e1805d806c7e4009a5974cd6b1d474b675852a + inherits: "npm:^2.0.3" + is-arguments: "npm:^1.0.4" + is-generator-function: "npm:^1.0.7" + is-typed-array: "npm:^1.1.3" + which-typed-array: "npm:^1.1.2" + checksum: c27054de2cea2229a66c09522d0fa1415fb12d861d08523a8846bf2e4cbf0079d4c3f725f09dcb87493549bcbf05f5798dce1688b53c6c17201a45759e7253f3 languageName: node linkType: hard @@ -34425,6 +35344,184 @@ __metadata: languageName: node linkType: hard +"vite-plugin-checker@npm:^0.6.4": + version: 0.6.4 + resolution: "vite-plugin-checker@npm:0.6.4" + dependencies: + "@babel/code-frame": "npm:^7.12.13" + ansi-escapes: "npm:^4.3.0" + chalk: "npm:^4.1.1" + chokidar: "npm:^3.5.1" + commander: "npm:^8.0.0" + fast-glob: "npm:^3.2.7" + fs-extra: "npm:^11.1.0" + npm-run-path: "npm:^4.0.1" + semver: "npm:^7.5.0" + strip-ansi: "npm:^6.0.0" + tiny-invariant: "npm:^1.1.0" + vscode-languageclient: "npm:^7.0.0" + vscode-languageserver: "npm:^7.0.0" + vscode-languageserver-textdocument: "npm:^1.0.1" + vscode-uri: "npm:^3.0.2" + peerDependencies: + eslint: ">=7" + meow: ^9.0.0 + optionator: ^0.9.1 + stylelint: ">=13" + typescript: "*" + vite: ">=2.0.0" + vls: "*" + vti: "*" + vue-tsc: ">=1.3.9" + peerDependenciesMeta: + eslint: + optional: true + meow: + optional: true + optionator: + optional: true + stylelint: + optional: true + typescript: + optional: true + vls: + optional: true + vti: + optional: true + vue-tsc: + optional: true + checksum: ae61f01b620c458e355ad05ff632e3143312e75c67acdaaa1fe5160d679283364867a4a8d6c6a3f85838f0251033275af96a1aa9b52eed227151cdbca0c996cf + languageName: node + linkType: hard + +"vite-plugin-node-polyfills@npm:^0.22.0": + version: 0.22.0 + resolution: "vite-plugin-node-polyfills@npm:0.22.0" + dependencies: + "@rollup/plugin-inject": "npm:^5.0.5" + node-stdlib-browser: "npm:^1.2.0" + peerDependencies: + vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + checksum: f8ddc452eb6fba280977d037f8a6406aa522e69590641ce72ce5bb31c3498856a9f63ab3671bc6a822dcd1ff9ba5cac02cacef4a0e170fd8500cdeeb38c81675 + languageName: node + linkType: hard + +"vite-tsconfig-paths@npm:^4.3.2": + version: 4.3.2 + resolution: "vite-tsconfig-paths@npm:4.3.2" + dependencies: + debug: "npm:^4.1.1" + globrex: "npm:^0.1.2" + tsconfck: "npm:^3.0.3" + peerDependencies: + vite: "*" + peerDependenciesMeta: + vite: + optional: true + checksum: f390ac1d1c3992fc5ac50f9274c1090f8b55ab34a89ea88893db9a6924a3b26c9f64bc1163615150ad100749db73b6b2cf1d57f6cd60df6e762ceb5b8ad30024 + languageName: node + linkType: hard + +"vite@npm:^5.2.11": + version: 5.2.11 + resolution: "vite@npm:5.2.11" + dependencies: + esbuild: "npm:^0.20.1" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.38" + rollup: "npm:^4.13.0" + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 664b8d68e4f5152ae16bd2041af1bbaf11c43630ac461835bc31ff7d019913b33e465386e09f66dc1037d7aeefbb06939e0173787c063319bc2bd30c3b9ad8e4 + languageName: node + linkType: hard + +"vm-browserify@npm:^1.0.1": + version: 1.1.2 + resolution: "vm-browserify@npm:1.1.2" + checksum: 0cc1af6e0d880deb58bc974921320c187f9e0a94f25570fca6b1bd64e798ce454ab87dfd797551b1b0cc1849307421aae0193cedf5f06bdb5680476780ee344b + languageName: node + linkType: hard + +"vscode-jsonrpc@npm:6.0.0": + version: 6.0.0 + resolution: "vscode-jsonrpc@npm:6.0.0" + checksum: 22c35873155a62e71c454ad71165683536361eaabc1f07af41cbfd83c4c3bbfe3b36b58faba2b059d8f20da61b645a8c687bdf449407196e0bdb0a080257ca69 + languageName: node + linkType: hard + +"vscode-languageclient@npm:^7.0.0": + version: 7.0.0 + resolution: "vscode-languageclient@npm:7.0.0" + dependencies: + minimatch: "npm:^3.0.4" + semver: "npm:^7.3.4" + vscode-languageserver-protocol: "npm:3.16.0" + checksum: 3eabd90cb76159bcbabd0884c130a8bb9cd90a583c348730eee97e565cf939ea87e3033d7e58c94a3d8709fabf9d794e6316167bf7de1e7481882357dd02aa28 + languageName: node + linkType: hard + +"vscode-languageserver-protocol@npm:3.16.0": + version: 3.16.0 + resolution: "vscode-languageserver-protocol@npm:3.16.0" + dependencies: + vscode-jsonrpc: "npm:6.0.0" + vscode-languageserver-types: "npm:3.16.0" + checksum: 6a1ca737d826a710271b36d72c0833dfc8f78c68416725173892195d04b358ee8eb1095d5edfb7a62c7ea01128c762b9463ee8b6b1949efe060a43fe621ea62a + languageName: node + linkType: hard + +"vscode-languageserver-textdocument@npm:^1.0.1": + version: 1.0.11 + resolution: "vscode-languageserver-textdocument@npm:1.0.11" + checksum: 1996a38e24571e05aa21dd4f46e0a6849e22301c9a66996762e77d9c6df3622de0bd31cd5742a0c0c47fb9dfd00b310ad08c44d08241873ea571edacd5238da6 + languageName: node + linkType: hard + +"vscode-languageserver-types@npm:3.16.0": + version: 3.16.0 + resolution: "vscode-languageserver-types@npm:3.16.0" + checksum: cc1bd68a7fe94152849e434cfc6fd8471f5c17198057fc6c95814d4b1655ab2b76d577b5fcd0f1f2a5df0285f054c96b9698e6d33e8183846f152d6e7d3ecc97 + languageName: node + linkType: hard + +"vscode-languageserver@npm:^7.0.0": + version: 7.0.0 + resolution: "vscode-languageserver@npm:7.0.0" + dependencies: + vscode-languageserver-protocol: "npm:3.16.0" + bin: + installServerIntoExtension: bin/installServerIntoExtension + checksum: a36f66ab2f43ff3a754ccca5030ac3ec73cf373ab3d4d65c1de59895198b3abb3760691ada71fd7837e7dbda1eb14526420b4b91fe562facabfc568a2e58a88a + languageName: node + linkType: hard + "vscode-oniguruma@npm:^1.7.0": version: 1.7.0 resolution: "vscode-oniguruma@npm:1.7.0" @@ -34439,6 +35536,13 @@ __metadata: languageName: node linkType: hard +"vscode-uri@npm:^3.0.2": + version: 3.0.8 + resolution: "vscode-uri@npm:3.0.8" + checksum: f7f217f526bf109589969fe6e66b71e70b937de1385a1d7bb577ca3ee7c5e820d3856a86e9ff2fa9b7a0bc56a3dd8c3a9a557d3fedd7df414bc618d5e6b567f9 + languageName: node + linkType: hard + "w3c-hr-time@npm:^1.0.2": version: 1.0.2 resolution: "w3c-hr-time@npm:1.0.2" @@ -34466,6 +35570,15 @@ __metadata: languageName: node linkType: hard +"w3c-xmlserializer@npm:^5.0.0": + version: 5.0.0 + resolution: "w3c-xmlserializer@npm:5.0.0" + dependencies: + xml-name-validator: "npm:^5.0.0" + checksum: 8712774c1aeb62dec22928bf1cdfd11426c2c9383a1a63f2bcae18db87ca574165a0fbe96b312b73652149167ac6c7f4cf5409f2eb101d9c805efe0e4bae798b + languageName: node + linkType: hard + "wait-on@npm:7.0.1, wait-on@npm:^7.0.1": version: 7.0.1 resolution: "wait-on@npm:7.0.1" @@ -34504,15 +35617,6 @@ __metadata: languageName: node linkType: hard -"warning@npm:^4.0.3": - version: 4.0.3 - resolution: "warning@npm:4.0.3" - dependencies: - loose-envify: "npm:^1.0.0" - checksum: aebab445129f3e104c271f1637fa38e55eb25f968593e3825bd2f7a12bd58dc3738bb70dc8ec85826621d80b4acfed5a29ebc9da17397c6125864d72301b937e - languageName: node - linkType: hard - "wasm-pack@npm:^0.12.1": version: 0.12.1 resolution: "wasm-pack@npm:0.12.1" @@ -35107,16 +36211,6 @@ __metadata: languageName: node linkType: hard -"webrtc-adapter@npm:^6.4.0": - version: 6.4.8 - resolution: "webrtc-adapter@npm:6.4.8" - dependencies: - rtcpeerconnection-shim: "npm:^1.2.14" - sdp: "npm:^2.9.0" - checksum: a6b09f59625c0ea851efdd31c05705490425b9207be406ea1b1319d5d2b6ccdda2825e418ae47f43fc0dec1e48e219c06987ab100da3861331d4518b160d9fbd - languageName: node - linkType: hard - "websocket-driver@npm:>=0.5.1, websocket-driver@npm:^0.7.4": version: 0.7.4 resolution: "websocket-driver@npm:0.7.4" @@ -35153,6 +36247,15 @@ __metadata: languageName: node linkType: hard +"whatwg-encoding@npm:^3.1.1": + version: 3.1.1 + resolution: "whatwg-encoding@npm:3.1.1" + dependencies: + iconv-lite: "npm:0.6.3" + checksum: 273b5f441c2f7fda3368a496c3009edbaa5e43b71b09728f90425e7f487e5cef9eb2b846a31bd760dd8077739c26faf6b5ca43a5f24033172b003b72cf61a93e + languageName: node + linkType: hard + "whatwg-fetch@npm:^3.6.2": version: 3.6.2 resolution: "whatwg-fetch@npm:3.6.2" @@ -35174,6 +36277,13 @@ __metadata: languageName: node linkType: hard +"whatwg-mimetype@npm:^4.0.0": + version: 4.0.0 + resolution: "whatwg-mimetype@npm:4.0.0" + checksum: a773cdc8126b514d790bdae7052e8bf242970cebd84af62fb2f35a33411e78e981f6c0ab9ed1fe6ec5071b09d5340ac9178e05b52d35a9c4bcf558ba1b1551df + languageName: node + linkType: hard + "whatwg-url@npm:^11.0.0": version: 11.0.0 resolution: "whatwg-url@npm:11.0.0" @@ -35184,6 +36294,16 @@ __metadata: languageName: node linkType: hard +"whatwg-url@npm:^14.0.0": + version: 14.0.0 + resolution: "whatwg-url@npm:14.0.0" + dependencies: + tr46: "npm:^5.0.0" + webidl-conversions: "npm:^7.0.0" + checksum: ac32e9ba9d08744605519bbe9e1371174d36229689ecc099157b6ba102d4251a95e81d81f3d80271eb8da182eccfa65653f07f0ab43ea66a6934e643fd091ba9 + languageName: node + linkType: hard + "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0" @@ -35256,6 +36376,19 @@ __metadata: languageName: node linkType: hard +"which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.2": + version: 1.1.15 + resolution: "which-typed-array@npm:1.1.15" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.7" + for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + checksum: 4465d5348c044032032251be54d8988270e69c6b7154f8fcb2a47ff706fe36f7624b3a24246b8d9089435a8f4ec48c1c1025c5d6b499456b9e5eff4f48212983 + languageName: node + linkType: hard + "which-typed-array@npm:^1.1.9": version: 1.1.10 resolution: "which-typed-array@npm:1.1.10" @@ -35354,6 +36487,13 @@ __metadata: languageName: node linkType: hard +"wonka@npm:^6.3.4": + version: 6.3.4 + resolution: "wonka@npm:6.3.4" + checksum: 77329eea673da07717476e1b8f1a22f1e1a4f261bb9a58fa446c03d3da13dbd5b254664f8aded5928d953f33ee5b399a17a4f70336e8b236e478209c0e78cda4 + languageName: node + linkType: hard + "word-wrap@npm:^1.2.3, word-wrap@npm:~1.2.3": version: 1.2.3 resolution: "word-wrap@npm:1.2.3" @@ -35737,6 +36877,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.16.0": + version: 8.16.0 + resolution: "ws@npm:8.16.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: a7783bb421c648b1e622b423409cb2a58ac5839521d2f689e84bc9dc41d59379c692dd405b15a997ea1d4c0c2e5314ad707332d0c558f15232d2bc07c0b4618a + languageName: node + linkType: hard + "ws@npm:^8.9.0": version: 8.11.0 resolution: "ws@npm:8.11.0" @@ -35811,6 +36966,13 @@ __metadata: languageName: node linkType: hard +"xml-name-validator@npm:^5.0.0": + version: 5.0.0 + resolution: "xml-name-validator@npm:5.0.0" + checksum: 3fcf44e7b73fb18be917fdd4ccffff3639373c7cb83f8fc35df6001fecba7942f1dbead29d91ebb8315e2f2ff786b508f0c9dc0215b6353f9983c6b7d62cb1f5 + languageName: node + linkType: hard + "xml2js@npm:^0.5.0": version: 0.5.0 resolution: "xml2js@npm:0.5.0" @@ -35932,16 +37094,6 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^18.1.2": - version: 18.1.3 - resolution: "yargs-parser@npm:18.1.3" - dependencies: - camelcase: "npm:^5.0.0" - decamelize: "npm:^1.2.0" - checksum: 25df918833592a83f52e7e4f91ba7d7bfaa2b891ebf7fe901923c2ee797534f23a176913ff6ff7ebbc1cc1725a044cc6a6539fed8bfd4e13b5b16376875f9499 - languageName: node - linkType: hard - "yargs-parser@npm:^20.2.2": version: 20.2.9 resolution: "yargs-parser@npm:20.2.9" @@ -35997,25 +37149,6 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^15.3.1": - version: 15.4.1 - resolution: "yargs@npm:15.4.1" - dependencies: - cliui: "npm:^6.0.0" - decamelize: "npm:^1.2.0" - find-up: "npm:^4.1.0" - get-caller-file: "npm:^2.0.1" - require-directory: "npm:^2.1.1" - require-main-filename: "npm:^2.0.0" - set-blocking: "npm:^2.0.0" - string-width: "npm:^4.2.0" - which-module: "npm:^2.0.0" - y18n: "npm:^4.0.0" - yargs-parser: "npm:^18.1.2" - checksum: f1ca680c974333a5822732825cca7e95306c5a1e7750eb7b973ce6dc4f97a6b0a8837203c8b194f461969bfe1fb1176d1d423036635285f6010b392fa498ab2d - languageName: node - linkType: hard - "yargs@npm:^16.2.0": version: 16.2.0 resolution: "yargs@npm:16.2.0"