UNPKG

@tetherto/wdk-wallet-ton-gasless

Version:

A simple package to manage BIP-32 wallets for the TON blockchain, which implement the gasless functionality

228 lines (190 loc) 7.79 kB
// Copyright 2024 Tether Operations Limited // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. 'use strict' import { WalletAccountReadOnly } from '@tetherto/wdk-wallet' import { WalletAccountReadOnlyTon } from '@tetherto/wdk-wallet-ton' import { Address, beginCell, internal, toNano, storeMessage } from '@ton/ton' import { TonApiClient } from '@ton-api/client' /** @typedef {import('@ton/ton').MessageRelaxed} MessageRelaxed */ /** @typedef {import('@ton/ton').TonClient} TonClient */ /** @typedef {import('@ton-api/client').SignRawParams} SignRawParams */ /** @typedef {import('@tetherto/wdk-wallet-ton').TonTransaction} TonTransaction */ /** @typedef {import('@tetherto/wdk-wallet-ton').TransactionResult} TransactionResult */ /** @typedef {import('@tetherto/wdk-wallet-ton').TransferOptions} TransferOptions */ /** @typedef {import('@tetherto/wdk-wallet-ton').TransferResult} TransferResult */ /** @typedef {import('@tetherto/wdk-wallet-ton').TonTransactionReceipt} TonTransactionReceipt */ /** * @typedef {Object} TonClientConfig * @property {string} url - The url of the ton center api. * @property {string} [secretKey] - If set, uses the api-key to authenticate on the ton center api. */ /** * @typedef {Object} TonApiClientConfig * @property {string} url - The url of the ton api. * @property {string} [secretKey] - If set, uses the api-key to authenticate on the ton api. */ /** * @typedef {Object} TonGaslessWalletConfig * @property {TonClientConfig | TonClient} tonClient - The ton client configuration, or an instance of the {@link TonClient} class. * @property {TonApiClientConfig | TonApiClient} tonApiClient - The ton api client configuration, or an instance of the {@link TonApiClient} class. * @property {Object} paymasterToken - The paymaster token configuration. * @property {string} paymasterToken.address - The address of the paymaster token. * @property {number | bigint} [transferMaxFee] - The maximum fee amount for transfer operations. */ const DUMMY_MESSAGE_VALUE = toNano(0.05) export default class WalletAccountReadOnlyTonGasless extends WalletAccountReadOnly { /** * Creates a new read-only ton gasless wallet account. * * @param {string | Uint8Array} publicKey - The account's public key. * @param {Omit<TonGaslessWalletConfig, 'transferMaxFee'>} config - The configuration object. */ constructor (publicKey, config) { const tonReadOnlyAccount = new WalletAccountReadOnlyTon(publicKey, config) super(tonReadOnlyAccount._address) /** * The read-only ton gasless wallet account configuration. * * @protected * @type {Omit<TonGaslessWalletConfig, 'transferMaxFee'>} */ this._config = config if (config.tonApiClient) { const { tonApiClient } = config /** * The ton api client. * * @protected * @type {TonApiClient | undefined} */ this._tonApiClient = tonApiClient instanceof TonApiClient ? tonApiClient : new TonApiClient({ baseUrl: tonApiClient.url, apiKey: tonApiClient.secretKey }) } /** @private */ this._tonReadOnlyAccount = tonReadOnlyAccount } /** * Returns the account's ton balance. * * @returns {Promise<bigint>} The ton balance (in nanotons). */ async getBalance () { return await this._tonReadOnlyAccount.getBalance() } /** * Returns the account balance for a specific token. * * @param {string} tokenAddress - The smart contract address of the token. * @returns {Promise<bigint>} The token balance (in base unit). */ async getTokenBalance (tokenAddress) { return await this._tonReadOnlyAccount.getTokenBalance(tokenAddress) } /** * Returns the account's balance for the paymaster token provided in the wallet account configuration. * * @returns {Promise<bigint>} The paymaster token balance (in base unit). */ async getPaymasterTokenBalance () { const { paymasterToken } = this._config return await this.getTokenBalance(paymasterToken.address) } /** * Quotes the costs of a send transaction operation. * * @param {TonTransaction} tx - The transaction. * @returns {Promise<Omit<TransactionResult, 'hash'>>} The transaction's quotes. */ async quoteSendTransaction (tx) { throw new Error("Method 'quoteSendTransaction(tx)' not supported on ton gasless.") } /** * Quotes the costs of a transfer operation. * * @param {TransferOptions} options - The transfer's options. * @param {Pick<TonGaslessWalletConfig, 'paymasterToken'>} [config] - If set, overrides the 'paymasterToken' options defined in the wallet account configuration. * @returns {Promise<Omit<TransferResult, 'hash'>>} The transfer's quotes. */ async quoteTransfer (options, config) { const message = await this._getGaslessTokenTransferMessage(options) const { commission } = await this._getGaslessTokenTransferRawParams(message, config ?? this._config) return { fee: commission } } /** * Returns a transaction's receipt. * * @param {string} hash - The transaction's hash. * @returns {Promise<TonTransactionReceipt | null>} - The receipt, or null if the transaction has not been included in a block yet. */ async getTransactionReceipt (hash) { return await this._tonReadOnlyAccount.getTransactionReceipt(hash) } /** * Creates and returns an internal message to execute the given token transfer. * * @protected * @param {TransferOptions} options - The transfer's options. * @returns {Promise<MessageRelaxed>} The internal message. */ async _getGaslessTokenTransferMessage ({ token, recipient, amount }) { recipient = Address.parse(recipient) const { relayAddress } = await this._tonApiClient.gasless.gaslessConfig() const jettonWalletAddress = await this._tonReadOnlyAccount._getJettonWalletAddress(token) const queryId = this._tonReadOnlyAccount._generateQueryId() const body = beginCell() .storeUint(0xf8a7ea5, 32) .storeUint(queryId, 64) .storeCoins(amount) .storeAddress(recipient) .storeAddress(relayAddress) .storeBit(false) .storeCoins(1n) .storeMaybeRef(null) .endCell() const message = internal({ to: jettonWalletAddress, value: DUMMY_MESSAGE_VALUE, bounce: true, body }) return message } /** * Creates and returns the ton api's raw parameters to execute the given message. * * @protected * @param {MessageRelaxed} message - The message. * @param {Pick<TonGaslessWalletConfig, 'paymasterToken'>} config - The configuration object. * @returns {Promise<SignRawParams>} The ton api's raw parameters. */ async _getGaslessTokenTransferRawParams (message, { paymasterToken }) { const wallet = this._tonReadOnlyAccount._wallet const rawParams = await this._tonApiClient.gasless.gaslessEstimate( Address.parse(paymasterToken.address), { walletAddress: wallet.address, walletPublicKey: Buffer.from(wallet.publicKey).toString('hex'), messages: [{ boc: beginCell() .storeWritable( storeMessage(message) ) .endCell() }] } ) return rawParams } }