diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 9079de25e..9ec87d196 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -91,6 +91,9 @@ jobs: if: always() && runner.os == 'macOS' run: | rm ${{ github.workspace }}/AuthKey.p8 + ipad-build: + uses: ./.github/workflows/ipad-build.yaml + secrets: inherit web-build: uses: ./.github/workflows/web-build.yaml diff --git a/.github/workflows/npm-gulp.yml b/.github/workflows/npm-gulp.yml new file mode 100644 index 000000000..f8aa8bb2c --- /dev/null +++ b/.github/workflows/npm-gulp.yml @@ -0,0 +1,28 @@ +name: NodeJS with Gulp + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Build + run: | + npm install + gulp diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 65d827acd..491202edd 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@tonkeeper/desktop", "license": "Apache-2.0", - "version": "3.26.2", + "version": "3.27.0", "description": "Your desktop wallet on The Open Network", "main": ".webpack/main", "repository": { diff --git a/apps/extension/package.json b/apps/extension/package.json index 873f6ae60..f05192020 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -1,6 +1,6 @@ { "name": "@tonkeeper/extension", - "version": "3.26.2", + "version": "3.27.0", "author": "Ton APPS UK Limited ", "description": "Your extension wallet on The Open Network", "dependencies": { diff --git a/apps/tablet/package.json b/apps/tablet/package.json index 13e1b088b..3b5199fa5 100644 --- a/apps/tablet/package.json +++ b/apps/tablet/package.json @@ -1,6 +1,6 @@ { "name": "@tonkeeper/tablet", - "version": "3.1.0", + "version": "3.2.0", "license": "Apache-2.0", "description": "Your tablet wallet on The Open Network", "type": "module", diff --git a/apps/twa/package.json b/apps/twa/package.json index 2903a64c1..dd5354cd0 100644 --- a/apps/twa/package.json +++ b/apps/twa/package.json @@ -1,6 +1,6 @@ { "name": "@tonkeeper/twa", - "version": "3.26.2", + "version": "3.27.0", "author": "Ton APPS UK Limited ", "description": "Your telegram mini app wallet on The Open Network", "dependencies": { diff --git a/apps/web/package.json b/apps/web/package.json index c1408e548..5d13e2fd7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@tonkeeper/web", - "version": "3.26.2", + "version": "3.27.0", "author": "Ton APPS UK Limited ", "description": "Your web wallet on The Open Network", "dependencies": { diff --git a/apps/web/public/img/trx.svg b/apps/web/public/img/trx.svg new file mode 100644 index 000000000..c21ce3b77 --- /dev/null +++ b/apps/web/public/img/trx.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/web/public/img/usdt-ton.png b/apps/web/public/img/usdt-ton.png new file mode 100644 index 000000000..cbf39b04f Binary files /dev/null and b/apps/web/public/img/usdt-ton.png differ diff --git a/apps/web/public/img/usdt-trc20.png b/apps/web/public/img/usdt-trc20.png new file mode 100644 index 000000000..e2c6f8010 Binary files /dev/null and b/apps/web/public/img/usdt-trc20.png differ diff --git a/packages/core/package.json b/packages/core/package.json index 32e0ac903..aa3b6cc03 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -24,7 +24,7 @@ "@keystonehq/keystone-sdk": "0.7.2", "@ledgerhq/hw-transport-webhid": "^6.28.6", "@ledgerhq/hw-transport-webusb": "^6.28.6", - "@ton-community/ton-ledger": "^7.2.0-pre.2", + "@ton-community/ton-ledger": "^7.2.0-pre.3", "@ton-keychain/core": "^0.0.4", "@ton/core": "0.56.0", "@ton/crypto": "3.2.0", diff --git a/packages/core/src/AppSdk.ts b/packages/core/src/AppSdk.ts index 0c29413ac..7c93a2655 100644 --- a/packages/core/src/AppSdk.ts +++ b/packages/core/src/AppSdk.ts @@ -6,7 +6,7 @@ import { NFT } from './entries/nft'; import { FavoriteSuggestion, LatestSuggestion } from './entries/suggestion'; import { TonContract, TonWalletStandard } from './entries/wallet'; import { KeystoneMessageType, KeystonePathInfo } from './service/keystone/types'; -import { LedgerTransaction } from './service/ledger/connector'; +import { LedgerTonProofRequest, LedgerTransaction } from './service/ledger/connector'; import { TonTransferParams } from './service/deeplinkingService'; export type GetPasswordType = 'confirm' | 'unlock'; @@ -38,7 +38,9 @@ export interface UIEvents { boc: string; wallet: TonWalletStandard; }; - ledger: { path: number[]; transaction: LedgerTransaction }; + ledger: + | { path: number[]; transactions: LedgerTransaction[] } + | { path: number[]; tonProof: LedgerTonProofRequest }; keystone: { message: Buffer; messageType: KeystoneMessageType; pathInfo?: KeystonePathInfo }; loading: void; transfer: TransferInitParams; diff --git a/packages/core/src/entries/signer.ts b/packages/core/src/entries/signer.ts index 750747f7b..1e500d52a 100644 --- a/packages/core/src/entries/signer.ts +++ b/packages/core/src/entries/signer.ts @@ -5,7 +5,7 @@ export type BaseSigner = (message: Cell) => Promise; export type CellSigner = BaseSigner & { type: 'cell' }; -export type LedgerSigner = ((message: LedgerTransaction) => Promise) & { +export type LedgerSigner = ((messages: LedgerTransaction[]) => Promise) & { type: 'ledger'; }; diff --git a/packages/core/src/service/ledger/connector.ts b/packages/core/src/service/ledger/connector.ts index e84e0bdcc..1ff94db95 100644 --- a/packages/core/src/service/ledger/connector.ts +++ b/packages/core/src/service/ledger/connector.ts @@ -14,6 +14,21 @@ const withDeadline = (p: Promise, ms: number): Promise => export type LedgerTonTransport = TonTransport; export type LedgerTransaction = Parameters[1]; +export type LedgerTonProofRequest = { + domain: string; + timestamp: number; + payload: Buffer; + testOnly?: boolean; + bounceable?: boolean; + chain?: number; + subwalletId?: number; + walletVersion?: 'v3r2' | 'v4'; +}; + +export type LedgerTonProofResponse = { + signature: Buffer; + hash: Buffer; +}; export const connectLedger = async () => { let transport: Transport; diff --git a/packages/core/src/service/ton-blockchain/sender/ledger-message-sender.ts b/packages/core/src/service/ton-blockchain/sender/ledger-message-sender.ts index cdadb1b8b..04e733f0e 100644 --- a/packages/core/src/service/ton-blockchain/sender/ledger-message-sender.ts +++ b/packages/core/src/service/ton-blockchain/sender/ledger-message-sender.ts @@ -3,7 +3,7 @@ import { BlockchainApi, EmulationApi } from '../../../tonApiV2'; import BigNumber from 'bignumber.js'; import { LedgerSigner } from '../../../entries/signer'; import { walletContractFromState } from '../../wallet/contractService'; -import { Address, Cell, comment as encodeComment, SendMode } from '@ton/core'; +import { Address, Cell, comment as encodeComment, SendMode, StateInit } from '@ton/core'; import { externalMessage, userInputAddressIsBounceable, @@ -12,7 +12,8 @@ import { getTTL, getWalletSeqNo, tonConnectAddressIsBounceable, - toStateInit + toStateInit, + estimationSigner } from '../utils'; import { AssetAmount } from '../../../entries/crypto/asset/asset-amount'; import { TonAsset, tonAssetAddressToString } from '../../../entries/crypto/asset/ton-asset'; @@ -25,7 +26,9 @@ import { MessagePayloadParam, serializePayload } from '../encoder/types'; import { TonPayloadFormat } from '@ton-community/ton-ledger/dist/TonTransport'; import { TON_ASSET } from '../../../entries/crypto/asset/constants'; import { TonEstimation } from '../../../entries/send'; -import { Network } from '../../../entries/network'; +import { LedgerTransaction } from '../../ledger/connector'; +import { WalletMessageSender } from './wallet-message-sender'; +import { TonConnectEncoder } from '../encoder/ton-connect-encoder'; export class LedgerMessageSender { constructor( @@ -34,28 +37,42 @@ export class LedgerMessageSender { private readonly signer: LedgerSigner ) {} + private async sign( + tx: T + ): Promise { + if (Array.isArray(tx)) { + return this.signer(tx) as Promise; + } else { + const res = await this.signer([tx]); + return res[0] as T extends LedgerTransaction ? Cell : Cell[]; + } + } + tonRawTransfer = async ({ to, value, body, bounce, - sendMode + sendMode, + init }: { to: Address; value: bigint; body?: Cell; sendMode: SendMode; bounce: boolean; + init?: StateInit; }) => { const { timestamp, seqno, contract } = await this.getTransferParameters(); - const transfer = await this.signer({ + const transfer = await this.sign({ to, bounce, amount: value, seqno, timeout: getTTL(timestamp), sendMode: sendMode, + stateInit: init, payload: body ? { type: 'unsafe', @@ -80,7 +97,7 @@ export class LedgerMessageSender { }) => { const { timestamp, seqno, contract } = await this.getTransferParameters(); - const transfer = await this.signer({ + const transfer = await this.sign({ to: Address.parse(to), bounce: await userInputAddressIsBounceable(this.api, to), amount: BigInt(weiAmount.toFixed(0)), @@ -135,7 +152,7 @@ export class LedgerMessageSender { const { customPayload, stateInit, jettonWalletAddress } = await jettonEncoder.jettonCustomPayload(tonAssetAddressToString(amount.asset.address)); - const transfer = await this.signer({ + const transfer = await this.sign({ to: Address.parse(jettonWalletAddress), bounce: true, amount: JettonEncoder.jettonTransferAmount, @@ -172,7 +189,7 @@ export class LedgerMessageSender { }) => { const { timestamp, seqno, contract } = await this.getTransferParameters(); - const transfer = await this.signer({ + const transfer = await this.sign({ to: Address.parse(nftAddress), bounce: true, amount: nftTransferAmount, @@ -194,32 +211,34 @@ export class LedgerMessageSender { }; tonConnectTransfer = async (transfer: TonConnectTransactionPayload) => { - if (transfer.messages.length !== 1) { - throw new LedgerError('Ledger signer does not support multiple messages'); - } - const { timestamp, seqno, contract } = await this.getTransferParameters(); - const message = transfer.messages[0]; - let transferCell: Cell; + let transferCells: Cell[]; try { - transferCell = await this.signer({ - to: Address.parse(message.address), - bounce: await tonConnectAddressIsBounceable(this.api, message.address), - amount: BigInt(message.amount), - seqno, - timeout: getTTL(timestamp), - sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS, - payload: message.payload - ? { - type: 'unsafe', - message: Cell.fromBase64(message.payload) - } - : undefined, - stateInit: toStateInit(message.stateInit) - }); + transferCells = await this.sign( + await Promise.all( + transfer.messages.map(async (message, index) => ({ + to: Address.parse(message.address), + bounce: await tonConnectAddressIsBounceable(this.api, message.address), + amount: BigInt(message.amount), + seqno: seqno + index, + timeout: getTTL(timestamp + index * 60), + sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS, + payload: message.payload + ? { + type: 'unsafe' as const, + message: Cell.fromBase64(message.payload) + } + : undefined, + stateInit: toStateInit(message.stateInit) + })) + ) + ); } catch (e) { console.error(e); + if (typeof e === 'object' && e && 'name' in e && e.name === 'UserCancelledError') { + throw e as Error; + } throw new LedgerError( typeof e === 'string' ? e @@ -229,7 +248,28 @@ export class LedgerMessageSender { ); } - return this.toSenderObject(externalMessage(contract, seqno, transferCell)); + const externalMessages = transferCells.map((cell, index) => + externalMessage(contract, seqno + index, cell) + ); + + return { + send: async () => { + await new BlockchainApi(this.api.tonApiV2).sendBlockchainMessage({ + sendBlockchainMessageRequest: { + batch: externalMessages.map(message => message.toBoc().toString('base64')) + } + }); + return externalMessages[0]; + }, + estimate: async (): Promise => { + return new WalletMessageSender(this.api, this.wallet, estimationSigner).estimate( + await new TonConnectEncoder(this.api, this.wallet.rawAddress).encodeTransfer({ + ...transfer, + variant: 'standard' + }) + ); + } + }; }; private async getTransferParameters() { diff --git a/packages/core/src/service/ton-blockchain/sender/multisig-create-order-sender.ts b/packages/core/src/service/ton-blockchain/sender/multisig-create-order-sender.ts index aba6621f2..3e30ffce6 100644 --- a/packages/core/src/service/ton-blockchain/sender/multisig-create-order-sender.ts +++ b/packages/core/src/service/ton-blockchain/sender/multisig-create-order-sender.ts @@ -11,7 +11,6 @@ import BigNumber from 'bignumber.js'; import { LedgerMessageSender } from './ledger-message-sender'; import { internal, SendMode } from '@ton/core'; import { TON_ASSET } from '../../../entries/crypto/asset/constants'; -import { Network } from '../../../entries/network'; export class MultisigCreateOrderSender implements ISender { constructor( diff --git a/packages/core/src/service/ton-blockchain/ton-raw-transaction.service.ts b/packages/core/src/service/ton-blockchain/ton-raw-transaction.service.ts index 167577bdb..3846ccc3a 100644 --- a/packages/core/src/service/ton-blockchain/ton-raw-transaction.service.ts +++ b/packages/core/src/service/ton-blockchain/ton-raw-transaction.service.ts @@ -3,7 +3,7 @@ import { BatteryMessageSender, GaslessMessageSender, LedgerMessageSender, Sender import BigNumber from 'bignumber.js'; import { getTonEstimationTonFee, TonEstimation } from '../../entries/send'; import { TonContract } from '../../entries/wallet'; -import { Address, Cell, internal, SendMode } from '@ton/core'; +import { Address, Cell, internal, SendMode, StateInit } from '@ton/core'; import { assertBalanceEnough } from './utils'; import { TON_ASSET } from '../../entries/crypto/asset/constants'; import { maxBigNumber } from '../../utils/common'; @@ -13,7 +13,7 @@ export type TonRawTransaction = { value: bigint; bounce?: boolean; body?: Cell; - stateInit?: Cell; + init?: StateInit; sendMode?: SendMode; }; diff --git a/packages/core/src/service/tonConnect/connectService.ts b/packages/core/src/service/tonConnect/connectService.ts index b64c49db6..761e91a63 100644 --- a/packages/core/src/service/tonConnect/connectService.ts +++ b/packages/core/src/service/tonConnect/connectService.ts @@ -292,11 +292,12 @@ export const tonConnectProofPayload = ( origin: string, wallet: string, payload: string -): ConnectProofPayload => { +): ConnectProofPayload & { domain: string } => { const timestampBuffer = Buffer.allocUnsafe(8); timestampBuffer.writeBigInt64LE(BigInt(timestamp)); - const domainBuffer = Buffer.from(new URL(origin).host); + const domain = new URL(origin).host; + const domainBuffer = Buffer.from(domain); const domainLengthBuffer = Buffer.allocUnsafe(4); domainLengthBuffer.writeInt32LE(domainBuffer.byteLength); @@ -329,7 +330,8 @@ export const tonConnectProofPayload = ( domainBuffer, payload, origin, - messageBuffer + messageBuffer, + domain }; }; @@ -386,7 +388,7 @@ export const tonDisconnectRequest = async (options: { storage: IStorage; webView const getMaxMessages = (account: Account) => { if (account.type === 'ledger') { - return 1; + return 4; } const wallet = account.activeTonWallet; diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index f8b966fa9..00b0b79e9 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -8,6 +8,7 @@ "accounts_manage_folder_name": "Folder Name", "accounts_new_folder": "New Folder", "actionTitle": "Open the wallet", + "activate": "Activate", "add": "Add", "add_dns_address": "Add wallet address that domain %1% will link to.", "add_wallet_existing_multisig_description": "%{number} wallets managed by your wallet list", @@ -57,6 +58,9 @@ "check_words_caption": "To check whether you’ve written down your recovery phrase correctly, please enter the %1%, %2%, and  %3% words.", "close": "Close", "collectibles_empty_header": "Your collectibles will be shown here", + "confirm_disable_two_fa_description": "Disabling 2FA reduces wallet security. Anyone with your recovery phrase could gain full control over your wallet.", + "confirm_disable_two_fa_ok_button": "Send 2FA disable request to your Telegram", + "confirm_disable_two_fa_title": "Disable 2FA?", "confirm_discard_btn_continue_editing": "Continue Editing", "confirm_discard_btn_discard": "Discard Changes", "confirm_discard_description": "You have unsaved changes. If you close this window, your progress will be lost. Do you want to continue?", @@ -107,6 +111,7 @@ "Enable_storing_config": "Enable storing config", "enter_password": "Enter password", "export_dot_csv": "Export .CSV", + "export_trc_20_wallet": "Export TRC20 Wallet", "force_reload": "Force Reload", "help": "Help", "hide": "Hide", @@ -165,6 +170,8 @@ "ledger_operation_not_supported": "The operation is not available for Ledger wallets. Select another wallet and try again.", "ledger_pair_subtitle": "Hardware module, Bluetooth or USB-C, limited TON features", "ledger_pair_title": "Pair with Ledger", + "ledger_steps_confirm_num_tx": "Confirm transaction #{number} on Ledger", + "ledger_steps_confirm_proof": "Confirm action on Ledger", "ledger_steps_confirm_tx": "Confirm your transaction on Ledger", "ledger_steps_connect": "Connect Ledger to your device", "ledger_steps_install_ton": "Install TON App ", @@ -179,6 +186,7 @@ "Manage_wallets": "Manage Wallets", "Minimize": "Minimize", "MinPassword": "Must be at least 6 characters.", + "multichain": "Multichain", "multi_send_about_w5": "About W5", "multi_send_add_more": "Add More", "multisend_confirm_error_insufficient_ton_for_fee": "Wallet balance %balance% is not enough to cover the blockchain fees. Minimum balance required: %required%. Unused TON will be returned to your wallet after the transaction.", @@ -293,6 +301,8 @@ "receive_ton_description": "Send only Toncoin TON and tokens\nin TON network to this address, or you\nmight lose your funds.", "receive_trc20": "Receive USDT TRC20", "receive_trc20_description": "Send only USDT TRC20\nto this address, or you might\nlose your funds.", + "receive_trx": "Receive Tron TRX", + "receive_trx_description": "Send only Tron TRX to this address, or you might lose your funds.", "recipients": "Recipients", "Redo": "Redo", "Reload": "Reload", @@ -372,21 +382,39 @@ "transaction_call_date": "Contract Call %{date}", "transaction_type_mint": "Mint", "transaction_type_purchase": "Purchase", + "tron_account_export_warning_explanation": "This phrase is for TRC20 only. It cannot restore your TON wallet. Use your TON recovery phrase for TON wallet recovery.", + "tron_top_up_trx_button": "Top Up TRX", + "tron_top_up_trx_description": "You need TRX to pay transaction fees. Balance: {balance}", + "tron_top_up_trx_title": "TRX is required for USD₮ TRC20", "try_again": "Try Again", "two_fa_confirm_tg_cannot_access_tg": "Can’t access your Telegram account?", "two_fa_confirm_tg_description": "Go to the @tonkeeper_2fa_bot and tap «Confirm» to complete the transaction.", "two_fa_confirm_tg_title": "Confirm Transaction in Telegram", + "two_fa_long": "Two-Factor Authentication", + "two_fa_recovery_cancelled_toast": "Recovery cancelled. Now transfer your funds to a new wallet for security. ", "two_fa_send_continue_with_tg": "Continue with Telegram", - "two_fa_settings_heading_description": "This experimental feature allows you to enable two-factor authentication (2FA) for added wallet security. It will prompt you for confirmation in Telegram during large transactions and help restore your wallet if you lose your mnemonic. Installation steps:", + "two_fa_settings_cancel_recovery_button": "Cancel Recovery", + "two_fa_settings_change_tg_button": "Change Linked Telegram Account", + "two_fa_settings_disable_button": "Disable 2FA", + "two_fa_settings_heading_active_description": "Your wallet is secured. You can disable two-factor authentication if necessary, but it will cause a decrease of the security.", + "two_fa_settings_heading_active_title": "Two-Factor Authentication Enabled", + "two_fa_settings_heading_description": "This experimental feature allows you to enable two-factor authentication (2FA) for added wallet security. It will prompt you for confirmation in Telegram during transactions. Installation steps:", + "two_fa_settings_heading_recovery_description": "2FA will be disabled on {date}. If you didn't initiate the recovery, cancel it and transfer your funds to a new wallet for security.", + "two_fa_settings_heading_recovery_title": "Wallet Recovery Process Started", "two_fa_settings_heading_title": "Two-Factor Authentication", + "two_fa_settings_reconnect_tg_connection_modal_description": "Scan the QR code or open Telegram to connect a new account. If you have access to your previous account, the change will be instant. Without access, it will take 14 days.", + "two_fa_settings_reconnect_tg_connection_modal_heading": "Connect a New Telegram Account", "two_fa_settings_set_up_deploy_step_button": "Activate 2FA", "two_fa_settings_set_up_deploy_step_description": "Confirm transaction to install 2FA extension", "two_fa_settings_set_up_tg_connection_modal_copy_button": "Copy Link", - "two_fa_settings_set_up_tg_connection_modal_heading": "Scan QR code below with your mobile phone or open Telegram on this device to connect.", + "two_fa_settings_set_up_tg_connection_modal_heading": "Scan the QR code or open Telegram to connect a new account.", "two_fa_settings_set_up_tg_connection_modal_open_button": "Open Telegram", "two_fa_settings_set_up_tg_step_description": "Confirm your connection in your Telegram ", - "two_fa_settings_warning_balance_required": "A TON balance is required to install or uninstall the extension.", + "two_fa_settings_tg_recovery_button": "Link new account", + "two_fa_settings_tg_recovery_text": "Can’t access your Telegram account? Linking a new account will take 14 days.", + "two_fa_settings_warning_balance_required": "0.5 TON is required to install or uninstall 2FA.", "two_fa_settings_warning_battery_gasless": "Battery mode and gasless transactions are not compatible with 2FA.", + "two_fa_settings_warning_can_not_recover": "2FA can't recover your secret phrase.", "two_fa_settings_warning_wallet_will_stop": "The same wallet will stop working on your other devices.", "two_fa_short": "2FA", "txActions_USDT_transfer": "USDT Transfer", @@ -398,6 +426,7 @@ "upload_file": "Upload file", "View": "View", "view_on_tonviewer": "View on Tonviewer", + "wallet_2fa_recovery_started": "Recovery Process Started", "wallet_address": "Wallet address", "wallet_aside_collectibles": "Collectibles", "wallet_aside_domains": "Domains", diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json index c98481eff..fe623984b 100644 --- a/packages/locales/src/tonkeeper-web/ru-RU.json +++ b/packages/locales/src/tonkeeper-web/ru-RU.json @@ -8,6 +8,7 @@ "accounts_manage_folder_name": "Имя папки", "accounts_new_folder": "Новая папка", "actionTitle": "Открыть Кошелек", + "activate": "Активировать", "add": "Добавить", "add_dns_address": "Добавьте адрес кошелька, на который будет ссылаться домен %1%", "add_wallet_existing_multisig_description": "%{number} кошельков управляются вашим списком кошельков", @@ -56,6 +57,9 @@ "check_words_caption": "Чтобы убедиться, что вы записали секретный ключ правильно, введите слова %1%, %2% и %3%.", "close": "Закрыть", "collectibles_empty_header": "Здесь будут ваши коллекции", + "confirm_disable_two_fa_description": "Отключение 2FA снижает безопасность кошелька. Любой, кто знает вашу фразу восстановления, может получить полный контроль над вашим кошельком.", + "confirm_disable_two_fa_ok_button": "Отправить запрос на отключение 2FA в ваш Telegram", + "confirm_disable_two_fa_title": "Отключить 2FA?", "confirm_discard_btn_continue_editing": "Продолжить редактирование", "confirm_discard_btn_discard": "Сбросить изменения и выйти", "confirm_discard_description": "У вас есть не сохраненные изменения. Если вы закроете это окно, ваш прогресс будет потерян. Вы хотите продолжить?", @@ -105,6 +109,7 @@ "Enable_storing_config": "Cохранение конфигурации", "enter_password": "Введите пароль", "export_dot_csv": "Экспорт в .CSV", + "export_trc_20_wallet": "Экспортировать TRC20 кошелек", "force_reload": "Принудительная перезагрузка", "help": "Помощь", "hide": "Скрыть", @@ -158,6 +163,8 @@ "ledger_operation_not_supported": "Операция недоступна для кошельков Ledger. Выберите другой кошелек и повторите попытку.", "ledger_pair_subtitle": "Более высокий уровень безопасности благодаря аппаратному кошельку", "ledger_pair_title": "Подключить Ledger", + "ledger_steps_confirm_num_tx": "Подтвердите транзакцию #{number} в Ledger", + "ledger_steps_confirm_proof": "Подтвердите действие в Ledger", "ledger_steps_confirm_tx": "Подтвердите транзакцию в Ledger", "ledger_steps_connect": "Подключите Ledger к своему устройству", "ledger_steps_install_ton": "Установить приложение ", @@ -172,6 +179,7 @@ "Manage_wallets": "Управление кошельками", "Minimize": "Свернуть", "MinPassword": "Должно быть не менее 6 символов.", + "multichain": "Мультичейн", "multi_send_about_w5": "Подробнее о W5", "multi_send_add_more": "Добавить еще", "multisend_confirm_error_insufficient_ton_for_fee": "Баланса кошелька %balance% недостаточно для покрытия комиссий блокчейна. Требуемый минимальный баланс: %required%. Неиспользованный остаток TON после транзакции будет возвращен на ваш кошелек.", @@ -282,6 +290,8 @@ "receive_ton_description": "Отправляйте на этот адрес только Toncoin TON и токены в сети TON, иначе вы можете потерять свои средства.", "receive_trc20": "Получить USDT TRC20", "receive_trc20_description": "Отправляйте на этот адрес только USDT TRC20, иначе вы можете потерять свои средства.", + "receive_trx": "Получить Tron TRX", + "receive_trx_description": "Отправляйте на этот адрес только Tron TRX, иначе вы можете потерять свои средства.", "recipients": "Получатели", "Redo": "Повторить", "Reload": "Перезагрузить", @@ -359,21 +369,39 @@ "transaction_call_date": "Вызов контракта %{date}", "transaction_type_mint": "Создание", "transaction_type_purchase": "Покупка", + "tron_account_export_warning_explanation": "Эта фраза предназначена только для TRC20. Она не может восстановить ваш кошелек TON. Используйте фразу восстановления TON для восстановления кошелька TON.", + "tron_top_up_trx_button": "Пополнить TRX", + "tron_top_up_trx_description": "Вам нужны TRX для оплаты комиссий за транзакции. Баланс: {balance}", + "tron_top_up_trx_title": "TRX требуется для операций с USD₮ TRC20", "try_again": "Повторить", "two_fa_confirm_tg_cannot_access_tg": "Не можете получить доступ к своему Telegram аккаунту?", "two_fa_confirm_tg_description": "Перейдите в @tonkeeper_2fa_bot и нажмите «Подтвердить», чтобы завершить транзакцию.", "two_fa_confirm_tg_title": "Подтвердите транзакцию в Telegram", + "two_fa_long": "Двухфакторная аутентификация", + "two_fa_recovery_cancelled_toast": "Восстановление отменено. Теперь переведите свои средства в новый кошелек для безопасности", "two_fa_send_continue_with_tg": "Продолжить с Telegram", - "two_fa_settings_heading_description": "Эта экспериментальная функция позволяет включить двухфакторную аутентификацию (2FA) для дополнительной безопасности кошелька. Она будет запрашивать подтверждение в Telegram при крупных транзакциях и поможет восстановить ваш кошелек, если вы потеряете мнемоническую фразу. Шаги установки:", + "two_fa_settings_cancel_recovery_button": "Отменить восстановление", + "two_fa_settings_change_tg_button": "Изменить привязанный аккаунт Telegram", + "two_fa_settings_disable_button": "Отключить 2FA", + "two_fa_settings_heading_active_description": "Ваш кошелек защищен. Вы можете отключить двухфакторную аутентификацию при необходимости, но это приведет к снижению уровня безопасности.", + "two_fa_settings_heading_active_title": "Двухфакторная аутентификация включена", + "two_fa_settings_heading_description": "Эта экспериментальная функция позволяет включить двухфакторную аутентификацию (2FA) для дополнительной безопасности кошелька. Она будет запрашивать подтверждение в Telegram во время транзакций. Шаги установки:", + "two_fa_settings_heading_recovery_description": "2FA будет отключена {date}. Если вы не инициировали восстановление, отмените его и переведите свои средства в новый кошелек для безопасности.", + "two_fa_settings_heading_recovery_title": "Процесс восстановления кошелька начат", "two_fa_settings_heading_title": "Двухфакторная аутентификация", + "two_fa_settings_reconnect_tg_connection_modal_description": "Отсканируйте QR-код или откройте Telegram, чтобы подключить новый аккаунт. Если у вас есть доступ к предыдущему аккаунту, изменение произойдет мгновенно. Без доступа это займет 14 дней.", + "two_fa_settings_reconnect_tg_connection_modal_heading": "Подключить новый аккаунт Telegram", "two_fa_settings_set_up_deploy_step_button": "Активировать 2FA", "two_fa_settings_set_up_deploy_step_description": "Подтвердите транзакцию для установки расширения 2FA", "two_fa_settings_set_up_tg_connection_modal_copy_button": "Копировать ссылку", - "two_fa_settings_set_up_tg_connection_modal_heading": "Отсканируйте QR-код ниже с помощью вашего мобильного телефона или откройте Telegram на этом устройстве для подключения.", + "two_fa_settings_set_up_tg_connection_modal_heading": "Отсканируйте QR-код или откройте Telegram, чтобы подключить новый аккаунт.", "two_fa_settings_set_up_tg_connection_modal_open_button": "Открыть Телеграм", "two_fa_settings_set_up_tg_step_description": "Подтвердите соединение в Telegram ", - "two_fa_settings_warning_balance_required": "Для установки или удаления расширения требуется баланс TON.", + "two_fa_settings_tg_recovery_button": "Привязать новый аккаунт", + "two_fa_settings_tg_recovery_text": "Не можете получить доступ к своему Telegram аккаунту? Привязка нового аккаунта займет 14 дней.", + "two_fa_settings_warning_balance_required": "Для установки или удаления расширения требуется 0.5 TON.", "two_fa_settings_warning_battery_gasless": "Батарейка Tonkeeper и безгазовые транзакции не работают с двухфакторной аутентификацией.", + "two_fa_settings_warning_can_not_recover": "2FA не поможет восстановить вашу секретную фразу.", "two_fa_settings_warning_wallet_will_stop": "Этот же кошелек перестанет работать на других ваших устройствах.", "two_fa_short": "2ФА", "txActions_USDT_transfer": "Перевод USDT", @@ -385,6 +413,7 @@ "upload_file": "Загрузить файл", "View": "Вид", "view_on_tonviewer": "Открыть в Tonviewer", + "wallet_2fa_recovery_started": "Процесс сброса запущен", "wallet_address": "Адрес кошелька", "wallet_aside_collectibles": "Коллекции", "wallet_aside_domains": "Домены", diff --git a/packages/uikit/src/components/ConnectLedgerNotification.tsx b/packages/uikit/src/components/ConnectLedgerNotification.tsx index be0554542..17e6b5f45 100644 --- a/packages/uikit/src/components/ConnectLedgerNotification.tsx +++ b/packages/uikit/src/components/ConnectLedgerNotification.tsx @@ -3,7 +3,11 @@ import { useAppSdk } from '../hooks/appSdk'; import { useTranslation } from '../hooks/translation'; import { Notification } from './Notification'; import { Button } from './fields/Button'; -import { LedgerTransaction } from '@tonkeeper/core/dist/service/ledger/connector'; +import { + LedgerTonProofRequest, + LedgerTonProofResponse, + LedgerTransaction +} from '@tonkeeper/core/dist/service/ledger/connector'; import { useConnectLedgerMutation } from '../state/ledger'; import styled from 'styled-components'; import { Cell } from '@ton/core'; @@ -31,13 +35,21 @@ const ButtonsBlock = styled.div` } `; +type LedgerContentLedgerParams = + | { path: number[]; transactions: LedgerTransaction[]; onSubmit: (result: Cell[]) => void } + | { + path: number[]; + tonProof: LedgerTonProofRequest; + onSubmit: (result: LedgerTonProofResponse) => void; + }; + export const LedgerContent: FC<{ - ledgerParams: { path: number[]; transaction: LedgerTransaction }; + ledgerParams: LedgerContentLedgerParams; onClose: (reason?: unknown) => void; - onSubmit: (result: Cell) => void; -}> = ({ ledgerParams, onClose, onSubmit }) => { +}> = ({ ledgerParams, onClose }) => { const { t } = useTranslation(); const [isCompleted, setIsCompleted] = useState(false); + const [currentTxToConfirmIndex, setCurrentTxToConfirmIndex] = useState(0); const { mutateAsync: connectLedger, @@ -47,29 +59,45 @@ export const LedgerContent: FC<{ reset: resetConnection } = useConnectLedgerMutation(); - const connect = () => { - connectLedger() - .then(transport => - transport - .signTransaction(ledgerParams.path, ledgerParams.transaction) - .then(val => { - setIsCompleted(true); - setTimeout(() => onSubmit(val), 500); - }) - .catch(e => { - console.error(e); - if ( - typeof e === 'object' && - 'message' in e && - e.message.includes('0x6985') - ) { - onClose(new UserCancelledError('Cancel auth request')); - } else { - onClose(e); - } - }) - ) - .catch(console.debug); + const connect = async () => { + try { + const transport = await connectLedger(); + try { + if ('tonProof' in ledgerParams) { + const val = await transport.getAddressProof( + ledgerParams.path, + ledgerParams.tonProof + ); + setIsCompleted(true); + setTimeout(() => ledgerParams.onSubmit(val), 500); + return; + } + + const result: Cell[] = []; + for (const transaction of ledgerParams.transactions) { + const val = await transport.signTransaction(ledgerParams.path, transaction); + result.push(val); + setCurrentTxToConfirmIndex(i => i + 1); + } + + setIsCompleted(true); + setTimeout(() => ledgerParams.onSubmit(result), 500); + } catch (e) { + console.error(e); + if ( + typeof e === 'object' && + e && + 'message' in e && + (e.message as string).includes('0x6985') + ) { + onClose(new UserCancelledError('Cancel auth request')); + } else { + onClose(e); + } + } + } catch (error) { + console.debug(error); + } }; useEffect(() => { @@ -92,9 +120,20 @@ export const LedgerContent: FC<{ currentStep = 'all-completed'; } + const connectionStepsProps = + 'transactions' in ledgerParams + ? { + transactionsToSign: ledgerParams.transactions.length, + signingTransactionIndex: currentTxToConfirmIndex, + action: 'transaction' as const + } + : { + action: 'ton-proof' as const + }; + return ( - + )} - {cantConnectLedger && ( - {t('ledger_operation_not_supported')} - )} + {cantConnectProof && {t('operation_not_supported')}} {isReadOnly && {t('operation_not_supported')}} {t('ton_login_notice')} diff --git a/packages/uikit/src/components/connect/TonTransactionNotification.tsx b/packages/uikit/src/components/connect/TonTransactionNotification.tsx index ce082ff6c..10500530c 100644 --- a/packages/uikit/src/components/connect/TonTransactionNotification.tsx +++ b/packages/uikit/src/components/connect/TonTransactionNotification.tsx @@ -20,12 +20,7 @@ import { H2, Label2, Label3 } from '../Text'; import { Button } from '../fields/Button'; import { MainButton, ResultButton, TransferViewHeaderBlock } from '../transfer/common'; import { EmulationList } from './EstimationLayout'; -import { - useAccountsState, - useActiveAccount, - useActiveApi, - useActiveWallet -} from '../../state/wallet'; +import { useAccountsState, useActiveAccount } from '../../state/wallet'; import { LedgerError } from '@tonkeeper/core/dist/errors/LedgerError'; import { AccountAndWalletInfo } from '../account/AccountAndWalletInfo'; import { useIsActiveAccountMultisig } from '../../state/multisig'; diff --git a/packages/uikit/src/components/ledger/LedgerConnectionSteps.tsx b/packages/uikit/src/components/ledger/LedgerConnectionSteps.tsx index e1d82296c..48f3445f4 100644 --- a/packages/uikit/src/components/ledger/LedgerConnectionSteps.tsx +++ b/packages/uikit/src/components/ledger/LedgerConnectionSteps.tsx @@ -1,10 +1,9 @@ import styled, { css } from 'styled-components'; -import { FC } from 'react'; +import { FC, useMemo } from 'react'; import { DoneIcon, DotIcon, ResponsiveSpinner } from '../Icon'; import { Body2, Body2Class } from '../Text'; import { LedgerIcon } from './LedgerIcons'; import { Dot } from '../Dot'; -import { useAppContext } from '../../hooks/appContext'; import { useAppSdk } from '../../hooks/appSdk'; import { useTranslation } from '../../hooks/translation'; import { useActiveConfig } from '../../state/wallet'; @@ -65,10 +64,12 @@ const AStyled = styled.span` `; export const LedgerConnectionSteps: FC<{ - showConfirmTxStep?: boolean; + transactionsToSign?: number; + signingTransactionIndex?: number; + action?: 'transaction' | 'ton-proof'; currentStep: 'connect' | 'open-ton' | 'confirm-tx' | 'all-completed'; className?: string; -}> = ({ currentStep, showConfirmTxStep, className }) => { +}> = ({ currentStep, transactionsToSign, signingTransactionIndex, className, action }) => { const config = useActiveConfig(); const { t } = useTranslation(); @@ -85,6 +86,11 @@ export const LedgerConnectionSteps: FC<{ sdk.openPage(faqBaseUrl + enPath); }; + const txToSignIndexes = useMemo( + () => new Array(transactionsToSign).fill(0).map((_, i) => i), + [transactionsToSign] + ); + return ( @@ -92,7 +98,7 @@ export const LedgerConnectionSteps: FC<{ step={ currentStep !== 'all-completed' ? currentStep - : showConfirmTxStep + : transactionsToSign ? 'confirm-tx' : 'open-ton' } @@ -121,7 +127,7 @@ export const LedgerConnectionSteps: FC<{ } > {t('ledger_steps_open_ton')} - {!showConfirmTxStep && ( + {!transactionsToSign && ( <> @@ -131,7 +137,7 @@ export const LedgerConnectionSteps: FC<{ )} - {showConfirmTxStep && ( + {transactionsToSign === 1 || action === 'ton-proof' ? ( - {t('ledger_steps_confirm_tx')} + {t( + action === 'ton-proof' + ? 'ledger_steps_confirm_proof' + : 'ledger_steps_confirm_tx' + )} - )} + ) : transactionsToSign && transactionsToSign > 1 ? ( + txToSignIndexes.map(index => ( + + signingTransactionIndex!) + ? 'future' + : currentStep === 'confirm-tx' && + signingTransactionIndex === index + ? 'active' + : 'completed' + } + /> + + {t('ledger_steps_confirm_num_tx', { number: index + 1 })} + + + )) + ) : null} ); diff --git a/packages/uikit/src/hooks/blockchain/multisig/useEstimateExisitingMultisigOrder.ts b/packages/uikit/src/hooks/blockchain/multisig/useEstimateExisitingMultisigOrder.ts index 3d1310dd8..80b751acc 100644 --- a/packages/uikit/src/hooks/blockchain/multisig/useEstimateExisitingMultisigOrder.ts +++ b/packages/uikit/src/hooks/blockchain/multisig/useEstimateExisitingMultisigOrder.ts @@ -1,5 +1,4 @@ import { useMutation } from '@tanstack/react-query'; -import { useAppContext } from '../../appContext'; import { useActiveMultisigWalletInfo } from '../../../state/multisig'; import { useAsyncQueryData } from '../../useAsyncQueryData'; import { AccountEvent, AccountsApi, MultisigOrder } from '@tonkeeper/core/dist/tonApiV2'; diff --git a/packages/uikit/src/hooks/blockchain/multisig/useSendExisitingMultisigOrder.ts b/packages/uikit/src/hooks/blockchain/multisig/useSendExisitingMultisigOrder.ts index 804e34282..e2faffeed 100644 --- a/packages/uikit/src/hooks/blockchain/multisig/useSendExisitingMultisigOrder.ts +++ b/packages/uikit/src/hooks/blockchain/multisig/useSendExisitingMultisigOrder.ts @@ -2,11 +2,14 @@ import { useMutation } from '@tanstack/react-query'; import { useActiveMultisigAccountHost, useActiveMultisigWalletInfo } from '../../../state/multisig'; import { useAsyncQueryData } from '../../useAsyncQueryData'; import { MultisigOrder } from '@tonkeeper/core/dist/tonApiV2'; -import { useActiveApi, useInvalidateActiveWalletQueries } from '../../../state/wallet'; +import { + useActiveAccount, + useActiveApi, + useInvalidateActiveWalletQueries +} from '../../../state/wallet'; import { useTonRawTransactionService } from '../useBlockchainService'; import { EXTERNAL_SENDER_CHOICE, useGetSender } from '../useSender'; -import { useAppContext } from '../../appContext'; import { useNotifyErrorHandle } from '../../useNotification'; import { MultisigEncoder } from '@tonkeeper/core/dist/service/ton-blockchain/encoder/multisig-encoder/multisig-encoder'; import { zeroFee } from '@tonkeeper/core/dist/service/ton-blockchain/utils'; @@ -21,6 +24,7 @@ export function useSendExisitingMultisigOrder(orderAddress: MultisigOrder['addre const rawTransactionService = useTonRawTransactionService(); const getSender = useGetSender(); const notifyError = useNotifyErrorHandle(); + const account = useActiveAccount(); return useMutation(async () => { try { diff --git a/packages/uikit/src/hooks/blockchain/useSender.ts b/packages/uikit/src/hooks/blockchain/useSender.ts index c167c88bf..a835406c7 100644 --- a/packages/uikit/src/hooks/blockchain/useSender.ts +++ b/packages/uikit/src/hooks/blockchain/useSender.ts @@ -412,11 +412,11 @@ export const useGetSender = () => { ); const signer = await getSigner(signerAccount.id, signerWallet.id); - if (signer.type !== 'cell') { - throw new Error('Unexpected signer type'); + if (signer.type === 'cell') { + return new WalletMessageSender(api, signerWallet, signer); + } else { + return new LedgerMessageSender(api, signerWallet, signer); } - - return new WalletMessageSender(api, signerWallet, signer); } if (!isStandardTonWallet(wallet)) { diff --git a/packages/uikit/src/state/mnemonic.ts b/packages/uikit/src/state/mnemonic.ts index eb24973d6..e18578cc1 100644 --- a/packages/uikit/src/state/mnemonic.ts +++ b/packages/uikit/src/state/mnemonic.ts @@ -8,7 +8,11 @@ import { CellSigner, Signer } from '@tonkeeper/core/dist/entries/signer'; import { TonWalletStandard, WalletId } from '@tonkeeper/core/dist/entries/wallet'; import { accountsStorage } from '@tonkeeper/core/dist/service/accountsStorage'; import { KeystoneMessageType } from '@tonkeeper/core/dist/service/keystone/types'; -import { LedgerTransaction } from '@tonkeeper/core/dist/service/ledger/connector'; +import { + LedgerTonProofRequest, + LedgerTonProofResponse, + LedgerTransaction +} from '@tonkeeper/core/dist/service/ledger/connector'; import { decryptWalletMnemonic, mnemonicToKeypair @@ -190,8 +194,8 @@ export const getSigner = async ( d => d.activeTonWalletId === wallet!.id )!; const path = getLedgerAccountPathByIndex(derivation.index); - const callback = async (transaction: LedgerTransaction) => - pairLedgerByNotification(sdk, path, transaction); + const callback = async (transactions: LedgerTransaction[]) => + pairLedgerByNotification<'transaction'>(sdk, path, { transactions }); callback.type = 'ledger' as const; return callback; } @@ -417,40 +421,86 @@ const pairKeystoneByNotification = async ( }); }; -const pairLedgerByNotification = async ( +export const getLedgerTonProofSigner = async ( sdk: IAppSdk, - path: number[], - transaction: LedgerTransaction -): Promise => { - const id = Date.now(); - return new Promise((resolve, reject) => { - sdk.uiEvents.emit('ledger', { - method: 'ledger', - id, - params: { path, transaction } - }); + accountId: AccountId, + { + walletId + }: { + walletId?: WalletId; + } = {} +): Promise<(request: LedgerTonProofRequest) => Promise> => { + const account = await accountsStorage(sdk.storage).getAccount(accountId); + if (!account) { + throw new Error('Account not found'); + } - const onCallback = (message: { - method: 'response'; - id?: number | undefined; - params: unknown; - }) => { - if (message.id === id) { - const { params } = message; - sdk.uiEvents.off('response', onCallback); + if (account.type !== 'ledger') { + throw new Error('Unexpected account type'); + } - if (params && typeof params === 'object' && params instanceof Cell) { - resolve(params as Cell); - } else { - if (params instanceof Error) { - reject(params); + const wallet = + walletId !== undefined ? account.getTonWallet(walletId) : account.activeTonWallet; + + const derivation = account.allAvailableDerivations.find( + d => d.activeTonWalletId === wallet!.id + )!; + const path = getLedgerAccountPathByIndex(derivation.index); + const callback = async (tonProof: LedgerTonProofRequest) => + pairLedgerByNotification<'ton-proof'>(sdk, path, { tonProof }); + callback.type = 'ledger' as const; + return callback; +}; + +const pairLedgerByNotification = async ( + sdk: IAppSdk, + path: number[], + request: T extends 'transaction' + ? { + transactions: LedgerTransaction[]; + } + : { + tonProof: LedgerTonProofRequest; + } +): Promise => { + const id = Date.now(); + return new Promise( + (resolve, reject) => { + sdk.uiEvents.emit('ledger', { + method: 'ledger', + id, + params: { path, ...request } + }); + + const onCallback = (message: { + method: 'response'; + id?: number | undefined; + params: unknown; + }) => { + if (message.id === id) { + const { params } = message; + sdk.uiEvents.off('response', onCallback); + + if ( + params && + typeof params === 'object' && + ((Array.isArray(params) && params[0] instanceof Cell) || + 'signature' in params) + ) { + resolve( + params as T extends 'transaction' ? Cell[] : LedgerTonProofResponse + ); } else { - reject(new Error(params?.toString())); + if (params instanceof Error) { + reject(params); + } else { + reject(new Error(params?.toString())); + } } } - } - }; + }; - sdk.uiEvents.on('response', onCallback); - }); + sdk.uiEvents.on('response', onCallback); + } + ); }; diff --git a/packages/uikit/src/state/tonConnect.ts b/packages/uikit/src/state/tonConnect.ts index 5ba3f378d..5ab2a4753 100644 --- a/packages/uikit/src/state/tonConnect.ts +++ b/packages/uikit/src/state/tonConnect.ts @@ -5,11 +5,12 @@ import { DAppManifest } from '@tonkeeper/core/dist/entries/tonConnect'; import { + createTonProofItem, getAppConnections, getTonConnectParams, + tonConnectProofPayload, toTonAddressItemReply, - toTonProofItemReply, - tonConnectProofPayload + toTonProofItemReply } from '@tonkeeper/core/dist/service/tonConnect/connectService'; import { AccountConnection, @@ -22,11 +23,12 @@ import { useAppSdk } from '../hooks/appSdk'; import { useTranslation } from '../hooks/translation'; import { subject } from '../libs/atom'; import { QueryKey } from '../libs/queryKey'; -import { signTonConnectOver } from './mnemonic'; +import { getLedgerTonProofSigner, signTonConnectOver } from './mnemonic'; import { isStandardTonWallet, TonWalletStandard, - WalletId + WalletId, + WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; import { IStorage } from '@tonkeeper/core/dist/Storage'; import { @@ -109,7 +111,7 @@ export const useConnectTonConnectAppMutation = () => { walletId: WalletId; } >(async ({ request, manifest, webViewUrl, account, walletId }) => { - const selectedIsLedget = account.type === 'ledger'; + const selectedIsLedger = account.type === 'ledger'; const network = getNetworkByAccount(account); const api = getContextApiByNetwork(appContext, network); @@ -127,32 +129,58 @@ export const useConnectTonConnectAppMutation = () => { result.push(toTonAddressItemReply(wallet, network)); } if (item.name === 'ton_proof') { - if (!isStandardTonWallet(wallet) || selectedIsLedget) { + if (!isStandardTonWallet(wallet)) { throw new TxConfirmationCustomError( "Current wallet doesn't support connection to the service" ); } - const signTonConnect = signTonConnectOver({ - sdk, - accountId: account.id, - t, - checkTouchId - }); - const timestamp = await getServerTime(api); + const proof = tonConnectProofPayload( - timestamp, + await getServerTime(api), webViewUrl ?? manifest.url, wallet.rawAddress, item.payload ); - result.push( - await toTonProofItemReply({ - storage: sdk.storage, - account, - signTonConnect, - proof - }) - ); + + if (selectedIsLedger) { + const ledgerTonProofSigner = await getLedgerTonProofSigner(sdk, account.id, { + walletId: wallet.id + }); + + if ( + wallet.version !== WalletVersion.V3R2 && + wallet.version !== WalletVersion.V4R2 + ) { + throw new TxConfirmationCustomError( + "Current wallet doesn't support connection to the service" + ); + } + + const { signature } = await ledgerTonProofSigner({ + domain: proof.domain, + timestamp: proof.timestamp, + payload: Buffer.from(proof.payload), + walletVersion: wallet.version === WalletVersion.V3R2 ? 'v3r2' : 'v4' + }); + + result.push({ name: 'ton_proof', proof: createTonProofItem(signature, proof) }); + } else { + const signTonConnect = signTonConnectOver({ + sdk, + accountId: account.id, + wallet, + t, + checkTouchId + }); + result.push( + await toTonProofItemReply({ + storage: sdk.storage, + account, + signTonConnect, + proof + }) + ); + } } } diff --git a/yarn.lock b/yarn.lock index bae24abac..9820b601b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7290,16 +7290,16 @@ __metadata: languageName: node linkType: hard -"@ton-community/ton-ledger@npm:^7.2.0-pre.2": - version: 7.2.0-pre.2 - resolution: "@ton-community/ton-ledger@npm:7.2.0-pre.2" +"@ton-community/ton-ledger@npm:^7.2.0-pre.3": + version: 7.2.0-pre.3 + resolution: "@ton-community/ton-ledger@npm:7.2.0-pre.3" dependencies: "@ledgerhq/hw-transport": "npm:^6.28.4" "@ton/crypto": "npm:^3.2.0" teslabot: "npm:^1.5.0" peerDependencies: "@ton/core": ">=0.52.2" - checksum: 10/9ddd536a8484b0afe34975f2e7b82c998134612c394ba94aa64140398d795daf2f734368300c28d09f2d697166b30387f7542bcfcd3fc73031ba233b7e09630d + checksum: 10/332ae8a7208471dbaca7f335c63d92b6eb9b0a617ef8ed267a7ea5ff51a1e6a765911a92f416fdbc8e7a29b2b927c1b62690c3464d3667332a1455176c3ef218 languageName: node linkType: hard @@ -7378,7 +7378,7 @@ __metadata: "@keystonehq/keystone-sdk": "npm:0.7.2" "@ledgerhq/hw-transport-webhid": "npm:^6.28.6" "@ledgerhq/hw-transport-webusb": "npm:^6.28.6" - "@ton-community/ton-ledger": "npm:^7.2.0-pre.2" + "@ton-community/ton-ledger": "npm:^7.2.0-pre.3" "@ton-keychain/core": "npm:^0.0.4" "@ton/core": "npm:0.56.0" "@ton/crypto": "npm:3.2.0"