Skip to content

Commit

Permalink
v3.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mytonwalletorg committed Jan 30, 2025
1 parent 7516274 commit 6321fc1
Show file tree
Hide file tree
Showing 147 changed files with 4,286 additions and 2,308 deletions.
1 change: 1 addition & 0 deletions changelogs/3.2.12.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bug fixes and performance improvements
9 changes: 9 additions & 0 deletions changelogs/3.3.0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
What’s New in v3.3?

• Refined fees for full transparency on every transaction.

• Explore 2.0: a dedicated tab with brand-new design, categories, and new apps.

• Handy navigation with a new bottom tab bar for easy access to essential features.

• Telegram Gifts in stunning HD quality at the highest frame rate.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mytonwallet",
"version": "3.2.11",
"version": "3.3.0",
"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": {
Expand Down
2 changes: 1 addition & 1 deletion public/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.2.11
3.3.0
7 changes: 4 additions & 3 deletions src/api/chains/ton/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ export const TON_BIP39_PATH = "m/44'/607'/0'";
export const ONE_TON = 1_000_000_000n;
export const TOKEN_TRANSFER_AMOUNT = 50000000n; // 0.05 TON
export const TINY_TOKEN_TRANSFER_AMOUNT = 18000000n; // 0.018 TON
export const TOKEN_REAL_TRANSFER_AMOUNT = 32100000n; // 0.0321 TON
export const TINY_TOKEN_REAL_TRANSFER_AMOUNT = 8000000n; // 0.008 TON
export const TINIEST_TOKEN_REAL_TRANSFER_AMOUNT = 3000000n; // 0.003 TON
export const TOKEN_TRANSFER_REAL_AMOUNT = 32100000n; // 0.0321 TON
export const TINY_TOKEN_TRANSFER_REAL_AMOUNT = 8000000n; // 0.008 TON
export const TINIEST_TOKEN_TRANSFER_REAL_AMOUNT = 3000000n; // 0.003 TON
export const TOKEN_TRANSFER_FORWARD_AMOUNT = 1n; // 0.000000001 TON
export const CLAIM_MINTLESS_AMOUNT = 20000000n; // 0.02 TON

export const NFT_TRANSFER_AMOUNT = 100000000n; // 0.1 TON
export const NFT_TRANSFER_REAL_AMOUNT = 5000000n; // 0.005 TON
export const NFT_TRANSFER_FORWARD_AMOUNT = 1n; // 0.000000001 TON
/**
* When the NFT contract handles the payload we send, it simply adds its data to the payload. If the resulting payload
Expand Down
19 changes: 19 additions & 0 deletions src/api/chains/ton/nfts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { calculateNftTransferFee } from './nfts';

describe('calculateNftTransferFee', () => {
it('calculates for 1 NFT', () => {
expect(calculateNftTransferFee(1, 1, 2939195n, 10000000n)).toBe(12939195n);
});

it('calculates for batch', () => {
expect(calculateNftTransferFee(3, 3, 6001837n, 100000000n)).toBe(306001837n);
});

it('calculates for multiple complete and 1 incomplete batch', () => {
expect(calculateNftTransferFee(9, 4, 7533158n, 1000000000n)).toBe(9018832895n);
});

it('calculates for multiple complete batchs', () => {
expect(calculateNftTransferFee(12, 4, 7533158n, 10000000000n)).toBe(120022599474n);
});
});
110 changes: 69 additions & 41 deletions src/api/chains/ton/nfts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
NOTCOIN_VOUCHERS_ADDRESS,
} from '../../../config';
import { parseAccountId } from '../../../util/account';
import { bigintMultiplyToNumber } from '../../../util/bigint';
import { compact } from '../../../util/iteratees';
import { generateQueryId } from './util';
import { buildNft } from './util/metadata';
Expand All @@ -25,6 +26,7 @@ import {
NFT_PAYLOAD_SAFE_MARGIN,
NFT_TRANSFER_AMOUNT,
NFT_TRANSFER_FORWARD_AMOUNT,
NFT_TRANSFER_REAL_AMOUNT,
NftOpCode,
} from './constants';
import { checkMultiTransactionDraft, checkToAddress, submitMultiTransfer } from './transactions';
Expand Down Expand Up @@ -120,75 +122,69 @@ export async function getNftUpdates(accountId: string, fromSec: number) {

export async function checkNftTransferDraft(options: {
accountId: string;
nftAddresses: string[];
nfts: ApiNft[];
toAddress: string;
comment?: string;
}): Promise<ApiCheckTransactionDraftResult> {
const { accountId, nftAddresses, comment } = options;
const { accountId, nfts, comment } = options;
let { toAddress } = options;

const { network } = parseAccountId(accountId);
const { address: fromAddress } = await fetchStoredTonWallet(accountId);

const checkAddressResult = await checkToAddress(network, toAddress);

if ('error' in checkAddressResult) {
return checkAddressResult;
const result: ApiCheckTransactionDraftResult = await checkToAddress(network, toAddress);
if ('error' in result) {
return result;
}

toAddress = checkAddressResult.resolvedAddress!;
toAddress = result.resolvedAddress!;

// We only need to check the first batch of a multi-transaction
const messages = nftAddresses.slice(0, NFT_BATCH_SIZE).map((nftAddress) => {
return {
payload: buildNftTransferPayload(fromAddress, toAddress, comment),
amount: NFT_TRANSFER_AMOUNT,
toAddress: nftAddress,
};
});
const messages = nfts
.slice(0, NFT_BATCH_SIZE) // We only need to check the first batch of a multi-transaction
.map((nft) => buildNftTransferMessage(nft, fromAddress, toAddress, comment));

const result = await checkMultiTransactionDraft(accountId, messages);
const transactionResult = await checkMultiTransactionDraft(accountId, messages);

if ('error' in result) {
return result;
if (transactionResult.fee !== undefined) {
const batchFee = transactionResult.fee;
result.fee = calculateNftTransferFee(nfts.length, messages.length, batchFee, NFT_TRANSFER_AMOUNT);
result.realFee = calculateNftTransferFee(nfts.length, messages.length, batchFee, NFT_TRANSFER_REAL_AMOUNT);
}

return {
...result,
...checkAddressResult,
};
if ('error' in transactionResult) {
result.error = transactionResult.error;
}

return result;
}

export async function submitNftTransfers(options: {
accountId: string;
password: string;
nftAddresses: string[];
nfts: ApiNft[];
toAddress: string;
comment?: string;
nfts?: ApiNft[];
}) {
const {
accountId, password, nftAddresses, toAddress, comment, nfts,
accountId, password, nfts, toAddress, comment,
} = options;

const { address: fromAddress } = await fetchStoredTonWallet(accountId);
const messages = nfts.map((nft) => buildNftTransferMessage(nft, fromAddress, toAddress, comment));
return submitMultiTransfer({ accountId, password, messages });
}

const messages = nftAddresses.map((nftAddress, index) => {
const nft = nfts?.[index];
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);

return {
payload,
amount: NFT_TRANSFER_AMOUNT,
toAddress: nftAddress,
};
});
function buildNftTransferMessage(nft: ApiNft, fromAddress: string, toAddress: string, comment?: string) {
const isNotcoinBurn = nft.collectionAddress === NOTCOIN_VOUCHERS_ADDRESS
&& (toAddress === BURN_ADDRESS || NOTCOIN_EXCHANGERS.includes(toAddress as any));
const payload = isNotcoinBurn
? buildNotcoinVoucherExchange(fromAddress, nft.address, nft.index)
: buildNftTransferPayload(fromAddress, toAddress, comment);

return submitMultiTransfer({ accountId, password, messages });
return {
payload,
amount: NFT_TRANSFER_AMOUNT,
toAddress: nft.address,
};
}

function buildNotcoinVoucherExchange(fromAddress: string, nftAddress: string, nftIndex: number) {
Expand Down Expand Up @@ -239,3 +235,35 @@ function buildNftTransferPayload(

return builder.endCell();
}

export function calculateNftTransferFee(
totalNftCount: number,
// How many NFTs were added to the multi-transaction before estimating it
estimatedBatchSize: number,
// The blockchain fee of the estimated multi-transaction
estimatedBatchBlockchainFee: bigint,
// How much TON is attached to each NFT during the transfer
amountPerNft: bigint,
) {
const fullBatchCount = Math.floor(totalNftCount / estimatedBatchSize);
let remainingBatchSize = totalNftCount % estimatedBatchSize;

// The blockchain fee for the first NFT in a batch is almost twice higher than the fee for the other NFTs. Therefore,
// simply using the average NFT fee to calculate the last incomplete batch fee gives an insufficient number. To fix
// that, we increase the last batch size.
//
// A real life example:
// 1 NFT in the batch: 0.002939195 TON
// 2 NFTs in the batch: 0.004470516 TON
// 3 NFTs in the batch: 0.006001837 TON
// 4 NFTs in the batch: 0.007533158 TON
if (remainingBatchSize > 0 && remainingBatchSize < estimatedBatchSize) {
remainingBatchSize += 1;
}

const totalBlockchainFee = bigintMultiplyToNumber(
estimatedBatchBlockchainFee,
(fullBatchCount * estimatedBatchSize + remainingBatchSize) / estimatedBatchSize,
);
return totalBlockchainFee + BigInt(totalNftCount) * amountPerNft;
}
12 changes: 6 additions & 6 deletions src/api/chains/ton/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ import { buildTokenSlug, getTokenByAddress } from '../../common/tokens';
import {
CLAIM_MINTLESS_AMOUNT,
DEFAULT_DECIMALS,
TINIEST_TOKEN_REAL_TRANSFER_AMOUNT,
TINY_TOKEN_REAL_TRANSFER_AMOUNT,
TINIEST_TOKEN_TRANSFER_REAL_AMOUNT,
TINY_TOKEN_TRANSFER_AMOUNT,
TOKEN_REAL_TRANSFER_AMOUNT,
TINY_TOKEN_TRANSFER_REAL_AMOUNT,
TOKEN_TRANSFER_AMOUNT,
TOKEN_TRANSFER_FORWARD_AMOUNT,
TOKEN_TRANSFER_REAL_AMOUNT,
} from './constants';
import { isActiveSmartContract } from './wallet';

Expand Down Expand Up @@ -389,13 +389,13 @@ export function getToncoinAmountForTransfer(token: ApiToken, willClaimMintless:
amount += TINY_TOKEN_TRANSFER_AMOUNT;

if (token.slug === TON_USDT_SLUG) {
realAmount += TINIEST_TOKEN_REAL_TRANSFER_AMOUNT;
realAmount += TINIEST_TOKEN_TRANSFER_REAL_AMOUNT;
} else {
realAmount += TINY_TOKEN_REAL_TRANSFER_AMOUNT;
realAmount += TINY_TOKEN_TRANSFER_REAL_AMOUNT;
}
} else {
amount += TOKEN_TRANSFER_AMOUNT;
realAmount += TOKEN_REAL_TRANSFER_AMOUNT;
realAmount += TOKEN_TRANSFER_REAL_AMOUNT;
}

if (willClaimMintless) {
Expand Down
52 changes: 21 additions & 31 deletions src/api/chains/ton/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import type {
TonTransferParams,
} from './types';
import type { TonWallet } from './util/tonCore';
import { ApiCommonError, ApiTransactionDraftError, ApiTransactionError } from '../../types';
import { ApiTransactionDraftError, ApiTransactionError } from '../../types';

import { DEFAULT_FEE, DIESEL_ADDRESS, TONCOIN } from '../../../config';
import { parseAccountId } from '../../../util/account';
Expand Down Expand Up @@ -126,6 +126,7 @@ export async function checkTransactionDraft(
isBase64Data,
stateInit: stateInitString,
forwardAmount,
allowGasless,
} = options;
let { toAddress, data } = options;

Expand All @@ -135,7 +136,6 @@ export async function checkTransactionDraft(

try {
result = await checkToAddress(network, toAddress);

if ('error' in result) {
return result;
}
Expand Down Expand Up @@ -259,23 +259,26 @@ export async function checkTransactionDraft(
realFee += safeBlockchainFee;
result.fee = fee;
result.realFee = realFee;
result.diesel = DIESEL_NOT_AVAILABLE;

let isEnoughBalance: boolean;

if (!tokenAddress) {
result.diesel = DIESEL_NOT_AVAILABLE;
isEnoughBalance = isFullTonTransfer
? toncoinBalance > blockchainFee
: toncoinBalance >= fee + amount;
} else {
const canTransferGasfully = toncoinBalance >= fee;
result.diesel = await getDiesel({
accountId,
tokenAddress,
canTransferGasfully,
toncoinBalance,
tokenBalance: balance,
});

if (allowGasless) {
result.diesel = await getDiesel({
accountId,
tokenAddress,
canTransferGasfully,
toncoinBalance,
tokenBalance: balance,
});
}

if (isDieselAvailable(result.diesel)) {
isEnoughBalance = amount + getDieselTokenAmount(result.diesel) <= balance;
Expand Down Expand Up @@ -865,16 +868,11 @@ export async function checkMultiTransactionDraft(
accountId: string,
messages: TonTransferParams[],
withDiesel = false,
) {
const { network } = parseAccountId(accountId);

const result: {
fee?: bigint;
totalAmount?: bigint;
} = {};

): Promise<{ fee?: bigint } & ({ error: ApiAnyDisplayError } | {})> {
const result: { fee?: bigint } = {};
let totalAmount: bigint = 0n;

const { network } = parseAccountId(accountId);
const { isInitialized, version } = await fetchStoredTonWallet(accountId);

try {
Expand All @@ -893,28 +891,20 @@ export async function checkMultiTransactionDraft(
}

const wallet = await getTonWallet(accountId);

if (!wallet) {
return { ...result, error: ApiCommonError.Unexpected };
}

const { balance } = await getWalletInfo(network, wallet);

const { transaction } = await signMultiTransaction({
network, wallet, messages, version,
});
const blockchainFee = await calculateFee(network, wallet, transaction, isInitialized);
result.fee = bigintMultiplyToNumber(blockchainFee, FEE_FACTOR);

const realFee = await calculateFee(network, wallet, transaction, isInitialized);

// TODO Should be `0` for `withDiesel`?
result.totalAmount = totalAmount;
result.fee = bigintMultiplyToNumber(realFee, FEE_FACTOR);

if (!withDiesel && balance < totalAmount + realFee) {
// TODO Should `totalAmount` be `0` for `withDiesel`?
if (!withDiesel && balance < totalAmount + result.fee) {
return { ...result, error: ApiTransactionDraftError.InsufficientBalance };
}

return result as { fee: bigint; totalAmount: bigint };
return result;
} catch (err: any) {
return handleServerError(err);
}
Expand Down
Loading

0 comments on commit 6321fc1

Please sign in to comment.