UNPKG

p-sdk-wallet

Version:

A comprehensive wallet SDK for React Native (pwc), supporting multi-chain and multi-account features.

1,651 lines (1,345 loc) 62 kB
# PWC Wallet SDK A comprehensive, secure, and user-friendly wallet SDK for React Native applications. This SDK provides a complete solution for managing cryptocurrency wallets with support for multiple blockchains, HD wallet generation, vanity addresses, NFT support, and advanced features like multi-transfer operations. ## 📋 Table of Contents ### 🚀 Quick Start - [Installation](#installation) - [Basic Wallet Creation](#1-basic-wallet-creation) - [Import Existing Wallet from Seed Phrase](#11-import-existing-wallet-from-seed-phrase) - [Vanity Wallet Generation](#2-vanity-wallet-generation) - [Custom Chain Configuration](#3-custom-chain-configuration) ### 📱 React Native Usage - [Simple Wallet Component](#simple-wallet-component) - [Custom Hook for Wallet Management](#custom-hook-for-wallet-management) ### ⚙️ Configuration - [Hybrid Configuration (Mobile App & SDK)](#hybrid-configuration-mobile-app--sdk) - [Built-in Chains](#built-in-chains) - [Custom Chain Management](#custom-chain-management) - [Override Built-in Chains](#override-built-in-chains) - [Add Custom Chains](#add-custom-chains) - [Setup All Chains at Once](#setup-all-chains-at-once) - [Chain Configuration Priority](#chain-configuration-priority) - [Utility Functions](#utility-functions) - [Vanity Wallet Settings](#vanity-wallet-settings) - [Supported Chains](#supported-chains) ### 💰 Core Wallet Functions - [Account Management](#account-management) - [Add New HD Account](#add-new-hd-account) - [Add New Solana Account](#add-new-solana-account) - [Import Account](#import-account) - [Get Accounts](#get-accounts) - [Balance & Token Functions](#balance--token-functions) - [Get Native Balance](#get-native-balance) - [Get Token Balance](#get-token-balance) - [Get Token Info](#get-token-info) - [Transaction Functions](#transaction-functions) - [Send Native Token](#send-native-token) - [Send Token](#send-token) - [Export Mnemonic](#export-mnemonic) ### 🔄 Multi-Transfer Operations - [Batch Send Native Tokens](#batch-send-native-tokens) - [Batch Send ERC-20 Tokens](#batch-send-erc-20-tokens) ### 📱 QR Code Functionality - [Generate Address QR](#generate-address-qr) - [Generate Mnemonic QR](#generate-mnemonic-qr) - [Generate Transaction QR](#generate-transaction-qr) - [Import Wallet from QR](#import-wallet-from-qr) - [Process Transaction from QR](#process-transaction-from-qr) - [Validate QR Code](#validate-qr-code) - [Generate EIP-681 Compatible Address QR (for Metamask, Binance, Trust Wallet)](#generate-eip-681-compatible-address-qr-for-metamask-binance-trust-wallet) ### 🖼️ NFT Functionality - [Get NFT Details](#get-nft-details) - [Get NFT Balance](#get-nft-balance) - [Get Collection Information](#get-collection-information) - [NFT Metadata & Attributes](#nft-metadata--attributes) - [NFT Transaction History](#nft-transaction-history) - [NFT Types and Interfaces](#nft-types-and-interfaces) - [React Native NFT Component Example](#react-native-nft-component-example) - [Get Owned NFTs](#get-owned-nfts) - [Transfer NFT](#transfer-nft) - [Estimate NFT Transfer Gas](#estimate-nft-transfer-gas) ### ⛽ Advanced Features - [Gas Estimation](#gas-estimation) - [Estimate Native Transfer Gas](#estimate-native-transfer-gas) - [Estimate Token Transfer Gas](#estimate-token-transfer-gas) - [Estimate Multi-Transfer Gas](#estimate-multi-transfer-gas) - [Estimate NFT Transfer Gas](#estimate-nft-transfer-gas) ### 🛠️ Advanced Transaction Utilities - [Build Generic Transaction](#build-generic-transaction) - [Sign Transaction](#sign-transaction) - [Send Raw Transaction](#send-raw-transaction) - [Approve Token (Public Utility)](#approve-token-public-utility) - [Track Transaction Status](#track-transaction-status) ### 🔐 Message Signing - [Sign Message (Vault)](#sign-message-recommended---using-vault) - [Sign Message (Private Key)](#sign-message-deprecated---using-private-key) - [Verify Message](#verify-message) - [Sign Typed Data (Vault)](#sign-typed-data-recommended---using-vault) - [Sign Typed Data (Private Key)](#sign-typed-data-deprecated---using-private-key) - [Verify Typed Data](#verify-typed-data) ### 🔢 Nonce Management - [Get Nonce](#get-nonce) - [Sign Message with Nonce (Vault)](#sign-message-with-nonce-recommended---using-vault) - [Sign Message with Nonce (Private Key)](#sign-message-with-nonce-deprecated---using-private-key) - [Verify Message with Nonce](#verify-message-with-nonce) ### 🔍 Read-Only Utilities (Token & NFT) - [Get Token Balance](#get-token-balance) - [Get Token Info](#get-token-info) - [Get Native Balance](#get-native-balance) - [Check Allowance](#check-allowance) - [Get NFT Details](#get-nft-details) - [Get NFT Balance](#get-nft-balance) - [Get Owned NFTs](#get-owned-nfts) ### 🔒 Security & Error Handling - [Security Considerations](#security-considerations) - [Error Handling](#error-handling) ### 🛠️ Development - [Contributing](#contributing) ### 🛠️ Advanced Transaction Utilities ### Build Generic Transaction Builds an unsigned transaction for any contract method. **Parameters:** - `contractAddress` - The contract address to interact with - `abi` - The contract ABI array - `method` - The method name to call - `args` - Arguments to pass to the method (optional) - `value` - Native token value to send (optional) - `chainId` - The chain ID (e.g., '1' for Ethereum, '56' for BSC) ```typescript import { buildTransaction } from 'pwc-wallet-sdk/services/TokenUtils'; const unsignedTx = await buildTransaction({ contractAddress: '0xA0b86a33E6441b8c4C8C8C8C8C8C8C8C8C8C8C8', // USDT contract abi: [ "function transfer(address to, uint256 amount) returns (bool)", "function balanceOf(address account) view returns (uint256)" ], method: 'transfer', args: ['0xRecipientAddress...', '1000000000'], // to address, amount (in smallest unit) chainId: '1' // Ethereum mainnet }); ``` ### Sign Transaction (RECOMMENDED - Using Vault) Signs an unsigned transaction using Vault for better security. **Parameters:** - `unsignedTx` - The unsigned transaction object from buildTransaction - `vault` - The vault instance containing the account - `fromAddress` - The sender's address - `chainId` - The chain ID ```typescript import { signTransactionWithVault } from 'pwc-wallet-sdk/services/TokenUtils'; const signedTx = await signTransactionWithVault(unsignedTx, vault, '0xYourAddress', '1'); ``` ### Sign Transaction (DEPRECATED - Using Private Key) ⚠️ **DEPRECATED**: Use signTransactionWithVault instead for better security. **Parameters:** - `unsignedTx` - The unsigned transaction object from buildTransaction - `privateKey` - The private key to sign with (without 0x prefix) - `chainId` - The chain ID ```typescript import { signTransaction } from 'pwc-wallet-sdk/services/TokenUtils'; const privateKey = 'your-private-key-without-0x-prefix'; const signedTx = await signTransaction(unsignedTx, privateKey, '1'); ``` ### Send Raw Transaction Broadcasts a signed transaction to the network. **Parameters:** - `signedTx` - The signed transaction as hex string - `chainId` - The chain ID ```typescript import { sendTransaction } from 'pwc-wallet-sdk/services/TokenUtils'; const txResponse = await sendTransaction(signedTx, '1'); console.log('Transaction hash:', txResponse.hash); console.log('Block number:', txResponse.blockNumber); ``` ### Approve Token (RECOMMENDED - Using Vault) Approves a spender to spend tokens using Vault for better security. **Parameters:** - `vault` - The vault instance containing the owner's account - `fromAddress` - The owner's address - `tokenAddress` - The ERC-20 token contract address - `spender` - The spender's address (e.g., DEX contract address) - `amount` - The amount to approve (string or bigint) - `chainId` - The chain ID ```typescript import { approveTokenWithVault } from 'pwc-wallet-sdk/services/TokenUtils'; const receipt = await approveTokenWithVault( vault, '0xOwner...', '0xToken...', '0xSpender...', '1000', '1' ); ``` ### Approve Token (DEPRECATED - Using Private Key) ⚠️ **DEPRECATED**: Use approveTokenWithVault instead for better security. **Parameters:** - `privateKey` - The owner's private key (without 0x prefix) - `tokenAddress` - The ERC-20 token contract address - `spender` - The spender's address (e.g., DEX contract address) - `amount` - The amount to approve (string or bigint) - `chainId` - The chain ID ```typescript import { approveToken } from 'pwc-wallet-sdk/services/TokenUtils'; const receipt = await approveToken('privateKey...', '0xToken...', '0xSpender...', '1000', '1'); ``` ### Track Transaction Status Tracks the status of a transaction with polling and callback notifications. **Parameters:** - `txHash` - The transaction hash to track - `chainId` - The chain ID - `callback` - Function called with status updates ('pending', 'confirmed', 'failed') - `pollInterval` - Polling interval in milliseconds (optional, default: 3000) ```typescript import { trackTxStatus } from 'pwc-wallet-sdk/services/TokenUtils'; trackTxStatus('0xTransactionHash...', '1', (status, receipt) => { switch (status) { case 'pending': console.log('Transaction is pending...'); break; case 'confirmed': console.log('Transaction confirmed!', receipt?.transactionHash); break; case 'failed': console.log('Transaction failed!', receipt?.transactionHash); break; } }, 5000); // Poll every 5 seconds ``` ### 🔐 Message Signing ### Sign Message (RECOMMENDED - Using Vault) Signs a message using Vault for better security. This is equivalent to MetaMask's `personal_sign` method. **Parameters:** - `message` - The message to sign (string or hex string) - `vault` - The vault instance containing the account - `fromAddress` - The signer's address ```typescript import { signMessageWithVault } from 'pwc-wallet-sdk/services/TokenUtils'; const signature = await signMessageWithVault('Hello World', vault, '0xYourAddress'); console.log('Signature:', signature); ``` ### Sign Message (DEPRECATED - Using Private Key) ⚠️ **DEPRECATED**: Use `signMessageWithVault` instead for better security. **Parameters:** - `message` - The message to sign (string or hex string) - `privateKey` - The private key to sign with (without 0x prefix) ```typescript import { signMessage } from 'pwc-wallet-sdk/services/TokenUtils'; const signature = await signMessage('Hello World', 'your-private-key'); console.log('Signature:', signature); ``` ### Verify Message Verifies a message signature and returns the signer's address. **Parameters:** - `message` - The original message that was signed - `signature` - The signature to verify ```typescript import { verifyMessage } from 'pwc-wallet-sdk/services/TokenUtils'; const signerAddress = await verifyMessage('Hello World', '0xSignature...'); console.log('Message was signed by:', signerAddress); ``` ### Sign Typed Data (RECOMMENDED - Using Vault) Signs a typed data message using Vault. This is equivalent to MetaMask's `eth_signTypedData` method. **Parameters:** - `typedData` - The typed data object to sign - `vault` - The vault instance containing the account - `fromAddress` - The signer's address ```typescript import { signTypedDataWithVault } from 'pwc-wallet-sdk/services/TokenUtils'; const typedData = { types: { Person: [ { name: 'name', type: 'string' }, { name: 'wallet', type: 'address' } ] }, primaryType: 'Person', domain: { name: 'MyApp', version: '1' }, message: { name: 'Alice', wallet: '0x123...' } }; const signature = await signTypedDataWithVault(typedData, vault, '0xYourAddress'); console.log('Typed Data Signature:', signature); ``` ### Sign Typed Data (DEPRECATED - Using Private Key) ⚠️ **DEPRECATED**: Use `signTypedDataWithVault` instead for better security. **Parameters:** - `typedData` - The typed data object to sign - `privateKey` - The private key to sign with (without 0x prefix) ```typescript import { signTypedData } from 'pwc-wallet-sdk/services/TokenUtils'; const typedData = { types: { Person: [{ name: 'name', type: 'string' }] }, primaryType: 'Person', domain: { name: 'MyApp' }, message: { name: 'Alice' } }; const signature = await signTypedData(typedData, 'your-private-key'); console.log('Typed Data Signature:', signature); ``` ### Verify Typed Data Verifies a typed data signature and returns the signer's address. **Parameters:** - `typedData` - The original typed data that was signed - `signature` - The signature to verify ```typescript import { verifyTypedData } from 'pwc-wallet-sdk/services/TokenUtils'; const signerAddress = await verifyTypedData(typedData, '0xSignature...'); console.log('Typed data was signed by:', signerAddress); ``` ### 🔢 Nonce Management ### Get Nonce Gets the transaction count (nonce) for an address. Useful for message signing with nonce and transaction building. **Parameters:** - `address` - The address to get nonce for - `chainId` - The chain ID (e.g., '1' for Ethereum, '56' for BSC) ```typescript import { getNonce } from 'pwc-wallet-sdk/services/TokenUtils'; const nonce = await getNonce('0xYourAddress', '1'); console.log('Current nonce:', nonce); ``` ### Sign Message with Nonce (RECOMMENDED - Using Vault) Signs a message with nonce using Vault for better security. This prevents replay attacks by including the current nonce in the message. **Parameters:** - `message` - The message to sign (string or hex string) - `vault` - The vault instance containing the account - `fromAddress` - The signer's address - `chainId` - The chain ID ```typescript import { signMessageWithNonce } from 'pwc-wallet-sdk/services/TokenUtils'; const result = await signMessageWithNonce('Hello World', vault, '0xYourAddress', '1'); console.log('Signature:', result.signature); console.log('Nonce:', result.nonce); ``` ### Sign Message with Nonce (DEPRECATED - Using Private Key) ⚠️ **DEPRECATED**: Use `signMessageWithNonce` with Vault instead for better security. **Parameters:** - `message` - The message to sign (string or hex string) - `privateKey` - The private key to sign with (without 0x prefix) - `address` - The signer's address (needed to get nonce) - `chainId` - The chain ID ```typescript import { signMessageWithNoncePrivateKey } from 'pwc-wallet-sdk/services/TokenUtils'; const result = await signMessageWithNoncePrivateKey('Hello World', 'your-private-key', '0xYourAddress', '1'); console.log('Signature:', result.signature); console.log('Nonce:', result.nonce); ``` ### Verify Message with Nonce Verifies a message signature with nonce and returns the signer's address. **Parameters:** - `message` - The original message that was signed (without nonce) - `signature` - The signature to verify - `nonce` - The nonce that was used in signing ```typescript import { verifyMessageWithNonce } from 'pwc-wallet-sdk/services/TokenUtils'; const signerAddress = await verifyMessageWithNonce('Hello World', '0xSignature...', 5); console.log('Message was signed by:', signerAddress); ``` ### 🔍 Read-Only Utilities (Token & NFT) ### Get Token Balance Gets the token balance for a specific account. **Parameters:** - `tokenAddress` - The ERC-20 token contract address - `account` - The account address to check balance for - `chainId` - The chain ID ```typescript import { getTokenBalance } from 'pwc-wallet-sdk/services/TokenUtils'; const balance = await getTokenBalance( '0xA0b86a33E6441b8c4C8C8C8C8C8C8C8C8C8C8C8', // USDT contract '0xYourWalletAddress...', '1' // Ethereum mainnet ); console.log('USDT Balance:', ethers.formatUnits(balance, 6)); // USDT has 6 decimals ``` ### Get Token Info Gets comprehensive information about an ERC-20 token. **Parameters:** - `tokenAddress` - The ERC-20 token contract address - `chainId` - The chain ID ```typescript import { getTokenInfo } from 'pwc-wallet-sdk/services/TokenUtils'; const info = await getTokenInfo( '0xA0b86a33E6441b8c4C8C8C8C8C8C8C8C8C8C8C8', // USDT contract '1' // Ethereum mainnet ); console.log('Token:', info.name, '(', info.symbol, ')'); console.log('Decimals:', info.decimals); console.log('Total Supply:', ethers.formatUnits(info.totalSupply, info.decimals)); ``` ### Get Native Balance Gets the native token balance (ETH, BNB, MATIC, etc.) for an account. **Parameters:** - `account` - The account address to check balance for - `chainId` - The chain ID ```typescript import { getNativeBalance } from 'pwc-wallet-sdk/services/TokenUtils'; const balance = await getNativeBalance('0xYourWalletAddress...', '1'); console.log('ETH Balance:', ethers.formatEther(balance)); // For BSC const bnbBalance = await getNativeBalance('0xYourWalletAddress...', '56'); console.log('BNB Balance:', ethers.formatEther(bnbBalance)); ``` ### Check Allowance Checks the allowance granted by an owner to a spender for a specific token. **Parameters:** - `tokenAddress` - The ERC-20 token contract address - `owner` - The token owner's address - `spender` - The spender's address (e.g., DEX contract address) - `chainId` - The chain ID ```typescript import { checkAllowance } from 'pwc-wallet-sdk/services/TokenUtils'; const allowance = await checkAllowance( '0xA0b86a33E6441b8c4C8C8C8C8C8C8C8C8C8C8C8', // USDT contract '0xYourWalletAddress...', // Owner '0xDexContractAddress...', // Spender (DEX) '1' // Ethereum mainnet ); console.log('Allowance:', ethers.formatUnits(allowance, 6)); // USDT has 6 decimals ``` ### Get NFT Details Gets comprehensive NFT detail by reading directly from blockchain. Supports both ERC-721 and ERC-1155 NFTs. **ERC-1155 Support Features:** - ✅ **Auto-detection**: Automatically detects ERC-721 vs ERC-1155 contracts - ✅ **Metadata resolution**: Reads metadata from `uri(tokenId)` function - ✅ **Transaction history**: Supports TransferSingle and TransferBatch events - ✅ **Collection info**: Gets collection name and symbol - ✅ **Token validation**: Checks if token exists via `exists(tokenId)` - ✅ **Multiple owners**: Handles ERC-1155's multiple ownership model **Parameters:** - `contractAddress` - NFT contract address - `tokenId` - Token ID (string) - `options` - Optional parameters for additional data - `includeMetadata` - Whether to fetch metadata (default: false) - `includeHistory` - Whether to fetch transaction history (default: false) - `includeCollection` - Whether to fetch collection info (default: false) ```typescript import { NFTService } from 'pwc-wallet-sdk'; const nftService = new NFTService('1'); // chainId = '1' for Ethereum (read-only) // For ERC-721 NFTs const nftDetail = await nftService.getNFTDetail( '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D', // BAYC contract '1234', // Token ID { includeMetadata: true, includeHistory: true, includeCollection: true } ); console.log('NFT Name:', nftDetail.name); console.log('Owner:', nftDetail.owner); console.log('Image URL:', nftDetail.image); console.log('Attributes:', nftDetail.attributes); // For ERC-1155 NFTs const erc1155Detail = await nftService.getNFTDetail( '0xERC1155Contract...', // ERC-1155 contract '123', // Token ID { includeMetadata: true, includeHistory: true, includeCollection: true } ); console.log('ERC-1155 Name:', erc1155Detail.name); console.log('Owner:', erc1155Detail.owner); // 'Multiple Owners' for ERC-1155 console.log('Token Type:', erc1155Detail.tokenType); // 'ERC-1155' ``` ### Get NFT Balance Gets NFT balance for an address from a specific contract. Supports both ERC-721 and ERC-1155. **Parameters:** - `address` - Wallet address to check balance for - `contractAddress` - NFT contract address - `tokenId` - (Optional) Token ID for ERC-1155 contracts ```typescript // For ERC-721 NFTs const nftBalance = await nftService.getNFTBalance( '0xYourWalletAddress...', '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D' // BAYC contract ); console.log('ERC-721 Count:', nftBalance.count); // For ERC-1155 NFTs (with specific token ID) const nftBalance = await nftService.getNFTBalance( '0xYourWalletAddress...', '0xERC1155Contract...', '123' // Token ID ); console.log('ERC-1155 Balance:', nftBalance.count); ``` ### Get ERC-1155 Balance Gets ERC-1155 token balance for a specific address and token ID. **Parameters:** - `address` - Wallet address to check balance for - `contractAddress` - ERC-1155 contract address - `tokenId` - Token ID to check balance for ```typescript const balance = await nftService.getERC1155Balance( '0xYourWalletAddress...', '0xERC1155Contract...', '123' // Token ID ); console.log('ERC-1155 Balance:', balance.toString()); ``` ### Get Multiple ERC-1155 Balances Gets balances for multiple ERC-1155 tokens at once. **Parameters:** - `address` - Wallet address to check balances for - `contractAddress` - ERC-1155 contract address - `tokenIds` - Array of token IDs to check ```typescript const balances = await nftService.getERC1155Balances( '0xYourWalletAddress...', '0xERC1155Contract...', ['123', '456', '789'] // Multiple token IDs ); balances.forEach(balance => { console.log(`Token ${balance.tokenId}: ${balance.amount.toString()}`); }); ``` ### Get Collection Information Gets collection information directly from the NFT contract. **Parameters:** - `contractAddress` - NFT contract address ```typescript const collection = await nftService.getCollectionInfo('0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D'); console.log('Collection Name:', collection.name); console.log('Total Supply:', collection.totalSupply); console.log('Token Type:', collection.tokenType); ``` ### NFT Metadata & Attributes NFT metadata is automatically resolved from tokenURI (IPFS or HTTP). Works for both ERC-721 and ERC-1155. **Key Differences:** - **ERC-721**: Single owner per token, uses `tokenURI(tokenId)` - **ERC-1155**: Multiple owners possible, uses `uri(tokenId)`, owner field shows "Multiple Owners" ```typescript import { NFTDetail, NFTDetailExtended, NFTCollection, NFTOptions } from 'pwc-wallet-sdk'; // Basic NFT information (on-chain data) interface NFTDetail { contractAddress: string; tokenId: string; name: string; owner: string; tokenType: 'ERC-721' | 'ERC-1155' | 'SPL-NFT'; chainId: string; tokenUri?: string; createdAt?: number; lastTransferAt?: number; } // Extended NFT information with metadata interface NFTDetailExtended extends NFTDetail { description?: string; image?: string; attributes?: Array<{ trait_type: string; value: string; display_type?: string }>; metadata?: NFTMetadata; collection?: NFTCollection; transactionHistory?: NFTTransaction[]; metadataSource?: 'ipfs' | 'http'; lastUpdated: number; } // Collection information interface NFTCollection { contractAddress: string; name: string; symbol: string; tokenType: string; chainId: string; totalSupply?: number; } ``` ### NFT Transaction History Gets transaction history from blockchain events. **Parameters:** - `contractAddress` - NFT contract address - `tokenId` - Token ID (string) ```typescript const history = await nftService.getTransactionHistory( '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D', // NFT contract '1234' // Token ID ); console.log('Transaction History:', history); // Returns array of: { hash, from, to, blockNumber, timestamp, type } ``` ### NFT Types and Interfaces Complete TypeScript interfaces for NFT functionality. ```typescript import { NFTDetail, NFTDetailExtended, NFTCollection, NFTOptions, NFTMetadata, NFTTransaction, NFTBalance } from 'pwc-wallet-sdk'; // Basic NFT information (on-chain data) interface NFTDetail { contractAddress: string; tokenId: string; name: string; owner: string; tokenType: 'ERC-721' | 'ERC-1155' | 'SPL-NFT'; chainId: string; tokenUri?: string; createdAt?: number; lastTransferAt?: number; } // Extended NFT information with metadata interface NFTDetailExtended extends NFTDetail { description?: string; image?: string; attributes?: Array<{ trait_type: string; value: string; display_type?: string }>; metadata?: NFTMetadata; collection?: NFTCollection; transactionHistory?: NFTTransaction[]; metadataSource?: 'ipfs' | 'http'; lastUpdated: number; } // NFT metadata from tokenURI interface NFTMetadata { name: string; description?: string; image?: string; attributes?: Array<{ trait_type: string; value: string; display_type?: string }>; external_url?: string; animation_url?: string; } // Collection information interface NFTCollection { contractAddress: string; name: string; symbol: string; tokenType: string; chainId: string; totalSupply?: number; } // NFT transaction history interface NFTTransaction { hash: string; from: string; to: string; blockNumber: number; timestamp: number; type: 'mint' | 'transfer'; } // NFT balance information interface NFTBalance { contractAddress: string; tokenIds: string[]; count: number; tokenType: string; chainId: string; } // NFT options for queries interface NFTOptions { includeMetadata?: boolean; includeHistory?: boolean; includeCollection?: boolean; } ``` ### React Native NFT Component Example ```typescript import React, { useState, useEffect } from 'react'; import { View, Text, Image, ScrollView } from 'react-native'; import { NFTService, NFTDetailExtended } from 'pwc-wallet-sdk'; const NFTDetailView = ({ contractAddress, tokenId, chainId }) => { const [nft, setNft] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { loadNFTDetail(); }, [contractAddress, tokenId]); const loadNFTDetail = async () => { try { setLoading(true); setError(null); const nftService = new NFTService(chainId); // Read-only operations const nftDetail = await nftService.getNFTDetail( contractAddress, tokenId, { includeMetadata: true, includeHistory: true } ); setNft(nftDetail); } catch (err) { setError(err.message); console.error('Failed to load NFT:', err); } finally { setLoading(false); } }; if (loading) { return <Text>Loading NFT...</Text>; } if (error) { return <Text>Error: {error}</Text>; } if (!nft) { return <Text>NFT not found</Text>; } return ( <ScrollView style={{ padding: 20 }}> {nft.image && ( <Image source={{ uri: nft.image }} style={{ width: 300, height: 300, alignSelf: 'center' }} resizeMode="cover" /> )} <Text style={{ fontSize: 24, fontWeight: 'bold', marginTop: 10 }}> {nft.name} </Text> {nft.description && ( <Text style={{ marginTop: 5 }}>{nft.description}</Text> )} <Text style={{ marginTop: 10 }}> Contract: {nft.contractAddress} </Text> <Text>Token ID: {nft.tokenId}</Text> <Text>Owner: {nft.owner}</Text> <Text>Type: {nft.tokenType}</Text> {nft.attributes && nft.attributes.length > 0 && ( <View style={{ marginTop: 15 }}> <Text style={{ fontSize: 18, fontWeight: 'bold' }}>Attributes:</Text> {nft.attributes.map((attr, index) => ( <Text key={index}> {attr.trait_type}: {attr.value} </Text> ))} </View> )} </ScrollView> ); }; ``` ### Get Owned NFTs Gets all NFTs owned by an address from multiple contracts. **Parameters:** - `account` - Wallet address to check - `contractAddresses` - Array of NFT contract addresses - `options` - Optional parameters (same as getNFTDetail) ```typescript const nfts = await nftService.getOwnedNFTs( '0xYourWalletAddress...', ['0xContract1...', '0xContract2...'], { includeMetadata: true } ); console.log('Total NFTs:', nfts.length); nfts.forEach(nft => { console.log(`${nft.name} (${nft.contractAddress})`); }); ``` ### Transfer NFT Transfers an NFT (ERC-721) from one address to another. **Parameters:** - `fromAddress` - Sender's address - `toAddress` - Recipient's address - `contractAddress` - NFT contract address (ERC-721) - `tokenId` - Token ID to transfer (string) ```typescript // Gửi NFT ERC-721 từ ví này sang ví khác const tx = await vault.transferNFT( '0xSenderAddress', '0xRecipientAddress', '0xNFTContractAddress', '1234' // Token ID ); console.log('NFT transfer tx hash:', tx.hash); ``` > **Note:** Hiện tại chỉ hỗ trợ EVM chains (Ethereum, BSC, v.v.). Solana NFT transfer chưa được hỗ trợ. --- ## NFT Transfer (ERC-721 & ERC-1155) ### Initialize NFTService for Transfer Operations For transfer operations, you need to initialize `NFTService` with vault and sender address: ```typescript import { NFTService, Vault } from 'pwc-wallet-sdk'; // Initialize for read-only operations const nftService = new NFTService('1'); // chainId = '1' for Ethereum // Initialize for write operations (transfer) const nftService = new NFTService('1', vault, fromAddress); ``` ### Transfer NFT (RECOMMENDED - Using Vault) The `transferNFTWithVault` function supports transferring both ERC-721 and ERC-1155 NFTs. **Parameters:** - `vault` - The Vault instance containing the sender's account - `fromAddress` - The sender's wallet address - `toAddress` - The recipient's wallet address - `contractAddress` - The NFT contract address - `tokenId` - The token ID to transfer - `amount` (optional, default "1") - The amount to transfer (required for ERC-1155, default 1 for ERC-721) **Usage:** ```typescript // Initialize NFTService for transfer operations const nftService = new NFTService('1', vault, fromAddress); // ERC-721: only basic parameters are required await nftService.transferNFTWithVault( fromAddress, toAddress, contractAddress, tokenId ); // ERC-1155: specify the amount (number of NFTs to transfer) await nftService.transferNFTWithVault( fromAddress, toAddress, contractAddress, tokenId, "5" // transfer 5 ERC-1155 NFTs ); ``` - The function will automatically detect the contract type (ERC-721 or ERC-1155) and call the correct transfer method. - If the contract is ERC-721, `amount` will default to 1. - If the contract is ERC-1155, `amount` is the number of NFTs to transfer. ### Deprecated: transferNFT The `transferNFT` function (without vault) also supports both standards, but is **not recommended for production**. ### Estimate NFT Transfer Gas Estimates the gas cost for transferring NFTs (ERC-721 and ERC-1155). **Parameters:** - `fromAddress` - The sender's address - `toAddress` - The recipient's address - `contractAddress` - The NFT contract address - `tokenId` - The token ID to transfer - `amount` (optional, default "1") - The amount to transfer (for ERC-1155, default 1 for ERC-721) **Usage:** ```typescript import { NFTService } from 'pwc-wallet-sdk'; // Initialize NFTService const nftService = new NFTService('1'); // Ethereum mainnet // Estimate gas for ERC-721 transfer const erc721Gas = await nftService.estimateGasForTransfer( '0xSender...', '0xRecipient...', '0xContract...', '123' ); console.log('ERC-721 transfer gas:', erc721Gas.toString()); // Estimate gas for ERC-1155 transfer const erc1155Gas = await nftService.estimateGasForTransfer( '0xSender...', '0xRecipient...', '0xContract...', '123', '5' // transfer 5 ERC-1155 NFTs ); console.log('ERC-1155 transfer gas:', erc1155Gas.toString()); ``` ### Estimate Gas for Various NFT Operations Estimates gas cost for different NFT operations including transfer, approve, and setApprovalForAll. **Parameters:** - `operation` - The operation type: 'transfer', 'approve', or 'setApprovalForAll' - `contractAddress` - The NFT contract address - `params` - Operation-specific parameters **Usage:** ```typescript // Estimate gas for ERC-721 approve const approveGas = await nftService.estimateGas('approve', '0xContract...', { toAddress: '0xSpender...', tokenId: '123' }); // Estimate gas for ERC-721 setApprovalForAll const setApprovalGas = await nftService.estimateGas('setApprovalForAll', '0xContract...', { toAddress: '0xOperator...', approved: true }); // Estimate gas for ERC-721 transfer const transferGas = await nftService.estimateGas('transfer', '0xContract...', { fromAddress: '0xSender...', toAddress: '0xRecipient...', tokenId: '123' }); // Estimate gas for ERC-1155 transfer const erc1155TransferGas = await nftService.estimateGas('transfer', '0xContract...', { fromAddress: '0xSender...', toAddress: '0xRecipient...', tokenId: '123', amount: '3' }); console.log('Approve gas:', approveGas.toString()); console.log('Set approval gas:', setApprovalGas.toString()); console.log('Transfer gas:', transferGas.toString()); console.log('ERC-1155 transfer gas:', erc1155TransferGas.toString()); ``` **Supported Operations:** - **ERC-721**: `transfer`, `approve`, `setApprovalForAll` - **ERC-1155**: `transfer` only (approve operations not supported) **Features:** - ✅ **Automatic Contract Detection**: Automatically detects ERC-721 vs ERC-1155 - ✅ **Token Validation**: Verifies token exists before estimating gas - ✅ **Parameter Validation**: Validates required parameters for each operation - ✅ **Error Handling**: Comprehensive error messages for invalid operations --- ## Features - 🔐 **Secure HD Wallet Management**: BIP-44 compliant hierarchical deterministic wallets - 🎯 **Vanity Address Generation**: Generate wallets with custom address prefixes - 🔗 **Multi-Chain Support**: Ethereum, BSC, Polygon, Arbitrum, Optimism, Base, and Solana - 🚀 **Multi-Transfer Operations**: Batch send tokens to multiple recipients - 🔧 **Custom Chain Support**: Add custom chains and override built-in configurations - 📱 **React Native Optimized**: Designed specifically for mobile applications - 🔒 **Encryption**: AES-256 encryption for secure vault storage - 🎨 **TypeScript**: Full TypeScript support with comprehensive type definitions - 🖼️ **NFT Support**: Core NFT functionality for individual tokens and collections - ⛽ **Gas Estimation**: Comprehensive gas estimation for native tokens, ERC-20 tokens, and NFTs ## Installation ```bash npm install pwc-wallet-sdk ``` ## Quick Start ### 1. Basic Wallet Creation ```typescript import { Vault } from 'pwc-wallet-sdk'; // Create a new wallet const { vault, encryptedVault } = await Vault.createNew('your-secure-password'); // Get wallet accounts const accounts = vault.getAccounts(); console.log('Wallet addresses:', accounts.map(acc => acc.address)); ``` ### 1.1. Import Existing Wallet from Seed Phrase ```typescript import { Vault } from 'pwc-wallet-sdk'; // Import wallet from existing mnemonic phrase const existingMnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; // For EVM chains (Ethereum, BSC, Polygon, etc.) const { vault, encryptedVault } = await Vault.createFromMnemonic( existingMnemonic, 'your-secure-password', 'evm' ); // For Solana const { vault, encryptedVault } = await Vault.createFromMnemonic( existingMnemonic, 'your-secure-password', 'solana' ); // Get wallet accounts const accounts = vault.getAccounts(); console.log('Imported wallet addresses:', accounts.map(acc => acc.address)); // Validate mnemonic before importing (optional) import { EncryptionService } from 'pwc-wallet-sdk'; const isValid = EncryptionService.validateMnemonic(existingMnemonic); if (!isValid) { throw new Error('Invalid mnemonic phrase'); } ``` ### 2. Vanity Wallet Generation ```typescript // Generate wallet with custom address prefix const { vault, encryptedVault, attempts, foundAddress } = await Vault.generateVanityHDWallet('your-password', (attempts, currentAddress) => { console.log(`Attempt ${attempts}: ${currentAddress}`); }); console.log(`Found address with prefix after ${attempts} attempts: ${foundAddress}`); ``` #### Mobile-Optimized Vanity Wallet Generation For mobile applications, use the mobile-optimized version for better performance: ```typescript // Mobile-optimized vanity wallet generation const { vault, encryptedVault, attempts, foundAddress } = await Vault.generateVanityHDWalletMobile('your-password', (attempts, currentAddress) => { console.log(`Mobile attempt ${attempts}: ${currentAddress}`); }, { prefix: 'a', // Single character prefix (much faster) maxAttempts: 50000, // Lower limit for mobile timeout: 30000, // 30 seconds timeout caseSensitive: false }); console.log(`Mobile vanity wallet generated in ${attempts} attempts: ${foundAddress}`); ``` **Mobile Optimizations:** - ✅ Batch processing (100 attempts per batch) - ✅ Shorter default prefix (1 character vs 3) - ✅ Lower max attempts (50K vs 1M) - ✅ Timeout protection (30 seconds) - ✅ More frequent progress updates - ✅ More frequent non-blocking yields ### 3. Custom Chain Configuration ```typescript import { setupChainConfigs, registerCustomChain, overrideChain } from 'pwc-wallet-sdk'; // Setup custom chains and overrides (call once when app starts) setupChainConfigs({ // Override built-in chains overrides: { '1': { rpcUrl: 'https://my-eth-rpc.com' }, // Use custom Ethereum RPC '56': { rpcUrl: 'https://my-bsc-rpc.com' }, // Use custom BSC RPC }, // Add custom chains customChains: { 'custom-1': { name: 'My Custom Chain', rpcUrl: 'https://my-custom-rpc.com', explorerUrl: 'https://my-explorer.com', nativeCurrency: { name: 'Token', symbol: 'TKN', decimals: 18 }, type: 'evm' }, 'custom-solana': { name: 'My Solana Chain', rpcUrl: 'https://my-solana-rpc.com', explorerUrl: 'https://my-solana-explorer.com', nativeCurrency: { name: 'SOL', symbol: 'SOL', decimals: 9 }, type: 'solana' } } }); // Load vault const vault = await Vault.load(password, encryptedVault); // Use custom chains normally await vault.sendToken(from, to, amount, tokenAddress, 'custom-1'); await vault.getNativeBalance(address, 'custom-solana'); ``` ## React Native Usage ### Simple Wallet Component ```typescript import React, { useState, useEffect } from 'react'; import { Vault, setupChainConfigs } from 'pwc-wallet-sdk'; const WalletComponent = () => { const [vault, setVault] = useState(null); const [balance, setBalance] = useState('0'); useEffect(() => { // Setup custom chains when component mounts setupChainConfigs({ overrides: { '1': { rpcUrl: 'https://my-eth-rpc.com' } }, customChains: { 'custom-1': { name: 'My Chain', rpcUrl: 'https://my-rpc.com', type: 'evm' } } }); loadWallet(); }, []); const loadWallet = async () => { try { const loadedVault = await Vault.load(password, encryptedVault); setVault(loadedVault); // Get balance using custom chain const balance = await loadedVault.getNativeBalance(address, 'custom-1'); setBalance(ethers.formatEther(balance)); } catch (error) { console.error('Failed to load wallet:', error); } }; const sendTransaction = async () => { if (!vault) return; try { // Send using custom chain const tx = await vault.sendToken(from, to, amount, tokenAddress, 'custom-1'); console.log('Transaction sent:', tx.hash); } catch (error) { console.error('Transaction failed:', error); } }; return ( <View> <Text>Balance: {balance}</Text> <Button title="Send Transaction" onPress={sendTransaction} /> </View> ); }; ``` ### Custom Hook for Wallet Management ```typescript import { useState, useEffect } from 'react'; import { Vault, setupChainConfigs } from 'pwc-wallet-sdk'; export const useWallet = (password: string, encryptedVault: EncryptedData) => { const [vault, setVault] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { initializeWallet(); }, []); const initializeWallet = async () => { try { setLoading(true); // Setup chains setupChainConfigs({ overrides: { '1': { rpcUrl: 'https://my-eth-rpc.com' } } }); const loadedVault = await Vault.load(password, encryptedVault); setVault(loadedVault); } catch (err) { setError(err.message); } finally { setLoading(false); } }; const sendToken = async (from: string, to: string, amount: string, tokenAddress: string, chainId: string) => { if (!vault) throw new Error('Wallet not loaded'); return vault.sendToken(from, to, amount, tokenAddress, chainId); }; const getBalance = async (address: string, chainId: string) => { if (!vault) throw new Error('Wallet not loaded'); return vault.getNativeBalance(address, chainId); }; return { vault, loading, error, sendToken, getBalance }; }; ``` ## Configuration ### Vanity Wallet Settings ```typescript import { VANITY_WALLET_CONFIG } from 'pwc-wallet-sdk'; // Default settings console.log(VANITY_WALLET_CONFIG.DEFAULT_PREFIX); // 'aaa' console.log(VANITY_WALLET_CONFIG.DEFAULT_MAX_ATTEMPTS); // 1000000 console.log(VANITY_WALLET_CONFIG.DEFAULT_CASE_SENSITIVE); // false ``` ### Supported Chains ```typescript import { SUPPORTED_CHAINS } from 'pwc-wallet-sdk'; // View all supported chains Object.entries(SUPPORTED_CHAINS).forEach(([chainId, config]) => { console.log(`${chainId}: ${config.name} (${config.type})`); }); ``` ## Core Wallet Functions ### Account Management #### Add New HD Account ```typescript // Add a new HD account to the vault const newAccount = vault.addHDAccount(); console.log('New account address:', newAccount.address); ``` #### Add New Solana Account ```typescript // Add a new Solana account const newSolanaAccount = vault.addSolanaAccount(); console.log('New Solana account:', newSolanaAccount.address); ``` #### Import Account ```typescript // Import an existing account with private key const importedAccount = vault.importAccount('private-key-without-0x-prefix'); console.log('Imported account:', importedAccount.address); ``` #### Get Accounts ```typescript // Get all accounts in the vault const accounts = vault.getAccounts(); console.log('All accounts:', accounts.map(acc => acc.address)); ``` ### Balance & Token Functions #### Get Native Balance ```typescript // Get native token balance (ETH, BNB, MATIC, etc.) const balance = await vault.getNativeBalance(address, '1'); // Ethereum console.log('ETH Balance:', ethers.formatEther(balance)); ``` #### Get Token Balance ```typescript // Get ERC-20 token balance const tokenBalance = await vault.getTokenBalance(address, tokenAddress, '1'); console.log('Token Balance:', ethers.formatUnits(tokenBalance, 18)); ``` #### Get Token Info ```typescript // Get token information const tokenInfo = await vault.getTokenInfo(tokenAddress, '1'); console.log('Token:', tokenInfo.name, '(', tokenInfo.symbol, ')'); ``` ### Transaction Functions #### Send Native Token ```typescript // Send native tokens (ETH, BNB, etc.) const tx = await vault.sendNativeToken(from, to, amount, '1'); console.log('Transaction hash:', tx.hash); ``` #### Send Token ```typescript // Send ERC-20 tokens const tx = await vault.sendToken(from, to, amount, tokenAddress, '1'); console.log('Transaction hash:', tx.hash); ``` #### Export Mnemonic ```typescript // Export the mnemonic phrase (requires password) const mnemonic = await vault.exportMnemonic(password); console.log('Mnemonic:', mnemonic); ``` ## Multi-Transfer Operations ### Batch Send Native Tokens ```typescript import { MultiTransferService } from 'pwc-wallet-sdk'; const multiTransferService = new MultiTransferService(vault, chainService, '1'); // '1' là chainId cho Ethereum mainnet const recipients = [ { address: '0x1111...', amount: '0.01' }, { address: '0x2222...', amount: '0.02' }, { address: '0x3333...', amount: '0.005' } ]; const result = await multiTransferService.transferNativeTokens( fromAddress, recipients, '1', // chainId { batchSize: 5, onProgress: (progress) => console.log('Progress:', progress) } ); console.log('Successful:', result.successfulCount); console.log('Failed:', result.failedCount); console.log('Total gas used:', result.totalGasUsed.toString()); ``` ### Batch Send ERC-20 Tokens ```typescript const result = await multiTransferService.transferTokens( fromAddress, tokenAddress, // USDT, USDC, etc. recipients, '1', // chainId { batchSize: 10 } ); console.log('Token transfers completed:', result.successfulCount); ``` ## Configuration ### ⚡️ Hybrid Configuration (Mobile App & SDK) > **New in vX.X.X:** You can now override all important config (RPC, explorer, gas, network gas, feature flags, etc.) directly from your app code, not just via environment variables. This enables full control for mobile/web apps and advanced use cases. #### How Hybrid Config Works - **Priority:**1*Mobile App Config** (passed to SDK at runtime) 2ironment Variables** (process.env) 3. **SDK Defaults** (hardcoded fallback) #### VaultConfig Interface ```typescript import { Vault, type VaultConfig } from 'pwc-wallet-sdk'; const config: VaultConfig = [object Object] rpcUrls:[object Object] 1https://your-eth-rpc.com', 56:https://your-bsc-rpc.com, }, explorerUrls:[object Object]1https://etherscan.io', 56: 'https://bscscan.com', }, gasConfig:[object Object] ERC20_TRANSFER:700, NATIVE_TRANSFER: 2200, BATCH_MULTIPLIER: 12 DEFAULT_PRIORITY_FEE: 200 }, networkGasConfig: { 1: { maxFeePerGas:400000000 maxPriorityFeePerGas: 150000}, 56: { maxFeePerGas: 30000000 maxPriorityFeePerGas: 500 }, }, defaultChainId: '1 features: [object Object] enableEIP1559: true, enableBatchProcessing: true, } }; ``` #### Usage Example (Recommended for Mobile Apps) ```typescript import { Vault, type VaultConfig } from 'pwc-wallet-sdk;// Define your app config once const appConfig: VaultConfig =[object Object] rpcUrls: { 1https://your-eth-rpc.com' }, gasConfig: { ERC20RANSFER: 7000 networkGasConfig: { '1: { maxFeePerGas: 400000n } }, }; // Pass config when creating new vault const [object Object] vault, encryptedVault } = await Vault.createNew(password', 'evm, appConfig); // Pass config when loading existing vault const vault = await Vault.load('password, encryptedVault, appConfig); // All operations will use the config automatically await vault.sendToken(from, to, amount, tokenAddress, '1'); ``` #### Alternative: Global Config (Advanced) You can also set config globally for all vaults: ```typescript import { setGlobalRPCConfig, setGlobalGasConfig, setGlobalNetworkGasConfig } from 'pwc-wallet-sdk'; setGlobalRPCConfig([object Object]1:https://my-eth-rpc.com });setGlobalGasConfig({ ERC20ANSFER: 70; setGlobalNetworkGasConfig({ '1: { maxFeePerGas: 4000 }); ``` #### Why Use Hybrid Config? - **Security:** Use your own RPC endpoints, not public ones - **Performance:** Optimize gas and network for your users - **Flexibility:** Switch between testnet/mainnet, or use custom chains - **Mobile/Web Ready:** No need to rely on process.env in mobile/web - **Simple:** Just pass config when creating/loading vault - no need to call global functions repeatedly ### Built-in Chains The SDK supports multiple blockchain networks out of the box: ```typescript import { SUPPORTED_CHAINS } from 'pwc-wallet-sdk'; // View all supported chains Object.entries(SUPPORTED_CHAINS).forEach(([chainId, config]) => { console.log(`${chainId}: ${config.name} (${config.type})`); }); ``` ### Custom Chain Management #### Override Built-in Chains ```typescript import { overrideChain } from 'pwc-wallet-sdk'; // Override Ethereum RPC URL overrideChain('1', { rpcUrl: 'https://my-eth-rpc.com' }); ``` #### Add Custom Chains ```typescript import { registerCustomChain } from 'pwc-wallet-sdk'; registerCustomChain('custom-1', { name: 'My Custom Chain', rpcUrl: 'https://my-custom-rpc.com', explorerUrl: 'https://my-explorer.com', nativeCurrency: { name: 'Token', symbol: 'TKN', decimals: 18 }, type: 'evm' }); ``` #### Setup All Chains at Once ```typescript import { setupChainConfigs } from 'pwc-wallet-sdk'; setupChainConfigs({ overrides: { '1': { rpcUrl: 'https://my-eth-rpc.com' }, '56': { rpcUrl: 'https://my-bsc-rpc.com' } }, customChains: { 'custom-1': { name: 'My Chain', rpcUrl: 'https://my-rpc.com', explorerUrl: 'https://my-explorer.com', nativeCurrency: { name: 'Token', symbol: 'TKN', decimals: 18 }, type: 'evm' } } }); ``` #### Chain Configuration Priority 1. **Custom chains** (highest priority) 2. **Overrides** (modify built-in chains) 3. **Built-in chains** (default configuration) ### Utility Functions ```typescript import { getChainConfig, getAllAvailableChains, clearCustomChains, clearOverrides } from 'pwc-wallet-sdk'; // Get chain configuration const config = getChainConfig('1'); // Get all available chains const allChains = getAllAvailableChains(); // Clear custom configurations clearCustomChains(); clearOverrides(); ``` ### Vanity Wallet Settings ```typescript import { VANITY_WALLET_CONFIG } from 'pwc-wallet-sdk'; // Default settings console.log(VANITY_WALLET_CONFIG.DEFAULT_PREFIX); // 'aaa' console.log(VANITY_WALLET_CONFIG.DEFAULT_MAX_ATTEMPTS); // 1000000 console.log(VANITY_WALLET_CONFIG.DEFAULT_CASE_SENSITIVE); // false ``` ### Supported Chains ```typescript import { SUPPORTED_CHAINS } from 'pwc-wallet-sdk'; // View all supported chains Object.entries(SUPPORTED_CHAINS).forEach(([chainId, config]) => { console.log(`${chainId}: ${config.name} (${config.type})`); }); ``` ## Advanced Features ### Gas Estimation ### Estimate Native Transfer Gas ```typescript const gasEstimate = await vault.estimateNativeTransferGas( fromAddress, toAddress, amount, '1' ); console.log('Estimated gas:', gasEstimate.toString()); console.log('Estimated cost (in ETH):', ethe