UNPKG

@luffalab/luffa-evm-sdk

Version:

luffa evm ts sdk

399 lines (303 loc) 10.1 kB
# Luffa EVM SDK ## Installation ```bash npm install @luffalab/luffa-evm-sdk ``` ## Quick Start ### Basic Initialization ```typescript import { LuffaEvmSdk } from '@luffalab/luffa-evm-sdk'; // Initialize SDK const sdk = new LuffaEvmSdk({ network: 'eth' // Supported: 'eth', 'eth_sepolia', 'bsc', 'bsc_test' }); // Check if running in Luffa wallet environment import { isLuffa } from '@luffalab/luffa-evm-sdk'; if (isLuffa()) { console.log('Running in Luffa wallet'); } ``` ## EIP-6963 Multi-Wallet Discovery Luffa EVM SDK implements the [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) standard for multi-wallet discovery, which is the recommended way to obtain wallet objects, superior to directly using `window.ethereum`. ### How It Works 1. SDK triggers `eip6963:announceProvider` event during initialization 2. When the page emits `eip6963:requestProvider` event, SDK triggers `eip6963:announceProvider` event again 3. DApps can listen to `eip6963:announceProvider` event to obtain wallet objects ### Usage Example ```typescript // Define received wallet provider type interface EIP6963ProviderDetail { info: { uuid: string; name: string; icon: string; rdns: string; }; provider: any; // Actually EIP-1193 compatible provider } // Store discovered wallet providers const discoveredProviders: EIP6963ProviderDetail[] = []; let luffaProvider = null; // The actual provider to use // Listen for wallet provider announcements window.addEventListener('eip6963:announceProvider', (event: CustomEvent<EIP6963ProviderDetail>) => { const { detail } = event; // Check if provider with same UUID already exists const exists = discoveredProviders.some(p => p.info.uuid === detail.info.uuid); if (!exists) { discoveredProviders.push(detail); // If this is Luffa wallet, store the provider if (detail.info.name === 'LuffaEvmWallet') { luffaProvider = detail.provider; } console.log(`Wallet discovered: ${detail.info.name}`); } }); // Request wallet providers window.dispatchEvent(new Event('eip6963:requestProvider')); ``` We strongly recommend DApp developers use the EIP-6963 standard to detect and connect wallets, rather than directly relying on the `window.ethereum` object. ## API Reference ### Wallet Connection Methods #### `eth_requestAccounts` Request user to connect wallet and return authorized account addresses. ```typescript const accounts = await luffaProvider.request({ method: 'eth_requestAccounts' }); console.log('Account address:', accounts[0]); ``` #### `eth_accounts` Get currently connected account addresses without triggering user authorization popup. ```typescript const accounts = await luffaProvider.request({ method: 'eth_accounts' }); if (accounts.length > 0) { console.log('Currently connected account:', accounts[0]); } else { console.log('No accounts connected'); } ``` ### Network Related Methods #### `eth_chainId` Get the current connected blockchain network ID. ```typescript const chainId = await luffaProvider.request({ method: 'eth_chainId' }); console.log('Current network ID:', chainId); // e.g.: "0x1" (Ethereum mainnet) ``` **Supported Networks:** | Network Name | Network ID | Chain ID (Hex) | Chain ID (Decimal) | |--------------|------------|----------------|-------------------| | Ethereum Mainnet | `eth` | `0x1` | 1 | | Ethereum Sepolia Testnet | `eth_sepolia` | `0xaa36a7` | 11155111 | | Binance Smart Chain Mainnet | `bsc` | `0x38` | 56 | | Binance Smart Chain Testnet | `bsc_test` | `0x61` | 97 | #### `wallet_switchEthereumChain` Request to switch to a specified blockchain network. ```typescript // Switch to BSC mainnet await luffaProvider.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: '0x38' }] }); console.log('Switch successful') ``` **Parameters:** ```typescript interface SwitchChainParams { chainId: string; // Chain ID in hexadecimal format } ``` ### Transaction Related Methods #### `eth_sendTransaction` Send Ethereum transactions. **Note: Current version only supports smart contract calls, does not support regular ETH transfers.** ##### Using ethers.js to Construct Contract Calls (Recommended) ```typescript import { ethers } from 'ethers'; // ERC20 token transfer example async function sendERC20Token() { // ERC20 contract ABI (only transfer method needed) const erc20ABI = [ "function transfer(address to, uint256 amount) returns (bool)" ]; const tokenAddress = '0xA0b86a33E6441e6e80D0c4C34F4f5FD4F4f5FD4F'; // Token contract address const recipientAddress = '0x742d35Cc6634C0532925a3b8D0C9e3e0C8b0e8c8'; const amount = ethers.parseUnits('100', 18); // 100 tokens (18 decimals) // Create contract interface const contract = new ethers.Interface(erc20ABI); // Encode function call data const data = contract.encodeFunctionData('transfer', [recipientAddress, amount]); // Send transaction const txHash = await window.ethereum.request({ method: 'eth_sendTransaction', params: [{ to: tokenAddress, data: data, gas: '0x186a0', // 100000 gasPrice: '0x09184e72a000' // 10 gwei }] }); console.log('ERC20 transfer transaction hash:', txHash); return txHash; } // ERC20 approval example async function approveERC20Token() { const erc20ABI = [ "function approve(address spender, uint256 amount) returns (bool)" ]; const tokenAddress = '0xA0b86a33E6441e6e80D0c4C34F4f5FD4F4f5FD4F'; const spenderAddress = '0x1234567890123456789012345678901234567890'; // Authorized address const amount = ethers.parseUnits('1000', 18); // Approve 1000 tokens const contract = new ethers.Interface(erc20ABI); const data = contract.encodeFunctionData('approve', [spenderAddress, amount]); const txHash = await window.ethereum.request({ method: 'eth_sendTransaction', params: [{ to: tokenAddress, data: data, gas: '0xea60', // 60000 gasPrice: '0x09184e72a000' }] }); console.log('ERC20 approval transaction hash:', txHash); return txHash; } // Call custom contract method async function callCustomContract() { // Custom contract ABI const customABI = [ "function setData(string memory _data, uint256 _value) payable" ]; const contractAddress = '0xYourContractAddress'; const contract = new ethers.Interface(customABI); // Encode function call const data = contract.encodeFunctionData('setData', [ 'Hello World', ethers.parseUnits('42', 0) // Integer 42 ]); const txHash = await window.ethereum.request({ method: 'eth_sendTransaction', params: [{ to: contractAddress, data: data, value: '0x16345785d8a0000', // 0.1 ETH (if function is payable) gas: '0x30d40', // 200000 gasPrice: '0x09184e72a000' }] }); console.log('Custom contract call transaction hash:', txHash); return txHash; } ``` #### `personal_sign` Sign messages for personal use, commonly used for identity verification. ```typescript const message = 'Hello, Luffa Wallet!'; const signature = await luffaProvider.request({ method: 'personal_sign', params: [message] }); console.log('Signature result:', signature); ``` ### Disconnect #### `wallet_revokePermissions` Disconnect from the wallet. ```typescript await luffaProvider.request({ method: 'wallet_revokePermissions' }); console.log('Disconnected'); ``` ## Event Listening SDK supports listening to wallet state change events, consistent with MetaMask. ### Account Change Events ```typescript // Listen for account changes luffaProvider.on('accountsChanged', (accounts) => { console.log('Accounts changed:', accounts); if (accounts.length === 0) { console.log('User disconnected'); } else { console.log('Current account:', accounts[0]); } }); ``` ### Network Change Events ```typescript // Listen for network changes luffaProvider.on('chainChanged', (chainId) => { console.log('Network switched to:', chainId); }); ``` ### Remove Event Listeners ```typescript // Remove specific listener const handleAccountsChanged = (accounts) => { console.log('Account change:', accounts); }; luffaProvider.on('accountsChanged', handleAccountsChanged); luffaProvider.removeListener('accountsChanged', handleAccountsChanged); ``` ## FAQ ### Q: How to detect if user is in Luffa wallet? ```typescript import { isLuffa } from '@luffalab/luffa-evm-sdk'; if (isLuffa()) { // In Luffa wallet } else { // Not in Luffa wallet, show guide page } ``` ### Q: How to handle network switching? ```typescript // Listen for network changes luffaProvider.on('chainChanged', (chainId) => { // Network switched, update application state window.location.reload(); // Simple handling approach }); // Actively switch network try { await luffaProvider.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: '0x38' }] // BSC mainnet }); } catch (error) { if (error.code === 4001) { console.log('User rejected network switch'); } else if (error.code === -32602) { console.log('Invalid chain ID'); } else { console.log('Network switch failed:', error.message); } } ``` ### Q: How to handle user disconnection? ```typescript luffaProvider.on('accountsChanged', (accounts) => { if (accounts.length === 0) { // User disconnected // Clean application state, redirect to connection page localStorage.removeItem('userAccount'); window.location.href = '/connect'; } }); ``` ### Q: How to verify signatures? ```typescript // Frontend signing const message = 'Hello World'; const signature = await luffaProvider.request({ method: 'personal_sign', params: [message] }); // Backend verification (Node.js example) const ethers = require('ethers'); function verifySignature(message, signature, expectedAddress) { const recoveredAddress = ethers.utils.verifyMessage(message, signature); return recoveredAddress.toLowerCase() === expectedAddress.toLowerCase(); } ``` ## Support For questions or suggestions, please contact the Luffa team or submit an Issue on GitHub.