diff --git a/.changeset/big-snakes-marry.md b/.changeset/big-snakes-marry.md new file mode 100644 index 000000000000..d3d8e8b01bf0 --- /dev/null +++ b/.changeset/big-snakes-marry.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +Always skip the analytics prompt in e2e tests diff --git a/.changeset/calm-pets-shout.md b/.changeset/calm-pets-shout.md new file mode 100644 index 000000000000..d99d754e65de --- /dev/null +++ b/.changeset/calm-pets-shout.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +bump storyly version diff --git a/.changeset/clever-rocks-watch.md b/.changeset/clever-rocks-watch.md new file mode 100644 index 000000000000..fcdf5af7b7ac --- /dev/null +++ b/.changeset/clever-rocks-watch.md @@ -0,0 +1,8 @@ +--- +"live-mobile": patch +--- + +fix(wallet-api): only call success and fail handlers when closing the flow on LLM + +Allows to properly retry the sign message without sending an error through the wallet-api +We also only send the success when we actually close the last success screen diff --git a/.changeset/dry-otters-shop.md b/.changeset/dry-otters-shop.md new file mode 100644 index 000000000000..187563b2688a --- /dev/null +++ b/.changeset/dry-otters-shop.md @@ -0,0 +1,6 @@ +--- +"ledger-live-desktop": patch +"@ledgerhq/live-nft": patch +--- + +Add detail drawers for inscriptions of ordinals protocol diff --git a/.changeset/empty-clouds-glow.md b/.changeset/empty-clouds-glow.md new file mode 100644 index 000000000000..373b48d68356 --- /dev/null +++ b/.changeset/empty-clouds-glow.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +Add ui for empty rare sats and inscriptions. Add error message from Error when inscriptions / rare sats can't load. Add dumy drawer for inscriptions diff --git a/.changeset/four-worms-end.md b/.changeset/four-worms-end.md new file mode 100644 index 000000000000..a64eceb72b84 --- /dev/null +++ b/.changeset/four-worms-end.md @@ -0,0 +1,6 @@ +--- +"@ledgerhq/live-common": patch +"@ledgerhq/coin-evm": patch +--- + +chore: update default explorer for local base & zkevm config, update evm bridge test diff --git a/.changeset/fresh-cobras-argue.md b/.changeset/fresh-cobras-argue.md new file mode 100644 index 000000000000..581e0dd9b2cd --- /dev/null +++ b/.changeset/fresh-cobras-argue.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +Fix unstable integration test diff --git a/.changeset/green-starfishes-suffer.md b/.changeset/green-starfishes-suffer.md new file mode 100644 index 000000000000..4aacaeeb1498 --- /dev/null +++ b/.changeset/green-starfishes-suffer.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +add change address disclaimer for utxos accounts diff --git a/.changeset/olive-drinks-complain.md b/.changeset/olive-drinks-complain.md new file mode 100644 index 000000000000..8bd20d8f14ad --- /dev/null +++ b/.changeset/olive-drinks-complain.md @@ -0,0 +1,6 @@ +--- +"ledger-live-desktop": patch +"live-mobile": patch +--- + +Update turbo.json to accept envs diff --git a/.changeset/rare-yaks-deny.md b/.changeset/rare-yaks-deny.md new file mode 100644 index 000000000000..e5c8877cb941 --- /dev/null +++ b/.changeset/rare-yaks-deny.md @@ -0,0 +1,8 @@ +--- +"@ledgerhq/coin-near": patch +"ledger-live-desktop": patch +"live-mobile": patch +"@ledgerhq/live-common": patch +--- + +remove rewards feature from near diff --git a/.changeset/six-hairs-raise.md b/.changeset/six-hairs-raise.md new file mode 100644 index 000000000000..4ad083d7a294 --- /dev/null +++ b/.changeset/six-hairs-raise.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +Run tests files at `e2e/specs/*.spec.ts` diff --git a/.changeset/slow-dingos-destroy.md b/.changeset/slow-dingos-destroy.md new file mode 100644 index 000000000000..93f3e2a4871e --- /dev/null +++ b/.changeset/slow-dingos-destroy.md @@ -0,0 +1,9 @@ +--- +"ledger-live-desktop": patch +"live-mobile": patch +"@ledgerhq/live-common": patch +--- + +fix(LLM): allow wallet-connect live-app to open a deep-link at any time + +Refactor by sharing a single constant for wallet-connect live-app manifest id diff --git a/.changeset/sour-poets-play.md b/.changeset/sour-poets-play.md new file mode 100644 index 000000000000..ae7b88b5aee8 --- /dev/null +++ b/.changeset/sour-poets-play.md @@ -0,0 +1,7 @@ +--- +"live-mobile": patch +--- + +fix(LLM): wallet-connect deep-link from native app for a request would reload the page + +Reloading the page would break the wallet-api flow shown when coming back to the app diff --git a/.changeset/strong-goats-rush.md b/.changeset/strong-goats-rush.md new file mode 100644 index 000000000000..319fbaa8634e --- /dev/null +++ b/.changeset/strong-goats-rush.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +Always skip the analytics prompt in e2e tests diff --git a/.changeset/tough-terms-film.md b/.changeset/tough-terms-film.md new file mode 100644 index 000000000000..ce11edd712fb --- /dev/null +++ b/.changeset/tough-terms-film.md @@ -0,0 +1,7 @@ +--- +"live-mobile": patch +--- + +fix(wallet-api): open any deep-link from the webview on LLM + +Needed for wallet-connect-live-app deep-link back to mobile dApps diff --git a/.changeset/witty-bugs-add.md b/.changeset/witty-bugs-add.md new file mode 100644 index 000000000000..232f7175fa82 --- /dev/null +++ b/.changeset/witty-bugs-add.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +storyly stories localization diff --git a/.changeset/witty-cows-breathe.md b/.changeset/witty-cows-breathe.md new file mode 100644 index 000000000000..16b2ab79e57d --- /dev/null +++ b/.changeset/witty-cows-breathe.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +Add protectBox to protect ordinals in discovery drawer diff --git a/.github/workflows/build-and-test-external.yml b/.github/workflows/build-and-test-external.yml index af5ed98ceefc..96769045a1fd 100644 --- a/.github/workflows/build-and-test-external.yml +++ b/.github/workflows/build-and-test-external.yml @@ -14,7 +14,7 @@ permissions: jobs: determine-affected: name: "Turbo Affected" - if: ${{contains(needs.determine-affected.outputs.paths, 'ledger-live-desktop') && github.event.pull_request.head.repo.full_name != github.repository }} + if: ${{github.event.pull_request.head.repo.full_name != github.repository }} uses: LedgerHQ/ledger-live/.github/workflows/turbo-affected-reusable.yml@develop with: head_branch: ${{ github.event.pull_request.head.ref || github.event.merge_group.head_ref }} @@ -26,18 +26,30 @@ jobs: needs: determine-affected if: ${{contains(needs.determine-affected.outputs.paths, 'ledger-live-desktop') && github.event.pull_request.head.repo.full_name != github.repository }} uses: LedgerHQ/ledger-live/.github/workflows/build-desktop-external-reusable.yml@develop + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + secrets: inherit test-desktop-external: name: "[External] Test Desktop" needs: determine-affected if: ${{contains(needs.determine-affected.outputs.paths, 'ledger-live-desktop') && github.event.pull_request.head.repo.full_name != github.repository }} uses: LedgerHQ/ledger-live/.github/workflows/test-desktop-external-reusable.yml@develop + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + secrets: inherit build-mobile-external: name: "[External] Build Mobile" needs: determine-affected if: ${{contains(needs.determine-affected.outputs.paths, 'ledger-live-mobile') && github.event.pull_request.head.repo.full_name != github.repository}} uses: LedgerHQ/ledger-live/.github/workflows/build-mobile-external-reusable.yml@develop + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + secrets: inherit # Final Check required ok: diff --git a/.github/workflows/build-and-test-pr.yml b/.github/workflows/build-and-test-pr.yml index cf2b38919b9e..37c55935c2e0 100644 --- a/.github/workflows/build-and-test-pr.yml +++ b/.github/workflows/build-and-test-pr.yml @@ -14,6 +14,7 @@ permissions: jobs: determine-affected: name: "Turbo Affected" + if: ${{github.event.pull_request.head.repo.full_name == github.repository }} uses: LedgerHQ/ledger-live/.github/workflows/turbo-affected-reusable.yml@develop with: head_branch: ${{ github.event.pull_request.head.ref || github.event.merge_group.head_ref }} diff --git a/.github/workflows/build-desktop-external-reusable.yml b/.github/workflows/build-desktop-external-reusable.yml index e22f9a170bca..67bc572856df 100644 --- a/.github/workflows/build-desktop-external-reusable.yml +++ b/.github/workflows/build-desktop-external-reusable.yml @@ -2,20 +2,18 @@ name: "@Desktop • Build App (external)" on: workflow_call: - workflow_dispatch: inputs: ref: + type: string description: | If you run this manually, and want to run on a PR, the correct ref should be refs/pull/{PR_NUMBER}/merge to have the "normal" scenario involving checking out a merge commit between your branch and the base branch. If you want to run only on a branch or specific commit, you can use either the sha or the branch name instead (prefer the first verion for PRs). - required: false - login: - description: The GitHub username that triggered the workflow - required: false - base_ref: - description: The base branch to merge the head into when checking out the code - required: false + required: true + repository: + description: The repository to checkout the code from + type: string + required: true jobs: build-desktop-app: @@ -46,7 +44,9 @@ jobs: } - uses: actions/checkout@v4 with: - ref: ${{ github.ref_name }} + ref: ${{ inputs.ref }} + repository: ${{ inputs.repository }} + token: ${{ secrets.GITHUB_TOKEN }} persist-credentials: false - name: Setup git user uses: LedgerHQ/ledger-live/tools/actions/composites/setup-git-user@develop diff --git a/.github/workflows/build-mobile-external-reusable.yml b/.github/workflows/build-mobile-external-reusable.yml index 30b9dc52fd37..fd729a3cae3e 100644 --- a/.github/workflows/build-mobile-external-reusable.yml +++ b/.github/workflows/build-mobile-external-reusable.yml @@ -2,23 +2,18 @@ name: "@Mobile • Build App (external)" on: workflow_call: - workflow_dispatch: inputs: ref: + type: string description: | If you run this manually, and want to run on a PR, the correct ref should be refs/pull/{PR_NUMBER}/merge to have the "normal" scenario involving checking out a merge commit between your branch and the base branch. If you want to run only on a branch or specific commit, you can use either the sha or the branch name instead (prefer the first verion for PRs). - login: - description: The GitHub username that triggered the workflow - required: false - base_ref: - description: The base branch to merge the head into when checking out the code - required: false - -concurrency: - group: ${{ github.workflow }}-${{ github.ref_name != 'develop' && github.ref || github.run_id }} - cancel-in-progress: true + required: true + repository: + description: The repository to checkout the code from + type: string + required: true permissions: contents: read @@ -26,13 +21,15 @@ permissions: jobs: build-mobile-app-android: name: "Build Ledger Live Mobile (Android)" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: NODE_OPTIONS: "--max-old-space-size=7168" steps: - uses: actions/checkout@v4 with: - ref: ${{ github.ref_name }} + ref: ${{ inputs.ref }} + repository: ${{ inputs.repository }} + token: ${{ secrets.GITHUB_TOKEN }} persist-credentials: false - name: Setup git user uses: LedgerHQ/ledger-live/tools/actions/composites/setup-git-user@develop @@ -92,7 +89,9 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.ref_name }} + ref: ${{ inputs.ref }} + repository: ${{ inputs.repository }} + token: ${{ secrets.GITHUB_TOKEN }} persist-credentials: false - name: Setup git user uses: LedgerHQ/ledger-live/tools/actions/composites/setup-git-user@develop @@ -105,7 +104,7 @@ jobs: run: pnpm i --filter="live-mobile..." --filter="ledger-live" --no-frozen-lockfile --unsafe-perm report: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [build-mobile-app-android, build-mobile-app-ios] if: ${{ !cancelled() && github.event_name == 'workflow_dispatch' }} steps: diff --git a/.github/workflows/test-desktop-external-reusable.yml b/.github/workflows/test-desktop-external-reusable.yml index 3069cf438fd1..d39b21a02ecb 100644 --- a/.github/workflows/test-desktop-external-reusable.yml +++ b/.github/workflows/test-desktop-external-reusable.yml @@ -2,20 +2,18 @@ name: "@Desktop • Test App (external)" on: workflow_call: - workflow_dispatch: inputs: ref: + type: string description: | If you run this manually, and want to run on a PR, the correct ref should be refs/pull/{PR_NUMBER}/merge to have the "normal" scenario involving checking out a merge commit between your branch and the base branch. If you want to run only on a branch or specific commit, you can use either the sha or the branch name instead (prefer the first verion for PRs). - required: false - login: - description: The GitHub username that triggered the workflow - required: false - base_ref: - description: The base branch to merge the head into when checking out the code - required: false + required: true + repository: + description: The repository to checkout the code from + type: string + required: true permissions: id-token: write @@ -27,12 +25,14 @@ jobs: env: NODE_OPTIONS: "--max-old-space-size=7168" FORCE_COLOR: 3 - CI_OS: ubuntu-latest - runs-on: ubuntu-latest + CI_OS: ubuntu-22.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 with: - ref: ${{ github.ref_name }} + ref: ${{ inputs.ref }} + repository: ${{ inputs.repository }} + token: ${{ secrets.GITHUB_TOKEN }} persist-credentials: false - name: Setup the toolchain uses: LedgerHQ/ledger-live/tools/actions/composites/setup-toolchain@develop @@ -63,12 +63,14 @@ jobs: env: NODE_OPTIONS: "--max-old-space-size=7168" FORCE_COLOR: 3 - CI_OS: ubuntu-latest - runs-on: ubuntu-latest + CI_OS: ubuntu-22.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 with: - ref: ${{ github.ref_name }} + ref: ${{ inputs.ref }} + repository: ${{ inputs.repository }} + token: ${{ secrets.GITHUB_TOKEN }} persist-credentials: false - name: Setup the toolchain uses: LedgerHQ/ledger-live/tools/actions/composites/setup-toolchain@develop @@ -84,17 +86,19 @@ jobs: env: NODE_OPTIONS: "--max-old-space-size=7168" FORCE_COLOR: 3 - CI_OS: ubuntu-latest + CI_OS: ubuntu-22.04 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 # DEBUG: "pw:browser*" # DEBUG_LOGS: 1 - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: status: ${{ steps.tests.outcome }} steps: - uses: actions/checkout@v4 with: - ref: ${{ github.ref_name }} + ref: ${{ inputs.ref }} + repository: ${{ inputs.repository }} + token: ${{ secrets.GITHUB_TOKEN }} persist-credentials: false - name: Setup the toolchain uses: LedgerHQ/ledger-live/tools/actions/composites/setup-toolchain@develop @@ -147,12 +151,14 @@ jobs: report: needs: [codechecks, unit-tests, e2e-tests-linux] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: ${{ !cancelled() && github.event_name == 'workflow_dispatch' }} steps: - uses: actions/checkout@v4 with: - ref: ${{ github.ref_name }} + ref: ${{ inputs.ref }} + repository: ${{ inputs.repository }} + token: ${{ secrets.GITHUB_TOKEN }} persist-credentials: false - name: "download linter results" uses: actions/download-artifact@v4 @@ -318,22 +324,3 @@ jobs: with: name: summary.json path: ${{ github.workspace }}/summary.json - - allure-report: - name: "Allure Reports Export on Server" - needs: [e2e-tests-linux] - runs-on: [ledger-live-medium] - if: ${{ !cancelled() && github.ref_name == 'develop' }} - steps: - - name: checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.ref_name }} - - name: Send Results and Generate Allure Report - Linux - uses: LedgerHQ/ledger-live/tools/actions/composites/upload-allure-report@develop - if: ${{ !cancelled() }} - with: - platform: linux - login: ${{ vars.ALLURE_USERNAME }} - password: ${{ secrets.ALLURE_LEDGER_LIVE_PASSWORD }} - path: allure-results-linux diff --git a/.github/workflows/test-mobile-e2e-reusable.yml b/.github/workflows/test-mobile-e2e-reusable.yml index cd50fb28eaea..da711859c946 100644 --- a/.github/workflows/test-mobile-e2e-reusable.yml +++ b/.github/workflows/test-mobile-e2e-reusable.yml @@ -32,6 +32,11 @@ on: description: "[iOS] Test Execution ticket ID. Ex: 'B2CQA-2461'" required: false type: string + speculos_tests: + description: Run Speculos tests + required: false + type: boolean + default: false # Uncomment to have log-level: trace on detox run and build # (cf: apps/ledger-live-mobile/detox.config.js) @@ -173,6 +178,8 @@ jobs: AVD_CORES: 4 AVD_RAM_SIZE: 4096M AVD_OPTIONS: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + SPECULOS_IMAGE_TAG: ghcr.io/ledgerhq/speculos:0.9.5 + COINAPPS: ${{ github.workspace }}/coin-apps outputs: status: ${{ steps.detox.outcome }} steps: @@ -255,12 +262,22 @@ jobs: disable-linux-hw-accel: false emulator-options: ${{ env.AVD_OPTIONS }} script: ./tools/scripts/wait_emulator_idle.sh + - name: Setup Speculos image and Coin Apps + if: ${{ inputs.speculos_tests }} + uses: LedgerHQ/ledger-live/tools/actions/composites/setup-speculos_image@develop + with: + coinapps_path: ${{ env.COINAPPS }} + speculos_tag: ${{ env.SPECULOS_IMAGE_TAG }} + bot_id: ${{ secrets.GH_BOT_APP_ID }} + bot_key: ${{ secrets.GH_BOT_PRIVATE_KEY }} - name: Run Android Tests id: detox - run: pnpm mobile e2e:ci -p android -t + run: pnpm mobile e2e:ci -p android -t $([[ "$INPUT_SPECULOS" == "true" ]] && printf %s '--speculos') timeout-minutes: 45 env: DETOX_INSTALL_TIMEOUT: 120000 + SEED: ${{ secrets.SEED_QAA_B2C }} + INPUT_SPECULOS: ${{ inputs.speculos_tests }} - name: Generate single file Allure report if: ${{ !cancelled() || steps.detox.outcome == 'cancelled' }} run: pnpm dlx allure-commandline generate apps/ledger-live-mobile/artifacts --single-file diff --git a/.github/workflows/test-mobile-e2e.yml b/.github/workflows/test-mobile-e2e.yml index 5f28d86dfb9a..54369f71fd71 100644 --- a/.github/workflows/test-mobile-e2e.yml +++ b/.github/workflows/test-mobile-e2e.yml @@ -28,6 +28,11 @@ on: description: "[iOS] Test Execution ticket ID. Ex: 'B2CQA-2461'" required: false type: string + speculos_tests: + description: Run Speculos tests + required: false + type: boolean + default: false # Uncomment to have log-level: trace on detox run and build # (cf: apps/ledger-live-mobile/detox.config.js) diff --git a/.github/workflows/test-mobile.yml b/.github/workflows/test-mobile.yml index 315586c03f30..f2faa42bb4cd 100644 --- a/.github/workflows/test-mobile.yml +++ b/.github/workflows/test-mobile.yml @@ -2,12 +2,6 @@ name: "@Mobile • Test App" run-name: "@Mobile • Test App triggered by ${{ inputs.login || github.actor }} ${{ format('on ref {0}', github.ref_name) }}" on: - push: - branches: - - main - - develop - - release - - hotfix workflow_dispatch: inputs: ref: diff --git a/.github/workflows/test-ui-e2e-only-desktop.yml b/.github/workflows/test-ui-e2e-only-desktop.yml index 5ea2379d9460..8cce945c1b09 100644 --- a/.github/workflows/test-ui-e2e-only-desktop.yml +++ b/.github/workflows/test-ui-e2e-only-desktop.yml @@ -130,7 +130,7 @@ jobs: export COINAPPS=$PWD/coin-apps export MOCK=0 if [ "${{ inputs.invert_filter }}" = true ]; then invert_filter="-invert"; fi - xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- pnpm desktop test:playwright:speculos ${INPUTS_TEST_FILTER:+--grep$invert_filter} "${{ inputs.test_filter }}" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- pnpm desktop test:playwright:speculos --max-failures 50 ${INPUTS_TEST_FILTER:+--grep$invert_filter} "${{ inputs.test_filter }}" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} env: INPUTS_TEST_FILTER: ${{ inputs.test_filter }} SEED: ${{ secrets.SEED_QAA_B2C }} diff --git a/apps/ledger-live-desktop/index-types.d.ts b/apps/ledger-live-desktop/index-types.d.ts index 64587364fbb3..d1e41cc52d9f 100644 --- a/apps/ledger-live-desktop/index-types.d.ts +++ b/apps/ledger-live-desktop/index-types.d.ts @@ -63,6 +63,7 @@ interface Window { getAllFeatureFlags: (appLanguage: string) => Partial<{ [key in FeatureId]: Feature }>; getAllEnvs: () => { [key in EnvName]: unknown }; + saveLogs: (path: string) => void; // for mocking purposes apparently? // eslint-disable-next-line diff --git a/apps/ledger-live-desktop/package.json b/apps/ledger-live-desktop/package.json index 0ab851b4b4f6..2c4905e1cd61 100644 --- a/apps/ledger-live-desktop/package.json +++ b/apps/ledger-live-desktop/package.json @@ -143,7 +143,7 @@ "rxjs": "^7.8.1", "secp256k1": "^4.0.3", "semver": "^7.1.3", - "storyly-web": "^2.8.0", + "storyly-web": "^3.5.0", "styled-components": "^5.3.3", "styled-system": "^5.1.5", "timemachine": "^0.3.2", diff --git a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantA/Main/components/MainFooter/index.tsx b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantA/Main/components/MainFooter/index.tsx index 0b29747d0e65..bb762e6033d9 100644 --- a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantA/Main/components/MainFooter/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantA/Main/components/MainFooter/index.tsx @@ -38,7 +38,13 @@ const MainFooter = ({ setWantToManagePreferences, onShareAnalyticsChange }: Main > {t("analyticsOptInPrompt.variantA.refuse")} - diff --git a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/components/Footer/index.tsx b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/components/Footer/index.tsx index 1e48da078f0a..0861a1e24fa7 100644 --- a/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/components/Footer/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/AnalyticsOptInPrompt/screens/VariantB/components/Footer/index.tsx @@ -30,6 +30,7 @@ const VariantBFooter = ({ clickOptions }: VariantBFooterProps) => { {t("analyticsOptInPrompt.variantB.refuse")} + + + ); +}; +export default Actions; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/SubTitle.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/SubTitle.tsx new file mode 100644 index 000000000000..21c43cae7fe2 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/SubTitle.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { Flex, Text } from "@ledgerhq/react-ui"; +import IconContainer from "LLD/features/Collectibles/components/Collection/TableRow/IconContainer"; +import { IconProps } from "LLD/features/Collectibles/types/Collection"; +import { MappingKeys } from "LLD/features/Collectibles/types/Ordinals"; +import { useTranslation } from "react-i18next"; + +type Props = { + icons: (({ size, color, style }: IconProps) => JSX.Element)[]; + names: MappingKeys[]; +}; + +const SubTitle: React.FC = ({ icons, names }) => { + const { t } = useTranslation(); + return ( + + + + {t("ordinals.inscriptions.detailsDrawer.storedOnChain")} + + + ); +}; + +export default SubTitle; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/index.tsx new file mode 100644 index 000000000000..abedd3a803ae --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/index.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; +import { DetailDrawer } from "LLD/features/Collectibles/components"; +import { CollectibleTypeEnum } from "LLD/features/Collectibles/types/enum/Collectibles"; +import useInscriptionDetailDrawer from "./useInscriptionDetailDrawer"; +import Actions from "./Actions"; +import SubTitle from "./SubTitle"; + +type ViewProps = ReturnType & { + onClose: () => void; +}; + +type Props = { + inscription: SimpleHashNft; + correspondingRareSat: SimpleHashNft | null | undefined; + isLoading: boolean; + onClose: () => void; +}; + +const View: React.FC = ({ data, rareSat, onClose }) => ( + + {rareSat?.icons && ( + + + + )} + + + + +); + +const InscriptionDetailDrawer = ({ + inscription, + isLoading, + correspondingRareSat, + onClose, +}: Props) => { + return ( + + ); +}; + +export default InscriptionDetailDrawer; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/useInscriptionDetailDrawer.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/useInscriptionDetailDrawer.ts new file mode 100644 index 000000000000..c82cae275514 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DetailsDrawer/useInscriptionDetailDrawer.ts @@ -0,0 +1,64 @@ +import { useMemo, useState } from "react"; +import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; +import useCollectibles from "LLD/features/Collectibles/hooks/useCollectibles"; +import { createDetails } from "LLD/features/Collectibles/utils/createInscriptionDetailsArrays"; +import { useCalendarFormatted } from "~/renderer/hooks/useDateFormatter"; +import { Tag } from "LLD/features/Collectibles/types/DetailDrawer"; +import { processRareSat } from "../helpers"; + +type Props = { + isLoading: boolean; + inscription: SimpleHashNft; + correspondingRareSat: SimpleHashNft | null | undefined; +}; + +const useInscriptionDetailDrawer = ({ isLoading, inscription, correspondingRareSat }: Props) => { + const [useFallback, setUseFallback] = useState(false); + const imageUri = + inscription.video_url || inscription.previews?.image_large_url || inscription.image_url; + + const isVideo = !!inscription.video_url; + + const contentType = isVideo ? "video" : imageUri ? "image" : ""; + + const { isPanAndZoomOpen, openCollectiblesPanAndZoom, closeCollectiblesPanAndZoom } = + useCollectibles(); + + const createdDateFromTimestamp = new Date(inscription.first_created?.timestamp || 0); + const formattedCreatedDate = useCalendarFormatted(createdDateFromTimestamp); + const createdDate = createdDateFromTimestamp === new Date(0) ? "" : formattedCreatedDate; + const details = createDetails(inscription, createdDate); + + const tags: Tag[] = + inscription.extra_metadata?.attributes?.map(attr => ({ + key: attr.trait_type, + value: attr.value, + })) || []; + + const rareSat = useMemo(() => { + if (correspondingRareSat) return processRareSat(correspondingRareSat); + }, [correspondingRareSat]); + + const data = { + areFieldsLoading: isLoading, + collectibleName: inscription.name || inscription.contract.name || "", + contentType, + collectionName: inscription.collection.name || "", + details: details, + previewUri: imageUri, + originalUri: imageUri, + isPanAndZoomOpen, + mediaType: inscription.video_properties?.mime_type || "image", + tags: tags, + useFallback: useFallback, + tokenId: inscription.nft_id, + isOpened: true, + closeCollectiblesPanAndZoom, + openCollectiblesPanAndZoom, + setUseFallback: setUseFallback, + }; + + return { inscription, data, rareSat }; +}; + +export default useInscriptionDetailDrawer; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/Header.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/Header.tsx index b1a82554bdb0..281c62f68903 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/Header.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/Header.tsx @@ -5,6 +5,8 @@ const DiscoveryDrawerHeader: React.FC = () => ( ( top: 0, left: "50%", transform: "translateX(-50%) translateY(-50%)", - filter: "blur(120px)", + filter: "blur(17px)", + opacity: 0.2, }} > diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/ProtectBox.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/ProtectBox.tsx new file mode 100644 index 000000000000..8c428e9f8a18 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/ProtectBox.tsx @@ -0,0 +1,35 @@ +import React, { useCallback } from "react"; +import { Flex, Text } from "@ledgerhq/react-ui"; +import Switch from "~/renderer/components/Switch"; +import { hasProtectedOrdinalsAssetsSelector } from "~/renderer/reducers/settings"; +import { setHasProtectedOrdinalsAssets } from "~/renderer/actions/settings"; +import { useDispatch, useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; + +const ProtectBox: React.FC = () => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const hasProtectedOrdinals = useSelector(hasProtectedOrdinalsAssetsSelector); + + const onSwitchChange = useCallback(() => { + dispatch(setHasProtectedOrdinalsAssets(!hasProtectedOrdinals)); + }, [dispatch, hasProtectedOrdinals]); + + return ( + + + {t("ordinals.inscriptions.discoveryDrawer.protectTitle")} + + + + {t("ordinals.inscriptions.discoveryDrawer.protectDescription")} + + + + + + + ); +}; + +export default ProtectBox; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/index.tsx index 806c91d5ece9..b2c36f00ffdc 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/DiscoveryDrawer/index.tsx @@ -1,6 +1,7 @@ import React from "react"; import DiscoveryDrawerHeader from "./Header"; -import { Button, CryptoIcon, Flex, Link, Text } from "@ledgerhq/react-ui"; +import ProtectBox from "./ProtectBox"; +import { Button, CryptoIcon, Flex, Text } from "@ledgerhq/react-ui"; import { SideDrawer } from "~/renderer/components/SideDrawer"; import { Direction } from "@ledgerhq/react-ui/components/layout/Drawer/index"; import { space } from "@ledgerhq/react-ui/styles/theme"; @@ -17,7 +18,6 @@ const DiscoveryDrawer = ({ isOpen, onClose }: Props) => { const { t } = useTranslation(); const learnMoreUrl = urls.whatAreOrdinals; const onButtonClick = () => openURL(learnMoreUrl); - const onLinkClick = () => onClose(); return ( { > {t("ordinals.inscriptions.discoveryDrawer.description")} - - - {t("ordinals.inscriptions.discoveryDrawer.viewCoinControl")} - + ); diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item.tsx deleted file mode 100644 index dc3f2ab8ffd8..000000000000 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { InscriptionsItemProps } from "LLD/features/Collectibles/types/Inscriptions"; -import TableRow from "LLD/features/Collectibles/components/Collection/TableRow"; -import React from "react"; - -type ItemProps = { - isLoading: boolean; -} & InscriptionsItemProps; - -const Item: React.FC = ({ - isLoading, - tokenName, - collectionName, - tokenIcons, - media, - rareSatName, - onClick, -}) => ( - -); - -export default Item; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item/index.tsx new file mode 100644 index 000000000000..b9c404527346 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/Item/index.tsx @@ -0,0 +1,40 @@ +import React, { useMemo } from "react"; +import { InscriptionsItemProps } from "LLD/features/Collectibles/types/Inscriptions"; +import TableRow from "LLD/features/Collectibles/components/Collection/TableRow"; +import { GroupedNftOrdinals } from "@ledgerhq/live-nft-react/index"; +import { findCorrespondingSat } from "LLD/features/Collectibles/utils/findCorrespondingSat"; +import { processRareSat } from "../helpers"; + +type ItemProps = { + isLoading: boolean; + inscriptionsGroupedWithRareSats: GroupedNftOrdinals[]; +} & InscriptionsItemProps; + +const Item: React.FC = ({ + isLoading, + tokenName, + collectionName, + media, + nftId, + inscriptionsGroupedWithRareSats, + onClick, +}) => { + const correspondingRareSat = findCorrespondingSat(inscriptionsGroupedWithRareSats, nftId); + const rareSat = useMemo(() => { + if (correspondingRareSat) return processRareSat(correspondingRareSat.rareSat); + }, [correspondingRareSat]); + + return ( + + ); +}; + +export default Item; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/helpers.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/helpers.ts index 0917bb99b130..695f0a214b1d 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/helpers.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/helpers.ts @@ -1,37 +1,14 @@ import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; -import { IconProps } from "LLD/features/Collectibles/types/Collection"; -import { mappingKeysWithIconAndName } from "../Icons"; -import { MappingKeys } from "LLD/features/Collectibles/types/Ordinals"; +import { createRareSatObject, matchCorrespondingIcon } from "../helpers"; -function matchCorrespondingIcon( - rareSats: SimpleHashNft[], -): Array JSX.Element> }> { - return rareSats.map(rareSat => { - const iconKeys: string[] = []; - const rarity = rareSat.extra_metadata?.ordinal_details?.sat_rarity?.toLowerCase(); - - if (rarity && rarity !== "common") { - iconKeys.push(rarity.replace(" ", "_")); - } - - const icons = iconKeys - .map( - iconKey => - mappingKeysWithIconAndName[iconKey as keyof typeof mappingKeysWithIconAndName]?.icon, - ) - .filter(Boolean) as Array<({ size, color, style }: IconProps) => JSX.Element>; - - return { ...rareSat, icons }; - }); -} - -export function getInscriptionsData(inscriptions: SimpleHashNft[]) { - const inscriptionsWithIcons = matchCorrespondingIcon(inscriptions); - return inscriptionsWithIcons.map(item => ({ +export function getInscriptionsData( + inscriptions: SimpleHashNft[], + onInscriptionClick: (inscription: SimpleHashNft) => void, +) { + return inscriptions.map(item => ({ tokenName: item.name || item.contract.name || "", + nftId: item.nft_id, collectionName: item.collection.name, - tokenIcons: item.icons, - rareSatName: [item.extra_metadata?.ordinal_details?.sat_rarity] as MappingKeys[], media: { uri: item.image_url || item.previews?.image_small_url, isLoading: false, @@ -39,9 +16,12 @@ export function getInscriptionsData(inscriptions: SimpleHashNft[]) { contentType: item.extra_metadata?.ordinal_details?.content_type, mediaType: "image", }, - onClick: () => { - console.log(`you clicked on : \x1b[32m${item.name}\x1b[0m inscription`); - }, - // it does nothing for now but it will be used for the next PR with the drawer + onClick: () => onInscriptionClick(item), })); } + +export function processRareSat(inscription: SimpleHashNft) { + const matchedRareSatsIcons = matchCorrespondingIcon(inscription); + const rareSatObject = createRareSatObject({ rareSat: matchedRareSatsIcons }); + return rareSatObject.rareSat[0]; +} diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/index.tsx index 489b9f88d905..e6269cb4034b 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/index.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Box, Flex } from "@ledgerhq/react-ui"; +import { Box, Flex, Icons } from "@ledgerhq/react-ui"; import { useInscriptionsModel } from "./useInscriptionsModel"; import TableContainer from "~/renderer/components/TableContainer"; import TableHeader from "LLD/features/Collectibles/components/Collection/TableHeader"; @@ -9,13 +9,28 @@ import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; import Loader from "../Loader"; import Error from "../Error"; import Item from "./Item"; +import EmptyCollection from "LLD/features/Collectibles/components/Collection/EmptyCollection"; +import { CollectibleTypeEnum } from "LLD/features/Collectibles/types/enum/Collectibles"; +import Button from "~/renderer/components/Button"; +import { useTranslation } from "react-i18next"; +import { GroupedNftOrdinals } from "@ledgerhq/live-nft-react/index"; -type ViewProps = ReturnType & { isLoading: boolean; isError: boolean }; +type ViewProps = ReturnType & { + isLoading: boolean; + isError: boolean; + error: Error | null; + inscriptionsGroupedWithRareSats: GroupedNftOrdinals[]; + onReceive: () => void; +}; type Props = { inscriptions: SimpleHashNft[]; isLoading: boolean; isError: boolean; + error: Error | null; + inscriptionsGroupedWithRareSats: GroupedNftOrdinals[]; + onReceive: () => void; + onInscriptionClick: (inscription: SimpleHashNft) => void; }; const View: React.FC = ({ @@ -23,34 +38,40 @@ const View: React.FC = ({ isLoading, isError, inscriptions, + error, + inscriptionsGroupedWithRareSats, onShowMore, + onReceive, }) => { + const { t } = useTranslation(); const hasInscriptions = inscriptions.length > 0 && !isError; const nothingToShow = !hasInscriptions && !isLoading && !isError; + const hasError = isError && error; return ( {isLoading && } - {isError && } + {hasError && } {hasInscriptions && inscriptions.map((item, index) => ( ))} {nothingToShow && ( - - {"NOTHING TO SHOW WAITING FOR DESIGN"} - + + + )} {displayShowMore && !isError && } @@ -58,8 +79,23 @@ const View: React.FC = ({ ); }; -const Inscriptions: React.FC = ({ inscriptions, isLoading, isError }) => ( - +const Inscriptions: React.FC = ({ + inscriptions, + isLoading, + isError, + error, + inscriptionsGroupedWithRareSats, + onReceive, + onInscriptionClick, +}) => ( + ); export default Inscriptions; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/useInscriptionsModel.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/useInscriptionsModel.tsx index c3893f978713..431e3234b68f 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/useInscriptionsModel.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/Inscriptions/useInscriptionsModel.tsx @@ -5,21 +5,28 @@ import { InscriptionsItemProps } from "LLD/features/Collectibles/types/Inscripti type Props = { inscriptions: SimpleHashNft[]; + onInscriptionClick: (inscription: SimpleHashNft) => void; }; -export const useInscriptionsModel = ({ inscriptions }: Props) => { - const [displayShowMore, setDisplayShowMore] = useState(false); - const [displayedObjects, setDisplayedObjects] = useState([]); - +export const useInscriptionsModel = ({ inscriptions, onInscriptionClick }: Props) => { const items: InscriptionsItemProps[] = useMemo( - () => getInscriptionsData(inscriptions), - [inscriptions], + () => getInscriptionsData(inscriptions, onInscriptionClick), + [inscriptions, onInscriptionClick], ); + const initialDisplayedObjects = items.slice(0, 3); + const initialDisplayShowMore = items.length > 3; + + const [displayShowMore, setDisplayShowMore] = useState(initialDisplayShowMore); + const [displayedObjects, setDisplayedObjects] = + useState(initialDisplayedObjects); + useEffect(() => { - if (items.length > 3) setDisplayShowMore(true); - setDisplayedObjects(items.slice(0, 3)); - }, [items]); + if (displayedObjects.length === 0) { + if (items.length > 3) setDisplayShowMore(true); + setDisplayedObjects(items.slice(0, 3)); + } + }, [items, displayedObjects.length]); const onShowMore = () => { setDisplayedObjects(prevDisplayedObjects => { diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/helpers.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/helpers.ts deleted file mode 100644 index 9a7423bbe53b..000000000000 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/helpers.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { mappingKeysWithIconAndName } from "../Icons"; -import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; -import { - SimpleHashNftWithIcons, - RareSat, - MappingKeys, -} from "LLD/features/Collectibles/types/Ordinals"; - -export function matchCorrespondingIcon(rareSats: SimpleHashNft[]): SimpleHashNftWithIcons[] { - return rareSats.map(rareSat => { - const iconKeys: string[] = []; - if (rareSat.name) { - iconKeys.push(rareSat.name.toLowerCase().replace(" ", "_")); - } - - if (rareSat.extra_metadata?.utxo_details?.satributes) { - iconKeys.push(...Object.keys(rareSat.extra_metadata.utxo_details.satributes)); - } - - const icons = iconKeys - .map( - iconKey => - mappingKeysWithIconAndName[iconKey as keyof typeof mappingKeysWithIconAndName]?.icon, - ) - .filter(Boolean) as RareSat["icons"]; - - return { ...rareSat, icons }; - }); -} - -export function createRareSatObject( - rareSats: Record, -): Record { - const result: Record = {}; - - for (const [key, value] of Object.entries(rareSats)) { - result[key] = value.map(rareSat => { - const { icons, extra_metadata } = rareSat; - const year = extra_metadata?.utxo_details?.sat_ranges?.[0]?.year || ""; - const displayed_names = - Object.values(extra_metadata?.utxo_details?.satributes || {}) - .map(attr => attr.display_name) - .join(" / ") || ""; - const names = rareSat.extra_metadata?.utxo_details?.satributes - ? (Object.keys(rareSat.extra_metadata.utxo_details.satributes) as MappingKeys[]) - : []; - const count = extra_metadata?.utxo_details?.value || 0; - const isMultipleRow = value.length > 1; - - return { - year, - displayed_names, - names, - count: `${count} ${count > 1 ? "sats" : "sat"}`, - isMultipleRow, - icons, - }; - }); - } - - return result; -} diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/index.tsx index fe8603eedfb6..e1e8dc8f9adb 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/index.tsx @@ -4,16 +4,22 @@ import TableContainer from "~/renderer/components/TableContainer"; import TableHeader from "LLD/features/Collectibles/components/Collection/TableHeader"; import Item from "./Item"; import { TableHeaderTitleKey } from "LLD/features/Collectibles/types/Collection"; -import { Box, Flex } from "@ledgerhq/react-ui"; +import { Box, Flex, Icons } from "@ledgerhq/react-ui"; import { TableHeader as TableHeaderContainer } from "./TableHeader"; import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; import Loader from "../Loader"; import Error from "../Error"; +import EmptyCollection from "../../../components/Collection/EmptyCollection"; +import { CollectibleTypeEnum } from "../../../types/enum/Collectibles"; +import Button from "~/renderer/components/Button"; +import { useTranslation } from "react-i18next"; type ViewProps = ReturnType & { isLoading: boolean; isError: boolean; isFetched: boolean; + error: Error | null; + onReceive: () => void; }; type Props = { @@ -21,19 +27,23 @@ type Props = { isLoading: boolean; isError: boolean; isFetched: boolean; + error: Error | null; + onReceive: () => void; }; -function View({ rareSats, isLoading, isError, isFetched }: ViewProps) { - const isLoaded = isFetched; +function View({ rareSats, isLoading, isError, isFetched, error, onReceive }: ViewProps) { + const { t } = useTranslation(); + const isLoaded = isFetched && !isError && !isLoading; const hasRareSats = Object.values(rareSats).length > 0; const dataReady = isLoaded && hasRareSats; + const hasError = isError && error; return ( {isLoading && } - {isError && } + {hasError && } {dataReady && } {dataReady && @@ -45,9 +55,14 @@ function View({ rareSats, isLoading, isError, isFetched }: ViewProps) { ))} {isLoaded && !hasRareSats && ( - - {"NOTHING TO SHOW WAITING FOR DESIGN"} - + + + )} @@ -55,12 +70,14 @@ function View({ rareSats, isLoading, isError, isFetched }: ViewProps) { ); } -const RareSats = ({ rareSats, isLoading, isError, isFetched }: Props) => { +const RareSats = ({ rareSats, isLoading, isError, isFetched, error, onReceive }: Props) => { return ( ); diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/useRareSatsModel.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/useRareSatsModel.ts index a59b1eafc054..e73078536530 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/useRareSatsModel.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/useRareSatsModel.ts @@ -1,4 +1,4 @@ -import { matchCorrespondingIcon, createRareSatObject } from "./helpers"; +import { matchCorrespondingIcon, createRareSatObject } from "../helpers"; import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; import { regroupRareSatsByContractAddress } from "@ledgerhq/live-nft-react"; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/helpers.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/helpers.ts new file mode 100644 index 000000000000..c323b37e8bd9 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/helpers.ts @@ -0,0 +1,78 @@ +import { mappingKeysWithIconAndName } from "./Icons"; +import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; +import { + SimpleHashNftWithIcons, + RareSat, + MappingKeys, +} from "LLD/features/Collectibles/types/Ordinals"; + +function processSingleRareSat(rareSat: SimpleHashNft): SimpleHashNftWithIcons { + const iconKeys: string[] = []; + if (rareSat.name) { + iconKeys.push(rareSat.name.toLowerCase().replace(" ", "_")); + } + + if (rareSat.extra_metadata?.utxo_details?.satributes) { + iconKeys.push(...Object.keys(rareSat.extra_metadata.utxo_details.satributes)); + } + + const icons = iconKeys + .map( + iconKey => + mappingKeysWithIconAndName[iconKey as keyof typeof mappingKeysWithIconAndName]?.icon, + ) + .filter(Boolean) as RareSat["icons"]; + + return { ...rareSat, icons }; +} + +function mapToRareSat(rareSat: SimpleHashNftWithIcons, isMultipleRow: boolean): RareSat { + const { icons, extra_metadata } = rareSat; + const year = extra_metadata?.utxo_details?.sat_ranges?.[0]?.year || ""; + const displayed_names = + Object.values(extra_metadata?.utxo_details?.satributes || {}) + .map(attr => attr.display_name) + .join(" / ") || ""; + const names = rareSat.extra_metadata?.utxo_details?.satributes + ? (Object.keys(rareSat.extra_metadata.utxo_details.satributes) as MappingKeys[]) + : []; + const count = extra_metadata?.utxo_details?.value || 0; + + return { + year, + displayed_names, + names, + count: `${count} ${count > 1 ? "sats" : "sat"}`, + isMultipleRow, + icons, + }; +} + +function processRareSatObject( + rareSats: Record, +): Record { + const result: Record = {}; + + for (const [key, value] of Object.entries(rareSats)) { + result[key] = value.map(rareSat => mapToRareSat(rareSat, value.length > 1)); + } + + return result; +} + +export function matchCorrespondingIcon( + rareSats: SimpleHashNft | SimpleHashNft[], +): SimpleHashNftWithIcons[] { + const rareSatArray = Array.isArray(rareSats) ? rareSats : [rareSats]; + return rareSatArray.map(processSingleRareSat); +} + +export function createRareSatObject( + rareSats: Record | SimpleHashNftWithIcons[], +): Record { + if (Array.isArray(rareSats)) { + return { default: rareSats.map(rareSat => mapToRareSat(rareSat, rareSats.length > 1)) }; + } else { + return processRareSatObject(rareSats); + } +} diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/index.tsx index 7f8bb60fa721..cbec04e52909 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/index.tsx @@ -5,6 +5,7 @@ import RareSats from "../../components/RareSats"; import DiscoveryDrawer from "../../components/Inscriptions/DiscoveryDrawer"; import { BitcoinAccount } from "@ledgerhq/coin-bitcoin/lib/types"; import { useBitcoinAccountModel } from "./useBitcoinAccountModel"; +import InscriptionDetailsDrawer from "../../components/Inscriptions/DetailsDrawer"; type ViewProps = ReturnType; @@ -17,12 +18,32 @@ const View: React.FC = ({ rest, rareSats, isDrawerOpen, + selectedInscription, + correspondingRareSat, + inscriptionsGroupedWithRareSats, handleDrawerClose, + onReceive, + onInscriptionClick, + onDetailsDrawerClose, }) => ( - - + + + {selectedInscription && ( + + )} ); diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/useBitcoinAccountModel.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/useBitcoinAccountModel.ts index 0c7ab8993add..c1c8d0ad620b 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/useBitcoinAccountModel.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/useBitcoinAccountModel.ts @@ -1,9 +1,12 @@ import { BitcoinAccount } from "@ledgerhq/coin-bitcoin/lib/types"; +import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; import useFetchOrdinals from "LLD/features/Collectibles/hooks/useFetchOrdinals"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { useDispatch, useSelector } from "react-redux"; +import { openModal } from "~/renderer/actions/modals"; import { setHasSeenOrdinalsDiscoveryDrawer } from "~/renderer/actions/settings"; import { hasSeenOrdinalsDiscoveryDrawerSelector } from "~/renderer/reducers/settings"; +import { findCorrespondingSat } from "LLD/features/Collectibles/utils/findCorrespondingSat"; interface Props { account: BitcoinAccount; @@ -12,8 +15,14 @@ interface Props { export const useBitcoinAccountModel = ({ account }: Props) => { const dispatch = useDispatch(); const hasSeenDiscoveryDrawer = useSelector(hasSeenOrdinalsDiscoveryDrawerSelector); + const [selectedInscription, setSelectedInscription] = useState(null); + const [correspondingRareSat, setCorrespondingRareSat] = useState< + SimpleHashNft | null | undefined + >(null); - const { rareSats, inscriptions, ...rest } = useFetchOrdinals({ account }); + const { rareSats, inscriptions, inscriptionsGroupedWithRareSats, ...rest } = useFetchOrdinals({ + account, + }); const [isDrawerOpen, setIsDrawerOpen] = useState(!hasSeenDiscoveryDrawer); @@ -28,5 +37,34 @@ export const useBitcoinAccountModel = ({ account }: Props) => { dispatch(setHasSeenOrdinalsDiscoveryDrawer(true)); }; - return { rareSats, inscriptions, rest, isDrawerOpen, handleDrawerClose }; + const onReceive = useCallback(() => { + dispatch( + openModal("MODAL_RECEIVE", { + account, + receiveOrdinalMode: true, + }), + ); + }, [dispatch, account]); + + const onInscriptionClick = (inscription: SimpleHashNft) => { + const groupedNft = findCorrespondingSat(inscriptionsGroupedWithRareSats, inscription.nft_id); + setCorrespondingRareSat(groupedNft?.rareSat ?? null); + setSelectedInscription(inscription); + }; + + const onDetailsDrawerClose = () => setSelectedInscription(null); + + return { + rareSats, + inscriptions, + rest, + isDrawerOpen, + selectedInscription, + correspondingRareSat, + inscriptionsGroupedWithRareSats, + onReceive, + handleDrawerClose, + onInscriptionClick, + onDetailsDrawerClose, + }; }; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/bitcoinPage.test.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/bitcoinPage.test.tsx index e1046b592629..66797fa60c8e 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/bitcoinPage.test.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/bitcoinPage.test.tsx @@ -18,24 +18,24 @@ jest.mock("~/renderer/linking", () => ({ describe("displayBitcoinPage", () => { it("should display Bitcoin page with rare sats and inscriptions", async () => { - const { user } = render(); + const { user } = render(, { + initialState: { + settings: { + hasSeenOrdinalsDiscoveryDrawer: true, + }, + }, + }); - await waitFor(() => expect(screen.getByText(/inscription #63691311/i)).toBeVisible()); - await waitFor(() => expect(screen.getByTestId(/raresaticon-palindrome-0/i)).toBeVisible()); - await user.hover(screen.getByTestId(/raresaticon-palindrome-0/i)); - await waitFor(() => expect(screen.getByText(/in a playful twist/i)).toBeVisible()); - await waitFor(() => - expect( - screen.getByText(/block 9 \/ first transaction \/ nakamoto \/ vintage/i), - ).toBeVisible(), - ); + await waitFor(() => expect(screen.getByText(/the great war #3695/i)).toBeVisible()); await waitFor(() => expect(screen.getByText(/see more inscriptions/i)).toBeVisible()); await user.click(screen.getByText(/see more inscriptions/i)); - await waitFor(() => expect(screen.getByText(/timechain #136/i)).toBeVisible()); - await waitFor(() => expect(screen.getByTestId(/raresaticon-jpeg-0/i)).toBeVisible()); - await user.hover(screen.getByTestId(/raresaticon-jpeg-0/i)); - await waitFor(() => expect(screen.getByText(/journey into the past with jpeg/i)).toBeVisible()); + await user.click(screen.getByText(/see more inscriptions/i)); + await waitFor(() => expect(screen.getByText(/bitcoin puppet #71/i)).toBeVisible()); + await waitFor(() => expect(screen.queryAllByTestId(/raresaticon-pizza-0/i)).toHaveLength(2)); + await user.hover(screen.queryAllByTestId(/raresaticon-pizza-0/i)[0]); + await waitFor(() => expect(screen.getByText(/papa john's pizza/i)).toBeVisible()); }); + it("should open discovery drawer when it is the first time feature is activated", async () => { const { user } = render(); @@ -43,4 +43,20 @@ describe("displayBitcoinPage", () => { await user.click(screen.getByText(/learn more/i)); expect(openURL).toHaveBeenCalledWith("https://www.ledger.com/academy/bitcoin-ordinals"); }); + + it("should open inscription detail drawer", async () => { + const { user } = render(, { + initialState: { + settings: { + hasSeenOrdinalsDiscoveryDrawer: true, + }, + }, + }); + + await waitFor(() => expect(screen.getByText(/the great war #3695/i)).toBeVisible()); + await user.click(screen.getByText(/the great war #3695/i)); + await expect(screen.getByText(/hide/i)).toBeVisible(); + // sat name + await expect(screen.getByText(/dlngbapxjdv/i)).toBeVisible(); + }); }); diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/nftsCollection.test.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/nftsCollection.test.tsx index 5104ff9891f1..5cf226f23b07 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/nftsCollection.test.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/__integration__/nftsCollection.test.tsx @@ -22,9 +22,9 @@ describe("displayNftCollection", () => { }); await waitFor(() => expect(screen.getByText(/momentum/i)).toBeVisible()); - await expect(screen.getByText(/receive nft/i)).toBeVisible(); - await expect(screen.getByText(/see gallery/i)).toBeVisible(); - await expect(screen.getByText(/see more collections/i)).toBeVisible(); + expect(screen.getByText(/receive nft/i)).toBeVisible(); + expect(screen.getByText(/see gallery/i)).toBeVisible(); + expect(screen.getByText(/see more collections/i)).toBeVisible(); }); it("should open the NFTs gallery", async () => { @@ -36,10 +36,10 @@ describe("displayNftCollection", () => { }); await waitFor(() => expect(screen.getByText(/momentum/i)).toBeVisible()); - await expect(screen.getByText(/receive nft/i)).toBeVisible(); - await expect(screen.getByText(/see gallery/i)).toBeVisible(); + expect(screen.getByText(/receive nft/i)).toBeVisible(); + expect(screen.getByText(/see gallery/i)).toBeVisible(); await user.click(screen.getByText(/see gallery/i)); - await expect(screen.getByText(/all nft/i)).toBeVisible(); + expect(screen.getByText(/all nft/i)).toBeVisible(); }); it("should open the corresponding NFTs collection and the correct detail drawer", async () => { @@ -65,10 +65,10 @@ describe("displayNftCollection", () => { // Open external viewer await user.click(screen.getByTestId("external-viewer-button")); - await expect(screen.getByText(/open in opensea.io/i)).toBeVisible(); + expect(screen.getByText(/open in opensea.io/i)).toBeVisible(); // Close drawer - await waitFor(() => user.click(screen.getByTestId("drawer-close-button"))); + await user.click(screen.getByTestId("drawer-close-button")); await waitFor(() => expect(screen.queryByTestId("side-drawer-container")).toBeNull()); }); @@ -80,7 +80,7 @@ describe("displayNftCollection", () => { initialRoute: `/`, }); - await expect(screen.getByText(/receive nft/i)).toBeVisible(); - await expect(screen.getByText(/learn more/i)).toBeVisible(); + expect(screen.getByText(/receive nft/i)).toBeVisible(); + expect(screen.getByText(/learn more/i)).toBeVisible(); }); }); diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/EmptyCollection.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/EmptyCollection.tsx index 464912b45c42..9dde6dc79a93 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/EmptyCollection.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/EmptyCollection.tsx @@ -30,7 +30,7 @@ const Placeholder = styled.div` type Props = { collectionType: CollectibleType; - currencyName: string; + currencyName?: string; children?: ReactNode; }; @@ -59,8 +59,22 @@ const EmptyCollection: React.FC = ({ collectionType, currencyName, childr ); - case CollectibleTypeEnum.Ordinal: - return null; + case CollectibleTypeEnum.Inscriptions: + return ( + + + {t("ordinals.inscriptions.empty")} + + + ); + case CollectibleTypeEnum.RareSat: + return ( + + + {t("ordinals.rareSats.empty")} + + + ); default: return t("collectibles.emptyState.default"); } diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/HeaderActions.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/HeaderActions.tsx index 5a3074491d6d..8f993d1b3d24 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/HeaderActions.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/components/Collection/HeaderActions.tsx @@ -1,7 +1,7 @@ -import Button from "~/renderer/components/Button"; -import { t } from "i18next"; import React from "react"; +import Button from "~/renderer/components/Button"; import Box from "~/renderer/components/Box"; +import { useTranslation } from "react-i18next"; type Props = { children?: JSX.Element; @@ -9,6 +9,7 @@ type Props = { }; const HeaderActions: React.FC = ({ children, textKey }) => { + const { t } = useTranslation(); return ( - - {displayBanner ? ( - - - - ) : ( - - - - )} {verified ? null : isModalOpened ? ( @@ -413,16 +440,13 @@ type BannerProps = { const WithdrawBanner = ({ onPress, hideBanner }: BannerProps) => { const { t } = useTranslation(); - const insets = useSafeAreaInsets(); return ( - - } - onPressDismiss={hideBanner} - onPress={onPress} - /> - + } + onPressDismiss={hideBanner} + onPress={onPress} + /> ); }; diff --git a/apps/ledger-live-mobile/src/screens/SignMessage/04-ValidationError.tsx b/apps/ledger-live-mobile/src/screens/SignMessage/04-ValidationError.tsx index 13facc028e45..b0aff88d5a3d 100644 --- a/apps/ledger-live-mobile/src/screens/SignMessage/04-ValidationError.tsx +++ b/apps/ledger-live-mobile/src/screens/SignMessage/04-ValidationError.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect } from "react"; +import React, { useCallback } from "react"; import { StyleSheet } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; import { CompositeScreenProps, useTheme } from "@react-navigation/native"; @@ -22,14 +22,12 @@ export default function ValidationError({ navigation, route }: Navigation) { const { colors } = useTheme(); const { error, onFailHandler } = route.params; - useEffect(() => { + const onClose = useCallback(() => { if (onFailHandler && error) { onFailHandler(error); } - }, [onFailHandler, error]); - const onClose = useCallback(() => { navigation.getParent>().pop(); - }, [navigation]); + }, [error, navigation, onFailHandler]); const retry = useCallback(() => { navigation.goBack(); }, [navigation]); diff --git a/apps/ledger-live-mobile/src/screens/SignMessage/04-ValidationSuccess.tsx b/apps/ledger-live-mobile/src/screens/SignMessage/04-ValidationSuccess.tsx index 9e1e3daa100f..3480b7c4ee8d 100644 --- a/apps/ledger-live-mobile/src/screens/SignMessage/04-ValidationSuccess.tsx +++ b/apps/ledger-live-mobile/src/screens/SignMessage/04-ValidationSuccess.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect } from "react"; +import React, { useCallback } from "react"; import { View, StyleSheet } from "react-native"; import { useTranslation } from "react-i18next"; import { CompositeScreenProps, useTheme } from "@react-navigation/native"; @@ -25,14 +25,13 @@ export default function ValidationSuccess({ const { t } = useTranslation(); const { signature, onConfirmationHandler } = route.params; - useEffect(() => { + const onClose = useCallback(() => { if (onConfirmationHandler && signature) { onConfirmationHandler(signature); } - }, [onConfirmationHandler, signature]); - const onClose = useCallback(() => { navigation.getParent>().pop(); - }, [navigation]); + }, [navigation, onConfirmationHandler, signature]); + return ( = { chainId: 1, nonce: 0, }), - expectedStatus: (_account, transaction): Partial => { - const estimatedFees = transaction.gasLimit.times(transaction.maxFeePerGas || 0); + expectedStatus: (_account, _, status): Partial => { + const estimatedFees = status.estimatedFees; return { amount: new BigNumber(1e19), // 10 ETH estimatedFees, // fees are calculated during preparation and therefore cannot be guessed without mocks totalSpent: new BigNumber(1e19).plus(estimatedFees), // fees are calculated during preparation and therefore cannot be guessed without mocks errors: { amount: new NotEnoughBalance(), // "The parent account balance is insufficient for network fees" since account is empty - gasLimit: new FeeNotLoaded(), // "Could not load fee rates. Please set manual fees" because gas estimation failed as the account is empty }, warnings: {}, }; diff --git a/libs/coin-modules/coin-near/src/account.ts b/libs/coin-modules/coin-near/src/account.ts index a55f2b6441f1..23679cc95401 100644 --- a/libs/coin-modules/coin-near/src/account.ts +++ b/libs/coin-modules/coin-near/src/account.ts @@ -24,8 +24,8 @@ function formatAccountSpecifics(account: NearAccount): string { str += `\n Staking Positions:\n`; str += nearResources.stakingPositions .map( - ({ validatorId, staked, pending, available, rewards }) => - ` Validator ID: ${validatorId} | Staked: ${staked} | Pending Release: ${pending} | Available: ${available} | Rewards: ${rewards}`, + ({ validatorId, staked, pending, available }) => + ` Validator ID: ${validatorId} | Staked: ${staked} | Pending Release: ${pending} | Available: ${available}`, ) .join("\n"); } diff --git a/libs/coin-modules/coin-near/src/api/node.ts b/libs/coin-modules/coin-near/src/api/node.ts index 789e39a584be..cc8c35fe16c1 100644 --- a/libs/coin-modules/coin-near/src/api/node.ts +++ b/libs/coin-modules/coin-near/src/api/node.ts @@ -217,7 +217,7 @@ export const getStakingPositions = async ( const activeDelegatedStakeBalance = await account.getActiveDelegatedStakeBalance(); const stakingPositions = await Promise.all( - activeDelegatedStakeBalance.stakedValidators.map(async ({ validatorId, amount: rawTotal }) => { + activeDelegatedStakeBalance.stakedValidators.map(async ({ validatorId }) => { const contract = new nearAPI.Contract(account, validatorId, { viewMethods: [ "get_account_staked_balance", @@ -248,12 +248,10 @@ export const getStakingPositions = async ( available = unstaked; pending = new BigNumber(0); } - let rewards = new BigNumber(0); const staked = new BigNumber(rawStaked); available = new BigNumber(available); pending = new BigNumber(pending); - rewards = new BigNumber(rawTotal || "0").minus(staked); const stakingThreshold = getYoctoThreshold(); @@ -271,7 +269,6 @@ export const getStakingPositions = async ( staked, available, pending, - rewards: rewards.gt(0) ? rewards : new BigNumber(0), validatorId, }; }), diff --git a/libs/coin-modules/coin-near/src/api/sdk.types.ts b/libs/coin-modules/coin-near/src/api/sdk.types.ts index ee87b16322df..2aac91f78d4a 100644 --- a/libs/coin-modules/coin-near/src/api/sdk.types.ts +++ b/libs/coin-modules/coin-near/src/api/sdk.types.ts @@ -69,7 +69,6 @@ export type NearStakingPosition = { staked: BigNumber; available: BigNumber; pending: BigNumber; - rewards: BigNumber; validatorId: string; }; diff --git a/libs/coin-modules/coin-near/src/logic.ts b/libs/coin-modules/coin-near/src/logic.ts index 29c326b02ce5..ef204c186ccf 100644 --- a/libs/coin-modules/coin-near/src/logic.ts +++ b/libs/coin-modules/coin-near/src/logic.ts @@ -119,7 +119,6 @@ export const mapStakingPositions = ( return { ...sp, formattedAmount: formatCurrencyUnit(unit, sp.staked, formatConfig), - formattedRewards: formatCurrencyUnit(unit, sp.rewards, formatConfig), formattedPending: formatCurrencyUnit(unit, sp.pending, formatConfig), formattedAvailable: formatCurrencyUnit(unit, sp.available, formatConfig), rank, diff --git a/libs/coin-modules/coin-near/src/serialization.ts b/libs/coin-modules/coin-near/src/serialization.ts index 715f71222d7c..e15332ef49e2 100644 --- a/libs/coin-modules/coin-near/src/serialization.ts +++ b/libs/coin-modules/coin-near/src/serialization.ts @@ -10,15 +10,12 @@ export function toNearResourcesRaw(r: NearResources): NearResourcesRaw { pendingBalance: pendingBalance.toString(), availableBalance: availableBalance.toString(), storageUsageBalance: storageUsageBalance.toString(), - stakingPositions: stakingPositions.map( - ({ staked, validatorId, available, pending, rewards }) => ({ - staked: staked.toString(), - available: available.toString(), - pending: pending.toString(), - rewards: rewards.toString(), - validatorId, - }), - ), + stakingPositions: stakingPositions.map(({ staked, validatorId, available, pending }) => ({ + staked: staked.toString(), + available: available.toString(), + pending: pending.toString(), + validatorId, + })), }; } @@ -35,15 +32,12 @@ export function fromNearResourcesRaw(r: NearResourcesRaw): NearResources { pendingBalance: new BigNumber(pendingBalance), availableBalance: new BigNumber(availableBalance), storageUsageBalance: new BigNumber(storageUsageBalance), - stakingPositions: stakingPositions.map( - ({ staked, validatorId, available, pending, rewards }) => ({ - staked: new BigNumber(staked), - available: new BigNumber(available), - pending: new BigNumber(pending), - rewards: new BigNumber(rewards), - validatorId, - }), - ), + stakingPositions: stakingPositions.map(({ staked, validatorId, available, pending }) => ({ + staked: new BigNumber(staked), + available: new BigNumber(available), + pending: new BigNumber(pending), + validatorId, + })), }; } diff --git a/libs/coin-modules/coin-near/src/types.ts b/libs/coin-modules/coin-near/src/types.ts index 7b8affe9203b..bdbb2e5abb13 100644 --- a/libs/coin-modules/coin-near/src/types.ts +++ b/libs/coin-modules/coin-near/src/types.ts @@ -54,7 +54,6 @@ export type NearResourcesRaw = { staked: string; available: string; pending: string; - rewards: string; validatorId: string; }[]; }; @@ -73,7 +72,6 @@ export type NearValidatorItem = { export type NearMappedStakingPosition = NearStakingPosition & { formattedAmount: string; - formattedRewards: string; formattedPending: string; formattedAvailable: string; rank: number; diff --git a/libs/ledger-live-common/src/currencies/helpers.ts b/libs/ledger-live-common/src/currencies/helpers.ts index 0c50932a4fd7..70c839fa1e05 100644 --- a/libs/ledger-live-common/src/currencies/helpers.ts +++ b/libs/ledger-live-common/src/currencies/helpers.ts @@ -16,6 +16,10 @@ export function isTokenCurrency(currency: Currency): currency is TokenCurrency { return currency.type === "TokenCurrency"; } +export function isUTXOCompliant(currencyFamilly: string): boolean { + return currencyFamilly === "bitcoin" || currencyFamilly === "cardano"; +} + export function listCurrencies(includeTokens: boolean): CryptoOrTokenCurrency[] { const currencies = listSupportedCurrencies(); diff --git a/libs/ledger-live-common/src/exchange/platform/startExchange.ts b/libs/ledger-live-common/src/exchange/platform/startExchange.ts index 959292d301e4..1bfbce785695 100644 --- a/libs/ledger-live-common/src/exchange/platform/startExchange.ts +++ b/libs/ledger-live-common/src/exchange/platform/startExchange.ts @@ -30,7 +30,7 @@ const startExchange = (input: StartExchangeInput): Observable { await withDevicePromise(deviceId, async transport => { - const providerNameAndSignature = getProviderConfig(exchangeType, provider); + const providerNameAndSignature = await getProviderConfig(exchangeType, provider); if (!providerNameAndSignature) throw new Error("Could not get provider infos"); diff --git a/libs/ledger-live-common/src/exchange/providers/getProvidersData.test.ts b/libs/ledger-live-common/src/exchange/providers/getProvidersData.test.ts new file mode 100644 index 000000000000..1f1ceef705dc --- /dev/null +++ b/libs/ledger-live-common/src/exchange/providers/getProvidersData.test.ts @@ -0,0 +1,106 @@ +import { transformData, getProvidersData, ProvidersDataResponse } from "./getProvidersData"; +import network from "@ledgerhq/live-network"; + +type TransformedProviderData = { + name: string; + publicKey: { + curve: string; + data: Buffer; + }; + signature: Buffer; +}; + +jest.mock("@ledgerhq/live-network"); + +describe("transformData", () => { + it("should transform providers data correctly", () => { + const providersData: ProvidersDataResponse = [ + { + name: "ProviderA", + signature: "a1b2c3", + public_key: "1234567890abcdef", + public_key_curve: "secp256k1", + }, + { + name: "ProviderB", + signature: "d4e5f6", + public_key: "abcdef1234567890", + public_key_curve: "secp256r1", + }, + ]; + + const expected: Record = { + providera: { + name: "ProviderA", + publicKey: { + curve: "secp256k1", + data: Buffer.from("1234567890abcdef", "hex"), + }, + signature: Buffer.from("a1b2c3", "hex"), + }, + providerb: { + name: "ProviderB", + publicKey: { + curve: "secp256r1", + data: Buffer.from("abcdef1234567890", "hex"), + }, + signature: Buffer.from("d4e5f6", "hex"), + }, + }; + + const result = transformData(providersData); + expect(result).toEqual(expected); + }); + + it("should handle empty providers data", () => { + const providersData: ProvidersDataResponse = []; + + const expected: Record = {}; + + const result = transformData(providersData); + expect(result).toEqual(expected); + }); +}); + +describe("getProvidersData", () => { + it("should fetch and transform providers data", async () => { + const mockProvidersData: ProvidersDataResponse = [ + { + name: "ProviderA", + signature: "a1b2c3", + public_key: "1234567890abcdef", + public_key_curve: "secp256k1", + }, + ]; + + (network as jest.Mock).mockResolvedValue({ data: mockProvidersData }); + + const result = await getProvidersData("someType"); + + expect(network).toHaveBeenCalledWith({ + method: "GET", + url: "https://crypto-assets-service.api.ledger.com/v1/partners", + params: { + output: "name,signature,public_key,public_key_curve", + service_name: "someType", + }, + }); + + expect(result).toEqual({ + providera: { + name: "ProviderA", + publicKey: { + curve: "secp256k1", + data: Buffer.from("1234567890abcdef", "hex"), + }, + signature: Buffer.from("a1b2c3", "hex"), + }, + }); + }); + + it("should handle errors when fetching data", async () => { + (network as jest.Mock).mockRejectedValue(new Error("Network error")); + + await expect(getProvidersData("someType")).rejects.toThrow("Network error"); + }); +}); diff --git a/libs/ledger-live-common/src/exchange/providers/getProvidersData.ts b/libs/ledger-live-common/src/exchange/providers/getProvidersData.ts new file mode 100644 index 000000000000..ac00655ed859 --- /dev/null +++ b/libs/ledger-live-common/src/exchange/providers/getProvidersData.ts @@ -0,0 +1,42 @@ +import { ExchangeProviderNameAndSignature } from "."; +import network from "@ledgerhq/live-network"; + +export type ProvidersDataResponse = { + name: string; + signature: string; + public_key: string; + public_key_curve: string; +}[]; + +export function transformData( + providersData: ProvidersDataResponse, +): Record { + const transformed = {}; + providersData.forEach(provider => { + const key = provider.name.toLowerCase(); + transformed[key] = { + name: provider.name, + publicKey: { + curve: provider.public_key_curve, + data: Buffer.from(provider.public_key, "hex"), + }, + signature: Buffer.from(provider.signature, "hex"), + }; + }); + return transformed; +} + +export const getProvidersData = async ( + type, +): Promise> => { + const { data: providersData } = await network({ + method: "GET", + url: "https://crypto-assets-service.api.ledger.com/v1/partners", + params: { + output: "name,signature,public_key,public_key_curve", + service_name: type, + }, + }); + + return transformData(providersData); +}; diff --git a/libs/ledger-live-common/src/exchange/providers/index.ts b/libs/ledger-live-common/src/exchange/providers/index.ts index da7bbef7e93e..e0ce5b22e165 100644 --- a/libs/ledger-live-common/src/exchange/providers/index.ts +++ b/libs/ledger-live-common/src/exchange/providers/index.ts @@ -26,10 +26,10 @@ export function convertToAppExchangePartnerKey( }; } -export const getProviderConfig = ( +export const getProviderConfig = async ( exchangeType: ExchangeTypes, providerName: string, -): ExchangeProviderNameAndSignature => { +): Promise => { if (getEnv("MOCK_EXCHANGE_TEST_CONFIG") && testProvider) { return testProvider; } @@ -41,7 +41,7 @@ export const getProviderConfig = ( case ExchangeTypes.Sell: case ExchangeTypes.SellNg: - return getSellProvider(providerName.toLowerCase()); + return await getSellProvider(providerName.toLowerCase()); default: throw new Error(`Unknown partner ${providerName} type`); diff --git a/libs/ledger-live-common/src/exchange/providers/sell.ts b/libs/ledger-live-common/src/exchange/providers/sell.ts index d820242d6b22..07a8ae3924b4 100644 --- a/libs/ledger-live-common/src/exchange/providers/sell.ts +++ b/libs/ledger-live-common/src/exchange/providers/sell.ts @@ -1,5 +1,6 @@ import { getEnv } from "@ledgerhq/live-env"; import { ExchangeProviderNameAndSignature } from "."; +import { getProvidersData } from "./getProvidersData"; const testSellProvider: ExchangeProviderNameAndSignature = { name: "SELL_TEST", @@ -17,33 +18,45 @@ const testSellProvider: ExchangeProviderNameAndSignature = { version: 2, }; -const sellProviders: Record = { - coinify: { - name: "Coinify", - publicKey: { - curve: "secp256r1", - data: Buffer.from( - "04CEA7DC8B189FA0D7A5A97530B50556CB0C14079C39CD44532D7037F2B96F0FA9C2DE588E1840B351B71114EE4021FC260F790A6F2D0CDF1C3E1899CCF97D3CCB", - "hex", - ), - }, - signature: Buffer.from( - "3043021f023ecbbb1dfd44f390944bd1f6c039942943009a51ca4f134589441476651a02200cbfdf2ebe32eb0b0a88be9b1fec343ed5b230a69e65a1d15b4e34ef4206a9dd", - "hex", - ), - }, +let providerDataCache: Record | null = null; + +/** + * The result is cached after the first successful fetch to avoid redundant network calls. + * - "fund" providers currently include Mercuryo, which is stored as a fund provider. + * If this behavior changes, this logic should be revisited. + * Reference: https://github.dev/LedgerHQ/crypto-assets/blob/main/assets/partners/mercuryo/common.json + */ +export const fetchAndMergeProviderData = async () => { + if (providerDataCache) { + return providerDataCache; + } + try { + const [sellProvidersData, fundProviderData] = await Promise.all([ + getProvidersData("sell"), + getProvidersData("fund"), // Mercuryo is currently treated as a fund provider + ]); + providerDataCache = { ...sellProvidersData, ...fundProviderData }; + return providerDataCache; + } catch (error) { + console.error("Error fetching or processing provider data:", error); + } }; -export const getSellProvider = (providerName: string): ExchangeProviderNameAndSignature => { +export const getSellProvider = async ( + providerName: string, +): Promise => { if (getEnv("MOCK_EXCHANGE_TEST_CONFIG")) { return testSellProvider; } - - const res = sellProviders[providerName.toLowerCase()]; + const res = await fetchAndMergeProviderData(); if (!res) { + throw new Error("Failed to fetch provider data"); + } + const provider = res[providerName.toLowerCase()]; + if (!provider) { throw new Error(`Unknown partner ${providerName}`); } - return res; + return provider; }; diff --git a/libs/ledger-live-common/src/exchange/providers/swap.ts b/libs/ledger-live-common/src/exchange/providers/swap.ts index 9338a69f9c1d..3a18b928e18f 100644 --- a/libs/ledger-live-common/src/exchange/providers/swap.ts +++ b/libs/ledger-live-common/src/exchange/providers/swap.ts @@ -1,5 +1,6 @@ import { ExchangeProviderNameAndSignature } from "."; import { isIntegrationTestEnv } from "../swap/utils/isIntegrationTestEnv"; +import { getProvidersData } from "./getProvidersData"; import network from "@ledgerhq/live-network"; export type SwapProviderConfig = { @@ -185,22 +186,6 @@ type CurrencyData = { signature: string; }; -type ProvidersDataResponse = { - name: string; - signature: string; - public_key: string; - public_key_curve: string; -}[]; - -type ProviderData = { - name: string; - publicKey: { - curve: string; - data: Buffer; - }; - signature: Buffer; -}; - let providerDataCache: Record | null = null; export const getSwapProvider = async ( @@ -215,35 +200,6 @@ export const getSwapProvider = async ( return res[providerName.toLowerCase()]; }; -function transformData(providersData: ProvidersDataResponse): Record { - const transformed = {}; - providersData.forEach(provider => { - const key = provider.name.toLowerCase(); - transformed[key] = { - name: provider.name, - publicKey: { - curve: provider.public_key_curve, - data: Buffer.from(provider.public_key, "hex"), - }, - signature: Buffer.from(provider.signature, "hex"), - }; - }); - return transformed; -} - -export const getProvidersData = async (): Promise> => { - const { data: providersData } = await network({ - method: "GET", - url: "https://crypto-assets-service.api.ledger.com/v1/partners", - params: { - output: "name,signature,public_key,public_key_curve", - service_name: "swap", - }, - }); - - return transformData(providersData); -}; - /** * Retrieves the currency data for a given ID * @param currencyId The unique identifier for the currency. @@ -285,7 +241,7 @@ export const fetchAndMergeProviderData = async () => { try { const [providersData, providersExtraData] = await Promise.all([ - getProvidersData(), + getProvidersData("swap"), getProvidersCDNData(), ]); diff --git a/libs/ledger-live-common/src/families/evm/config.ts b/libs/ledger-live-common/src/families/evm/config.ts index 25e97982bfbc..dd667bf20277 100644 --- a/libs/ledger-live-common/src/families/evm/config.ts +++ b/libs/ledger-live-common/src/families/evm/config.ts @@ -550,8 +550,8 @@ const evmConfig: CurrencyLiveConfigDefinition = { uri: "https://zkevm-rpc.com", }, explorer: { - type: "etherscan", - uri: "https://api-zkevm.polygonscan.com/api", + type: "blockscout", + uri: "https://zkevm.blockscout.com/api", }, }, }, @@ -566,8 +566,8 @@ const evmConfig: CurrencyLiveConfigDefinition = { uri: "https://developer-access-mainnet.base.org", }, explorer: { - type: "etherscan", - uri: "https://api.basescan.org/api", + type: "blockscout", + uri: "https://base.blockscout.com/api", }, }, }, diff --git a/libs/ledger-live-common/src/families/near/__snapshots__/bridge.integration.test.ts.snap b/libs/ledger-live-common/src/families/near/__snapshots__/bridge.integration.test.ts.snap index e987b6ac5b45..966e6cc2e0a3 100644 --- a/libs/ledger-live-common/src/families/near/__snapshots__/bridge.integration.test.ts.snap +++ b/libs/ledger-live-common/src/families/near/__snapshots__/bridge.integration.test.ts.snap @@ -18,7 +18,6 @@ exports[`near currency bridge scanAccounts near seed 1 1`] = ` { "available": "10000000000000000001", "pending": "0", - "rewards": "10000000000000000001", "staked": "0", "validatorId": "figment.poolv1.near", }, diff --git a/libs/ledger-live-common/src/families/near/banner.test.ts b/libs/ledger-live-common/src/families/near/banner.test.ts index 78852671f140..e6b54dc19a0f 100644 --- a/libs/ledger-live-common/src/families/near/banner.test.ts +++ b/libs/ledger-live-common/src/families/near/banner.test.ts @@ -125,7 +125,6 @@ describe("near/banner", () => { staked: new BigNumber("1.29802125309300073830514e+23"), available: new BigNumber("1"), pending: new BigNumber("0"), - rewards: new BigNumber("1.462125309300073830515e+21"), validatorId: "vcap.poolv1.near", }; jest.spyOn(preloadedData, "getCurrentNearPreloadData").mockReturnValue(validatorsMap); diff --git a/libs/ledger-live-common/src/wallet-api/constants.ts b/libs/ledger-live-common/src/wallet-api/constants.ts index dec4009fd284..d5cf195b1f53 100644 --- a/libs/ledger-live-common/src/wallet-api/constants.ts +++ b/libs/ledger-live-common/src/wallet-api/constants.ts @@ -49,4 +49,6 @@ export const BUY_SELL_UI_APP_ID = "buy-sell-ui"; export const CARD_APP_ID = "card-program"; +export const WC_ID = "ledger-wallet-connect"; + export const INTERNAL_APP_IDS = [DEFAULT_MULTIBUY_APP_ID, BUY_SELL_UI_APP_ID, CARD_APP_ID]; diff --git a/libs/ledgerjs/packages/context-module/package.json b/libs/ledgerjs/packages/context-module/package.json index a455812e585a..464e14501e65 100644 --- a/libs/ledgerjs/packages/context-module/package.json +++ b/libs/ledgerjs/packages/context-module/package.json @@ -2,6 +2,7 @@ "name": "@ledgerhq/context-module", "version": "0.1.2", "description": "Ledger context module", + "private": true, "files": [ "./lib" ], @@ -29,4 +30,4 @@ "axios": "1.7.7", "ethers": "^5.7.2" } -} \ No newline at end of file +} diff --git a/libs/ledgerjs/packages/keyring-eth/package.json b/libs/ledgerjs/packages/keyring-eth/package.json index fa0e36fc0280..df8deaabfa34 100644 --- a/libs/ledgerjs/packages/keyring-eth/package.json +++ b/libs/ledgerjs/packages/keyring-eth/package.json @@ -2,6 +2,7 @@ "name": "@ledgerhq/keyring-eth", "version": "0.2.4", "description": "Ledger keyring eth module", + "private": true, "files": [ "./lib" ], diff --git a/libs/live-nft-react/src/hooks/useFetchOrdinals.ts b/libs/live-nft-react/src/hooks/useFetchOrdinals.ts index 0464912acbba..dac2bd116ac0 100644 --- a/libs/live-nft-react/src/hooks/useFetchOrdinals.ts +++ b/libs/live-nft-react/src/hooks/useFetchOrdinals.ts @@ -6,9 +6,15 @@ import { FetchNftsProps, OrdinalsChainsEnum } from "./types"; import { NFTS_QUERY_KEY } from "../queryKeys"; import { processOrdinals } from "./helpers/ordinals"; -type Result = UseInfiniteQueryResult, Error> & { +export interface GroupedNftOrdinals { + rareSat: SimpleHashNft; + inscription: SimpleHashNft; +} + +export type Result = UseInfiniteQueryResult, Error> & { rareSats: SimpleHashNft[]; inscriptions: SimpleHashNft[]; + inscriptionsGroupedWithRareSats: GroupedNftOrdinals[]; }; export function useFetchOrdinals({ addresses, threshold }: FetchNftsProps): Result { @@ -35,6 +41,18 @@ export function useFetchOrdinals({ addresses, threshold }: FetchNftsProps): Resu const { rareSats, inscriptions } = useMemo(() => processOrdinals(nfts), [nfts]); + const inscriptionsGroupedWithRareSats: GroupedNftOrdinals[] = []; + + rareSats.forEach(sat => { + const inscription = inscriptions.find(inscription => { + const location = inscription.extra_metadata?.ordinal_details?.location?.split(":")[0]; + return location === sat.contract_address; + }); + if (inscription) { + inscriptionsGroupedWithRareSats.push({ rareSat: sat, inscription }); + } + }); + useEffect(() => { if (queryResult.hasNextPage && !queryResult.isFetchingNextPage) { queryResult.fetchNextPage(); @@ -45,5 +63,6 @@ export function useFetchOrdinals({ addresses, threshold }: FetchNftsProps): Resu ...queryResult, rareSats, inscriptions, + inscriptionsGroupedWithRareSats, }; } diff --git a/libs/live-nft/src/api/types.ts b/libs/live-nft/src/api/types.ts index 4491f38c75da..acddedd2dda2 100644 --- a/libs/live-nft/src/api/types.ts +++ b/libs/live-nft/src/api/types.ts @@ -68,6 +68,46 @@ export interface preview { readonly predominate_color: string; } +export interface video_properties { + readonly audio_coding: string; + readonly duration: number; + readonly height: number; + readonly mime_type: string; + readonly size: number; + readonly video_coding: string; + readonly width: number; +} + +export interface owner { + readonly first_acquired_date: string; + readonly last_acquired_date: string; + readonly owner_address: string; + readonly quantity: number; + readonly quantity_string: string; +} + +export interface collection { + readonly name: string; + readonly spam_score: number; + readonly description?: string; +} + +export interface firstCreated { + readonly block_number: number; + readonly minted_to: string; + readonly quantity: number; + readonly quantity_string: string; + readonly timestamp: string; + readonly transaction: string; + readonly transaction_initiator: string; +} + +export interface attribute { + readonly trait_type: string; + readonly value: string; + readonly display_type: string; +} + export interface SimpleHashNft { readonly nft_id: string; readonly chain: string; @@ -79,10 +119,11 @@ export interface SimpleHashNft { readonly token_count: number; readonly previews?: preview; readonly other_url?: string; - readonly collection: { - readonly name: string; - readonly spam_score: number; - }; + readonly video_url?: string; + readonly video_properties?: video_properties; + readonly owners?: owner[]; + readonly collection: collection; + readonly first_created?: firstCreated; readonly contract: { readonly type: string; readonly name?: string; @@ -91,6 +132,7 @@ export interface SimpleHashNft { readonly ledger_metadata?: { readonly ledger_stax_image: string; }; + readonly attributes?: attribute[]; readonly utxo_details?: UtxoDetails; readonly ordinal_details?: ordinal_details; readonly image_original_url: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5943e68ee2e1..9107c46762e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -536,8 +536,8 @@ importers: specifier: ^7.1.3 version: 7.5.4 storyly-web: - specifier: ^2.8.0 - version: 2.16.5 + specifier: ^3.5.0 + version: 3.6.7 styled-components: specifier: ^5.3.3 version: 5.3.11(react-dom@18.2.0(react@18.2.0))(react-is@17.0.2)(react@18.2.0) @@ -12347,10 +12347,6 @@ packages: resolution: {integrity: sha512-yqcRVnjf+qS+tC4NxOKLJOaSJ+csHmh/dHUzvCTkf5rLsplwXYRnny2r0tqGTQ4tuXMxwgSMKPYwicg81P+xuw==} engines: {node: '>=12'} - '@sentry-internal/feedback@7.109.0': - resolution: {integrity: sha512-EL7N++poxvJP9rYvh6vSu24tsKkOveNCcCj4IM7+irWPjsuD2GLYYlhp/A/Mtt9l7iqO4plvtiQU5HGk7smcTQ==} - engines: {node: '>=12'} - '@sentry-internal/feedback@8.16.0': resolution: {integrity: sha512-BmRazZKl6iiVSg6eybUNOI1ve4eZqYpJYjkX48Jedn+7iZg7z12MNYl6IWPFBcN+sg+clf4wiKDr/SYS0yNemQ==} engines: {node: '>=14.18'} @@ -12359,10 +12355,6 @@ packages: resolution: {integrity: sha512-TnqxqJGhbFhhYRhTG2WLFer+lVieV7mNGeIxFBiw1L4kuj8KGl+C0sknssKyZSRVJFSahhHIosHJGRMkkD//7g==} engines: {node: '>=12'} - '@sentry-internal/replay-canvas@7.109.0': - resolution: {integrity: sha512-Lh/K60kmloR6lkPUcQP0iamw7B/MdEUEx/ImAx4tUSMrLj+IoUEcq/ECgnnVyQkJq59+8nPEKrVLt7x6PUPEjw==} - engines: {node: '>=12'} - '@sentry-internal/replay-canvas@8.16.0': resolution: {integrity: sha512-Bjh6pCDLZIPAPU2dNvJfI7BQV16rsRtYcylJgkGamjf8IcaBu7r/Whsvt1q34xO29xc0ISlp+0xG+YAdN1690Q==} engines: {node: '>=14.18'} @@ -12375,18 +12367,10 @@ packages: resolution: {integrity: sha512-+u9RRf5eL3StiyiRyAHZmdkAR7GTSGx4Mt4Lmi5NEtCcWlTGZ1QgW2r8ZbhouVmTiJkjhQgYCyej3cojtazeJg==} engines: {node: '>=8'} - '@sentry-internal/tracing@7.109.0': - resolution: {integrity: sha512-PzK/joC5tCuh2R/PRh+7dp+uuZl7pTsBIjPhVZHMTtb9+ls65WkdZJ1/uKXPouyz8NOo9Xok7aEvEo9seongyw==} - engines: {node: '>=8'} - '@sentry/browser@7.100.1': resolution: {integrity: sha512-IxHQ08ixf0bmaWpe4yt1J4UUsOpg02fxax9z3tOQYXw5MSzz5pDXn8M8DFUVJB3wWuyXhHXTub9yD3VIP9fnoA==} engines: {node: '>=8'} - '@sentry/browser@7.109.0': - resolution: {integrity: sha512-yx+OFG+Ab9qUDDgV9ZDv8M9O9Mqr0fjKta/LMlWALYLjzkMvxsPlRPFj7oMBlHqOTVLDeg7lFYmsA8wyWQ8Z8g==} - engines: {node: '>=8'} - '@sentry/browser@8.16.0': resolution: {integrity: sha512-8Fxmk2aFWRixi2IKixiJR10Du34yb13HYr2iRw1haPKb5ZKa6CFA+XAnSzwpPZxO0RSHuPQR06YNkXaQ8fRAQQ==} engines: {node: '>=14.18'} @@ -12491,10 +12475,6 @@ packages: resolution: {integrity: sha512-f+ItUge/o9AjlveQq0ZUbQauKlPH1FIJbC1TRaYLJ4KNfOdrsh8yZ29RmWv0cFJ/e+FGTr603gWpRPObF5rM8Q==} engines: {node: '>=8'} - '@sentry/core@7.109.0': - resolution: {integrity: sha512-xwD4U0IlvvlE/x/g/W1I8b4Cfb16SsCMmiEuBf6XxvAa3OfWBxKoqLifb3GyrbxMC4LbIIZCN/SvLlnGJPgszA==} - engines: {node: '>=8'} - '@sentry/core@8.16.0': resolution: {integrity: sha512-l9mQgm5OqnykvZMh6PmJ/9ygW4qLyEFop+pQH/uM5zQCZQvEa7rvAd9QXKHdbVKq1CxJa/nJiByc8wPWxsftGQ==} engines: {node: '>=14.18'} @@ -12557,10 +12537,6 @@ packages: resolution: {integrity: sha512-B1NFjzGEFaqejxBRdUyEzH8ChXc2kfiqlA/W/Lg0aoWIl2/7nuMk+l4ld9gW5F5bIAXDTVd5vYltb1lWEbpr7w==} engines: {node: '>=12'} - '@sentry/replay@7.109.0': - resolution: {integrity: sha512-hCDjbTNO7ErW/XsaBXlyHFsUhneyBUdTec1Swf98TFEfVqNsTs6q338aUcaR8dGRLbLrJ9YU9D1qKq++v5h2CA==} - engines: {node: '>=12'} - '@sentry/types@6.19.7': resolution: {integrity: sha512-jH84pDYE+hHIbVnab3Hr+ZXr1v8QABfhx39KknxqKWr2l0oEItzepV0URvbEhB446lk/S/59230dlUUIBGsXbg==} engines: {node: '>=6'} @@ -12569,10 +12545,6 @@ packages: resolution: {integrity: sha512-fLM+LedHuKzOd8IhXBqaQuym+AA519MGjeczBa5kGakes/BbAsUMwsNfjsKQedp7Kh44RgYF99jwoRPK2oDrXw==} engines: {node: '>=8'} - '@sentry/types@7.109.0': - resolution: {integrity: sha512-egCBnDv3YpVFoNzRLdP0soVrxVLCQ+rovREKJ1sw3rA2/MFH9WJ+DZZexsX89yeAFzy1IFsCp7/dEqudusml6g==} - engines: {node: '>=8'} - '@sentry/types@8.16.0': resolution: {integrity: sha512-cIRsn7gWGVaWHgCniBWA0N8PNwzDYibhjyjPRTMxUjuZCT37i7zxByKKmd9u4TpRIJ64MyirNyM0O6T0A26fpg==} engines: {node: '>=14.18'} @@ -12585,10 +12557,6 @@ packages: resolution: {integrity: sha512-Ve6dXr1o6xiBe3VCoJgiutmBKrugryI65EZAbYto5XI+t+PjiLLf9wXtEMF24ZrwImo4Lv3E9Uqza+fWkEbw6A==} engines: {node: '>=8'} - '@sentry/utils@7.109.0': - resolution: {integrity: sha512-3RjxMOLMBwZ5VSiH84+o/3NY2An4Zldjz0EbfEQNRY9yffRiCPJSQiCJID8EoylCFOh/PAhPimBhqbtWJxX6iw==} - engines: {node: '>=8'} - '@sentry/utils@8.16.0': resolution: {integrity: sha512-tltCf2DVzz5TiYjxu/Rxbc9Qmm04893MFshV97jOTBcQeO2AAZBEl5rAoTCv1P08y7Yg+KiVwCx9Zj2x5U80/g==} engines: {node: '>=14.18'} @@ -17792,9 +17760,6 @@ packages: resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} engines: {node: '>= 0.8'} - copy-to-clipboard@3.3.3: - resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} - copy-to@2.0.1: resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==} @@ -18567,9 +18532,6 @@ packages: des.js@1.1.0: resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} - desandro-matches-selector@2.0.2: - resolution: {integrity: sha512-+1q0nXhdzg1IpIJdMKalUwvvskeKnYyEe3shPRwedNcWtnhEKT3ZxvFjzywHDeGcKViIxTCAoOYQWP1qD7VNyg==} - destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -19487,9 +19449,6 @@ packages: resolution: {integrity: sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==} engines: {node: '>=6.5.0', npm: '>=3'} - ev-emitter@1.1.1: - resolution: {integrity: sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q==} - event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} @@ -20324,9 +20283,6 @@ packages: firebase@9.23.0: resolution: {integrity: sha512-/4lUVY0lUvBDIaeY1q6dUYhS8Sd18Qb9CgWkPZICUo9IXpJNCEagfNZXBBFCkMTTN5L5gx2Hjr27y21a9NzUcA==} - fizzy-ui-utils@2.0.7: - resolution: {integrity: sha512-CZXDVXQ1If3/r8s0T+v+qVeMshhfcuq0rqIFgJnrtd+Bu8GmDmqMjntjUePypVtjHXKJ6V4sw9zeyox34n9aCg==} - flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} @@ -20631,9 +20587,6 @@ packages: resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} engines: {node: '>=8'} - get-size@2.0.3: - resolution: {integrity: sha512-lXNzT/h/dTjTxRbm9BXb+SGxxzkm97h/PCIKtlN/CBCxxmkkIVV21udumMS93MuVTDX583gqc94v3RjuHmI+2Q==} - get-stream@3.0.0: resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} engines: {node: '>=4'} @@ -21866,12 +21819,6 @@ packages: peerDependencies: ws: '*' - isotope-horizontal@2.0.1: - resolution: {integrity: sha512-rTm8K3d3Xt/pQBOdZj06IJpNkAjL+my4SWQLxJhjnxWTdDEX+r5qBsdstOou9kIvJFkgLYaR+YOog+7en6kHXw==} - - isotope-layout@3.0.6: - resolution: {integrity: sha512-z2ZKablhocXhoNyWwzJPFd7u7FWbYbVJA51Nvsqsod8jH2ExGc1SwDsSWKE54e3PhXzqf2yZPhFSq/c2MR1arw==} - isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} @@ -23460,9 +23407,6 @@ packages: marky@1.2.5: resolution: {integrity: sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==} - masonry-layout@4.2.2: - resolution: {integrity: sha512-iGtAlrpHNyxaR19CvKC3npnEcAwszXoyJiI8ARV2ePi7fmYhIud25MHK8Zx4P0LCC4d3TNO9+rFa1KoK1OEOaA==} - match-sorter@6.3.4: resolution: {integrity: sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==} @@ -24719,9 +24663,6 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} - outlayer@2.1.1: - resolution: {integrity: sha512-+GplXsCQ3VrbGujAeHEzP9SXsBmJxzn/YdDSQZL0xqBmAWBmortu2Y9Gwdp9J0bgDQ8/YNIPMoBM13nTwZfAhw==} - outvariant@1.4.2: resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==} @@ -27756,8 +27697,8 @@ packages: prop-types: optional: true - storyly-web@2.16.5: - resolution: {integrity: sha512-5kb3krdsjNCiALAVWBFDfPtj6EVODujalCgppzD1eUgjByfdYC5+yTyqblUlk1AAd9evGzaxqhHut0W4Ph8LLA==} + storyly-web@3.6.7: + resolution: {integrity: sha512-2i7yOUF4xENFynQwDGZmGWLKHcXeTjhxJwtD8g4Vj163GI9Zw8WfLwsYgSZWgP1mMC+vq3iP+l8TgHuBd/r2rg==} stream-browserify@3.0.0: resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} @@ -28452,9 +28393,6 @@ packages: tocbot@4.25.0: resolution: {integrity: sha512-kE5wyCQJ40hqUaRVkyQ4z5+4juzYsv/eK+aqD97N62YH0TxFhzJvo22RUQQZdO3YnXAk42ZOfOpjVdy+Z0YokA==} - toggle-selection@1.0.6: - resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} - toidentifier@1.0.0: resolution: {integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==} engines: {node: '>=0.6'} @@ -33977,7 +33915,7 @@ snapshots: '@emotion/core@10.3.1(react@18.2.0)': dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.25.0 '@emotion/cache': 10.0.29 '@emotion/css': 10.0.27 '@emotion/serialize': 0.11.16 @@ -39659,12 +39597,6 @@ snapshots: '@sentry/types': 7.100.1 '@sentry/utils': 7.100.1 - '@sentry-internal/feedback@7.109.0': - dependencies: - '@sentry/core': 7.109.0 - '@sentry/types': 7.109.0 - '@sentry/utils': 7.109.0 - '@sentry-internal/feedback@8.16.0': dependencies: '@sentry/core': 8.16.0 @@ -39678,13 +39610,6 @@ snapshots: '@sentry/types': 7.100.1 '@sentry/utils': 7.100.1 - '@sentry-internal/replay-canvas@7.109.0': - dependencies: - '@sentry/core': 7.109.0 - '@sentry/replay': 7.109.0 - '@sentry/types': 7.109.0 - '@sentry/utils': 7.109.0 - '@sentry-internal/replay-canvas@8.16.0': dependencies: '@sentry-internal/replay': 8.16.0 @@ -39705,12 +39630,6 @@ snapshots: '@sentry/types': 7.100.1 '@sentry/utils': 7.100.1 - '@sentry-internal/tracing@7.109.0': - dependencies: - '@sentry/core': 7.109.0 - '@sentry/types': 7.109.0 - '@sentry/utils': 7.109.0 - '@sentry/browser@7.100.1': dependencies: '@sentry-internal/feedback': 7.100.1 @@ -39721,16 +39640,6 @@ snapshots: '@sentry/types': 7.100.1 '@sentry/utils': 7.100.1 - '@sentry/browser@7.109.0': - dependencies: - '@sentry-internal/feedback': 7.109.0 - '@sentry-internal/replay-canvas': 7.109.0 - '@sentry-internal/tracing': 7.109.0 - '@sentry/core': 7.109.0 - '@sentry/replay': 7.109.0 - '@sentry/types': 7.109.0 - '@sentry/utils': 7.109.0 - '@sentry/browser@8.16.0': dependencies: '@sentry-internal/browser-utils': 8.16.0 @@ -39834,11 +39743,6 @@ snapshots: '@sentry/types': 7.100.1 '@sentry/utils': 7.100.1 - '@sentry/core@7.109.0': - dependencies: - '@sentry/types': 7.109.0 - '@sentry/utils': 7.109.0 - '@sentry/core@8.16.0': dependencies: '@sentry/types': 8.16.0 @@ -39974,19 +39878,10 @@ snapshots: '@sentry/types': 7.100.1 '@sentry/utils': 7.100.1 - '@sentry/replay@7.109.0': - dependencies: - '@sentry-internal/tracing': 7.109.0 - '@sentry/core': 7.109.0 - '@sentry/types': 7.109.0 - '@sentry/utils': 7.109.0 - '@sentry/types@6.19.7': {} '@sentry/types@7.100.1': {} - '@sentry/types@7.109.0': {} - '@sentry/types@8.16.0': {} '@sentry/utils@6.19.7': @@ -39998,10 +39893,6 @@ snapshots: dependencies: '@sentry/types': 7.100.1 - '@sentry/utils@7.109.0': - dependencies: - '@sentry/types': 7.109.0 - '@sentry/utils@8.16.0': dependencies: '@sentry/types': 8.16.0 @@ -46240,7 +46131,7 @@ snapshots: babel-plugin-macros@2.8.0: dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.25.0 cosmiconfig: 6.0.0 resolve: 1.22.8 @@ -47833,10 +47724,6 @@ snapshots: depd: 2.0.0 keygrip: 1.1.0 - copy-to-clipboard@3.3.3: - dependencies: - toggle-selection: 1.0.6 - copy-to@2.0.1: {} copy-webpack-plugin@10.2.4(webpack@5.91.0(metro@0.80.8)): @@ -48685,7 +48572,7 @@ snapshots: date-fns@2.30.0: dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.25.0 dateformat@4.6.3: {} @@ -48904,8 +48791,6 @@ snapshots: inherits: 2.0.4 minimalistic-assert: 1.0.1 - desandro-matches-selector@2.0.2: {} - destroy@1.2.0: {} detect-indent@6.1.0: {} @@ -49144,7 +49029,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.25.0 csstype: 3.1.3 dom-serializer@0.2.2: @@ -49799,8 +49684,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.1(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) @@ -49857,23 +49742,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): - dependencies: - debug: 4.3.4 - enhanced-resolve: 5.16.0 - eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - fast-glob: 3.3.2 - get-tsconfig: 4.7.5 - is-core-module: 2.13.1 - is-glob: 4.0.3 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-node - - eslint-import-resolver-webpack - - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.4 @@ -49918,17 +49786,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - transitivePeerDependencies: - - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 @@ -50018,33 +49875,6 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): - dependencies: - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - hasown: 2.0.2 - is-core-module: 2.13.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.0 - semver: 6.3.1 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.2.2) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: array-includes: 3.1.8 @@ -50510,8 +50340,6 @@ snapshots: is-hex-prefixed: 1.0.0 strip-hex-prefix: 1.0.0 - ev-emitter@1.1.1: {} - event-emitter@0.3.5: dependencies: d: 1.0.2 @@ -50827,12 +50655,12 @@ snapshots: react: 18.2.0 react-native: 0.73.6(@babel/core@7.24.3)(react@18.2.0) - expo-font@11.4.0(67hrxnryf4ycl5nhvln5laaykq): + expo-font@11.4.0(hxmq25pkdf6fvmayb5kzqynb3q): dependencies: expo: 49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.8)(metro@0.80.8)(minimatch@5.1.6)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) fontfaceobserver: 2.3.0 optionalDependencies: - expo-asset: 8.10.1(tw2cl75ufjodqrrf2cewclqsqi) + expo-asset: 8.10.1(expo-constants@14.5.1(expo-modules-core@1.5.11)(expo@49.0.23)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(expo-modules-core@1.5.11(expo-constants@14.5.1)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(expo@49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.8)(metro@0.80.8)(minimatch@5.1.6)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) expo-constants: 14.4.2(expo-modules-core@1.5.11(expo-constants@14.5.1)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(expo@49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.8)(metro@0.80.8)(minimatch@5.1.6)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) expo-modules-core: 1.5.11(expo-constants@14.5.1)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) react: 18.2.0 @@ -50957,7 +50785,7 @@ snapshots: expo-asset: 8.10.1(tw2cl75ufjodqrrf2cewclqsqi) expo-constants: 14.4.2(expo-modules-core@1.5.11(expo-constants@14.5.1)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(expo@49.0.23(@babel/core@7.24.3)(@expo/metro-config@0.10.7)(expo-modules-core@1.5.11)(glob@7.2.3)(metro-core@0.80.8)(metro@0.80.8)(minimatch@5.1.6)(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0))(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(metro-resolver@0.80.8)(metro-transform-worker@0.80.8)(react@18.2.0))(react@18.2.0) expo-file-system: 15.4.5(tw2cl75ufjodqrrf2cewclqsqi) - expo-font: 11.4.0(67hrxnryf4ycl5nhvln5laaykq) + expo-font: 11.4.0(hxmq25pkdf6fvmayb5kzqynb3q) expo-keep-awake: 12.3.0(tw2cl75ufjodqrrf2cewclqsqi) expo-modules-autolinking: 1.5.1(55fu4l7dolnnxrys6pt2pnhfne) fbemitter: 3.0.0 @@ -51385,10 +51213,6 @@ snapshots: transitivePeerDependencies: - encoding - fizzy-ui-utils@2.0.7: - dependencies: - desandro-matches-selector: 2.0.2 - flat-cache@3.2.0: dependencies: flatted: 3.3.1 @@ -51726,8 +51550,6 @@ snapshots: get-port@5.1.1: {} - get-size@2.0.3: {} - get-stream@3.0.0: {} get-stream@4.1.0: @@ -52314,7 +52136,7 @@ snapshots: history@4.10.1: dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.25.0 loose-envify: 1.4.0 resolve-pathname: 3.0.0 tiny-invariant: 1.3.3 @@ -53174,18 +52996,6 @@ snapshots: dependencies: ws: 8.17.1 - isotope-horizontal@2.0.1: - dependencies: - isotope-layout: 3.0.6 - - isotope-layout@3.0.6: - dependencies: - desandro-matches-selector: 2.0.2 - fizzy-ui-utils: 2.0.7 - get-size: 2.0.3 - masonry-layout: 4.2.2 - outlayer: 2.1.1 - isstream@0.1.2: {} istanbul-lib-coverage@3.2.2: {} @@ -56430,11 +56240,6 @@ snapshots: marky@1.2.5: {} - masonry-layout@4.2.2: - dependencies: - get-size: 2.0.3 - outlayer: 2.1.1 - match-sorter@6.3.4: dependencies: '@babel/runtime': 7.24.1 @@ -58465,12 +58270,6 @@ snapshots: outdent@0.5.0: {} - outlayer@2.1.1: - dependencies: - ev-emitter: 1.1.1 - fizzy-ui-utils: 2.0.7 - get-size: 2.0.3 - outvariant@1.4.2: {} ow@0.17.0: @@ -62528,14 +62327,10 @@ snapshots: optionalDependencies: prop-types: 15.8.1 - storyly-web@2.16.5: + storyly-web@3.6.7: dependencies: - '@babel/runtime': 7.24.1 - '@sentry/browser': 7.109.0 + '@babel/runtime': 7.25.0 canvas-confetti: 1.9.2 - copy-to-clipboard: 3.3.3 - isotope-horizontal: 2.0.1 - isotope-layout: 3.0.6 platform: 1.3.6 swiper: 7.4.1 uuid: 8.3.2 @@ -63606,8 +63401,6 @@ snapshots: tocbot@4.25.0: {} - toggle-selection@1.0.6: {} - toidentifier@1.0.0: {} toidentifier@1.0.1: {} diff --git a/tools/actions/composites/setup-speculos_image/action.yml b/tools/actions/composites/setup-speculos_image/action.yml new file mode 100644 index 000000000000..498c5517fe54 --- /dev/null +++ b/tools/actions/composites/setup-speculos_image/action.yml @@ -0,0 +1,34 @@ +name: "Setup Speculos Image and Coin Apps" +description: "Uploading allure report to Allure Server" +inputs: + coinapps_path: + required: true + description: "Path to the coin apps folder" + speculos_tag: + required: false + description: "Speculos docker image tag" + bot_id: + description: "GitHub Bot ID" + bot_key: + description: "GitHub Bot private key" + +runs: + using: composite + + steps: + - name: Generate token + id: generate-token + uses: tibdex/github-app-token@v1 + with: + app_id: ${{ inputs.bot_id }} + private_key: ${{ inputs.bot_key }} + - name: Retrieving coin apps + uses: actions/checkout@v4 + with: + ref: master + repository: LedgerHQ/coin-apps + token: ${{ steps.generate-token.outputs.token }} + path: ${{ inputs.coinapps_path }} + - name: Pull docker image + run: docker pull ${{ inputs.speculos_tag }} + shell: bash diff --git a/turbo.json b/turbo.json index 47c9ae79538e..140d8db52b3d 100644 --- a/turbo.json +++ b/turbo.json @@ -68,7 +68,8 @@ }, "start": { "cache": false, - "dependsOn": ["^build"] + "dependsOn": ["^build"], + "persistent": true }, "unimported": { "cache": false