Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
6925ace
feat(demo-wallet): wallet UI redesign (TON-1682)
heyllog Jun 11, 2026
9b5d236
refactor(demo-wallet): remove dead pre-redesign components
heyllog Jun 11, 2026
9db1ec7
refactor(demo-wallet): move shared hooks to core/hooks
heyllog Jun 11, 2026
2ca06da
refactor(demo-wallet): move shared utils to core/utils
heyllog Jun 11, 2026
e98492b
refactor(demo-wallet): move routing to core
heyllog Jun 11, 2026
346a62d
refactor(demo-wallet): move Card, Input, FallbackImage to core/compon…
heyllog Jun 11, 2026
3311ad1
refactor(demo-wallet): move Layout and NewLayout to core/components/s…
heyllog Jun 11, 2026
aa01c7a
refactor(demo-wallet): move lib and infra utils to core/lib
heyllog Jun 11, 2026
63a39e6
refactor(demo-wallet): add notifications feature
heyllog Jun 11, 2026
2f3f187
refactor(demo-wallet): add nft feature
heyllog Jun 11, 2026
0fde86a
refactor(demo-wallet): add jettons feature
heyllog Jun 11, 2026
b0eecf0
refactor(demo-wallet): add send feature
heyllog Jun 11, 2026
3537b23
refactor(demo-wallet): add swap feature
heyllog Jun 11, 2026
06514fd
refactor(demo-wallet): add staking feature
heyllog Jun 11, 2026
4deca5a
refactor(demo-wallet): add transactions feature
heyllog Jun 11, 2026
22aeb4a
refactor(demo-wallet): add ton-connect feature
heyllog Jun 11, 2026
c6c479e
refactor(demo-wallet): add settings feature
heyllog Jun 11, 2026
483c85c
refactor(demo-wallet): add wallets feature
heyllog Jun 11, 2026
906e908
refactor(demo-wallet): add dashboard feature
heyllog Jun 11, 2026
4b5b67a
refactor(demo-wallet): move remaining shared primitives, hooks and ma…
heyllog Jun 11, 2026
c6c91c0
refactor(demo-wallet): retire the flat components barrel
heyllog Jun 11, 2026
4c480ec
refactor(demo-wallet): consolidate Button onto core/components/ui/button
heyllog Jun 11, 2026
b7f7f1a
feat(demo-wallet): redesigned transaction history on the dashboard
heyllog Jun 11, 2026
92cd3b7
feat(demo-wallet): open transaction history rows in the explorer
heyllog Jun 11, 2026
2a16819
feat(demo-wallet): format history amounts with formatUnits + formatLa…
heyllog Jun 11, 2026
e4deb2e
refactor(demo-wallet): remove formatTonForDisplay
heyllog Jun 11, 2026
a614d6e
refactor(demo-wallet): rework amounts display
heyllog Jun 11, 2026
08a3482
refactor(demo-wallet): add assets page
heyllog Jun 11, 2026
c1123b4
refactor(demo-wallet): add nfts page
heyllog Jun 11, 2026
b5da1f5
refactor(demo-wallet): add history page
heyllog Jun 11, 2026
a06f93d
refactor(demo-wallet): rework send page
heyllog Jun 11, 2026
fa05c28
refactor(demo-wallet): rework swap page
heyllog Jun 11, 2026
56d348f
refactor(demo-wallet): replace TON with GRAM
heyllog Jun 11, 2026
b053dc6
refactor(demo-wallet): replace action buttons on the dashboard
heyllog Jun 11, 2026
184278b
refactor(demo-wallet): rework staking page
heyllog Jun 11, 2026
952c2a9
refactor(demo-wallet): fix paste handler with opened connect modal
heyllog Jun 12, 2026
a4d7886
refactor(demo-wallet): remove dead legacy components
heyllog Jun 12, 2026
3eba886
refactor(demo-wallet): redesign ton connect modals
heyllog Jun 12, 2026
49c1c1c
refactor(demo-wallet): redesign ton connect screen
heyllog Jun 12, 2026
5d98331
refactor(demo-wallet): improve error handling
heyllog Jun 12, 2026
56ef7c2
refactor(demo-wallet): save mnemonic before continue
heyllog Jun 12, 2026
b45c940
refactor(demo-wallet): fix for 0 decimals
heyllog Jun 12, 2026
da630d5
refactor(demo-wallet): fix some tests for new UI
heyllog Jun 12, 2026
57e8f61
refactor(demo-wallet): fix all tests
heyllog Jun 14, 2026
1df5e91
refactor(demo-wallet): replace TON with GRAM
heyllog Jun 14, 2026
0614bd6
merge main
heyllog Jun 14, 2026
83c6785
refactor(demo-wallet): fix extension e2e
heyllog Jun 14, 2026
0268f9b
refactor(demo-wallet): fix extension size
heyllog Jun 15, 2026
9bfd797
refactor(walletkit): rework confirm save mnemonic
heyllog Jun 15, 2026
ec2bd67
refactor(demo-wallet): delete kits changes
heyllog Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
48 changes: 21 additions & 27 deletions apps/demo-wallet/e2e/demo-wallet/DemoWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,26 @@ export class DemoWallet extends WalletApp {
throw new Error('[importWallet] mnemonic is required setup WALLET_MNEMONIC');
}
const app = await this.open();

// Welcome → "Add an existing wallet" → "Recovery phrase"
await app.getByTestId('welcome-add-existing').click();
await app.getByTestId('add-wallet-import').click();

// Setup password
await app.getByTestId('title').filter({ hasText: 'Setup Password', visible: true });
await app.getByTestId('subtitle').filter({ hasText: 'Create Password', visible: true });
await app.getByTestId('password').fill(this.password);
await app.getByTestId('password-confirm').fill(this.password);
await app.getByTestId('password-submit').click();

// Navigate to Import tab
await app.getByTestId('title').filter({ hasText: 'Setup Wallet' }).waitFor({ state: 'visible' });
await app.getByTestId('tab-import').click();
await app.getByTestId('subtitle').filter({ hasText: 'Import Wallet', visible: true });

// Select mainnet
// Import wallet screen: select mainnet, paste the phrase, continue
await app.getByTestId('network-select-mainnet').click();

// Paste mnemonic using clipboard
await app.evaluate(async (m) => {
await navigator.clipboard.writeText(m);
}, mnemonic);
await app.getByTestId('paste-mnemonic').click();

// Import wallet
await app.getByTestId('import-wallet-process').click();
await app.getByTestId('title').filter({ hasText: 'TON Wallet' }).waitFor({ state: 'attached' });

// Wait for the dashboard (the settings button only exists there)
await app.getByTestId('wallet-menu').waitFor({ state: 'visible' });

// Disable auto-lock and hold-to-sign for e2e tests
await app.getByTestId('wallet-menu').click();
Expand All @@ -64,6 +60,8 @@ export class DemoWallet extends WalletApp {
async connectBy(url: string, shouldSkipConnect: boolean = false, confirm: boolean = true): Promise<void> {
const app = await this.open();
await delay(500);
// Open the "Connect to dApp" modal, then paste the TON Connect link.
await app.getByTestId('connect-dapp-button').click();
await app.getByTestId('tonconnect-url').fill(url);
await app.getByTestId('tonconnect-process').click();

Expand All @@ -79,22 +77,22 @@ export class DemoWallet extends WalletApp {
return;
}

const modal = app.getByTestId('request').filter({ hasText: 'Connect Request' });
const modal = app.getByTestId('connect-request');
await modal.waitFor({ state: 'visible' });
const chose = app.getByTestId(confirm ? 'connect-approve' : 'connect-reject');

await chose.waitFor({ state: 'attached' });
await chose.waitFor({ state: 'visible' });
await chose.click();
await modal.waitFor({ state: 'detached' });
await this.close();
}

async signData(confirm: boolean = true): Promise<void> {
const app = await this.open();
const modal = app.getByTestId('request').filter({ hasText: 'Sign Data Request' });
const modal = app.getByTestId('sign-data-request');
await modal.waitFor({ state: 'visible' });
const chose = app.getByTestId(confirm ? 'sign-data-approve' : 'sign-data-reject');
await chose.waitFor({ state: 'attached' });
await chose.waitFor({ state: 'visible' });
await chose.click();
await modal.waitFor({ state: 'detached' });
await this.close();
Expand All @@ -110,10 +108,10 @@ export class DemoWallet extends WalletApp {

async accept(confirm: boolean = true): Promise<void> {
const app = await this.open();
const modal = app.getByTestId('request').filter({ hasText: 'Transaction Request' });
const modal = app.getByTestId('transaction-request');
await modal.waitFor({ state: 'visible' });
const chose = app.getByTestId(confirm ? 'send-transaction-approve' : 'send-transaction-reject');
await chose.waitFor({ state: 'attached' });
await chose.waitFor({ state: 'visible' });
await chose.click();
await modal.waitFor({ state: 'detached' });
await this.close();
Expand All @@ -133,19 +131,15 @@ export class DemoWallet extends WalletApp {
await app.getByTestId('use-my-address').click();

// Fill in amount
await app.getByTestId('amount-input').fill(amount);
await app.getByTestId('send-amount-input').fill(amount);

// Click send button
await app.getByTestId('send-submit').click();

// Wait for transaction request modal
const modal = app.getByTestId('request').filter({ hasText: 'Transaction Request' });
await modal.waitFor({ state: 'visible' });

// Approve or reject
// Wait for the transaction request modal (anchored on its type-specific action button)
const chose = app.getByTestId(confirm ? 'send-transaction-approve' : 'send-transaction-reject');
await chose.waitFor({ state: 'attached' });
await chose.waitFor({ state: 'visible' });
await chose.click();
await modal.waitFor({ state: 'detached' });
await chose.waitFor({ state: 'detached' });
}
}
5 changes: 3 additions & 2 deletions apps/demo-wallet/e2e/localSendTransaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ test.describe('Local Send Transaction', () => {
// Send TON locally to own address and approve
await wallet.sendTonToSelf('0.001', true);

// Verify we're back on wallet page (transaction was processed)
// Verify we're back on the dashboard (transaction was processed). The settings
// button only exists on the wallet dashboard, so it's a stable anchor.
const app = await wallet.open();
await expect(app.getByTestId('title').filter({ hasText: 'TON Wallet' })).toBeVisible({ timeout: 10000 });
await expect(app.getByTestId('wallet-menu')).toBeVisible({ timeout: 10000 });

await wallet.close();
});
Expand Down
10 changes: 3 additions & 7 deletions apps/demo-wallet/e2e/pages/SetupPasswordPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ export class SetupPasswordPage {

// Locators

get title() {
return this.page.getByTestId('title').filter({ hasText: 'Setup Password' });
}

get subtitle() {
return this.page.getByTestId('subtitle');
}
Expand All @@ -34,17 +30,17 @@ export class SetupPasswordPage {
}

get helperText() {
return this.page.getByText('At least 4 characters');
return this.page.getByText('Make sure to remember', { exact: false });
}

get errorMessage() {
return this.page.locator('[data-testid="password-error"], .text-red-600');
return this.page.locator('[data-testid="password-error"], .text-red-500');
}

// Actions

async waitForPage() {
await this.title.waitFor({ state: 'visible' });
await this.passwordInput.waitFor({ state: 'visible' });
}

async fillPassword(password: string) {
Expand Down
53 changes: 50 additions & 3 deletions apps/demo-wallet/e2e/pages/SetupWalletPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,61 @@

import type { Page } from '@playwright/test';

/**
* The screen reached right after setting a password for the "create" path —
* the redesigned "Recovery phrase" (create-wallet) screen.
*/
export class SetupWalletPage {
/** Hold-to-continue gesture duration on the save-phrase confirmation modal (ms). */
private static readonly HOLD_DURATION = 1500;

constructor(private readonly page: Page) {}

get title() {
return this.page.getByTestId('title').filter({ hasText: 'Setup Wallet' });
get revealButton() {
return this.page.getByTestId('reveal-mnemonic');
}

get continueButton() {
return this.page.getByTestId('create-wallet-confirm');
}

/** The "Hold to continue" button inside the save-phrase confirmation modal. */
get holdToContinue() {
return this.page.getByTestId('save-phrase-hold');
}

async waitForPage() {
await this.title.waitFor({ state: 'visible' });
await this.revealButton.waitFor({ state: 'visible' });
}

/** Open the save-phrase confirmation modal. */
async openConfirm() {
await this.continueButton.click();
await this.holdToContinue.waitFor({ state: 'visible' });
}

/**
* Press and hold the button for `ms`, then release. The gesture is wall-clock
* timed in the component, so the press must really last that long.
*/
private async pressHold(ms: number) {
await this.holdToContinue.hover();
await this.page.mouse.down();
await this.page.waitForTimeout(ms);
await this.page.mouse.up();
}

/** Open the modal and complete the hold-to-continue gesture to create the wallet. */
async confirmAndCreate() {
await this.openConfirm();
// Hold past the gesture duration plus the completion delay so onComplete fires.
await this.pressHold(SetupWalletPage.HOLD_DURATION + 700);
}

/** A short tap on the hold button — not long enough to confirm. */
async tapHold() {
await this.holdToContinue.hover();
await this.page.mouse.down();
await this.page.mouse.up();
}
}
21 changes: 0 additions & 21 deletions apps/demo-wallet/e2e/pages/UnlockWalletPage.ts

This file was deleted.

1 change: 0 additions & 1 deletion apps/demo-wallet/e2e/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@

export { SetupPasswordPage } from './SetupPasswordPage';
export { SetupWalletPage } from './SetupWalletPage';
export { UnlockWalletPage } from './UnlockWalletPage';
72 changes: 16 additions & 56 deletions apps/demo-wallet/e2e/ui-tests/importWallet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import type { NetworkType } from '@demo/wallet-core';

import { testWithUIFixture } from './UITestFixture';
Expand Down Expand Up @@ -39,102 +40,68 @@ const testMatrix: ImportWalletTestCase[] = [
{ network: 'testnet', version: 'v5r1', interfaceType: 'signer' },
];

/** Welcome → "Add an existing wallet" → "Recovery phrase" → set a password → land on the import screen. */
async function openImportScreen(page: Page): Promise<void> {
await page.getByTestId('welcome-add-existing').click();
await page.getByTestId('add-wallet-import').click();
await page.getByTestId('password').fill(TEST_PASSWORD);
await page.getByTestId('password-confirm').fill(TEST_PASSWORD);
await page.getByTestId('password-submit').click();
await page.getByTestId('paste-mnemonic').waitFor({ state: 'visible' });
}

test.describe('Import Wallet Flow', () => {
test.beforeEach(async ({ page }) => {
if (!TEST_MNEMONIC) {
test.skip(true, 'WALLET_MNEMONIC environment variable is required');
}

// Setup password first
await page.getByTestId('title').filter({ hasText: 'Setup Password' }).waitFor({ state: 'visible' });
await page.getByTestId('subtitle').filter({ hasText: 'Create Password' }).waitFor({ state: 'visible' });
await page.getByTestId('password').fill(TEST_PASSWORD);
await page.getByTestId('password-confirm').fill(TEST_PASSWORD);
await page.getByTestId('password-submit').click();

// Wait for setup wallet page - Layout title is "Setup Wallet"
await page.getByTestId('title').filter({ hasText: 'Setup Wallet' }).waitFor({ state: 'visible' });

// Click on "Import" tab
await page.getByTestId('tab-import').click();
await page.getByTestId('subtitle').filter({ hasText: 'Import Wallet' }).waitFor({ state: 'visible' });
await openImportScreen(page);
});

for (const testCase of testMatrix) {
const testName = `Import wallet - ${testCase.network} / ${testCase.version} / ${testCase.interfaceType}`;

test(testName, async ({ page }) => {
// Select network
await page.getByTestId(`network-select-${testCase.network}`).click();

// Verify network button is enabled
await expect(page.getByTestId(`network-select-${testCase.network}`)).toBeEnabled();

// Select wallet version
await page.getByTestId(`version-select-${testCase.version}`).click();

// Verify version button is enabled
await expect(page.getByTestId(`version-select-${testCase.version}`)).toBeEnabled();

// Select interface type
await page.getByTestId(`interface-select-${testCase.interfaceType}`).click();

// Verify interface button is enabled
await expect(page.getByTestId(`interface-select-${testCase.interfaceType}`)).toBeEnabled();

// Paste mnemonic using the Paste button
// Paste the recovery phrase via the Paste button (reads the clipboard).
await page.evaluate(async (mnemonic) => {
await navigator.clipboard.writeText(mnemonic);
}, TEST_MNEMONIC);

// Click the Paste button
await page.getByTestId('paste-mnemonic').click();

// Wait for words to be filled
await page.waitForTimeout(500);

// Click Import Wallet button
await page.getByTestId('import-wallet-process').click();

// Verify navigation to wallet dashboard
await page.getByTestId('title').filter({ hasText: 'TON Wallet' }).waitFor({ state: 'visible' });
// The settings button only exists on the wallet dashboard.
await expect(page.getByTestId('wallet-menu')).toBeVisible();
});
}
});

test.describe('Import Wallet - Validation', () => {
test.beforeEach(async ({ page }) => {
// Setup password first
await page.getByTestId('title').filter({ hasText: 'Setup Password' }).waitFor({ state: 'visible' });
await page.getByTestId('subtitle').filter({ hasText: 'Create Password' }).waitFor({ state: 'visible' });
await page.getByTestId('password').fill(TEST_PASSWORD);
await page.getByTestId('password-confirm').fill(TEST_PASSWORD);
await page.getByTestId('password-submit').click();

// Wait for setup wallet page - Layout title is "Setup Wallet"
await page.getByTestId('title').filter({ hasText: 'Setup Wallet' }).waitFor({ state: 'visible' });

// Click on "Import" tab
await page.getByTestId('tab-import').click();
await page.getByTestId('subtitle').filter({ hasText: 'Import Wallet' }).waitFor({ state: 'visible' });
await openImportScreen(page);
});

test('Import button is disabled with no mnemonic', async ({ page }) => {
// The Import Wallet button should be disabled without mnemonic
await expect(page.getByTestId('import-wallet-process')).toBeDisabled();
});

test('Import button is disabled with less than 12 words', async ({ page }) => {
// Fill only 10 words
const testWords = 'word1 word2 word3 word4 word5 word6 word7 word8 word9 word10';
await page.evaluate(async (mnemonic) => {
await navigator.clipboard.writeText(mnemonic);
}, testWords);

await page.getByTestId('paste-mnemonic').click();
await page.waitForTimeout(300);

// The Import Wallet button should be disabled
await expect(page.getByTestId('import-wallet-process')).toBeDisabled();
});

Expand All @@ -143,21 +110,14 @@ test.describe('Import Wallet - Validation', () => {
test.skip(true, 'WALLET_MNEMONIC environment variable is required');
}

// Paste mnemonic
await page.evaluate(async (mnemonic) => {
await navigator.clipboard.writeText(mnemonic);
}, TEST_MNEMONIC);

await page.getByTestId('paste-mnemonic').click();
await page.waitForTimeout(300);

// Click Clear button
await page.getByTestId('clear-mnemonic').click();

// Verify all inputs are empty (word count should be 0)
await expect(page.getByTestId('word-count')).toHaveText('0/24 words');

// Import button should be disabled
await expect(page.getByTestId('import-wallet-process')).toBeDisabled();
});
});
Loading
Loading