@tetherto/wdk-wallet-evm
Version:
A simple package to manage BIP-32 wallets for evm blockchains.
210 lines (173 loc) • 7.4 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 { WalletAccountReadOnly } from '@tetherto/wdk-wallet'
import { BrowserProvider, Contract, JsonRpcProvider } from 'ethers'
/** @typedef {import('ethers').Provider} Provider */
/** @typedef {import('ethers').Eip1193Provider} Eip1193Provider */
/** @typedef {import('ethers').TransactionReceipt} EvmTransactionReceipt */
/** @typedef {import('@tetherto/wdk-wallet').TransactionResult} TransactionResult */
/** @typedef {import('@tetherto/wdk-wallet').TransferOptions} TransferOptions */
/** @typedef {import('@tetherto/wdk-wallet').TransferResult} TransferResult */
/**
* @typedef {Object} EvmTransaction
* @property {string} to - The transaction's recipient.
* @property {number | bigint} value - The amount of ethers to send to the recipient (in weis).
* @property {string} [data] - The transaction's data in hex format.
* @property {number | bigint} [gasLimit] - The maximum amount of gas this transaction is permitted to use.
* @property {number | bigint} [gasPrice] - The price (in wei) per unit of gas this transaction will pay.
* @property {number | bigint} [maxFeePerGas] - The maximum price (in wei) per unit of gas this transaction will pay for the combined [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) block's base fee and this transaction's priority fee.
* @property {number | bigint} [maxPriorityFeePerGas] - The price (in wei) per unit of gas this transaction will allow in addition to the [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) block's base fee to bribe miners into giving this transaction priority. This is included in the maxFeePerGas, so this will not affect the total maximum cost set with maxFeePerGas.
*/
/**
* @typedef {Object} EvmWalletConfig
* @property {string | Eip1193Provider} [provider] - The url of the rpc provider, or an instance of a class that implements eip-1193.
* @property {number | bigint} [transferMaxFee] - The maximum fee amount for transfer operations.
*/
export default class WalletAccountReadOnlyEvm extends WalletAccountReadOnly {
/**
* Creates a new evm read-only wallet account.
*
* @param {string} address - The account's address.
* @param {Omit<EvmWalletConfig, 'transferMaxFee'>} [config] - The configuration object.
*/
constructor (address, config = { }) {
super(address)
/**
* The read-only wallet account configuration.
*
* @protected
* @type {Omit<EvmWalletConfig, 'transferMaxFee'>}
*/
this._config = config
const { provider } = config
if (provider) {
/**
* An ethers provider to interact with a node of the blockchain.
*
* @protected
* @type {Provider | undefined}
*/
this._provider = typeof provider === 'string'
? new JsonRpcProvider(provider)
: new BrowserProvider(provider)
}
}
/**
* Returns the account's eth balance.
*
* @returns {Promise<bigint>} The eth balance (in weis).
*/
async getBalance () {
if (!this._provider) {
throw new Error('The wallet must be connected to a provider to retrieve balances.')
}
const address = await this.getAddress()
const balance = await this._provider.getBalance(address)
return balance
}
/**
* 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) {
if (!this._provider) {
throw new Error('The wallet must be connected to a provider to retrieve token balances.')
}
const address = await this.getAddress()
const abi = ['function balanceOf(address owner) view returns (uint256)']
const contract = new Contract(tokenAddress, abi, this._provider)
const balance = await contract.balanceOf(address)
return balance
}
/**
* Quotes the costs of a send transaction operation.
*
* @param {EvmTransaction} tx - The transaction.
* @returns {Promise<Omit<TransactionResult, 'hash'>>} The transaction's quotes.
*/
async quoteSendTransaction (tx) {
if (!this._provider) {
throw new Error('The wallet must be connected to a provider to quote send transaction operations.')
}
const gas = await this._provider.estimateGas({
from: await this.getAddress(),
...tx
})
const { maxFeePerGas } = await this._provider.getFeeData()
return { fee: gas * maxFeePerGas }
}
/**
* Quotes the costs of a transfer operation.
*
* @param {TransferOptions} options - The transfer's options.
* @returns {Promise<Omit<TransferResult, 'hash'>>} The transfer's quotes.
*/
async quoteTransfer (options) {
if (!this._provider) {
throw new Error('The wallet must be connected to a provider to quote transfer operations.')
}
const tx = await WalletAccountReadOnlyEvm._getTransferTransaction(options)
const result = await this.quoteSendTransaction(tx)
return result
}
/**
* Returns a transaction's receipt.
*
* @param {string} hash - The transaction's hash.
* @returns {Promise<EvmTransactionReceipt | null>} – The receipt, or null if the transaction has not been included in a block yet.
*/
async getTransactionReceipt (hash) {
if (!this._provider) {
throw new Error('The wallet must be connected to a provider to fetch transaction receipts.')
}
return await this._provider.getTransactionReceipt(hash)
}
/**
* Returns the current allowance for the given token and spender.
* @param {string} token The token’s address.
* @param {string} spender The spender’s address.
* @returns {Promise<bigint>} The allowance.
*/
async getAllowance (token, spender) {
if (!this._provider) {
throw new Error('The wallet must be connected to a provider to retrieve allowances.')
}
const address = await this.getAddress()
const abi = ['function allowance(address owner, address spender) view returns (uint256)']
const contract = new Contract(token, abi, this._provider)
const allowance = await contract.allowance(address, spender)
return allowance
}
/**
* Returns an evm transaction to execute the given token transfer.
*
* @protected
* @param {TransferOptions} options - The transfer's options.
* @returns {Promise<EvmTransaction>} The evm transaction.
*/
static async _getTransferTransaction (options) {
const { token, recipient, amount } = options
const abi = ['function transfer(address to, uint256 amount) returns (bool)']
const contract = new Contract(token, abi)
const tx = {
to: token,
value: 0,
data: contract.interface.encodeFunctionData('transfer', [recipient, amount])
}
return tx
}
}