From e99aeca6dfcac563ae42792926507867ac6ff68f Mon Sep 17 00:00:00 2001 From: Nikita Kuznetsov Date: Thu, 16 Jan 2025 10:41:24 +0100 Subject: [PATCH] Support Extra Currency via Ton Connect --- apps/extension/src/provider/tonconnect.ts | 3 +- packages/core/src/entries/tonConnect.ts | 2 + .../ton-blockchain/encoder/encoder-base.ts | 70 +++++++++++++++++++ .../encoder/extra-currency-encoder.ts | 50 ++----------- .../encoder/ton-connect-encoder.ts | 14 ++-- .../src/service/tonConnect/connectService.ts | 3 +- 6 files changed, 92 insertions(+), 50 deletions(-) create mode 100644 packages/core/src/service/ton-blockchain/encoder/encoder-base.ts diff --git a/apps/extension/src/provider/tonconnect.ts b/apps/extension/src/provider/tonconnect.ts index d9a382ac9..710c70d68 100644 --- a/apps/extension/src/provider/tonconnect.ts +++ b/apps/extension/src/provider/tonconnect.ts @@ -52,7 +52,8 @@ export const getDeviceInfo = (): DeviceInfo => { 'SendTransaction', { name: 'SendTransaction', - maxMessages: 4 + maxMessages: 4, + extraCurrenciesSupported: true } ] }; diff --git a/packages/core/src/entries/tonConnect.ts b/packages/core/src/entries/tonConnect.ts index e024290dc..6455a9f9d 100644 --- a/packages/core/src/entries/tonConnect.ts +++ b/packages/core/src/entries/tonConnect.ts @@ -26,6 +26,7 @@ export interface TonConnectTransactionPayloadMessage { amount: string | number; payload?: string; // base64 cell stateInit?: string; // base64 cell + extra_currencies?: [{ id: number; value: string }]; } export type TonConnectAccount = { @@ -169,6 +170,7 @@ export enum SEND_TRANSACTION_ERROR_CODES { export type SendTransactionFeature = { name: 'SendTransaction'; maxMessages: number; + extraCurrenciesSupported?: boolean; }; export type SendTransactionFeatureDeprecated = 'SendTransaction'; diff --git a/packages/core/src/service/ton-blockchain/encoder/encoder-base.ts b/packages/core/src/service/ton-blockchain/encoder/encoder-base.ts new file mode 100644 index 000000000..a3a827b71 --- /dev/null +++ b/packages/core/src/service/ton-blockchain/encoder/encoder-base.ts @@ -0,0 +1,70 @@ +import { Address } from '@ton/core/dist/address/Address'; +import { Cell } from '@ton/core/dist/boc/Cell'; +import { Dictionary } from '@ton/core/dist/dict/Dictionary'; +import type { CurrencyCollection } from '@ton/core/dist/types/CurrencyCollection'; +import type { MessageRelaxed } from '@ton/core/dist/types/MessageRelaxed'; +import type { StateInit } from '@ton/core/dist/types/StateInit'; +import BigNumber from 'bignumber.js'; + +export abstract class EncoderBase { + private getOtherDict = () => { + return Dictionary.empty(Dictionary.Keys.Uint(32), Dictionary.Values.BigVarUint(5)); + }; + + protected currencyValue(src: { + amount: string | number; + extraCurrencies: + | { + id: number; + value: string; + }[] + | undefined; + }): CurrencyCollection { + const coins = BigInt(src.amount); + + if (!src.extraCurrencies) { + return { coins }; + } + + const other = this.getOtherDict(); + + for (let extra of src.extraCurrencies) { + other.set(extra.id, BigInt(extra.value)); + } + + return { coins, other }; + } + + protected extraCurrencyValue(src: { id: number; weiAmount: BigNumber }): CurrencyCollection { + const other = this.getOtherDict(); + + other.set(src.id, BigInt(src.weiAmount.toFixed(0))); + + return { coins: BigInt('0'), other }; + } + + protected internalMessage(src: { + to: Address; + value: CurrencyCollection; + bounce: boolean; + init?: StateInit; + body?: Cell; + }): MessageRelaxed { + return { + info: { + type: 'internal', + dest: src.to, + value: src.value, + bounce: src.bounce, + ihrDisabled: true, + bounced: false, + ihrFee: 0n, + forwardFee: 0n, + createdAt: 0, + createdLt: 0n + }, + init: src.init ?? undefined, + body: src.body ?? Cell.EMPTY + }; + } +} diff --git a/packages/core/src/service/ton-blockchain/encoder/extra-currency-encoder.ts b/packages/core/src/service/ton-blockchain/encoder/extra-currency-encoder.ts index 2c33d2ecb..83337242e 100644 --- a/packages/core/src/service/ton-blockchain/encoder/extra-currency-encoder.ts +++ b/packages/core/src/service/ton-blockchain/encoder/extra-currency-encoder.ts @@ -1,19 +1,14 @@ -import { - Address, - Cell, - CurrencyCollection, - Dictionary, - MessageRelaxed, - SendMode, - StateInit -} from '@ton/core'; +import { Address, SendMode } from '@ton/core'; import { userInputAddressIsBounceable } from '../utils'; import BigNumber from 'bignumber.js'; import { APIConfig } from '../../../entries/apis'; import { MessagePayloadParam, serializePayload, WalletOutgoingMessage } from './types'; +import { EncoderBase } from './encoder-base'; -export class ExtraCurrencyEncoder { - constructor(private readonly api: APIConfig, private readonly _walletAddress: string) {} +export class ExtraCurrencyEncoder extends EncoderBase { + constructor(private readonly api: APIConfig, private readonly _walletAddress: string) { + super(); + } encodeTransfer = async ( transfer: @@ -38,39 +33,6 @@ export class ExtraCurrencyEncoder { } }; - private extraCurrencyValue(src: { id: number; weiAmount: BigNumber }): CurrencyCollection { - const other = Dictionary.empty(Dictionary.Keys.Uint(32), Dictionary.Values.BigVarUint(5)); - - other.set(src.id, BigInt(src.weiAmount.toFixed(0))); - - return { coins: BigInt('0'), other }; - } - - private internalMessage(src: { - to: Address; - value: CurrencyCollection; - bounce: boolean; - init?: StateInit; - body?: Cell; - }): MessageRelaxed { - return { - info: { - type: 'internal', - dest: src.to, - value: src.value, - bounce: src.bounce, - ihrDisabled: true, - bounced: false, - ihrFee: 0n, - forwardFee: 0n, - createdAt: 0, - createdLt: 0n - }, - init: src.init ?? undefined, - body: src.body ?? Cell.EMPTY - }; - } - private encodeSingleTransfer = async ({ id, to, diff --git a/packages/core/src/service/ton-blockchain/encoder/ton-connect-encoder.ts b/packages/core/src/service/ton-blockchain/encoder/ton-connect-encoder.ts index a06badf99..cc6a68ebe 100644 --- a/packages/core/src/service/ton-blockchain/encoder/ton-connect-encoder.ts +++ b/packages/core/src/service/ton-blockchain/encoder/ton-connect-encoder.ts @@ -6,9 +6,12 @@ import { TON_CONNECT_MSG_VARIANTS_ID, TonConnectTransactionPayload } from '../../../entries/tonConnect'; +import { EncoderBase } from './encoder-base'; -export class TonConnectEncoder { - constructor(private readonly api: APIConfig, private readonly walletAddress: string) {} +export class TonConnectEncoder extends EncoderBase { + constructor(private readonly api: APIConfig, private readonly walletAddress: string) { + super(); + } encodeTransfer = async ( transfer: TonConnectTransactionPayload & { @@ -30,10 +33,13 @@ export class TonConnectEncoder { sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS, messages: await Promise.all( messages.map(async item => - internal({ + this.internalMessage({ to: Address.parse(item.address), bounce: await tonConnectAddressIsBounceable(this.api, item.address), - value: BigInt(item.amount), + value: this.currencyValue({ + amount: item.amount, + extraCurrencies: item.extra_currencies + }), init: toStateInit(item.stateInit), body: item.payload ? Cell.fromBase64(item.payload) : undefined }) diff --git a/packages/core/src/service/tonConnect/connectService.ts b/packages/core/src/service/tonConnect/connectService.ts index 761e91a63..dec79ae3f 100644 --- a/packages/core/src/service/tonConnect/connectService.ts +++ b/packages/core/src/service/tonConnect/connectService.ts @@ -161,7 +161,8 @@ export const getDeviceInfo = (appVersion: string, maxMessages: number): DeviceIn 'SendTransaction', { name: 'SendTransaction', - maxMessages: maxMessages + maxMessages: maxMessages, + extraCurrenciesSupported: true } ] };