diff --git a/changelogs/1.20.17.txt b/changelogs/1.20.17.txt
new file mode 100644
index 00000000..619f4cd5
--- /dev/null
+++ b/changelogs/1.20.17.txt
@@ -0,0 +1 @@
+Bug fixes and performance improvements
diff --git a/package-lock.json b/package-lock.json
index 53db3936..e891e2eb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "mytonwallet",
- "version": "1.20.16",
+ "version": "1.20.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mytonwallet",
- "version": "1.20.16",
+ "version": "1.20.17",
"license": "GPL-3.0-or-later",
"dependencies": {
"@awesome-cordova-plugins/core": "^6.6.0",
diff --git a/package.json b/package.json
index 81bd0ced..7ce936ab 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mytonwallet",
- "version": "1.20.16",
+ "version": "1.20.17",
"description": "The most feature-rich web wallet and browser extension for TON – with support of multi-accounts, tokens (jettons), NFT, TON DNS, TON Sites, TON Proxy, and TON Magic.",
"main": "index.js",
"scripts": {
diff --git a/public/version.txt b/public/version.txt
index a46bb8ef..e268e252 100644
--- a/public/version.txt
+++ b/public/version.txt
@@ -1 +1 @@
-1.20.16
+1.20.17
diff --git a/src/api/blockchains/ton/contracts/NominatorPool.ts b/src/api/blockchains/ton/contracts/NominatorPool.ts
index e4b0a347..f9c4ff51 100644
--- a/src/api/blockchains/ton/contracts/NominatorPool.ts
+++ b/src/api/blockchains/ton/contracts/NominatorPool.ts
@@ -1,5 +1,5 @@
import type {
- Cell, Contract, ContractProvider, TupleItem,
+ Cell, Contract, ContractProvider,
} from '@ton/core';
import {
Address, beginCell, contractAddress, TupleReader,
@@ -33,11 +33,12 @@ export class NominatorPool implements Contract {
withdrawRequested: boolean;
}[]> {
const res = await provider.get('list_nominators', []);
+ const tupleReader = (res.stack as TupleReader).readTuple();
+ const itemsArray = (tupleReader as any).items as bigint[][];
- const items = (res.stack as any).items[0].items;
-
- return items.map((item: { items: TupleItem[] }) => {
- const tuple = new TupleReader(item.items);
+ return itemsArray.map((items: bigint[]) => {
+ const tupleItems = items.map((value) => ({ type: 'int' as const, value }));
+ const tuple = new TupleReader(tupleItems);
const hash = tuple.readBigNumber().toString(16).padStart(64, '0');
const address = Address.parse(`0:${hash}`);
diff --git a/src/api/blockchains/ton/nfts.ts b/src/api/blockchains/ton/nfts.ts
index 8c69ad5e..ceda5f93 100644
--- a/src/api/blockchains/ton/nfts.ts
+++ b/src/api/blockchains/ton/nfts.ts
@@ -6,6 +6,7 @@ import type { ApiNft, ApiNftUpdate } from '../../types';
import type { ApiCheckTransactionDraftResult } from './types';
import {
+ BURN_ADDRESS,
NFT_BATCH_SIZE,
NOTCOIN_EXCHANGERS,
NOTCOIN_FORWARD_TON_AMOUNT,
@@ -156,7 +157,8 @@ export async function submitNftTransfers(options: {
const messages = nftAddresses.map((nftAddress, index) => {
const nft = nfts?.[index];
- const isNotcoinBurn = nft?.collectionAddress === NOTCOIN_VOUCHERS_ADDRESS;
+ const isNotcoinBurn = nft?.collectionAddress === NOTCOIN_VOUCHERS_ADDRESS
+ && (toAddress === BURN_ADDRESS || NOTCOIN_EXCHANGERS.includes(toAddress as any));
const payload = isNotcoinBurn
? buildNotcoinVoucherExchange(fromAddress, nftAddress, nft!.index)
: buildNftTransferPayload(fromAddress, toAddress, comment);
diff --git a/src/api/blockchains/ton/transactions.ts b/src/api/blockchains/ton/transactions.ts
index 81cba36b..1fd82f66 100644
--- a/src/api/blockchains/ton/transactions.ts
+++ b/src/api/blockchains/ton/transactions.ts
@@ -41,6 +41,7 @@ import { parseTxId } from './util';
import { fetchAddressBook, fetchLatestTxId, fetchTransactions } from './util/apiV3';
import { decryptMessageComment, encryptMessageComment } from './util/encryption';
import { buildNft, parseWalletTransactionBody } from './util/metadata';
+import { sendExternal } from './util/sendExternal';
import { fetchNftItems } from './util/tonapiio';
import {
commentToBytes,
@@ -52,7 +53,6 @@ import {
parseAddress,
parseBase64,
resolveTokenWalletAddress,
- sendExternal,
toBase64Address,
} from './util/tonCore';
import { fetchStoredAccount, fetchStoredAddress } from '../../common/accounts';
@@ -169,9 +169,8 @@ export async function checkTransactionDraft(
const account = await fetchStoredAccount(accountId);
const { address } = account;
- const isLedger = !!account.ledger;
- if (data && typeof data === 'string' && !isBase64Data && !isLedger) {
+ if (data && typeof data === 'string' && !isBase64Data) {
data = commentToBytes(data);
}
diff --git a/src/api/blockchains/ton/util/TonClient.ts b/src/api/blockchains/ton/util/TonClient.ts
index f88c9d55..240c7e8f 100644
--- a/src/api/blockchains/ton/util/TonClient.ts
+++ b/src/api/blockchains/ton/util/TonClient.ts
@@ -11,6 +11,7 @@ import { logDebug } from '../../../../util/logs';
axiosRetry(axios, {
retries: DEFAULT_RETRIES,
+ shouldResetTimeout: true,
retryDelay: (retryCount) => {
return retryCount * DEFAULT_ERROR_PAUSE;
},
diff --git a/src/api/blockchains/ton/util/sendExternal.ts b/src/api/blockchains/ton/util/sendExternal.ts
new file mode 100644
index 00000000..9f09a862
--- /dev/null
+++ b/src/api/blockchains/ton/util/sendExternal.ts
@@ -0,0 +1,51 @@
+import type { Cell } from '@ton/core';
+import { beginCell, external, storeMessage } from '@ton/core';
+
+import type { TonClient } from './TonClient';
+import type { TonWallet } from './tonCore';
+
+import { dieselSendBoc } from './diesel';
+
+export async function sendExternal(
+ client: TonClient,
+ wallet: TonWallet,
+ message: Cell,
+ withDiesel?: boolean,
+) {
+ const {
+ address,
+ init,
+ } = wallet;
+
+ let neededInit: { data: Cell; code: Cell } | undefined;
+ if (init && !await client.isContractDeployed(address)) {
+ neededInit = init;
+ }
+
+ const ext = external({
+ to: address,
+ init: neededInit ? {
+ code: neededInit.code,
+ data: neededInit.data,
+ } : undefined,
+ body: message,
+ });
+
+ const cell = beginCell()
+ .store(storeMessage(ext))
+ .endCell();
+
+ const msgHash = cell.hash().toString('base64');
+ const boc = cell.toBoc().toString('base64');
+
+ if (withDiesel) {
+ await dieselSendBoc(boc);
+ } else {
+ await client.sendFile(boc);
+ }
+
+ return {
+ boc,
+ msgHash,
+ };
+}
diff --git a/src/api/blockchains/ton/util/tonCore.ts b/src/api/blockchains/ton/util/tonCore.ts
index 3a6ba26e..1846e27b 100644
--- a/src/api/blockchains/ton/util/tonCore.ts
+++ b/src/api/blockchains/ton/util/tonCore.ts
@@ -1,7 +1,5 @@
import type { OpenedContract } from '@ton/core';
-import {
- Address, beginCell, Builder, Cell, external, storeMessage,
-} from '@ton/core';
+import { Address, Builder, Cell } from '@ton/core';
import axios from 'axios';
import { WalletContractV1R1 } from '@ton/ton/dist/wallets/WalletContractV1R1';
import { WalletContractV1R2 } from '@ton/ton/dist/wallets/WalletContractV1R2';
@@ -32,9 +30,13 @@ import { JettonWallet } from '../contracts/JettonWallet';
import { hexToBytes } from '../../../common/utils';
import { getEnvironment } from '../../../environment';
import {
- DEFAULT_IS_BOUNCEABLE, DNS_ZONES_MAP, JettonOpCode, LiquidStakingOpCode, OpCode, WORKCHAIN,
+ DEFAULT_IS_BOUNCEABLE,
+ DNS_ZONES_MAP,
+ JettonOpCode,
+ LiquidStakingOpCode,
+ OpCode,
+ WORKCHAIN,
} from '../constants';
-import { dieselSendBoc } from './diesel';
import { generateQueryId } from './index';
import { TonClient } from './TonClient';
@@ -225,6 +227,12 @@ export function packBytesAsSnake(bytes: Uint8Array, maxBytes = TON_MAX_COMMENT_B
return bytes;
}
+ return packBytesAsSnakeCell(bytes);
+}
+
+export function packBytesAsSnakeCell(bytes: Uint8Array): Cell {
+ const buffer = Buffer.from(bytes);
+
const mainBuilder = new Builder();
let prevBuilder: Builder | undefined;
let currentBuilder = mainBuilder;
@@ -365,38 +373,3 @@ export async function getDnsItemDomain(network: ApiNetwork, address: Address | s
return `${base}${zone}`;
}
-
-export async function sendExternal(
- client: TonClient,
- wallet: TonWallet,
- message: Cell,
- withDiesel?: boolean,
-) {
- const { address, init } = wallet;
-
- let neededInit: { data: Cell; code: Cell } | undefined;
- if (init && !await client.isContractDeployed(address)) {
- neededInit = init;
- }
-
- const ext = external({
- to: address,
- init: neededInit ? { code: neededInit.code, data: neededInit.data } : undefined,
- body: message,
- });
-
- const cell = beginCell()
- .store(storeMessage(ext))
- .endCell();
-
- const msgHash = cell.hash().toString('base64');
- const boc = cell.toBoc().toString('base64');
-
- if (withDiesel) {
- await dieselSendBoc(boc);
- } else {
- await client.sendFile(boc);
- }
-
- return { boc, msgHash };
-}
diff --git a/src/components/settings/SettingsWalletVersion.tsx b/src/components/settings/SettingsWalletVersion.tsx
index 1e06339f..85048bdc 100644
--- a/src/components/settings/SettingsWalletVersion.tsx
+++ b/src/components/settings/SettingsWalletVersion.tsx
@@ -117,7 +117,7 @@ function SettingsWalletVersion({
{lang('$read_more_about_wallet_version', {
ton_link: (
-
+
ton.org
),
diff --git a/src/config.ts b/src/config.ts
index 213681ac..bc948505 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -123,7 +123,7 @@ export const PROXY_HOSTS = process.env.PROXY_HOSTS;
export const TINY_TRANSFER_MAX_COST = 0.01;
-export const LANG_CACHE_NAME = 'mtw-lang-118';
+export const LANG_CACHE_NAME = 'mtw-lang-120';
export const LANG_LIST: LangItem[] = [{
langCode: 'en',
@@ -292,7 +292,7 @@ export const BURN_ADDRESS = 'UQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJKZ';
export const DEFAULT_WALLET_VERSION: ApiWalletVersion = 'v4R2';
export const POPULAR_WALLET_VERSIONS: ApiWalletVersion[] = ['v3R1', 'v3R2', 'v4R2', 'W5'];
-export const DEFAULT_TIMEOUT = 5000;
+export const DEFAULT_TIMEOUT = 10000;
export const DEFAULT_RETRIES = 3;
export const DEFAULT_ERROR_PAUSE = 500;
diff --git a/src/global/actions/api/swap.ts b/src/global/actions/api/swap.ts
index 44813333..525f7171 100644
--- a/src/global/actions/api/swap.ts
+++ b/src/global/actions/api/swap.ts
@@ -39,6 +39,8 @@ const PAIRS_CACHE: Record
{
});
addActionHandler('estimateSwap', async (global, actions, { shouldBlock, isEnoughToncoin }) => {
+ if (isEstimateSwapBeingExecuted) return;
+
+ isEstimateSwapBeingExecuted = true;
const resetParams = {
amountOutMin: '0',
transactionFee: '0',
@@ -547,6 +552,7 @@ addActionHandler('estimateSwap', async (global, actions, { shouldBlock, isEnough
...resetParams,
});
setGlobal(global);
+ isEstimateSwapBeingExecuted = false;
return;
}
@@ -565,6 +571,7 @@ addActionHandler('estimateSwap', async (global, actions, { shouldBlock, isEnough
errorType: SwapErrorType.InvalidPair,
});
setGlobal(global);
+ isEstimateSwapBeingExecuted = false;
return;
}
@@ -593,6 +600,7 @@ addActionHandler('estimateSwap', async (global, actions, { shouldBlock, isEnough
shouldTryDiesel: isEnoughToncoin === false,
});
+ isEstimateSwapBeingExecuted = false;
global = getGlobal();
if (!estimate || 'error' in estimate) {
@@ -648,6 +656,9 @@ addActionHandler('estimateSwap', async (global, actions, { shouldBlock, isEnough
});
addActionHandler('estimateSwapCex', async (global, actions, { shouldBlock }) => {
+ if (isEstimateCexSwapBeingExecuted) return;
+
+ isEstimateCexSwapBeingExecuted = true;
const amount = global.currentSwap.inputSource === SwapInputSource.In
? { amountOut: undefined }
: { amountIn: undefined };
@@ -681,6 +692,7 @@ addActionHandler('estimateSwapCex', async (global, actions, { shouldBlock }) =>
...resetParams,
});
setGlobal(global);
+ isEstimateCexSwapBeingExecuted = false;
return;
}
@@ -716,6 +728,7 @@ addActionHandler('estimateSwapCex', async (global, actions, { shouldBlock }) =>
});
global = getGlobal();
+ isEstimateCexSwapBeingExecuted = false;
if (!estimate || 'errors' in estimate) {
global = updateCurrentSwap(global, {
diff --git a/src/i18n/ru.yaml b/src/i18n/ru.yaml
index c93fe634..e27fbfe8 100644
--- a/src/i18n/ru.yaml
+++ b/src/i18n/ru.yaml
@@ -190,7 +190,7 @@ View Address on %ton_explorer_name%: Открыть в %ton_explorer_name%
No suggestions, you're on your own!: Ничего не найдено :(
Play Sounds: Включить звуки
Focus on asset value rather than current balance: Фокус на стоимости активов вместо текущего баланса
-$tiny_transfers_help: Отключите эту опцию, чтобы показывать переводы стоимостью менее $%value%. Имейте в виду, что такие переводы часто используются для спама и мошенничества.
+$tiny_transfers_help: Скрыть переводы стоимостью менее $%value%. Имейте в виду — такие суммы часто отправляют спамеры и мошенники.
Today: Сегодня
Yesterday: Вчера
Now: Сейчас
@@ -279,9 +279,9 @@ $dapps-description: Вы можете подключиться к децентр
Logged in with MyTonWallet: Подключены с помощью MyTonWallet
No active connections: Нет активных подключений
Other: Другие
-Assets & Activity: Активы и Лента
+Assets & Activity: Активы и лента
Token Settings: Настройки токенов
-My Tokens: Мои Токены
+My Tokens: Мои токены
Add Token: Добавить токен
Delete Token: Удалить токен
Are you sure you want to delete?: Вы уверены, что хотите удалить %token%?
@@ -528,10 +528,7 @@ Required: Обязательно
Comment is too long.: Комментарий слишком длинный.
Memo: Memo
Memo was copied!: Memo был скопирован!
-$hide_tokens_no_cost_help: |
- Выключите эту опцию, чтобы отображать в вашей учётной записи токены со стоимостью менее $%value%.
- Пожалуйста, имейте в виду, что такие жетоны часто используются для спама и мошенничества.
- Вы также можете выборочно включить и отключить конкретные токены, используя список ниже на этом экране.
+$hide_tokens_no_cost_help: Скрыть токены стоимостью менее $%value%. Имейте в виду — такие токены часто отправляют спамеры и мошенники. На этом же экране ниже вы можете скрыть отдельные токены.
No internet connection. Please check your connection and try again.: Отсутствует подключение к интернету. Пожалуйста, проверьте своё соединение и попробуйте снова.
To use this feature, first enable Face ID in your phone settings.: Чтобы использовать эту функцию, сначала активируйте идентификацию лица в настройках телефона.
To use this feature, first enable biometrics in your phone settings.: Чтобы использовать эту функцию, сначала активируйте биометрию в настройках телефона.
@@ -608,7 +605,7 @@ until %date%: до %date%
"%volume% in %amount% parts": "%volume% в %amount% частях"
Unfreeze: Разморозить
Claim: Заклеймить
-Confirm Unfreezing: Подтверждение размораживания
+Confirm Unfreezing: Подтвердите размораживание
Insufficient Balance for Fee.: Недостаточный баланс для комиссии.
$fee_value_bold: "**Комиссия:** %fee%"
MyTonWallet Features: Функции MyTonWallet
diff --git a/src/util/fetch.ts b/src/util/fetch.ts
index abd3fd34..e0e9b5d5 100644
--- a/src/util/fetch.ts
+++ b/src/util/fetch.ts
@@ -5,7 +5,7 @@ import { pause } from './schedulers';
type QueryParams = Record;
-const DEFAULT_TIMEOUTS = [15000, 30000]; // 15, 15, 30 sec
+const MAX_TIMEOUT = 30000; // 30 sec
export async function fetchJson(url: string | URL, data?: QueryParams, init?: RequestInit) {
const urlObject = new URL(url);
@@ -37,7 +37,7 @@ export async function fetchWithRetry(url: string | URL, init?: RequestInit, opti
}) {
const {
retries = DEFAULT_RETRIES,
- timeouts = DEFAULT_TIMEOUTS,
+ timeouts = DEFAULT_TIMEOUT,
shouldSkipRetryFn = isNotTemporaryError,
} = options ?? {};
@@ -52,7 +52,7 @@ export async function fetchWithRetry(url: string | URL, init?: RequestInit, opti
const timeout = Array.isArray(timeouts)
? timeouts[i - 1] ?? timeouts[timeouts.length - 1]
- : timeouts;
+ : Math.min(timeouts * i, MAX_TIMEOUT);
const response = await fetchWithTimeout(url, init, timeout);
statusCode = response.status;
diff --git a/src/util/ledger/index.ts b/src/util/ledger/index.ts
index f202c3dc..0752d7ed 100644
--- a/src/util/ledger/index.ts
+++ b/src/util/ledger/index.ts
@@ -23,6 +23,7 @@ import type { LedgerWalletInfo } from './types';
import { ApiTransactionError } from '../../api/types';
import {
+ BURN_ADDRESS,
NOTCOIN_EXCHANGERS, NOTCOIN_VOUCHERS_ADDRESS, ONE_TON, TONCOIN_SLUG,
} from '../../config';
import { callApi } from '../../api';
@@ -38,7 +39,11 @@ import {
WALLET_IS_BOUNCEABLE,
WORKCHAIN,
} from '../../api/blockchains/ton/constants';
-import { toBase64Address } from '../../api/blockchains/ton/util/tonCore';
+import {
+ commentToBytes,
+ packBytesAsSnakeCell,
+ toBase64Address,
+} from '../../api/blockchains/ton/util/tonCore';
import {
ApiHardwareBlindSigningNotEnabled,
ApiUnsupportedVersionError,
@@ -49,6 +54,7 @@ import { parseAccountId } from '../account';
import compareVersions from '../compareVersions';
import { logDebugError } from '../logs';
import { pause } from '../schedulers';
+import { isValidLedgerComment } from './utils';
type TransactionParams = {
to: Address;
@@ -274,8 +280,10 @@ export async function submitLedgerTransfer(
));
isBounceable = true;
} else if (comment) {
- if (isUnsafeSupported) {
+ if (isValidLedgerComment(comment)) {
payload = { type: 'comment', text: comment };
+ } else if (isUnsafeSupported) {
+ payload = { type: 'unsafe', message: buildCommentPayload(comment) };
} else {
return {
error: ApiTransactionError.NotSupportedHardwareOperation,
@@ -355,12 +363,15 @@ export async function submitLedgerNftTransfer(options: {
const { seqno } = walletInfo!;
- const isNotcoinBurn = nft?.collectionAddress === NOTCOIN_VOUCHERS_ADDRESS;
+ const isNotcoinBurn = nft?.collectionAddress === NOTCOIN_VOUCHERS_ADDRESS
+ && (toAddress === BURN_ADDRESS || NOTCOIN_EXCHANGERS.includes(toAddress as any));
// eslint-disable-next-line no-null/no-null
let forwardPayload: Cell | null = null;
+ let forwardAmount = NFT_TRANSFER_TONCOIN_FORWARD_AMOUNT;
if (isNotcoinBurn) {
({ forwardPayload, toAddress } = buildNotcoinVoucherExchange(nftAddress, nft!.index));
+ forwardAmount = 50000000n;
} else if (comment) {
forwardPayload = buildCommentPayload(comment);
}
@@ -380,7 +391,7 @@ export async function submitLedgerNftTransfer(options: {
responseDestination: Address.parse(fromAddress!),
// eslint-disable-next-line no-null/no-null
customPayload: null,
- forwardAmount: NFT_TRANSFER_TONCOIN_FORWARD_AMOUNT,
+ forwardAmount,
forwardPayload,
},
});
@@ -458,10 +469,8 @@ export async function buildLedgerTokenTransfer(
}
function buildCommentPayload(comment: string) {
- return new Builder()
- .storeUint(0, 32)
- .storeStringTail(comment)
- .endCell();
+ const bytes = commentToBytes(comment);
+ return packBytesAsSnakeCell(bytes);
}
export async function signLedgerTransactions(accountId: string, messages: ApiDappTransfer[], options?: {
diff --git a/src/util/ledger/utils.ts b/src/util/ledger/utils.ts
new file mode 100644
index 00000000..29083237
--- /dev/null
+++ b/src/util/ledger/utils.ts
@@ -0,0 +1,11 @@
+import { isAscii } from '../stringFormat';
+
+const MAX_COMMENT_SIZE = 120;
+
+export function isValidLedgerComment(comment: string) {
+ return isAscii(comment) && isLedgerCommentLengthValid(comment);
+}
+
+export function isLedgerCommentLengthValid(comment: string) {
+ return comment.length <= MAX_COMMENT_SIZE;
+}