diff --git a/.github/workflows/auto-format.yml b/.github/workflows/auto-format.yml index f8b0eec524..e703327416 100644 --- a/.github/workflows/auto-format.yml +++ b/.github/workflows/auto-format.yml @@ -10,12 +10,19 @@ jobs: runs-on: ubuntu-24.04 steps: + - name: Create GitHub App Token + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_APP_ID }} + private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} + - name: Checkout code uses: actions/checkout@v4 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.ref }} - token: ${{ secrets.GIX_CREATE_PR_PAT }} + token: ${{ steps.app-token.outputs.token }} - name: Prepare uses: ./.github/actions/prepare diff --git a/.github/workflows/auto-update-i18n.yml b/.github/workflows/auto-update-i18n.yml index 27fb8723ee..8c99c7efbc 100644 --- a/.github/workflows/auto-update-i18n.yml +++ b/.github/workflows/auto-update-i18n.yml @@ -12,12 +12,18 @@ jobs: runs-on: ubuntu-24.04 steps: + - name: Create GitHub App Token + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_APP_ID }} + private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} - name: Checkout code uses: actions/checkout@v4 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.ref }} - token: ${{ secrets.GIX_CREATE_PR_PAT }} + token: ${{ steps.app-token.outputs.token }} - name: Prepare uses: ./.github/actions/prepare diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index f3c210081b..d0088eec95 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -21,6 +21,12 @@ jobs: runs-on: ubuntu-24.04 steps: + - name: Create GitHub App Token + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_APP_ID }} + private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} - name: Checkout repository uses: actions/checkout@v4 @@ -43,7 +49,7 @@ jobs: - name: Create Pull Request uses: ./.github/actions/create-pr with: - token: ${{ secrets.GIX_CREATE_PR_PAT }} + token: ${{ steps.app-token.outputs.token }} branch: "chore(release)/v${{ steps.bump_version.outputs.new_version }}" title: "chore(release): v${{ steps.bump_version.outputs.new_version }}" body: | diff --git a/.github/workflows/frontend-remove-unused-components.yml b/.github/workflows/frontend-remove-unused-components.yml index fd3cb87113..9c5be126b6 100644 --- a/.github/workflows/frontend-remove-unused-components.yml +++ b/.github/workflows/frontend-remove-unused-components.yml @@ -10,6 +10,12 @@ jobs: runs-on: ubuntu-24.04 steps: + - name: Create GitHub App Token + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_APP_ID }} + private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} - name: Checkout uses: actions/checkout@v4 - name: Prepare @@ -37,7 +43,7 @@ jobs: if: env.CHANGES_DETECTED == 'true' uses: ./.github/actions/create-pr with: - token: ${{ secrets.GIX_CREATE_PR_PAT }} + token: ${{ steps.app-token.outputs.token }} branch: bot-frontend-remove-unused-svelte-files title: 'chore(frontend): Remove Unused Svelte Files' body: | diff --git a/.github/workflows/update-rust.yml b/.github/workflows/update-rust.yml index e6365bd098..f5ed63ac3d 100644 --- a/.github/workflows/update-rust.yml +++ b/.github/workflows/update-rust.yml @@ -12,6 +12,12 @@ jobs: rust-update: runs-on: ubuntu-24.04 steps: + - name: Create GitHub App Token + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_APP_ID }} + private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} - uses: actions/checkout@v4 # First, check rust GitHub releases for a new version. We assume that the @@ -51,8 +57,8 @@ jobs: if: ${{ steps.update.outputs.updated == '1' }} uses: ./.github/actions/create-pr with: - token: ${{ secrets.GIX_CREATE_PR_PAT }} + token: ${{ steps.app-token.outputs.token }} branch: bot-rust-update title: 'build(backend): Update Rust version' body: | - A new Rust version available. \ No newline at end of file + A new Rust version available. diff --git a/.github/workflows/update-snapshots.yml b/.github/workflows/update-snapshots.yml index 6c1013b136..fe1ab4817c 100644 --- a/.github/workflows/update-snapshots.yml +++ b/.github/workflows/update-snapshots.yml @@ -45,6 +45,13 @@ jobs: echo "This workflow should not be triggered with workflow_dispatch on main" exit 1 + - name: Create GitHub App Token + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_APP_ID }} + private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} + - name: Checkout if: ${{ github.event_name != 'pull_request' }} uses: actions/checkout@v4 @@ -55,7 +62,7 @@ jobs: with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.ref }} - token: ${{ secrets.GIX_CREATE_PR_PAT }} + token: ${{ steps.app-token.outputs.token }} - name: Deploy the backend uses: ./.github/actions/deploy-backend diff --git a/.github/workflows/update-tokens.yml b/.github/workflows/update-tokens.yml index 3fda89cb95..9f1742e01e 100644 --- a/.github/workflows/update-tokens.yml +++ b/.github/workflows/update-tokens.yml @@ -10,6 +10,12 @@ jobs: update-tokens-sns: runs-on: ubuntu-24.04 steps: + - name: Create GitHub App Token + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_APP_ID }} + private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} - name: Checkout uses: actions/checkout@v4 - name: Prepare @@ -28,7 +34,7 @@ jobs: if: env.CHANGES_DETECTED == 'true' uses: ./.github/actions/create-pr with: - token: ${{ secrets.GIX_CREATE_PR_PAT }} + token: ${{ steps.app-token.outputs.token }} branch: bot-tokens-sns-update title: 'feat(frontend): Update Sns Tokens' body: | @@ -37,6 +43,12 @@ jobs: update-tokens-ckerc20: runs-on: ubuntu-24.04 steps: + - name: Create GitHub App Token + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_APP_ID }} + private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} - name: Checkout uses: actions/checkout@v4 - name: Prepare @@ -58,7 +70,7 @@ jobs: if: env.CHANGES_DETECTED == 'true' uses: ./.github/actions/create-pr with: - token: ${{ secrets.GIX_CREATE_PR_PAT }} + token: ${{ steps.app-token.outputs.token }} branch: bot-tokens-ckerc20-update title: 'feat(frontend): Update ckErc20 Tokens' body: | diff --git a/e2e/about-why-oisy-modal.spec.ts-snapshots/should-display-about-why-oisy-modal-1-Google-Chrome-linux.png b/e2e/about-why-oisy-modal.spec.ts-snapshots/should-display-about-why-oisy-modal-1-Google-Chrome-linux.png index 241596453f..06535cbe3b 100644 Binary files a/e2e/about-why-oisy-modal.spec.ts-snapshots/should-display-about-why-oisy-modal-1-Google-Chrome-linux.png and b/e2e/about-why-oisy-modal.spec.ts-snapshots/should-display-about-why-oisy-modal-1-Google-Chrome-linux.png differ diff --git a/e2e/activity-page.spec.ts-snapshots/should-display-activity-page-1-Google-Chrome-linux.png b/e2e/activity-page.spec.ts-snapshots/should-display-activity-page-1-Google-Chrome-linux.png index b715848226..5a71f39cb6 100644 Binary files a/e2e/activity-page.spec.ts-snapshots/should-display-activity-page-1-Google-Chrome-linux.png and b/e2e/activity-page.spec.ts-snapshots/should-display-activity-page-1-Google-Chrome-linux.png differ diff --git a/e2e/homepage.spec.ts-snapshots/should-display-homepage-in-logged-in-state-1-Google-Chrome-linux.png b/e2e/homepage.spec.ts-snapshots/should-display-homepage-in-logged-in-state-1-Google-Chrome-linux.png index 25cbc07bbf..0103118a2a 100644 Binary files a/e2e/homepage.spec.ts-snapshots/should-display-homepage-in-logged-in-state-1-Google-Chrome-linux.png and b/e2e/homepage.spec.ts-snapshots/should-display-homepage-in-logged-in-state-1-Google-Chrome-linux.png differ diff --git a/e2e/receive-tokens-modal.spec.ts-snapshots/should-display-receive-tokens-modal-1-Google-Chrome-linux.png b/e2e/receive-tokens-modal.spec.ts-snapshots/should-display-receive-tokens-modal-1-Google-Chrome-linux.png index 10a5760122..c52d24dd05 100644 Binary files a/e2e/receive-tokens-modal.spec.ts-snapshots/should-display-receive-tokens-modal-1-Google-Chrome-linux.png and b/e2e/receive-tokens-modal.spec.ts-snapshots/should-display-receive-tokens-modal-1-Google-Chrome-linux.png differ diff --git a/e2e/transactions-page.spec.ts b/e2e/transactions-page.spec.ts index 7bd29714e1..4db3278cd9 100644 --- a/e2e/transactions-page.spec.ts +++ b/e2e/transactions-page.spec.ts @@ -11,7 +11,7 @@ testWithII('should display BTC transactions page', async ({ page, iiPage }) => { }); // TODO: resolve the below test flakiness -testWithII.skip('should display ETH transactions page', async ({ page, iiPage }) => { +testWithII('should display ETH transactions page', async ({ page, iiPage }) => { const transactionsPage = new TransactionsPage({ page, iiPage, tokenSymbol: 'ETH' }); await transactionsPage.waitForReady(); diff --git a/e2e/transactions-page.spec.ts-snapshots/should-display-ETH-transactions-page-1-Google-Chrome-linux.png b/e2e/transactions-page.spec.ts-snapshots/should-display-ETH-transactions-page-1-Google-Chrome-linux.png index a30594ec1e..76768e7522 100644 Binary files a/e2e/transactions-page.spec.ts-snapshots/should-display-ETH-transactions-page-1-Google-Chrome-linux.png and b/e2e/transactions-page.spec.ts-snapshots/should-display-ETH-transactions-page-1-Google-Chrome-linux.png differ diff --git a/e2e/utils/components/promotion-carousel.component.ts b/e2e/utils/components/promotion-carousel.component.ts new file mode 100644 index 0000000000..740f395847 --- /dev/null +++ b/e2e/utils/components/promotion-carousel.component.ts @@ -0,0 +1,33 @@ +import type { Page } from '@playwright/test'; + +export class PromotionCarousel { + #page: Page; + + constructor(page: Page) { + this.#page = page; + } + + public async navigateToSlide(slideNumber: number): Promise { + const navigation1Selector = `[data-tid="carousel-slide-navigation-${slideNumber}"]:visible`; + await this.#page.click(navigation1Selector); + } + + public async freezeCarousel(): Promise { + // Freeze the carousel by applying the first slide's transform style to all visible slides + await this.#page.$$eval(`div[data-tid="carousel-slide"]:visible`, (elements) => { + elements.forEach((el) => { + const slideStyle = el.style.transform; + el.style.transform = slideStyle; + }); + }); + // Freeze the carousel navigation indicators by fixing their current width and background color styles + await this.#page.$$eval(`[data-tid^="carousel-slide-navigation-"]:visible`, (elements) => { + elements.forEach((el) => { + const indicatorWidth = el.style.width; + const indicatorBackgroundColor = el.style.backgroundColor; + el.style.width = indicatorWidth; + el.style.backgroundColor = indicatorBackgroundColor; + }); + }); + } +} diff --git a/e2e/utils/pages/activity.page.ts b/e2e/utils/pages/activity.page.ts index 606d1a4477..c6f1966bbb 100644 --- a/e2e/utils/pages/activity.page.ts +++ b/e2e/utils/pages/activity.page.ts @@ -17,5 +17,6 @@ export class ActivityPage extends HomepageLoggedIn { // TODO: Implement this method clicking on the navigation item instead of using the URL, when the activity page is implemented override async extendWaitForReady(): Promise { await this.#page.goto(ACTIVITY_URL); + await this.waitForLoadState(); } } diff --git a/e2e/utils/pages/explorer.page.ts b/e2e/utils/pages/explorer.page.ts index c1e2605603..dfd86cba45 100644 --- a/e2e/utils/pages/explorer.page.ts +++ b/e2e/utils/pages/explorer.page.ts @@ -10,5 +10,6 @@ export class ExplorerPage extends HomepageLoggedIn { override async extendWaitForReady(): Promise { await this.clickByTestId(NAVIGATION_ITEM_EXPLORER); + await this.waitForLoadState(); } } diff --git a/e2e/utils/pages/homepage.page.ts b/e2e/utils/pages/homepage.page.ts index 19efe741cc..0448d23134 100644 --- a/e2e/utils/pages/homepage.page.ts +++ b/e2e/utils/pages/homepage.page.ts @@ -11,8 +11,9 @@ import { TOKEN_CARD } from '$lib/constants/test-ids.constants'; import { type InternetIdentityPage } from '@dfinity/internet-identity-playwright'; -import { nonNullish } from '@dfinity/utils'; +import { isNullish, nonNullish } from '@dfinity/utils'; import { expect, type Locator, type Page, type ViewportSize } from '@playwright/test'; +import { PromotionCarousel } from '../components/promotion-carousel.component'; import { HOMEPAGE_URL, LOCAL_REPLICA_URL } from '../constants/e2e.constants'; import { getQRCodeValueFromDataURL } from '../qr-code.utils'; import { getReceiveTokensModalQrCodeButtonSelector } from '../selectors.utils'; @@ -55,6 +56,7 @@ interface WaitForLocatorOptions { abstract class Homepage { readonly #page: Page; readonly #viewportSize?: ViewportSize; + private promotionCarousel?: PromotionCarousel; protected constructor({ page, viewportSize }: HomepageParams) { this.#page = page; @@ -201,6 +203,19 @@ abstract class Homepage { await expect(modal).toHaveScreenshot(); } + async setCarouselFirstSlide(): Promise { + if (isNullish(this.promotionCarousel)) { + this.promotionCarousel = new PromotionCarousel(this.#page); + } + + await this.promotionCarousel.navigateToSlide(1); + await this.promotionCarousel.freezeCarousel(); + } + + async waitForLoadState() { + await this.#page.waitForLoadState('networkidle'); + } + abstract extendWaitForReady(): Promise; abstract waitForReady(): Promise; @@ -218,6 +233,7 @@ export class HomepageLoggedOut extends Homepage { */ async waitForReady(): Promise { await this.waitForHomepageReady(); + await this.waitForLoadState(); } } @@ -299,6 +315,10 @@ export class HomepageLoggedIn extends Homepage { await this.waitForTokensInitialization(); + await this.waitForLoadState(); + + await this.setCarouselFirstSlide(); + await this.extendWaitForReady(); } } diff --git a/e2e/utils/pages/settings.page.ts b/e2e/utils/pages/settings.page.ts index 35b088c9c6..6c05f47f92 100644 --- a/e2e/utils/pages/settings.page.ts +++ b/e2e/utils/pages/settings.page.ts @@ -15,5 +15,7 @@ export class SettingsPage extends HomepageLoggedIn { await this.clickByTestId(NAVIGATION_ITEM_SETTINGS); await this.mockSelector({ selector: `[data-tid="${SETTINGS_ADDRESS_LABEL}"]` }); + + await this.waitForLoadState(); } } diff --git a/e2e/utils/pages/transactions.page.ts b/e2e/utils/pages/transactions.page.ts index bd7e609f13..93038718b9 100644 --- a/e2e/utils/pages/transactions.page.ts +++ b/e2e/utils/pages/transactions.page.ts @@ -16,5 +16,6 @@ export class TransactionsPage extends HomepageLoggedIn { override async extendWaitForReady(): Promise { await this.clickByTestId(`${TOKEN_CARD}-${this.#tokenSymbol}`); + await this.waitForLoadState(); } } diff --git a/package-lock.json b/package-lock.json index e2c6cbee5c..3cebf04490 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "@testing-library/svelte": "^5.2.4", "@types/dom-view-transitions": "^1.0.5", "@types/node": "^20.14.9", - "@vitest/coverage-v8": "^2.1.4", + "@vitest/coverage-v8": "^2.1.7", "autoprefixer": "^10.4.20", "dotenv": "^16.4.5", "fake-indexeddb": "^6.0.0", @@ -53,10 +53,10 @@ "jsqr": "^1.4.0", "pem-file": "^1.0.1", "postcss": "^8.4.49", - "prettier": "^3.3.3", + "prettier": "^3.4.1", "prettier-plugin-organize-imports": "^4.1.0", - "prettier-plugin-svelte": "^3.2.8", - "prettier-plugin-tailwindcss": "^0.6.8", + "prettier-plugin-svelte": "^3.3.2", + "prettier-plugin-tailwindcss": "^0.6.9", "sass": "^1.81.0", "svelte": "^4.2.19", "svelte-check": "^4.1.0", @@ -3945,9 +3945,9 @@ "peer": true }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.4.tgz", - "integrity": "sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.7.tgz", + "integrity": "sha512-deQ4J+yu6nEjmEfcBndbgrRM95IZoRpV1dDVRbZhjUcgYVZz/Wc4YaLiDDt9Sy5qcikrJUZMlrUxDy7dBojebg==", "dev": true, "license": "MIT", "dependencies": { @@ -3960,7 +3960,7 @@ "istanbul-reports": "^3.1.7", "magic-string": "^0.30.12", "magicast": "^0.3.5", - "std-env": "^3.7.0", + "std-env": "^3.8.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" }, @@ -3968,8 +3968,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.4", - "vitest": "2.1.4" + "@vitest/browser": "2.1.7", + "vitest": "2.1.7" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -4003,13 +4003,14 @@ "license": "MIT" }, "node_modules/@vitest/expect": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", - "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.7.tgz", + "integrity": "sha512-folWk4qQDEedgUyvaZw94LIJuNLoDtY+rhKhhNy0csdwifn/pQz8EWVRnyrW3j0wMpy+xwJT8WiwiYxk+i+s7w==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.4", - "@vitest/utils": "2.1.4", + "@vitest/spy": "2.1.7", + "@vitest/utils": "2.1.7", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, @@ -4018,12 +4019,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz", - "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.7.tgz", + "integrity": "sha512-nKMTnuJrarFH+7llWxeLmYRldIwTY3OM1DzdytHj0f2+fah6Cyk4XbswhjOiTCnAvXsZAEoo1OaD6rneSSU+3Q==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.4", + "@vitest/spy": "2.1.7", "estree-walker": "^3.0.3", "magic-string": "^0.30.12" }, @@ -4044,10 +4046,11 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz", - "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.7.tgz", + "integrity": "sha512-HoqRIyfQlXPrRDB43h0lC8eHPUDPwFweMaD6t+psOvwClCC+oZZim6wPMjuoMnRdiFxXqbybg/QbuewgTwK1vA==", "dev": true, + "license": "MIT", "dependencies": { "tinyrainbow": "^1.2.0" }, @@ -4056,12 +4059,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz", - "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.7.tgz", + "integrity": "sha512-MrDNpXUIXksR57qipYh068SOX4N1hVw6oVILlTlfeTyA1rp0asuljyp15IZwKqhjpWLObFj+tiNrOM4R8UnSqg==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.4", + "@vitest/utils": "2.1.7", "pathe": "^1.1.2" }, "funding": { @@ -4069,12 +4073,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz", - "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.7.tgz", + "integrity": "sha512-OioIxV/xS393DKdlkRNhmtY0K37qVdCv8w1M2SlLTBSX+fNK6zgcd01VlT1nXdbKVDaB8Zb6BOfQYYoGeGTEGg==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.4", + "@vitest/pretty-format": "2.1.7", "magic-string": "^0.30.12", "pathe": "^1.1.2" }, @@ -4083,10 +4088,11 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz", - "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.7.tgz", + "integrity": "sha512-e5pzIaIC0LBrb/j1FaF7HXlPJLGtltiAkwXTMqNEHALJc7USSLEwziJ+aIWTmjsWNg89zazg37h7oZITnublsQ==", "dev": true, + "license": "MIT", "dependencies": { "tinyspy": "^3.0.2" }, @@ -4095,12 +4101,13 @@ } }, "node_modules/@vitest/utils": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", - "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.7.tgz", + "integrity": "sha512-7gUdvIzCCuIrMZu0WHTvDJo8C1NsUtOqmwmcS3bRHUcfHemj29wmkzLVNuWQD7WHoBD/+I7WIgrnzt7kxR54ow==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.4", + "@vitest/pretty-format": "2.1.7", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, @@ -4764,6 +4771,7 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" } @@ -5209,6 +5217,7 @@ "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, + "license": "MIT", "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -5253,6 +5262,7 @@ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 16" } @@ -5612,6 +5622,7 @@ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -8759,7 +8770,8 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lru-cache": { "version": "10.4.3", @@ -9502,6 +9514,7 @@ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14.16" } @@ -9905,9 +9918,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", "dev": true, "license": "MIT", "bin": { @@ -9951,20 +9964,22 @@ } }, "node_modules/prettier-plugin-svelte": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.8.tgz", - "integrity": "sha512-PAHmmU5cGZdnhW4mWhmvxuG2PVbbHIxUuPOdUKvfE+d4Qt2d29iU5VWrPdsaW5YqVEE0nqhlvN4eoKmVMpIF3Q==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.2.tgz", + "integrity": "sha512-kRPjH8wSj2iu+dO+XaUv4vD8qr5mdDmlak3IT/7AOgGIMRG86z/EHOLauFcClKEnOUf4A4nOA7sre5KrJD4Raw==", "dev": true, + "license": "MIT", "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.8.tgz", - "integrity": "sha512-dGu3kdm7SXPkiW4nzeWKCl3uoImdd5CTZEJGxyypEPL37Wj0HT2pLqjrvSei1nTeuQfO4PUfjeW5cTUNRLZ4sA==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.9.tgz", + "integrity": "sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.21.3" }, @@ -10718,9 +10733,10 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "license": "MIT" }, "node_modules/stream-shift": { "version": "1.0.3", @@ -11515,6 +11531,7 @@ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -12348,10 +12365,11 @@ } }, "node_modules/vite-node": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz", - "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.7.tgz", + "integrity": "sha512-b/5MxSWd0ftWt1B1LHfzCw0ASzaxHztUwP0rcsBhkDSGy9ZDEDieSIjFG3I78nI9dUN0eSeD6LtuKPZGjwwpZQ==", "dev": true, + "license": "MIT", "dependencies": { "cac": "^6.7.14", "debug": "^4.3.7", @@ -12363,7 +12381,7 @@ "vite-node": "vite-node.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -12409,46 +12427,47 @@ } }, "node_modules/vitest": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz", - "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.7.tgz", + "integrity": "sha512-wzJ7Wri44ufkzTZbI1lHsdHfiGdFRmnJ9qIudDQ6tknjJeHhF5QgNSSjk7KRZUU535qEiEXFJ7tSHqyzyIv0jQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.4", - "@vitest/mocker": "2.1.4", - "@vitest/pretty-format": "^2.1.4", - "@vitest/runner": "2.1.4", - "@vitest/snapshot": "2.1.4", - "@vitest/spy": "2.1.4", - "@vitest/utils": "2.1.4", + "@vitest/expect": "2.1.7", + "@vitest/mocker": "2.1.7", + "@vitest/pretty-format": "^2.1.7", + "@vitest/runner": "2.1.7", + "@vitest/snapshot": "2.1.7", + "@vitest/spy": "2.1.7", + "@vitest/utils": "2.1.7", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", "magic-string": "^0.30.12", "pathe": "^1.1.2", - "std-env": "^3.7.0", + "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.1.4", + "vite-node": "2.1.7", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.4", - "@vitest/ui": "2.1.4", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "2.1.7", + "@vitest/ui": "2.1.7", "happy-dom": "*", "jsdom": "*" }, @@ -12492,6 +12511,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -12508,28 +12528,8 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/vitest/node_modules/vite-node": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz", - "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==", "dev": true, - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.7", - "pathe": "^1.1.2", - "vite": "^5.0.0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "license": "MIT" }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", diff --git a/package.json b/package.json index d5d2a774e1..bbaa74c77b 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "@testing-library/svelte": "^5.2.4", "@types/dom-view-transitions": "^1.0.5", "@types/node": "^20.14.9", - "@vitest/coverage-v8": "^2.1.4", + "@vitest/coverage-v8": "^2.1.7", "autoprefixer": "^10.4.20", "dotenv": "^16.4.5", "fake-indexeddb": "^6.0.0", @@ -94,10 +94,10 @@ "jsqr": "^1.4.0", "pem-file": "^1.0.1", "postcss": "^8.4.49", - "prettier": "^3.3.3", + "prettier": "^3.4.1", "prettier-plugin-organize-imports": "^4.1.0", - "prettier-plugin-svelte": "^3.2.8", - "prettier-plugin-tailwindcss": "^0.6.8", + "prettier-plugin-svelte": "^3.3.2", + "prettier-plugin-tailwindcss": "^0.6.9", "sass": "^1.81.0", "svelte": "^4.2.19", "svelte-check": "^4.1.0", diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 06f5df86b0..8c05b81d59 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.82.0" +channel = "1.83.0" targets = ["wasm32-unknown-unknown"] diff --git a/src/backend/backend.did b/src/backend/backend.did index e9612209b0..2939e2f6b4 100644 --- a/src/backend/backend.did +++ b/src/backend/backend.did @@ -77,6 +77,8 @@ type CustomToken = record { version : opt nat64; enabled : bool; }; +type DappCarouselSettings = record { hidden_dapp_ids : vec text }; +type DappSettings = record { dapp_carousel : DappCarouselSettings }; type DefiniteCanisterSettingsArgs = record { controller : principal; freezing_threshold : nat; @@ -179,6 +181,7 @@ type SelectedUtxosFeeResponse = record { fee_satoshis : nat64; utxos : vec Utxo; }; +type Settings = record { dapp : DappSettings }; type Stats = record { user_profile_count : nat64; custom_token_count : nat64; @@ -219,6 +222,7 @@ type UserCredential = record { type UserProfile = record { credentials : vec UserCredential; version : opt nat64; + settings : Settings; created_timestamp : nat64; updated_timestamp : nat64; }; diff --git a/src/backend/src/bitcoin_utils.rs b/src/backend/src/bitcoin_utils.rs index 38802a49b3..28e8eddf96 100644 --- a/src/backend/src/bitcoin_utils.rs +++ b/src/backend/src/bitcoin_utils.rs @@ -1,6 +1,6 @@ -use ic_cdk::api::management_canister::bitcoin::Utxo; +//! Functions [inspired by ckBTC Minter](https://github.com/dfinity/ic/blob/285a5db07da50a4e350ec43bf3b488cc6fe36102/rs/bitcoin/ckbtc/minter/src/lib.rs#L1258) -/// Functions [inspired by ckBTC Minter](https://github.com/dfinity/ic/blob/285a5db07da50a4e350ec43bf3b488cc6fe36102/rs/bitcoin/ckbtc/minter/src/lib.rs#L1258) +use ic_cdk::api::management_canister::bitcoin::Utxo; /// Selects a subset of UTXOs with the specified total target value and removes /// the selected UTXOs from the available set. diff --git a/src/backend/tests/it/user_profile.rs b/src/backend/tests/it/user_profile.rs index 6d5f591757..c11d6c17eb 100644 --- a/src/backend/tests/it/user_profile.rs +++ b/src/backend/tests/it/user_profile.rs @@ -18,6 +18,12 @@ fn test_create_user_profile_creates_default_profile() { let user_profile = response.expect("Create failed"); + assert!(user_profile + .settings + .dapp + .dapp_carousel + .hidden_dapp_ids + .is_empty()); assert_eq!(user_profile.credentials.len(), 0); assert!(user_profile.version.is_none()); } diff --git a/src/declarations/backend/backend.did b/src/declarations/backend/backend.did index e9612209b0..2939e2f6b4 100644 --- a/src/declarations/backend/backend.did +++ b/src/declarations/backend/backend.did @@ -77,6 +77,8 @@ type CustomToken = record { version : opt nat64; enabled : bool; }; +type DappCarouselSettings = record { hidden_dapp_ids : vec text }; +type DappSettings = record { dapp_carousel : DappCarouselSettings }; type DefiniteCanisterSettingsArgs = record { controller : principal; freezing_threshold : nat; @@ -179,6 +181,7 @@ type SelectedUtxosFeeResponse = record { fee_satoshis : nat64; utxos : vec Utxo; }; +type Settings = record { dapp : DappSettings }; type Stats = record { user_profile_count : nat64; custom_token_count : nat64; @@ -219,6 +222,7 @@ type UserCredential = record { type UserProfile = record { credentials : vec UserCredential; version : opt nat64; + settings : Settings; created_timestamp : nat64; updated_timestamp : nat64; }; diff --git a/src/declarations/backend/backend.did.d.ts b/src/declarations/backend/backend.did.d.ts index 54e3c3ca7b..8df69cfcbf 100644 --- a/src/declarations/backend/backend.did.d.ts +++ b/src/declarations/backend/backend.did.d.ts @@ -80,6 +80,12 @@ export interface CustomToken { version: [] | [bigint]; enabled: boolean; } +export interface DappCarouselSettings { + hidden_dapp_ids: Array; +} +export interface DappSettings { + dapp_carousel: DappCarouselSettings; +} export interface DefiniteCanisterSettingsArgs { controller: Principal; freezing_threshold: bigint; @@ -189,6 +195,9 @@ export interface SelectedUtxosFeeResponse { fee_satoshis: bigint; utxos: Array; } +export interface Settings { + dapp: DappSettings; +} export interface Stats { user_profile_count: bigint; custom_token_count: bigint; @@ -235,6 +244,7 @@ export interface UserCredential { export interface UserProfile { credentials: Array; version: [] | [bigint]; + settings: Settings; created_timestamp: bigint; updated_timestamp: bigint; } diff --git a/src/declarations/backend/backend.factory.certified.did.js b/src/declarations/backend/backend.factory.certified.did.js index cd864d28b9..c16bff58de 100644 --- a/src/declarations/backend/backend.factory.certified.did.js +++ b/src/declarations/backend/backend.factory.certified.did.js @@ -141,9 +141,15 @@ export const idlFactory = ({ IDL }) => { verified_date_timestamp: IDL.Opt(IDL.Nat64), credential_type: CredentialType }); + const DappCarouselSettings = IDL.Record({ + hidden_dapp_ids: IDL.Vec(IDL.Text) + }); + const DappSettings = IDL.Record({ dapp_carousel: DappCarouselSettings }); + const Settings = IDL.Record({ dapp: DappSettings }); const UserProfile = IDL.Record({ credentials: IDL.Vec(UserCredential), version: IDL.Opt(IDL.Nat64), + settings: Settings, created_timestamp: IDL.Nat64, updated_timestamp: IDL.Nat64 }); diff --git a/src/declarations/backend/backend.factory.did.js b/src/declarations/backend/backend.factory.did.js index fda8a02f6c..ba6c79a70a 100644 --- a/src/declarations/backend/backend.factory.did.js +++ b/src/declarations/backend/backend.factory.did.js @@ -141,9 +141,15 @@ export const idlFactory = ({ IDL }) => { verified_date_timestamp: IDL.Opt(IDL.Nat64), credential_type: CredentialType }); + const DappCarouselSettings = IDL.Record({ + hidden_dapp_ids: IDL.Vec(IDL.Text) + }); + const DappSettings = IDL.Record({ dapp_carousel: DappCarouselSettings }); + const Settings = IDL.Record({ dapp: DappSettings }); const UserProfile = IDL.Record({ credentials: IDL.Vec(UserCredential), version: IDL.Opt(IDL.Nat64), + settings: Settings, created_timestamp: IDL.Nat64, updated_timestamp: IDL.Nat64 }); diff --git a/src/frontend/src/btc/components/core/BtcLoaderWallets.svelte b/src/frontend/src/btc/components/core/BtcLoaderWallets.svelte index 2669de7ded..6fd353166a 100644 --- a/src/frontend/src/btc/components/core/BtcLoaderWallets.svelte +++ b/src/frontend/src/btc/components/core/BtcLoaderWallets.svelte @@ -1,6 +1,8 @@ - + diff --git a/src/frontend/src/btc/components/transactions/BtcTransactionModal.svelte b/src/frontend/src/btc/components/transactions/BtcTransactionModal.svelte index a62f506bee..719aeaa3aa 100644 --- a/src/frontend/src/btc/components/transactions/BtcTransactionModal.svelte +++ b/src/frontend/src/btc/components/transactions/BtcTransactionModal.svelte @@ -57,6 +57,7 @@ }} hash={id} value={nonNullish(value) ? BigNumber.from(value) : undefined} + {token} sendToLabel={$i18n.transaction.text.to} typeLabel={type === 'send' ? $i18n.send.text.send : $i18n.receive.text.receive} > diff --git a/src/frontend/src/btc/schedulers/btc-wallet.scheduler.ts b/src/frontend/src/btc/schedulers/btc-wallet.scheduler.ts index a63e21fd8a..0e9f689c46 100644 --- a/src/frontend/src/btc/schedulers/btc-wallet.scheduler.ts +++ b/src/frontend/src/btc/schedulers/btc-wallet.scheduler.ts @@ -1,7 +1,10 @@ import { BTC_BALANCE_MIN_CONFIRMATIONS } from '$btc/constants/btc.constants'; +import type { BtcTransactionUi } from '$btc/types/btc'; import type { BtcPostMessageDataResponseWallet } from '$btc/types/btc-post-message'; import { mapBtcTransaction } from '$btc/utils/btc-transactions.utils'; -import type { BitcoinNetwork } from '$declarations/signer/signer.did'; +import { BITCOIN_CANISTER_IDS } from '$env/networks.btc.env'; +import { getBalanceQuery } from '$icp/api/bitcoin.api'; +import { queryAndUpdate, type QueryAndUpdateRequestParams } from '$lib/actors/query.ic'; import { getBtcBalance } from '$lib/api/signer.api'; import { WALLET_TIMER_INTERVAL_MILLIS } from '$lib/constants/app.constants'; import { btcAddressData } from '$lib/rest/blockchain.rest'; @@ -9,16 +12,29 @@ import { btcLatestBlockHeight } from '$lib/rest/blockstream.rest'; import { SchedulerTimer, type Scheduler, type SchedulerJobData } from '$lib/schedulers/scheduler'; import type { BtcAddress } from '$lib/types/address'; import type { BitcoinTransaction } from '$lib/types/blockchain'; -import type { OptionIdentity } from '$lib/types/identity'; +import type { OptionCanisterIdText } from '$lib/types/canister'; import type { PostMessageDataRequestBtc } from '$lib/types/post-message'; import type { CertifiedData } from '$lib/types/store'; +import { mapToSignerBitcoinNetwork } from '$lib/utils/network.utils'; +import { type BitcoinNetwork } from '@dfinity/ckbtc'; import { assertNonNullish, isNullish, jsonReplacer, nonNullish } from '@dfinity/utils'; +interface LoadBtcWalletParams extends QueryAndUpdateRequestParams { + bitcoinNetwork: BitcoinNetwork; + btcAddress: BtcAddress; + shouldFetchTransactions?: boolean; + minterCanisterId?: OptionCanisterIdText; +} interface BtcWalletStore { - balance: CertifiedData | undefined; + balance: CertifiedData | undefined; transactions: Record>; } +interface BtcWalletData { + balance: CertifiedData; + uncertifiedTransactions: CertifiedData[]; +} + export class BtcWalletScheduler implements Scheduler { private timer = new SchedulerTimer('syncBtcWalletStatus'); @@ -46,10 +62,11 @@ export class BtcWalletScheduler implements Scheduler }); } - private async loadBtcTransactionsData({ btcAddress }: { btcAddress: BtcAddress }): Promise<{ - newTransactions: BitcoinTransaction[]; - latestBitcoinBlockHeight: number | undefined; - }> { + private async loadBtcTransactionsData({ + btcAddress + }: { + btcAddress: BtcAddress; + }): Promise[]> { try { const { txs: fetchedTransactions } = await btcAddressData({ btcAddress }); @@ -57,84 +74,139 @@ export class BtcWalletScheduler implements Scheduler isNullish(this.store.transactions[`${hash}`]) ); - this.store = { - ...this.store, - transactions: { - ...this.store.transactions, - ...newTransactions.reduce( - (acc, transaction) => ({ - ...acc, - [transaction.hash]: { - certified: false, - data: transaction - } - }), - {} - ) - } - }; - const latestBitcoinBlockHeight = await btcLatestBlockHeight(); - return { newTransactions, latestBitcoinBlockHeight }; + return newTransactions.map((transaction) => ({ + data: mapBtcTransaction({ transaction, btcAddress, latestBitcoinBlockHeight }), + certified: false + })); } catch (error) { // We don't want to disrupt the user experience if we can't fetch the transactions or latest block height. console.error('Error fetching BTC transactions data:', error); - // TODO: Return an error instead of an object with empty array. - return { newTransactions: [], latestBitcoinBlockHeight: undefined }; + // TODO: Return an error instead of an empty array. + return []; } } - private async loadBtcBalance({ + private loadBtcBalance = async ({ identity, - bitcoinNetwork - }: { - identity: OptionIdentity; - bitcoinNetwork: BitcoinNetwork; - }): Promise> { - const balance = await getBtcBalance({ - identity, - network: bitcoinNetwork, - minConfirmations: BTC_BALANCE_MIN_CONFIRMATIONS - }); - const certifiedBalance = { - data: balance, + bitcoinNetwork, + btcAddress, + minterCanisterId, + certified = true + }: Omit): Promise< + CertifiedData + > => { + if (!certified) { + // Query BTC balance only if minterCanisterId and BITCOIN_CANISTER_IDS[minterCanisterId] are available + // These values will be there only for "mainnet", for other networks - balance on "query" will be null + return { + data: + nonNullish(minterCanisterId) && BITCOIN_CANISTER_IDS[minterCanisterId] + ? await getBalanceQuery({ + identity, + network: bitcoinNetwork, + address: btcAddress, + bitcoinCanisterId: BITCOIN_CANISTER_IDS[minterCanisterId], + minConfirmations: BTC_BALANCE_MIN_CONFIRMATIONS + }) + : null, + certified: false + }; + } + + return { + data: await getBtcBalance({ + identity, + network: mapToSignerBitcoinNetwork({ + network: bitcoinNetwork + }), + minConfirmations: BTC_BALANCE_MIN_CONFIRMATIONS + }), certified: true }; + }; - this.store = { - ...this.store, - balance: certifiedBalance - }; + private loadWalletData = async ({ + certified, + identity, + bitcoinNetwork, + btcAddress, + minterCanisterId, + shouldFetchTransactions + }: LoadBtcWalletParams) => { + const balance = await this.loadBtcBalance({ + identity, + bitcoinNetwork, + certified, + btcAddress, + minterCanisterId + }); - return certifiedBalance; - } + // TODO: investigate and implement "update" call for BTC transactions + const uncertifiedTransactions = + shouldFetchTransactions && !certified + ? await this.loadBtcTransactionsData({ btcAddress }) + : []; + + return { balance, uncertifiedTransactions }; + }; - /* TODO: The following steps need to be done: - * 1. [Required] Fetch uncertified transactions via BTC transaction API. - * 2. [Improvement] Query uncertified balance in oder to improve UX (signer.getBtcBalance takes ~5s to complete). - * 3. [Required] Fetch certified transactions via BE endpoint (to be discussed). - * */ private syncWallet = async ({ identity, data }: SchedulerJobData) => { const bitcoinNetwork = data?.bitcoinNetwork; - assertNonNullish(bitcoinNetwork, 'No BTC network provided to get BTC certified balance.'); + assertNonNullish(bitcoinNetwork, 'No BTC network provided to get BTC balance.'); const btcAddress = data?.btcAddress.data; assertNonNullish(btcAddress, 'No BTC address provided to get BTC transactions.'); - const balance = await this.loadBtcBalance({ identity, bitcoinNetwork }); - const { newTransactions, latestBitcoinBlockHeight } = - nonNullish(data) && data.shouldFetchTransactions - ? await this.loadBtcTransactionsData({ btcAddress }) - : { newTransactions: [], latestBitcoinBlockHeight: undefined }; - - // TODO: handle the case when tx data is available but latestBitcoinBlockHeight is undefined - const uncertifiedTransactions = nonNullish(latestBitcoinBlockHeight) - ? newTransactions.map((transaction) => ({ - data: mapBtcTransaction({ transaction, btcAddress, latestBitcoinBlockHeight }), - certified: false - })) - : []; + // TODO: implement "onCertifiedError" to handle errors in update calls + await queryAndUpdate({ + request: ({ identity: _, certified }) => + this.loadWalletData({ + certified, + identity, + btcAddress, + bitcoinNetwork, + shouldFetchTransactions: data?.shouldFetchTransactions, + minterCanisterId: data?.minterCanisterId + }), + onLoad: ({ certified: _, ...rest }) => this.syncWalletData(rest), + identity, + resolution: 'all_settled' + }); + }; + + private syncWalletData = ({ + response: { balance, uncertifiedTransactions } + }: { + response: BtcWalletData; + }) => { + const newBalance = + isNullish(this.store.balance) || + this.store.balance.data !== balance.data || + (!this.store.balance.certified && balance.certified); + const newTransactions = uncertifiedTransactions.length > 0; + + this.store = { + ...this.store, + ...(newBalance && { balance }), + ...(newTransactions && { + transactions: { + ...this.store.transactions, + ...uncertifiedTransactions.reduce( + (acc, uncertifiedTransaction) => ({ + ...acc, + [uncertifiedTransaction.data.id]: uncertifiedTransaction + }), + {} + ) + } + }) + }; + + if (!newBalance && !newTransactions) { + return; + } this.postMessageWallet({ wallet: { diff --git a/src/frontend/src/btc/schema/btc-post-message.schema.ts b/src/frontend/src/btc/schema/btc-post-message.schema.ts index 70d7a81c6a..d4ca1ae3b0 100644 --- a/src/frontend/src/btc/schema/btc-post-message.schema.ts +++ b/src/frontend/src/btc/schema/btc-post-message.schema.ts @@ -1,10 +1,12 @@ import { JsonTransactionsTextSchema, - PostMessageDataResponseSchema, - PostMessageWalletDataSchema + PostMessageDataResponseSchema } from '$lib/schema/post-message.schema'; +import type { CertifiedData } from '$lib/types/store'; +import { z } from 'zod'; -const BtcPostMessageWalletDataSchema = PostMessageWalletDataSchema.extend({ +const BtcPostMessageWalletDataSchema = z.object({ + balance: z.custom>(), newTransactions: JsonTransactionsTextSchema }); diff --git a/src/frontend/src/btc/services/btc-listener.services.ts b/src/frontend/src/btc/services/btc-listener.services.ts index 05b8277167..0c8e645f17 100644 --- a/src/frontend/src/btc/services/btc-listener.services.ts +++ b/src/frontend/src/btc/services/btc-listener.services.ts @@ -2,7 +2,7 @@ import { btcTransactionsStore } from '$btc/stores/btc-transactions.store'; import type { BtcPostMessageDataResponseWallet } from '$btc/types/btc-post-message'; import { balancesStore } from '$lib/stores/balances.store'; import type { TokenId } from '$lib/types/token'; -import { jsonReviver } from '@dfinity/utils'; +import { jsonReviver, nonNullish } from '@dfinity/utils'; import { BigNumber } from '@ethersproject/bignumber'; export const syncWallet = ({ @@ -19,13 +19,17 @@ export const syncWallet = ({ } } = data; - balancesStore.set({ - tokenId, - data: { - data: BigNumber.from(balance), - certified - } - }); + if (nonNullish(balance)) { + balancesStore.set({ + tokenId, + data: { + data: BigNumber.from(balance), + certified + } + }); + } else { + balancesStore.reset(tokenId); + } btcTransactionsStore.prepend({ tokenId, diff --git a/src/frontend/src/btc/services/worker.btc-wallet.services.ts b/src/frontend/src/btc/services/worker.btc-wallet.services.ts index ae9d05fc30..a70f5981bd 100644 --- a/src/frontend/src/btc/services/worker.btc-wallet.services.ts +++ b/src/frontend/src/btc/services/worker.btc-wallet.services.ts @@ -5,21 +5,27 @@ import { btcAddressRegtestStore, btcAddressTestnetStore } from '$lib/stores/address.store'; +import type { OptionCanisterIdText } from '$lib/types/canister'; import type { WalletWorker } from '$lib/types/listener'; import type { PostMessage } from '$lib/types/post-message'; import type { Token } from '$lib/types/token'; import { isNetworkIdBTCMainnet, isNetworkIdBTCRegtest, - isNetworkIdBTCTestnet, - mapToSignerBitcoinNetwork + isNetworkIdBTCTestnet } from '$lib/utils/network.utils'; import { get } from 'svelte/store'; export const initBtcWalletWorker = async ({ - id: tokenId, - network: { id: networkId } -}: Token): Promise => { + token: { + id: tokenId, + network: { id: networkId } + }, + minterCanisterId +}: { + token: Token; + minterCanisterId?: OptionCanisterIdText; +}): Promise => { const WalletWorker = await import('$btc/workers/btc-wallet.worker?worker'); const worker: Worker = new WalletWorker.default(); @@ -48,11 +54,10 @@ export const initBtcWalletWorker = async ({ ? btcAddressRegtestStore : btcAddressMainnetStore ), - bitcoinNetwork: mapToSignerBitcoinNetwork({ - network: isTestnetNetwork ? 'testnet' : isRegtestNetwork ? 'regtest' : 'mainnet' - }), + bitcoinNetwork: isTestnetNetwork ? 'testnet' : isRegtestNetwork ? 'regtest' : 'mainnet', // only mainnet transactions can be fetched via Blockchain API - shouldFetchTransactions: isNetworkIdBTCMainnet(networkId) + shouldFetchTransactions: isNetworkIdBTCMainnet(networkId), + minterCanisterId }; return { diff --git a/src/frontend/src/icp/api/bitcoin.api.ts b/src/frontend/src/icp/api/bitcoin.api.ts index 039b890468..2a70bb844e 100644 --- a/src/frontend/src/icp/api/bitcoin.api.ts +++ b/src/frontend/src/icp/api/bitcoin.api.ts @@ -33,15 +33,19 @@ export const getBalanceQuery = async ({ identity, address, network, - bitcoinCanisterId -}: BitcoinCanisterParams): Promise => { + bitcoinCanisterId, + minConfirmations +}: BitcoinCanisterParams & { + minConfirmations?: number; +}): Promise => { assertNonNullish(identity); const { getBalanceQuery } = await bitcoinCanister({ identity, bitcoinCanisterId }); return getBalanceQuery({ address, - network + network, + minConfirmations }); }; diff --git a/src/frontend/src/icp/components/transactions/IcNoIndexPlaceholder.svelte b/src/frontend/src/icp/components/transactions/IcNoIndexPlaceholder.svelte index 0a17ff3f22..32ab7fc6c7 100644 --- a/src/frontend/src/icp/components/transactions/IcNoIndexPlaceholder.svelte +++ b/src/frontend/src/icp/components/transactions/IcNoIndexPlaceholder.svelte @@ -17,7 +17,7 @@

{$i18n.transactions.text.transaction_history_unavailable}

-

+

{replaceOisyPlaceholders( placeholderType === 'not-working' ? $i18n.transactions.text.index_canister_not_working_explanation diff --git a/src/frontend/src/lib/components/carousel/Carousel.svelte b/src/frontend/src/lib/components/carousel/Carousel.svelte index 481427e241..91bafbc36b 100644 --- a/src/frontend/src/lib/components/carousel/Carousel.svelte +++ b/src/frontend/src/lib/components/carousel/Carousel.svelte @@ -3,6 +3,7 @@ import { onMount } from 'svelte'; import Controls from '$lib/components/carousel/Controls.svelte'; import Indicators from '$lib/components/carousel/Indicators.svelte'; + import { CAROUSEL_CONTAINER } from '$lib/constants/test-ids.constants'; import { moveSlider, extendCarouselSliderFrame } from '$lib/utils/carousel.utils'; export let autoplay = 5000; @@ -245,10 +246,11 @@

-
+
diff --git a/src/frontend/src/lib/components/carousel/Indicator.svelte b/src/frontend/src/lib/components/carousel/Indicator.svelte index 85794800c0..a22398b9bd 100644 --- a/src/frontend/src/lib/components/carousel/Indicator.svelte +++ b/src/frontend/src/lib/components/carousel/Indicator.svelte @@ -1,4 +1,5 @@ -

{replaceOisyPlaceholders($i18n.dapps.text.sign_in)}

+

{replaceOisyPlaceholders($i18n.dapps.text.sign_in)}

diff --git a/src/frontend/src/lib/components/navigation/NavigationMenu.svelte b/src/frontend/src/lib/components/navigation/NavigationMenu.svelte index fc01e3903c..13309c7a5f 100644 --- a/src/frontend/src/lib/components/navigation/NavigationMenu.svelte +++ b/src/frontend/src/lib/components/navigation/NavigationMenu.svelte @@ -48,7 +48,7 @@ href={networkUrl({ path: AppPath.Tokens, networkId: $networkId, - isTransactionsRoute, + usePreviousRoute: isTransactionsRoute, fromRoute })} ariaLabel={$i18n.navigation.alt.tokens} @@ -63,7 +63,7 @@ href={networkUrl({ path: AppPath.Activity, networkId: $networkId, - isTransactionsRoute, + usePreviousRoute: isTransactionsRoute, fromRoute })} ariaLabel={$i18n.navigation.alt.activity} @@ -78,7 +78,7 @@ href={networkUrl({ path: AppPath.Explore, networkId: $networkId, - isTransactionsRoute, + usePreviousRoute: isTransactionsRoute, fromRoute })} ariaLabel={$i18n.navigation.alt.dapp_explorer} @@ -93,7 +93,7 @@ href={networkUrl({ path: AppPath.Settings, networkId: $networkId, - isTransactionsRoute, + usePreviousRoute: isTransactionsRoute, fromRoute })} ariaLabel={$i18n.navigation.alt.settings} diff --git a/src/frontend/src/lib/components/receive/ReceiveAddresses.svelte b/src/frontend/src/lib/components/receive/ReceiveAddresses.svelte index ac891b255e..4df1827b8f 100644 --- a/src/frontend/src/lib/components/receive/ReceiveAddresses.svelte +++ b/src/frontend/src/lib/components/receive/ReceiveAddresses.svelte @@ -146,7 +146,7 @@ > {$i18n.receive.ethereum.text.ethereum} - {$i18n.receive.icp.text.your_private_eth_address} @@ -172,7 +172,7 @@ > {$i18n.receive.icp.text.principal} - {$i18n.receive.icp.text.use_for_icrc_deposit} diff --git a/src/frontend/src/lib/components/send/SendTokensList.svelte b/src/frontend/src/lib/components/send/SendTokensList.svelte index 0a2fb06ff0..f10bfcf264 100644 --- a/src/frontend/src/lib/components/send/SendTokensList.svelte +++ b/src/frontend/src/lib/components/send/SendTokensList.svelte @@ -34,7 +34,7 @@ {/each}
{:else} -

+

{$i18n.tokens.manage.text.all_tokens_zero_balance}

{/if} diff --git a/src/frontend/src/lib/components/settings/SettingsVersion.svelte b/src/frontend/src/lib/components/settings/SettingsVersion.svelte index eeed9349a4..d8d0a70baa 100644 --- a/src/frontend/src/lib/components/settings/SettingsVersion.svelte +++ b/src/frontend/src/lib/components/settings/SettingsVersion.svelte @@ -9,7 +9,7 @@ const releaseUrl = `${OISY_REPO_URL}/releases/tag/${version}`; -

+

{OISY_NAME}

{$i18n.tokens.text.all_tokens_with_zero_hidden}

-

{$i18n.tokens.text.buy_or_receive}

+

{$i18n.tokens.text.buy_or_receive}

diff --git a/src/frontend/src/lib/components/tokens/TokenLogo.svelte b/src/frontend/src/lib/components/tokens/TokenLogo.svelte index 8e6e6f357e..361befbe6f 100644 --- a/src/frontend/src/lib/components/tokens/TokenLogo.svelte +++ b/src/frontend/src/lib/components/tokens/TokenLogo.svelte @@ -33,7 +33,7 @@ /> {#if badge?.type === 'tokenCount' && badge.count > 0} diff --git a/src/frontend/src/lib/components/transactions/TransactionModal.svelte b/src/frontend/src/lib/components/transactions/TransactionModal.svelte index e1d9162c99..d4158dd1b9 100644 --- a/src/frontend/src/lib/components/transactions/TransactionModal.svelte +++ b/src/frontend/src/lib/components/transactions/TransactionModal.svelte @@ -7,9 +7,9 @@ import Copy from '$lib/components/ui/Copy.svelte'; import ExternalLink from '$lib/components/ui/ExternalLink.svelte'; import Value from '$lib/components/ui/Value.svelte'; - import { tokenWithFallback } from '$lib/derived/token.derived'; import { i18n } from '$lib/stores/i18n.store'; import { modalStore } from '$lib/stores/modal.store'; + import type { OptionToken } from '$lib/types/token'; import type { TransactionUiCommon } from '$lib/types/transaction'; import { formatSecondsToDate, @@ -21,6 +21,7 @@ export let commonData: TransactionUiCommon; export let hash: string | undefined; export let value: BigNumber | undefined; + export let token: OptionToken; export let typeLabel: string; export let sendToLabel: string | undefined; @@ -113,16 +114,16 @@ {/if} - {#if nonNullish(value)} + {#if nonNullish(value) && nonNullish(token)} {$i18n.core.text.amount} {formatToken({ value, - unitName: $tokenWithFallback.decimals, - displayDecimals: $tokenWithFallback.decimals + unitName: token.decimals, + displayDecimals: token.decimals })} - {$tokenWithFallback.symbol} + {token.symbol} {/if} diff --git a/src/frontend/src/lib/components/transactions/TransactionsPlaceholder.svelte b/src/frontend/src/lib/components/transactions/TransactionsPlaceholder.svelte index 50f34848a5..1951b4c24f 100644 --- a/src/frontend/src/lib/components/transactions/TransactionsPlaceholder.svelte +++ b/src/frontend/src/lib/components/transactions/TransactionsPlaceholder.svelte @@ -25,7 +25,7 @@

{$i18n.transactions.text.transaction_history}

-

+

{$i18n.transactions.text.buy_or_receive}

diff --git a/src/frontend/src/lib/components/ui/ButtonCloseModal.svelte b/src/frontend/src/lib/components/ui/ButtonCloseModal.svelte index 349c0d6409..29e22b344e 100644 --- a/src/frontend/src/lib/components/ui/ButtonCloseModal.svelte +++ b/src/frontend/src/lib/components/ui/ButtonCloseModal.svelte @@ -7,6 +7,6 @@ export let colorStyle: ButtonColorStyle = 'secondary'; - diff --git a/src/frontend/src/lib/components/ui/DropdownButton.svelte b/src/frontend/src/lib/components/ui/DropdownButton.svelte index 10d9e05c6a..784151d937 100644 --- a/src/frontend/src/lib/components/ui/DropdownButton.svelte +++ b/src/frontend/src/lib/components/ui/DropdownButton.svelte @@ -8,7 +8,7 @@