@tetherto/wdk-wallet-ton-gasless
Version:
A simple package to manage BIP-32 wallets for the TON blockchain, which implement the gasless functionality
202 lines (170 loc) • 6.11 kB
JavaScript
// 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 { WalletAccountTon } from '@tetherto/wdk-wallet-ton'
import { beginCell, internal, SendMode, external, storeMessage } from '@ton/ton'
import WalletAccountReadOnlyTonGasless from './wallet-account-read-only-ton-gasless.js'
/** @typedef {import('@tetherto/wdk-wallet').IWalletAccount} IWalletAccount */
/** @typedef {import('@tetherto/wdk-wallet-ton').KeyPair} KeyPair */
/** @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('./wallet-account-read-only-ton-gasless.js').TonGaslessWalletConfig} TonGaslessWalletConfig */
/** @implements {IWalletAccount} */
export default class WalletAccountTonGasless extends WalletAccountReadOnlyTonGasless {
/**
* Creates a new ton gasless wallet account.
*
* @param {string | Uint8Array} seed - The wallet's [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) seed phrase.
* @param {string} path - The BIP-44 derivation path (e.g. "0'/0/0").
* @param {TonGaslessWalletConfig} config - The configuration object.
*/
constructor (seed, path, config) {
const tonAccount = new WalletAccountTon(seed, path, config)
super(tonAccount.keyPair.publicKey, config)
/**
* The ton gasless wallet account configuration.
*
* @protected
* @type {TonGaslessWalletConfig}
*/
this._config = config
/** @private */
this._tonAccount = tonAccount
}
/**
* The derivation path's index of this account.
*
* @type {number}
*/
get index () {
return this._tonAccount.index
}
/**
* The derivation path of this account (see [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)).
*
* @type {string}
*/
get path () {
return this._tonAccount.path
}
/**
* The account's key pair.
*
* @type {KeyPair}
*/
get keyPair () {
return this._tonAccount.keyPair
}
/**
* Signs a message.
*
* @param {string} message - The message to sign.
* @returns {Promise<string>} The message's signature.
*/
async sign (message) {
return await this._tonAccount.sign(message)
}
/**
* Verifies a message's signature.
*
* @param {string} message - The original message.
* @param {string} signature - The signature to verify.
* @returns {Promise<boolean>} True if the signature is valid.
*/
async verify (message, signature) {
return await this._tonAccount.verify(message, signature)
}
/**
* Sends a transaction.
*
* @param {TonTransaction} tx - The transaction.
* @returns {Promise<TransactionResult>} The transaction's result.
*/
async sendTransaction (tx) {
throw new Error("Method 'sendTransaction(tx)' not supported on ton gasless.")
}
/**
* Transfers a token to another address.
*
* @param {TransferOptions} options - The transfer's options.
* @param {Pick<TonGaslessWalletConfig, 'paymasterToken' | 'transferMaxFee'>} [config] - If set, overrides the 'paymasterToken' and 'transferMaxFee' options defined in the wallet account configuration.
* @returns {Promise<TransferResult>} The transfer's result.
*/
async transfer (options, config) {
const { paymasterToken, transferMaxFee } = config ?? this._config
const message = await this._getGaslessTokenTransferMessage(options)
const rawParams = await this._getGaslessTokenTransferRawParams(message, { paymasterToken })
const fee = rawParams.commission
if (transferMaxFee !== undefined && fee >= transferMaxFee) {
throw new Error('The transfer operation exceeds the transfer max fee.')
}
const hash = await this._sendGaslessTokenTransfer(rawParams)
return {
hash,
fee
}
}
/**
* Returns a read-only copy of the account.
*
* @returns {Promise<WalletAccountReadOnlyTonGasless>} The read-only account.
*/
async toReadOnlyAccount () {
const readOnlyAccount = new WalletAccountReadOnlyTonGasless(this.keyPair.publicKey, this._config)
return readOnlyAccount
}
/**
* Disposes the wallet account, erasing the private key from the memory.
*/
dispose () {
this._tonAccount.dispose()
}
/** @private */
async _sendGaslessTokenTransfer (rawParams) {
const contract = this._tonAccount._contract
const seqno = await contract.getSeqno()
const transfer = contract.createTransfer({
seqno,
authType: 'internal',
secretKey: this.keyPair.privateKey,
sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS,
timeout: Math.ceil(Date.now() / 1_000) + 2 * 60,
messages: rawParams.messages.map(message =>
internal({
to: message.address,
value: BigInt(message.amount),
body: message.payload
})
)
})
const boc = beginCell()
.storeWritable(
storeMessage(
external({
init: seqno === 0 ? contract.init : undefined,
to: contract.address,
body: transfer
})
)
)
.endCell()
await this._tonApiClient.gasless.gaslessSend({
walletPublicKey: Buffer.from(this.keyPair.publicKey).toString('hex'),
boc
})
return transfer.hash().toString('hex')
}
}