diff --git a/Makefile b/Makefile index 3191870e..7cd74861 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ include .env export ELECTROBUN_DEVELOPER_ID ELECTROBUN_TEAMID ELECTROBUN_APPLEID ELECTROBUN_APPLEIDPASS endif -.PHONY: install dev dev-hmr build build-stable build-canary build-signed prune-bundle dmg clean help vault sign-check verify publish release upload-dmg submodules modules-install modules-build modules-clean audit build-zcash-cli build-zcash-cli-debug +.PHONY: install dev dev-hmr build build-stable build-canary build-signed prune-bundle dmg clean help vault sign-check verify publish release upload-dmg submodules modules-install modules-build modules-clean audit build-zcash-cli build-zcash-cli-debug test test-rest # --- Submodules (auto-init on fresh worktrees/clones) --- @@ -33,6 +33,13 @@ modules-clean: build-zcash-cli: cd $(PROJECT_DIR)/zcash-cli && cargo build --release +ifdef ELECTROBUN_DEVELOPER_ID + @echo "Signing zcash-cli binary..." + codesign --force --verbose --timestamp \ + --sign "Developer ID Application: $(ELECTROBUN_DEVELOPER_ID) ($(ELECTROBUN_TEAMID))" \ + --options runtime \ + $(PROJECT_DIR)/zcash-cli/target/release/zcash-cli +endif build-zcash-cli-debug: cd $(PROJECT_DIR)/zcash-cli && cargo build @@ -55,7 +62,7 @@ dev-hmr: install build: install cd $(PROJECT_DIR) && bun run build -build-stable: install +build-stable: install build-zcash-cli cd $(PROJECT_DIR) && bun run build:stable build-canary: install @@ -103,6 +110,13 @@ dmg: xcrun stapler staple "$$DMG_OUT"; \ echo "DMG ready: $$DMG_OUT" +# --- Testing --- + +test: test-rest + +test-rest: + cd $(PROJECT_DIR) && bun test __tests__/rest-api.test.ts + clean: modules-clean cd $(PROJECT_DIR) && rm -rf dist node_modules build _build artifacts @@ -200,4 +214,6 @@ help: @echo " make publish - Show distribution artifacts" @echo " make release - Build, sign, and create new GitHub release" @echo " make upload-dmg - Upload signed DMG to existing CI draft release" + @echo " make test - Run all tests" + @echo " make test-rest - Run REST API integration tests (requires running vault)" @echo " make clean - Remove all build artifacts and node_modules" diff --git a/firmware/manifest.json b/firmware/manifest.json index 6c459184..50703e80 100644 --- a/firmware/manifest.json +++ b/firmware/manifest.json @@ -41,15 +41,30 @@ }, "unsigned_firmware": { "solana": { - "version": "7.10.0-solana", - "filename": "firmware.keepkey.solana-8acfd898.bin", - "sha256_payload": "8ad2ecd35ad0d9714a3592edfaa6343c0c7c63dc33677907cd8eb9ffb4f8bea7", - "sha256_full": "0906d9343c1a971b069715d84b78b4fce4ff4ba095e2b63ca7de18e6dd60a686", - "note": "Unsigned firmware with Solana (Ed25519) support — requires bootloader policy override" + "version": "7.14.0-solana", + "filename": "firmware.keepkey.solana-7.11.0.bin", + "sha256_full": "75dc509a90f70a1f0025a9c5451532f407c4f78df5ef7d1d7b9b2ef9b9740e19", + "note": "Unsigned firmware with Solana (Ed25519) support" + }, + "bip85": { + "version": "7.14.0-bip85", + "filename": "firmware.keepkey.bip85-7.11.0-d54797ee.bin", + "sha256_full": "02def01217709269ff39c2c4745179e45c9c6892b19299de6cdd2f2becfddc87", + "note": "Unsigned firmware with BIP-85, Solana, Tron, TON, Zcash support" + }, + "zcash": { + "version": "7.14.0-zcash", + "filename": "firmware.keepkey.zcash-spendauth-d54797ee.bin", + "sha256_full": "8ed86d578019f127998215268b75165b92c52948647a8b31949e4f311d695d3d", + "note": "Unsigned firmware with Zcash Orchard shielded transaction support" } }, "firmware_hashes": { - "8ad2ecd35ad0d9714a3592edfaa6343c0c7c63dc33677907cd8eb9ffb4f8bea7": "7.10.0-solana", + "75dc509a90f70a1f0025a9c5451532f407c4f78df5ef7d1d7b9b2ef9b9740e19": "7.14.0-solana", + "02def01217709269ff39c2c4745179e45c9c6892b19299de6cdd2f2becfddc87": "7.14.0-bip85", + "8ed86d578019f127998215268b75165b92c52948647a8b31949e4f311d695d3d": "7.14.0-zcash", + "84c94eb69193d8bdf6be48992b852f25c3b30f0ea133019491849825137aca2f": "7.14.0-zcash-askneg", + "cab8446a1d17dd847938476be282e6e45041cece750afcdb089f63fa4ba5a248": "7.14.0-zcash-widereduction", "958764cf3baa53eec0002eab9c54e02ce6f5fdab71e7efbbe723f958e26ff419": "7.10.0", "24cca93ef5e7907dc6d8405b8ab9800d4e072dd9259138cf7679107985b88137": "7.9.3", "9e691874bb6966aa0616d36b60489b82fab166d96e5166518eaa3e11468bf6a8": "7.9.1", diff --git a/firmware/releases.json b/firmware/releases.json index 013d2abb..e64998f0 100644 --- a/firmware/releases.json +++ b/firmware/releases.json @@ -54,6 +54,11 @@ "fe98454e7ebd4aef4a6db5bd4c60f52cf3f58b974283a7c1e1fcc5fea02cf3eb": "v2.1.4" }, "firmware": { + "75dc509a90f70a1f0025a9c5451532f407c4f78df5ef7d1d7b9b2ef9b9740e19": "v7.14.0-solana", + "02def01217709269ff39c2c4745179e45c9c6892b19299de6cdd2f2becfddc87": "v7.14.0-bip85", + "8ed86d578019f127998215268b75165b92c52948647a8b31949e4f311d695d3d": "v7.14.0-zcash", + "84c94eb69193d8bdf6be48992b852f25c3b30f0ea133019491849825137aca2f": "v7.14.0-zcash-askneg", + "cab8446a1d17dd847938476be282e6e45041cece750afcdb089f63fa4ba5a248": "v7.14.0-zcash-widereduction", "958764cf3baa53eec0002eab9c54e02ce6f5fdab71e7efbbe723f958e26ff419": "v7.10.0", "24cca93ef5e7907dc6d8405b8ab9800d4e072dd9259138cf7679107985b88137": "v7.9.3", "9e691874bb6966aa0616d36b60489b82fab166d96e5166518eaa3e11468bf6a8": "v7.9.1", diff --git a/projects/keepkey-sdk/lib/index.d.ts b/projects/keepkey-sdk/lib/index.d.ts index e8ef8011..55148cc8 100644 --- a/projects/keepkey-sdk/lib/index.d.ts +++ b/projects/keepkey-sdk/lib/index.d.ts @@ -1,5 +1,5 @@ import { VaultClient } from './client'; -import type { SdkConfig, DeviceFeatures, DeviceInfo, SignedTx, AddressRequest, EthSignTxParams, EthSignTypedDataParams, EthSignMessageParams, EthVerifyMessageParams, BtcSignTxParams, CosmosAminoSignParams, XrpSignTxParams, BnbSignTxParams, SolanaSignTxParams, GetPublicKeyRequest, BatchPubkeysPath, ApplySettingsParams, HealthResponse, SupportedAsset } from './types'; +import type { SdkConfig, DeviceFeatures, DeviceInfo, SignedTx, AddressRequest, EthSignTxParams, EthSignTypedDataParams, EthSignMessageParams, EthVerifyMessageParams, BtcSignTxParams, CosmosAminoSignParams, XrpSignTxParams, BnbSignTxParams, SolanaSignTxParams, TronSignTxParams, TonSignTxParams, GetPublicKeyRequest, BatchPubkeysPath, ApplySettingsParams, HealthResponse, SupportedAsset } from './types'; export { SdkError } from './client'; export * from './types'; export declare class KeepKeySdk { @@ -124,6 +124,12 @@ export declare class KeepKeySdk { solanaGetAddress: (params: AddressRequest) => Promise<{ address: string; }>; + tronGetAddress: (params: AddressRequest) => Promise<{ + address: string; + }>; + tonGetAddress: (params: AddressRequest) => Promise<{ + address: string; + }>; }; eth: { ethSignTransaction: (params: EthSignTxParams) => Promise; @@ -180,6 +186,12 @@ export declare class KeepKeySdk { solana: { solanaSignTransaction: (params: SolanaSignTxParams) => Promise; }; + tron: { + tronSignTransaction: (params: TronSignTxParams) => Promise; + }; + ton: { + tonSignTransaction: (params: TonSignTxParams) => Promise; + }; xpub: { getPublicKey: (params: GetPublicKeyRequest) => Promise<{ xpub: string; diff --git a/projects/keepkey-sdk/lib/index.d.ts.map b/projects/keepkey-sdk/lib/index.d.ts.map index 4096b2f2..658226bf 100644 --- a/projects/keepkey-sdk/lib/index.d.ts.map +++ b/projects/keepkey-sdk/lib/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/projects/keepkey-sdk/lib/index.js b/projects/keepkey-sdk/lib/index.js index ae4c08a3..933a0aa6 100644 --- a/projects/keepkey-sdk/lib/index.js +++ b/projects/keepkey-sdk/lib/index.js @@ -67,6 +67,8 @@ class KeepKeySdk { // v1 SDK compat alias binanceGetAddress: (params) => this.client.post('/addresses/bnb', params), solanaGetAddress: (params) => this.client.post('/addresses/solana', params), + tronGetAddress: (params) => this.client.post('/addresses/tron', params), + tonGetAddress: (params) => this.client.post('/addresses/ton', params), }; // ═══════════════════════════════════════════════════════════════════ // eth — Ethereum signing @@ -154,6 +156,18 @@ class KeepKeySdk { solanaSignTransaction: (params) => this.client.post('/solana/sign-transaction', params), }; // ═══════════════════════════════════════════════════════════════════ + // tron — Tron signing + // ═══════════════════════════════════════════════════════════════════ + this.tron = { + tronSignTransaction: (params) => this.client.post('/tron/sign-transaction', params), + }; + // ═══════════════════════════════════════════════════════════════════ + // ton — TON signing + // ═══════════════════════════════════════════════════════════════════ + this.ton = { + tonSignTransaction: (params) => this.client.post('/ton/sign-transaction', params), + }; + // ═══════════════════════════════════════════════════════════════════ // xpub — public key operations (batch + single) // ═══════════════════════════════════════════════════════════════════ this.xpub = { diff --git a/projects/keepkey-sdk/lib/index.js.map b/projects/keepkey-sdk/lib/index.js.map index 9befb033..de6cb77b 100644 --- a/projects/keepkey-sdk/lib/index.js.map +++ b/projects/keepkey-sdk/lib/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/projects/keepkey-sdk/lib/types.d.ts b/projects/keepkey-sdk/lib/types.d.ts index 988969be..b188956b 100644 --- a/projects/keepkey-sdk/lib/types.d.ts +++ b/projects/keepkey-sdk/lib/types.d.ts @@ -121,6 +121,18 @@ export interface SolanaSignTxParams { addressNList?: number[]; raw_tx: string; } +export interface TronSignTxParams { + addressNList: number[]; + from: string; + to: string; + amount: number; + memo?: string; +} +export interface TonSignTxParams { + address_n?: number[]; + addressNList?: number[]; + raw_tx: string; +} export interface GetPublicKeyRequest { address_n: number[]; ecdsa_curve_name?: string; diff --git a/projects/keepkey-sdk/lib/types.d.ts.map b/projects/keepkey-sdk/lib/types.d.ts.map index 0193422d..c3ae69fa 100644 --- a/projects/keepkey-sdk/lib/types.d.ts.map +++ b/projects/keepkey-sdk/lib/types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,SAAS;IACxB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iDAAiD;IACjD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,0CAA0C;IAC1C,WAAW,CAAC,EAAE;QACZ,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,GAAG,CAAC,EAAE,MAAM,CAAA;KACb,CAAA;CACF;AAGD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,eAAe,EAAE,OAAO,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,OAAO,CAAA;IACvB,qBAAqB,EAAE,OAAO,CAAA;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,OAAO,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;IACnB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,QAAQ,EAAE,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IAC1D,KAAK,EAAE,MAAM,CAAA;IACb,gBAAgB,EAAE,MAAM,CAAA;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,OAAO,CAAA;IAClB,oBAAoB,EAAE,OAAO,CAAA;IAC7B,kBAAkB,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;CACnC;AAED,MAAM,WAAW,QAAQ;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;CAChB;AAGD,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAGD,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,GAAG,CAAA;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAGD,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,GAAG,EAAE,CAAA;IACb,OAAO,EAAE,GAAG,EAAE,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAGD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,GAAG,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;CACtB;AAGD,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAGD,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAGD,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;CACf;AAGD,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAGD,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;CACf;AAGD,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAGD,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAGD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,OAAO,CAAA;IAClB,gBAAgB,EAAE,OAAO,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;CACpB"} \ No newline at end of file diff --git a/projects/keepkey-sdk/package.json b/projects/keepkey-sdk/package.json index 148571e4..99f4c9b4 100644 --- a/projects/keepkey-sdk/package.json +++ b/projects/keepkey-sdk/package.json @@ -1,6 +1,6 @@ { "name": "keepkey-vault-sdk", - "version": "2.0.2", + "version": "2.0.3", "description": "TypeScript SDK for KeepKey Vault REST API — zero dependencies, native fetch", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/projects/keepkey-sdk/src/index.ts b/projects/keepkey-sdk/src/index.ts index 390027c3..3755c878 100644 --- a/projects/keepkey-sdk/src/index.ts +++ b/projects/keepkey-sdk/src/index.ts @@ -14,6 +14,8 @@ import type { XrpSignTxParams, BnbSignTxParams, SolanaSignTxParams, + TronSignTxParams, + TonSignTxParams, GetPublicKeyRequest, BatchPubkeysPath, ApplySettingsParams, @@ -235,6 +237,12 @@ export class KeepKeySdk { solanaGetAddress: (params: AddressRequest): Promise<{ address: string }> => this.client.post('/addresses/solana', params), + + tronGetAddress: (params: AddressRequest): Promise<{ address: string }> => + this.client.post('/addresses/tron', params), + + tonGetAddress: (params: AddressRequest): Promise<{ address: string }> => + this.client.post('/addresses/ton', params), } // ═══════════════════════════════════════════════════════════════════ @@ -396,6 +404,22 @@ export class KeepKeySdk { this.client.post('/solana/sign-transaction', params), } + // ═══════════════════════════════════════════════════════════════════ + // tron — Tron signing + // ═══════════════════════════════════════════════════════════════════ + tron = { + tronSignTransaction: (params: TronSignTxParams): Promise => + this.client.post('/tron/sign-transaction', params), + } + + // ═══════════════════════════════════════════════════════════════════ + // ton — TON signing + // ═══════════════════════════════════════════════════════════════════ + ton = { + tonSignTransaction: (params: TonSignTxParams): Promise => + this.client.post('/ton/sign-transaction', params), + } + // ═══════════════════════════════════════════════════════════════════ // xpub — public key operations (batch + single) // ═══════════════════════════════════════════════════════════════════ diff --git a/projects/keepkey-sdk/src/types.ts b/projects/keepkey-sdk/src/types.ts index c758c2f6..5d78ce23 100644 --- a/projects/keepkey-sdk/src/types.ts +++ b/projects/keepkey-sdk/src/types.ts @@ -142,6 +142,22 @@ export interface SolanaSignTxParams { raw_tx: string // base64-encoded raw transaction } +// ── Tron Types ───────────────────────────────────────────────────── +export interface TronSignTxParams { + addressNList: number[] + from: string + to: string + amount: number // amount in sun (1 TRX = 1,000,000 sun) + memo?: string +} + +// ── TON Types ────────────────────────────────────────────────────── +export interface TonSignTxParams { + address_n?: number[] + addressNList?: number[] + raw_tx: string // base64 or hex encoded raw transaction +} + // ── Public Key Types ──────────────────────────────────────────────── export interface GetPublicKeyRequest { address_n: number[] diff --git a/projects/keepkey-vault/scripts/collect-externals.ts b/projects/keepkey-vault/scripts/collect-externals.ts index 244f42c3..60df4876 100644 --- a/projects/keepkey-vault/scripts/collect-externals.ts +++ b/projects/keepkey-vault/scripts/collect-externals.ts @@ -246,7 +246,7 @@ if (existsSync(keepkeyDir)) { if (existsSync(nestedNm)) { const result = Bun.spawnSync(['du', '-sk', nestedNm]) const kb = parseInt(result.stdout.toString().split('\t')[0] || '0', 10) - rmSync(nestedNm, { recursive: true }) + try { rmSync(nestedNm, { recursive: true }) } catch { /* already removed */ } strippedKK += kb } } diff --git a/projects/keepkey-vault/src/bun/db.ts b/projects/keepkey-vault/src/bun/db.ts index a0c60c64..932ed53b 100644 --- a/projects/keepkey-vault/src/bun/db.ts +++ b/projects/keepkey-vault/src/bun/db.ts @@ -212,6 +212,11 @@ export function initDb() { for (const col of ['explorer_address_link TEXT', 'explorer_tx_link TEXT']) { try { db.exec(`ALTER TABLE custom_chains ADD COLUMN ${col}`) } catch { /* already exists */ } } + // Activity tracking columns on api_log (sign/broadcast ops) + for (const col of ['txid TEXT', 'chain TEXT', 'activity_type TEXT']) { + try { db.exec(`ALTER TABLE api_log ADD COLUMN ${col}`) } catch { /* already exists */ } + } + try { db.exec(`CREATE INDEX IF NOT EXISTS idx_api_log_activity ON api_log(activity_type)`) } catch { /* already exists */ } console.log(`[db] SQLite cache ready at ${dbPath}`) } catch (e: any) { @@ -278,6 +283,21 @@ export function setCachedBalances(deviceId: string, balances: ChainBalance[]) { } } +/** Update a single chain's cached balance (upsert). */ +export function updateCachedBalance(deviceId: string, balance: ChainBalance) { + try { + if (!db) return + const tokensJson = balance.tokens && balance.tokens.length > 0 ? JSON.stringify(balance.tokens) : null + db.run( + `INSERT OR REPLACE INTO balances (device_id, chain_id, symbol, balance, balance_usd, address, tokens_json, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [deviceId, balance.chainId, balance.symbol, balance.balance, balance.balanceUsd, balance.address, tokensJson, Date.now()] + ) + } catch (e: any) { + console.warn('[db] updateCachedBalance failed:', e.message) + } +} + export function clearBalances(deviceId?: string) { try { if (!db) return @@ -511,8 +531,8 @@ export function insertApiLog(entry: ApiLogEntry) { try { if (!db) return db.run( - `INSERT INTO api_log (method, route, timestamp, duration_ms, status, app_name, image_url, request_body, response_body) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + `INSERT INTO api_log (method, route, timestamp, duration_ms, status, app_name, image_url, request_body, response_body, txid, chain, activity_type) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ entry.method, entry.route, @@ -523,6 +543,9 @@ export function insertApiLog(entry: ApiLogEntry) { entry.imageUrl || null, entry.requestBody ? JSON.stringify(entry.requestBody) : null, entry.responseBody ? JSON.stringify(entry.responseBody) : null, + entry.txid || null, + entry.chain || null, + entry.activityType || null, ] ) // Periodic prune (every ~100 inserts, check if over limit) @@ -574,6 +597,114 @@ export function clearApiLogs() { } +// ── Recent Activity (unified from api_log + swap_history) ───────────── + +import type { RecentActivity, ActivityType } from '../shared/types' + +/** Check if a txid already exists in api_log */ +export function apiLogTxidExists(txid: string): boolean { + try { + if (!db) return false + const row = db.query('SELECT 1 FROM api_log WHERE txid = ? LIMIT 1').get(txid) + return !!row + } catch { return false } +} + +/** Update response_body metadata for an existing api_log entry by txid (used to refresh confirmation counts) */ +export function updateApiLogTxMeta(txid: string, meta: Record) { + try { + if (!db) return + db.run('UPDATE api_log SET response_body = ? WHERE txid = ?', [JSON.stringify(meta), txid]) + } catch (e: any) { + console.warn('[db] updateApiLogTxMeta failed:', e.message) + } +} + +const VALID_ACTIVITY_TYPES = new Set(['send', 'receive', 'swap', 'sign', 'message', 'approve', 'broadcast']) + +/** Query api_log entries that have activity_type set + swap_history, merged by timestamp */ +export function getRecentActivityFromLog(limit = 50, chainFilter?: string): RecentActivity[] { + try { + if (!db) return [] + + // Build query with optional chain filter + let logSql = `SELECT id, txid, chain, activity_type, app_name, timestamp, route, method, response_body + FROM api_log WHERE activity_type IS NOT NULL` + const logParams: any[] = [] + if (chainFilter) { + logSql += ` AND chain = ?` + logParams.push(chainFilter) + } + logSql += ` ORDER BY timestamp DESC LIMIT ?` + logParams.push(limit) + + const logRows = db.query(logSql).all(...logParams) as Array<{ + id: number; txid: string | null; chain: string | null; activity_type: string; + app_name: string; timestamp: number; route: string; method: string; response_body: string | null + }> + + const logActivities: RecentActivity[] = logRows.map(r => { + // Parse tx metadata from response_body (stored by scan) + let meta: any = null + if (r.response_body) { try { meta = JSON.parse(r.response_body) } catch {} } + return { + id: String(r.id), + txid: r.txid || undefined, + chain: r.chain || '?', + type: (VALID_ACTIVITY_TYPES.has(r.activity_type) ? (r.activity_type === 'broadcast' ? 'send' : r.activity_type) : 'sign') as ActivityType, + source: (r.method === 'RPC' ? 'app' : 'api') as 'app' | 'api', + appName: r.method === 'RPC' ? undefined : r.app_name, + status: r.activity_type === 'broadcast' || r.activity_type === 'swap' ? 'broadcast' : 'signed', + createdAt: r.timestamp, + confirmations: meta?.confirmations ?? undefined, + blockHeight: meta?.blockHeight ?? undefined, + amount: meta?.value ?? undefined, + fee: meta?.fee ?? undefined, + } + }) + + // Swap history entries (dedupe by txid against logActivities) + let swapSql = `SELECT id, txid, from_symbol, to_symbol, from_chain_id, from_amount, status, created_at + FROM swap_history` + const swapParams: any[] = [] + if (chainFilter) { + // Match swap by from_symbol (chain filter is a symbol like 'BTC') + swapSql += ` WHERE from_symbol = ?` + swapParams.push(chainFilter) + } + swapSql += ` ORDER BY created_at DESC LIMIT ?` + swapParams.push(limit) + + const swapRows = db.query(swapSql).all(...swapParams) as Array<{ + id: string; txid: string; from_symbol: string; to_symbol: string; + from_chain_id: string; from_amount: string; status: string; created_at: number + }> + + const logTxids = new Set(logActivities.filter(a => a.txid).map(a => a.txid)) + const swapActivities: RecentActivity[] = swapRows + .filter(r => !logTxids.has(r.txid)) + .map(r => ({ + id: r.id, + txid: r.txid, + chain: r.from_symbol, + chainId: r.from_chain_id, + type: 'swap' as const, + source: 'app' as const, + amount: r.from_amount, + asset: `${r.from_symbol}\u2192${r.to_symbol}`, + status: r.status === 'completed' ? 'broadcast' as const : r.status === 'failed' ? 'failed' as const : 'broadcast' as const, + createdAt: r.created_at, + })) + + return [...logActivities, ...swapActivities] + .sort((a, b) => b.createdAt - a.createdAt) + .slice(0, limit) + } catch (e: any) { + console.warn('[db] getRecentActivityFromLog failed:', e.message) + return [] + } +} + // ── Device Snapshot (watch-only cache) ────────────────────────────── export function saveDeviceSnapshot(deviceId: string, label: string, firmwareVer: string, featuresJson: string) { @@ -834,8 +965,9 @@ export function getSwapHistory(filter?: SwapHistoryFilter): SwapHistoryRecord[] params.push(filter.toDate) } if (filter?.asset) { - sql += ` AND (from_symbol LIKE ? OR to_symbol LIKE ? OR from_asset LIKE ? OR to_asset LIKE ?)` - const q = `%${filter.asset}%` + sql += ` AND (from_symbol LIKE ? ESCAPE '\\' OR to_symbol LIKE ? ESCAPE '\\' OR from_asset LIKE ? ESCAPE '\\' OR to_asset LIKE ? ESCAPE '\\')` + const escaped = filter.asset.replace(/[\\%_]/g, c => '\\' + c) + const q = `%${escaped}%` params.push(q, q, q, q) } diff --git a/projects/keepkey-vault/src/bun/index.ts b/projects/keepkey-vault/src/bun/index.ts index 0255133f..adbc6ffd 100644 --- a/projects/keepkey-vault/src/bun/index.ts +++ b/projects/keepkey-vault/src/bun/index.ts @@ -19,7 +19,7 @@ import { CHAINS, customChainToChainDef, isChainSupported } from "../shared/chain import type { ChainDef } from "../shared/chains" import { BtcAccountManager } from "./btc-accounts" import { EvmAddressManager, evmAddressPath } from "./evm-addresses" -import { initDb, getCustomTokens, addCustomToken as dbAddCustomToken, removeCustomToken as dbRemoveCustomToken, getCustomChains, addCustomChainDb, removeCustomChainDb, getSetting, setSetting, setTokenVisibility as dbSetTokenVisibility, removeTokenVisibility as dbRemoveTokenVisibility, getAllTokenVisibility, insertApiLog, getApiLogs, clearApiLogs, setCachedBalances, getCachedBalances, saveCachedPubkey, getLatestDeviceSnapshot, getCachedPubkeys, saveReport, getReportsList, getReportById, deleteReport, reportExists, getSwapHistory, getSwapHistoryStats, getBip85Seeds, saveBip85Seed, deleteBip85Seed, clearCachedPubkeys } from "./db" +import { initDb, getCustomTokens, addCustomToken as dbAddCustomToken, removeCustomToken as dbRemoveCustomToken, getCustomChains, addCustomChainDb, removeCustomChainDb, getSetting, setSetting, setTokenVisibility as dbSetTokenVisibility, removeTokenVisibility as dbRemoveTokenVisibility, getAllTokenVisibility, insertApiLog, getApiLogs, clearApiLogs, setCachedBalances, getCachedBalances, updateCachedBalance, saveCachedPubkey, getLatestDeviceSnapshot, getCachedPubkeys, saveReport, getReportsList, getReportById, deleteReport, reportExists, getSwapHistory, getSwapHistoryStats, getBip85Seeds, saveBip85Seed, deleteBip85Seed, clearCachedPubkeys, getRecentActivityFromLog, apiLogTxidExists, updateApiLogTxMeta } from "./db" import { generateReport, reportToPdfBuffer } from "./reports" import { extractTransactionsFromReport, toCoinTrackerCsv, toZenLedgerCsv } from "./tax-export" import * as os from "os" @@ -423,7 +423,9 @@ const rpc = BrowserView.defineRPC({ }, tonGetAddress: async (params) => { if (!engine.wallet) throw new Error('No device connected') - const result = await engine.wallet.tonGetAddress(params) + // Default to non-bounceable (UQ) — bounceable (EQ) bounces funds if wallet is uninitialized + const bounceable = params.bounceable ?? false + const result = await engine.wallet.tonGetAddress({ ...params, bounceable }) const addr = typeof result === 'string' ? result : result?.address if (addr) cacheAddress('ton', JSON.stringify(params.addressNList || []), addr) return result @@ -509,13 +511,24 @@ const rpc = BrowserView.defineRPC({ signature: result.signature instanceof Uint8Array ? Buffer.from(result.signature).toString('hex') : result.signature, + // Pass rawTx + tronGridTx through for broadcast + rawTx: typeof params.rawTx === 'string' ? params.rawTx + : params.rawTx instanceof Uint8Array ? Buffer.from(params.rawTx).toString('hex') + : undefined, + tronGridTx: (params as any).tronGridTx, } }, tonSignTx: async (params) => { if (!engine.wallet) throw new Error('No device connected') const result = await engine.wallet.tonSignTx(params) if (!result) throw new Error('tonSignTx returned no result') - return result + return { + signature: result.signature instanceof Uint8Array + ? Buffer.from(result.signature).toString('hex') + : result.signature, + // Pass tonBuildResult through for BOC assembly in broadcastTx + tonBuildResult: (params as any).tonBuildResult, + } }, // ── Pioneer integration (batch portfolio API) ──────────────── @@ -594,10 +607,17 @@ const rpc = BrowserView.defineRPC({ try { const addrParams: any = { addressNList: chain.defaultPath, showDisplay: false, coin: chain.coin } if (chain.scriptType) addrParams.scriptType = chain.scriptType + // TON: always non-bounceable (UQ) — bounceable (EQ) bounces if wallet uninitialized + if (chain.chainFamily === 'ton') addrParams.bounceable = false const method = chain.id === 'ripple' ? 'rippleGetAddress' : chain.rpcMethod const result = await wallet[method](addrParams) const address = typeof result === 'string' ? result : result?.address || '' - if (address) pubkeys.push({ caip: chain.caip, pubkey: address, chainId: chain.id, symbol: chain.symbol, networkId: chain.networkId }) + if (address) { + pubkeys.push({ caip: chain.caip, pubkey: address, chainId: chain.id, symbol: chain.symbol, networkId: chain.networkId }) + if (chain.id === 'tron') console.log(`[getBalances] TRON address derived: ${address}, caip: ${chain.caip}, networkId: ${chain.networkId}`) + } else { + if (chain.id === 'tron') console.warn(`[getBalances] TRON address derivation returned empty! result:`, JSON.stringify(result)) + } } catch (e: any) { console.warn(`[getBalances] ${chain.coin} address failed:`, e.message) } @@ -634,63 +654,49 @@ const rpc = BrowserView.defineRPC({ if (chain.networkId) networkToChain.set(chain.networkId, chain.id) } - // 3. Single API call — use GetPortfolio (charts endpoint) which returns - // both native balances AND tokens (ERC-20, etc.) + // 3. Single API call — GetPortfolioBalances returns natives + tokens in one flat array const results: ChainBalance[] = [] try { if (!pioneer) throw new Error('Pioneer client not available') const resp = await withTimeout( - pioneer.GetPortfolio({ + pioneer.GetPortfolioBalances({ pubkeys: pubkeys.map(p => ({ caip: p.caip, pubkey: p.pubkey })) }), PIONEER_TIMEOUT_MS, - 'GetPortfolio' + 'GetPortfolioBalances' ) - // Unwrap Swagger double-wrap: { data: { data: { balances, tokens } } } - const portfolio = resp?.data?.data || resp?.data || {} - const nativeEntries: any[] = portfolio.balances || (Array.isArray(portfolio) ? portfolio : []) - const portfolioTokens: any[] = portfolio.tokens || [] - - console.log(`[getBalances] GetPortfolio response: ${nativeEntries.length} balances, ${portfolioTokens.length} tokens`) + // Unwrap: { data: { balances: [...] } } or { data: [...] } + const rawData = resp?.data?.data || resp?.data || {} + const allEntries: any[] = rawData.balances || (Array.isArray(rawData) ? rawData : []) + + console.log(`[getBalances] GetPortfolioBalances response: ${allEntries.length} entries`) + // Log TRON-specific entries for debugging + const tronEntries = allEntries.filter((d: any) => d.caip?.includes('tron') || d.networkId?.includes('tron')) + if (tronEntries.length > 0) { + console.log(`[getBalances] TRON entries from Pioneer: ${tronEntries.length}`) + for (const t of tronEntries) console.log(` TRON: caip=${t.caip}, pubkey=${t.pubkey}, address=${t.address}, balance=${t.balance}, usd=${t.valueUsd}, type=${t.type}`) + } else { + console.warn(`[getBalances] TRON: NO entries returned from Pioneer`) + } // Log BTC-specific entries for debugging - const btcNatives = nativeEntries.filter((d: any) => d.caip?.includes('bip122') || d.pubkey?.startsWith('xpub') || d.pubkey?.startsWith('ypub') || d.pubkey?.startsWith('zpub')) - console.log(`[getBalances] BTC native entries from Pioneer: ${btcNatives.length}`) + const btcNatives = allEntries.filter((d: any) => d.caip?.includes('bip122') || d.pubkey?.startsWith('xpub') || d.pubkey?.startsWith('ypub') || d.pubkey?.startsWith('zpub')) + console.log(`[getBalances] BTC entries from Pioneer: ${btcNatives.length}`) for (const b of btcNatives) { console.log(` BTC: caip=${b.caip}, pubkey=${String(b.pubkey).substring(0, 24)}..., balance=${b.balance}, valueUsd=${b.valueUsd}, address=${b.address}`) } - // Convert portfolio.tokens (different shape) into the same format as native entries - // so our existing token grouping logic works on them - const tokenEntries: any[] = [] - for (const t of portfolioTokens) { - if (!t.assetCaip) continue - // Skip native assets that leaked into tokens array - if (t.assetCaip.includes('/slip44:')) continue - tokenEntries.push({ - caip: t.assetCaip, - networkId: t.networkId, - symbol: t.token?.symbol || 'UNK', - name: t.token?.name || t.token?.coingeckoId || 'Unknown', - balance: t.token?.balance?.toString() || '0', - valueUsd: t.token?.balanceUSD || 0, - priceUsd: t.token?.price || 0, - decimals: t.token?.decimals ?? t.token?.decimal ?? 18, - type: 'token', - contract: t.assetCaip.match(/\/erc20:(0x[a-fA-F0-9]+)/)?.[1] || undefined, - }) - } - - // Also scan nativeEntries for any tokens mixed in (belt + suspenders) + // Classify entries into natives vs tokens const pureNatives: any[] = [] - for (const d of nativeEntries) { - const caip = d.caip || '' + const tokenEntries: any[] = [] + for (const entry of allEntries) { + const caip = entry.caip || '' const caipPath = caip.split('/')[1] || '' - const isTokenByCaip = caipPath && !caipPath.startsWith('slip44:') - const isTokenByType = d.type === 'token' && d.isNative !== true + const isTokenByCaip = caipPath && !caipPath.startsWith('slip44:') && !caipPath.startsWith('native:') + const isTokenByType = entry.type === 'token' || (entry.isNative === false && entry.contract) if (isTokenByCaip || isTokenByType) { - tokenEntries.push(d) + tokenEntries.push(entry) } else { - pureNatives.push(d) + pureNatives.push(entry) } } @@ -761,30 +767,6 @@ const rpc = BrowserView.defineRPC({ } } catch { /* custom tokens lookup failed, non-fatal */ } - // Fallback: if GetPortfolio returned zero native balances, try GetPortfolioBalances for ALL pubkeys - if (pureNatives.length === 0 && pioneer && pubkeys.length > 0) { - console.log('[getBalances] GetPortfolio returned 0 natives — falling back to GetPortfolioBalances for all pubkeys') - try { - const fallbackResp = await withTimeout( - pioneer.GetPortfolioBalances({ - pubkeys: pubkeys.map(p => ({ caip: p.caip, pubkey: p.pubkey })) - }), - PIONEER_TIMEOUT_MS, - 'GetPortfolioBalances-all' - ) - const fallbackData = fallbackResp?.data?.data || fallbackResp?.data || {} - const fallbackBalances = fallbackData.balances || (Array.isArray(fallbackData) ? fallbackData : []) - if (Array.isArray(fallbackBalances)) { - for (const b of fallbackBalances) { - pureNatives.push(b) - } - console.log(`[getBalances] GetPortfolioBalances fallback returned ${pureNatives.length} native entries`) - } - } catch (e: any) { - console.warn('[getBalances] GetPortfolioBalances fallback failed:', e.message) - } - } - // Aggregate BTC entries into one ChainBalance + update per-xpub balances console.log(`[getBalances] pureNatives count: ${pureNatives.length}`) for (const n of pureNatives) { @@ -839,8 +821,13 @@ const rpc = BrowserView.defineRPC({ continue } + // Match by CAIP, then by networkId prefix (handles slip44 vs native CAIP variants), + // then pubkey, then address field (Pioneer may use either) + const entryNetwork = entry.caip.split('/')[0] // e.g. "tron:0x2b6653dc" const match = pureNatives.find((d: any) => d.caip === entry.caip) + || pureNatives.find((d: any) => d.caip && d.caip.split('/')[0] === entryNetwork) || pureNatives.find((d: any) => d.pubkey === entry.pubkey) + || pureNatives.find((d: any) => d.address === entry.pubkey) const chainTokens = tokensByChainId.get(entry.chainId) // Sum token USD values into the chain total const tokenUsdTotal = chainTokens?.reduce((sum, t) => sum + t.balanceUsd, 0) || 0 @@ -868,33 +855,6 @@ const rpc = BrowserView.defineRPC({ }) } - // BTC fallback: GetPortfolio (charts/portfolio) returns empty for BTC xpubs. - // Use GetPortfolioBalances (/portfolio) which correctly returns BTC data. - if (btcTotalBalance === 0 && btcPubkeyEntries.length > 0 && pioneer) { - console.log('[getBalances] BTC zero from GetPortfolio (charts) — trying GetPortfolioBalances fallback') - try { - const btcResp = await withTimeout( - pioneer.GetPortfolioBalances({ - pubkeys: btcPubkeyEntries.map(e => ({ caip: e.caip, pubkey: e.pubkey })) - }), - PIONEER_TIMEOUT_MS, - 'GetPortfolioBalances-BTC' - ) - const btcBalances = btcResp?.data?.balances || btcResp?.data || [] - for (const b of (Array.isArray(btcBalances) ? btcBalances : [])) { - const bal = parseFloat(String(b.balance ?? '0')) - const usd = Number(b.valueUsd ?? 0) - btcTotalBalance += bal - btcTotalUsd += usd - if (!btcAddress && b.address) btcAddress = b.address - if (b.pubkey) btcAccounts.updateXpubBalance(b.pubkey, String(b.balance ?? '0'), usd) - } - console.log(`[getBalances] BTC fallback result: ${btcTotalBalance.toFixed(8)} BTC, $${btcTotalUsd.toFixed(2)}`) - } catch (e: any) { - console.warn('[getBalances] BTC GetPortfolioBalances fallback failed:', e.message) - } - } - // Push one aggregated BTC entry if (btcPubkeyEntries.length > 0) { const selectedXpub = btcAccounts.getSelectedXpub() @@ -906,6 +866,7 @@ const rpc = BrowserView.defineRPC({ }) } + // Push updated BTC accounts to frontend try { rpc.send['btc-accounts-update'](btcAccounts.toAccountSet()) } catch { /* webview not ready */ } // Push updated EVM addresses to frontend @@ -969,6 +930,7 @@ const rpc = BrowserView.defineRPC({ } else { const addrParams: any = { addressNList: chain.defaultPath, showDisplay: false, coin: chain.chainFamily === 'evm' ? 'Ethereum' : chain.coin } if (chain.scriptType) addrParams.scriptType = chain.scriptType + if (chain.chainFamily === 'ton') addrParams.bounceable = false const method = chain.id === 'ripple' ? 'rippleGetAddress' : chain.rpcMethod const result = await wallet[method](addrParams) pubkey = typeof result === 'string' ? result : result?.address || '' @@ -984,7 +946,16 @@ const rpc = BrowserView.defineRPC({ } catch (e: any) { console.warn(`[getBalance] ${chain.coin} portfolio failed:`, e.message) } - return { chainId: chain.id, symbol: chain.symbol, balance, balanceUsd, address: pubkey } + const result: ChainBalance = { chainId: chain.id, symbol: chain.symbol, balance, balanceUsd, address: pubkey } + + // Update single-chain cache + push to frontend so Dashboard stays in sync + try { + const deviceId = engine.getDeviceState().deviceId || 'unknown' + updateCachedBalance(deviceId, result) + } catch { /* never block on cache failure */ } + try { rpc.send['balance-updated'](result) } catch { /* webview not ready */ } + + return result }, buildTx: async (params) => { @@ -1017,6 +988,7 @@ const rpc = BrowserView.defineRPC({ coin: chain.coin, } if (chain.scriptType) addrParams.scriptType = chain.scriptType + if (chain.chainFamily === 'ton') addrParams.bounceable = false const walletMethod = chain.id === 'ripple' ? 'rippleGetAddress' : chain.rpcMethod const addrResult = await wallet[walletMethod](addrParams) fromAddress = typeof addrResult === 'string' ? addrResult : addrResult?.address @@ -1052,12 +1024,66 @@ const rpc = BrowserView.defineRPC({ const rpcUrl = chain.id.startsWith('evm-custom-') ? getRpcUrl(chain) : undefined const evmIdx = chain.chainFamily === 'evm' ? (params.evmAddressIndex ?? evmAddresses.getSelectedAddress()?.addressIndex ?? 0) : undefined + + // TON: derive Ed25519 public key for wallet deployment (StateInit) + let publicKeyHex: string | undefined + if (chain.chainFamily === 'ton') { + try { + // Bypass hdwallet's getPublicKeys() — it forces a BTC scriptType which + // firmware rejects for ed25519. Call transport.call() directly instead. + const Messages = await import('@keepkey/device-protocol/lib/messages_pb') + const gpk = new Messages.GetPublicKey() + gpk.setAddressNList(chain.defaultPath) + gpk.setEcdsaCurveName('ed25519') + gpk.setShowDisplay(false) + const resp = await wallet.transport.call( + Messages.MessageType.MESSAGETYPE_GETPUBLICKEY, + gpk, + { msgTimeout: 10000 } + ) + const pubKeyProto = resp.proto as any + // Try node.publicKey first (raw bytes), fall back to xpub decode + const node = pubKeyProto.getNode?.() + const rawKey = node?.getPublicKey_asU8?.() + if (rawKey && (rawKey.length === 32 || rawKey.length === 33)) { + // ed25519 node key is 33 bytes: 0x00 prefix + 32-byte key + const keyBytes = rawKey.length === 33 && rawKey[0] === 0x00 ? rawKey.subarray(1) : rawKey.length === 32 ? rawKey : null + if (!keyBytes || keyBytes.length !== 32) throw new Error(`Unexpected ed25519 key length: ${rawKey.length}`) + publicKeyHex = Buffer.from(keyBytes).toString('hex') + } else { + // Fallback: decode xpub to extract raw key + const xpubStr = pubKeyProto.getXpub?.() + if (xpubStr) { + const bs58check = require('bs58check') + const decoded: Buffer = bs58check.decode(xpubStr) + if (decoded.length >= 78 && decoded[45] === 0x00) { + publicKeyHex = Buffer.from(decoded.subarray(46, 78)).toString('hex') + } + } + } + if (publicKeyHex) { + console.log(`[buildTx] TON ed25519 pubkey: ${publicKeyHex}`) + // Compute the correct v4r2 wallet address from the public key. + // The firmware may derive a wrong address (sha256(pubkey) instead of + // sha256(stateInit)), so always use our vault-computed address. + const { tonV4R2Address } = await import('./txbuilder/ton') + fromAddress = tonV4R2Address(publicKeyHex) + console.log(`[buildTx] TON v4r2 address: ${fromAddress}`) + } else { + console.warn(`[buildTx] TON: GetPublicKey returned no usable key`) + } + } catch (e: any) { + console.warn(`[buildTx] TON public key derivation failed:`, e.message) + } + } + const result = await buildTx(pioneer, chain, { ...params, fromAddress, xpub, rpcUrl, evmAddressIndex: evmIdx, + publicKeyHex, }) return { unsignedTx: result.unsignedTx, fee: result.fee } @@ -1068,17 +1094,26 @@ const rpc = BrowserView.defineRPC({ const chain = getAllChains().find(c => c.id === params.chainId) if (!chain) throw new Error(`Unknown chain: ${params.chainId}`) + let result: { txid: string } + // Custom chains: broadcast via direct RPC const rpcUrl = chain.id.startsWith('evm-custom-') ? getRpcUrl(chain) : undefined if (rpcUrl) { const serialized = params.signedTx?.serializedTx || params.signedTx?.serialized || (typeof params.signedTx === 'string' ? params.signedTx : undefined) if (!serialized || typeof serialized !== 'string') throw new Error(`Cannot extract serialized tx from: ${JSON.stringify(params.signedTx).slice(0, 200)}`) const txid = await broadcastEvmTx(rpcUrl, serialized) - return { txid } + result = { txid } + } else { + const pioneer = await getPioneer() + result = await broadcastTx(pioneer, chain, params.signedTx) } - const pioneer = await getPioneer() - return await broadcastTx(pioneer, chain, params.signedTx) + // Track broadcast in api_log + notify frontend + const logEntry: ApiLogEntry = { method: 'RPC', route: 'broadcastTx', timestamp: Date.now(), durationMs: 0, status: 200, appName: 'vault', txid: result.txid, chain: chain.symbol, activityType: 'broadcast' } + insertApiLog(logEntry) + try { rpc.send['api-log'](logEntry) } catch { /* webview not ready */ } + + return result }, getMarketData: async (params) => { @@ -1689,6 +1724,9 @@ const rpc = BrowserView.defineRPC({ } catch (e: any) { console.warn('[index] Failed to register swap for tracking:', e.message) } + // Track swap in api_log + const fromChain = getAllChains().find(c => c.id === params.fromChainId) + insertApiLog({ method: 'RPC', route: 'executeSwap', timestamp: Date.now(), durationMs: 0, status: 200, appName: 'vault', txid: result.txid, chain: fromChain?.symbol || params.fromChainId, activityType: 'swap' }) return result }, getPendingSwaps: async () => { @@ -1734,6 +1772,90 @@ const rpc = BrowserView.defineRPC({ } }, + // ── Recent Activity (from api_log + swap_history) ──────── + getRecentActivity: async (params) => { + return getRecentActivityFromLog(params?.limit || 50, params?.chainId) + }, + scanChainHistory: async (params) => { + const chain = getAllChains().find(c => c.id === params.chainId) + if (!chain) throw new Error(`Unknown chain: ${params.chainId}`) + + // Get the address/xpub for this chain from cached balances + // UTXO chains store xpub, account-based chains store address + const deviceId = engine.getDeviceState().deviceId + if (!deviceId) throw new Error('No device connected') + const cachedBalances = getCachedBalances(deviceId) + const chainBalance = cachedBalances?.balances?.find(b => b.chainId === params.chainId) + const pubkey = chainBalance?.address + if (!pubkey) throw new Error(`No cached address for ${chain.symbol} — load balances first`) + + const pioneer = await getPioneer() + console.log(`[activity] Scanning ${chain.symbol} history for ${chain.chainFamily === 'utxo' ? 'xpub' : 'address'}: ${pubkey.slice(0, 16)}...`) + + const resp = await withTimeout( + pioneer.GetTxHistory({ queries: [{ pubkey, caip: chain.caip }] }), + PIONEER_TIMEOUT_MS, + `GetTxHistory(${chain.symbol})` + ) + const data = resp?.data || resp + const histories = data?.histories || data?.data?.histories || [] + const txs: any[] = histories[0]?.transactions || [] + + if (txs.length === 0) { + console.log(`[activity] No transactions found for ${chain.symbol}`) + return { count: 0 } + } + + // Insert new txs, update confirmations on existing ones + let inserted = 0 + let updated = 0 + for (const tx of txs) { + const txid = tx.txid || tx.hash || tx.txHash + if (!txid) continue + + const direction = tx.direction || (tx.value < 0 ? 'sent' : 'received') + const activityType = direction === 'sent' ? 'send' : 'receive' + const ts = tx.timestamp ? tx.timestamp * 1000 : tx.blockTime ? tx.blockTime * 1000 : Date.now() + const confirmations = typeof tx.confirmations === 'number' ? tx.confirmations : 0 + const blockHeight = tx.blockHeight || tx.block_height || tx.height || 0 + const value = tx.value != null ? String(tx.value) : undefined + const fee = tx.fee != null ? String(tx.fee) : undefined + + // Tx metadata stored in response_body + const meta = { confirmations, blockHeight, value, fee, direction } + + if (apiLogTxidExists(txid)) { + // Update confirmation count on existing entry + updateApiLogTxMeta(txid, meta) + updated++ + } else { + // New tx — insert + insertApiLog({ + method: 'SCAN', + route: `history/${params.chainId}`, + timestamp: ts, + durationMs: 0, + status: 200, + appName: 'vault', + txid, + chain: chain.symbol, + activityType, + responseBody: meta, + }) + inserted++ + } + } + + console.log(`[activity] Scanned ${chain.symbol}: ${txs.length} txs, ${inserted} new, ${updated} updated`) + return { count: inserted } + }, + dismissActivity: async (_params) => { + // No-op: api_log entries are audit records, not dismissible + }, + clearRecentActivity: async () => { + // No-op: api_log entries are audit records + }, + // ── Balance cache (instant portfolio) ──────────────────── getCachedBalances: async () => { const deviceId = engine.getDeviceState().deviceId diff --git a/projects/keepkey-vault/src/bun/rest-api.ts b/projects/keepkey-vault/src/bun/rest-api.ts index 947e8153..ad6476da 100644 --- a/projects/keepkey-vault/src/bun/rest-api.ts +++ b/projects/keepkey-vault/src/bun/rest-api.ts @@ -317,6 +317,13 @@ function addressNListToBIP32(addressNList: number[]): string { /** Start time for uptime calculation */ const startTime = Date.now() +/** Route prefix → chain symbol for activity tracking */ +const ROUTE_TO_CHAIN: Record = { + eth: 'ETH', utxo: 'BTC', cosmos: 'ATOM', osmosis: 'OSMO', + thorchain: 'RUNE', mayachain: 'CACAO', xrp: 'XRP', + solana: 'SOL', tron: 'TRX', ton: 'TON', +} + /** Set of signing endpoints that require user approval */ const SIGNING_ROUTES = new Set([ '/eth/sign-transaction', '/eth/sign-typed-data', '/eth/sign', @@ -360,10 +367,16 @@ export function startRestApi(engine: EngineController, auth: AuthStore, port = 1 let reqBody: any = undefined // Per-request response helpers (capture req for CORS origin check) - const json = (data: unknown, status = 200) => { + const json = (data: unknown, status = 200, activity?: { txid?: string; chain?: string; activityType?: string }) => { const resp = new Response(JSON.stringify(data), { status, headers: { 'Content-Type': 'application/json', ...corsHeaders(req) }, }) + // Auto-detect activity type from route if not explicitly provided + let resolvedActivity = activity + if (!resolvedActivity && method === 'POST' && SIGNING_ROUTES.has(path)) { + const chainForRoute = ROUTE_TO_CHAIN[path.split('/')[1]] + if (chainForRoute) resolvedActivity = { chain: chainForRoute, activityType: 'sign' } + } // Log the request with body + response + duration if (callbacks?.onApiLog) { const { appName, imageUrl } = resolveAppInfo() @@ -373,6 +386,7 @@ export function startRestApi(engine: EngineController, auth: AuthStore, port = 1 status, appName, imageUrl: imageUrl || undefined, requestBody: reqBody, responseBody: data, + ...resolvedActivity, }) } return resp @@ -809,6 +823,7 @@ export function startRestApi(engine: EngineController, auth: AuthStore, port = 1 const result = await wallet.tonGetAddress({ addressNList: body.address_n, showDisplay: body.show_display ?? false, + bounceable: false, // UQ prefix — safe for uninitialized wallets }) const address = typeof result === 'string' ? result : (result as any)?.address || result if (addressCache.size >= MAX_CACHE_SIZE) evictOldest(addressCache, Math.ceil(MAX_CACHE_SIZE * 0.2)) @@ -934,7 +949,9 @@ export function startRestApi(engine: EngineController, auth: AuthStore, port = 1 ...(body.versionGroupId !== undefined ? { versionGroupId: body.versionGroupId } : {}), ...(body.branchId !== undefined ? { branchId: body.branchId } : {}), }) - return json(validateResponse(result, S.UtxoSignTransactionResponse, path)) + // Explicit chain for UTXO — auto-detect defaults to BTC but could be LTC/DOGE/etc + const coinSymbol = coin === 'Bitcoin' ? 'BTC' : coin === 'Litecoin' ? 'LTC' : coin === 'Dogecoin' ? 'DOGE' : coin === 'Dash' ? 'DASH' : coin === 'BitcoinCash' ? 'BCH' : coin + return json(validateResponse(result, S.UtxoSignTransactionResponse, path), 200, { chain: coinSymbol, activityType: 'sign' }) } // ── COSMOS SIGNING (6 endpoints) ────────────────────────────── @@ -1336,7 +1353,7 @@ export function startRestApi(engine: EngineController, auth: AuthStore, port = 1 } else if (coinType === 607) { // TON uses ed25519 with 3-element path (m/44'/607'/0') — don't extend to 5 const tonNList = p.address_n - const r = await wallet.tonGetAddress({ addressNList: tonNList, showDisplay: false }) + const r = await wallet.tonGetAddress({ addressNList: tonNList, showDisplay: false, bounceable: false }) address = typeof r === 'string' ? r : (r as any)?.address || '' } diff --git a/projects/keepkey-vault/src/bun/txbuilder/index.ts b/projects/keepkey-vault/src/bun/txbuilder/index.ts index f513a85c..ed7fef3f 100644 --- a/projects/keepkey-vault/src/bun/txbuilder/index.ts +++ b/projects/keepkey-vault/src/bun/txbuilder/index.ts @@ -8,6 +8,8 @@ import { buildEvmTx, type BuildEvmParams } from './evm' import { buildCosmosTx, type BuildCosmosParams } from './cosmos' import { buildXrpTx, type BuildXrpParams } from './xrp' import { sendShielded, type ShieldedSendParams } from './zcash-shielded' +import { buildTonTransfer, assembleTonSignedBoc, getTonSeqno, getTonWalletState, broadcastTonBoc, type TonBuildResult } from './ton' +import { getPioneerApiBase } from '../pioneer' export type { BuildTxParams } @@ -18,7 +20,7 @@ export type { BuildTxParams } export async function buildTx( pioneer: any, chain: ChainDef, - params: BuildTxParams & { fromAddress?: string; xpub?: string; rpcUrl?: string; accountPath?: number[]; evmAddressIndex?: number }, + params: BuildTxParams & { fromAddress?: string; xpub?: string; rpcUrl?: string; accountPath?: number[]; evmAddressIndex?: number; publicKeyHex?: string }, ): Promise<{ unsignedTx: any; fee: string }> { switch (chain.chainFamily) { case 'utxo': { @@ -97,15 +99,24 @@ export async function buildTx( let rawTx: string try { - // Pioneer SDK: BuildTransfer1 = POST /solana/build-transfer - // Returns { serialized: "base64..." } with 65-byte header (sig_count + dummy_sig + message) - const resp = await pioneer.BuildTransfer1({ - from: params.fromAddress, - to: params.to, - amount: solAmountLamports, - memo: params.memo || undefined, + // Direct HTTP call to Pioneer — do NOT use pioneer.BuildTransferN() as the + // swagger-codegen suffix is non-deterministic (shifts when new chains add + // endpoints with the same operationId "BuildTransfer"). + const base = getPioneerApiBase() + const resp = await fetch(`${base}/api/v1/solana/build-transfer`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + from: params.fromAddress, + to: params.to, + amount: solAmountLamports, + memo: params.memo || undefined, + }), }) - const data = resp?.data + const data = await resp.json() as any + if (!resp.ok || data?.success === false) { + throw new Error(data?.error || data?.message || `HTTP ${resp.status}`) + } rawTx = data?.serialized if (!rawTx) throw new Error('Pioneer did not return serialized tx for Solana') } catch (e: any) { @@ -120,6 +131,111 @@ export async function buildTx( return { unsignedTx, fee: '0.000005' } } + case 'tron': { + // Tron — TronGrid builds the raw protobuf tx (raw_data_hex), device signs + if (!params.fromAddress) throw new Error('fromAddress required for Tron') + + // Convert TRX amount to sun (6 decimals) + const sunAmount = (() => { + const parts = params.amount.split('.') + const whole = parts[0] || '0' + const frac = (parts[1] || '').slice(0, 6).padEnd(6, '0') + // Keep as Number — TronGrid expects integer, and TRX max supply (99B) fits safely in Number + const sun = BigInt(whole) * 1000000n + BigInt(frac) + if (sun > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error('TRX amount too large') + return Number(sun) + })() + + let tronGridTx: any + try { + // Use TronGrid's createtransaction — it returns raw_data_hex (serialized protobuf) + // which is exactly what the KeepKey firmware needs to sign. + // Pioneer's build-transfer returns JSON but not protobuf bytes. + const resp = await fetch('https://api.trongrid.io/wallet/createtransaction', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + owner_address: params.fromAddress, + to_address: params.to, + amount: sunAmount, + visible: true, + }), + }) + tronGridTx = await resp.json() as any + if (tronGridTx?.Error) throw new Error(tronGridTx.Error) + if (!resp.ok) throw new Error(`HTTP ${resp.status}`) + if (!tronGridTx?.raw_data_hex) throw new Error('TronGrid did not return raw_data_hex') + } catch (e: any) { + throw new Error(`Tron tx build failed: ${e.message}`) + } + + const tronUnsignedTx = { + addressNList: chain.defaultPath, + rawTx: tronGridTx.raw_data_hex, + // Store full TronGrid response — broadcasttransaction needs raw_data JSON + tronGridTx, + } + // Tron: bandwidth is typically free for TRX transfers + return { unsignedTx: tronUnsignedTx, fee: '0' } + } + + case 'ton': { + // TON — build v4r2 wallet transfer, device signs cell hash + if (!params.fromAddress) throw new Error('fromAddress required for TON') + + // Convert TON amount to nanoTON (9 decimals) + const nanoTon = (() => { + const parts = params.amount.split('.') + const whole = parts[0] || '0' + const frac = (parts[1] || '').slice(0, 9).padEnd(9, '0') + return String(BigInt(whole) * 1000000000n + BigInt(frac)) + })() + + // Check wallet state — uninitialized wallets need StateInit in first tx + let walletState: { initialized: boolean; balance: string } + let seqno: number + try { + ;[seqno, walletState] = await Promise.all([ + getTonSeqno(params.fromAddress), + getTonWalletState(params.fromAddress), + ]) + } catch (e: any) { + throw new Error(`TON network error — cannot determine wallet state: ${e.message}`) + } + + const needsDeploy = !walletState.initialized + if (needsDeploy && !params.publicKeyHex) { + throw new Error('TON wallet not initialized — public key required for deployment') + } + + const expireAt = Math.floor(Date.now() / 1000) + 120 // 2 minutes + + const tonBuild = buildTonTransfer({ + fromAddress: params.fromAddress, + to: params.to, + amountNano: nanoTon, + memo: params.memo, + seqno, + expireAt, + needsDeploy, + publicKeyHex: params.publicKeyHex, + }) + + const tonUnsignedTx = { + addressNList: chain.defaultPath, + rawTx: tonBuild.rawTx, // cell hash (32 bytes hex) — firmware signs this + seqno: tonBuild.seqno, + expireAt: tonBuild.expireAt, + toAddress: tonBuild.toAddress, + amount: tonBuild.amountNano, + workchain: 0, + // Store build result for BOC assembly after signing + tonBuildResult: tonBuild, + } + // TON fees: ~0.005 TON for simple transfers, ~0.01 for deploy+transfer + return { unsignedTx: tonUnsignedTx, fee: needsDeploy ? '0.01' : '0.005' } + } + default: throw new Error(`Unsupported chain family: ${chain.chainFamily}`) } @@ -147,6 +263,10 @@ export async function signTx( return wallet.rippleSignTx(unsignedTx) case 'solana': return wallet.solanaSignTx(unsignedTx) + case 'tron': + return wallet.tronSignTx(unsignedTx) + case 'ton': + return wallet.tonSignTx(unsignedTx) case 'zcash-shielded': // Shielded signing is handled by the zcash-shielded module (sidecar + device) // The full flow is orchestrated by sendShielded() — this should not be called directly @@ -171,6 +291,53 @@ export async function broadcastTx( // EVM/UTXO: hdwallet returns { serializedTx: "hex" } // Cosmos: proto-tx-builder returns { serialized: "base64" } — must convert to hex for Pioneer // Solana: hdwallet returns { signature: Uint8Array } — pass signature to Pioneer for assembly + // TON: assemble signed BOC and broadcast via TON Center + if (chain.chainFamily === 'ton') { + const tonBuildResult = signedTx?.tonBuildResult as TonBuildResult | undefined + const sigHex = signedTx?.signature + if (!tonBuildResult || !sigHex) throw new Error('TON broadcast requires tonBuildResult and signature') + + const sigBuf = Buffer.from(typeof sigHex === 'string' ? sigHex : Buffer.from(sigHex).toString('hex'), 'hex') + const bocBase64 = assembleTonSignedBoc(tonBuildResult, sigBuf) + + // Broadcast through Pioneer (same as all other chains) + const result = await pioneer.Broadcast({ networkId: chain.networkId, serialized: bocBase64 }) + const data = result?.data + if (data && typeof data === 'object' && data.success === false) { + const errMsg = typeof data.error === 'string' ? data.error : JSON.stringify(data.error || data) + throw new Error(`TON broadcast rejected: ${errMsg}`) + } + const txid = data?.txid || data?.tx_hash || data?.hash + || data?.results?.hash || data?.results?.tx_hash + || (typeof data === 'string' && data.length >= 32 ? data : undefined) + // TON's sendBoc doesn't always return a hash — success:true is sufficient + if (!txid && data?.success === true) return { txid: tonBuildResult.bodyHash || 'pending' } + if (!txid) throw new Error(`TON broadcast: unexpected response — ${JSON.stringify(data).slice(0, 200)}`) + return { txid } + } + + // Tron: broadcast via TronGrid's broadcasttransaction (JSON format, needs raw_data + signature) + if (chain.chainFamily === 'tron') { + const tronGridTx = signedTx?.tronGridTx + const sigHex = signedTx?.signature + if (!tronGridTx || !sigHex) throw new Error('Tron broadcast requires tronGridTx and signature') + + const broadcastBody = { ...tronGridTx, signature: [sigHex] } + const resp = await fetch('https://api.trongrid.io/wallet/broadcasttransaction', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(broadcastBody), + }) + const data = await resp.json() as any + if (data?.result === true && data?.txid) return { txid: data.txid } + // TronGrid returns error messages as hex-encoded strings + let errMsg = data?.code || 'Unknown error' + if (data?.message) { + try { errMsg = Buffer.from(data.message, 'hex').toString('utf8') } catch { errMsg = data.message } + } + throw new Error(`Tron broadcast failed: ${errMsg}`) + } + let serializedTx: string if (chain.chainFamily === 'solana') { // Solana: solanaSignTx RPC handler assembles full signed tx (base64) diff --git a/projects/keepkey-vault/src/bun/txbuilder/ton.ts b/projects/keepkey-vault/src/bun/txbuilder/ton.ts new file mode 100644 index 00000000..1d379d3d --- /dev/null +++ b/projects/keepkey-vault/src/bun/txbuilder/ton.ts @@ -0,0 +1,658 @@ +/** + * Minimal TON BOC builder for v4r2 wallet transfers. + * + * Constructs the cell tree, computes representation hashes, serializes to BOC, + * and handles signing + broadcast via TON Center API. + * + * TON wallets are smart contracts. The first outgoing tx must include StateInit + * (contract code + initial data with public key) to deploy the wallet on-chain. + */ +import { createHash } from 'crypto' + +// ── v4r2 wallet contract code (well-known constant) ────────────────── +// Source: https://github.com/ton-blockchain/wallet-contract +const V4R2_CODE_BOC_B64 = 'te6cckECFAEAAtQAART/APSkE/S88sgLAQIBIAIPAgFIAwYC5tAB0NMDIXGwkl8E4CLXScEgkl8E4ALTHyGCEHBsdWe9IoIQZHN0cr2wkl8F4AP6QDAg+kQByMoHy//J0O1E0IEBQNch9AQwXIEBCPQKb6Exs5JfB+AF0z/IJYIQcGx1Z7qSODDjDQOCEGRzdHK6kl8G4w0EBQB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAHDgIBIAgNAgFYCQoAPbKd+1E0IEBQNch9AQwAsjKB8v/ydABgQEI9ApvoTGACASALDAAZrc52omhAIGuQ64X/wAAZrx32omhAEGuQ64WPwAARuMl+1E0NcLH4AFm9JCtvaiaECAoGuQ+gIYRw1AgIR6STfSmRDOaQPp/5g3gSgBt4EBSJhxWfMYQE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8QERITAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVAj45Sg=' + +// ── v4r2 address derivation (matches firmware's ton_get_address) ────── + +const V4R2_CODE_HASH = Buffer.from('feb5ff6820e2ff0d9483e7e0d62c817d846789fb4ae580c878866d959dabd5c0', 'hex') +const V4R2_CODE_DEPTH = 7 +const V4R2_WALLET_ID_CONST = 698983191 // 0x29A9A317 + +/** Compute the correct v4r2 wallet address from a raw 32-byte ed25519 public key */ +export function tonV4R2Address(publicKeyHex: string, workchain = 0, bounceable = false): string { + const pubkey = Buffer.from(publicKeyHex, 'hex') + if (pubkey.length !== 32) throw new Error(`Expected 32-byte pubkey, got ${pubkey.length}`) + + // Data cell repr: d1(0x00) + d2(0x51) + seqno(4B=0) + walletId(4B) + pubkey(32B) + 0x40 (plugin=0 + completion) + const dataRepr = Buffer.alloc(43) + dataRepr[0] = 0x00; dataRepr[1] = 0x51 + dataRepr.writeUInt32BE(V4R2_WALLET_ID_CONST, 6) + pubkey.copy(dataRepr, 10) + dataRepr[42] = 0x40 + const dataHash = createHash('sha256').update(dataRepr).digest() + + // StateInit repr: d1(0x02) + d2(0x01) + 0x34 + codeDepth(2B) + dataDepth(2B) + codeHash(32B) + dataHash(32B) + const siRepr = Buffer.alloc(71) + siRepr[0] = 0x02; siRepr[1] = 0x01; siRepr[2] = 0x34 + siRepr.writeUInt16BE(V4R2_CODE_DEPTH, 3) + siRepr.writeUInt16BE(0, 5) // data depth = 0 + V4R2_CODE_HASH.copy(siRepr, 7) + dataHash.copy(siRepr, 39) + const addrHash = createHash('sha256').update(siRepr).digest() + + // Encode as user-friendly address: tag(1) + wc(1) + hash(32) + crc16(2) → base64url + const tag = bounceable ? 0x11 : 0x51 + const raw = Buffer.alloc(36) + raw[0] = tag + raw[1] = workchain === -1 ? 0xFF : workchain & 0xFF + addrHash.copy(raw, 2) + const crc = tonCrc16(raw.subarray(0, 34)) + raw[34] = (crc >> 8) & 0xFF + raw[35] = crc & 0xFF + return raw.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') +} + +function tonCrc16(data: Buffer): number { + let crc = 0 + for (let i = 0; i < data.length; i++) { + crc ^= data[i] << 8 + for (let j = 0; j < 8; j++) crc = crc & 0x8000 ? (crc << 1) ^ 0x1021 : crc << 1 + crc &= 0xFFFF + } + return crc +} + +// ── TON user-friendly address parsing ───────────────────────────────── + +const BASE64URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' +const BASE64STD = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + +/** Parse a TON user-friendly or raw address → { workchain, hash } */ +export function parseTonAddress(addr: string): { workchain: number; hash: Buffer } { + // Raw format: "workchain:hex" e.g. "0:abcdef..." + const rawMatch = addr.match(/^(-?\d+):([0-9a-fA-F]{64})$/) + if (rawMatch) { + return { workchain: parseInt(rawMatch[1], 10), hash: Buffer.from(rawMatch[2], 'hex') } + } + + // User-friendly: base64url or base64 encoded, 48 chars → 36 bytes + let b64 = addr + if (b64.includes('-') || b64.includes('_')) { + // Convert base64url → standard base64 + b64 = b64.replace(/-/g, '+').replace(/_/g, '/') + } + // Add padding if needed + while (b64.length % 4 !== 0) b64 += '=' + + const raw = Buffer.from(b64, 'base64') + if (raw.length !== 36) throw new Error(`Invalid TON address length: ${raw.length}`) + + // Byte 0: tag (0x11=bounceable, 0x51=non-bounceable, +0x80 for testnet) + // Byte 1: workchain (0x00=basechain, 0xFF=masterchain) + // Bytes 2-33: 256-bit hash + // Bytes 34-35: CRC16-XMODEM + const expectedCrc = (raw[34] << 8) | raw[35] + const actualCrc = tonCrc16(raw.subarray(0, 34)) + if (expectedCrc !== actualCrc) { + throw new Error(`Invalid TON address checksum: expected ${expectedCrc.toString(16)}, got ${actualCrc.toString(16)}`) + } + + const wc = raw[1] === 0xFF ? -1 : raw[1] + const hash = raw.subarray(2, 34) + + return { workchain: wc, hash: Buffer.from(hash) } +} + +// ── Bit-level writer ────────────────────────────────────────────────── + +class BitWriter { + private buf: Buffer + private len = 0 + + constructor(capacity = 1023) { + this.buf = Buffer.alloc(Math.ceil(capacity / 8)) + } + + get bitLength(): number { return this.len } + + writeBit(v: boolean): this { + if (this.len >= this.buf.length * 8) { + throw new Error(`BitWriter overflow: capacity ${this.buf.length * 8} bits exceeded`) + } + if (v) this.buf[this.len >> 3] |= (0x80 >> (this.len & 7)) + this.len++ + return this + } + + writeUint(value: bigint | number, bits: number): this { + const v = BigInt(value) + for (let i = bits - 1; i >= 0; i--) { + this.writeBit(((v >> BigInt(i)) & 1n) === 1n) + } + return this + } + + writeInt(value: number, bits: number): this { + if (value < 0) return this.writeUint(BigInt(value) + (1n << BigInt(bits)), bits) + return this.writeUint(value, bits) + } + + writeBytes(b: Buffer | Uint8Array): this { + for (const byte of b) this.writeUint(byte, 8) + return this + } + + /** Write addr_none$00 */ + writeAddressNone(): this { + return this.writeUint(0, 2) + } + + /** Write addr_std$10 anycast:nothing workchain:int8 address:bits256 */ + writeAddress(workchain: number, hash: Buffer): this { + this.writeUint(2, 2) // addr_std tag = 0b10 + this.writeBit(false) // no anycast + this.writeInt(workchain, 8) + this.writeBytes(hash) + return this + } + + /** Write Grams (VarUInteger 16) */ + writeCoins(amount: bigint): this { + if (amount === 0n) return this.writeUint(0, 4) + let byteLen = 0 + let v = amount + while (v > 0n) { byteLen++; v >>= 8n } + this.writeUint(byteLen, 4) + for (let i = byteLen - 1; i >= 0; i--) { + this.writeUint(Number((amount >> BigInt(i * 8)) & 0xFFn), 8) + } + return this + } + + /** Get data bytes with augmented padding (1-bit + trailing zeros) */ + toAugmentedBytes(): Buffer { + const byteLen = Math.ceil(this.len / 8) + const result = Buffer.alloc(byteLen) + this.buf.copy(result, 0, 0, byteLen) + if (this.len % 8 !== 0) { + result[byteLen - 1] |= (0x80 >> (this.len & 7)) + } + return result + } +} + +// ── Cell representation ─────────────────────────────────────────────── + +interface Cell { + bits: BitWriter + refs: Cell[] +} + +function newCell(): { cell: Cell; bits: BitWriter } { + const bits = new BitWriter() + const cell: Cell = { bits, refs: [] } + return { cell, bits } +} + +/** SHA-256 representation hash of a standard cell */ +function cellHash(cell: Cell): Buffer { + const bitsLen = cell.bits.bitLength + const d1 = cell.refs.length // refs descriptor + const d2 = Math.ceil(bitsLen / 8) + Math.floor(bitsLen / 8) // bits descriptor + const data = cell.bits.toAugmentedBytes() + + const h = createHash('sha256') + h.update(Buffer.from([d1, d2])) + h.update(data) + for (const ref of cell.refs) { + const d = cellDepth(ref) + h.update(Buffer.from([d >> 8, d & 0xFF])) + } + for (const ref of cell.refs) { + h.update(cellHash(ref)) + } + return h.digest() +} + +function cellDepth(cell: Cell): number { + if (cell.refs.length === 0) return 0 + return 1 + Math.max(...cell.refs.map(cellDepth)) +} + +// ── BOC serialization ───────────────────────────────────────────────── + +/** Serialize a cell tree to BOC (Bag of Cells) → base64 string */ +function serializeBoc(root: Cell): string { + // Collect all unique cells (topological order: root first, leaves last) + const allCells: Cell[] = [] + const hashToIdx = new Map() + + function collect(cell: Cell) { + const h = cellHash(cell).toString('hex') + if (hashToIdx.has(h)) return + const idx = allCells.length + hashToIdx.set(h, idx) + allCells.push(cell) + for (const ref of cell.refs) collect(ref) + } + collect(root) + + const cellCount = allCells.length + const refSize = cellCount <= 0xFF ? 1 : cellCount <= 0xFFFF ? 2 : 3 + + // Serialize each cell: d1 + d2 + augmented_data + ref_indices + const serializedCells: Buffer[] = [] + let totalDataSize = 0 + for (const cell of allCells) { + const bitsLen = cell.bits.bitLength + const d1 = cell.refs.length + const d2 = Math.ceil(bitsLen / 8) + Math.floor(bitsLen / 8) + const data = cell.bits.toAugmentedBytes() + + const cellBuf = Buffer.alloc(2 + data.length + cell.refs.length * refSize) + cellBuf[0] = d1 + cellBuf[1] = d2 + data.copy(cellBuf, 2) + + let off = 2 + data.length + for (const ref of cell.refs) { + const idx = hashToIdx.get(cellHash(ref).toString('hex'))! + for (let i = refSize - 1; i >= 0; i--) { + cellBuf[off++] = (idx >> (i * 8)) & 0xFF + } + } + serializedCells.push(cellBuf) + totalDataSize += cellBuf.length + } + + const offsetSize = totalDataSize <= 0xFF ? 1 : totalDataSize <= 0xFFFF ? 2 : 4 + + // BOC header: magic + flags_byte + offset_size + cell_count + roots + absent + data_len + root_idx + cells + const headerBuf = Buffer.alloc(4 + 1 + 1 + refSize * 3 + offsetSize + refSize) + let p = 0 + // Magic: b5ee9c72 + headerBuf[p++] = 0xB5; headerBuf[p++] = 0xEE; headerBuf[p++] = 0x9C; headerBuf[p++] = 0x72 + // Flags: has_idx=0, has_crc32=0, has_cache_bits=0, flags=0, ref_size + headerBuf[p++] = refSize + // Offset size + headerBuf[p++] = offsetSize + // Cell count (refSize bytes BE) + for (let i = refSize - 1; i >= 0; i--) headerBuf[p++] = (cellCount >> (i * 8)) & 0xFF + // Root count = 1 + for (let i = refSize - 1; i >= 0; i--) headerBuf[p++] = (i === 0 ? 1 : 0) + // Absent count = 0 + for (let i = 0; i < refSize; i++) headerBuf[p++] = 0 + // Data size (offsetSize bytes BE) + for (let i = offsetSize - 1; i >= 0; i--) headerBuf[p++] = (totalDataSize >> (i * 8)) & 0xFF + // Root index = 0 + for (let i = 0; i < refSize; i++) headerBuf[p++] = 0 + + return Buffer.concat([headerBuf, ...serializedCells]).toString('base64') +} + +// ── Transfer message construction ───────────────────────────────────── + +const V4R2_WALLET_ID = 698983191 // 0x29A9A317 + +/** Build the internal message cell (transfer to recipient) */ +function buildInternalMessage( + destWorkchain: number, + destHash: Buffer, + amountNano: bigint, + bounce: boolean, + memo?: string, +): Cell { + const { cell, bits } = newCell() + + // int_msg_info$0 + bits.writeBit(false) // tag = 0 + bits.writeBit(true) // ihr_disabled + bits.writeBit(bounce) // bounce + bits.writeBit(false) // bounced + bits.writeAddressNone() // src = addr_none + bits.writeAddress(destWorkchain, destHash) // dest + bits.writeCoins(amountNano) // value + bits.writeBit(false) // extra_currencies = empty + bits.writeCoins(0n) // ihr_fee + bits.writeCoins(0n) // fwd_fee + bits.writeUint(0, 64) // created_lt + bits.writeUint(0, 32) // created_at + bits.writeBit(false) // no StateInit + + if (memo && memo.length > 0) { + // Body as reference cell with text comment (op=0x00000000 + utf8) + bits.writeBit(true) // body is ref + const { cell: bodyCell, bits: bodyBits } = newCell() + bodyBits.writeUint(0, 32) // op = 0 (text comment) + bodyBits.writeBytes(Buffer.from(memo, 'utf8')) + cell.refs.push(bodyCell) + } else { + bits.writeBit(false) // no body + } + + return cell +} + +/** Build the unsigned body cell (what gets hashed → signed) */ +function buildUnsignedBody( + seqno: number, + expireAt: number, + internalMsg: Cell, +): Cell { + const { cell, bits } = newCell() + + bits.writeUint(V4R2_WALLET_ID, 32) // wallet_id + bits.writeUint(expireAt, 32) // valid_until + bits.writeUint(seqno, 32) // seqno + bits.writeUint(0, 8) // op = 0 (simple send for v4r2) + bits.writeUint(3, 8) // send_mode = 3 (pay fees separately + ignore errors) + cell.refs.push(internalMsg) + + return cell +} + +/** Build the signed body cell (signature prepended to unsigned body) */ +function buildSignedBody( + signature: Buffer, + seqno: number, + expireAt: number, + internalMsg: Cell, +): Cell { + const { cell, bits } = newCell() + + bits.writeBytes(signature) // 512 bits = 64 bytes + bits.writeUint(V4R2_WALLET_ID, 32) + bits.writeUint(expireAt, 32) + bits.writeUint(seqno, 32) + bits.writeUint(0, 8) // op + bits.writeUint(3, 8) // send_mode + cell.refs.push(internalMsg) + + return cell +} + +// ── StateInit for wallet deployment ─────────────────────────────────── + +/** Deserialize a BOC base64 string into its root cell (minimal parser for single-root BOCs) */ +function deserializeBocToCell(bocB64: string): Cell { + const buf = Buffer.from(bocB64, 'base64') + // Parse BOC header: magic(4) + flags_byte(1) + offset_size(1) + ... + // We trust the well-known v4r2 code BOC — just extract the root cell + const magic = buf.readUInt32BE(0) + if (magic !== 0xB5EE9C72) throw new Error('Invalid BOC magic') + const flagsByte = buf[4] + const refSize = flagsByte & 0x07 + const hasCrc = (flagsByte >> 6) & 1 + const offsetSize = buf[5] + const p = 6 + const cellCount = readBE(buf, p, refSize) + const rootCount = readBE(buf, p + refSize, refSize) + const absentCount = readBE(buf, p + refSize * 2, refSize) + const dataSize = readBE(buf, p + refSize * 3, offsetSize) + const rootIdx = readBE(buf, p + refSize * 3 + offsetSize, refSize) + + const cellDataStart = p + refSize * 3 + offsetSize + refSize * rootCount + + // Parse cells sequentially (each: d1 + d2 + data + ref_indices) + const cells: Cell[] = [] + let offset = cellDataStart + for (let i = 0; i < cellCount; i++) { + const d1 = buf[offset++] + const d2 = buf[offset++] + const refsCount = d1 & 0x07 + const dataByteLen = Math.ceil(d2 / 2) + const dataBits = new BitWriter(dataByteLen * 8) + const rawData = buf.subarray(offset, offset + dataByteLen) + // Compute actual bit length: if d2 is odd, last byte has completion tag + let bitLen = dataByteLen * 8 + if (d2 % 2 === 1 && dataByteLen > 0) { + // Find the completion tag (last 1-bit) + const lastByte = rawData[dataByteLen - 1] + let trailing = 0 + for (let b = 0; b < 8; b++) { + if ((lastByte >> b) & 1) { trailing = b; break } + } + bitLen = (dataByteLen - 1) * 8 + (7 - trailing) + } + for (let b = 0; b < bitLen; b++) { + dataBits.writeBit(!!(rawData[b >> 3] & (0x80 >> (b & 7)))) + } + offset += dataByteLen + + const refIndices: number[] = [] + for (let r = 0; r < refsCount; r++) { + refIndices.push(readBE(buf, offset, refSize)) + offset += refSize + } + cells.push({ bits: dataBits, refs: [], _refIndices: refIndices } as any) + } + + // Resolve references + for (const c of cells) { + const indices = (c as any)._refIndices as number[] + c.refs = indices.map(idx => cells[idx]) + delete (c as any)._refIndices + } + + return cells[rootIdx] +} + +function readBE(buf: Buffer, offset: number, size: number): number { + let val = 0 + for (let i = 0; i < size; i++) val = (val << 8) | buf[offset + i] + return val +} + +/** Cached v4r2 code cell (parsed once) */ +let _v4r2CodeCell: Cell | null = null +function getV4R2CodeCell(): Cell { + if (!_v4r2CodeCell) _v4r2CodeCell = deserializeBocToCell(V4R2_CODE_BOC_B64) + return _v4r2CodeCell +} + +/** Build the initial data cell for a v4r2 wallet: seqno(32) + wallet_id(32) + pubkey(256) */ +function buildV4R2DataCell(publicKey: Buffer): Cell { + const { cell, bits } = newCell() + bits.writeUint(0, 32) // seqno = 0 + bits.writeUint(V4R2_WALLET_ID, 32) // wallet_id + bits.writeBytes(publicKey) // 256-bit public key + bits.writeBit(false) // plugins dict = empty + return cell +} + +/** Build a StateInit cell: split_depth:nothing + special:nothing + code:just + data:just + library:nothing */ +function buildStateInit(code: Cell, data: Cell): Cell { + const { cell, bits } = newCell() + bits.writeBit(false) // split_depth = nothing + bits.writeBit(false) // special = nothing + bits.writeBit(true) // code = just (ref) + bits.writeBit(true) // data = just (ref) + bits.writeBit(false) // library = nothing + cell.refs.push(code) + cell.refs.push(data) + return cell +} + +/** Build the external message cell wrapping the signed body, with optional StateInit */ +function buildExternalMessage( + destWorkchain: number, + destHash: Buffer, + signedBody: Cell, + stateInit?: Cell, +): Cell { + const { cell, bits } = newCell() + + // ext_in_msg_info$10 + bits.writeUint(2, 2) // tag = 0b10 + bits.writeAddressNone() // src = addr_none + bits.writeAddress(destWorkchain, destHash) // dest = wallet address + bits.writeCoins(0n) // import_fee = 0 + + if (stateInit) { + bits.writeBit(true) // Maybe(Either StateInit ^StateInit) = just + bits.writeBit(true) // Either = right (StateInit as ref) + cell.refs.push(stateInit) + } else { + bits.writeBit(false) // no StateInit + } + + bits.writeBit(true) // body is ref + cell.refs.push(signedBody) + + return cell +} + +// ── Public API ──────────────────────────────────────────────────────── + +export interface TonBuildResult { + /** Hex-encoded hash of the unsigned body cell — firmware signs this */ + bodyHash: string + /** Serialized unsigned body cell bytes (hex) — passed as rawTx to firmware */ + rawTx: string + /** Parameters for display on device */ + seqno: number + expireAt: number + toAddress: string + amountNano: string + /** Whether the wallet needs deployment (first tx includes StateInit) */ + needsDeploy: boolean + /** Public key hex (needed for StateInit if deploying) */ + publicKeyHex?: string + /** Internal state needed to build signed BOC after signing */ + _internal: { + destWorkchain: number + destHash: string // hex — survives JSON serialization (Buffer doesn't) + fromWorkchain: number + fromHash: string // hex + amountNano: string // string — BigInt can't be JSON.stringified + bounce: boolean + memo?: string + } +} + +/** Build a TON v4r2 wallet transfer. Returns unsigned body hash + data for signing. */ +export function buildTonTransfer(params: { + fromAddress: string + to: string + amountNano: string + memo?: string + seqno: number + expireAt: number + needsDeploy?: boolean + publicKeyHex?: string +}): TonBuildResult { + const from = parseTonAddress(params.fromAddress) + const dest = parseTonAddress(params.to) + + // Non-bounceable for uninitialised wallets, bounceable for active contracts + // Default to bounceable (safer: funds bounce back if dest doesn't exist) + const bounce = params.to.startsWith('EQ') || params.to.includes(':') + + const amountNano = BigInt(params.amountNano) + const internalMsg = buildInternalMessage(dest.workchain, dest.hash, amountNano, bounce, params.memo) + const unsignedBody = buildUnsignedBody(params.seqno, params.expireAt, internalMsg) + const bodyHashBuf = cellHash(unsignedBody) + + return { + bodyHash: bodyHashBuf.toString('hex'), + rawTx: bodyHashBuf.toString('hex'), // 32 bytes — firmware signs this directly + seqno: params.seqno, + expireAt: params.expireAt, + toAddress: params.to, + amountNano: params.amountNano, + needsDeploy: !!params.needsDeploy, + publicKeyHex: params.publicKeyHex, + _internal: { + destWorkchain: dest.workchain, + destHash: dest.hash.toString('hex'), + fromWorkchain: from.workchain, + fromHash: from.hash.toString('hex'), + amountNano: amountNano.toString(), + bounce, + memo: params.memo, + }, + } +} + +/** Assemble the signed BOC from build result + 64-byte Ed25519 signature → base64 BOC */ +export function assembleTonSignedBoc( + buildResult: TonBuildResult, + signature: Buffer, +): string { + const { _internal: int } = buildResult + // Reconstruct Buffers from hex (survives JSON round-trip through RPC) + const destHash = Buffer.from(int.destHash, 'hex') + const fromHash = Buffer.from(int.fromHash, 'hex') + const internalMsg = buildInternalMessage(int.destWorkchain, destHash, BigInt(int.amountNano), int.bounce, int.memo) + const signedBody = buildSignedBody(signature, buildResult.seqno, buildResult.expireAt, internalMsg) + + // For uninitialized wallets, include StateInit (deploys the wallet contract) + let stateInit: Cell | undefined + if (buildResult.needsDeploy && buildResult.publicKeyHex) { + const pubKey = Buffer.from(buildResult.publicKeyHex, 'hex') + const codeCell = getV4R2CodeCell() + const dataCell = buildV4R2DataCell(pubKey) + stateInit = buildStateInit(codeCell, dataCell) + } + + const extMsg = buildExternalMessage(int.fromWorkchain, fromHash, signedBody, stateInit) + return serializeBoc(extMsg) +} + +const TON_API_TIMEOUT_MS = 30_000 + +function tonFetch(url: string, init?: RequestInit): Promise { + const controller = new AbortController() + const timer = setTimeout(() => controller.abort(), TON_API_TIMEOUT_MS) + return fetch(url, { ...init, signal: controller.signal }).finally(() => clearTimeout(timer)) +} + +/** Check if a TON wallet is initialized (has contract code deployed) */ +export async function getTonWalletState(address: string): Promise<{ initialized: boolean; balance: string }> { + const resp = await tonFetch(`https://toncenter.com/api/v2/getAddressInformation?address=${encodeURIComponent(address)}`) + const data = await resp.json() as any + if (!data?.ok) throw new Error(`Failed to get TON wallet state: ${data?.error || 'unknown'}`) + const state = data?.result?.state + const balance = data?.result?.balance || '0' + return { initialized: state === 'active', balance } +} + +/** Fetch the current seqno for a TON wallet address. Returns 0 for uninitialized wallets. */ +export async function getTonSeqno(address: string): Promise { + const resp = await tonFetch('https://toncenter.com/api/v2/runGetMethod', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ address, method: 'seqno', stack: [] }), + }) + const data = await resp.json() as any + if (!data?.ok) return 0 // uninitialized wallet → seqno 0 + const exitCode = data?.result?.exit_code + if (exitCode !== 0) return 0 // contract method failed → likely uninitialized + const stack = data?.result?.stack + if (!stack || !stack[0]) return 0 + // stack[0] = ["num", "0x..."] + const val = stack[0][1] || stack[0] + const seqno = typeof val === 'string' ? parseInt(val, 16) : Number(val) + if (!Number.isFinite(seqno) || seqno < 0 || seqno > 0xFFFFFFFF) { + throw new Error(`Invalid TON seqno: ${seqno}`) + } + return seqno +} + +/** Broadcast a signed BOC to the TON network via TON Center */ +export async function broadcastTonBoc(bocBase64: string): Promise { + const resp = await tonFetch('https://toncenter.com/api/v2/sendBoc', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ boc: bocBase64 }), + }) + const data = await resp.json() as any + if (!data?.ok) { + const err = data?.error || data?.description || 'unknown' + throw new Error(`TON broadcast failed: ${err}`) + } + // TON doesn't return txid from sendBoc — compute from external message hash + // Return the hash field if available, otherwise a placeholder + return data?.result?.hash || data?.result?.msg_hash || 'pending' +} diff --git a/projects/keepkey-vault/src/mainview/App.tsx b/projects/keepkey-vault/src/mainview/App.tsx index 6f9dbe1f..162cd544 100644 --- a/projects/keepkey-vault/src/mainview/App.tsx +++ b/projects/keepkey-vault/src/mainview/App.tsx @@ -25,7 +25,7 @@ import { useDeviceState } from "./hooks/useDeviceState" import { useUpdateState } from "./hooks/useUpdateState" import { rpcRequest, onRpcMessage } from "./lib/rpc" import { Z } from "./lib/z-index" -import { SwapTracker } from "./components/SwapTracker" +import { ActivityTracker } from "./components/ActivityTracker" import type { PinRequestType, PairingRequestInfo, SigningRequestInfo, ApiLogEntry, AppSettings } from "../shared/types" type AppPhase = "splash" | "claimed" | "setup" | "ready" @@ -623,7 +623,7 @@ function App() { wcUri={wcUri} onClose={handleCloseWalletConnect} /> - {swapsEnabled && } + {/* Enable API Bridge dialog — shown when user tries to launch an app with REST disabled */} {(pendingAppUrl || pendingWcOpen) && ( <> diff --git a/projects/keepkey-vault/src/mainview/components/ActivityPanel.tsx b/projects/keepkey-vault/src/mainview/components/ActivityPanel.tsx new file mode 100644 index 00000000..8d598e11 --- /dev/null +++ b/projects/keepkey-vault/src/mainview/components/ActivityPanel.tsx @@ -0,0 +1,462 @@ +/** + * ActivityPanel — drawer showing recent transaction activity per network. + * + * Forced network select dropdown + refresh icon triggers Pioneer scan. + * No "All" — user must pick a network. Only shows that network's txids. + */ +import { useState, useEffect, useMemo, useCallback, useRef } from "react" +import { Box, Text, Flex, VStack, HStack, Image } from "@chakra-ui/react" +import { rpcRequest } from "../lib/rpc" +import { Z } from "../lib/z-index" +import { CHAINS } from "../../shared/chains" +import { caipToIcon } from "../../shared/assetLookup" +import type { RecentActivity, PendingSwap, ChainBalance } from "../../shared/types" + +interface ActivityPanelProps { + open: boolean + onClose: () => void + activities: RecentActivity[] + pendingSwaps: PendingSwap[] + onRefresh: () => void +} + +const CHAIN_COLORS: Record = {} +CHAINS.forEach(c => { CHAIN_COLORS[c.symbol] = c.color; CHAIN_COLORS[c.id] = c.color }) + +const TYPE_CONFIG: Record = { + send: { label: 'Sent', color: '#E53E3E' }, + receive: { label: 'Received', color: '#23DCC8' }, + swap: { label: 'Swap', color: '#F7931A' }, + sign: { label: 'Signed', color: '#627EEA' }, + message: { label: 'Message', color: '#8247E5' }, + approve: { label: 'Approve', color: '#FF0420' }, +} + +const STATUS_CONFIG: Record = { + broadcast: { label: 'Broadcast', color: '#23DCC8' }, + signed: { label: 'Signed', color: '#F7931A' }, + failed: { label: 'Failed', color: '#E53E3E' }, +} + +// Required confirmations per chain family before considered "confirmed" +const CONF_REQUIRED: Record = { + BTC: 6, LTC: 6, DOGE: 6, DASH: 6, BCH: 6, DGB: 6, ZEC: 24, + ETH: 12, MATIC: 128, AVAX: 12, BNB: 15, ARB: 12, OP: 12, BASE: 12, + ATOM: 1, RUNE: 1, CACAO: 1, OSMO: 1, + XRP: 1, SOL: 32, TRX: 19, TON: 1, MON: 12, HYPE: 12, +} +function getRequiredConfs(symbol: string): number { return CONF_REQUIRED[symbol] || 6 } + +/** Confirmation badge: red = unconfirmed, yellow = partial, green = confirmed */ +function ConfBadge({ confirmations, chain }: { confirmations?: number; chain: string }) { + if (confirmations === undefined) return null + const required = getRequiredConfs(chain) + const isConfirmed = confirmations >= required + const isUnconfirmed = confirmations === 0 + const color = isUnconfirmed ? '#E53E3E' : isConfirmed ? '#23DCC8' : '#F7931A' + const label = isUnconfirmed ? 'Unconfirmed' : isConfirmed ? 'Confirmed' : `${confirmations}/${required}` + return ( + + + {label} + + ) +} + +function getExplorerUrl(chainSymbol: string, txid: string): string | null { + if (!chainSymbol || !txid) return null + const chain = CHAINS.find(c => c.symbol === chainSymbol || c.id === chainSymbol) + if (!chain?.explorerTxUrl) return null + return chain.explorerTxUrl.replace('{{txid}}', txid) +} + +function truncateTxid(txid: string): string { + if (txid.length <= 16) return txid + return txid.slice(0, 8) + '...' + txid.slice(-8) +} + +function timeAgo(ts: number): string { + const diff = Date.now() - ts + if (diff < 60_000) return 'just now' + if (diff < 3600_000) return `${Math.floor(diff / 60_000)}m ago` + if (diff < 86400_000) return `${Math.floor(diff / 3600_000)}h ago` + return `${Math.floor(diff / 86400_000)}d ago` +} + +function ActivityRow({ activity }: { activity: RecentActivity }) { + const [copied, setCopied] = useState(false) + const typeConf = TYPE_CONFIG[activity.type] || TYPE_CONFIG.sign + const statusConf = STATUS_CONFIG[activity.status] || STATUS_CONFIG.signed + + const handleCopy = (text: string) => { + navigator.clipboard.writeText(text) + setCopied(true) + setTimeout(() => setCopied(false), 1500) + } + + const explorerUrl = activity.txid ? getExplorerUrl(activity.chain, activity.txid) : null + + const isUnconfirmed = activity.confirmations !== undefined && activity.confirmations === 0 + + return ( + + + + {typeConf.label} + {activity.source === 'api' && ( + API + )} + + + {timeAgo(activity.createdAt)} + + {(activity.amount || activity.fee) && ( + + {activity.amount && {activity.amount} {activity.asset || activity.chain}} + {activity.fee && fee: {activity.fee}} + + )} + + {activity.txid ? ( + handleCopy(activity.txid!)} title={copied ? 'Copied!' : 'Click to copy'}> + {copied ? 'Copied!' : truncateTxid(activity.txid)} + + ) : ( + no txid + )} + + {activity.blockHeight ? blk {activity.blockHeight} : null} + {explorerUrl && rpcRequest('openUrl', { url: explorerUrl })}>Explorer} + + + + ) +} + +function SwapRow({ swap }: { swap: PendingSwap }) { + const [copied, setCopied] = useState(false) + const handleCopy = (text: string) => { navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 1500) } + const explorerUrl = getExplorerUrl(swap.fromSymbol, swap.txid) + const statusColor = swap.status === 'completed' ? '#23DCC8' : swap.status === 'failed' ? '#E53E3E' : swap.status === 'refunded' ? '#F7931A' : '#627EEA' + + return ( + + + + Swap + {swap.fromSymbol} \u2192 {swap.toSymbol} + + {swap.status} + + {swap.fromAmount && {swap.fromAmount} {swap.fromSymbol}{swap.expectedOutput ? ` \u2192 ${swap.expectedOutput} ${swap.toSymbol}` : ''}} + + handleCopy(swap.txid)} title={copied ? 'Copied!' : 'Click to copy'}> + {copied ? 'Copied!' : truncateTxid(swap.txid)} + + + {explorerUrl && rpcRequest('openUrl', { url: explorerUrl })}>Explorer} + {timeAgo(swap.createdAt)} + + + + ) +} + +/** Refresh/scan icon SVG */ +const RefreshIcon = ({ spinning }: { spinning?: boolean }) => ( + + + +) + +type ChainOption = { id: string; symbol: string; coin: string; caip: string; networkId: string; color: string; balanceUsd: number } + +/** Custom dropdown with chain logos */ +function NetworkSelector({ chainOptions, selectedChain, selectedDef, scanning, scanResult, onSelect, onScan }: { + chainOptions: ChainOption[] + selectedChain: string + selectedDef: ReturnType + scanning: boolean + scanResult: string | null + onSelect: (id: string) => void + onScan: () => void +}) { + const [dropdownOpen, setDropdownOpen] = useState(false) + + return ( + + {/* Trigger */} + setDropdownOpen(!dropdownOpen)} + > + {selectedDef ? ( + } + /> + ) : ( + + )} + + {selectedDef ? `${selectedDef.coin} (${selectedDef.symbol})` : 'Select network...'} + + {selectedDef && {selectedDef.networkId}} + {dropdownOpen ? '\u25B2' : '\u25BC'} + + + {/* Refresh icon */} + + + + + {/* Scan result */} + {scanResult && ( + + {scanResult} + + )} + + {/* Dropdown list */} + {dropdownOpen && ( + <> + {/* Click-away */} + setDropdownOpen(false)} /> + + + {chainOptions.map(c => ( + { onSelect(c.id); setDropdownOpen(false) }} + > + } + /> + {c.symbol} + {c.coin} + {c.networkId} + {selectedChain === c.id && {'\u2713'}} + + ))} + + + + )} + + ) +} + +export function ActivityPanel({ open, onClose, activities, pendingSwaps, onRefresh }: ActivityPanelProps) { + const [tab, setTab] = useState<'activity' | 'swaps'>('activity') + const [selectedChain, setSelectedChain] = useState('') + const [scanning, setScanning] = useState(false) + const [scanResult, setScanResult] = useState(null) + const [availableChains, setAvailableChains] = useState([]) + + // Load chains that have balances + useEffect(() => { + if (!open) return + rpcRequest<{ balances: ChainBalance[]; updatedAt: number } | null>('getCachedBalances') + .then(result => { + if (result?.balances) { + setAvailableChains(result.balances) + // Auto-select first chain if none selected + if (!selectedChain && result.balances.length > 0) { + const sorted = [...result.balances].sort((a, b) => b.balanceUsd - a.balanceUsd) + const first = CHAINS.find(c => c.id === sorted[0].chainId) + if (first) setSelectedChain(first.id) + } + } + }) + .catch(() => {}) + }, [open]) + + const chainMap = useMemo(() => new Map(CHAINS.map(c => [c.id, c])), []) + const chainOptions = useMemo(() => { + return availableChains + .map(b => { + const def = chainMap.get(b.chainId) + if (!def) return null + return { id: def.id, symbol: def.symbol, coin: def.coin, caip: def.caip, networkId: def.networkId, color: def.color, balanceUsd: b.balanceUsd } + }) + .filter((c): c is NonNullable => c !== null) + .sort((a, b) => b.balanceUsd - a.balanceUsd) + }, [availableChains, chainMap]) + + const selectedDef = useMemo(() => CHAINS.find(c => c.id === selectedChain), [selectedChain]) + + // Filter activities to selected chain + const filteredActivities = useMemo(() => { + if (!selectedDef) return [] + return activities.filter(a => a.chain === selectedDef.symbol) + }, [activities, selectedDef]) + + const activeSwaps = useMemo(() => + pendingSwaps.filter(s => s.status !== 'completed' && s.status !== 'failed' && s.status !== 'refunded'), + [pendingSwaps] + ) + + const filteredSwaps = useMemo(() => { + if (!selectedDef) return activeSwaps + return activeSwaps.filter(s => s.fromSymbol === selectedDef.symbol) + }, [activeSwaps, selectedDef]) + + const nonSwapActivities = useMemo(() => { + const swapTxids = new Set(pendingSwaps.map(s => s.txid)) + return filteredActivities.filter(a => !(a.type === 'swap' && a.txid && swapTxids.has(a.txid))) + }, [filteredActivities, pendingSwaps]) + + const scanningRef = useRef(false) + const handleScan = useCallback(async () => { + if (!selectedChain || scanningRef.current) return + scanningRef.current = true + setScanning(true) + setScanResult(null) + try { + const result = await rpcRequest<{ count: number }>('scanChainHistory', { chainId: selectedChain }, 60000) + setScanResult(result.count > 0 ? `+${result.count} tx${result.count > 1 ? 's' : ''}` : 'Up to date') + onRefresh() + } catch (e: any) { + setScanResult(e.message || 'Failed') + } finally { + scanningRef.current = false + setScanning(false) + } + }, [selectedChain, onRefresh]) + + useEffect(() => { setScanResult(null) }, [selectedChain]) + + if (!open) return null + + return ( + <> + + + + + {/* Header */} + + Recent Activity + × + + + {/* Tabs */} + + setTab('activity')} + > + History + + {pendingSwaps.length > 0 && ( + setTab('swaps')} + > + Swaps ({pendingSwaps.length}) + + )} + + + {/* Network selector + refresh */} + {tab === 'activity' && ( + + )} + + {/* Content */} + + + {tab === 'activity' && ( + <> + {!selectedChain && ( + + Select a network to view transaction history + + )} + {selectedChain && ( + <> + {filteredSwaps.map(swap => ( + + ))} + {nonSwapActivities.map(activity => ( + + ))} + {nonSwapActivities.length === 0 && filteredSwaps.length === 0 && ( + + No activity for {selectedDef?.symbol} — hit refresh to scan + + )} + + )} + + )} + {tab === 'swaps' && ( + <> + {pendingSwaps.map(swap => ( + + ))} + {pendingSwaps.length === 0 && ( + No swaps + )} + + )} + + + + + ) +} diff --git a/projects/keepkey-vault/src/mainview/components/ActivityTracker.tsx b/projects/keepkey-vault/src/mainview/components/ActivityTracker.tsx new file mode 100644 index 00000000..0363550f --- /dev/null +++ b/projects/keepkey-vault/src/mainview/components/ActivityTracker.tsx @@ -0,0 +1,198 @@ +/** + * ActivityTracker — floating bubble (bottom-left) showing recent transaction activity. + * + * Always visible. Queries api_log (with activity_type) + swap_history. + * Captures: broadcasts, swaps, API signs, messages. + */ +import { useState, useEffect, useCallback, useRef } from "react" +import { Box, Text } from "@chakra-ui/react" +import { rpcRequest, onRpcMessage } from "../lib/rpc" +import { Z } from "../lib/z-index" +import { ActivityPanel } from "./ActivityPanel" +import type { RecentActivity, PendingSwap, SwapStatusUpdate, ApiLogEntry } from "../../shared/types" + +const TRACKER_CSS = ` + @keyframes kkActivityPulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(35,220,200,0.5); } + 50% { box-shadow: 0 0 0 8px rgba(35,220,200,0); } + } + @keyframes kkBounceUp { + 0% { transform: scale(1) translateY(0); } + 20% { transform: scale(1.35) translateY(-6px); } + 40% { transform: scale(1.15) translateY(-2px); } + 60% { transform: scale(1.25) translateY(-4px); } + 80% { transform: scale(1.05) translateY(-1px); } + 100% { transform: scale(1) translateY(0); } + } + @keyframes kkCountSlideUp { + 0% { opacity: 0; transform: translateY(8px) scale(0.8); } + 40% { opacity: 1; transform: translateY(-3px) scale(1.1); } + 70% { transform: translateY(1px) scale(1.0); } + 100% { opacity: 1; transform: translateY(0) scale(1); } + } +` + +export function ActivityTracker() { + const [activities, setActivities] = useState([]) + const [pendingSwaps, setPendingSwaps] = useState([]) + const [panelOpen, setPanelOpen] = useState(false) + const [hasNew, setHasNew] = useState(false) + const [bouncing, setBouncing] = useState(false) + const lastCountRef = useRef(0) + const bounceTimeoutRef = useRef>() + + // Fetch recent activities from api_log + swap_history (unified query) + const fetchActivities = useCallback(() => { + rpcRequest('getRecentActivity', { limit: 50 }, 5000) + .then((result) => { if (result) setActivities(result) }) + .catch((err) => { console.warn('[ActivityTracker] fetch activities failed:', err.message) }) + }, []) + + // Fetch pending swaps (for live swap tracking) + const fetchSwaps = useCallback(() => { + rpcRequest('getPendingSwaps', undefined, 5000) + .then((result) => { if (result) setPendingSwaps(result) }) + .catch((err) => { console.warn('[ActivityTracker] fetch swaps failed:', err.message) }) + }, []) + + // Fetch on mount + useEffect(() => { fetchActivities(); fetchSwaps() }, [fetchActivities, fetchSwaps]) + + // Listen for new api-log entries — re-fetch if it's a sign/broadcast + useEffect(() => { + const unsub = onRpcMessage('api-log', (entry: ApiLogEntry) => { + if (entry.activityType) { + // New sign/broadcast logged — refresh activity list + fetchActivities() + } + }) + return unsub + }, [fetchActivities]) + + // Listen for swap updates (keep swap awareness) + useEffect(() => { + const unsub1 = onRpcMessage('swap-update', (_update: SwapStatusUpdate) => { + fetchSwaps() + }) + const unsub2 = onRpcMessage('swap-complete', (swap: PendingSwap) => { + fetchSwaps() + fetchActivities() + if (swap.status === 'completed' || swap.status === 'refunded') { + window.dispatchEvent(new CustomEvent('keepkey-swap-completed', { + detail: { fromChainId: swap.fromChainId, toChainId: swap.toChainId } + })) + } + }) + return () => { unsub1(); unsub2() } + }, [fetchSwaps, fetchActivities]) + + // Listen for swap-executed DOM event from SwapDialog + useEffect(() => { + let t1: ReturnType + let t2: ReturnType + const handler = () => { + fetchActivities() + fetchSwaps() + t1 = setTimeout(fetchActivities, 1000) + t2 = setTimeout(fetchSwaps, 1000) + } + window.addEventListener('keepkey-swap-executed', handler) + return () => { + window.removeEventListener('keepkey-swap-executed', handler) + clearTimeout(t1) + clearTimeout(t2) + } + }, [fetchActivities, fetchSwaps]) + + // Detect new items — trigger bounce animation + const activeSwapCount = pendingSwaps.filter(s => + s.status !== 'completed' && s.status !== 'failed' && s.status !== 'refunded' + ).length + const totalCount = activities.length + activeSwapCount + useEffect(() => { + if (totalCount > lastCountRef.current && lastCountRef.current > 0) { + setHasNew(true) + setBouncing(true) + if (bounceTimeoutRef.current) clearTimeout(bounceTimeoutRef.current) + bounceTimeoutRef.current = setTimeout(() => setBouncing(false), 700) + } + lastCountRef.current = totalCount + }, [totalCount]) + + const handleOpen = () => { + setPanelOpen(true) + setHasNew(false) + } + + // Label + const displayCount = activities.length + activeSwapCount + let label: string + if (displayCount === 0) { + label = 'Activity' + } else if (activeSwapCount > 0 && activities.length > 0) { + label = `${displayCount} event${displayCount > 1 ? 's' : ''}` + } else if (activeSwapCount > 0) { + label = `${activeSwapCount} swap${activeSwapCount > 1 ? 's' : ''}` + } else { + label = `${activities.length} tx${activities.length > 1 ? 's' : ''}` + } + + const bubbleAnimation = bouncing + ? 'kkBounceUp 0.7s cubic-bezier(0.34, 1.56, 0.64, 1)' + : hasNew + ? 'kkActivityPulse 2s ease-in-out infinite' + : 'none' + + return ( + <> + + + {/* Floating bubble — always visible */} + + 0 ? "rgba(35,220,200,0.15)" : "rgba(255,255,255,0.05)"} + border="1px solid" + borderColor={displayCount > 0 ? "rgba(35,220,200,0.4)" : "rgba(255,255,255,0.1)"} + borderRadius="full" + px="3" + py="1.5" + cursor="pointer" + _hover={{ bg: displayCount > 0 ? "rgba(35,220,200,0.25)" : "rgba(255,255,255,0.1)", transform: "scale(1.05)" }} + transition="all 0.2s" + onClick={handleOpen} + style={{ animation: bubbleAnimation }} + > + {activeSwapCount > 0 ? ( + + ) : ( + 0 ? 1 : 0.5}>⚡ + )} + 0 ? "#23DCC8" : "whiteAlpha.500"} + style={bouncing ? { + display: 'inline-block', + animation: 'kkCountSlideUp 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)', + } : {}} + > + {label} + + + + + {/* Activity panel */} + setPanelOpen(false)} + activities={activities} + pendingSwaps={pendingSwaps} + onRefresh={() => { fetchActivities(); fetchSwaps() }} + /> + + ) +} diff --git a/projects/keepkey-vault/src/mainview/components/AssetPage.tsx b/projects/keepkey-vault/src/mainview/components/AssetPage.tsx index 5ee83fce..5778d6d7 100644 --- a/projects/keepkey-vault/src/mainview/components/AssetPage.tsx +++ b/projects/keepkey-vault/src/mainview/components/AssetPage.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useMemo } from "react" import { useTranslation } from "react-i18next" import { Box, Flex, Text, Button, Image, VStack, HStack, IconButton } from "@chakra-ui/react" -import { FaArrowDown, FaArrowUp, FaExchangeAlt, FaPlus, FaEye, FaEyeSlash, FaShieldAlt, FaCheck } from "react-icons/fa" +import { FaArrowDown, FaArrowUp, FaExchangeAlt, FaPlus, FaEye, FaEyeSlash, FaShieldAlt, FaCheck, FaSyncAlt } from "react-icons/fa" import { rpcRequest } from "../lib/rpc" import type { ChainDef } from "../../shared/chains" import { CHAINS, BTC_SCRIPT_TYPES, btcAccountPath, isChainSupported } from "../../shared/chains" @@ -38,6 +38,24 @@ export function AssetPage({ chain, balance, onBack, firmwareVersion }: AssetPage const [deriveError, setDeriveError] = useState(null) const [currentPath, setCurrentPath] = useState(chain.defaultPath) + // Single-chain refresh + const [refreshing, setRefreshing] = useState(false) + const [refreshedBalance, setRefreshedBalance] = useState(null) + const handleRefresh = useCallback(async () => { + setRefreshing(true) + try { + const updated = await rpcRequest("getBalance", { chainId: chain.id }) + setRefreshedBalance(updated) + } catch (e) { + console.warn(`[AssetPage] refresh ${chain.id} failed:`, e) + } finally { + setRefreshing(false) + } + }, [chain.id]) + + // Use refreshed balance if available, otherwise prop + const activeBalance = refreshedBalance || balance + // Feature flag: swaps const [swapsEnabled, setSwapsEnabled] = useState(false) useEffect(() => { @@ -77,7 +95,11 @@ export function AssetPage({ chain, balance, onBack, firmwareVersion }: AssetPage const effectivePath = (isBtc && btcSelected) ? btcSelected.fullPath : currentPath const effectiveScriptType = (isBtc && btcSelected) ? btcSelected.scriptType : chain.scriptType - const deriveAddress = useCallback(async (path?: number[]) => { + // TON: bounceable toggle (default: non-bounceable / UQ for safe receiving) + const isTon = chain.chainFamily === 'ton' + const [tonBounceable, setTonBounceable] = useState(false) // default: non-bounceable (UQ) + + const deriveAddress = useCallback(async (path?: number[], overrideBounceable?: boolean) => { const usePath = path || effectivePath if (path) setCurrentPath(path) setLoading(true) @@ -90,6 +112,8 @@ export function AssetPage({ chain, balance, onBack, firmwareVersion }: AssetPage } const st = (isBtc && btcSelected) ? btcSelected.scriptType : chain.scriptType if (st) params.scriptType = st + // TON: pass bounceable flag for UQ vs EQ address + if (isTon) params.bounceable = overrideBounceable ?? tonBounceable const result = await rpcRequest(chain.rpcMethod, params, 60000) const addr = typeof result === "string" ? result : result?.address || String(result) setAddress(addr) @@ -99,7 +123,7 @@ export function AssetPage({ chain, balance, onBack, firmwareVersion }: AssetPage setAddress(null) } setLoading(false) - }, [chain, effectivePath, isBtc, btcSelected]) + }, [chain, effectivePath, isBtc, btcSelected, isTon, tonBounceable]) // Re-derive address when BTC xpub selection or change/index changes useEffect(() => { @@ -153,13 +177,13 @@ export function AssetPage({ chain, balance, onBack, firmwareVersion }: AssetPage } }, [isEvm, evmAddresses.selectedIndex, evmAddresses.addresses]) - // Only auto-derive once on mount, not on every address change + // Auto-derive once on mount; TON always re-derives to ensure correct bounceable flag useEffect(() => { - if (!address && !deriveError) deriveAddress() + if (isTon || (!address && !deriveError)) deriveAddress() }, []) // eslint-disable-line react-hooks/exhaustive-deps // ── Token spam filter ────────────────────────────────────────────── - const tokens = useMemo(() => balance?.tokens || [], [balance?.tokens]) + const tokens = useMemo(() => activeBalance?.tokens || [], [activeBalance?.tokens]) const [visibilityMap, setVisibilityMap] = useState>({}) const [showHidden, setShowHidden] = useState(false) @@ -202,7 +226,7 @@ export function AssetPage({ chain, balance, onBack, firmwareVersion }: AssetPage const hiddenCount = spamTokens.length + zeroValueTokens.length const tokenTotalUsd = useMemo(() => cleanTokens.reduce((sum, t) => sum + (t.balanceUsd || 0), 0), [cleanTokens]) const spamTotalUsd = useMemo(() => spamTokens.reduce((sum, t) => sum + (t.balanceUsd || 0), 0), [spamTokens]) - const cleanBalanceUsd = (balance?.balanceUsd || 0) - spamTotalUsd + const cleanBalanceUsd = (activeBalance?.balanceUsd || 0) - spamTotalUsd const [showAddToken, setShowAddToken] = useState(false) const [showSwapDialog, setShowSwapDialog] = useState(false) @@ -405,14 +429,26 @@ export function AssetPage({ chain, balance, onBack, firmwareVersion }: AssetPage /> {chain.coin} {chain.symbol} - {balance && ( - + {activeBalance && ( + - {balance.balance} {chain.symbol} + {activeBalance.balance} {chain.symbol} {cleanBalanceUsd > 0 && ( )} + + + )} @@ -511,13 +547,16 @@ export function AssetPage({ chain, balance, onBack, firmwareVersion }: AssetPage btcAddressIndex={btcAddressIndex} onBtcChangeIndex={handleBtcChangeIndex} onBtcAddressIndex={setBtcAddressIndex} + isTon={isTon} + tonBounceable={tonBounceable} + onTonBounceableChange={(v) => { setTonBounceable(v); deriveAddress(undefined, v) }} /> )} {view === "send" && ( setSelectedToken(null)} xpubOverride={isBtc ? btcSelected?.xpubData?.xpub : undefined} @@ -530,7 +569,7 @@ export function AssetPage({ chain, balance, onBack, firmwareVersion }: AssetPage open={showSwapDialog} onClose={() => setShowSwapDialog(false)} chain={chain} - balance={balance} + balance={activeBalance} address={address} /> {view === "privacy" && isZcash && ( diff --git a/projects/keepkey-vault/src/mainview/components/Dashboard.tsx b/projects/keepkey-vault/src/mainview/components/Dashboard.tsx index ef0c75bb..b1cf526b 100644 --- a/projects/keepkey-vault/src/mainview/components/Dashboard.tsx +++ b/projects/keepkey-vault/src/mainview/components/Dashboard.tsx @@ -13,6 +13,7 @@ import { Bip85VaultDialog } from "./Bip85VaultDialog" import { rpcRequest, onRpcMessage } from "../lib/rpc" import { categorizeTokens } from "../../shared/spamFilter" import type { ChainBalance, CustomChain, TokenVisibilityStatus, AppSettings } from "../../shared/types" +import { playChaChing } from "../lib/sounds" const DASHBOARD_ANIMATIONS = ` @keyframes pulseGold { @@ -199,6 +200,25 @@ export function Dashboard({ onLoaded, watchOnly, onOpenSettings, firmwareVersion return () => window.removeEventListener('keepkey-swap-completed', handler) }, [refreshBalances]) + // Live balance sync: merge single-chain updates from backend (e.g. AssetPage refresh) + useEffect(() => { + return onRpcMessage("balance-updated", (updated: ChainBalance) => { + setBalances(prev => { + const old = prev.get(updated.chainId) + const oldUsd = old?.balanceUsd ?? 0 + const newUsd = updated.balanceUsd ?? 0 + // Cha-ching when balance increased + if (newUsd > oldUsd && oldUsd > 0) { + playChaChing() + } + const next = new Map(prev) + next.set(updated.chainId, updated) + return next + }) + setCacheUpdatedAt(Date.now()) + }) + }, []) + // Compute spam-filtered USD per chain: subtract spam token values from chain totals const cleanBalanceUsd = useMemo(() => { const overrides = new Map(Object.entries(visibilityMap).map(([k, v]) => [k.toLowerCase(), v])) diff --git a/projects/keepkey-vault/src/mainview/components/ReceiveView.tsx b/projects/keepkey-vault/src/mainview/components/ReceiveView.tsx index 99aea866..f429a0e0 100644 --- a/projects/keepkey-vault/src/mainview/components/ReceiveView.tsx +++ b/projects/keepkey-vault/src/mainview/components/ReceiveView.tsx @@ -25,6 +25,10 @@ interface ReceiveViewProps { btcAddressIndex?: number onBtcChangeIndex?: (v: 0 | 1) => void onBtcAddressIndex?: (v: number) => void + // TON bounceable toggle + isTon?: boolean + tonBounceable?: boolean + onTonBounceableChange?: (bounceable: boolean) => void } function CopyableField({ label, value, mono = true }: { label: string; value: string; mono?: boolean }) { @@ -84,6 +88,7 @@ function CopyableField({ label, value, mono = true }: { label: string; value: st export function ReceiveView({ chain, address, loading, error, currentPath, onDerive, scriptType, xpub, isBtc, btcChangeIndex = 0, btcAddressIndex = 0, onBtcChangeIndex, onBtcAddressIndex, + isTon, tonBounceable = false, onTonBounceableChange, }: ReceiveViewProps) { const { t } = useTranslation("receive") const [showing, setShowing] = useState(false) @@ -100,10 +105,11 @@ export function ReceiveView({ } const st = scriptType || chain.scriptType if (st) params.scriptType = st + if (isTon) params.bounceable = tonBounceable await rpcRequest(chain.rpcMethod, params, 60000) } catch (e: any) { console.error("showOnDevice:", e) } setShowing(false) - }, [chain, currentPath, scriptType]) + }, [chain, currentPath, scriptType, isTon, tonBounceable]) const handlePathApply = useCallback((newPath: number[]) => { setPathDialogOpen(false) @@ -275,6 +281,49 @@ export function ReceiveView({ )} + {/* TON: bounceable / non-bounceable toggle */} + {isTon && onTonBounceableChange && ( + + + + Address Type + + + + + + + {!tonBounceable && ( + + Non-bounceable — funds won't bounce back if wallet is uninitialized + + )} + {tonBounceable && ( + + Bounceable — funds will bounce back if wallet contract is not deployed + + )} + + )} + {/* Address — copyable */} diff --git a/projects/keepkey-vault/src/mainview/components/SendForm.tsx b/projects/keepkey-vault/src/mainview/components/SendForm.tsx index ed5aa345..a07d1dd4 100644 --- a/projects/keepkey-vault/src/mainview/components/SendForm.tsx +++ b/projects/keepkey-vault/src/mainview/components/SendForm.tsx @@ -7,6 +7,7 @@ import { getAsset } from "../../shared/assetLookup" import { QrScannerOverlay } from "./QrScannerOverlay" import type { ChainDef } from "../../shared/chains" import type { ChainBalance, TokenBalance, BuildTxResult, BroadcastResult } from "../../shared/types" +import { validateAddress } from "../../shared/address-validation" type SendPhase = 'input' | 'built' | 'signed' | 'broadcast' @@ -139,15 +140,14 @@ export function SendForm({ chain, address, balance, token, onClearToken, xpubOve return n * pricePerUnit }, [amount, hasPrice, pricePerUnit, isMax]) - const recipientTooShort = useMemo(() => { - if (!recipient) return false - // Most addresses are 25+ chars; catch obvious typos - return recipient.length > 0 && recipient.length < 10 - }, [recipient]) + const addressValidation = useMemo(() => { + if (!recipient) return null + return validateAddress(recipient, chain) + }, [recipient, chain]) const handleBuild = useCallback(async () => { if (!recipient || (!amount && !isMax)) return - if (recipientTooShort) { setError(t("addressTooShort")); return } + if (addressValidation && !addressValidation.valid) { setError(t(addressValidation.error!)); return } if (exceedsBalance) { setError(t("exceedsBalanceShort")); return } setLoading(true) setError(null) @@ -174,7 +174,7 @@ export function SendForm({ chain, address, balance, token, onClearToken, xpubOve setError(e.message || t("failedToBuild")) } setLoading(false) - }, [chain, recipient, amount, memo, feeLevel, isMax, recipientTooShort, exceedsBalance, isTokenSend, token, xpubOverride, scriptTypeOverride, evmAddressIndex]) + }, [chain, recipient, amount, memo, feeLevel, isMax, addressValidation, exceedsBalance, isTokenSend, token, xpubOverride, scriptTypeOverride, evmAddressIndex]) const handleSign = useCallback(async () => { if (!buildResult) return @@ -350,6 +350,9 @@ export function SendForm({ chain, address, balance, token, onClearToken, xpubOve + {addressValidation && !addressValidation.valid && ( + {t(addressValidation.error!)} + )} {/* Amount input with USD conversion */} @@ -458,7 +461,7 @@ export function SendForm({ chain, address, balance, token, onClearToken, xpubOve color="black" _hover={{ bg: "kk.goldHover" }} onClick={handleBuild} - disabled={loading || !recipient || (!amount && !isMax)} + disabled={loading || !recipient || (!amount && !isMax) || (addressValidation != null && !addressValidation.valid)} w="full" > {loading ? t("buildingTransaction") : t("buildTransaction")} diff --git a/projects/keepkey-vault/src/mainview/components/ZcashPrivacyTab.tsx b/projects/keepkey-vault/src/mainview/components/ZcashPrivacyTab.tsx index b59674b3..6868a7ac 100644 --- a/projects/keepkey-vault/src/mainview/components/ZcashPrivacyTab.tsx +++ b/projects/keepkey-vault/src/mainview/components/ZcashPrivacyTab.tsx @@ -1,10 +1,21 @@ -import { useState, useEffect, useCallback, useRef } from "react" +import { useState, useEffect, useCallback, useRef, useMemo } from "react" import { useTranslation } from "react-i18next" import { Box, Flex, Text, Button, Input, Spinner } from "@chakra-ui/react" import { FaShieldAlt, FaCopy, FaCheck } from "react-icons/fa" import { rpcRequest, onRpcMessage } from "../lib/rpc" import { generateQRSvg } from "../lib/qr" +/** Validate Zcash recipient: unified (u1...), Sapling (zs1...), or transparent (t1.../t3...) */ +function validateZcashRecipient(addr: string): { valid: boolean; error?: string } { + const s = addr.trim() + if (!s) return { valid: false } + if (s.startsWith('u1') && s.length >= 70) return { valid: true } + if (s.startsWith('zs1') && s.length >= 70) return { valid: true } + const BASE58 = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/ + if ((s.startsWith('t1') || s.startsWith('t3')) && s.length === 35 && BASE58.test(s)) return { valid: true } + return { valid: false, error: 'invalidZcashRecipient' } +} + type SidecarStatus = "checking" | "ready" | "not_running" | "initializing" type ScanState = "idle" | "scanning" | "done" @@ -49,6 +60,12 @@ export function ZcashPrivacyTab() { const [sendResult, setSendResult] = useState(null) const [sendError, setSendError] = useState(null) + // Address validation + const recipientValidation = useMemo(() => { + if (!recipient) return null + return validateZcashRecipient(recipient) + }, [recipient]) + // ── Fetch balance ───────────────────────────────────────────────── const refreshBalance = useCallback(async () => { try { @@ -183,6 +200,10 @@ export function ZcashPrivacyTab() { // ── Send shielded ───────────────────────────────────────────────── const handleSend = useCallback(async () => { if (!recipient || !amount) return + if (recipientValidation && !recipientValidation.valid) { + setSendError(recipientValidation.error ? t(recipientValidation.error) : t("invalidZcashRecipient")) + return + } setSending(true) setSendError(null) setSendResult(null) @@ -192,8 +213,10 @@ export function ZcashPrivacyTab() { const whole = BigInt(parts[0] || "0") * 100_000_000n const fracStr = (parts[1] || "").padEnd(8, "0").slice(0, 8) const frac = BigInt(fracStr) - const zatoshis = Number(whole + frac) - if (!Number.isFinite(zatoshis) || zatoshis <= 0) throw new Error("Invalid amount") + const zatoshisBig = whole + frac + // ZEC max supply is 21M = 2,100,000,000,000,000 zatoshis — fits in Number.MAX_SAFE_INTEGER + if (zatoshisBig <= 0n || zatoshisBig > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("Invalid amount") + const zatoshis = Number(zatoshisBig) // Validate memo length (Orchard memo field is 512 bytes max) if (memo && new TextEncoder().encode(memo).length > 512) { throw new Error(t("memoTooLong")) @@ -212,7 +235,7 @@ export function ZcashPrivacyTab() { setSendError(e.message || "Send failed") } setSending(false) - }, [recipient, amount, memo, refreshBalance]) + }, [recipient, amount, memo, recipientValidation, refreshBalance]) // ── Copy address ────────────────────────────────────────────────── const copyAddress = useCallback(() => { @@ -466,6 +489,9 @@ export function ZcashPrivacyTab() { _hover={{ borderColor: "kk.textMuted" }} _focus={{ borderColor: "kk.gold", boxShadow: "none" }} /> + {recipientValidation && !recipientValidation.valid && recipientValidation.error && ( + {t(recipientValidation.error)} + )} {sending ? ( <> {t("sending")} diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/de/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/de/asset.json index 8de6545e..570d7d63 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/de/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/de/asset.json @@ -16,5 +16,6 @@ "hideToken": "Token ausblenden", "revertToAutoDetect": "Auf automatische Erkennung zurücksetzen", "unhide": "Einblenden", - "addCustomToken": "Benutzerdefinierten Token hinzufügen" + "addCustomToken": "Benutzerdefinierten Token hinzufügen", + "invalidZcashRecipient": "Ungültige Adresse — muss u1... (unified) oder t1.../t3... (transparent) sein" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/de/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/de/send.json index 2ef76119..7b98d822 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/de/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/de/send.json @@ -17,7 +17,6 @@ "feeFast": "Schnell", "exceedsBalance": "Betrag übersteigt das verfügbare Guthaben ({{balance}} {{symbol}})", "exceedsBalanceShort": "Betrag übersteigt das verfügbare Guthaben", - "addressTooShort": "Die Adresse scheint zu kurz — bitte überprüfen", "buildingTransaction": "Transaktion wird erstellt...", "buildTransaction": "Transaktion erstellen", "transactionReady": "Transaktion bereit", @@ -36,5 +35,23 @@ "sendAnother": "Weitere senden", "failedToBuild": "Transaktionserstellung fehlgeschlagen", "signingFailed": "Signierung fehlgeschlagen", - "broadcastFailed": "Senden fehlgeschlagen" + "broadcastFailed": "Senden fehlgeschlagen", + "switchInput": "Zwischen Krypto- und USD-Eingabe wechseln", + "invalidAddress": "Ungültige Adresse für dieses Netzwerk", + "invalidBtcAddress": "Ungültige Bitcoin-Adresse — muss mit 1, 3 oder bc1 beginnen", + "invalidLtcAddress": "Ungültige Litecoin-Adresse — muss mit L, M, 3 oder ltc1 beginnen", + "invalidDogeAddress": "Ungültige Dogecoin-Adresse — muss mit D beginnen", + "invalidBchAddress": "Ungültige Bitcoin Cash-Adresse", + "invalidDashAddress": "Ungültige Dash-Adresse — muss mit X beginnen", + "invalidZecAddress": "Ungültige Zcash-Adresse — muss mit t1 oder t3 beginnen", + "invalidDgbAddress": "Ungültige DigiByte-Adresse", + "invalidEvmAddress": "Ungültige Adresse — muss 0x gefolgt von 40 Hexadezimalzeichen sein", + "invalidCosmosPrefix": "Falsches Adresspräfix für dieses Netzwerk", + "invalidCosmosLength": "Ungültige Cosmos-Adresslänge", + "invalidCosmosAddress": "Ungültige Cosmos-Adresse", + "invalidXrpAddress": "Ungültige XRP-Adresse — muss mit r beginnen", + "invalidSolanaAddress": "Ungültige Solana-Adresse — muss 32-44 Base58-Zeichen haben", + "invalidTronAddress": "Ungültige Tron-Adresse — muss mit T beginnen (34 Zeichen)", + "invalidTonAddress": "Ungültige TON-Adresse", + "invalidZcashShieldedAddress": "Ungültige geschützte Adresse — muss mit u1 beginnen" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/en/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/en/asset.json index f5183d67..3252afc5 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/en/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/en/asset.json @@ -44,5 +44,6 @@ "copyAddress": "Copy address", "copied": "Copied", "zcashCliRequired": "Build zcash-cli to enable", - "memoTooLong": "Memo exceeds 512 bytes" + "memoTooLong": "Memo exceeds 512 bytes", + "invalidZcashRecipient": "Invalid address — must be u1... (unified) or t1.../t3... (transparent)" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/en/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/en/send.json index 6fb0725c..c423232a 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/en/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/en/send.json @@ -17,7 +17,6 @@ "feeFast": "Fast", "exceedsBalance": "Amount exceeds available balance ({{balance}} {{symbol}})", "exceedsBalanceShort": "Amount exceeds available balance", - "addressTooShort": "Address looks too short — please verify", "buildingTransaction": "Building Transaction...", "buildTransaction": "Build Transaction", "transactionReady": "Transaction Ready", @@ -37,5 +36,22 @@ "failedToBuild": "Failed to build transaction", "signingFailed": "Signing failed", "broadcastFailed": "Broadcast failed", - "switchInput": "Switch between crypto and USD input" + "switchInput": "Switch between crypto and USD input", + "invalidAddress": "Invalid address for this network", + "invalidBtcAddress": "Invalid Bitcoin address — must start with 1, 3, or bc1", + "invalidLtcAddress": "Invalid Litecoin address — must start with L, M, 3, or ltc1", + "invalidDogeAddress": "Invalid Dogecoin address — must start with D", + "invalidBchAddress": "Invalid Bitcoin Cash address", + "invalidDashAddress": "Invalid Dash address — must start with X", + "invalidZecAddress": "Invalid Zcash address — must start with t1 or t3", + "invalidDgbAddress": "Invalid DigiByte address", + "invalidEvmAddress": "Invalid address — must be 0x followed by 40 hex characters", + "invalidCosmosPrefix": "Wrong address prefix for this network", + "invalidCosmosLength": "Invalid Cosmos address length", + "invalidCosmosAddress": "Invalid Cosmos address", + "invalidXrpAddress": "Invalid XRP address — must start with r", + "invalidSolanaAddress": "Invalid Solana address — must be 32-44 base58 characters", + "invalidTronAddress": "Invalid Tron address — must start with T (34 characters)", + "invalidTonAddress": "Invalid TON address", + "invalidZcashShieldedAddress": "Invalid shielded address — must start with u1" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/es/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/es/asset.json index 1386723e..f1a12dce 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/es/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/es/asset.json @@ -16,5 +16,6 @@ "hideToken": "Ocultar token", "revertToAutoDetect": "Revertir a detección automática", "unhide": "Mostrar", - "addCustomToken": "Agregar token personalizado" + "addCustomToken": "Agregar token personalizado", + "invalidZcashRecipient": "Dirección inválida — debe ser u1... (unified) o t1.../t3... (transparent)" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/es/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/es/send.json index d517395f..85568d66 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/es/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/es/send.json @@ -17,7 +17,6 @@ "feeFast": "Rápida", "exceedsBalance": "La cantidad excede el saldo disponible ({{balance}} {{symbol}})", "exceedsBalanceShort": "La cantidad excede el saldo disponible", - "addressTooShort": "La dirección parece muy corta — por favor verifica", "buildingTransaction": "Construyendo transacción...", "buildTransaction": "Construir transacción", "transactionReady": "Transacción lista", @@ -36,5 +35,23 @@ "sendAnother": "Enviar otra", "failedToBuild": "Error al construir la transacción", "signingFailed": "Error al firmar", - "broadcastFailed": "Error al transmitir" + "broadcastFailed": "Error al transmitir", + "switchInput": "Cambiar entre entrada de cripto y USD", + "invalidAddress": "Dirección inválida para esta red", + "invalidBtcAddress": "Dirección Bitcoin inválida — debe comenzar con 1, 3 o bc1", + "invalidLtcAddress": "Dirección Litecoin inválida — debe comenzar con L, M, 3 o ltc1", + "invalidDogeAddress": "Dirección Dogecoin inválida — debe comenzar con D", + "invalidBchAddress": "Dirección Bitcoin Cash inválida", + "invalidDashAddress": "Dirección Dash inválida — debe comenzar con X", + "invalidZecAddress": "Dirección Zcash inválida — debe comenzar con t1 o t3", + "invalidDgbAddress": "Dirección DigiByte inválida", + "invalidEvmAddress": "Dirección inválida — debe ser 0x seguido de 40 caracteres hexadecimales", + "invalidCosmosPrefix": "Prefijo de dirección incorrecto para esta red", + "invalidCosmosLength": "Longitud de dirección Cosmos inválida", + "invalidCosmosAddress": "Dirección Cosmos inválida", + "invalidXrpAddress": "Dirección XRP inválida — debe comenzar con r", + "invalidSolanaAddress": "Dirección Solana inválida — debe tener 32-44 caracteres base58", + "invalidTronAddress": "Dirección Tron inválida — debe comenzar con T (34 caracteres)", + "invalidTonAddress": "Dirección TON inválida", + "invalidZcashShieldedAddress": "Dirección protegida inválida — debe comenzar con u1" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/fr/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/fr/asset.json index e5cabe92..9a276100 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/fr/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/fr/asset.json @@ -16,5 +16,6 @@ "hideToken": "Masquer le jeton", "revertToAutoDetect": "Revenir à la détection automatique", "unhide": "Afficher", - "addCustomToken": "Ajouter un jeton personnalisé" + "addCustomToken": "Ajouter un jeton personnalisé", + "invalidZcashRecipient": "Adresse invalide — doit être u1... (unified) ou t1.../t3... (transparent)" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/fr/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/fr/send.json index ccaf8def..31992b6a 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/fr/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/fr/send.json @@ -17,7 +17,6 @@ "feeFast": "Rapide", "exceedsBalance": "Le montant dépasse le solde disponible ({{balance}} {{symbol}})", "exceedsBalanceShort": "Le montant dépasse le solde disponible", - "addressTooShort": "L'adresse semble trop courte — veuillez vérifier", "buildingTransaction": "Construction de la transaction...", "buildTransaction": "Construire la transaction", "transactionReady": "Transaction prête", @@ -36,5 +35,23 @@ "sendAnother": "Envoyer une autre", "failedToBuild": "Échec de la construction de la transaction", "signingFailed": "Échec de la signature", - "broadcastFailed": "Échec de la diffusion" + "broadcastFailed": "Échec de la diffusion", + "switchInput": "Basculer entre saisie crypto et USD", + "invalidAddress": "Adresse invalide pour ce réseau", + "invalidBtcAddress": "Adresse Bitcoin invalide — doit commencer par 1, 3 ou bc1", + "invalidLtcAddress": "Adresse Litecoin invalide — doit commencer par L, M, 3 ou ltc1", + "invalidDogeAddress": "Adresse Dogecoin invalide — doit commencer par D", + "invalidBchAddress": "Adresse Bitcoin Cash invalide", + "invalidDashAddress": "Adresse Dash invalide — doit commencer par X", + "invalidZecAddress": "Adresse Zcash invalide — doit commencer par t1 ou t3", + "invalidDgbAddress": "Adresse DigiByte invalide", + "invalidEvmAddress": "Adresse invalide — doit être 0x suivi de 40 caractères hexadécimaux", + "invalidCosmosPrefix": "Préfixe d'adresse incorrect pour ce réseau", + "invalidCosmosLength": "Longueur d'adresse Cosmos invalide", + "invalidCosmosAddress": "Adresse Cosmos invalide", + "invalidXrpAddress": "Adresse XRP invalide — doit commencer par r", + "invalidSolanaAddress": "Adresse Solana invalide — doit comporter 32-44 caractères base58", + "invalidTronAddress": "Adresse Tron invalide — doit commencer par T (34 caractères)", + "invalidTonAddress": "Adresse TON invalide", + "invalidZcashShieldedAddress": "Adresse protégée invalide — doit commencer par u1" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/it/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/it/asset.json index 4c57485b..c64adeec 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/it/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/it/asset.json @@ -16,5 +16,6 @@ "hideToken": "Nascondi token", "revertToAutoDetect": "Ripristina rilevamento automatico", "unhide": "Mostra", - "addCustomToken": "Aggiungi token personalizzato" + "addCustomToken": "Aggiungi token personalizzato", + "invalidZcashRecipient": "Indirizzo non valido — deve essere u1... (unified) o t1.../t3... (transparent)" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/it/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/it/send.json index 7831f9bc..14fd7df5 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/it/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/it/send.json @@ -17,7 +17,6 @@ "feeFast": "Veloce", "exceedsBalance": "L'importo supera il saldo disponibile ({{balance}} {{symbol}})", "exceedsBalanceShort": "L'importo supera il saldo disponibile", - "addressTooShort": "L'indirizzo sembra troppo corto — verifica", "buildingTransaction": "Costruzione transazione...", "buildTransaction": "Costruisci transazione", "transactionReady": "Transazione pronta", @@ -36,5 +35,23 @@ "sendAnother": "Invia un'altra", "failedToBuild": "Costruzione transazione fallita", "signingFailed": "Firma fallita", - "broadcastFailed": "Trasmissione fallita" + "broadcastFailed": "Trasmissione fallita", + "switchInput": "Cambia tra input cripto e USD", + "invalidAddress": "Indirizzo non valido per questa rete", + "invalidBtcAddress": "Indirizzo Bitcoin non valido — deve iniziare con 1, 3 o bc1", + "invalidLtcAddress": "Indirizzo Litecoin non valido — deve iniziare con L, M, 3 o ltc1", + "invalidDogeAddress": "Indirizzo Dogecoin non valido — deve iniziare con D", + "invalidBchAddress": "Indirizzo Bitcoin Cash non valido", + "invalidDashAddress": "Indirizzo Dash non valido — deve iniziare con X", + "invalidZecAddress": "Indirizzo Zcash non valido — deve iniziare con t1 o t3", + "invalidDgbAddress": "Indirizzo DigiByte non valido", + "invalidEvmAddress": "Indirizzo non valido — deve essere 0x seguito da 40 caratteri esadecimali", + "invalidCosmosPrefix": "Prefisso indirizzo errato per questa rete", + "invalidCosmosLength": "Lunghezza indirizzo Cosmos non valida", + "invalidCosmosAddress": "Indirizzo Cosmos non valido", + "invalidXrpAddress": "Indirizzo XRP non valido — deve iniziare con r", + "invalidSolanaAddress": "Indirizzo Solana non valido — deve avere 32-44 caratteri base58", + "invalidTronAddress": "Indirizzo Tron non valido — deve iniziare con T (34 caratteri)", + "invalidTonAddress": "Indirizzo TON non valido", + "invalidZcashShieldedAddress": "Indirizzo protetto non valido — deve iniziare con u1" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/ja/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/ja/asset.json index 87abcdb1..ca00ccae 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/ja/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/ja/asset.json @@ -16,5 +16,6 @@ "hideToken": "トークンを非表示", "revertToAutoDetect": "自動検出に戻す", "unhide": "再表示", - "addCustomToken": "カスタムトークンを追加" + "addCustomToken": "カスタムトークンを追加", + "invalidZcashRecipient": "無効なアドレス — u1...(unified)またはt1.../t3...(transparent)である必要があります" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/ja/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/ja/send.json index 5e984988..af8af9cb 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/ja/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/ja/send.json @@ -17,7 +17,6 @@ "feeFast": "速い", "exceedsBalance": "金額が利用可能残高を超えています ({{balance}} {{symbol}})", "exceedsBalanceShort": "金額が利用可能残高を超えています", - "addressTooShort": "アドレスが短すぎるようです — 確認してください", "buildingTransaction": "トランザクションを構築中...", "buildTransaction": "トランザクションを構築", "transactionReady": "トランザクション準備完了", @@ -36,5 +35,23 @@ "sendAnother": "別の送信", "failedToBuild": "トランザクションの構築に失敗しました", "signingFailed": "署名に失敗しました", - "broadcastFailed": "ブロードキャストに失敗しました" + "broadcastFailed": "ブロードキャストに失敗しました", + "switchInput": "暗号通貨とUSD入力を切り替え", + "invalidAddress": "このネットワークの無効なアドレス", + "invalidBtcAddress": "無効なBitcoinアドレス — 1、3、またはbc1で始まる必要があります", + "invalidLtcAddress": "無効なLitecoinアドレス — L、M、3、またはltc1で始まる必要があります", + "invalidDogeAddress": "無効なDogecoinアドレス — Dで始まる必要があります", + "invalidBchAddress": "無効なBitcoin Cashアドレス", + "invalidDashAddress": "無効なDashアドレス — Xで始まる必要があります", + "invalidZecAddress": "無効なZcashアドレス — t1またはt3で始まる必要があります", + "invalidDgbAddress": "無効なDigiByteアドレス", + "invalidEvmAddress": "無効なアドレス — 0xの後に40桁の16進数文字が必要です", + "invalidCosmosPrefix": "このネットワークのアドレスプレフィックスが間違っています", + "invalidCosmosLength": "無効なCosmosアドレスの長さ", + "invalidCosmosAddress": "無効なCosmosアドレス", + "invalidXrpAddress": "無効なXRPアドレス — rで始まる必要があります", + "invalidSolanaAddress": "無効なSolanaアドレス — 32-44文字のbase58である必要があります", + "invalidTronAddress": "無効なTronアドレス — Tで始まる必要があります(34文字)", + "invalidTonAddress": "無効なTONアドレス", + "invalidZcashShieldedAddress": "無効なシールドアドレス — u1で始まる必要があります" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/ko/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/ko/asset.json index c723c25e..4f737e03 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/ko/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/ko/asset.json @@ -16,5 +16,6 @@ "hideToken": "토큰 숨기기", "revertToAutoDetect": "자동 감지로 되돌리기", "unhide": "숨기기 해제", - "addCustomToken": "커스텀 토큰 추가" + "addCustomToken": "커스텀 토큰 추가", + "invalidZcashRecipient": "유효하지 않은 주소 — u1... (unified) 또는 t1.../t3... (transparent)이어야 합니다" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/ko/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/ko/send.json index 285982cd..0f05820d 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/ko/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/ko/send.json @@ -17,7 +17,6 @@ "feeFast": "빠름", "exceedsBalance": "금액이 사용 가능 잔액을 초과합니다 ({{balance}} {{symbol}})", "exceedsBalanceShort": "금액이 사용 가능 잔액을 초과합니다", - "addressTooShort": "주소가 너무 짧아 보입니다 — 확인해 주세요", "buildingTransaction": "트랜잭션 구성 중...", "buildTransaction": "트랜잭션 구성", "transactionReady": "트랜잭션 준비 완료", @@ -36,5 +35,23 @@ "sendAnother": "추가 전송", "failedToBuild": "트랜잭션 구성 실패", "signingFailed": "서명 실패", - "broadcastFailed": "브로드캐스트 실패" + "broadcastFailed": "브로드캐스트 실패", + "switchInput": "암호화폐와 USD 입력 전환", + "invalidAddress": "이 네트워크에 유효하지 않은 주소", + "invalidBtcAddress": "유효하지 않은 Bitcoin 주소 — 1, 3 또는 bc1로 시작해야 합니다", + "invalidLtcAddress": "유효하지 않은 Litecoin 주소 — L, M, 3 또는 ltc1로 시작해야 합니다", + "invalidDogeAddress": "유효하지 않은 Dogecoin 주소 — D로 시작해야 합니다", + "invalidBchAddress": "유효하지 않은 Bitcoin Cash 주소", + "invalidDashAddress": "유효하지 않은 Dash 주소 — X로 시작해야 합니다", + "invalidZecAddress": "유효하지 않은 Zcash 주소 — t1 또는 t3로 시작해야 합니다", + "invalidDgbAddress": "유효하지 않은 DigiByte 주소", + "invalidEvmAddress": "유효하지 않은 주소 — 0x 뒤에 40자의 16진수 문자가 필요합니다", + "invalidCosmosPrefix": "이 네트워크의 주소 접두사가 잘못되었습니다", + "invalidCosmosLength": "유효하지 않은 Cosmos 주소 길이", + "invalidCosmosAddress": "유효하지 않은 Cosmos 주소", + "invalidXrpAddress": "유효하지 않은 XRP 주소 — r로 시작해야 합니다", + "invalidSolanaAddress": "유효하지 않은 Solana 주소 — 32-44자의 base58이어야 합니다", + "invalidTronAddress": "유효하지 않은 Tron 주소 — T로 시작해야 합니다 (34자)", + "invalidTonAddress": "유효하지 않은 TON 주소", + "invalidZcashShieldedAddress": "유효하지 않은 보호 주소 — u1로 시작해야 합니다" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/nl/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/nl/asset.json index 36864e49..590b97ae 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/nl/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/nl/asset.json @@ -44,5 +44,6 @@ "copyAddress": "Adres kopiëren", "copied": "Gekopieerd", "zcashCliRequired": "Bouw zcash-cli om in te schakelen", - "memoTooLong": "Memo overschrijdt 512 bytes" + "memoTooLong": "Memo overschrijdt 512 bytes", + "invalidZcashRecipient": "Ongeldig adres — moet u1... (unified) of t1.../t3... (transparent) zijn" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/nl/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/nl/send.json index 261eae02..cd3a2466 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/nl/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/nl/send.json @@ -17,7 +17,6 @@ "feeFast": "Snel", "exceedsBalance": "Bedrag overschrijdt beschikbaar saldo ({{balance}} {{symbol}})", "exceedsBalanceShort": "Bedrag overschrijdt beschikbaar saldo", - "addressTooShort": "Adres lijkt te kort — controleer het alstublieft", "buildingTransaction": "Transactie opbouwen...", "buildTransaction": "Transactie opbouwen", "transactionReady": "Transactie gereed", @@ -37,5 +36,22 @@ "failedToBuild": "Transactie opbouwen mislukt", "signingFailed": "Ondertekening mislukt", "broadcastFailed": "Uitzending mislukt", - "switchInput": "Wissel tussen crypto- en USD-invoer" + "switchInput": "Wissel tussen crypto- en USD-invoer", + "invalidAddress": "Ongeldig adres voor dit netwerk", + "invalidBtcAddress": "Ongeldig Bitcoin-adres — moet beginnen met 1, 3 of bc1", + "invalidLtcAddress": "Ongeldig Litecoin-adres — moet beginnen met L, M, 3 of ltc1", + "invalidDogeAddress": "Ongeldig Dogecoin-adres — moet beginnen met D", + "invalidBchAddress": "Ongeldig Bitcoin Cash-adres", + "invalidDashAddress": "Ongeldig Dash-adres — moet beginnen met X", + "invalidZecAddress": "Ongeldig Zcash-adres — moet beginnen met t1 of t3", + "invalidDgbAddress": "Ongeldig DigiByte-adres", + "invalidEvmAddress": "Ongeldig adres — moet 0x gevolgd door 40 hexadecimale tekens zijn", + "invalidCosmosPrefix": "Verkeerd adresprefix voor dit netwerk", + "invalidCosmosLength": "Ongeldige Cosmos-adreslengte", + "invalidCosmosAddress": "Ongeldig Cosmos-adres", + "invalidXrpAddress": "Ongeldig XRP-adres — moet beginnen met r", + "invalidSolanaAddress": "Ongeldig Solana-adres — moet 32-44 base58-tekens zijn", + "invalidTronAddress": "Ongeldig Tron-adres — moet beginnen met T (34 tekens)", + "invalidTonAddress": "Ongeldig TON-adres", + "invalidZcashShieldedAddress": "Ongeldig beschermd adres — moet beginnen met u1" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/pl/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/pl/asset.json index f054b9ac..e1f054f5 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/pl/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/pl/asset.json @@ -44,5 +44,6 @@ "copyAddress": "Kopiuj adres", "copied": "Skopiowano", "zcashCliRequired": "Zbuduj zcash-cli, aby włączyć", - "memoTooLong": "Notatka przekracza 512 bajtów" + "memoTooLong": "Notatka przekracza 512 bajtów", + "invalidZcashRecipient": "Nieprawidłowy adres — musi być u1... (unified) lub t1.../t3... (transparent)" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/pl/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/pl/send.json index 90a1cb8d..01e34ab5 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/pl/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/pl/send.json @@ -17,7 +17,6 @@ "feeFast": "Szybka", "exceedsBalance": "Kwota przekracza dostępne saldo ({{balance}} {{symbol}})", "exceedsBalanceShort": "Kwota przekracza dostępne saldo", - "addressTooShort": "Adres wygląda na zbyt krótki — proszę zweryfikować", "buildingTransaction": "Budowanie transakcji...", "buildTransaction": "Zbuduj transakcję", "transactionReady": "Transakcja gotowa", @@ -37,5 +36,22 @@ "failedToBuild": "Nie udało się zbudować transakcji", "signingFailed": "Podpisywanie nie powiodło się", "broadcastFailed": "Nadawanie nie powiodło się", - "switchInput": "Przełącz między wprowadzaniem krypto a USD" + "switchInput": "Przełącz między wprowadzaniem krypto a USD", + "invalidAddress": "Nieprawidłowy adres dla tej sieci", + "invalidBtcAddress": "Nieprawidłowy adres Bitcoin — musi zaczynać się od 1, 3 lub bc1", + "invalidLtcAddress": "Nieprawidłowy adres Litecoin — musi zaczynać się od L, M, 3 lub ltc1", + "invalidDogeAddress": "Nieprawidłowy adres Dogecoin — musi zaczynać się od D", + "invalidBchAddress": "Nieprawidłowy adres Bitcoin Cash", + "invalidDashAddress": "Nieprawidłowy adres Dash — musi zaczynać się od X", + "invalidZecAddress": "Nieprawidłowy adres Zcash — musi zaczynać się od t1 lub t3", + "invalidDgbAddress": "Nieprawidłowy adres DigiByte", + "invalidEvmAddress": "Nieprawidłowy adres — musi być 0x i 40 znaków szesnastkowych", + "invalidCosmosPrefix": "Nieprawidłowy prefiks adresu dla tej sieci", + "invalidCosmosLength": "Nieprawidłowa długość adresu Cosmos", + "invalidCosmosAddress": "Nieprawidłowy adres Cosmos", + "invalidXrpAddress": "Nieprawidłowy adres XRP — musi zaczynać się od r", + "invalidSolanaAddress": "Nieprawidłowy adres Solana — musi mieć 32-44 znaki base58", + "invalidTronAddress": "Nieprawidłowy adres Tron — musi zaczynać się od T (34 znaki)", + "invalidTonAddress": "Nieprawidłowy adres TON", + "invalidZcashShieldedAddress": "Nieprawidłowy adres chroniony — musi zaczynać się od u1" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/pt/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/pt/asset.json index 86ed3913..b71a8439 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/pt/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/pt/asset.json @@ -16,5 +16,6 @@ "hideToken": "Ocultar token", "revertToAutoDetect": "Reverter para detecção automática", "unhide": "Reexibir", - "addCustomToken": "Adicionar token personalizado" + "addCustomToken": "Adicionar token personalizado", + "invalidZcashRecipient": "Endereço inválido — deve ser u1... (unified) ou t1.../t3... (transparent)" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/pt/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/pt/send.json index 4a04103f..add656d3 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/pt/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/pt/send.json @@ -17,7 +17,6 @@ "feeFast": "Rápida", "exceedsBalance": "O valor excede o saldo disponível ({{balance}} {{symbol}})", "exceedsBalanceShort": "O valor excede o saldo disponível", - "addressTooShort": "O endereço parece muito curto — por favor verifique", "buildingTransaction": "Construindo transação...", "buildTransaction": "Construir transação", "transactionReady": "Transação pronta", @@ -36,5 +35,23 @@ "sendAnother": "Enviar outra", "failedToBuild": "Falha ao construir transação", "signingFailed": "Falha na assinatura", - "broadcastFailed": "Falha na transmissão" + "broadcastFailed": "Falha na transmissão", + "switchInput": "Alternar entre entrada de cripto e USD", + "invalidAddress": "Endereço inválido para esta rede", + "invalidBtcAddress": "Endereço Bitcoin inválido — deve começar com 1, 3 ou bc1", + "invalidLtcAddress": "Endereço Litecoin inválido — deve começar com L, M, 3 ou ltc1", + "invalidDogeAddress": "Endereço Dogecoin inválido — deve começar com D", + "invalidBchAddress": "Endereço Bitcoin Cash inválido", + "invalidDashAddress": "Endereço Dash inválido — deve começar com X", + "invalidZecAddress": "Endereço Zcash inválido — deve começar com t1 ou t3", + "invalidDgbAddress": "Endereço DigiByte inválido", + "invalidEvmAddress": "Endereço inválido — deve ser 0x seguido de 40 caracteres hexadecimais", + "invalidCosmosPrefix": "Prefixo de endereço incorreto para esta rede", + "invalidCosmosLength": "Comprimento de endereço Cosmos inválido", + "invalidCosmosAddress": "Endereço Cosmos inválido", + "invalidXrpAddress": "Endereço XRP inválido — deve começar com r", + "invalidSolanaAddress": "Endereço Solana inválido — deve ter 32-44 caracteres base58", + "invalidTronAddress": "Endereço Tron inválido — deve começar com T (34 caracteres)", + "invalidTonAddress": "Endereço TON inválido", + "invalidZcashShieldedAddress": "Endereço protegido inválido — deve começar com u1" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/ru/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/ru/asset.json index 25bbc0a3..7159e30e 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/ru/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/ru/asset.json @@ -16,5 +16,6 @@ "hideToken": "Скрыть токен", "revertToAutoDetect": "Вернуть автоопределение", "unhide": "Показать", - "addCustomToken": "Добавить пользовательский токен" + "addCustomToken": "Добавить пользовательский токен", + "invalidZcashRecipient": "Недействительный адрес — должен быть u1... (unified) или t1.../t3... (transparent)" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/ru/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/ru/send.json index 77ff5c3a..4cbbab67 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/ru/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/ru/send.json @@ -17,7 +17,6 @@ "feeFast": "Быстро", "exceedsBalance": "Сумма превышает доступный баланс ({{balance}} {{symbol}})", "exceedsBalanceShort": "Сумма превышает доступный баланс", - "addressTooShort": "Адрес слишком короткий — пожалуйста, проверьте", "buildingTransaction": "Построение транзакции...", "buildTransaction": "Построить транзакцию", "transactionReady": "Транзакция готова", @@ -36,5 +35,23 @@ "sendAnother": "Отправить ещё", "failedToBuild": "Не удалось построить транзакцию", "signingFailed": "Ошибка подписи", - "broadcastFailed": "Ошибка трансляции" + "broadcastFailed": "Ошибка трансляции", + "switchInput": "Переключить между вводом крипто и USD", + "invalidAddress": "Недействительный адрес для этой сети", + "invalidBtcAddress": "Недействительный адрес Bitcoin — должен начинаться с 1, 3 или bc1", + "invalidLtcAddress": "Недействительный адрес Litecoin — должен начинаться с L, M, 3 или ltc1", + "invalidDogeAddress": "Недействительный адрес Dogecoin — должен начинаться с D", + "invalidBchAddress": "Недействительный адрес Bitcoin Cash", + "invalidDashAddress": "Недействительный адрес Dash — должен начинаться с X", + "invalidZecAddress": "Недействительный адрес Zcash — должен начинаться с t1 или t3", + "invalidDgbAddress": "Недействительный адрес DigiByte", + "invalidEvmAddress": "Недействительный адрес — должен быть 0x и 40 шестнадцатеричных символов", + "invalidCosmosPrefix": "Неверный префикс адреса для этой сети", + "invalidCosmosLength": "Недействительная длина адреса Cosmos", + "invalidCosmosAddress": "Недействительный адрес Cosmos", + "invalidXrpAddress": "Недействительный адрес XRP — должен начинаться с r", + "invalidSolanaAddress": "Недействительный адрес Solana — должен содержать 32-44 символа base58", + "invalidTronAddress": "Недействительный адрес Tron — должен начинаться с T (34 символа)", + "invalidTonAddress": "Недействительный адрес TON", + "invalidZcashShieldedAddress": "Недействительный защищённый адрес — должен начинаться с u1" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/th/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/th/asset.json index d233b6b9..f3507e4c 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/th/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/th/asset.json @@ -44,5 +44,6 @@ "copyAddress": "คัดลอกที่อยู่", "copied": "คัดลอกแล้ว", "zcashCliRequired": "สร้าง zcash-cli เพื่อเปิดใช้งาน", - "memoTooLong": "บันทึกเกิน 512 ไบต์" + "memoTooLong": "บันทึกเกิน 512 ไบต์", + "invalidZcashRecipient": "ที่อยู่ไม่ถูกต้อง — ต้องเป็น u1... (unified) หรือ t1.../t3... (transparent)" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/th/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/th/send.json index a256a2dc..ad46dcb6 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/th/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/th/send.json @@ -17,7 +17,6 @@ "feeFast": "เร็ว", "exceedsBalance": "จำนวนเกินยอดคงเหลือที่พร้อมใช้งาน ({{balance}} {{symbol}})", "exceedsBalanceShort": "จำนวนเกินยอดคงเหลือที่พร้อมใช้งาน", - "addressTooShort": "ที่อยู่ดูสั้นเกินไป — กรุณาตรวจสอบ", "buildingTransaction": "กำลังสร้างธุรกรรม...", "buildTransaction": "สร้างธุรกรรม", "transactionReady": "ธุรกรรมพร้อมแล้ว", @@ -37,5 +36,22 @@ "failedToBuild": "ไม่สามารถสร้างธุรกรรมได้", "signingFailed": "การลงนามล้มเหลว", "broadcastFailed": "การประกาศล้มเหลว", - "switchInput": "สลับระหว่างการป้อนคริปโตและ USD" + "switchInput": "สลับระหว่างการป้อนคริปโตและ USD", + "invalidAddress": "ที่อยู่ไม่ถูกต้องสำหรับเครือข่ายนี้", + "invalidBtcAddress": "ที่อยู่ Bitcoin ไม่ถูกต้อง — ต้องเริ่มต้นด้วย 1, 3 หรือ bc1", + "invalidLtcAddress": "ที่อยู่ Litecoin ไม่ถูกต้อง — ต้องเริ่มต้นด้วย L, M, 3 หรือ ltc1", + "invalidDogeAddress": "ที่อยู่ Dogecoin ไม่ถูกต้อง — ต้องเริ่มต้นด้วย D", + "invalidBchAddress": "ที่อยู่ Bitcoin Cash ไม่ถูกต้อง", + "invalidDashAddress": "ที่อยู่ Dash ไม่ถูกต้อง — ต้องเริ่มต้นด้วย X", + "invalidZecAddress": "ที่อยู่ Zcash ไม่ถูกต้อง — ต้องเริ่มต้นด้วย t1 หรือ t3", + "invalidDgbAddress": "ที่อยู่ DigiByte ไม่ถูกต้อง", + "invalidEvmAddress": "ที่อยู่ไม่ถูกต้อง — ต้องเป็น 0x ตามด้วยอักขระฐานสิบหก 40 ตัว", + "invalidCosmosPrefix": "คำนำหน้าที่อยู่ไม่ถูกต้องสำหรับเครือข่ายนี้", + "invalidCosmosLength": "ความยาวที่อยู่ Cosmos ไม่ถูกต้อง", + "invalidCosmosAddress": "ที่อยู่ Cosmos ไม่ถูกต้อง", + "invalidXrpAddress": "ที่อยู่ XRP ไม่ถูกต้อง — ต้องเริ่มต้นด้วย r", + "invalidSolanaAddress": "ที่อยู่ Solana ไม่ถูกต้อง — ต้องมี 32-44 อักขระ base58", + "invalidTronAddress": "ที่อยู่ Tron ไม่ถูกต้อง — ต้องเริ่มต้นด้วย T (34 อักขระ)", + "invalidTonAddress": "ที่อยู่ TON ไม่ถูกต้อง", + "invalidZcashShieldedAddress": "ที่อยู่ที่มีการป้องกันไม่ถูกต้อง — ต้องเริ่มต้นด้วย u1" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/tr/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/tr/asset.json index 3638e756..7422d173 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/tr/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/tr/asset.json @@ -44,5 +44,6 @@ "copyAddress": "Adresi kopyala", "copied": "Kopyalandı", "zcashCliRequired": "Etkinleştirmek için zcash-cli derleyin", - "memoTooLong": "Not 512 baytı aşıyor" + "memoTooLong": "Not 512 baytı aşıyor", + "invalidZcashRecipient": "Geçersiz adres — u1... (unified) veya t1.../t3... (transparent) olmalıdır" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/tr/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/tr/send.json index 030a9e2e..82482385 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/tr/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/tr/send.json @@ -17,7 +17,6 @@ "feeFast": "Hızlı", "exceedsBalance": "Miktar kullanılabilir bakiyeyi aşıyor ({{balance}} {{symbol}})", "exceedsBalanceShort": "Miktar kullanılabilir bakiyeyi aşıyor", - "addressTooShort": "Adres çok kısa görünüyor — lütfen doğrulayın", "buildingTransaction": "İşlem oluşturuluyor...", "buildTransaction": "İşlem oluştur", "transactionReady": "İşlem hazır", @@ -37,5 +36,22 @@ "failedToBuild": "İşlem oluşturulamadı", "signingFailed": "İmzalama başarısız oldu", "broadcastFailed": "Yayınlama başarısız oldu", - "switchInput": "Kripto ve USD girişi arasında geçiş yap" + "switchInput": "Kripto ve USD girişi arasında geçiş yap", + "invalidAddress": "Bu ağ için geçersiz adres", + "invalidBtcAddress": "Geçersiz Bitcoin adresi — 1, 3 veya bc1 ile başlamalıdır", + "invalidLtcAddress": "Geçersiz Litecoin adresi — L, M, 3 veya ltc1 ile başlamalıdır", + "invalidDogeAddress": "Geçersiz Dogecoin adresi — D ile başlamalıdır", + "invalidBchAddress": "Geçersiz Bitcoin Cash adresi", + "invalidDashAddress": "Geçersiz Dash adresi — X ile başlamalıdır", + "invalidZecAddress": "Geçersiz Zcash adresi — t1 veya t3 ile başlamalıdır", + "invalidDgbAddress": "Geçersiz DigiByte adresi", + "invalidEvmAddress": "Geçersiz adres — 0x ve ardından 40 onaltılık karakter olmalıdır", + "invalidCosmosPrefix": "Bu ağ için yanlış adres öneki", + "invalidCosmosLength": "Geçersiz Cosmos adres uzunluğu", + "invalidCosmosAddress": "Geçersiz Cosmos adresi", + "invalidXrpAddress": "Geçersiz XRP adresi — r ile başlamalıdır", + "invalidSolanaAddress": "Geçersiz Solana adresi — 32-44 base58 karakter olmalıdır", + "invalidTronAddress": "Geçersiz Tron adresi — T ile başlamalıdır (34 karakter)", + "invalidTonAddress": "Geçersiz TON adresi", + "invalidZcashShieldedAddress": "Geçersiz korumalı adres — u1 ile başlamalıdır" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/vi/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/vi/asset.json index 1955d91d..cabb063b 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/vi/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/vi/asset.json @@ -44,5 +44,6 @@ "copyAddress": "Sao chép địa chỉ", "copied": "Đã sao chép", "zcashCliRequired": "Xây dựng zcash-cli để kích hoạt", - "memoTooLong": "Ghi chú vượt quá 512 byte" + "memoTooLong": "Ghi chú vượt quá 512 byte", + "invalidZcashRecipient": "Địa chỉ không hợp lệ — phải là u1... (unified) hoặc t1.../t3... (transparent)" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/vi/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/vi/send.json index bf08b078..788a19e2 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/vi/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/vi/send.json @@ -17,7 +17,6 @@ "feeFast": "Nhanh", "exceedsBalance": "Số lượng vượt quá số dư khả dụng ({{balance}} {{symbol}})", "exceedsBalanceShort": "Số lượng vượt quá số dư khả dụng", - "addressTooShort": "Địa chỉ có vẻ quá ngắn — vui lòng kiểm tra lại", "buildingTransaction": "Đang xây dựng giao dịch...", "buildTransaction": "Xây dựng giao dịch", "transactionReady": "Giao dịch đã sẵn sàng", @@ -37,5 +36,22 @@ "failedToBuild": "Không thể xây dựng giao dịch", "signingFailed": "Ký thất bại", "broadcastFailed": "Phát sóng thất bại", - "switchInput": "Chuyển đổi giữa nhập tiền mã hóa và USD" + "switchInput": "Chuyển đổi giữa nhập tiền mã hóa và USD", + "invalidAddress": "Địa chỉ không hợp lệ cho mạng này", + "invalidBtcAddress": "Địa chỉ Bitcoin không hợp lệ — phải bắt đầu bằng 1, 3 hoặc bc1", + "invalidLtcAddress": "Địa chỉ Litecoin không hợp lệ — phải bắt đầu bằng L, M, 3 hoặc ltc1", + "invalidDogeAddress": "Địa chỉ Dogecoin không hợp lệ — phải bắt đầu bằng D", + "invalidBchAddress": "Địa chỉ Bitcoin Cash không hợp lệ", + "invalidDashAddress": "Địa chỉ Dash không hợp lệ — phải bắt đầu bằng X", + "invalidZecAddress": "Địa chỉ Zcash không hợp lệ — phải bắt đầu bằng t1 hoặc t3", + "invalidDgbAddress": "Địa chỉ DigiByte không hợp lệ", + "invalidEvmAddress": "Địa chỉ không hợp lệ — phải là 0x theo sau bởi 40 ký tự thập lục phân", + "invalidCosmosPrefix": "Tiền tố địa chỉ sai cho mạng này", + "invalidCosmosLength": "Độ dài địa chỉ Cosmos không hợp lệ", + "invalidCosmosAddress": "Địa chỉ Cosmos không hợp lệ", + "invalidXrpAddress": "Địa chỉ XRP không hợp lệ — phải bắt đầu bằng r", + "invalidSolanaAddress": "Địa chỉ Solana không hợp lệ — phải có 32-44 ký tự base58", + "invalidTronAddress": "Địa chỉ Tron không hợp lệ — phải bắt đầu bằng T (34 ký tự)", + "invalidTonAddress": "Địa chỉ TON không hợp lệ", + "invalidZcashShieldedAddress": "Địa chỉ được bảo vệ không hợp lệ — phải bắt đầu bằng u1" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/zh/asset.json b/projects/keepkey-vault/src/mainview/i18n/locales/zh/asset.json index bf92d1ba..85c128cf 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/zh/asset.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/zh/asset.json @@ -16,5 +16,6 @@ "hideToken": "隐藏代币", "revertToAutoDetect": "恢复自动检测", "unhide": "取消隐藏", - "addCustomToken": "添加自定义代币" + "addCustomToken": "添加自定义代币", + "invalidZcashRecipient": "无效地址 — 必须是 u1...(unified)或 t1.../t3...(transparent)" } diff --git a/projects/keepkey-vault/src/mainview/i18n/locales/zh/send.json b/projects/keepkey-vault/src/mainview/i18n/locales/zh/send.json index 2e1c090b..bb0fc428 100644 --- a/projects/keepkey-vault/src/mainview/i18n/locales/zh/send.json +++ b/projects/keepkey-vault/src/mainview/i18n/locales/zh/send.json @@ -17,7 +17,6 @@ "feeFast": "快速", "exceedsBalance": "金额超出可用余额 ({{balance}} {{symbol}})", "exceedsBalanceShort": "金额超出可用余额", - "addressTooShort": "地址似乎太短 — 请验证", "buildingTransaction": "正在构建交易...", "buildTransaction": "构建交易", "transactionReady": "交易已就绪", @@ -36,5 +35,23 @@ "sendAnother": "再次发送", "failedToBuild": "构建交易失败", "signingFailed": "签名失败", - "broadcastFailed": "广播失败" + "broadcastFailed": "广播失败", + "switchInput": "切换加密货币和美元输入", + "invalidAddress": "此网络的地址无效", + "invalidBtcAddress": "无效的Bitcoin地址 — 必须以1、3或bc1开头", + "invalidLtcAddress": "无效的Litecoin地址 — 必须以L、M、3或ltc1开头", + "invalidDogeAddress": "无效的Dogecoin地址 — 必须以D开头", + "invalidBchAddress": "无效的Bitcoin Cash地址", + "invalidDashAddress": "无效的Dash地址 — 必须以X开头", + "invalidZecAddress": "无效的Zcash地址 — 必须以t1或t3开头", + "invalidDgbAddress": "无效的DigiByte地址", + "invalidEvmAddress": "无效的地址 — 必须是0x后跟40个十六进制字符", + "invalidCosmosPrefix": "此网络的地址前缀不正确", + "invalidCosmosLength": "无效的Cosmos地址长度", + "invalidCosmosAddress": "无效的Cosmos地址", + "invalidXrpAddress": "无效的XRP地址 — 必须以r开头", + "invalidSolanaAddress": "无效的Solana地址 — 必须是32-44个base58字符", + "invalidTronAddress": "无效的Tron地址 — 必须以T开头(34个字符)", + "invalidTonAddress": "无效的TON地址", + "invalidZcashShieldedAddress": "无效的隐蔽地址 — 必须以u1开头" } diff --git a/projects/keepkey-vault/src/mainview/lib/sounds.ts b/projects/keepkey-vault/src/mainview/lib/sounds.ts new file mode 100644 index 00000000..460abaa1 --- /dev/null +++ b/projects/keepkey-vault/src/mainview/lib/sounds.ts @@ -0,0 +1,41 @@ +/** + * Web Audio API sound effects — no external files needed. + */ + +let audioCtx: AudioContext | null = null + +function getCtx(): AudioContext { + if (!audioCtx) audioCtx = new AudioContext() + return audioCtx +} + +/** Play a short cha-ching coin sound (two rising tones). */ +export function playChaChing() { + try { + const ctx = getCtx() + const now = ctx.currentTime + + // First coin clink + playTone(ctx, 1200, now, 0.08, 0.3) + playTone(ctx, 1800, now + 0.01, 0.06, 0.2) + + // Second clink (higher, like a register) + playTone(ctx, 1600, now + 0.12, 0.08, 0.25) + playTone(ctx, 2400, now + 0.13, 0.1, 0.2) + } catch { + // Audio not available (e.g. no user gesture yet) — silently skip + } +} + +function playTone(ctx: AudioContext, freq: number, startTime: number, duration: number, volume: number) { + const osc = ctx.createOscillator() + const gain = ctx.createGain() + osc.type = 'sine' + osc.frequency.value = freq + gain.gain.setValueAtTime(volume, startTime) + gain.gain.exponentialRampToValueAtTime(0.001, startTime + duration) + osc.connect(gain) + gain.connect(ctx.destination) + osc.start(startTime) + osc.stop(startTime + duration) +} diff --git a/projects/keepkey-vault/src/shared/address-validation.ts b/projects/keepkey-vault/src/shared/address-validation.ts new file mode 100644 index 00000000..b28e684a --- /dev/null +++ b/projects/keepkey-vault/src/shared/address-validation.ts @@ -0,0 +1,170 @@ +/** + * Per-chain address format validation. + * Lightweight format checks — no external dependencies, no checksum verification. + * Catches obvious typos and cross-chain address mistakes before hitting the network. + */ +import type { ChainDef } from './chains' + +const BASE58 = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/ +const HEX40 = /^0x[0-9a-fA-F]{40}$/ +const BECH32 = /^[a-z]+1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{6,}$/i + +interface ValidationResult { + valid: boolean + error?: string // i18n key +} + +const OK: ValidationResult = { valid: true } +const fail = (error: string): ValidationResult => ({ valid: false, error }) + +/** Validate address format for a specific chain */ +export function validateAddress(address: string, chain: ChainDef): ValidationResult { + const trimmed = address.trim() + if (!trimmed) return fail('addressEmpty') + + switch (chain.chainFamily) { + case 'utxo': + return validateUtxoAddress(trimmed, chain) + case 'evm': + return validateEvmAddress(trimmed) + case 'cosmos': + return validateCosmosAddress(trimmed, chain) + case 'xrp': + return validateXrpAddress(trimmed) + case 'solana': + return validateSolanaAddress(trimmed) + case 'tron': + return validateTronAddress(trimmed) + case 'ton': + return validateTonAddress(trimmed) + case 'zcash-shielded': + return validateZcashShieldedAddress(trimmed) + default: + return OK // Unknown chain family — don't block + } +} + +// ── UTXO chains ───────────────────────────────────────────────────────── + +function validateUtxoAddress(addr: string, chain: ChainDef): ValidationResult { + switch (chain.id) { + case 'bitcoin': + // Legacy (1...), P2SH (3...), Bech32 (bc1...) + if (addr.startsWith('1') && addr.length >= 25 && addr.length <= 34 && BASE58.test(addr)) return OK + if (addr.startsWith('3') && addr.length === 34 && BASE58.test(addr)) return OK + if (addr.toLowerCase().startsWith('bc1') && addr.length >= 42 && addr.length <= 62 && BECH32.test(addr)) return OK + return fail('invalidBtcAddress') + + case 'litecoin': + // Legacy (L/M...), P2SH (3...), Bech32 (ltc1...) + if ((addr.startsWith('L') || addr.startsWith('M')) && addr.length >= 25 && addr.length <= 34 && BASE58.test(addr)) return OK + if (addr.startsWith('3') && addr.length === 34 && BASE58.test(addr)) return OK + if (addr.toLowerCase().startsWith('ltc1') && addr.length >= 42 && BECH32.test(addr)) return OK + return fail('invalidLtcAddress') + + case 'dogecoin': + if (addr.startsWith('D') && addr.length >= 25 && addr.length <= 34 && BASE58.test(addr)) return OK + return fail('invalidDogeAddress') + + case 'bitcoincash': + // Legacy (1...) or CashAddr (q..., bitcoincash:q...) + if (addr.startsWith('1') && addr.length >= 25 && addr.length <= 34 && BASE58.test(addr)) return OK + const stripped = addr.replace(/^bitcoincash:/i, '') + if ((stripped.startsWith('q') || stripped.startsWith('p')) && stripped.length >= 42) return OK + return fail('invalidBchAddress') + + case 'dash': + if (addr.startsWith('X') && addr.length === 34 && BASE58.test(addr)) return OK + return fail('invalidDashAddress') + + case 'zcash': + // Transparent: t1... (34 chars) or t3... (34 chars) + if ((addr.startsWith('t1') || addr.startsWith('t3')) && addr.length === 35 && BASE58.test(addr)) return OK + return fail('invalidZecAddress') + + case 'digibyte': + if (addr.startsWith('D') && addr.length >= 25 && addr.length <= 34 && BASE58.test(addr)) return OK + if (addr.toLowerCase().startsWith('dgb1') && BECH32.test(addr)) return OK + return fail('invalidDgbAddress') + + default: + // Generic UTXO — just check reasonable length + base58 or bech32 + if (addr.length >= 25 && addr.length <= 62 && (BASE58.test(addr) || BECH32.test(addr))) return OK + return fail('invalidAddress') + } +} + +// ── EVM ───────────────────────────────────────────────────────────────── + +function validateEvmAddress(addr: string): ValidationResult { + if (HEX40.test(addr)) return OK + // ENS names — let them through (resolver handles it) + if (addr.endsWith('.eth') && addr.length >= 7) return OK + return fail('invalidEvmAddress') +} + +// ── Cosmos family ─────────────────────────────────────────────────────── + +const COSMOS_PREFIXES: Record = { + cosmos: 'cosmos1', + thorchain: 'thor1', + mayachain: 'maya1', + osmosis: 'osmo1', +} + +function validateCosmosAddress(addr: string, chain: ChainDef): ValidationResult { + const expectedPrefix = COSMOS_PREFIXES[chain.id] + if (expectedPrefix) { + if (!addr.startsWith(expectedPrefix)) return fail('invalidCosmosPrefix') + // Bech32: prefix + "1" + data chars (typically 38-59 total) + if (addr.length < 39 || addr.length > 65) return fail('invalidCosmosLength') + return OK + } + // Unknown cosmos chain — just check bech32 format + if (BECH32.test(addr) && addr.length >= 39) return OK + return fail('invalidCosmosAddress') +} + +// ── XRP ───────────────────────────────────────────────────────────────── + +function validateXrpAddress(addr: string): ValidationResult { + if (addr.startsWith('r') && addr.length >= 25 && addr.length <= 35 && BASE58.test(addr)) return OK + return fail('invalidXrpAddress') +} + +// ── Solana ────────────────────────────────────────────────────────────── + +function validateSolanaAddress(addr: string): ValidationResult { + if (BASE58.test(addr) && addr.length >= 32 && addr.length <= 44) return OK + return fail('invalidSolanaAddress') +} + +// ── Tron ──────────────────────────────────────────────────────────────── + +function validateTronAddress(addr: string): ValidationResult { + if (addr.startsWith('T') && addr.length === 34 && BASE58.test(addr)) return OK + return fail('invalidTronAddress') +} + +// ── TON ───────────────────────────────────────────────────────────────── + +function validateTonAddress(addr: string): ValidationResult { + // Raw: 0:hex (66 chars), User-friendly: EQ/UQ + base64 (48 chars), or raw hex + if (/^0:[0-9a-fA-F]{64}$/.test(addr)) return OK + if ((addr.startsWith('EQ') || addr.startsWith('UQ')) && addr.length === 48) return OK + // Newer formats + if (/^[A-Za-z0-9_-]{46,48}$/.test(addr)) return OK + return fail('invalidTonAddress') +} + +// ── Zcash shielded ────────────────────────────────────────────────────── + +function validateZcashShieldedAddress(addr: string): ValidationResult { + // Unified addresses start with 'u1' + if (addr.startsWith('u1') && addr.length >= 70) return OK + // Sapling addresses start with 'zs1' + if (addr.startsWith('zs1') && addr.length >= 70) return OK + // Transparent fallback (some wallets send from shielded tab to transparent) + if ((addr.startsWith('t1') || addr.startsWith('t3')) && addr.length === 35 && BASE58.test(addr)) return OK + return fail('invalidZcashShieldedAddress') +} diff --git a/projects/keepkey-vault/src/shared/rpc-schema.ts b/projects/keepkey-vault/src/shared/rpc-schema.ts index fd9fb01b..02594665 100644 --- a/projects/keepkey-vault/src/shared/rpc-schema.ts +++ b/projects/keepkey-vault/src/shared/rpc-schema.ts @@ -1,5 +1,5 @@ import type { ElectrobunRPCSchema } from 'electrobun/bun' -import type { DeviceStateInfo, FirmwareProgress, FirmwareAnalysis, PinRequest, CharacterRequest, ChainBalance, BuildTxParams, BuildTxResult, BroadcastResult, BtcAccountSet, BtcScriptType, EvmAddressSet, CustomToken, CustomChain, AppSettings, BtcGetAddressParams, EthGetAddressParams, EthSignTxParams, BtcSignTxParams, GetPublicKeysParams, UpdateInfo, UpdateStatus, TokenVisibilityStatus, PairingRequestInfo, PairedAppInfo, SigningRequestInfo, ApiLogEntry, PioneerChainInfo, ReportMeta, ReportData, SwapAsset, SwapQuote, SwapQuoteParams, ExecuteSwapParams, SwapResult, PendingSwap, SwapStatusUpdate, SwapHistoryRecord, SwapHistoryFilter, SwapHistoryStats } from './types' +import type { DeviceStateInfo, FirmwareProgress, FirmwareAnalysis, PinRequest, CharacterRequest, ChainBalance, BuildTxParams, BuildTxResult, BroadcastResult, BtcAccountSet, BtcScriptType, EvmAddressSet, CustomToken, CustomChain, AppSettings, BtcGetAddressParams, EthGetAddressParams, EthSignTxParams, BtcSignTxParams, GetPublicKeysParams, UpdateInfo, UpdateStatus, TokenVisibilityStatus, PairingRequestInfo, PairedAppInfo, SigningRequestInfo, ApiLogEntry, PioneerChainInfo, ReportMeta, ReportData, SwapAsset, SwapQuote, SwapQuoteParams, ExecuteSwapParams, SwapResult, PendingSwap, SwapStatusUpdate, SwapHistoryRecord, SwapHistoryFilter, SwapHistoryStats, RecentActivity } from './types' /** * RPC Schema for Bun ↔ WebView communication. @@ -152,6 +152,12 @@ export type VaultRPCSchema = ElectrobunRPCSchema & { getSwapHistoryStats: { params: void; response: SwapHistoryStats } exportSwapReport: { params: { fromDate?: number; toDate?: number; format: 'pdf' | 'csv' }; response: { filePath: string } } + // ── Recent Activity ────────────────────────────────────────────────── + getRecentActivity: { params: { limit?: number; chainId?: string } | void; response: RecentActivity[] } + scanChainHistory: { params: { chainId: string }; response: { count: number } } + dismissActivity: { params: { id: string }; response: void } + clearRecentActivity: { params: void; response: void } + // ── Balance cache (instant portfolio) ───────────────────────────── getCachedBalances: { params: void; response: { balances: ChainBalance[]; updatedAt: number } | null } @@ -200,6 +206,7 @@ export type VaultRPCSchema = ElectrobunRPCSchema & { 'swap-update': SwapStatusUpdate 'swap-complete': PendingSwap 'scan-progress': { percent: number; scannedHeight: number; tipHeight: number; blocksPerSec: number; etaSeconds: number } + 'balance-updated': ChainBalance } } webview: { diff --git a/projects/keepkey-vault/src/shared/types.ts b/projects/keepkey-vault/src/shared/types.ts index 4abc1d33..4b4c7986 100644 --- a/projects/keepkey-vault/src/shared/types.ts +++ b/projects/keepkey-vault/src/shared/types.ts @@ -265,6 +265,10 @@ export interface ApiLogEntry { imageUrl?: string requestBody?: any // parsed JSON body (POST requests) responseBody?: any // parsed JSON response + // ── Activity tracking (populated for sign/broadcast operations) ── + txid?: string // blockchain txid (computed from signed tx or from broadcast response) + chain?: string // chain symbol (BTC, ETH, ATOM, etc.) + activityType?: string // sign | broadcast | swap | message } // Supported fiat currencies @@ -531,6 +535,30 @@ export interface SwapHistoryStats { pending: number } +// ── Recent Activity types ────────────────────────────────────────────── + +export type ActivityType = 'send' | 'receive' | 'swap' | 'sign' | 'message' | 'approve' +export type ActivitySource = 'app' | 'api' + +export interface RecentActivity { + id: string + txid?: string // blockchain txid (may be absent for sign-only before broadcast) + chain: string // chain symbol (BTC, ETH, ATOM, etc.) + chainId?: string // internal chain id (bitcoin, ethereum, etc.) — for explorer links + type: ActivityType + source: ActivitySource + to?: string + amount?: string + asset?: string // token symbol if different from chain native + appName?: string // for API-originating activities + status: 'signed' | 'broadcast' | 'failed' + createdAt: number + // ── On-chain confirmation data (populated by scan, updated on rescan) ── + confirmations?: number // current confirmation count (0 = unconfirmed/mempool) + blockHeight?: number // block the tx was mined in (0 = unconfirmed) + fee?: string // tx fee (human-readable) +} + // RPC types — derived from the single source of truth in rpc-schema.ts // Import VaultRPCSchema from './rpc-schema' if you need the full Electrobun schema. // These aliases are for convenience in frontend code that doesn't need Electrobun types.