diff --git a/.changeset/chilled-points-argue.md b/.changeset/chilled-points-argue.md new file mode 100644 index 000000000000..71b02c5b50a4 --- /dev/null +++ b/.changeset/chilled-points-argue.md @@ -0,0 +1,7 @@ +--- +"live-mobile": minor +"@ledgerhq/coin-stellar": patch +"@ledgerhq/live-common": patch +--- + +Add Stellar memo input on the recipient selection step diff --git a/.changeset/clean-nails-laugh.md b/.changeset/clean-nails-laugh.md new file mode 100644 index 000000000000..d61bc854c70a --- /dev/null +++ b/.changeset/clean-nails-laugh.md @@ -0,0 +1,5 @@ +--- +"live-mobile": minor +--- + +LLM - Remove react-native-adjust SDK and all adjust related parts from the codebase diff --git a/.changeset/cyan-berries-refuse.md b/.changeset/cyan-berries-refuse.md new file mode 100644 index 000000000000..cda778b117bd --- /dev/null +++ b/.changeset/cyan-berries-refuse.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +LLM - Updated iOS Privacy Info file diff --git a/.changeset/dull-plums-cheer.md b/.changeset/dull-plums-cheer.md new file mode 100644 index 000000000000..3b47a462695f --- /dev/null +++ b/.changeset/dull-plums-cheer.md @@ -0,0 +1,5 @@ +--- +"live-mobile": patch +--- + +Fix the memo on the Solana send flow diff --git a/.changeset/eighty-geckos-smash.md b/.changeset/eighty-geckos-smash.md new file mode 100644 index 000000000000..3432442cfa65 --- /dev/null +++ b/.changeset/eighty-geckos-smash.md @@ -0,0 +1,6 @@ +--- +"live-mobile": minor +"@ledgerhq/live-common": patch +--- + +Display human readable errors when the send flow fails diff --git a/.changeset/few-bikes-act.md b/.changeset/few-bikes-act.md new file mode 100644 index 000000000000..9aab1a5d7ed1 --- /dev/null +++ b/.changeset/few-bikes-act.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": patch +--- + +Do not set `ptxSwapCoreExperiment` as `false` in analytics diff --git a/.changeset/green-rice-cheer.md b/.changeset/green-rice-cheer.md new file mode 100644 index 000000000000..028c42b9fd23 --- /dev/null +++ b/.changeset/green-rice-cheer.md @@ -0,0 +1,8 @@ +--- +"live-mobile": patch +--- + +Stax/Flex onboarding: + - Hide "Backup with Recover" section + - Auto redirect to Recover upsell between the onboarding and the post onboarding + diff --git a/.changeset/lazy-mayflies-talk.md b/.changeset/lazy-mayflies-talk.md new file mode 100644 index 000000000000..aaae8aa28a7d --- /dev/null +++ b/.changeset/lazy-mayflies-talk.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": minor +--- + +Fix overflow issues in Add account flow diff --git a/.changeset/nervous-kangaroos-mix.md b/.changeset/nervous-kangaroos-mix.md new file mode 100644 index 000000000000..730b1d01493d --- /dev/null +++ b/.changeset/nervous-kangaroos-mix.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": minor +--- + +fixed: swap history for tokens explorer link diff --git a/.changeset/new-plums-shout.md b/.changeset/new-plums-shout.md new file mode 100644 index 000000000000..77c94bf9f92c --- /dev/null +++ b/.changeset/new-plums-shout.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/coin-xrp": patch +--- + +Fix Cardano getAccountInfo diff --git a/.changeset/ninety-moles-listen.md b/.changeset/ninety-moles-listen.md new file mode 100644 index 000000000000..97a74a88fe6e --- /dev/null +++ b/.changeset/ninety-moles-listen.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/live-common": minor +--- + +fixed: thorswap get status send transactionId instead of LL ids diff --git a/.changeset/purple-peaches-cross.md b/.changeset/purple-peaches-cross.md new file mode 100644 index 000000000000..ba44696212f4 --- /dev/null +++ b/.changeset/purple-peaches-cross.md @@ -0,0 +1,6 @@ +--- +"ledger-live-desktop": patch +"live-mobile": patch +--- + +LLM / LLD - Update Analytics Opt In Prompt wording diff --git a/.changeset/slow-buses-end.md b/.changeset/slow-buses-end.md new file mode 100644 index 000000000000..d49e20e30a09 --- /dev/null +++ b/.changeset/slow-buses-end.md @@ -0,0 +1,6 @@ +--- +"@ledgerhq/types-live": patch +"@ledgerhq/live-common": patch +--- + +Add recoverUpsellRedirection feature flag diff --git a/.github/workflows/test-desktop-reusable.yml b/.github/workflows/test-desktop-reusable.yml index ed11840cfe59..ef97e5e9be8d 100644 --- a/.github/workflows/test-desktop-reusable.yml +++ b/.github/workflows/test-desktop-reusable.yml @@ -68,7 +68,7 @@ jobs: name: lint-desktop path: ${{ github.workspace }}/apps/ledger-live-desktop/lint-desktop.json - name: check for dead code - run: pnpm desktop unimported + run: pnpm desktop knip-check shell: bash unit-tests: diff --git a/.github/workflows/test-mobile-e2e-reusable.yml b/.github/workflows/test-mobile-e2e-reusable.yml index d8aa6cea9b56..a7fbb34e8a1e 100644 --- a/.github/workflows/test-mobile-e2e-reusable.yml +++ b/.github/workflows/test-mobile-e2e-reusable.yml @@ -139,7 +139,7 @@ jobs: allure-report-ios: name: "Allure Reports Export on Server" runs-on: [ledger-live-medium] - if: ${{ inputs.slack_notif || github.event_name == 'push' }} + if: ${{ always() && (inputs.slack_notif || github.event_name == 'push') }} needs: [detox-tests-ios] outputs: report-url: ${{ steps.upload.outputs.report-url }} @@ -290,7 +290,7 @@ jobs: allure-report-android: name: "Allure Reports Export on Server" runs-on: [ledger-live-medium] - if: ${{ inputs.slack_notif || github.event_name == 'push' }} + if: ${{ always() && (inputs.slack_notif || github.event_name == 'push') }} outputs: report-url: ${{ steps.upload.outputs.report-url }} result: ${{ steps.summary.outputs.test_result }} @@ -376,7 +376,7 @@ jobs: report-on-slack: runs-on: ubuntu-22.04 needs: [allure-report-android, allure-report-ios] - if: ${{ (failure() && github.event_name == 'push') || inputs.slack_notif }} + if: ${{ (failure() && github.event_name == 'push') || (always() && inputs.slack_notif) }} env: IOS_STATUS: ${{ needs.allure-report-ios.outputs.status }} IOS_REPORT_URL: ${{ needs.allure-report-ios.outputs.report-url }} diff --git a/.github/workflows/test-mobile-reusable.yml b/.github/workflows/test-mobile-reusable.yml index 8f55389d423f..db126e93fb13 100644 --- a/.github/workflows/test-mobile-reusable.yml +++ b/.github/workflows/test-mobile-reusable.yml @@ -53,7 +53,7 @@ jobs: - name: Run linter run: pnpm lint --filter="live-mobile" --api="http://127.0.0.1:${{ steps.toolchain.outputs.port }}" --token="${{ secrets.TURBOREPO_SERVER_TOKEN }}" --team="foo" -- --format="json" -o="lint-mobile.json" - name: check for dead code - run: pnpm mobile unimported + run: pnpm mobile knip-check shell: bash - name: Run code checkers run: pnpm typecheck --filter="live-mobile" --api="http://127.0.0.1:${{ steps.toolchain.outputs.port }}" --token="${{ secrets.TURBOREPO_SERVER_TOKEN }}" --team="foo" diff --git a/CODEOWNERS b/CODEOWNERS index edd3ed1398a5..487735fc58c9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -23,14 +23,9 @@ libs/live-nft-react/ @ledgerhq/live-hub libs/live-nft/ @ledgerhq/live-hub libs/promise/ @ledgerhq/live-hub libs/ui/ @ledgerhq/live-hub -libs/live-hooks/ @ledgerhq/live-hub -libs/live-countervalues/ @ledgerhq/live-hub -libs/live-countervalues-react/ @ledgerhq/live-hub -libs/live-nft/ @ledgerhq/live-hub -libs/live-nft-react/ @ledgerhq/live-hub libs/live-wallet/ @ledgerhq/live-hub -libs/trustchain/ @ledgerhq/live-hub -libs/hw-trustchain/ @ledgerhq/live-hub +libs/ledger-key-ring-protocol/ @ledgerhq/live-hub +libs/hw-ledger-key-ring-protocol/ @ledgerhq/live-hub # Blockchain team .github/**/bot-*.yml @ledgerhq/live-blockchain-support @@ -77,6 +72,7 @@ libs/ledger-live-common/src/exchange/ @ledgerhq/p libs/exchange-module/ @ledgerhq/ptx libs/wallet-api-acre-module/ @ledgerhq/ptx libs/ledgerjs/packages/hw-app-exchange/ @ledgerhq/ptx + # Wallet API team **/PlatformAppProviderWrapper.tsx @ledgerhq/wallet-api **/Web3AppWebview/ @ledgerhq/wallet-api diff --git a/apps/cli/package.json b/apps/cli/package.json index cf08de010816..bb7ce855fdaa 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -53,7 +53,7 @@ "asciichart": "1.5.25", "bignumber.js": "9.1.2", "bip39": "3.1.0", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "command-line-args": "5.2.1", "cors": "2.8.5", "express": "4.19.2", diff --git a/apps/ledger-live-desktop/.unimportedrc.json b/apps/ledger-live-desktop/.unimportedrc.json deleted file mode 100644 index 4ea42c55028b..000000000000 --- a/apps/ledger-live-desktop/.unimportedrc.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "entry": [ - "src/index.ts", - "src/renderer/index.ts", - "src/preloader/index.ts", - "src/renderer/webworkers/workers/*.ts", - "src/webviewPreloader/dappPreloader.ts", - "src/webviewPreloader/index.ts" - ], - "extensions": [".ts", ".js", ".jsx", ".tsx"], - "ignorePatterns": ["**/node_modules/**"], - "ignoreUnresolved": ["../../../../release-notes.json", "unzip-crx-3"], - "ignoreUnimported": [ - "**/*.test.*", - "**/*.spec.*", - "**/*.d.ts", - "**/types.js", - "**/types.*", - "node_modules/**", - "src/generate-cryptoassets-md.test.ts", - "src/generate-cryptoassets-md.ts", - - "src/newArch/features/Collectibles/**", - "src/newArch/features/WalletSync/__tests__/shared.tsx", - "src/renderer/DesktopStorageProvider.ts" - ], - "ignoreUnused": [ - "@types/semver", - "@types/qrcode", - "@types/react-key-handler", - "prop-types", - "allure-commandline", - "msw", - "undici" - ], - "aliases": { - "~/*": ["./src/*"] - } -} diff --git a/apps/ledger-live-desktop/package.json b/apps/ledger-live-desktop/package.json index fe5228c266d4..85662fd51056 100644 --- a/apps/ledger-live-desktop/package.json +++ b/apps/ledger-live-desktop/package.json @@ -48,8 +48,8 @@ "test:playwright:clean": "git clean -fdX tests/artifacts", "typecheck": "node scripts/typecheck.js", "check": "pnpm lint; pnpm typecheck", - "unimported": "unimported", - "assets:replace": "zx ./scripts/replace-assets.mjs" + "assets:replace": "zx ./scripts/replace-assets.mjs", + "knip-check": "pnpm knip --directory ../.. -W apps/ledger-live-desktop" }, "dependencies": { "@braze/web-sdk": "4.10.2", diff --git a/apps/ledger-live-desktop/src/config/urls.ts b/apps/ledger-live-desktop/src/config/urls.ts index 70cae363b2a4..f815d8d987a2 100644 --- a/apps/ledger-live-desktop/src/config/urls.ts +++ b/apps/ledger-live-desktop/src/config/urls.ts @@ -177,7 +177,6 @@ export const urls = { "https://cdn.figment.io/legal/Current%20Ledger_Online%20Staking%20Delgation%20Services%20Agreement.pdf", ens: "https://support.ledger.com/article/9710787581469-zd", ledgerLiveMobile: { - storeLink: "https://r354.adj.st/?adj_t=t2esmlk&adj_campaign=Ledger_Live", appStore: "https://apps.apple.com/app/id1361671700", playStore: "https://play.google.com/store/apps/details?id=com.ledger.live", }, @@ -188,6 +187,13 @@ export const urls = { "https://shop.ledger.com?utm_source=live&utm_medium=draw&utm_campaign=ledger_sync_lns_uncompatible&utm_content=to_shop", learnMoreLedgerSync: "https://www.ledger.com/blog-ledger-sync-synchronize-your-crypto-accounts-effortless-private-and-secure", + + // Node errors + txBroadcastErrors: { + badTxns: "https://support.ledger.com/article/5129526865821-zd", + blobsLimit: "https://support.ledger.com/article/17830974229661-zd", + txnMempoolConflict: "https://support.ledger.com/article/14593285242525-zd", + }, }; export const vaultSigner = { diff --git a/apps/ledger-live-desktop/src/renderer/analytics/segment.ts b/apps/ledger-live-desktop/src/renderer/analytics/segment.ts index 5a36b3dcb6d4..e43f241e4395 100644 --- a/apps/ledger-live-desktop/src/renderer/analytics/segment.ts +++ b/apps/ledger-live-desktop/src/renderer/analytics/segment.ts @@ -89,8 +89,9 @@ const getPtxAttributes = () => { const ptxSwapLiveAppDemoThree = analyticsFeatureFlagMethod("ptxSwapLiveAppDemoThree")?.enabled; const ptxSwapExodusProvider = analyticsFeatureFlagMethod("ptxSwapExodusProvider")?.enabled; const ptxSwapCoreExperimentFlag = analyticsFeatureFlagMethod("ptxSwapCoreExperiment"); - const ptxSwapCoreExperiment = - ptxSwapCoreExperimentFlag?.enabled && ptxSwapCoreExperimentFlag?.params?.variant; + const ptxSwapCoreExperiment = ptxSwapCoreExperimentFlag?.enabled + ? ptxSwapCoreExperimentFlag?.params?.variant + : undefined; const isBatch1Enabled: boolean = !!fetchAdditionalCoins?.enabled && fetchAdditionalCoins?.params?.batch === 1; diff --git a/apps/ledger-live-desktop/src/renderer/components/CurrencyBadge.tsx b/apps/ledger-live-desktop/src/renderer/components/CurrencyBadge.tsx index 12923e71f26f..6a4695d8020d 100644 --- a/apps/ledger-live-desktop/src/renderer/components/CurrencyBadge.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/CurrencyBadge.tsx @@ -88,9 +88,9 @@ export function CurrencyCircleIcon({ } function CurrencyBadge({ currency }: { currency: CryptoCurrency | TokenCurrency }) { return ( - + - + ({ + color: "palette.text.shade100", + ff: "Inter|SemiBold", + fontSize: 4, +}))` + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; +`; + const CurrencyLabel = styled(Text).attrs(() => ({ color: "palette.text.shade60", ff: "Inter|SemiBold", @@ -179,9 +199,9 @@ export function CurrencyOption({ const isParentTagDisplayed = !hideParentTag && (currency as TokenCurrency).parentCurrency; const textContents = singleLineLayout ? ( <> - + {`${currency.name} (${currency.ticker})`} - + {isParentTagDisplayed ? ( {(currency as TokenCurrency).parentCurrency.name} ) : null} @@ -207,10 +227,10 @@ export function CurrencyOption({ ); return ( - + {textContents} - + ); } const renderOption = ({ data }: { data: C }) => ( diff --git a/apps/ledger-live-desktop/src/renderer/drawers/SwapOperationDetails/index.tsx b/apps/ledger-live-desktop/src/renderer/drawers/SwapOperationDetails/index.tsx index ddfd9c564bec..e917713c05ec 100644 --- a/apps/ledger-live-desktop/src/renderer/drawers/SwapOperationDetails/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/drawers/SwapOperationDetails/index.tsx @@ -126,9 +126,9 @@ const SwapOperationDetails = ({ const theme = useTheme(); const statusColor = getStatusColor(status, theme); const { t } = useTranslation(); - const url = - fromCurrency.type === "CryptoCurrency" && - getTransactionExplorer(getDefaultExplorerView(fromCurrency), operation.hash); + const mainCurrency = + fromCurrency.type === "CryptoCurrency" ? fromCurrency : fromCurrency.parentCurrency; + const url = getTransactionExplorer(getDefaultExplorerView(mainCurrency), operation.hash); useEffect(() => { const getProvideData = async () => { diff --git a/apps/ledger-live-desktop/src/renderer/modals/Send/steps/StepConfirmation.tsx b/apps/ledger-live-desktop/src/renderer/modals/Send/steps/StepConfirmation.tsx index 254bec493c0d..68d7addeeb33 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/Send/steps/StepConfirmation.tsx +++ b/apps/ledger-live-desktop/src/renderer/modals/Send/steps/StepConfirmation.tsx @@ -12,6 +12,7 @@ import SuccessDisplay from "~/renderer/components/SuccessDisplay"; import { OperationDetails } from "~/renderer/drawers/OperationDetails"; import { setDrawer } from "~/renderer/drawers/Provider"; import { multiline } from "~/renderer/styles/helpers"; +import { urls } from "~/config/urls"; import { StepProps } from "../types"; import NodeError from "./Confirmation/NodeError"; import ErrorDisplay from "~/renderer/components/ErrorDisplay"; @@ -89,7 +90,7 @@ function StepConfirmation({ /> {signed ? ( this.page.getByTestId(`quote-container-${providerName}-${exchangeType}`); @@ -220,6 +212,8 @@ export class SwapPage extends AppPage { async fillInOriginCurrencyAmount(electronApp: ElectronApplication, amount: string) { const [, webview] = electronApp.windows(); await webview.getByTestId(this.fromAccountAmoutInput).fill(amount); + //wait for potential origin amount error to be loaded + await this.page.waitForTimeout(500); } @step("Select currency to swap to: $1") @@ -229,22 +223,23 @@ export class SwapPage extends AppPage { await this.chooseAssetDrawer.chooseFromAsset(currency); } - @step("Verify minimum swap amount error message is displayed") - async verifyMinimumSwapAmountErrorMessageIsDisplayed( + @step("Verify swap amount error message is displayed: $2") + async verifySwapAmountErrorMessageIsDisplayed( electronApp: ElectronApplication, accountToDebit: Account, + message: string | RegExp, ) { const [, webview] = electronApp.windows(); if (!accountToDebit.accountType) { - const errorMessageRegex = new RegExp( - `Minimum \\d+(\\.\\d{1,5})? ${accountToDebit.currency.ticker} needed for quotes\\.\\s*$`, - ); - const actualText = await webview.locator('span[color*="error"]').innerText(); - expect(actualText).toMatch(errorMessageRegex); - await expect(webview.getByTestId(this.numberOfQuotes)).not.toBeVisible(); + const errorSpan = await webview.locator('span[color*="error"]').textContent(); + expect(errorSpan).toMatch(message); + //that specific amount error doesn't trigger quotes + if (message instanceof RegExp) { + await expect(webview.getByTestId(this.numberOfQuotes)).not.toBeVisible(); + } } else { await expect(webview.getByTestId(this.numberOfQuotes)).toBeVisible(); - await expect(webview.locator(this.errorSpan("Not enough balance."))).toBeVisible(); + await expect(webview.locator(this.errorSpan(message))).toBeVisible(); } await expect(webview.getByTestId(`execute-button`)).not.toBeEnabled(); } diff --git a/apps/ledger-live-desktop/tests/specs/speculos/receive.address.spec.ts b/apps/ledger-live-desktop/tests/specs/speculos/receive.address.spec.ts index b9b12bb744fd..5ecf99b5bc6e 100644 --- a/apps/ledger-live-desktop/tests/specs/speculos/receive.address.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/speculos/receive.address.spec.ts @@ -76,7 +76,7 @@ for (const account of accounts) { } test.describe("Receive", () => { - const account = Account.TRX_2; + const account = Account.TRX_3; test.use({ userdata: "skip-onboarding", speculosApp: account.currency.speculosApp, diff --git a/apps/ledger-live-desktop/tests/specs/speculos/send.tx.spec.ts b/apps/ledger-live-desktop/tests/specs/speculos/send.tx.spec.ts index f2a04b9cac7b..cdb5b54295c9 100644 --- a/apps/ledger-live-desktop/tests/specs/speculos/send.tx.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/speculos/send.tx.spec.ts @@ -20,7 +20,7 @@ const transactionsAmountInvalid = [ xrayTicket: "B2CQA-2569", }, { - transaction: new Transaction(Account.XRP_1, Account.XRP_2, "1", Fee.MEDIUM), + transaction: new Transaction(Account.XRP_1, Account.XRP_3, "1", Fee.MEDIUM), expectedErrorMessage: "Recipient address is inactive. Send at least 10 XRP to activate it", xrayTicket: "B2CQA-2571", }, diff --git a/apps/ledger-live-desktop/tests/specs/speculos/swap.spec.ts b/apps/ledger-live-desktop/tests/specs/speculos/swap.spec.ts index 0aab0bfdcae8..ba908a500063 100644 --- a/apps/ledger-live-desktop/tests/specs/speculos/swap.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/speculos/swap.spec.ts @@ -10,6 +10,8 @@ import { getDescription } from "tests/utils/customJsonReporter"; import { Application } from "tests/page"; import { ElectronApplication } from "@playwright/test"; +const app: AppInfos = AppInfos.EXCHANGE; + const swaps = [ { swap: new Swap( @@ -112,13 +114,11 @@ const swaps = [ }, ]; -const app: AppInfos = AppInfos.EXCHANGE; - for (const { swap, xrayTicket } of swaps) { test.describe("Swap - Accepted (without tx broadcast)", () => { test.beforeAll(async () => { process.env.SWAP_DISABLE_APPS_INSTALL = "true"; - process.env.SWAP_API_BASE = "https://swap-stg.ledger.com/v5"; + process.env.SWAP_API_BASE = "https://swap-stg.ledger-test.com/v5"; process.env.DISABLE_TRANSACTION_BROADCAST = "true"; }); @@ -183,7 +183,7 @@ for (const { swap, xrayTicket } of rejectedSwaps) { test.describe("Swap - Rejected on device", () => { test.beforeAll(async () => { process.env.SWAP_DISABLE_APPS_INSTALL = "true"; - process.env.SWAP_API_BASE = "https://swap-stg.ledger.com/v5"; + process.env.SWAP_API_BASE = "https://swap-stg.ledger-test.com/v5"; }); const accPair: string[] = [swap.accountToDebit, swap.accountToCredit].map(acc => @@ -273,7 +273,7 @@ for (const { swap, xrayTicket } of tooLowAmountForQuoteSwaps) { test.describe("Swap - with too low amount (throwing UI errors)", () => { test.beforeAll(async () => { process.env.SWAP_DISABLE_APPS_INSTALL = "true"; - process.env.SWAP_API_BASE = "https://swap-stg.ledger.com/v5"; + process.env.SWAP_API_BASE = "https://swap-stg.ledger-test.com/v5"; }); const accPair: string[] = [swap.accountToDebit, swap.accountToCredit].map(acc => @@ -308,7 +308,40 @@ for (const { swap, xrayTicket } of tooLowAmountForQuoteSwaps) { }, async ({ app, electronApp }) => { await addTmsLink(getDescription(test.info().annotations).split(", ")); - await performSwapUntilBalanceErrorMessageStep(app, electronApp, swap); + await performSwapUntilQuoteSelectionStep(app, electronApp, swap); + const errorMessage = swap.accountToDebit.accountType + ? "Not enough balance." + : new RegExp( + `Minimum \\d+(\\.\\d{1,5})? ${swap.accountToDebit.currency.ticker} needed for quotes\\.\\s*$`, + ); + await app.swap.verifySwapAmountErrorMessageIsDisplayed( + electronApp, + swap.accountToDebit, + errorMessage, + ); + //following error doesn't appear if accountToDebit has accountType erc20 + if (!swap.accountToDebit.accountType) { + await app.swap.fillInOriginCurrencyAmount(electronApp, ""); + await app.swap.fillInOriginCurrencyAmount( + electronApp, + (parseFloat(swap.amount) * 1000).toString(), + ); + await app.swap.verifySwapAmountErrorMessageIsDisplayed( + electronApp, + swap.accountToDebit, + "Not enough balance, including network fee.", + ); + await app.swap.fillInOriginCurrencyAmount(electronApp, ""); + await app.swap.fillInOriginCurrencyAmount( + electronApp, + (parseFloat(swap.amount) * 100_000_000).toString(), + ); + await app.swap.verifySwapAmountErrorMessageIsDisplayed( + electronApp, + swap.accountToDebit, + "No quotes available.", + ); + } }, ); }); @@ -319,6 +352,7 @@ async function performSwapUntilQuoteSelectionStep( electronApp: ElectronApplication, swap: Swap, ) { + //todo: remove 2 following lines after LIVE-14410 await app.layout.goToAccounts(); await app.accounts.navigateToAccountByName(swap.accountToDebit.accountName); await app.layout.goToSwap(); @@ -330,15 +364,6 @@ async function performSwapUntilQuoteSelectionStep( await app.swap.fillInOriginCurrencyAmount(electronApp, swap.amount); } -async function performSwapUntilBalanceErrorMessageStep( - app: Application, - electronApp: ElectronApplication, - swap: Swap, -) { - await performSwapUntilQuoteSelectionStep(app, electronApp, swap); - await app.swap.verifyMinimumSwapAmountErrorMessageIsDisplayed(electronApp, swap.accountToDebit); -} - async function performSwapUntilDeviceVerificationStep( app: Application, electronApp: ElectronApplication, diff --git a/apps/ledger-live-mobile/.env.android.prerelease b/apps/ledger-live-mobile/.env.android.prerelease index bebc72147daa..e31040346200 100644 --- a/apps/ledger-live-mobile/.env.android.prerelease +++ b/apps/ledger-live-mobile/.env.android.prerelease @@ -1,7 +1,6 @@ APP_NAME="Ledger Live" SENTRY_DSN=https://ea730d6c531f40679e7306c888f7fbc5@o118392.ingest.sentry.io/6723478 ANALYTICS_TOKEN=jfUZbw28ig8JpEi9DZpTUc21dKUKu1e3 -ADJUST_APP_TOKEN=104p56owfekg BRAZE_ANDROID_API_KEY="b34d1245-04d6-4209-9580-eed4ec82cf17" BRAZE_IOS_API_KEY="93d6248d-f2e9-4255-84bc-6b551b7afcd2" BRAZE_CUSTOM_ENDPOINT="sdk.fra-02.braze.eu" \ No newline at end of file diff --git a/apps/ledger-live-mobile/.env.android.release b/apps/ledger-live-mobile/.env.android.release index 5b85a94edcb8..3beb50181f19 100644 --- a/apps/ledger-live-mobile/.env.android.release +++ b/apps/ledger-live-mobile/.env.android.release @@ -1,7 +1,6 @@ APP_NAME="Ledger Live" SENTRY_DSN=https://bfd61f321b4144ffbce693aa6c6be9d0@o118392.ingest.sentry.io/6505213 ANALYTICS_TOKEN=jfUZbw28ig8JpEi9DZpTUc21dKUKu1e3 -ADJUST_APP_TOKEN=104p56owfekg BRAZE_ANDROID_API_KEY="b34d1245-04d6-4209-9580-eed4ec82cf17" BRAZE_IOS_API_KEY="93d6248d-f2e9-4255-84bc-6b551b7afcd2" BRAZE_CUSTOM_ENDPOINT="sdk.fra-02.braze.eu" \ No newline at end of file diff --git a/apps/ledger-live-mobile/.env.android.staging b/apps/ledger-live-mobile/.env.android.staging index 777c7af8c342..cd58db3a57e8 100644 --- a/apps/ledger-live-mobile/.env.android.staging +++ b/apps/ledger-live-mobile/.env.android.staging @@ -1,7 +1,6 @@ APP_NAME="LL [STAGING]" SENTRY_DSN=https://0109819a39084e718120d031def0db38@o118392.ingest.sentry.io/6619343 ANALYTICS_TOKEN=Yc026bN2XbyBhTCPDFY0VibJugAKnjmh -ADJUST_APP_TOKEN=v88jjyrsto8w BRAZE_ANDROID_API_KEY="4ef07be0-a4ea-4f73-81a9-760e473959f3" BRAZE_IOS_API_KEY="4d6c9f5b-823e-4ea0-8158-5359bdf89618" BRAZE_CUSTOM_ENDPOINT="sdk.fra-02.braze.eu" \ No newline at end of file diff --git a/apps/ledger-live-mobile/.env.ios.prerelease b/apps/ledger-live-mobile/.env.ios.prerelease index eba9faf795f8..54cbc249144e 100644 --- a/apps/ledger-live-mobile/.env.ios.prerelease +++ b/apps/ledger-live-mobile/.env.ios.prerelease @@ -2,11 +2,6 @@ APP_NAME="Ledger Live" SENTRY_DSN=https://52a4181714f74dffa9cdf48224dba505@o118392.ingest.sentry.io/6723477 ANALYTICS_TOKEN=jfUZbw28ig8JpEi9DZpTUc21dKUKu1e3 GOOGLE_SERVICE_INFO_NAME="GoogleService-Info-Production" -ADJUST_APP_TOKEN=104p56owfekg -ADJUST_BUY_GENERIC_EVENT_ID=1sc6n8 -ADJUST_BUY_NANOX_EVENT_ID=jf9r4k -ADJUST_BUY_NANOS_EVENT_ID=qn40mj -ADJUST_BUY_NANOSP_EVENT_ID=fq8lrx BRAZE_ANDROID_API_KEY="b34d1245-04d6-4209-9580-eed4ec82cf17" BRAZE_IOS_API_KEY="93d6248d-f2e9-4255-84bc-6b551b7afcd2" BRAZE_CUSTOM_ENDPOINT="sdk.fra-02.braze.eu" \ No newline at end of file diff --git a/apps/ledger-live-mobile/.env.ios.release b/apps/ledger-live-mobile/.env.ios.release index cf5df6a8a5ed..744006c1f4cb 100644 --- a/apps/ledger-live-mobile/.env.ios.release +++ b/apps/ledger-live-mobile/.env.ios.release @@ -2,11 +2,6 @@ APP_NAME="Ledger Live" SENTRY_DSN=https://b4ac5f291b7a48529fc7ec1f34cf57ed@o118392.ingest.sentry.io/6505205 ANALYTICS_TOKEN=jfUZbw28ig8JpEi9DZpTUc21dKUKu1e3 GOOGLE_SERVICE_INFO_NAME="GoogleService-Info-Production" -ADJUST_APP_TOKEN=104p56owfekg -ADJUST_BUY_GENERIC_EVENT_ID=1sc6n8 -ADJUST_BUY_NANOX_EVENT_ID=jf9r4k -ADJUST_BUY_NANOS_EVENT_ID=qn40mj -ADJUST_BUY_NANOSP_EVENT_ID=fq8lrx BRAZE_ANDROID_API_KEY="b34d1245-04d6-4209-9580-eed4ec82cf17" BRAZE_IOS_API_KEY="93d6248d-f2e9-4255-84bc-6b551b7afcd2" BRAZE_CUSTOM_ENDPOINT="sdk.fra-02.braze.eu" \ No newline at end of file diff --git a/apps/ledger-live-mobile/.env.ios.staging b/apps/ledger-live-mobile/.env.ios.staging index f09971978085..481b4aa292c6 100644 --- a/apps/ledger-live-mobile/.env.ios.staging +++ b/apps/ledger-live-mobile/.env.ios.staging @@ -2,11 +2,6 @@ APP_NAME="LL [STAGING]" SENTRY_DSN=https://bd8b742115104e82b9ad3686fd9fd9f2@o118392.ingest.sentry.io/6619346 ANALYTICS_TOKEN=Yc026bN2XbyBhTCPDFY0VibJugAKnjmh GOOGLE_SERVICE_INFO_NAME="GoogleService-Info-Staging" -ADJUST_APP_TOKEN=v88jjyrsto8w -ADJUST_BUY_GENERIC_EVENT_ID=h7zcss -ADJUST_BUY_NANOX_EVENT_ID=8cykqm -ADJUST_BUY_NANOS_EVENT_ID=rocmaw -ADJUST_BUY_NANOSP_EVENT_ID=y0ku50 BRAZE_ANDROID_API_KEY="4ef07be0-a4ea-4f73-81a9-760e473959f3" BRAZE_IOS_API_KEY="4d6c9f5b-823e-4ea0-8158-5359bdf89618" BRAZE_CUSTOM_ENDPOINT="sdk.fra-02.braze.eu" \ No newline at end of file diff --git a/apps/ledger-live-mobile/.env.mock b/apps/ledger-live-mobile/.env.mock index 920bb6c6f5d3..f243d3f1eb60 100644 --- a/apps/ledger-live-mobile/.env.mock +++ b/apps/ledger-live-mobile/.env.mock @@ -5,7 +5,6 @@ DISABLE_YELLOW_BOX=1 GOOGLE_SERVICE_INFO_NAME="GoogleService-Info-Testing" MOCK_SCAN_RECIPIENT=bitcoin:3HX3Q4wgYi8nKakxv7kmdCgLWJFrFgcqEt?amount=0.001 FORCE_DEBUG_VISIBLE=1 -ADJUST_APP_TOKEN=cbxft2ch7wn4 BRAZE_ANDROID_API_KEY="be5e1bc8-43f1-4864-b097-076a3c693a43" BRAZE_IOS_API_KEY="e0a7dfaf-fc30-48f6-b998-01dbebbb73a4" BRAZE_CUSTOM_ENDPOINT="sdk.fra-02.braze.eu" diff --git a/apps/ledger-live-mobile/.unimportedrc.json b/apps/ledger-live-mobile/.unimportedrc.json deleted file mode 100644 index 812172f14cf7..000000000000 --- a/apps/ledger-live-mobile/.unimportedrc.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "entry": ["index.js"], - "extensions": [".ts", ".js", ".jsx", ".tsx", ".d.ts"], - "ignorePatterns": ["**/node_modules/**"], - "ignoreUnresolved": [], - "ignoreUnimported": [ - "**/*.test.*", - "**/*.spec.*", - "**/*.d.ts", - "src/__test__/*", - "src/__tests__/*", - "src/**/__mocks__/*", - "src/**/*.android.*", - "src/**/*.ios.*", - "src/components/RootNavigator/types.ts", - "src/logic/keyboardVisible.ts", - "src/contentCards/cards/vertical/*", - "src/**/__integrations__/*.tsx", - "src/MobileStorageProvider.ts", - "src/newArch/features/WalletSync/components/Error/index.tsx", - "src/newArch/features/WalletSync/screens/Synchronize/PinCodeDisplay.tsx", - "src/newArch/features/WalletSync/screens/Synchronize/PinCodeInput.tsx" - ], - "ignoreUnused": [ - "@react-native-masked-view/masked-view", - "@react-native/gradle-plugin", - "@react-native/metro-config", - "@segment/sovran-react-native", - "expo-file-system", - "expo-image-loader", - "expo-modules-autolinking", - "expo-modules-core", - "expo-crypto", - "prop-types", - "react-native-codegen", - "react-native-fast-pbkdf2", - "react-native-level-fs", - "react-native-navigation-bar-color", - "react-native-os", - "react-native-randombytes", - "react-native-tcp", - "react-native-tcp-socket", - "react-native-udp" - ] -} diff --git a/apps/ledger-live-mobile/android/app/build.gradle b/apps/ledger-live-mobile/android/app/build.gradle index 156e3bb71bd5..99665ff9588f 100644 --- a/apps/ledger-live-mobile/android/app/build.gradle +++ b/apps/ledger-live-mobile/android/app/build.gradle @@ -221,10 +221,6 @@ dependencies { // implementation 'com.brentvatne.react:react-native-video' implementation project(':react-native-video') implementation "androidx.appcompat:appcompat:1.0.0" - - // Adjust - implementation 'com.google.android.gms:play-services-analytics:10.0.1' - implementation 'com.android.installreferrer:installreferrer:1.0' } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) diff --git a/apps/ledger-live-mobile/android/app/proguard-rules.pro b/apps/ledger-live-mobile/android/app/proguard-rules.pro index a58bfb1e8bd0..edc668ce2e67 100644 --- a/apps/ledger-live-mobile/android/app/proguard-rules.pro +++ b/apps/ledger-live-mobile/android/app/proguard-rules.pro @@ -15,7 +15,6 @@ -keep class com.facebook.hermes.unicode.** { *; } -keep class com.facebook.jni.** { *; } --keep class com.adjust.sdk.** { *; } -keep class com.google.android.gms.common.ConnectionResult { int SUCCESS; } diff --git a/apps/ledger-live-mobile/android/app/src/main/AndroidManifest.xml b/apps/ledger-live-mobile/android/app/src/main/AndroidManifest.xml index 2c0584df9886..439bf2a44219 100644 --- a/apps/ledger-live-mobile/android/app/src/main/AndroidManifest.xml +++ b/apps/ledger-live-mobile/android/app/src/main/AndroidManifest.xml @@ -25,8 +25,7 @@ - - + 7.7.0) @@ -1112,9 +1109,6 @@ PODS: - React-Mapbuffer (0.74.6): - glog - React-debug - - react-native-adjust (4.38.0): - - Adjust (= 4.38.0) - - React-Core - react-native-biometrics (3.0.1): - React-Core - react-native-ble-plx (3.1.2): @@ -1678,7 +1672,6 @@ DEPENDENCIES: - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - - react-native-adjust (from `../node_modules/react-native-adjust`) - react-native-biometrics (from `../node_modules/react-native-biometrics`) - react-native-ble-plx (from `../node_modules/react-native-ble-plx`) - react-native-config (from `../node_modules/react-native-config`) @@ -1754,7 +1747,6 @@ DEPENDENCIES: SPEC REPOS: trunk: - - Adjust - BrazeKit - BrazeLocation - BrazeUI @@ -1871,8 +1863,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/logger" React-Mapbuffer: :path: "../node_modules/react-native/ReactCommon" - react-native-adjust: - :path: "../node_modules/react-native-adjust" react-native-biometrics: :path: "../node_modules/react-native-biometrics" react-native-ble-plx: @@ -2019,7 +2009,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - Adjust: c98d85594a746ff9f2bafe424c5beb6db66cd6a8 boost: d3f49c53809116a5d38da093a8aa78bf551aed09 braze-react-native-sdk: 62de95dfbbfde54fa101a5d8c75e19ea639e4eda BrazeKit: ee31d3b5113646cbd6ad44eafc93b14c3c3c504c @@ -2082,7 +2071,6 @@ SPEC CHECKSUMS: React-jsitracing: df84cc252a1f4bb0970f7fe13c470451b18c2cbb React-logger: de9b65c8c7b71a663e6e99d347b1c445f5190c39 React-Mapbuffer: 766bb4d8f655d816913325b353d800debbde7209 - react-native-adjust: 801fe33f0dc0097b0474f11d08572a89fa6602ce react-native-biometrics: 352e5a794bfffc46a0c86725ea7dc62deb085bdc react-native-ble-plx: cd7b99ddaf7c15e652c63f87a12e96c715ee3c80 react-native-config: 8f7283449bbb048902f4e764affbbf24504454af diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj b/apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj index 4ae8af6d1d64..9cc6536b5a72 100644 --- a/apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj +++ b/apps/ledger-live-mobile/ios/ledgerlivemobile.xcodeproj/project.pbxproj @@ -459,7 +459,6 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-ledgerlivemobile/Pods-ledgerlivemobile-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/Adjust/Adjust.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/BrazeKit/BrazeKit.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/BrazeLocation/BrazeLocation.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/BrazeUI/BrazeUI.bundle", @@ -498,7 +497,6 @@ ); name = "[CP] Copy Pods Resources"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Adjust.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/BrazeKit.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/BrazeLocation.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/BrazeUI.bundle", diff --git a/apps/ledger-live-mobile/ios/ledgerlivemobile/PrivacyInfo.xcprivacy b/apps/ledger-live-mobile/ios/ledgerlivemobile/PrivacyInfo.xcprivacy index bdea2127c16a..f206435fb484 100644 --- a/apps/ledger-live-mobile/ios/ledgerlivemobile/PrivacyInfo.xcprivacy +++ b/apps/ledger-live-mobile/ios/ledgerlivemobile/PrivacyInfo.xcprivacy @@ -3,48 +3,107 @@ NSPrivacyAccessedAPITypes + + NSPrivacyCollectedDataTypes - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryUserDefaults - NSPrivacyAccessedAPITypeReasons + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeUserID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes - CA92.1 - 1C8F.1 - C56D.1 + NSPrivacyCollectedDataTypePurposeAnalytics + NSPrivacyCollectedDataTypePurposeDeveloperAdvertising + NSPrivacyCollectedDataTypePurposeProductPersonalization + NSPrivacyCollectedDataTypePurposeAppFunctionality - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryFileTimestamp - NSPrivacyAccessedAPITypeReasons + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes - C617.1 - 0A2A.1 - 3B52.1 + NSPrivacyCollectedDataTypePurposeAnalytics + NSPrivacyCollectedDataTypePurposeDeveloperAdvertising - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryDiskSpace - NSPrivacyAccessedAPITypeReasons + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCrashData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes - E174.1 - 85F4.1 + NSPrivacyCollectedDataTypePurposeAnalytics - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategorySystemBootTime - NSPrivacyAccessedAPITypeReasons + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePerformanceData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes - 35F9.1 + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + NSPrivacyCollectedDataTypePurposeDeveloperAdvertising + NSPrivacyCollectedDataTypePurposeProductPersonalization + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherUsageData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + NSPrivacyCollectedDataTypePurposeDeveloperAdvertising + NSPrivacyCollectedDataTypePurposeProductPersonalization + NSPrivacyCollectedDataTypePurposeAppFunctionality - NSPrivacyCollectedDataTypes - NSPrivacyTracking + NSPrivacyTrackingDomains + diff --git a/apps/ledger-live-mobile/package.json b/apps/ledger-live-mobile/package.json index a6775cb7f42a..31a39e855085 100644 --- a/apps/ledger-live-mobile/package.json +++ b/apps/ledger-live-mobile/package.json @@ -68,7 +68,7 @@ "download-hermes-profile": "zx ./scripts/download-hermes-profile.mjs", "e2e:loadConfig": "ts-node ./e2e/bridge/start-server.ts", "check": "pnpm lint; pnpm typecheck", - "unimported": "unimported", + "knip-check": "pnpm knip --directory ../.. -W apps/ledger-live-mobile", "assets:replace": "zx ./scripts/replace-assets.mjs" }, "dependencies": { @@ -167,7 +167,6 @@ "react-i18next": "11.18.6", "react-is": "18.2.0", "react-native": "0.74.6", - "react-native-adjust": "4.38.0", "react-native-android-location-services-dialog-box": "2.8.2", "react-native-animatable": "1.4.0", "react-native-biometrics": "3.0.1", @@ -274,7 +273,7 @@ "@types/uuid": "8.3.4", "@types/ws": "8.5.10", "babel-jest": "29.7.0", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "cors": "2.8.5", "detox": "20.26.2", "detox-allure2-adapter": "1.0.0-alpha.12", diff --git a/apps/ledger-live-mobile/src/actions/settings.ts b/apps/ledger-live-mobile/src/actions/settings.ts index c68dacd7c88a..cd6e33948d58 100755 --- a/apps/ledger-live-mobile/src/actions/settings.ts +++ b/apps/ledger-live-mobile/src/actions/settings.ts @@ -72,6 +72,7 @@ import { SettingsSetFromLedgerSyncOnboardingPayload, SettingsWhitelistNftCollectionPayload, SettingsUnwhitelistNftCollectionPayload, + SettingsSetHasBeenRedirectedToPostOnboardingPayload, } from "./types"; import { ImageType } from "~/components/CustomImage/types"; @@ -275,6 +276,11 @@ export const setHasBeenUpsoldProtect = createAction( + SettingsActionTypes.SET_HAS_BEEN_REDIRECTED_TO_POST_ONBOARDING, + ); + export const setGeneralTermsVersionAccepted = createAction( SettingsActionTypes.SET_GENERAL_TERMS_VERSION_ACCEPTED, ); diff --git a/apps/ledger-live-mobile/src/actions/types.ts b/apps/ledger-live-mobile/src/actions/types.ts index 81e39504f8d7..90350625cf43 100644 --- a/apps/ledger-live-mobile/src/actions/types.ts +++ b/apps/ledger-live-mobile/src/actions/types.ts @@ -275,6 +275,7 @@ export enum SettingsActionTypes { SET_FEATURE_FLAGS_BANNER_VISIBLE = "SET_FEATURE_FLAGS_BANNER_VISIBLE", SET_DEBUG_APP_LEVEL_DRAWER_OPENED = "SET_DEBUG_APP_LEVEL_DRAWER_OPENED", SET_HAS_BEEN_UPSOLD_PROTECT = "SET_HAS_BEEN_UPSOLD_PROTECT", + SET_HAS_BEEN_REDIRECTED_TO_POST_ONBOARDING = "SET_HAS_BEEN_REDIRECTED_TO_POST_ONBOARDING", SET_GENERAL_TERMS_VERSION_ACCEPTED = "SET_GENERAL_TERMS_VERSION_ACCEPTED", SET_ONBOARDING_TYPE = "SET_ONBOARDING_TYPE", SET_CLOSED_NETWORK_BANNER = "SET_CLOSED_NETWORK_BANNER", @@ -383,6 +384,8 @@ export type SettingsSetDebugAppLevelDrawerOpenedPayload = SettingsState["debugAppLevelDrawerOpened"]; export type SettingsSetHasBeenUpsoldProtectPayload = SettingsState["hasBeenUpsoldProtect"]; +export type SettingsSetHasBeenRedirectedToPostOnboardingPayload = + SettingsState["hasBeenRedirectedToPostOnboarding"]; export type SettingsCompleteOnboardingPayload = void | SettingsState["hasCompletedOnboarding"]; export type SettingsSetGeneralTermsVersionAccepted = SettingsState["generalTermsVersionAccepted"]; diff --git a/apps/ledger-live-mobile/src/components/AdjustSetup.tsx b/apps/ledger-live-mobile/src/components/AdjustSetup.tsx deleted file mode 100644 index f35a62acafd6..000000000000 --- a/apps/ledger-live-mobile/src/components/AdjustSetup.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useEffect } from "react"; - -import { - Adjust, - AdjustEventTrackingSuccess, - AdjustEventTrackingFailure, - AdjustConfig, -} from "react-native-adjust"; -import Config from "react-native-config"; -import { useSelector } from "react-redux"; -import { trackingEnabledSelector } from "~/reducers/settings"; - -export default function AdjustSetup() { - const trackingEnabled: boolean = useSelector(trackingEnabledSelector); - - useEffect(() => { - const adjustConfig = new AdjustConfig( - Config.ADJUST_APP_TOKEN as string, - __DEV__ ? AdjustConfig.EnvironmentSandbox : AdjustConfig.EnvironmentProduction, // @TODO: Change to Production when ready - ); - adjustConfig.setDelayStart(Math.random() * 7 + 1); - if (__DEV__) { - adjustConfig.setLogLevel(AdjustConfig.LogLevelDebug); - } - if (Config.DEBUG_ADJUST_LOGS) { - adjustConfig.setEventTrackingSucceededCallbackListener( - (eventSuccess: AdjustEventTrackingSuccess) => { - // Printing all event success properties. - console.warn("Event tracking succeeded!", eventSuccess); - }, - ); - - adjustConfig.setEventTrackingFailedCallbackListener( - (eventFailure: AdjustEventTrackingFailure) => { - // Printing all event failure properties. - console.error("Event tracking failed!", eventFailure); - }, - ); - } - - Adjust.create(adjustConfig); - - return () => { - Adjust.componentWillUnmount(); - }; - }, []); - - useEffect(() => { - Adjust.setEnabled(trackingEnabled); - }, [trackingEnabled]); - - return null; -} diff --git a/apps/ledger-live-mobile/src/components/RootNavigator/SendFundsNavigator.tsx b/apps/ledger-live-mobile/src/components/RootNavigator/SendFundsNavigator.tsx index 70b3a18eaead..63b169757b22 100644 --- a/apps/ledger-live-mobile/src/components/RootNavigator/SendFundsNavigator.tsx +++ b/apps/ledger-live-mobile/src/components/RootNavigator/SendFundsNavigator.tsx @@ -14,6 +14,7 @@ import SendSummary from "~/screens/SendFunds/04-Summary"; import SelectDevice from "~/screens/SelectDevice"; import SendConnectDevice from "~/screens/ConnectDevice"; import SendValidationSuccess from "~/screens/SendFunds/07-ValidationSuccess"; +import SendBroadcastError from "~/screens/SendFunds/07-SendBroadcastError"; import SendValidationError from "~/screens/SendFunds/07-ValidationError"; import { getStackNavigatorConfig } from "~/navigation/navigatorConfig"; import StepHeader from "../StepHeader"; @@ -190,6 +191,11 @@ export default function SendFundsNavigator() { gestureEnabled: false, }} /> + null, headerTitle: () => null }} + /> ( - +export default (props: MemoTagInputProps) => ( + ({ memo: value || undefined })} + valueToTxPatch={value => tx => ({ ...tx, memo: value || undefined })} /> ); diff --git a/apps/ledger-live-mobile/src/families/cardano/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/cardano/MemoTagInput.tsx index 9ba0fcc5848f..3559c2d94810 100644 --- a/apps/ledger-live-mobile/src/families/cardano/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/cardano/MemoTagInput.tsx @@ -4,9 +4,9 @@ import type { Transaction as CardanoTransaction } from "@ledgerhq/live-common/fa import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput"; -export default (props: MemoTagInputProps) => ( - +export default (props: MemoTagInputProps) => ( + ({ memo: value || undefined })} + valueToTxPatch={value => tx => ({ ...tx, memo: value || undefined })} /> ); diff --git a/apps/ledger-live-mobile/src/families/casper/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/casper/MemoTagInput.tsx index fb56651b4a10..7c502ec01b88 100644 --- a/apps/ledger-live-mobile/src/families/casper/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/casper/MemoTagInput.tsx @@ -5,13 +5,13 @@ import type { Transaction as CasperTransaction } from "@ledgerhq/live-common/fam import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput"; -export default (props: MemoTagInputProps) => { +export default (props: MemoTagInputProps) => { const { t } = useTranslation(); return ( - + text.replace(/\D/g, "")} - valueToTxPatch={value => ({ transferId: value || undefined })} + valueToTxPatch={value => tx => ({ ...tx, transferId: value || undefined })} placeholder={t("send.summary.transferId")} /> ); diff --git a/apps/ledger-live-mobile/src/families/cosmos/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/cosmos/MemoTagInput.tsx index 931b4f4410a0..5cb714cc53c7 100644 --- a/apps/ledger-live-mobile/src/families/cosmos/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/cosmos/MemoTagInput.tsx @@ -4,9 +4,9 @@ import type { Transaction as CosmosTransaction } from "@ledgerhq/live-common/fam import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput"; -export default (props: MemoTagInputProps) => ( - +export default (props: MemoTagInputProps) => ( + ({ memo: value || undefined })} + valueToTxPatch={value => tx => ({ ...tx, memo: value || undefined })} /> ); diff --git a/apps/ledger-live-mobile/src/families/crypto_org/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/crypto_org/MemoTagInput.tsx index 071f056ba57b..bd48ceb79db7 100644 --- a/apps/ledger-live-mobile/src/families/crypto_org/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/crypto_org/MemoTagInput.tsx @@ -4,9 +4,9 @@ import type { Transaction as CryptoOrgTransaction } from "@ledgerhq/live-common/ import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput"; -export default (props: MemoTagInputProps) => ( - +export default (props: MemoTagInputProps) => ( + ({ memo: value || undefined })} + valueToTxPatch={value => tx => ({ ...tx, memo: value || undefined })} /> ); diff --git a/apps/ledger-live-mobile/src/families/hedera/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/hedera/MemoTagInput.tsx index c5e0ba790714..0dc6a31f20dd 100644 --- a/apps/ledger-live-mobile/src/families/hedera/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/hedera/MemoTagInput.tsx @@ -4,9 +4,9 @@ import type { Transaction as HederaTransaction } from "@ledgerhq/live-common/fam import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput"; -export default (props: MemoTagInputProps) => ( - +export default (props: MemoTagInputProps) => ( + ({ memo: value || undefined })} + valueToTxPatch={value => tx => ({ ...tx, memo: value || undefined })} /> ); diff --git a/apps/ledger-live-mobile/src/families/internet_computer/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/internet_computer/MemoTagInput.tsx index 632e27873f67..1f543503e926 100644 --- a/apps/ledger-live-mobile/src/families/internet_computer/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/internet_computer/MemoTagInput.tsx @@ -4,10 +4,10 @@ import { Transaction as ICPTransaction } from "@ledgerhq/live-common/families/in import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput"; -export default (props: MemoTagInputProps) => ( - +export default (props: MemoTagInputProps) => ( + text.replace(/\D/g, "")} - valueToTxPatch={value => ({ memo: value || undefined })} + valueToTxPatch={value => tx => ({ ...tx, memo: value || undefined })} /> ); diff --git a/apps/ledger-live-mobile/src/families/solana/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/solana/MemoTagInput.tsx index f8426619131f..8a12d8d93b3f 100644 --- a/apps/ledger-live-mobile/src/families/solana/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/solana/MemoTagInput.tsx @@ -1,12 +1,15 @@ +import merge from "lodash/merge"; import React from "react"; -import { Transaction as SolanaTransaction } from "@ledgerhq/live-common/generated/types"; +import { Transaction as SolanaTransaction } from "@ledgerhq/live-common/families/solana/types"; import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput"; -export default (props: MemoTagInputProps) => ( - +export default (props: MemoTagInputProps) => ( + ({ memo: value || undefined })} + valueToTxPatch={value => tx => + merge({}, tx, { model: { uiState: { memo: value || undefined } } }) + } /> ); diff --git a/apps/ledger-live-mobile/src/families/stacks/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/stacks/MemoTagInput.tsx index 07874cc81084..f15b17279ef1 100644 --- a/apps/ledger-live-mobile/src/families/stacks/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/stacks/MemoTagInput.tsx @@ -4,9 +4,9 @@ import type { Transaction as StacksTransaction } from "@ledgerhq/live-common/fam import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput"; -export default (props: MemoTagInputProps) => ( - +export default (props: MemoTagInputProps) => ( + ({ memo: value || undefined })} + valueToTxPatch={value => tx => ({ ...tx, memo: value || undefined })} /> ); diff --git a/apps/ledger-live-mobile/src/families/stellar/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/stellar/MemoTagInput.tsx new file mode 100644 index 000000000000..75ff1d1345ea --- /dev/null +++ b/apps/ledger-live-mobile/src/families/stellar/MemoTagInput.tsx @@ -0,0 +1,65 @@ +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import { isMemoValid } from "@ledgerhq/live-common/families/stellar/bridge/logic"; +import { + StellarWrongMemoFormat, + type Transaction as StellarTransaction, +} from "@ledgerhq/live-common/families/stellar/types"; +import { AnimatedInputSelect } from "@ledgerhq/native-ui"; +import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; +import { MemoTypeDrawer, MEMO_TYPES } from "./MemoTypeDrawer"; + +export default ({ onChange }: MemoTagInputProps) => { + const { t } = useTranslation(); + + const [memoType, setMemoType] = useState("NO_MEMO"); + const [memoValue, setMemoValue] = React.useState(""); + const [isOpen, setIsOpen] = useState(false); + + const handleChange = (type: MemoType, value: string) => { + const error = isMemoValid(type, value) ? undefined : new StellarWrongMemoFormat(); + const patch = (tx: StellarTransaction) => ({ ...tx, memoType: type, memoValue: value }); + onChange({ value, patch, error }); + }; + + const handleChangeType = (type: MemoType) => { + const value = type === "NO_MEMO" ? "" : memoValue; + handleChange(type, value); + + setMemoType(type); + if (value !== memoValue) setMemoValue(value); + setIsOpen(false); + }; + + const handleChangeValue = (value: string) => { + const type = memoType === "NO_MEMO" && value ? "MEMO_TEXT" : memoType; + handleChange(type, value); + + setMemoValue(value); + if (type !== memoType) setMemoType(type); + }; + + return ( + <> + setIsOpen(true), + }} + /> + + setIsOpen(false)} + value={memoType} + onChange={handleChangeType} + /> + + ); +}; + +type MemoType = Parameters<(typeof MEMO_TYPES)["get"]>[0]; diff --git a/apps/ledger-live-mobile/src/families/stellar/MemoTypeDrawer.tsx b/apps/ledger-live-mobile/src/families/stellar/MemoTypeDrawer.tsx new file mode 100644 index 000000000000..f6168494ef34 --- /dev/null +++ b/apps/ledger-live-mobile/src/families/stellar/MemoTypeDrawer.tsx @@ -0,0 +1,68 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { TouchableOpacity, TouchableOpacityProps } from "react-native"; + +import type { StellarMemoType } from "@ledgerhq/live-common/families/stellar/types"; +import { Icons, Text } from "@ledgerhq/native-ui"; +import Circle from "~/components/Circle"; +import QueuedDrawer from "~/components/QueuedDrawer"; + +export const MEMO_TYPES = new Map([ + ["NO_MEMO", "stellar.memoType.NO_MEMO"], + ["MEMO_TEXT", "stellar.memoType.MEMO_TEXT"], + ["MEMO_ID", "stellar.memoType.MEMO_ID"], + ["MEMO_HASH", "stellar.memoType.MEMO_HASH"], + ["MEMO_RETURN", "stellar.memoType.MEMO_RETURN"], +]); + +type Props = { + isOpen: boolean; + closeModal: () => void; + value: MemoType; + onChange: (value: MemoType) => void; +}; + +export function MemoTypeDrawer({ isOpen, closeModal, value, onChange }: Props) { + const { t } = useTranslation(); + return ( + + {t("send.summary.memo.type")} + + } + isRequestingToBeOpened={isOpen} + onClose={closeModal} + > + {Array.from(MEMO_TYPES).map(([type, label]) => ( + + ); +} + +type OptionProps = TouchableOpacityProps & { label: string; selected?: boolean }; +function Option({ label, selected = false, onPress }: OptionProps) { + return ( + + + {label} + + {selected && ( + + + + )} + + ); +} + +type MemoType = (typeof StellarMemoType)[number]; diff --git a/apps/ledger-live-mobile/src/families/ton/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/ton/MemoTagInput.tsx index 8caca4d628ca..e52abb191a56 100644 --- a/apps/ledger-live-mobile/src/families/ton/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/ton/MemoTagInput.tsx @@ -5,14 +5,12 @@ import type { Transaction as TonTransaction } from "@ledgerhq/live-common/famili import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput"; -export default (props: MemoTagInputProps) => { +export default (props: MemoTagInputProps) => { const { t } = useTranslation(); return ( - + - value ? { comment: { isEncrypted: false, text: value } } : { comment: undefined } - } + valueToTxPatch={value => tx => ({ ...tx, comment: { isEncrypted: false, text: value } })} placeholder={t("send.summary.comment")} /> ); diff --git a/apps/ledger-live-mobile/src/families/xrp/MemoTagInput.tsx b/apps/ledger-live-mobile/src/families/xrp/MemoTagInput.tsx index 9f5cc1ff9eaf..29b97083b0d1 100644 --- a/apps/ledger-live-mobile/src/families/xrp/MemoTagInput.tsx +++ b/apps/ledger-live-mobile/src/families/xrp/MemoTagInput.tsx @@ -5,13 +5,13 @@ import type { Transaction as RippleTransaction } from "@ledgerhq/live-common/fam import type { MemoTagInputProps } from "LLM/features/MemoTag/types"; import { GenericMemoTagInput } from "LLM/features/MemoTag/components/GenericMemoTagInput"; -export default (props: MemoTagInputProps) => { +export default (props: MemoTagInputProps) => { const { t } = useTranslation(); return ( - + text.replace(/\D/g, "")} - valueToTxPatch={value => ({ tag: value ? Number(value) : undefined })} + valueToTxPatch={value => tx => ({ ...tx, tag: value ? Number(value) : undefined })} placeholder={t("send.summary.tag")} /> ); diff --git a/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/index.ts b/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/index.ts new file mode 100644 index 000000000000..7800bc38e010 --- /dev/null +++ b/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/index.ts @@ -0,0 +1,39 @@ +import { useEffect } from "react"; +import { useSelector } from "react-redux"; +import { lastConnectedDeviceSelector } from "~/reducers/settings"; +import { useOpenPostOnboardingCallback } from "./useOpenPostOnboardingCallback"; +import { useShouldRedirect } from "./useShouldRedirect"; +import { useOpenProtectUpsellCallback } from "./useOpenProtectUpsellCallback"; +import { useIsFocused } from "@react-navigation/core"; + +/** + * Redirects the user to the post onboarding or the protect (Ledger Recover) upsell if needed + * */ +export function useAutoRedirectToPostOnboarding() { + const focused = useIsFocused(); + const lastConnectedDevice = useSelector(lastConnectedDeviceSelector); + + const { shouldRedirectToProtectUpsell, shouldRedirectToPostOnboarding } = useShouldRedirect(); + + const openProtectUpsell = useOpenProtectUpsellCallback(); + const openPostOnboarding = useOpenPostOnboardingCallback(); + + const isFocused = useIsFocused(); + + useEffect(() => { + if (!isFocused) return; + if (shouldRedirectToProtectUpsell) { + openProtectUpsell(); + } else if (shouldRedirectToPostOnboarding && lastConnectedDevice) { + openPostOnboarding(lastConnectedDevice.modelId); + } + }, [ + lastConnectedDevice, + openPostOnboarding, + openProtectUpsell, + shouldRedirectToPostOnboarding, + shouldRedirectToProtectUpsell, + focused, + isFocused, + ]); +} diff --git a/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/useOpenPostOnboardingCallback.ts b/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/useOpenPostOnboardingCallback.ts new file mode 100644 index 000000000000..770f7e9ef74c --- /dev/null +++ b/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/useOpenPostOnboardingCallback.ts @@ -0,0 +1,19 @@ +import { useStartPostOnboardingCallback } from "@ledgerhq/live-common/postOnboarding/hooks/useStartPostOnboardingCallback"; +import { DeviceModelId } from "@ledgerhq/types-devices"; +import { useCallback } from "react"; + +/** + * Returns a callback to open the post onboarding screen + * */ +export function useOpenPostOnboardingCallback() { + const startPostOnboarding = useStartPostOnboardingCallback(); + return useCallback( + (deviceModelId: DeviceModelId) => { + startPostOnboarding({ + deviceModelId: deviceModelId, + resetNavigationStack: false, + }); + }, + [startPostOnboarding], + ); +} diff --git a/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/useOpenProtectUpsellCallback.ts b/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/useOpenProtectUpsellCallback.ts new file mode 100644 index 000000000000..6e675f3aa844 --- /dev/null +++ b/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/useOpenProtectUpsellCallback.ts @@ -0,0 +1,73 @@ +import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; +import { + Source, + useAlreadyOnboardedURI, + useHomeURI, + usePostOnboardingURI, + useTouchScreenOnboardingUpsellURI, +} from "@ledgerhq/live-common/hooks/recoverFeatureFlag"; +import { DeviceModelId } from "@ledgerhq/types-devices"; +import { useIsFocused } from "@react-navigation/core"; +import { useCallback, useEffect, useState } from "react"; +import { Linking } from "react-native"; +import { useDispatch, useSelector } from "react-redux"; +import { setHasBeenUpsoldProtect } from "~/actions/settings"; +import { internetReachable } from "~/logic/internetReachable"; +import { lastConnectedDeviceSelector, onboardingTypeSelector } from "~/reducers/settings"; +import { OnboardingType } from "~/reducers/types"; + +/** + * Returns a callback to open the Protect (Ledger Recover) upsell + * */ +export function useOpenProtectUpsellCallback() { + const lastConnectedDevice = useSelector(lastConnectedDeviceSelector); + const onboardingType = useSelector(onboardingTypeSelector); + const protectFeature = useFeature("protectServicesMobile"); + const recoverAlreadyOnboardedURI = useAlreadyOnboardedURI(protectFeature); + const recoverPostOnboardingURI = usePostOnboardingURI(protectFeature); + const touchScreenURI = useTouchScreenOnboardingUpsellURI( + protectFeature, + Source.LLM_ONBOARDING_24, + ); + const recoverHomeURI = useHomeURI(protectFeature); + const dispatch = useDispatch(); + const [redirectionStarted, setRedirectionStarted] = useState(false); + const isFocused = useIsFocused(); + + useEffect(() => { + if (redirectionStarted && !isFocused) { + dispatch(setHasBeenUpsoldProtect(true)); + } + }, [redirectionStarted, isFocused, dispatch]); + + return useCallback(async () => { + const internetConnected = await internetReachable(); + if (internetConnected && protectFeature?.enabled) { + const redirect = (url: string) => { + Linking.openURL(url); + setRedirectionStarted(true); + }; + if ( + lastConnectedDevice && + touchScreenURI && + [DeviceModelId.stax, DeviceModelId.europa].includes(lastConnectedDevice.modelId) + ) { + redirect(touchScreenURI); + } else if (recoverPostOnboardingURI && onboardingType === OnboardingType.restore) { + redirect(recoverPostOnboardingURI); + } else if (recoverHomeURI && onboardingType === OnboardingType.setupNew) { + redirect(recoverHomeURI); + } else if (recoverAlreadyOnboardedURI) { + redirect(recoverAlreadyOnboardedURI); + } + } + }, [ + lastConnectedDevice, + onboardingType, + protectFeature?.enabled, + recoverAlreadyOnboardedURI, + recoverHomeURI, + recoverPostOnboardingURI, + touchScreenURI, + ]); +} diff --git a/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/useShouldRedirect.test.ts b/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/useShouldRedirect.test.ts new file mode 100644 index 000000000000..340411f74f8d --- /dev/null +++ b/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/useShouldRedirect.test.ts @@ -0,0 +1,226 @@ +import { Device } from "@ledgerhq/live-common/hw/actions/types"; +import { useShouldRedirect } from "./useShouldRedirect"; +import { DeviceModelId } from "@ledgerhq/types-devices"; + +jest.mock("react-redux", () => ({ + useSelector: (fn: () => void) => fn(), +})); + +jest.mock("@ledgerhq/live-common/featureFlags/index", () => ({ + useFeature: jest.fn(), +})); + +jest.mock("~/reducers/settings", () => ({ + hasBeenUpsoldProtectSelector: jest.fn(), + hasBeenRedirectedToPostOnboardingSelector: jest.fn(), + lastConnectedDeviceSelector: jest.fn(), +})); + +const { useFeature } = jest.requireMock("@ledgerhq/live-common/featureFlags/index"); +const { + hasBeenUpsoldProtectSelector, + hasBeenRedirectedToPostOnboardingSelector, + lastConnectedDeviceSelector, +} = jest.requireMock("~/reducers/settings"); + +function mockUseFeature(value: { enabled: boolean }) { + useFeature.mockReturnValue(value); +} +function mockHasBeenUpsoldProtect(value: boolean) { + hasBeenUpsoldProtectSelector.mockReturnValue(value); +} + +function mockHasRedirectedToPostOnboarding(value: boolean) { + hasBeenRedirectedToPostOnboardingSelector.mockReturnValue(value); +} + +function mockLastConnectedDevice(value: Device) { + lastConnectedDeviceSelector.mockReturnValue(value); +} + +type Scenario = { + device: { modelId: DeviceModelId }; + featureFlagEnabled: boolean; + expected: { shouldRedirectToProtectUpsell: boolean; shouldRedirectToPostOnboarding: boolean }; +}; + +function testScenarios(scenarios: Scenario[]) { + it.each(scenarios)( + "should return $expected for $device and feature flag enabled: $featureFlagEnabled", + ({ device, featureFlagEnabled, expected }) => { + mockLastConnectedDevice(device as Device); + mockUseFeature({ enabled: featureFlagEnabled }); + + const result = useShouldRedirect(); + + expect( + [result.shouldRedirectToPostOnboarding, result.shouldRedirectToProtectUpsell].filter( + Boolean, + ).length, + ).toBeLessThanOrEqual(1); + + expect(result).toEqual(expected); + }, + ); +} + +describe("useShouldRedirect", () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("user HAS NOT BEEN UPSOLD protect & HAS NOT BEEN REDIRECTED to post onboarding", () => { + beforeEach(() => { + mockHasBeenUpsoldProtect(false); + mockHasRedirectedToPostOnboarding(false); + }); + + testScenarios([ + { + device: { modelId: DeviceModelId.nanoSP }, + featureFlagEnabled: false, + expected: { shouldRedirectToProtectUpsell: false, shouldRedirectToPostOnboarding: true }, + }, + { + device: { modelId: DeviceModelId.nanoSP }, + featureFlagEnabled: true, + expected: { shouldRedirectToProtectUpsell: false, shouldRedirectToPostOnboarding: true }, + }, + { + device: { modelId: DeviceModelId.nanoX }, + featureFlagEnabled: false, + expected: { shouldRedirectToProtectUpsell: true, shouldRedirectToPostOnboarding: false }, + }, + { + device: { modelId: DeviceModelId.nanoX }, + featureFlagEnabled: true, + expected: { shouldRedirectToProtectUpsell: true, shouldRedirectToPostOnboarding: false }, + }, + { + device: { modelId: DeviceModelId.stax }, + featureFlagEnabled: false, + expected: { shouldRedirectToProtectUpsell: false, shouldRedirectToPostOnboarding: true }, + }, + { + device: { modelId: DeviceModelId.stax }, + featureFlagEnabled: true, + expected: { shouldRedirectToProtectUpsell: true, shouldRedirectToPostOnboarding: false }, + }, + { + device: { modelId: DeviceModelId.europa }, + featureFlagEnabled: false, + expected: { shouldRedirectToProtectUpsell: false, shouldRedirectToPostOnboarding: true }, + }, + { + device: { modelId: DeviceModelId.europa }, + featureFlagEnabled: true, + expected: { shouldRedirectToProtectUpsell: true, shouldRedirectToPostOnboarding: false }, + }, + ]); + }); + + describe("user HAS BEEN UPSOLD protect & HAS NOT BEEN REDIRECTED to post onboarding", () => { + beforeEach(() => { + mockHasBeenUpsoldProtect(true); + mockHasRedirectedToPostOnboarding(false); + }); + + [ + DeviceModelId.nanoS, + DeviceModelId.nanoSP, + DeviceModelId.nanoX, + DeviceModelId.stax, + DeviceModelId.europa, + ].forEach(modelId => { + [true, false].forEach(featureFlagEnabled => + testScenarios([ + { + device: { modelId }, + featureFlagEnabled, + expected: { + shouldRedirectToProtectUpsell: false, + shouldRedirectToPostOnboarding: true, + }, + }, + ]), + ); + }); + }); + + describe("user HAS BEEN UPSOLD PROTECT & HAS BEEN REDIRECTED to post onboarding", () => { + beforeEach(() => { + mockHasBeenUpsoldProtect(true); + mockHasRedirectedToPostOnboarding(true); + }); + [ + DeviceModelId.nanoS, + DeviceModelId.nanoSP, + DeviceModelId.nanoX, + DeviceModelId.stax, + DeviceModelId.europa, + ].forEach(modelId => { + [true, false].forEach(featureFlagEnabled => + testScenarios([ + { + device: { modelId }, + featureFlagEnabled, + expected: { + shouldRedirectToProtectUpsell: false, + shouldRedirectToPostOnboarding: false, + }, + }, + ]), + ); + }); + }); + + describe("user HAS NOT BEEN UPSOLD protect & HAS BEEN REDIRECTED to post onboarding", () => { + beforeEach(() => { + mockHasBeenUpsoldProtect(false); + mockHasRedirectedToPostOnboarding(true); + }); + + testScenarios([ + { + device: { modelId: DeviceModelId.nanoSP }, + featureFlagEnabled: false, + expected: { shouldRedirectToProtectUpsell: false, shouldRedirectToPostOnboarding: false }, + }, + { + device: { modelId: DeviceModelId.nanoSP }, + featureFlagEnabled: true, + expected: { shouldRedirectToProtectUpsell: false, shouldRedirectToPostOnboarding: false }, + }, + { + device: { modelId: DeviceModelId.nanoX }, + featureFlagEnabled: false, + expected: { shouldRedirectToProtectUpsell: true, shouldRedirectToPostOnboarding: false }, + }, + { + device: { modelId: DeviceModelId.nanoX }, + featureFlagEnabled: true, + expected: { shouldRedirectToProtectUpsell: true, shouldRedirectToPostOnboarding: false }, + }, + { + device: { modelId: DeviceModelId.stax }, + featureFlagEnabled: false, + expected: { shouldRedirectToProtectUpsell: false, shouldRedirectToPostOnboarding: false }, + }, + { + device: { modelId: DeviceModelId.stax }, + featureFlagEnabled: true, + expected: { shouldRedirectToProtectUpsell: true, shouldRedirectToPostOnboarding: false }, + }, + { + device: { modelId: DeviceModelId.europa }, + featureFlagEnabled: false, + expected: { shouldRedirectToProtectUpsell: false, shouldRedirectToPostOnboarding: false }, + }, + { + device: { modelId: DeviceModelId.europa }, + featureFlagEnabled: true, + expected: { shouldRedirectToProtectUpsell: true, shouldRedirectToPostOnboarding: false }, + }, + ]); + }); +}); diff --git a/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/useShouldRedirect.ts b/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/useShouldRedirect.ts new file mode 100644 index 000000000000..1fb48978c797 --- /dev/null +++ b/apps/ledger-live-mobile/src/hooks/useAutoRedirectToPostOnboarding/useShouldRedirect.ts @@ -0,0 +1,38 @@ +import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; +import { DeviceModelId } from "@ledgerhq/types-devices"; +import { useSelector } from "react-redux"; +import { + hasBeenRedirectedToPostOnboardingSelector, + hasBeenUpsoldProtectSelector, + lastConnectedDeviceSelector, +} from "~/reducers/settings"; + +/** + * Returns whether the user should be redirected to the Protect upsell or the post onboarding + * */ +export function useShouldRedirect(): { + shouldRedirectToProtectUpsell: boolean; + shouldRedirectToPostOnboarding: boolean; +} { + const hasBeenUpsoldProtect = useSelector(hasBeenUpsoldProtectSelector); + const hasRedirectedToPostOnboarding = useSelector(hasBeenRedirectedToPostOnboardingSelector); + const recoverUpsellRedirection = useFeature("recoverUpsellRedirection"); + const lastConnectedDevice = useSelector(lastConnectedDeviceSelector); + const eligibleDevicesForUpsell = recoverUpsellRedirection?.enabled + ? [DeviceModelId.nanoX, DeviceModelId.stax, DeviceModelId.europa] + : [DeviceModelId.nanoX]; + + const eligibleForUpsell = lastConnectedDevice?.modelId + ? eligibleDevicesForUpsell.includes(lastConnectedDevice.modelId) + : false; + + const shouldRedirectToProtectUpsell = !hasBeenUpsoldProtect && eligibleForUpsell; + + const shouldRedirectToPostOnboarding = + !shouldRedirectToProtectUpsell && !hasRedirectedToPostOnboarding; + + return { + shouldRedirectToProtectUpsell, + shouldRedirectToPostOnboarding, + }; +} diff --git a/apps/ledger-live-mobile/src/index.tsx b/apps/ledger-live-mobile/src/index.tsx index f7345a14b3c5..2cfc962a7b25 100644 --- a/apps/ledger-live-mobile/src/index.tsx +++ b/apps/ledger-live-mobile/src/index.tsx @@ -65,7 +65,6 @@ import Modals from "~/screens/Modals"; import NavBarColorHandler from "~/components/NavBarColorHandler"; import { FirebaseRemoteConfigProvider } from "~/components/FirebaseRemoteConfig"; import { FirebaseFeatureFlagsProvider } from "~/components/FirebaseFeatureFlags"; -import AdjustSetup from "~/components/AdjustSetup"; import { TermsAndConditionMigrateLegacyData } from "~/logic/terms"; import HookDynamicContentCards from "~/dynamicContent/useContentCards"; import PlatformAppProviderWrapper from "./PlatformAppProviderWrapper"; @@ -334,7 +333,6 @@ export default class Root extends Component { <> - diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index fcac11ccce4d..d38d68373596 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -315,6 +315,20 @@ "title": "{{message}}", "description": "Something went wrong. Please retry. If the problem persists, please save your logs using the button below and provide them to Ledger Support." }, + "TransactionBroadcastError": { + "title": "Transaction Broadcast Unsuccessfull", + "description": "Your transaction failed to broadcast on the {{network}} network. Your {{coin}} have not been transferred and remain in your account.", + "needHelp": "Need help ?", + "helpCenter": { + "title": "Help center", + "desc": "Visit our Help center or contact us via the widget for assistance.", + "cta": "Get help" + }, + "technical": { + "title": "Technical error", + "cta": "Save logs" + } + }, "FeeEstimationFailed": { "title": "Sorry, fee estimation failed", "description": "Try setting the fee manually (status: {{status}})." @@ -2856,9 +2870,9 @@ "developerMode": "Developer mode", "developerModeDesc": "Show developer apps in My Ledger and enable Testnet apps.", "analytics": "Analytics", - "analyticsDesc": "Enable Ledger to track app usage data to help measure Ledger Live’s performance and enhance both the app and your experience.", + "analyticsDesc": "Enable Ledger to collect app usage data to help measure Ledger Live’s performance and enhance both the app and your experience.", "personalizedRecommendations": "Personalized experience", - "personalizedRecommendationsDesc": "Enable Ledger to track app usage data to provide personalized recommendations and content that match your preferences and to help measure the performance of our marketing campaigns.", + "personalizedRecommendationsDesc": "Enable Ledger to collect app usage data to provide personalized recommendations and content that match your preferences and to help measure the performance of our marketing campaigns.", "analyticsModal": { "title": "Share analytics", "desc": "Enable analytics to help Ledger improve user experience", @@ -6789,12 +6803,12 @@ "subtitle": "Sharing your Ledger Live data helps us understand your preferences and show content that's relevant to you.", "content": { "able": { - "title": "With your consent, Ledger will be able to track data about how you use Ledger Live (including page visits and clicks):", + "title": "With your consent, Ledger will be able to collect data about how you use Ledger Live (including page visits and clicks):", "diagAndUsage": "To measure the performance of Ledger Live and enhance both the app and your experience (Analytics).", "personnalizationData": "To provide you with personalized recommendations and content tailored to your preferences, as well as to help us measure the effectiveness of our marketing campaigns (Personalization)." }, "unable": { - "title": "Ledger Live will never track information regarding:", + "title": "Ledger Live will never collect information regarding:", "adresses": "Your assets.", "balance": "Your portfolio.", "personnalInfos": "Your personal identifying information (such as your name or your email address)." @@ -6806,7 +6820,7 @@ }, "infoText": { "info": "You can revoke your consent any time in the app settings.", - "link": "Learn more about our Tracking Policy" + "link": "Learn more about how we handle your data" } } }, diff --git a/apps/ledger-live-mobile/src/logic/postOnboarding/useNavigateToPostOnboardingHubCallback.ts b/apps/ledger-live-mobile/src/logic/postOnboarding/useNavigateToPostOnboardingHubCallback.ts index ef7952266928..20fa38013395 100644 --- a/apps/ledger-live-mobile/src/logic/postOnboarding/useNavigateToPostOnboardingHubCallback.ts +++ b/apps/ledger-live-mobile/src/logic/postOnboarding/useNavigateToPostOnboardingHubCallback.ts @@ -4,11 +4,11 @@ import { RootNavigation } from "~/components/RootNavigator/types/helpers"; import { NavigatorName, ScreenName } from "~/const"; export function useNavigateToPostOnboardingHubCallback() { - const navigation = useNavigation(); + const navigation = useNavigation(); return useCallback( (resetNavigationStack?: boolean) => { if (resetNavigationStack) { - (navigation as unknown as RootNavigation).reset({ + navigation.reset({ index: 0, routes: [ { @@ -34,8 +34,11 @@ export function useNavigateToPostOnboardingHubCallback() { ], }); } else { - navigation.navigate(NavigatorName.PostOnboarding, { - screen: ScreenName.PostOnboardingHub, + navigation.navigate(NavigatorName.Base, { + screen: NavigatorName.PostOnboarding, + params: { + screen: ScreenName.PostOnboardingHub, + }, }); } }, diff --git a/apps/ledger-live-mobile/src/logic/screenTransactionHooks.ts b/apps/ledger-live-mobile/src/logic/screenTransactionHooks.ts index 018b2a115fb3..be7cafaa0ff8 100644 --- a/apps/ledger-live-mobile/src/logic/screenTransactionHooks.ts +++ b/apps/ledger-live-mobile/src/logic/screenTransactionHooks.ts @@ -14,6 +14,10 @@ import { formatOperation, formatAccount, } from "@ledgerhq/live-common/account/index"; +import { + createTransactionBroadcastError, + TransactionBroadcastError, +} from "@ledgerhq/live-common/errors/transactionBroadcastErrors"; import { formatTransaction } from "@ledgerhq/live-common/transaction/index"; import { getAccountBridge } from "@ledgerhq/live-common/bridge/index"; import { execAndWaitAtLeast } from "@ledgerhq/live-common/promise"; @@ -24,6 +28,7 @@ import { StackNavigationProp } from "@react-navigation/stack"; import { updateAccountWithUpdater } from "../actions/accounts"; import logger from "../logger"; import { ScreenName } from "~/const"; +import { urls } from "~/utils/urls"; import type { StackNavigatorNavigation, StackNavigatorRoute, @@ -260,7 +265,14 @@ export function useSignedTxHandler({ throw transactionSignError; } - const operation = await broadcast(signedOperation); + const operation = await broadcast(signedOperation).catch((err: Error) => { + const currency = mainAccount.currency; + throw createTransactionBroadcastError(err, urls, { + network: currency.name, + coin: currency.ticker, + }); + }); + log( "transaction-summary", `✔️ broadcasted! optimistic operation: ${formatOperation(mainAccount)(operation)}`, @@ -282,6 +294,16 @@ export function useSignedTxHandler({ logger.critical(error as Error); } + if ( + error instanceof TransactionBroadcastError && + route.name === ScreenName.SendConnectDevice + ) { + return (navigation as StackNavigationProp<{ [key: string]: object }>).replace( + ScreenName.SendBroadcastError, + { ...route.params, error }, + ); + } + (navigation as StackNavigationProp<{ [key: string]: object }>).replace( route.name.replace("ConnectDevice", "ValidationError"), { ...route.params, error }, diff --git a/apps/ledger-live-mobile/src/navigation/DeeplinksProvider.tsx b/apps/ledger-live-mobile/src/navigation/DeeplinksProvider.tsx index 177faea6b79c..41a84eda32a6 100644 --- a/apps/ledger-live-mobile/src/navigation/DeeplinksProvider.tsx +++ b/apps/ledger-live-mobile/src/navigation/DeeplinksProvider.tsx @@ -100,6 +100,7 @@ const linkingOptions = () => ({ prefixes: [ "ledgerlive://", "https://ledger.com", + // FIXME: We will be fixing the universal links in this epic : https://ledgerhq.atlassian.net/browse/LIVE-14732 /** * Adjust universal links attached to iOS Bundle ID com.ledger.live * (local debug, prod & nightly builds) diff --git a/apps/ledger-live-mobile/src/newArch/components/Collapsible/index.tsx b/apps/ledger-live-mobile/src/newArch/components/Collapsible/index.tsx new file mode 100644 index 000000000000..15bc6c47dd28 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/components/Collapsible/index.tsx @@ -0,0 +1,71 @@ +import React, { memo, ReactNode, useCallback, useState } from "react"; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + runOnJS, +} from "react-native-reanimated"; +import styled from "styled-components/native"; +import { Flex, Icons, Text } from "@ledgerhq/native-ui"; +import { FlexBoxProps } from "@ledgerhq/native-ui/lib/components/Layout/Flex/index"; + +export default memo(Collapsible); + +type Props = FlexBoxProps & { + title: ReactNode; + children: ReactNode; + collapsed?: boolean; +}; + +function Collapsible({ title, children, collapsed = false, ...titleContainerProps }: Props) { + const [isCollapsed, setIsCollapsed] = useState(collapsed); + const collapseAnimation = useSharedValue(collapsed ? 0 : 1); + + const toggleCollapsed = useCallback(() => { + const value = Math.round(collapseAnimation.value + 1) % 2; + + const { onStart, onDone }: { onStart?: () => void; onDone: () => void } = + value === 0 + ? { onDone: () => setIsCollapsed(true) } + : { onStart: () => setIsCollapsed(false), onDone: () => {} }; + + onStart?.(); + + collapseAnimation.value = withTiming(value, { duration: 200 }, finished => { + if (finished) { + runOnJS(onDone)(); + } + }); + }, [collapseAnimation]); + + const animatedChevron = useAnimatedStyle(() => ({ + transform: [{ rotate: `${collapseAnimation.value * 90}deg` }], + })); + const animateContent = useAnimatedStyle(() => ({ + maxHeight: `${collapseAnimation.value * 100}%`, + })); + + const header = typeof title === "string" ? {title} : title; + + return ( + <> + + + {header} + + + + + + + + {!isCollapsed && children} + + + ); +} + +const Toggle = styled.TouchableOpacity` + flex-direction: row; + align-items: center; +`; diff --git a/apps/ledger-live-mobile/src/newArch/components/CopyButton/index.tsx b/apps/ledger-live-mobile/src/newArch/components/CopyButton/index.tsx new file mode 100644 index 000000000000..5ac267aff549 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/components/CopyButton/index.tsx @@ -0,0 +1,58 @@ +import Clipboard from "@react-native-clipboard/clipboard"; +import React, { memo, useCallback, useMemo } from "react"; +import Animated, { useSharedValue, useAnimatedStyle, withTiming } from "react-native-reanimated"; +import styled from "styled-components/native"; +import { Button, Icons } from "@ledgerhq/native-ui"; +import { ButtonProps } from "@ledgerhq/native-ui/components/cta/Button"; + +export default memo(CopyButton); + +type Props = Omit & { + text: string; + transitionDuration?: number; +}; + +function CopyButton({ text, ...props }: Props) { + const transition = useSharedValue(0); + const handleCopy = useCallback(() => { + Clipboard.setString(text); + + transition.value = withTiming(1, { duration: 200 }); + setTimeout(() => (transition.value = withTiming(0, { duration: 200 })), 1200); + }, [text, transition]); + + const copyIconAnimation = useAnimatedStyle(() => ({ + opacity: 1 - transition.value, + })); + + const checkIconAnimation = useAnimatedStyle(() => ({ + opacity: transition.value, + })); + + const icon = useMemo( + () => ( + + + + + + + + + ), + [copyIconAnimation, checkIconAnimation], + ); + + return + + + ); +} + +type InformativeBannerProps = { + title: string; + description: string; + numberOfLines?: number; + children: React.ReactNode; +}; + +function InformativeBanner({ + title, + description, + numberOfLines, + children, +}: InformativeBannerProps) { + return ( + + + {title} : {description} + + {children} + + ); +} + +const InformativeBannerButton = styled(Button).attrs({ + isNewIcon: true, + iconPosition: "left", + size: "small", + activeOpacity: 0.5, +})` + background-color: ${({ theme }) => theme.colors.opacityDefault.c05}; + border-radius: 8px; +`; diff --git a/apps/ledger-live-mobile/src/screens/Settings/Debug/Configuration/RecoverUpsellRow.tsx b/apps/ledger-live-mobile/src/screens/Settings/Debug/Configuration/RecoverUpsellRow.tsx new file mode 100644 index 000000000000..a85163724364 --- /dev/null +++ b/apps/ledger-live-mobile/src/screens/Settings/Debug/Configuration/RecoverUpsellRow.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import SettingsRow from "~/components/SettingsRow"; +import Switch from "~/components/Switch"; +import { useFeature, useFeatureFlags } from "@ledgerhq/live-common/featureFlags/index"; + +export function RecoverUpsellRow() { + const { overrideFeature, resetFeature } = useFeatureFlags(); + + const protectFeature = useFeature("protectServicesMobile"); + + if (protectFeature === null || protectFeature === undefined) return null; + + const currentTarget = protectFeature?.params?.protectId; + + const onChange = (enabled: boolean) => { + if (enabled) { + overrideFeature("protectServicesMobile", { + ...protectFeature, + params: { ...protectFeature?.params, protectId: "protect-prod" }, + }); + } else { + resetFeature("protectServicesMobile"); + } + }; + + return ( + + + + ); +} diff --git a/apps/ledger-live-mobile/src/screens/Settings/Debug/Configuration/ResetOnboardingStateRow.tsx b/apps/ledger-live-mobile/src/screens/Settings/Debug/Configuration/ResetOnboardingStateRow.tsx index a364d984d4ff..ecc1cf381937 100644 --- a/apps/ledger-live-mobile/src/screens/Settings/Debug/Configuration/ResetOnboardingStateRow.tsx +++ b/apps/ledger-live-mobile/src/screens/Settings/Debug/Configuration/ResetOnboardingStateRow.tsx @@ -1,7 +1,13 @@ import React, { useContext } from "react"; import { useDispatch, useSelector } from "react-redux"; import SettingsRow from "~/components/SettingsRow"; -import { completeOnboarding, setHasOrderedNano, setReadOnlyMode } from "~/actions/settings"; +import { + completeOnboarding, + setHasBeenRedirectedToPostOnboarding, + setHasBeenUpsoldProtect, + setHasOrderedNano, + setReadOnlyMode, +} from "~/actions/settings"; import { RebootContext } from "~/context/Reboot"; import { knownDevicesSelector } from "~/reducers/ble"; import { removeKnownDevices } from "~/actions/ble"; @@ -22,6 +28,8 @@ export default function ResetOnboardingStateRow() { dispatch(setHasOrderedNano(false)); dispatch(completeOnboarding(false)); dispatch(removeKnownDevices(knownDevices.map(d => d.id))); + dispatch(setHasBeenUpsoldProtect(false)); + dispatch(setHasBeenRedirectedToPostOnboarding(false)); unacceptGeneralTerms(); requestAnimationFrame(() => { reboot(); diff --git a/apps/ledger-live-mobile/src/screens/Settings/Debug/Configuration/index.tsx b/apps/ledger-live-mobile/src/screens/Settings/Debug/Configuration/index.tsx index 369b7308e0c0..19334be5c9ec 100644 --- a/apps/ledger-live-mobile/src/screens/Settings/Debug/Configuration/index.tsx +++ b/apps/ledger-live-mobile/src/screens/Settings/Debug/Configuration/index.tsx @@ -17,6 +17,7 @@ import ResetOnboardingStateRow from "./ResetOnboardingStateRow"; import NftMetadataServiceRow from "./NftMetadataServiceRow"; import HasStaxEuropaRows from "./HasStaxEuropaRows"; import SkipOnboardingRow from "./SkipOnboardingRow"; +import { RecoverUpsellRow } from "./RecoverUpsellRow"; export default function Configuration() { const navigation = useNavigation>(); @@ -40,6 +41,7 @@ export default function Configuration() { + diff --git a/apps/ledger-live-mobile/src/screens/SyncOnboarding/CompletionScreen.tsx b/apps/ledger-live-mobile/src/screens/SyncOnboarding/CompletionScreen.tsx index cd8a3b7fd76f..1e726129e92b 100644 --- a/apps/ledger-live-mobile/src/screens/SyncOnboarding/CompletionScreen.tsx +++ b/apps/ledger-live-mobile/src/screens/SyncOnboarding/CompletionScreen.tsx @@ -1,8 +1,7 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useEffect } from "react"; import { Flex } from "@ledgerhq/native-ui"; import { StackScreenProps } from "@react-navigation/stack"; import { TouchableWithoutFeedback } from "react-native-gesture-handler"; -import { useStartPostOnboardingCallback } from "@ledgerhq/live-common/postOnboarding/hooks/useStartPostOnboardingCallback"; import { NavigatorName, ScreenName } from "~/const"; import { SyncOnboardingStackParamList } from "~/components/RootNavigator/types/SyncOnboardingNavigator"; @@ -10,6 +9,8 @@ import { BaseComposite, RootNavigation } from "~/components/RootNavigator/types/ import { DeviceModelId } from "@ledgerhq/devices"; import EuropaCompletionView from "./EuropaCompletionView"; import StaxCompletionView from "./StaxCompletionView"; +import { useDispatch } from "react-redux"; +import { setHasBeenRedirectedToPostOnboarding } from "~/actions/settings"; type Props = BaseComposite< StackScreenProps @@ -17,40 +18,41 @@ type Props = BaseComposite< const CompletionScreen = ({ navigation, route }: Props) => { const { device } = route.params; - const startPostOnboarding = useStartPostOnboardingCallback(); - - const redirectToPostOnboarding = useCallback(() => { - startPostOnboarding({ - deviceModelId: device.modelId, - resetNavigationStack: true, - fallbackIfNoAction: () => - // Resets the navigation stack to avoid allowing to go back to the onboarding welcome screen - // FIXME: bindings to react-navigation seem to have issues with composites - (navigation as unknown as RootNavigation).reset({ - index: 0, - routes: [ - { - name: NavigatorName.Base, - state: { - routes: [ - { - name: NavigatorName.Main, - }, - ], + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(setHasBeenRedirectedToPostOnboarding(false)); + }, [dispatch]); + + const hasRedirected = React.useRef(false); + + const redirectToMainScreen = useCallback(() => { + if (hasRedirected.current) return; + hasRedirected.current = true; + (navigation as unknown as RootNavigation).reset({ + index: 0, + routes: [ + { + name: NavigatorName.Base, + state: { + routes: [ + { + name: NavigatorName.Main, }, - }, - ], - }), + ], + }, + }, + ], }); - }, [device.modelId, navigation, startPostOnboarding]); + }, [navigation]); return ( - + {device.modelId === DeviceModelId.europa ? ( - + ) : ( - + )} diff --git a/apps/ledger-live-mobile/src/screens/SyncOnboarding/SyncOnboardingCompanion.tsx b/apps/ledger-live-mobile/src/screens/SyncOnboarding/SyncOnboardingCompanion.tsx index 13a2e30b11c5..9a3a9e9e3133 100644 --- a/apps/ledger-live-mobile/src/screens/SyncOnboarding/SyncOnboardingCompanion.tsx +++ b/apps/ledger-live-mobile/src/screens/SyncOnboarding/SyncOnboardingCompanion.tsx @@ -18,7 +18,7 @@ import { useTranslation } from "react-i18next"; import { getDeviceModel } from "@ledgerhq/devices"; import { Device } from "@ledgerhq/live-common/hw/actions/types"; import { useDispatch } from "react-redux"; -import useFeature from "@ledgerhq/live-common/featureFlags/useFeature"; +import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; import { SeedPhraseType, StorylyInstanceID } from "@ledgerhq/types-live"; import { DeviceModelId } from "@ledgerhq/types-devices"; @@ -148,6 +148,8 @@ export const SyncOnboardingCompanion: React.FC = ( const { t } = useTranslation(); const dispatchRedux = useDispatch(); const deviceInitialApps = useFeature("deviceInitialApps"); + const recoverUpsellRedirection = useFeature("recoverUpsellRedirection"); + const hasBackupStep = !recoverUpsellRedirection?.enabled; const productName = getDeviceModel(device.modelId).productName || device.modelId; const deviceName = device.deviceName || productName; @@ -369,17 +371,21 @@ export const SyncOnboardingCompanion: React.FC = ( if (deviceOnboardingState?.isOnboarded && !seededDeviceHandled.current) { if (deviceOnboardingState?.currentOnboardingStep === DeviceOnboardingStep.Ready) { // device was just seeded - setCompanionStepKey(CompanionStepKey.Backup); + setCompanionStepKey(hasBackupStep ? CompanionStepKey.Backup : CompanionStepKey.Apps); seededDeviceHandled.current = true; return; } else if ( deviceOnboardingState?.currentOnboardingStep === DeviceOnboardingStep.WelcomeScreen1 ) { - // switch to the apps step - __DEV__ - ? setCompanionStepKey(CompanionStepKey.Backup) // for ease of testing in dev mode without having to reset the device - : setCompanionStepKey(CompanionStepKey.Apps); - + // device was already seeded + if (hasBackupStep) { + __DEV__ + ? setCompanionStepKey(CompanionStepKey.Backup) // for ease of testing in dev mode without having to reset the device + : setCompanionStepKey(CompanionStepKey.Apps); + } else { + // switch to the apps step + setCompanionStepKey(CompanionStepKey.Apps); + } seededDeviceHandled.current = true; return; } @@ -433,7 +439,12 @@ export const SyncOnboardingCompanion: React.FC = ( default: break; } - }, [deviceOnboardingState, notifyEarlySecurityCheckShouldReset, shouldRestoreApps]); + }, [ + deviceOnboardingState, + notifyEarlySecurityCheckShouldReset, + hasBackupStep, + shouldRestoreApps, + ]); // When the user gets close to the seed generation step, sets the lost synchronization delay // and timers to a higher value. It avoids having a warning message while the connection is lost @@ -638,17 +649,21 @@ export const SyncOnboardingCompanion: React.FC = ( ), }, - { - key: CompanionStepKey.Backup, - title: t("syncOnboarding.backup.title"), - doneTitle: t("syncOnboarding.backup.title"), - renderBody: () => ( - setCompanionStepKey(CompanionStepKey.Apps)} - /> - ), - }, + ...(hasBackupStep + ? [ + { + key: CompanionStepKey.Backup, + title: t("syncOnboarding.backup.title"), + doneTitle: t("syncOnboarding.backup.title"), + renderBody: () => ( + setCompanionStepKey(CompanionStepKey.Apps)} + /> + ), + }, + ] + : []), ...(deviceInitialApps?.enabled ? [ { @@ -682,13 +697,14 @@ export const SyncOnboardingCompanion: React.FC = ( [ t, productName, - seedPathStatus, + hasBackupStep, deviceInitialApps?.enabled, device, + seedPathStatus, + shouldRestoreApps, handleInstallAppsComplete, initialAppsToInstall, companionStepKey, - shouldRestoreApps, ], ); diff --git a/apps/ledger-live-mobile/src/utils/urls.tsx b/apps/ledger-live-mobile/src/utils/urls.tsx index ed9e3fb8d5c1..0fd754e885e0 100644 --- a/apps/ledger-live-mobile/src/utils/urls.tsx +++ b/apps/ledger-live-mobile/src/utils/urls.tsx @@ -218,4 +218,11 @@ export const urls = { learnMore: "https://support.ledger.com/article/4404389367057-zd", }, updateLedgerLive: "https://support.ledger.com/article/4410960111889-zd", + + // Node errors + txBroadcastErrors: { + badTxns: "https://support.ledger.com/article/5129526865821-zd", + blobsLimit: "https://support.ledger.com/article/17830974229661-zd", + txnMempoolConflict: "https://support.ledger.com/article/14593285242525-zd", + }, }; diff --git a/knip.json b/knip.json new file mode 100644 index 000000000000..8fa93bfc396a --- /dev/null +++ b/knip.json @@ -0,0 +1,112 @@ +{ + "$schema": "https://unpkg.com/knip@5/schema.json", + "rules": { + "binaries": "off", + "classMembers": "off", + "dependencies": "error", + "devDependencies": "off", + "optionalPeerDependencies": "off", + "duplicates": "warn", + "enumMembers": "warn", + "exports": "warn", + "files": "error", + "nsExports": "off", + "nsTypes": "off", + "types": "warn", + "unlisted": "off", + "unresolved": "off" + }, + "ignoreExportsUsedInFile": true, + "project": ["src/**"], + + "ignore": [ + "**/__integrations__/*", + "**/*.test.*", + "**/*.spec.*", + "**/tests/**", + "**/__test__/**", + "**/__tests__/**", + "**/__mocks__/**", + "**/e2e/**", + "**/*.config.*", + "**/scripts/**", + "**/tools/**" + ], + + "workspaces": { + "./apps/ledger-live-desktop": { + "entry": [ + "src/index.ts", + "src/renderer/index.ts", + "src/preloader/index.ts", + "src/renderer/webworkers/workers/*.ts", + "src/webviewPreloader/dappPreloader.ts", + "src/webviewPreloader/index.ts" + ], + "ignore": ["src/newArch/features/Collectibles/**", "**/types.*"], + "ignoreBinaries": ["eslint", "knip", "prettier", "zx", "lint"], + "ignoreDependencies": ["prop-types", "allure-commandline", "msw"] + }, + "./apps/ledger-live-mobile": { + "entry": ["index.js", "src/families/**"], + "ignore": ["src/**/*.android.*", "src/**/*.ios.*", "src/logic/keyboardVisible.ts"], + "ignoreDependencies": [ + "@react-native-masked-view/masked-view", + "@react-native/gradle-plugin", + "@react-native/metro-config", + "asyncstorage-down", + "buffer", + "expo-crypto", + "expo-file-system", + "expo-image-loader", + "expo-modules-autolinking", + "expo-modules-core", + "prop-types", + "react-native-fast-pbkdf2", + "react-native-level-fs", + "react-native-navigation-bar-color", + "react-native-randombytes", + "react-native-tcp-socket", + "react-native-udp" + ] + }, + "./libs/live-nft-react": { + "entry": ["src/index.ts", "src/tools/*", "src/hooks/*"] + }, + "./libs/live-nft": { + "entry": ["src/api/index.ts", "src/index.ts", "src/index.test.ts", "src/types.ts"] + }, + "./libs/env": { + "entry": ["src/index.ts"] + }, + "./libs/live-hooks": { + "entry": ["src/useDebounce.ts", "src/useThrottledFunction.ts"] + }, + "./libs/live-countervalues": { + "entry": ["src/logic.ts", "src/api/index.ts", "src/types.ts", "src/portfolio.ts"] + }, + "./libs/live-countervalues-react": { + "entry": ["src/index.tsx", "src/portfolio.tsx"] + }, + "./libs/live-wallet": { + "entry": [ + "src/walletsync/index.ts", + "src/cloudsync/index.ts", + "src/liveqr/cross.ts", + "src/liveqr/importAccounts.ts", + "src/ordering.ts", + "src/addAccounts.ts", + "src/store.ts", + "src/accountName.ts" + ] + }, + "./libs/ledger-key-ring-protocol": { + "entry": ["src/index.ts", "src/store.ts", "src/qrcode/index.ts"] + }, + "./libs/hw-ledger-key-ring-protocol": { + "entry": ["src/index.ts"], + "ignoreDependencies": ["@ledgerhq/logs", "@ledgerhq/live-env"] + }, + "./libs/promise": { "entry": ["src/index.ts"] } + } +} diff --git a/libs/coin-modules/coin-stellar/src/types/bridge.ts b/libs/coin-modules/coin-stellar/src/types/bridge.ts index ed0a5f21c03b..93b5487ba362 100644 --- a/libs/coin-modules/coin-stellar/src/types/bridge.ts +++ b/libs/coin-modules/coin-stellar/src/types/bridge.ts @@ -32,7 +32,13 @@ export enum NetworkCongestionLevel { HIGH = "HIGH", } -export const StellarMemoType = ["NO_MEMO", "MEMO_TEXT", "MEMO_ID", "MEMO_HASH", "MEMO_RETURN"]; +export const StellarMemoType = [ + "NO_MEMO", + "MEMO_TEXT", + "MEMO_ID", + "MEMO_HASH", + "MEMO_RETURN", +] as const; export type StellarTransactionMode = "send" | "changeTrust"; diff --git a/libs/coin-modules/coin-xrp/src/api/index.integ.test.ts b/libs/coin-modules/coin-xrp/src/api/index.integ.test.ts index b9ad99455218..eeb6853d6409 100644 --- a/libs/coin-modules/coin-xrp/src/api/index.integ.test.ts +++ b/libs/coin-modules/coin-xrp/src/api/index.integ.test.ts @@ -5,7 +5,8 @@ import { sign } from "ripple-keypairs"; describe("Xrp Api", () => { let module: Api; - const address = "rKtXXTVno77jhu6tto1MAXjepyuaKaLcqB"; + const address = "rh1HPuRVsYYvThxG2Bs1MfjmrVC73S16Fb"; + const emptyAddress = "rKtXXTVno77jhu6tto1MAXjepyuaKaLcqB"; // Account with no transaction (at the time of this writing) const xrpPubKey = process.env["PUB_KEY"]!; const xrpSecretKey = process.env["SECRET_KEY"]!; @@ -16,7 +17,6 @@ describe("Xrp Api", () => { describe("estimateFees", () => { it("returns a default value", async () => { // Given - const address = "rDCyjRD2TcSSGUQpEcEhJGmDWfjPJpuGxu"; const amount = BigInt(100); // When @@ -56,12 +56,20 @@ describe("Xrp Api", () => { }); describe("getBalance", () => { - it("returns a list regarding address parameter", async () => { + it("returns an amount above 0 when address has transactions", async () => { // When const result = await module.getBalance(address); // Then - expect(result).toBeGreaterThan(0); + expect(result).toBeGreaterThan(BigInt(0)); + }); + + it("returns 0 when address has no transaction", async () => { + // When + const result = await module.getBalance(emptyAddress); + + // Then + expect(result).toBe(BigInt(0)); }); }); @@ -76,14 +84,16 @@ describe("Xrp Api", () => { }); // Then - expect(result.slice(0, 34)).toEqual("120000228000000024001BCDA6201B001F"); + expect(result.slice(0, 34)).toEqual("1200002280000000240002588F201B001D"); expect(result.slice(38)).toEqual( - "61400000000000000A6840000000000000018114CF30F590D7A9067B2604D80D46090FBF342EBE988314CA26FB6B0EF6859436C2037BA0A9913208A59B98", + "61400000000000000A68400000000000000181142A6ADC782DAFDDB464E434B684F01416B8A33B208314CA26FB6B0EF6859436C2037BA0A9913208A59B98", ); }); }); - describe("combine", () => { + // To enable this test, you need to fill an `.env` file at the root of this package. Example can be found in `.env.integ.test.example`. + // The value hardcoded here depends on the value filled in the `.env` file. + describe.skip("combine", () => { it("returns a signed raw transaction", async () => { // Given const rawTx = diff --git a/libs/coin-modules/coin-xrp/src/bridge/synchronization.ts b/libs/coin-modules/coin-xrp/src/bridge/synchronization.ts index 215394b51fd0..39d09e749fd0 100644 --- a/libs/coin-modules/coin-xrp/src/bridge/synchronization.ts +++ b/libs/coin-modules/coin-xrp/src/bridge/synchronization.ts @@ -3,7 +3,7 @@ import { Operation } from "@ledgerhq/types-live"; import { encodeAccountId } from "@ledgerhq/coin-framework/account/index"; import { GetAccountShape, mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers"; import { getAccountInfo, getServerInfos, getTransactions } from "../network"; -import { NEW_ACCOUNT_ERROR_MESSAGE, parseAPIValue } from "../logic"; +import { parseAPIValue } from "../logic"; import { filterOperations } from "./logic"; export const getAccountShape: GetAccountShape = async info => { @@ -17,7 +17,7 @@ export const getAccountShape: GetAccountShape = async info => { }); const accountInfo = await getAccountInfo(address); - if (!accountInfo || accountInfo.error === NEW_ACCOUNT_ERROR_MESSAGE) { + if (accountInfo.isNewAccount) { return { id: accountId, xpub: address, @@ -42,10 +42,10 @@ export const getAccountShape: GetAccountShape = async info => { const minLedgerVersion = Number(ledgers[0]); const maxLedgerVersion = Number(ledgers[1]); - const trustlines = accountInfo.account_data.OwnerCount; + const trustlines = accountInfo.ownerCount; - const balance = new BigNumber(accountInfo.account_data.Balance); - const spendableBalance = new BigNumber(accountInfo.account_data.Balance) + const balance = new BigNumber(accountInfo.balance); + const spendableBalance = new BigNumber(accountInfo.balance) .minus(reserveMinXRP) .minus(reservePerTrustline.times(trustlines)); diff --git a/libs/coin-modules/coin-xrp/src/logic/getBalance.test.ts b/libs/coin-modules/coin-xrp/src/logic/getBalance.test.ts index 45eb26491265..95c74aafc207 100644 --- a/libs/coin-modules/coin-xrp/src/logic/getBalance.test.ts +++ b/libs/coin-modules/coin-xrp/src/logic/getBalance.test.ts @@ -3,7 +3,7 @@ import { getBalance } from "./getBalance"; const mockGetAccountInfo = jest.fn(); jest.mock("../network", () => ({ - getAccountInfo: (arg: unknown) => mockGetAccountInfo(arg), + getAccountInfo: (address: string) => mockGetAccountInfo(address), })); describe("getBalance", () => { @@ -16,9 +16,7 @@ describe("getBalance", () => { const balance = faker.number.bigInt(100_000_000); const address = "ACCOUNT_ADDRESS"; mockGetAccountInfo.mockResolvedValue({ - account_data: { - Balance: balance.toString(), - }, + balance, }); // When diff --git a/libs/coin-modules/coin-xrp/src/logic/getBalance.ts b/libs/coin-modules/coin-xrp/src/logic/getBalance.ts index ffc6d3925061..43906cc87d67 100644 --- a/libs/coin-modules/coin-xrp/src/logic/getBalance.ts +++ b/libs/coin-modules/coin-xrp/src/logic/getBalance.ts @@ -2,5 +2,5 @@ import { getAccountInfo } from "../network"; export async function getBalance(address: string): Promise { const accountInfo = await getAccountInfo(address); - return BigInt(accountInfo.account_data.Balance); + return BigInt(accountInfo.balance); } diff --git a/libs/coin-modules/coin-xrp/src/logic/index.ts b/libs/coin-modules/coin-xrp/src/logic/index.ts index 67681c8d3fbb..034fa644dc3b 100644 --- a/libs/coin-modules/coin-xrp/src/logic/index.ts +++ b/libs/coin-modules/coin-xrp/src/logic/index.ts @@ -13,6 +13,3 @@ export { } from "./utils"; export { parseAPIValue } from "./common"; - -//FIXME -export { NEW_ACCOUNT_ERROR_MESSAGE } from "../network"; diff --git a/libs/coin-modules/coin-xrp/src/logic/utils.test.ts b/libs/coin-modules/coin-xrp/src/logic/utils.test.ts new file mode 100644 index 000000000000..1c76d6a024c7 --- /dev/null +++ b/libs/coin-modules/coin-xrp/src/logic/utils.test.ts @@ -0,0 +1,60 @@ +import { cachedRecipientIsNew } from "./utils"; + +jest.mock("ripple-address-codec", () => ({ + isValidClassicAddress: () => true, +})); +const mockGetAccountInfo = jest.fn(); +jest.mock("../network", () => ({ + getAccountInfo: (address: string) => mockGetAccountInfo(address), +})); + +describe("cachedRecipientIsNew", () => { + afterEach(() => { + mockGetAccountInfo.mockClear(); + }); + + it("returns true when network returns a new empty account", async () => { + // Given + mockGetAccountInfo.mockResolvedValueOnce({ + isNewAccount: true, + balance: "0", + ownerCount: 0, + sequence: 0, + }); + + // When + const result = await cachedRecipientIsNew("address1"); + + // Then + expect(mockGetAccountInfo).toHaveBeenCalledTimes(1); + expect(result).toBeTruthy(); + }); + + it("returns false when network a valid AccountInfo", async () => { + // Given + mockGetAccountInfo.mockResolvedValueOnce({ + isNewAccount: false, + balance: "999441667919804", + ownerCount: 0, + sequence: 999441667919804, + }); + + // When + const result = await cachedRecipientIsNew("address2"); + + // Then + expect(mockGetAccountInfo).toHaveBeenCalledTimes(1); + expect(result).toBeFalsy(); + }); + + it("throws an error when network throws an error", async () => { + // Given + mockGetAccountInfo.mockImplementationOnce(() => { + throw new Error("Malformed address"); + }); + + // When & Then + await expect(cachedRecipientIsNew("address3")).rejects.toThrow("Malformed address"); + expect(mockGetAccountInfo).toHaveBeenCalledTimes(1); + }); +}); diff --git a/libs/coin-modules/coin-xrp/src/logic/utils.ts b/libs/coin-modules/coin-xrp/src/logic/utils.ts index ff4bdaa91b85..2afeb46e5961 100644 --- a/libs/coin-modules/coin-xrp/src/logic/utils.ts +++ b/libs/coin-modules/coin-xrp/src/logic/utils.ts @@ -1,6 +1,6 @@ import BigNumber from "bignumber.js"; import { isValidClassicAddress } from "ripple-address-codec"; -import { getAccountInfo, NEW_ACCOUNT_ERROR_MESSAGE } from "../network"; +import { getAccountInfo } from "../network"; export const UINT32_MAX = new BigNumber(2).pow(32).minus(1); @@ -15,7 +15,7 @@ export const validateTag = (tag: BigNumber) => { export const getNextValidSequence = async (address: string) => { const accInfo = await getAccountInfo(address, true); - return accInfo.account_data.Sequence; + return accInfo.sequence; }; function isRecipientValid(recipient: string): boolean { @@ -26,10 +26,7 @@ const recipientIsNew = async (recipient: string): Promise => { if (!isRecipientValid(recipient)) return false; const info = await getAccountInfo(recipient); - if (info.error === NEW_ACCOUNT_ERROR_MESSAGE) { - return true; - } - return false; + return info.isNewAccount; }; const cacheRecipientsNew: Record = {}; diff --git a/libs/coin-modules/coin-xrp/src/network/index.test.ts b/libs/coin-modules/coin-xrp/src/network/index.test.ts new file mode 100644 index 000000000000..3934cf1f4f3e --- /dev/null +++ b/libs/coin-modules/coin-xrp/src/network/index.test.ts @@ -0,0 +1,114 @@ +import network from "@ledgerhq/live-network"; +import { getAccountInfo } from "."; +import coinConfig, { type XrpCoinConfig } from "../config"; + +jest.mock("@ledgerhq/live-network"); + +describe("getAccountInfo", () => { + beforeAll(() => { + coinConfig.setCoinConfig( + () => + ({ + node: "", + }) as XrpCoinConfig, + ); + }); + + it("returns an empty AccountInfo when returns an error 'actNotFound'", async () => { + // Given + const emptyAddress = "rNCgVpHinUDjXP2vHDFDMjm7ssBwpveHya"; + (network as jest.Mock).mockResolvedValue({ + data: { + result: { + account: emptyAddress, + error: "actNotFound", + error_code: 19, + error_message: "Account not found.", + ledger_hash: "F2E6EFD279C3663B62D9DC9977106EC25BA8F89DA551C2D7AB3AE5D75B146258", + ledger_index: 91772714, + request: { + account: emptyAddress, + command: "account_info", + ledger_index: "validated", + }, + status: "error", + validated: true, + }, + }, + }); + + // When + const result = await getAccountInfo(emptyAddress); + + // Then + expect(result).toEqual({ + isNewAccount: true, + balance: "0", + ownerCount: 0, + sequence: 0, + }); + }); + + it("returns a valid AccountInfo when return a correct AccountInfo", async () => { + // Given + const address = "rh1HPuRVsYYvThxG2Bs1MfjmrVC73S16Fb"; + (network as jest.Mock).mockResolvedValue({ + data: { + result: { + account_data: { + Account: address, + Balance: "999441667919804", + Flags: 0, + LedgerEntryType: "AccountRoot", + OwnerCount: 0, + PreviousTxnID: "947F03794C982FE4C7C9FECC4C33C543BB25B82938895EBA8F9B6021CC27A571", + PreviousTxnLgrSeq: 725208, + Sequence: 153743, + index: "BC0DAE09C0BFBC4A49AA94B849266588BFD6E1F554B184B5788AC55D6E07EB95", + }, + ledger_hash: "93E952B2770233B0ABFBFBBFBC3E2E2159DCABD07FEB5F4C49174027935D9FBB", + ledger_index: 1908009, + status: "success", + validated: true, + }, + }, + }); + + // When + const result = await getAccountInfo(address); + + // Then + expect(result).toEqual({ + isNewAccount: false, + balance: "999441667919804", + ownerCount: 0, + sequence: 153743, + }); + }); + + it("throws an error when backend returns any other error", async () => { + // Given + const invalidAddress = "rNCgVpHinUDjXP2vHDFDMjm7ssBwpveHyaa"; + (network as jest.Mock).mockResolvedValue({ + result: { + error: "actMalformed", + error_code: 35, + error_message: "Account malformed.", + ledger_hash: "87DE2DD287BCAD6E81720BC6E6361EF01A66EE70A37B6BDF1EFF2E719D9410AE", + ledger_index: 91772741, + request: { + account: invalidAddress, + command: "account_info", + ledger_index: "validated", + }, + status: "error", + validated: true, + }, + }); + + // When & Then + await expect(getAccountInfo(invalidAddress)).rejects.toThrow( + "Cannot read properties of undefined (reading 'result')", + ); + }); +}); diff --git a/libs/coin-modules/coin-xrp/src/network/index.ts b/libs/coin-modules/coin-xrp/src/network/index.ts index c8e3897952e4..dace2a441708 100644 --- a/libs/coin-modules/coin-xrp/src/network/index.ts +++ b/libs/coin-modules/coin-xrp/src/network/index.ts @@ -1,9 +1,12 @@ import network from "@ledgerhq/live-network"; import coinConfig from "../config"; +import type { AccountInfo } from "../types/model"; import { + isErrorResponse, isResponseStatus, type AccountInfoResponse, type AccountTxResponse, + type ErrorResponse, type LedgerResponse, type ServerInfoResponse, type SubmitReponse, @@ -20,10 +23,10 @@ export const submit = async (signature: string): Promise => { export const getAccountInfo = async ( recipient: string, current?: boolean, -): Promise => { +): Promise => { const { data: { result }, - } = await network<{ result: AccountInfoResponse }>({ + } = await network<{ result: AccountInfoResponse | ErrorResponse }>({ method: "POST", url: getNodeUrl(), data: { @@ -41,7 +44,21 @@ export const getAccountInfo = async ( throw new Error(`couldn't fetch account info ${recipient}`); } - return result; + if (isErrorResponse(result)) { + return { + isNewAccount: true, + balance: "0", + ownerCount: 0, + sequence: 0, + }; + } else { + return { + isNewAccount: false, + balance: result.account_data.Balance, + ownerCount: result.account_data.OwnerCount, + sequence: result.account_data.Sequence, + }; + } }; export const getServerInfos = async (): Promise => { diff --git a/libs/coin-modules/coin-xrp/src/network/types.ts b/libs/coin-modules/coin-xrp/src/network/types.ts index 998d0e8cc6d9..b7fe56c3939c 100644 --- a/libs/coin-modules/coin-xrp/src/network/types.ts +++ b/libs/coin-modules/coin-xrp/src/network/types.ts @@ -193,3 +193,22 @@ export type LedgerResponse = { ledger_index: number; validated: boolean; } & ResponseStatus; + +export type ErrorResponse = { + account: string; + error: string; + error_code: number; + error_message: string; + ledger_hash: string; + ledger_index: number; + request: { + account: string; + command: string; + ledger_index: string; + }; + status: string; + validated: boolean; +}; +export function isErrorResponse(obj: object): obj is ErrorResponse { + return "status" in obj && obj.status === "error" && "error" in obj; +} diff --git a/libs/coin-modules/coin-xrp/src/types/index.ts b/libs/coin-modules/coin-xrp/src/types/index.ts index 2aa9dd916aa1..34dbbe906c79 100644 --- a/libs/coin-modules/coin-xrp/src/types/index.ts +++ b/libs/coin-modules/coin-xrp/src/types/index.ts @@ -1,2 +1,3 @@ export * from "./bridge"; +export * from "./model"; export * from "./signer"; diff --git a/libs/coin-modules/coin-xrp/src/types/model.ts b/libs/coin-modules/coin-xrp/src/types/model.ts new file mode 100644 index 000000000000..0ce2da76f469 --- /dev/null +++ b/libs/coin-modules/coin-xrp/src/types/model.ts @@ -0,0 +1,6 @@ +export type AccountInfo = { + isNewAccount: boolean; + balance: string; + ownerCount: number; + sequence: number; +}; diff --git a/libs/env/.unimportedrc.json b/libs/env/.unimportedrc.json deleted file mode 100644 index cbe49f21aaf0..000000000000 --- a/libs/env/.unimportedrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": ["src/index.ts"], - "ignoreUnimported": [], - "ignoreUnresolved": [] -} diff --git a/libs/env/package.json b/libs/env/package.json index 9d044a31ae73..f9938b945a6d 100644 --- a/libs/env/package.json +++ b/libs/env/package.json @@ -37,6 +37,6 @@ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache", "lint:fix": "pnpm lint --fix", "typecheck": "tsc --noEmit", - "unimported": "unimported" + "unimported": "pnpm knip --directory ../.. -W libs/env" } } diff --git a/libs/hw-ledger-key-ring-protocol/.unimportedrc.json b/libs/hw-ledger-key-ring-protocol/.unimportedrc.json deleted file mode 100644 index 3ee80ec81306..000000000000 --- a/libs/hw-ledger-key-ring-protocol/.unimportedrc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": ["src/index.ts"], - "ignoreUnused": ["@ledgerhq/logs", "@ledgerhq/live-env"] -} diff --git a/libs/hw-ledger-key-ring-protocol/package.json b/libs/hw-ledger-key-ring-protocol/package.json index 5c041f2cace0..44a8a699338d 100644 --- a/libs/hw-ledger-key-ring-protocol/package.json +++ b/libs/hw-ledger-key-ring-protocol/package.json @@ -41,7 +41,7 @@ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache", "lint:fix": "pnpm lint --fix", "typecheck": "tsc --noEmit", - "unimported": "unimported", + "unimported": "pnpm knip --directory ../.. -W libs/hw-ledger-key-ring-protocol", "test": "jest" }, "typesVersions": { diff --git a/libs/ledger-key-ring-protocol/.unimportedrc.json b/libs/ledger-key-ring-protocol/.unimportedrc.json deleted file mode 100644 index 15af0c544900..000000000000 --- a/libs/ledger-key-ring-protocol/.unimportedrc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "entry": ["src/index.ts", "src/store.ts", "src/qrcode/index.ts"], - "ignorePatterns": [ - "**/node_modules/**", - "**/*.test.ts", - "**/__tests__/**", - "**/test-scenarios/**", - "**/test-helpers/**" - ], - "ignoreUnused": [ - "@ledgerhq/hw-transport-mocker", - "@ledgerhq/speculos-transport", - "@ledgerhq/types-devices", - "@ledgerhq/live-env" - ] -} diff --git a/libs/ledger-key-ring-protocol/package.json b/libs/ledger-key-ring-protocol/package.json index 7e4cc770a5a5..cc1121083235 100644 --- a/libs/ledger-key-ring-protocol/package.json +++ b/libs/ledger-key-ring-protocol/package.json @@ -84,7 +84,7 @@ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache", "lint:fix": "pnpm lint --fix", "typecheck": "tsc --noEmit", - "unimported": "unimported", + "unimported": "pnpm knip --directory ../.. -W libs/ledger-key-ring-protocol", "test": "jest", "e2e": "ts-node scripts/e2e.ts" } diff --git a/libs/ledger-live-common/.unimportedrc.json b/libs/ledger-live-common/.unimportedrc.json index 534d9060f42d..58226330923f 100644 --- a/libs/ledger-live-common/.unimportedrc.json +++ b/libs/ledger-live-common/.unimportedrc.json @@ -309,6 +309,7 @@ "src/exchange/swap/const/blockchain.ts", "src/families/cardano/logic.ts", "src/families/cardano/staking.ts", + "src/families/stellar/bridge/logic.ts", "src/families/stellar/logic.ts", "src/families/tezos/logic.ts", "src/families/tezos/react.ts", diff --git a/libs/ledger-live-common/src/errors/transactionBroadcastErrors.ts b/libs/ledger-live-common/src/errors/transactionBroadcastErrors.ts index 5eb24105e9ad..1d37c4e21e47 100644 --- a/libs/ledger-live-common/src/errors/transactionBroadcastErrors.ts +++ b/libs/ledger-live-common/src/errors/transactionBroadcastErrors.ts @@ -10,26 +10,31 @@ export interface TransactionBroadcastError extends Error, TxData { export const createTransactionBroadcastError = ( error: Error, + urls: { faq: string; txBroadcastErrors: Record }, data: TxData, ): TransactionBroadcastError => { - return new TransactionBroadcastError(error.message, { url: url(error.message), ...data }); + return new TransactionBroadcastError(error.message, { + url: url(error.message, urls.txBroadcastErrors) ?? urls.faq, + ...data, + }); }; -function url(message: string) { +function url(message: string, urls: Record) { if ( message.includes("-25: bad-tnxs-inputs-missingorspent") || message.includes("-25: Missing inputs") ) { - return "https://support.ledger.com/article/5129526865821-zd"; + return urls.badTxns; } if (message.includes("blobs limit in txpool is full")) { - return "https://support.ledger.com/article/17830974229661-zd"; + return urls.blobsLimit; } if (message.includes("txn-mempool-conflict")) { - return "https://support.ledger.com/article/14593285242525-zd"; + return urls.txnMempoolConflict; } } type TxData = { coin?: string; network?: string }; +type SpecificErrors = "badTxns" | "blobsLimit" | "txnMempoolConflict"; diff --git a/libs/ledger-live-common/src/exchange/swap/types.ts b/libs/ledger-live-common/src/exchange/swap/types.ts index 88ee733bdfee..dd051baed23e 100644 --- a/libs/ledger-live-common/src/exchange/swap/types.ts +++ b/libs/ledger-live-common/src/exchange/swap/types.ts @@ -173,6 +173,7 @@ type ValidSwapStatus = "pending" | "onhold" | "expired" | "finished" | "refunded export type SwapStatusRequest = { provider: string; swapId: string; + transactionId?: string; operationId?: string; }; export type SwapStatus = { diff --git a/libs/ledger-live-common/src/exchange/swap/updateAccountSwapStatus.ts b/libs/ledger-live-common/src/exchange/swap/updateAccountSwapStatus.ts index ceece6e8e36e..e5c86be4abd4 100644 --- a/libs/ledger-live-common/src/exchange/swap/updateAccountSwapStatus.ts +++ b/libs/ledger-live-common/src/exchange/swap/updateAccountSwapStatus.ts @@ -1,28 +1,47 @@ import { isSwapOperationPending } from "./"; import { getMultipleStatus } from "./getStatus"; -import type { SubAccount, Account, SwapOperation } from "@ledgerhq/types-live"; +import type { SubAccount, Account, SwapOperation, Operation } from "@ledgerhq/types-live"; import type { SwapStatusRequest, UpdateAccountSwapStatus } from "./types"; import { log } from "@ledgerhq/logs"; const maybeGetUpdatedSwapHistory = async ( swapHistory: SwapOperation[] | null | undefined, + operations: Operation[] | null | undefined, ): Promise => { const pendingSwapIds: SwapStatusRequest[] = []; let accountNeedsUpdating = false; let consolidatedSwapHistory: SwapOperation[] = []; + if (swapHistory) { for (const { provider, swapId, status, operationId } of swapHistory) { if (isSwapOperationPending(status)) { + const transactionId = + provider === "thorswap" + ? operations?.find(o => o.id.includes(operationId))?.hash + : undefined; pendingSwapIds.push({ provider, swapId, - ...(provider === "thorswap" && { operationId }), + transactionId, + ...(provider === "thorswap" && { operationId }), // to be removed after Thorswap is fully migrated }); } } if (pendingSwapIds.length) { - const uniquePendingSwapIdsMap = new Map(pendingSwapIds.map(item => [item.swapId, item])); + const uniquePendingSwapIdsMap = new Map(); + for (const item of pendingSwapIds) { + const existingItem = uniquePendingSwapIdsMap.get(item.swapId); + + if (!existingItem) { + uniquePendingSwapIdsMap.set(item.swapId, item); + } else { + if (item.transactionId && !existingItem.transactionId) { + uniquePendingSwapIdsMap.set(item.swapId, item); + } + } + } + const uniquePendingSwapIds = Array.from(uniquePendingSwapIdsMap.values()); if (uniquePendingSwapIds.length !== pendingSwapIds.length) { log( @@ -51,14 +70,20 @@ const maybeGetUpdatedSwapHistory = async ( }; const updateAccountSwapStatus: UpdateAccountSwapStatus = async (account: Account) => { - const swapHistoryUpdated = await maybeGetUpdatedSwapHistory(account.swapHistory); + const swapHistoryUpdated = await maybeGetUpdatedSwapHistory( + account.swapHistory, + account.operations, + ); let subAccountSwapHistoryUpdated = false; let subAccounts: SubAccount[] = []; if (account.type === "Account" && account.subAccounts?.length) { subAccounts = await Promise.all( account.subAccounts.map(async (subAccount: SubAccount): Promise => { - const updatedSwapHistory = await maybeGetUpdatedSwapHistory(subAccount.swapHistory); + const updatedSwapHistory = await maybeGetUpdatedSwapHistory( + subAccount.swapHistory, + subAccount.operations, + ); //As soon as we get one update, we will need to update the parent account if (updatedSwapHistory) subAccountSwapHistoryUpdated = true; return { diff --git a/libs/ledger-live-common/src/families/stellar/bridge/logic.ts b/libs/ledger-live-common/src/families/stellar/bridge/logic.ts new file mode 100644 index 000000000000..81f4484f6401 --- /dev/null +++ b/libs/ledger-live-common/src/families/stellar/bridge/logic.ts @@ -0,0 +1 @@ +export * from "@ledgerhq/coin-stellar/bridge/logic"; diff --git a/libs/ledger-live-common/src/families/stellar/bridge/mock.ts b/libs/ledger-live-common/src/families/stellar/bridge/mock.ts index 87346e99678a..8aed62809ca9 100644 --- a/libs/ledger-live-common/src/families/stellar/bridge/mock.ts +++ b/libs/ledger-live-common/src/families/stellar/bridge/mock.ts @@ -59,10 +59,6 @@ const createTransaction = (): Transaction => ({ }); const updateTransaction = (t, patch) => { - if ("recipient" in patch && patch.recipient !== t.recipient) { - return { ...t, ...patch, memoType: null }; - } - return { ...t, ...patch }; }; diff --git a/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts b/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts index 1821b3bbbedc..c155af2a60bd 100644 --- a/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts +++ b/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts @@ -512,6 +512,7 @@ export const DEFAULT_FEATURES: Features = { warningVisible: true, }, }, + recoverUpsellRedirection: DEFAULT_FEATURE, }; // Firebase SDK treat JSON values as strings diff --git a/libs/ledger-live-common/src/hooks/recoverFeatureFlag.ts b/libs/ledger-live-common/src/hooks/recoverFeatureFlag.ts index 0ae1ac130ad4..a5c0b09a4a21 100644 --- a/libs/ledger-live-common/src/hooks/recoverFeatureFlag.ts +++ b/libs/ledger-live-common/src/hooks/recoverFeatureFlag.ts @@ -172,6 +172,8 @@ export function useCustomURI( if (source && deeplinkCampaign) { uri.searchParams.append("ajs_recover_source", source); uri.searchParams.append("ajs_recover_campaign", deeplinkCampaign); + uri.searchParams.append("ajs_prop_source", source); + uri.searchParams.append("ajs_prop_campaign", deeplinkCampaign); } return uri; @@ -190,3 +192,16 @@ export function useCustomPath( return usePath(servicesConfig, uri); } + +export enum Source { + LLM_ONBOARDING_24 = "llm-onboarding-24", + LLD_ONBOARDING_24 = "lld-onboarding-24", +} + +export function useTouchScreenOnboardingUpsellURI( + servicesConfig: Feature_ProtectServicesMobile | null, + source: Source, +): string | undefined { + const campaign = "touchscreen-onboarding"; + return useCustomURI(servicesConfig, "upsell", source, campaign); +} diff --git a/libs/ledger-live-common/src/postOnboarding/hooks/useStartPostOnboardingCallback.ts b/libs/ledger-live-common/src/postOnboarding/hooks/useStartPostOnboardingCallback.ts index fe00f2de4eaa..a39fd7591e4a 100644 --- a/libs/ledger-live-common/src/postOnboarding/hooks/useStartPostOnboardingCallback.ts +++ b/libs/ledger-live-common/src/postOnboarding/hooks/useStartPostOnboardingCallback.ts @@ -43,10 +43,8 @@ export function useStartPostOnboardingCallback(): (options: StartPostOnboardingO ); if (actions.length === 0) { - if (fallbackIfNoAction) { - dispatch(postOnboardingSetFinished()); - fallbackIfNoAction(); - } + dispatch(postOnboardingSetFinished()); + if (fallbackIfNoAction) fallbackIfNoAction(); return; } navigateToPostOnboardingHub(resetNavigationStack); diff --git a/libs/ledgerjs/packages/types-live/src/feature.ts b/libs/ledgerjs/packages/types-live/src/feature.ts index 408842b58745..5833a3680011 100644 --- a/libs/ledgerjs/packages/types-live/src/feature.ts +++ b/libs/ledgerjs/packages/types-live/src/feature.ts @@ -194,6 +194,7 @@ export type Features = CurrencyFeatures & { llmMemoTag: Feature_MemoTag; lldMemoTag: Feature_MemoTag; ldmkTransport: Feature_LdmkTransport; + recoverUpsellRedirection: Feature_RecoverUpsellRedirection; }; /** @@ -554,6 +555,7 @@ export type Feature_lldNftsGalleryNewArch = DefaultFeature; export type Feature_lldnewArchOrdinals = DefaultFeature; export type Feature_SpamFilteringTx = DefaultFeature; export type Feature_MemoTag = DefaultFeature; +export type Feature_RecoverUpsellRedirection = DefaultFeature; /** * Utils types. diff --git a/libs/live-countervalues-react/.unimportedrc.json b/libs/live-countervalues-react/.unimportedrc.json deleted file mode 100644 index 206907d69c3f..000000000000 --- a/libs/live-countervalues-react/.unimportedrc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": ["src/index.tsx", "src/portfolio.tsx"], - "ignoreUnused": ["@ledgerhq/cryptoassets"] -} diff --git a/libs/live-countervalues-react/package.json b/libs/live-countervalues-react/package.json index 880eee1f3249..28f457fff87b 100644 --- a/libs/live-countervalues-react/package.json +++ b/libs/live-countervalues-react/package.json @@ -50,7 +50,7 @@ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache", "lint:fix": "pnpm lint --fix", "typecheck": "tsc --noEmit", - "unimported": "unimported", + "unimported": "pnpm knip --directory ../.. -W libs/live-countervalues-react", "test": "jest" }, "typesVersions": { diff --git a/libs/live-countervalues/.unimportedrc.json b/libs/live-countervalues/.unimportedrc.json deleted file mode 100644 index d118f08853dc..000000000000 --- a/libs/live-countervalues/.unimportedrc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": ["src/logic.ts", "src/api/index.ts", "src/types.ts", "src/portfolio.ts"], - "ignoreUnused": ["@ledgerhq/cryptoassets"] -} diff --git a/libs/live-countervalues/package.json b/libs/live-countervalues/package.json index 55bed1a9ab12..11565e5d6253 100644 --- a/libs/live-countervalues/package.json +++ b/libs/live-countervalues/package.json @@ -44,7 +44,7 @@ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache", "lint:fix": "pnpm lint --fix", "typecheck": "tsc --noEmit", - "unimported": "unimported", + "unimported": "pnpm knip --directory ../.. -W libs/live-countervalues", "test": "jest" }, "typesVersions": { diff --git a/libs/live-hooks/.unimportedrc.json b/libs/live-hooks/.unimportedrc.json deleted file mode 100644 index 3cf5f8f13486..000000000000 --- a/libs/live-hooks/.unimportedrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": ["src/useDebounce.ts", "src/useThrottledFunction.ts"], - "ignoreUnimported": [], - "ignoreUnresolved": [] -} diff --git a/libs/live-hooks/package.json b/libs/live-hooks/package.json index c16aa78068a1..0299a630a078 100644 --- a/libs/live-hooks/package.json +++ b/libs/live-hooks/package.json @@ -41,7 +41,7 @@ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache", "lint:fix": "pnpm lint --fix", "typecheck": "tsc --noEmit", - "unimported": "unimported", + "unimported": "pnpm knip --directory ../.. -W libs/live-hooks", "test": "jest" }, "typesVersions": { diff --git a/libs/live-nft-react/.unimportedrc.json b/libs/live-nft-react/.unimportedrc.json deleted file mode 100644 index 187a263de59b..000000000000 --- a/libs/live-nft-react/.unimportedrc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": ["src/index.ts", "src/tools/*", "src/hooks/*"], - "ignoreUnused": ["@ledgerhq/coin-framework/nft/nftId"] -} diff --git a/libs/live-nft-react/package.json b/libs/live-nft-react/package.json index f4b2f65afc28..30d1f7320847 100644 --- a/libs/live-nft-react/package.json +++ b/libs/live-nft-react/package.json @@ -50,7 +50,7 @@ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache", "lint:fix": "pnpm lint --fix", "typecheck": "tsc --noEmit", - "unimported": "unimported", + "unimported": "pnpm knip --directory ../.. -W libs/live-nft-react", "test": "jest" }, "typesVersions": { diff --git a/libs/live-nft/.unimportedrc.json b/libs/live-nft/.unimportedrc.json deleted file mode 100644 index b6725fd9c9e7..000000000000 --- a/libs/live-nft/.unimportedrc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "entry": ["src/api/*", "src/index.ts", "src/index.test.ts", "src/types.ts"], - "ignoreUnused": [] -} diff --git a/libs/live-nft/package.json b/libs/live-nft/package.json index 417a9b1373e8..c272a8dc00d9 100644 --- a/libs/live-nft/package.json +++ b/libs/live-nft/package.json @@ -42,7 +42,7 @@ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache", "lint:fix": "pnpm lint --fix", "typecheck": "tsc --noEmit", - "unimported": "unimported", + "unimported": "pnpm knip --directory ../.. -W libs/live-nft", "test": "jest" }, "typesVersions": { diff --git a/libs/live-wallet/.unimportedrc.json b/libs/live-wallet/.unimportedrc.json deleted file mode 100644 index 38730cf69452..000000000000 --- a/libs/live-wallet/.unimportedrc.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "entry": [ - "src/walletsync/index.ts", - "src/cloudsync/index.ts", - "src/liveqr/cross.ts", - "src/liveqr/importAccounts.ts", - "src/ordering.ts", - "src/addAccounts.ts", - "src/store.ts", - "src/accountName.ts" - ], - "ignoreUnresolved": ["bufferutil", "utf-8-validate"], - "ignorePatterns": [ - "**/node_modules/**", - "**/__tests__/**", - "**/*.test.ts", - "walletsync/__mocks__/**" - ] -} diff --git a/libs/live-wallet/package.json b/libs/live-wallet/package.json index acf45f5b0c72..44a0d791d4b7 100644 --- a/libs/live-wallet/package.json +++ b/libs/live-wallet/package.json @@ -60,7 +60,7 @@ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache", "lint:fix": "pnpm lint --fix", "typecheck": "tsc --noEmit", - "unimported": "unimported", + "unimported": "pnpm knip --directory ../.. -W libs/live-wallet", "test": "jest" }, "typesVersions": { diff --git a/libs/promise/.unimportedrc.json b/libs/promise/.unimportedrc.json deleted file mode 100644 index cbe49f21aaf0..000000000000 --- a/libs/promise/.unimportedrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entry": ["src/index.ts"], - "ignoreUnimported": [], - "ignoreUnresolved": [] -} diff --git a/libs/promise/package.json b/libs/promise/package.json index 64098bd689ed..83fc8a0f17c9 100644 --- a/libs/promise/package.json +++ b/libs/promise/package.json @@ -36,6 +36,6 @@ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache", "lint:fix": "pnpm lint --fix", "test": "jest", - "unimported": "unimported" + "unimported": "pnpm knip --directory ../.. -W libs/promise" } } diff --git a/libs/ui/packages/native/src/components/cta/Button/index.tsx b/libs/ui/packages/native/src/components/cta/Button/index.tsx index 8dc25543548b..ed0637729981 100644 --- a/libs/ui/packages/native/src/components/cta/Button/index.tsx +++ b/libs/ui/packages/native/src/components/cta/Button/index.tsx @@ -181,10 +181,10 @@ const Button = (props: ButtonProps): React.ReactElement => { return ( diff --git a/package.json b/package.json index 51f212be3c46..2565d4b35504 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "lint": "pnpm turbo lint --no-daemon", "lint:fix": "pnpm turbo lint:fix --no-daemon", "typecheck": "pnpm turbo typecheck --no-daemon", + "knip-check": "pnpm turbo knip-check", "unimported": "pnpm turbo unimported", "desktop": "pnpm --filter ledger-live-desktop", "cli": "pnpm --filter live-cli", @@ -171,6 +172,7 @@ "eslint-config-prettier": "9.1.0", "eslint-plugin-json": "3.1.0", "eslint-plugin-prettier": "5.1.3", + "knip": "5.34.1", "nyc": "15.1.0", "prettier": "3.2.5", "rimraf": "4.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0cbfcedb0e61..b602a9b29548 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,6 +102,9 @@ importers: eslint-plugin-prettier: specifier: 5.1.3 version: 5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5) + knip: + specifier: 5.34.1 + version: 5.34.1(typescript@5.4.3) nyc: specifier: 15.1.0 version: 15.1.0 @@ -205,8 +208,8 @@ importers: specifier: 3.1.0 version: 3.1.0 body-parser: - specifier: 1.20.2 - version: 1.20.2 + specifier: 1.20.3 + version: 1.20.3 command-line-args: specifier: 5.2.1 version: 5.2.1 @@ -1112,9 +1115,6 @@ importers: react-native: specifier: 0.74.6 version: 0.74.6(@babel/core@7.24.3)(@types/react@18.2.73)(react@18.2.0) - react-native-adjust: - specifier: 4.38.0 - version: 4.38.0 react-native-android-location-services-dialog-box: specifier: 2.8.2 version: 2.8.2(react-native@0.74.6(@babel/core@7.24.3)(@types/react@18.2.73)(react@18.2.0)) @@ -1429,8 +1429,8 @@ importers: specifier: 29.7.0 version: 29.7.0(@babel/core@7.24.3) body-parser: - specifier: 1.20.2 - version: 1.20.2 + specifier: 1.20.3 + version: 1.20.3 cors: specifier: 2.8.5 version: 2.8.5 @@ -7324,11 +7324,11 @@ importers: specifier: 6.0.0 version: 6.0.0 '@aws-sdk/client-s3': - specifier: 3.651.0 - version: 3.651.0 + specifier: 3.651.1 + version: 3.651.1 '@aws-sdk/lib-storage': - specifier: 3.651.0 - version: 3.651.0(@aws-sdk/client-s3@3.651.0) + specifier: 3.651.1 + version: 3.651.1(@aws-sdk/client-s3@3.651.1) '@types/express': specifier: 4.17.21 version: 4.17.21 @@ -7524,8 +7524,8 @@ packages: resolution: {integrity: sha512-rYBuNB7uqCO9xZc0OAwM2K6QJAo2Syt1L5OhEaf7zG7FulNMyrK6kJPg1WrvNE90tW6gUdDaTy3XsQ7lq6O7uA==} engines: {node: '>=14.0.0'} - '@aws-sdk/client-s3@3.651.0': - resolution: {integrity: sha512-37+kxxjnlOAUCb1aHpoLakW4XRG23HrkX8X3cEjxaFLQxorPUiMvfAYQEQQkYD5yggaG+5aM5GAhxkTUTqA5xw==} + '@aws-sdk/client-s3@3.651.1': + resolution: {integrity: sha512-xNm+ixNRcotyrHgjUGGEyara6kCKgDdW2EVjHBZa5T+tbmtyqezwH3UzbSDZ6MlNoLhJMfR7ozuwYTIOARoBfA==} engines: {node: '>=16.0.0'} '@aws-sdk/client-sso-oidc@3.540.0': @@ -7534,18 +7534,18 @@ packages: peerDependencies: '@aws-sdk/credential-provider-node': ^3.540.0 - '@aws-sdk/client-sso-oidc@3.650.0': - resolution: {integrity: sha512-6J7IS0f8ovhvbIAZaynOYP+jPX8344UlTjwHxjaXHgFvI8axu3+NslKtEEV5oHLhgzDvrKbinsu5lgE2n4Sqng==} + '@aws-sdk/client-sso-oidc@3.651.1': + resolution: {integrity: sha512-PKwAyTJW8pgaPIXm708haIZWBAwNycs25yNcD7OQ3NLcmgGxvrx6bSlhPEGcvwdTYwQMJsdx8ls+khlYbLqTvQ==} engines: {node: '>=16.0.0'} peerDependencies: - '@aws-sdk/client-sts': ^3.650.0 + '@aws-sdk/client-sts': ^3.651.1 '@aws-sdk/client-sso@3.540.0': resolution: {integrity: sha512-rrQZMuw4sxIo3eyAUUzPQRA336mPRnrAeSlSdVHBKZD8Fjvoy0lYry2vNhkPLpFZLso1J66KRyuIv4LzRR3v1Q==} engines: {node: '>=14.0.0'} - '@aws-sdk/client-sso@3.650.0': - resolution: {integrity: sha512-YKm14gCMChD/jlCisFlsVqB8HJujR41bl4Fup2crHwNJxhD/9LTnzwMiVVlBqlXr41Sfa6fSxczX2AMP8NM14A==} + '@aws-sdk/client-sso@3.651.1': + resolution: {integrity: sha512-Fm8PoMgiBKmmKrY6QQUGj/WW6eIiQqC1I0AiVXfO+Sqkmxcg3qex+CZBAYrTuIDnvnc/89f9N4mdL8V9DRn03Q==} engines: {node: '>=16.0.0'} '@aws-sdk/client-sts@3.540.0': @@ -7554,16 +7554,16 @@ packages: peerDependencies: '@aws-sdk/credential-provider-node': ^3.540.0 - '@aws-sdk/client-sts@3.650.0': - resolution: {integrity: sha512-ISK0ZQYA7O5/WYgslpWy956lUBudGC9d7eL0FFbiL0j50N80Gx3RUv22ezvZgxJWE0W3DqNr4CE19sPYn4Lw8g==} + '@aws-sdk/client-sts@3.651.1': + resolution: {integrity: sha512-4X2RqLqeDuVLk+Omt4X+h+Fa978Wn+zek/AM4HSPi4C5XzRBEFLRRtOQUvkETvIjbEwTYQhm0LdgzcBH4bUqIg==} engines: {node: '>=16.0.0'} '@aws-sdk/core@3.535.0': resolution: {integrity: sha512-+Yusa9HziuaEDta1UaLEtMAtmgvxdxhPn7jgfRY6PplqAqgsfa5FR83sxy5qr2q7xjQTwHtV4MjQVuOjG9JsLw==} engines: {node: '>=14.0.0'} - '@aws-sdk/core@3.649.0': - resolution: {integrity: sha512-dheG/X2y25RHE7K+TlS32kcy7TgDg1OpWV44BQRoE0OBPAWmFR1D1qjjTZ7WWrdqRPKzcnDj1qED8ncyncOX8g==} + '@aws-sdk/core@3.651.1': + resolution: {integrity: sha512-eqOq3W39K+5QTP5GAXtmP2s9B7hhM2pVz8OPe5tqob8o1xQgkwdgHerf3FoshO9bs0LDxassU/fUSz1wlwqfqg==} engines: {node: '>=16.0.0'} '@aws-sdk/credential-provider-env@3.535.0': @@ -7586,18 +7586,18 @@ packages: resolution: {integrity: sha512-igN/RbsnulIBwqXbwsWmR3srqmtbPF1dm+JteGvUY31FW65fTVvWvSr945Y/cf1UbhPmIQXntlsqESqpkhTHwg==} engines: {node: '>=14.0.0'} - '@aws-sdk/credential-provider-ini@3.650.0': - resolution: {integrity: sha512-x2M9buZxIsKuUbuDgkGHhAKYBpn0/rYdKlwuFuOhXyyAcnhvPj0lgNF2KE4ld/GF1mKr7FF/uV3G9lM6PFaYmA==} + '@aws-sdk/credential-provider-ini@3.651.1': + resolution: {integrity: sha512-yOzPC3GbwLZ8IYzke4fy70ievmunnBUni/MOXFE8c9kAIV+/RMC7IWx14nAAZm0gAcY+UtCXvBVZprFqmctfzA==} engines: {node: '>=16.0.0'} peerDependencies: - '@aws-sdk/client-sts': ^3.650.0 + '@aws-sdk/client-sts': ^3.651.1 '@aws-sdk/credential-provider-node@3.540.0': resolution: {integrity: sha512-HKQZJbLHlrHX9A0B1poiYNXIIQfy8whTjuosTCYKPDBhhUyVAQfxy/KG726j0v43IhaNPLgTGZCJve4hAsazSw==} engines: {node: '>=14.0.0'} - '@aws-sdk/credential-provider-node@3.650.0': - resolution: {integrity: sha512-uBra5YjzS/gWSekAogfqJfY6c+oKQkkou7Cjc4d/cpMNvQtF1IBdekJ7NaE1RfsDEz3uH1+Myd07YWZAJo/2Qw==} + '@aws-sdk/credential-provider-node@3.651.1': + resolution: {integrity: sha512-QKA74Qs83FTUz3jS39kBuNbLAnm6cgDqomm7XS/BkYgtUq+1lI9WL97astNIuoYvumGIS58kuIa+I3ycOA4wgw==} engines: {node: '>=16.0.0'} '@aws-sdk/credential-provider-process@3.535.0': @@ -7612,8 +7612,8 @@ packages: resolution: {integrity: sha512-tKkFqK227LF5ajc5EL6asXS32p3nkofpP8G7NRpU7zOEOQCg01KUc4JRX+ItI0T007CiN1J19yNoFqHLT/SqHg==} engines: {node: '>=14.0.0'} - '@aws-sdk/credential-provider-sso@3.650.0': - resolution: {integrity: sha512-069nkhcwximbvyGiAC6Fr2G+yrG/p1S3NQ5BZ2cMzB1hgUKo6TvgFK7nriYI4ljMQ+UWxqPwIdTqiUmn2iJmhg==} + '@aws-sdk/credential-provider-sso@3.651.1': + resolution: {integrity: sha512-7jeU+Jbn65aDaNjkjWDQcXwjNTzpYNKovkSSRmfVpP5WYiKerVS5mrfg3RiBeiArou5igCUtYcOKlRJiGRO47g==} engines: {node: '>=16.0.0'} '@aws-sdk/credential-provider-web-identity@3.540.0': @@ -7626,11 +7626,11 @@ packages: peerDependencies: '@aws-sdk/client-sts': ^3.649.0 - '@aws-sdk/lib-storage@3.651.0': - resolution: {integrity: sha512-AK41Gn/KWtOHC4X9fPjtG63FgssT+CS8CTWIhrM2k1cw4Ut+Wt8L1C4otXfyyLqTyLs/UGEFHn7ed68mGj6nAQ==} + '@aws-sdk/lib-storage@3.651.1': + resolution: {integrity: sha512-IFV7qqg9ktJAa94VD4Li1L/2MdjuskHwAo9jYYd1QZDmZb8UZG3ZrO0zzB6lc5Z4JZADscSVdUaZLSMCkz9U0g==} engines: {node: '>=16.0.0'} peerDependencies: - '@aws-sdk/client-s3': ^3.651.0 + '@aws-sdk/client-s3': ^3.651.1 '@aws-sdk/middleware-bucket-endpoint@3.535.0': resolution: {integrity: sha512-7sijlfQsc4UO9Fsl11mU26Y5f9E7g6UoNg/iJUBpC5pgvvmdBRO5UEhbB/gnqvOEPsBXyhmfzbstebq23Qdz7A==} @@ -7652,8 +7652,8 @@ packages: resolution: {integrity: sha512-rBIzldY9jjRATxICDX7t77aW6ctqmVDgnuAOgbVT5xgHftt4o7PGWKoMvl/45hYqoQgxVFnCBof9bxkqSBebVA==} engines: {node: '>=14.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.651.0': - resolution: {integrity: sha512-mGAOIjhNDcBK5+JD+W+Ky5YJL98jTNFTENJV/GiQ9t3CdqK3p02MNr/T6VwzEpzsJvJD23amogiEZeiqSQiibg==} + '@aws-sdk/middleware-flexible-checksums@3.651.1': + resolution: {integrity: sha512-cFlXSzhdRKU1vOFTIYC3HzkN7Dwwcf07rKU1sB/PrDy4ztLhGgAwvcRwj2AqErZB62C5AdN4l7peB1Iw/oSxRQ==} engines: {node: '>=16.0.0'} '@aws-sdk/middleware-host-header@3.535.0': @@ -7692,8 +7692,8 @@ packages: resolution: {integrity: sha512-/dLG/E3af6ohxkQ5GBHT8tZfuPIg6eItKxCXuulvYj0Tqgf3Mb+xTsvSkxQsJF06RS4sH7Qsg/PnB8ZfrJrXpg==} engines: {node: '>=14.0.0'} - '@aws-sdk/middleware-sdk-s3@3.649.0': - resolution: {integrity: sha512-3H8735xTAD7IxNdreT6qv2YRk4CGOGfz8ufZo5pROJYZ4N5rfcdDMvb8szMSLvQHegqS4v1DqO9nrOPgc0I2Qg==} + '@aws-sdk/middleware-sdk-s3@3.651.1': + resolution: {integrity: sha512-4BameU35aBSzrm3L/Iphc6vFLRhz6sBwgQf09mqPA2ZlX/YFqVe8HbS8wM4DG02W8A2MRTnHXRIfFoOrErp2sw==} engines: {node: '>=16.0.0'} '@aws-sdk/middleware-signing@3.535.0': @@ -7728,8 +7728,8 @@ packages: resolution: {integrity: sha512-tqCsEsEj8icW0SAh3NvyhRUq54Gz2pu4NM2tOSrFp7SO55heUUaRLSzYteNZCTOupH//AAaZvbN/UUTO/DrOog==} engines: {node: '>=14.0.0'} - '@aws-sdk/signature-v4-multi-region@3.649.0': - resolution: {integrity: sha512-feJfSHtCarFmTMZSE5k7/A+m4FrdCrmotljc/AmXArWy3wl8XFyxE5tFVW/PiUgbgeoVDN+ZLt3YYtItHfNUWQ==} + '@aws-sdk/signature-v4-multi-region@3.651.1': + resolution: {integrity: sha512-aLPCMq4c/A9DmdZLhufWOgfHN2Vgft65dB2tfbATjs6kZjusSaDFxWzjmWX3y8i2ZQ+vU0nAkkWIHFJdf+47fA==} engines: {node: '>=16.0.0'} '@aws-sdk/token-providers@3.540.0': @@ -13271,6 +13271,11 @@ packages: resolution: {integrity: sha512-OU0YllH51/CxD8iyr3UHSMwYqTGTyuxFdCMH/0F978t+iDmJseC/ttrWPb22zmYkhkrjqtipzC1xaMuax5QKIA==} engines: {node: '>=16.0.0'} + '@snyk/github-codeowners@1.1.0': + resolution: {integrity: sha512-lGFf08pbkEac0NYgVf4hdANpAgApRjNByLXB+WBip3qj1iendOIyAwP2GKkKbQMNVy2r1xxDf0ssfWscoiC+Vw==} + engines: {node: '>=8.10'} + hasBin: true + '@soda/friendly-errors-webpack-plugin@1.8.1': resolution: {integrity: sha512-h2ooWqP8XuFqTXT+NyAFbrArzfQA7R6HTezADrvD9Re8fxMLTPPniLdqVTdDaO0eIoLaAwKT+d6w+5GeTk7Vbg==} engines: {node: '>=8.0.0'} @@ -16992,6 +16997,10 @@ packages: resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + bonjour-service@1.2.1: resolution: {integrity: sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==} @@ -19110,6 +19119,9 @@ packages: resolution: {integrity: sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==} engines: {node: '>=6.0.0'} + easy-table@1.2.0: + resolution: {integrity: sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==} + ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} @@ -22726,6 +22738,10 @@ packages: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true + jiti@2.3.3: + resolution: {integrity: sha512-EX4oNDwcXSivPrw2qKH2LB5PoFxEvgtv2JgwW0bU858HoLQ+kutSvjLMUqBd0PeJYEinLWhoI9Ol0eYMqj/wNQ==} + hasBin: true + jmespath@0.15.0: resolution: {integrity: sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==} engines: {node: '>= 0.6.0'} @@ -23062,6 +23078,14 @@ packages: resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} engines: {node: '>= 8'} + knip@5.34.1: + resolution: {integrity: sha512-MwQjEWBxsi7MnTL3JpLJwYRhqD1262Fv+TylMYDyZRAVJMmEs4y0qie5zv8VjU5DBSGoEs35Ssu7I/2klNOQgw==} + engines: {node: '>=18.6.0'} + hasBin: true + peerDependencies: + '@types/node': '>=18' + typescript: '>=5.0.4' + known-css-properties@0.26.0: resolution: {integrity: sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==} @@ -25197,6 +25221,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + parse-numeric-range@1.3.0: resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} @@ -26026,6 +26054,10 @@ packages: resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==} engines: {node: '>= 0.8'} + pretty-ms@9.1.0: + resolution: {integrity: sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==} + engines: {node: '>=18'} + probot@13.2.2: resolution: {integrity: sha512-wx8VoNb0ujn2k3nq0MSIUd4r7/RUn2Di3ZY07F6O5IYm3z3HNGDDX2XEreJnaZDOFjQaZ6XJbYGmiaD4SovAVQ==} engines: {node: '>=18'} @@ -26235,6 +26267,10 @@ packages: resolution: {integrity: sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==} engines: {node: '>=0.6'} + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + qs@6.5.3: resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} engines: {node: '>=0.6'} @@ -26468,9 +26504,6 @@ packages: peerDependencies: react: ^0.14.9 || ^15.3.0 || ^16.0.0 - react-native-adjust@4.38.0: - resolution: {integrity: sha512-kNobWxemV4h+/iPCWd5fzE4cuT3rhqSaZQvPpKvLMzqxhpuCdEzaVm30T7SCs0a/StyFzlPk2D++0ktVZjt2SA==} - react-native-android-location-services-dialog-box@2.8.2: resolution: {integrity: sha512-zBTi0xJQoF6GdImRhXOCXezYuALlPgbctqG3eOrZu63hdrlvTBhqATkb6DB5JM9kpcMbX1skMIOI3m71zKGsPA==} peerDependencies: @@ -27903,6 +27936,10 @@ packages: resolution: {integrity: sha512-Tv3AVvZD9sjEwBvsm1jc8to96FwmZMOYt5UL7qu3vAuA/3F/hqKe9gdRSlkvpRO4ttUAUrDKRpjM0vUnXZQhqA==} hasBin: true + smol-toml@1.3.0: + resolution: {integrity: sha512-tWpi2TsODPScmi48b/OQZGi2lgUmBCHy6SZrhi/FdnnHiU1GwebbCfuQuxsC3nHaLwtYeJGPrDZDIeodDOc4pA==} + engines: {node: '>= 18'} + smoldot@2.0.22: resolution: {integrity: sha512-B50vRgTY6v3baYH6uCgL15tfaag5tcS2o/P5q1OiXcKGv1axZDfz2dzzMuIkVpyMR2ug11F6EAtQlmYBQd292g==} @@ -28457,6 +28494,9 @@ packages: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} + summary@2.1.0: + resolution: {integrity: sha512-nMIjMrd5Z2nuB2RZCKJfFMjgS3fygbeyGk9PxPPaJR1RIcyN9yn4A63Isovzm3ZtQuEkLBVgMdPup8UeLH7aQw==} + superagent@6.1.0: resolution: {integrity: sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==} engines: {node: '>= 7.0.0'} @@ -30795,6 +30835,12 @@ packages: peerDependencies: zod: ^3.18.0 + zod-validation-error@3.4.0: + resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.18.0 + zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} @@ -31051,27 +31097,27 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-s3@3.651.0': + '@aws-sdk/client-s3@3.651.1': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.650.0(@aws-sdk/client-sts@3.650.0) - '@aws-sdk/client-sts': 3.650.0 - '@aws-sdk/core': 3.649.0 - '@aws-sdk/credential-provider-node': 3.650.0(@aws-sdk/client-sso-oidc@3.650.0(@aws-sdk/client-sts@3.650.0))(@aws-sdk/client-sts@3.650.0) + '@aws-sdk/client-sso-oidc': 3.651.1(@aws-sdk/client-sts@3.651.1) + '@aws-sdk/client-sts': 3.651.1 + '@aws-sdk/core': 3.651.1 + '@aws-sdk/credential-provider-node': 3.651.1(@aws-sdk/client-sso-oidc@3.651.1(@aws-sdk/client-sts@3.651.1))(@aws-sdk/client-sts@3.651.1) '@aws-sdk/middleware-bucket-endpoint': 3.649.0 '@aws-sdk/middleware-expect-continue': 3.649.0 - '@aws-sdk/middleware-flexible-checksums': 3.651.0 + '@aws-sdk/middleware-flexible-checksums': 3.651.1 '@aws-sdk/middleware-host-header': 3.649.0 '@aws-sdk/middleware-location-constraint': 3.649.0 '@aws-sdk/middleware-logger': 3.649.0 '@aws-sdk/middleware-recursion-detection': 3.649.0 - '@aws-sdk/middleware-sdk-s3': 3.649.0 + '@aws-sdk/middleware-sdk-s3': 3.651.1 '@aws-sdk/middleware-ssec': 3.649.0 '@aws-sdk/middleware-user-agent': 3.649.0 '@aws-sdk/region-config-resolver': 3.649.0 - '@aws-sdk/signature-v4-multi-region': 3.649.0 + '@aws-sdk/signature-v4-multi-region': 3.651.1 '@aws-sdk/types': 3.649.0 '@aws-sdk/util-endpoints': 3.649.0 '@aws-sdk/util-user-agent-browser': 3.649.0 @@ -31159,13 +31205,13 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.650.0(@aws-sdk/client-sts@3.650.0)': + '@aws-sdk/client-sso-oidc@3.651.1(@aws-sdk/client-sts@3.651.1)': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sts': 3.650.0 - '@aws-sdk/core': 3.649.0 - '@aws-sdk/credential-provider-node': 3.650.0(@aws-sdk/client-sso-oidc@3.650.0(@aws-sdk/client-sts@3.650.0))(@aws-sdk/client-sts@3.650.0) + '@aws-sdk/client-sts': 3.651.1 + '@aws-sdk/core': 3.651.1 + '@aws-sdk/credential-provider-node': 3.651.1(@aws-sdk/client-sso-oidc@3.651.1(@aws-sdk/client-sts@3.651.1))(@aws-sdk/client-sts@3.651.1) '@aws-sdk/middleware-host-header': 3.649.0 '@aws-sdk/middleware-logger': 3.649.0 '@aws-sdk/middleware-recursion-detection': 3.649.0 @@ -31247,11 +31293,11 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso@3.650.0': + '@aws-sdk/client-sso@3.651.1': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.649.0 + '@aws-sdk/core': 3.651.1 '@aws-sdk/middleware-host-header': 3.649.0 '@aws-sdk/middleware-logger': 3.649.0 '@aws-sdk/middleware-recursion-detection': 3.649.0 @@ -31334,13 +31380,13 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sts@3.650.0': + '@aws-sdk/client-sts@3.651.1': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.650.0(@aws-sdk/client-sts@3.650.0) - '@aws-sdk/core': 3.649.0 - '@aws-sdk/credential-provider-node': 3.650.0(@aws-sdk/client-sso-oidc@3.650.0(@aws-sdk/client-sts@3.650.0))(@aws-sdk/client-sts@3.650.0) + '@aws-sdk/client-sso-oidc': 3.651.1(@aws-sdk/client-sts@3.651.1) + '@aws-sdk/core': 3.651.1 + '@aws-sdk/credential-provider-node': 3.651.1(@aws-sdk/client-sso-oidc@3.651.1(@aws-sdk/client-sts@3.651.1))(@aws-sdk/client-sts@3.651.1) '@aws-sdk/middleware-host-header': 3.649.0 '@aws-sdk/middleware-logger': 3.649.0 '@aws-sdk/middleware-recursion-detection': 3.649.0 @@ -31389,7 +31435,7 @@ snapshots: fast-xml-parser: 4.2.5 tslib: 2.6.2 - '@aws-sdk/core@3.649.0': + '@aws-sdk/core@3.651.1': dependencies: '@smithy/core': 2.4.1 '@smithy/node-config-provider': 3.1.5 @@ -31457,14 +31503,14 @@ snapshots: - '@aws-sdk/credential-provider-node' - aws-crt - '@aws-sdk/credential-provider-ini@3.650.0(@aws-sdk/client-sso-oidc@3.650.0(@aws-sdk/client-sts@3.650.0))(@aws-sdk/client-sts@3.650.0)': + '@aws-sdk/credential-provider-ini@3.651.1(@aws-sdk/client-sso-oidc@3.651.1(@aws-sdk/client-sts@3.651.1))(@aws-sdk/client-sts@3.651.1)': dependencies: - '@aws-sdk/client-sts': 3.650.0 + '@aws-sdk/client-sts': 3.651.1 '@aws-sdk/credential-provider-env': 3.649.0 '@aws-sdk/credential-provider-http': 3.649.0 '@aws-sdk/credential-provider-process': 3.649.0 - '@aws-sdk/credential-provider-sso': 3.650.0(@aws-sdk/client-sso-oidc@3.650.0(@aws-sdk/client-sts@3.650.0)) - '@aws-sdk/credential-provider-web-identity': 3.649.0(@aws-sdk/client-sts@3.650.0) + '@aws-sdk/credential-provider-sso': 3.651.1(@aws-sdk/client-sso-oidc@3.651.1(@aws-sdk/client-sts@3.651.1)) + '@aws-sdk/credential-provider-web-identity': 3.649.0(@aws-sdk/client-sts@3.651.1) '@aws-sdk/types': 3.649.0 '@smithy/credential-provider-imds': 3.2.1 '@smithy/property-provider': 3.1.4 @@ -31492,14 +31538,14 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.650.0(@aws-sdk/client-sso-oidc@3.650.0(@aws-sdk/client-sts@3.650.0))(@aws-sdk/client-sts@3.650.0)': + '@aws-sdk/credential-provider-node@3.651.1(@aws-sdk/client-sso-oidc@3.651.1(@aws-sdk/client-sts@3.651.1))(@aws-sdk/client-sts@3.651.1)': dependencies: '@aws-sdk/credential-provider-env': 3.649.0 '@aws-sdk/credential-provider-http': 3.649.0 - '@aws-sdk/credential-provider-ini': 3.650.0(@aws-sdk/client-sso-oidc@3.650.0(@aws-sdk/client-sts@3.650.0))(@aws-sdk/client-sts@3.650.0) + '@aws-sdk/credential-provider-ini': 3.651.1(@aws-sdk/client-sso-oidc@3.651.1(@aws-sdk/client-sts@3.651.1))(@aws-sdk/client-sts@3.651.1) '@aws-sdk/credential-provider-process': 3.649.0 - '@aws-sdk/credential-provider-sso': 3.650.0(@aws-sdk/client-sso-oidc@3.650.0(@aws-sdk/client-sts@3.650.0)) - '@aws-sdk/credential-provider-web-identity': 3.649.0(@aws-sdk/client-sts@3.650.0) + '@aws-sdk/credential-provider-sso': 3.651.1(@aws-sdk/client-sso-oidc@3.651.1(@aws-sdk/client-sts@3.651.1)) + '@aws-sdk/credential-provider-web-identity': 3.649.0(@aws-sdk/client-sts@3.651.1) '@aws-sdk/types': 3.649.0 '@smithy/credential-provider-imds': 3.2.1 '@smithy/property-provider': 3.1.4 @@ -31540,10 +31586,10 @@ snapshots: - '@aws-sdk/credential-provider-node' - aws-crt - '@aws-sdk/credential-provider-sso@3.650.0(@aws-sdk/client-sso-oidc@3.650.0(@aws-sdk/client-sts@3.650.0))': + '@aws-sdk/credential-provider-sso@3.651.1(@aws-sdk/client-sso-oidc@3.651.1(@aws-sdk/client-sts@3.651.1))': dependencies: - '@aws-sdk/client-sso': 3.650.0 - '@aws-sdk/token-providers': 3.649.0(@aws-sdk/client-sso-oidc@3.650.0(@aws-sdk/client-sts@3.650.0)) + '@aws-sdk/client-sso': 3.651.1 + '@aws-sdk/token-providers': 3.649.0(@aws-sdk/client-sso-oidc@3.651.1(@aws-sdk/client-sts@3.651.1)) '@aws-sdk/types': 3.649.0 '@smithy/property-provider': 3.1.4 '@smithy/shared-ini-file-loader': 3.1.5 @@ -31564,17 +31610,17 @@ snapshots: - '@aws-sdk/credential-provider-node' - aws-crt - '@aws-sdk/credential-provider-web-identity@3.649.0(@aws-sdk/client-sts@3.650.0)': + '@aws-sdk/credential-provider-web-identity@3.649.0(@aws-sdk/client-sts@3.651.1)': dependencies: - '@aws-sdk/client-sts': 3.650.0 + '@aws-sdk/client-sts': 3.651.1 '@aws-sdk/types': 3.649.0 '@smithy/property-provider': 3.1.4 '@smithy/types': 3.4.0 tslib: 2.6.2 - '@aws-sdk/lib-storage@3.651.0(@aws-sdk/client-s3@3.651.0)': + '@aws-sdk/lib-storage@3.651.1(@aws-sdk/client-s3@3.651.1)': dependencies: - '@aws-sdk/client-s3': 3.651.0 + '@aws-sdk/client-s3': 3.651.1 '@smithy/abort-controller': 3.1.2 '@smithy/middleware-endpoint': 3.1.1 '@smithy/smithy-client': 3.3.0 @@ -31628,7 +31674,7 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.6.2 - '@aws-sdk/middleware-flexible-checksums@3.651.0': + '@aws-sdk/middleware-flexible-checksums@3.651.1': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 @@ -31637,6 +31683,7 @@ snapshots: '@smithy/node-config-provider': 3.1.5 '@smithy/protocol-http': 4.1.1 '@smithy/types': 3.4.0 + '@smithy/util-middleware': 3.0.4 '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 @@ -31704,9 +31751,9 @@ snapshots: '@smithy/util-config-provider': 2.3.0 tslib: 2.6.2 - '@aws-sdk/middleware-sdk-s3@3.649.0': + '@aws-sdk/middleware-sdk-s3@3.651.1': dependencies: - '@aws-sdk/core': 3.649.0 + '@aws-sdk/core': 3.651.1 '@aws-sdk/types': 3.649.0 '@aws-sdk/util-arn-parser': 3.568.0 '@smithy/core': 2.4.1 @@ -31786,9 +31833,9 @@ snapshots: '@smithy/types': 2.12.0 tslib: 2.6.2 - '@aws-sdk/signature-v4-multi-region@3.649.0': + '@aws-sdk/signature-v4-multi-region@3.651.1': dependencies: - '@aws-sdk/middleware-sdk-s3': 3.649.0 + '@aws-sdk/middleware-sdk-s3': 3.651.1 '@aws-sdk/types': 3.649.0 '@smithy/protocol-http': 4.1.1 '@smithy/signature-v4': 4.1.1 @@ -31807,9 +31854,9 @@ snapshots: - '@aws-sdk/credential-provider-node' - aws-crt - '@aws-sdk/token-providers@3.649.0(@aws-sdk/client-sso-oidc@3.650.0(@aws-sdk/client-sts@3.650.0))': + '@aws-sdk/token-providers@3.649.0(@aws-sdk/client-sso-oidc@3.651.1(@aws-sdk/client-sts@3.651.1))': dependencies: - '@aws-sdk/client-sso-oidc': 3.650.0(@aws-sdk/client-sts@3.650.0) + '@aws-sdk/client-sso-oidc': 3.651.1(@aws-sdk/client-sts@3.651.1) '@aws-sdk/types': 3.649.0 '@smithy/property-provider': 3.1.4 '@smithy/shared-ini-file-loader': 3.1.5 @@ -32166,7 +32213,7 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.0 + picocolors: 1.1.1 '@babel/parser@7.24.1': dependencies: @@ -35119,7 +35166,7 @@ snapshots: '@expo/metro-config': 0.10.7 '@expo/osascript': 2.0.33 '@expo/spawn-async': 1.7.2 - body-parser: 1.20.2 + body-parser: 1.20.3 chalk: 4.1.2 connect: 3.7.0 fs-extra: 9.0.0 @@ -41008,6 +41055,12 @@ snapshots: '@smithy/types': 3.4.0 tslib: 2.6.2 + '@snyk/github-codeowners@1.1.0': + dependencies: + commander: 4.1.1 + ignore: 5.3.1 + p-map: 4.0.0 + '@soda/friendly-errors-webpack-plugin@1.8.1(webpack@5.95.0)': dependencies: chalk: 3.0.0 @@ -46459,7 +46512,7 @@ snapshots: caniuse-lite: 1.0.30001600 fraction.js: 4.3.7 normalize-range: 0.1.2 - picocolors: 1.0.0 + picocolors: 1.1.1 postcss: 8.4.32 postcss-value-parser: 4.2.0 @@ -46469,7 +46522,7 @@ snapshots: caniuse-lite: 1.0.30001600 fraction.js: 4.3.7 normalize-range: 0.1.2 - picocolors: 1.0.0 + picocolors: 1.1.1 postcss: 8.4.38 postcss-value-parser: 4.2.0 @@ -47179,6 +47232,23 @@ snapshots: transitivePeerDependencies: - supports-color + body-parser@1.20.3: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + bonjour-service@1.2.1: dependencies: fast-deep-equal: 3.1.3 @@ -49689,6 +49759,12 @@ snapshots: easy-stack@1.0.1: {} + easy-table@1.2.0: + dependencies: + ansi-regex: 5.0.1 + optionalDependencies: + wcwidth: 1.0.1 + ecc-jsbn@0.1.2: dependencies: jsbn: 0.1.1 @@ -55511,6 +55587,8 @@ snapshots: jiti@1.21.0: {} + jiti@2.3.3: {} + jmespath@0.15.0: {} joi@17.12.2: @@ -56017,6 +56095,26 @@ snapshots: klona@2.0.6: {} + knip@5.34.1(typescript@5.4.3): + dependencies: + '@nodelib/fs.walk': 1.2.8 + '@snyk/github-codeowners': 1.1.0 + easy-table: 1.2.0 + enhanced-resolve: 5.17.1 + fast-glob: 3.3.2 + jiti: 2.3.3 + js-yaml: 4.1.0 + minimist: 1.2.8 + picocolors: 1.1.1 + picomatch: 4.0.2 + pretty-ms: 9.1.0 + smol-toml: 1.3.0 + strip-json-comments: 5.0.1 + summary: 2.1.0 + typescript: 5.4.3 + zod: 3.23.8 + zod-validation-error: 3.4.0(zod@3.23.8) + known-css-properties@0.26.0: {} koa-bodyparser@4.4.1: @@ -56142,7 +56240,7 @@ snapshots: launch-editor@2.8.0: dependencies: - picocolors: 1.0.0 + picocolors: 1.1.1 shell-quote: 1.8.1 layout-base@1.0.2: {} @@ -59142,6 +59240,8 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-ms@4.0.0: {} + parse-numeric-range@1.3.0: {} parse-path@7.0.0: @@ -60192,6 +60292,10 @@ snapshots: pretty-hrtime@1.0.3: {} + pretty-ms@9.1.0: + dependencies: + parse-ms: 4.0.0 + probot@13.2.2: dependencies: '@octokit/core': 5.1.0 @@ -60502,6 +60606,10 @@ snapshots: dependencies: side-channel: 1.0.6 + qs@6.13.0: + dependencies: + side-channel: 1.0.6 + qs@6.5.3: {} query-string@5.1.1: @@ -60774,8 +60882,6 @@ snapshots: raf: 3.4.1 react: 18.2.0 - react-native-adjust@4.38.0: {} - react-native-android-location-services-dialog-box@2.8.2(react-native@0.74.6(@babel/core@7.24.3)(@types/react@18.2.73)(react@18.2.0)): dependencies: react-native: 0.74.6(@babel/core@7.24.3)(@types/react@18.2.73)(react@18.2.0) @@ -62594,7 +62700,7 @@ snapshots: servify@0.1.12: dependencies: - body-parser: 1.20.2 + body-parser: 1.20.3 cors: 2.8.5 express: 4.19.2 request: 2.88.2 @@ -62790,6 +62896,8 @@ snapshots: transitivePeerDependencies: - supports-color + smol-toml@1.3.0: {} + smoldot@2.0.22: dependencies: ws: 8.17.1 @@ -63569,6 +63677,8 @@ snapshots: transitivePeerDependencies: - supports-color + summary@2.1.0: {} + superagent@6.1.0: dependencies: component-emitter: 1.3.1 @@ -65217,7 +65327,7 @@ snapshots: dependencies: browserslist: 4.23.0 escalade: 3.1.2 - picocolors: 1.0.0 + picocolors: 1.1.1 update-browserslist-db@1.1.1(browserslist@4.24.2): dependencies: @@ -66109,7 +66219,7 @@ snapshots: gzip-size: 6.0.0 html-escaper: 2.0.2 opener: 1.5.2 - picocolors: 1.0.0 + picocolors: 1.1.1 sirv: 2.0.4 ws: 7.5.10 transitivePeerDependencies: @@ -67163,6 +67273,10 @@ snapshots: dependencies: zod: 3.23.8 + zod-validation-error@3.4.0(zod@3.23.8): + dependencies: + zod: 3.23.8 + zod@3.22.4: {} zod@3.23.8: {} diff --git a/tools/actions/turborepo-s3-cache/package.json b/tools/actions/turborepo-s3-cache/package.json index 5c8af2f4a02a..b491c0dc1907 100644 --- a/tools/actions/turborepo-s3-cache/package.json +++ b/tools/actions/turborepo-s3-cache/package.json @@ -14,8 +14,8 @@ "@actions/cache": "3.2.4", "@actions/core": "1.10.1", "@actions/github": "6.0.0", - "@aws-sdk/client-s3": "3.651.0", - "@aws-sdk/lib-storage": "3.651.0", + "@aws-sdk/client-s3": "3.651.1", + "@aws-sdk/lib-storage": "3.651.1", "@types/express": "4.17.21", "@types/fs-extra": "9.0.13", "express": "4.19.2", diff --git a/turbo.json b/turbo.json index 10083face441..b20a1090b618 100644 --- a/turbo.json +++ b/turbo.json @@ -81,6 +81,9 @@ "unimported": { "cache": false }, + "knip-check": { + "cache": false + }, "android:apk:local": { "dependsOn": ["^build"] },