@goequitize/rwa-token-sdk
Version:
SDK for creating and managing RWA token transactions with compliance features
275 lines (274 loc) • 11.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RwaTokenBase = void 0;
const ethers_1 = require("ethers");
/**
* Abstract base class for RWA Token operations across different chains
*/
class RwaTokenBase {
/**
* Create a new instance of the RwaTokenBase
* @param provider The JSON-RPC provider instance
* @param chainId The chain ID of the connected network
*/
constructor(provider, chainId) {
this.balanceCache = new Map();
this.decimalsCache = new Map();
this.symbolCache = new Map();
this.provider = provider;
this.chainId = chainId;
}
/**
* Get the decimals of a token
* @param tokenAddress The address of the token contract
* @returns The number of decimals of the token
*/
async getDecimals(tokenAddress) {
// Check cache first
if (this.decimalsCache.has(tokenAddress)) {
return this.decimalsCache.get(tokenAddress);
}
// Native token case
if (tokenAddress.toLowerCase() === '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'.toLowerCase()) {
return 18; // Native tokens (ETH, BNB, etc.) typically have 18 decimals
}
try {
const tokenContract = new ethers_1.ethers.Contract(tokenAddress, ['function decimals() view returns (uint8)'], this.provider);
const decimals = await tokenContract.decimals();
// Cache the result
this.decimalsCache.set(tokenAddress, decimals);
return decimals;
}
catch (error) {
throw new Error(`Failed to get token decimals: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Get the symbol of a token
* @param tokenAddress The address of the token contract
* @returns The symbol of the token
*/
async getSymbol(tokenAddress) {
// Check cache first
if (this.symbolCache.has(tokenAddress)) {
return this.symbolCache.get(tokenAddress);
}
// Native token case
if (tokenAddress.toLowerCase() === '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'.toLowerCase()) {
// Return appropriate symbol based on chainId
switch (this.chainId) {
case 1: // Ethereum Mainnet
return 'ETH';
case 56: // Binance Smart Chain
return 'BNB';
case 137: // Polygon
return 'MATIC';
default:
return 'NATIVE';
}
}
try {
const tokenContract = new ethers_1.ethers.Contract(tokenAddress, ['function symbol() view returns (string)'], this.provider);
const symbol = await tokenContract.symbol();
// Cache the result
this.symbolCache.set(tokenAddress, symbol);
return symbol;
}
catch (error) {
throw new Error(`Failed to get token symbol: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Get the token balance of an address
* @param tokenAddress The address of the token contract
* @param walletAddress The address of the wallet to check the balance of
* @returns The formatted token balance (e.g., 123.45)
*/
async getTokenBalance(tokenAddress, walletAddress) {
// Generate a cache key
const cacheKey = `${tokenAddress}_${walletAddress}`;
// Native token case
if (tokenAddress.toLowerCase() === '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'.toLowerCase()) {
try {
const balance = await this.provider.getBalance(walletAddress);
return ethers_1.ethers.formatEther(balance);
}
catch (error) {
throw new Error(`Failed to get native token balance: ${error instanceof Error ? error.message : String(error)}`);
}
}
try {
const tokenContract = new ethers_1.ethers.Contract(tokenAddress, [
'function balanceOf(address account) view returns (uint256)',
'function decimals() view returns (uint8)'
], this.provider);
const balanceWei = await tokenContract.balanceOf(walletAddress);
const decimals = await this.getDecimals(tokenAddress);
// Format the balance using the token's decimals
const formattedBalance = ethers_1.ethers.formatUnits(balanceWei, decimals);
return formattedBalance;
}
catch (error) {
throw new Error(`Failed to get token balance: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Get information about current gas prices and estimated costs
* @param txnType The type of transaction (used for gas estimation)
* @returns Gas price information
*/
async getGasInfo(txnType) {
try {
const feeData = await this.provider.getFeeData();
// Determine multiplier for recommended gas based on transaction type
let recommendedMultiplier = 1.2; // Default 20% markup
if (txnType) {
// Adjust multiplier based on transaction type
switch (txnType.toLowerCase()) {
case 'mint':
case 'burn':
recommendedMultiplier = 1.3; // 30% markup for more complex operations
break;
case 'transfer':
recommendedMultiplier = 1.1; // 10% markup for simple transfers
break;
default:
recommendedMultiplier = 1.2; // Default 20% markup
}
}
// Use integer math for multiplier
const multiplierInt = BigInt(Math.round(recommendedMultiplier * 100));
// Calculate recommended values with markup using integer math
const recommendedGasPrice = feeData.gasPrice ?
ethers_1.ethers.formatUnits((feeData.gasPrice * multiplierInt) / 100n, 'gwei') :
'0';
const recommendedMaxFeePerGas = feeData.maxFeePerGas ?
ethers_1.ethers.formatUnits((feeData.maxFeePerGas * multiplierInt) / 100n, 'gwei') :
'0';
const recommendedMaxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?
ethers_1.ethers.formatUnits((feeData.maxPriorityFeePerGas * multiplierInt) / 100n, 'gwei') :
'0';
return {
market: {
gasPrice: feeData.gasPrice ? ethers_1.ethers.formatUnits(feeData.gasPrice, 'gwei') : '0',
maxFeePerGas: feeData.maxFeePerGas ? ethers_1.ethers.formatUnits(feeData.maxFeePerGas, 'gwei') : '0',
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ? ethers_1.ethers.formatUnits(feeData.maxPriorityFeePerGas, 'gwei') : '0',
},
recommended: {
gasPrice: recommendedGasPrice,
maxFeePerGas: recommendedMaxFeePerGas,
maxPriorityFeePerGas: recommendedMaxPriorityFeePerGas,
}
};
}
catch (error) {
throw new Error(`Failed to get gas info: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Sign a transaction using a private key
* @param rawTx The unsigned transaction
* @param privateKey The private key to sign with
* @returns The signed transaction
*/
async signTxn(rawTx, privateKey) {
try {
// Create a wallet from the private key
const wallet = new ethers_1.ethers.Wallet(privateKey, this.provider);
// Sign the transaction
const signedTx = await wallet.signTransaction({
to: rawTx.to,
data: rawTx.data,
value: rawTx.value ? ethers_1.ethers.parseEther(rawTx.value) : undefined,
gasLimit: rawTx.gasLimit ? BigInt(rawTx.gasLimit) : undefined,
maxFeePerGas: rawTx.maxFeePerGas ? ethers_1.ethers.parseUnits(rawTx.maxFeePerGas, 'gwei') : undefined,
maxPriorityFeePerGas: rawTx.maxPriorityFeePerGas ? ethers_1.ethers.parseUnits(rawTx.maxPriorityFeePerGas, 'gwei') : undefined,
chainId: this.chainId
});
return signedTx;
}
catch (error) {
throw new Error(`Failed to sign transaction: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Broadcast a signed transaction to the network
* @param signedTxn The signed transaction hex string
* @returns Transaction response information
*/
async broadcast(signedTxn) {
try {
// Send the raw transaction
const tx = await this.provider.broadcastTransaction(signedTxn);
// Construct an explorer URL based on the chain ID
let explorerURL = '';
switch (this.chainId) {
case 1: // Ethereum Mainnet
explorerURL = `https://etherscan.io/tx/${tx.hash}`;
break;
case 5: // Goerli Testnet
explorerURL = `https://goerli.etherscan.io/tx/${tx.hash}`;
break;
case 56: // Binance Smart Chain
explorerURL = `https://bscscan.com/tx/${tx.hash}`;
break;
case 97: // BSC Testnet
explorerURL = `https://testnet.bscscan.com/tx/${tx.hash}`;
break;
case 137: // Polygon
explorerURL = `https://polygonscan.com/tx/${tx.hash}`;
break;
case 80001: // Mumbai (Polygon Testnet)
explorerURL = `https://mumbai.polygonscan.com/tx/${tx.hash}`;
break;
default:
explorerURL = '';
}
return {
hash: tx.hash,
explorerURL
};
}
catch (error) {
throw new Error(`Failed to broadcast transaction: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Get the logs and status of a transaction
* @param txHash The transaction hash
* @returns The transaction logs and status
*/
async getTxnLogs(txHash) {
try {
// Get the transaction receipt
const receipt = await this.provider.getTransactionReceipt(txHash);
if (!receipt) {
// Transaction is still pending
return {
status: 'pending',
logs: []
};
}
// Determine status based on receipt
const status = receipt.status === 1 ? 'success' : 'failed';
// Process the logs
const processedLogs = receipt.logs.map(log => {
// In a real implementation, we would decode the logs based on ABI
// For now, return the raw logs
return {
address: log.address,
topics: log.topics,
data: log.data
};
});
return {
status,
logs: processedLogs
};
}
catch (error) {
throw new Error(`Failed to get transaction logs: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
exports.RwaTokenBase = RwaTokenBase;