From 5dbec74a1ef9991acad84fc96ef8120b051b12f1 Mon Sep 17 00:00:00 2001 From: Yashovardhan Agrawal <21066442+yashovardhan@users.noreply.github.com> Date: Thu, 11 Sep 2025 14:28:36 +0400 Subject: [PATCH 1/3] temp --- embedded-wallets/sdk/node/README.mdx | 483 +++++++++++ embedded-wallets/sdk/node/examples.mdx | 799 ++++++++++++++++++ embedded-wallets/sdk/node/usage/README.mdx | 102 +++ embedded-wallets/sdk/node/usage/connect.mdx | 482 +++++++++++ .../sdk/node/usage/evm-integration.mdx | 433 ++++++++++ .../sdk/node/usage/private-key.mdx | 316 +++++++ src/components/EWSDKCards/index.tsx | 21 + 7 files changed, 2636 insertions(+) create mode 100644 embedded-wallets/sdk/node/README.mdx create mode 100644 embedded-wallets/sdk/node/examples.mdx create mode 100644 embedded-wallets/sdk/node/usage/README.mdx create mode 100644 embedded-wallets/sdk/node/usage/connect.mdx create mode 100644 embedded-wallets/sdk/node/usage/evm-integration.mdx create mode 100644 embedded-wallets/sdk/node/usage/private-key.mdx diff --git a/embedded-wallets/sdk/node/README.mdx b/embedded-wallets/sdk/node/README.mdx new file mode 100644 index 00000000000..40b48b83a05 --- /dev/null +++ b/embedded-wallets/sdk/node/README.mdx @@ -0,0 +1,483 @@ +--- +title: Embedded Wallets SDK for Node.js +sidebar_label: Getting Started +description: 'MetaMask Embedded Wallets SDK for Node.js | Backend Documentation' +--- + +import TabItem from '@theme/TabItem' +import Tabs from '@theme/Tabs' +import SdkTroubleshootingIntro from '../_common/_sdk-troubleshooting-intro.mdx' + +## Overview + +The MetaMask Embedded Wallets Node.js SDK (formerly Web3Auth Node SDK) is a backend solution designed for server-side authentication and key management. This SDK enables seamless integration of Web3 authentication into backend applications, AI agents, and programmatic use cases. + +Unlike frontend SDKs, the Node.js SDK is **stateless and sessionless**, making it ideal for: + +- Backend AI agents +- Server-side wallet operations +- Programmatic blockchain interactions +- Custodial wallet services + +## Key Features + +- **Stateless Architecture**: No session management required +- **Multi-Chain Support**: EVM chains, Solana, and other blockchains +- **Custom Authentication**: Mandatory custom auth with single key share +- **Private Key Access**: Direct access to private keys for any blockchain +- **Backend-Optimized**: Designed specifically for server environments + +## Requirements + +- Node.js 18+ +- Custom authentication setup (mandatory) +- Web3Auth Dashboard project configuration + +## Installation + +Install the Web3Auth Node SDK and required providers: + +```bash npm2yarn +npm install --save @web3auth/node-sdk +``` + +### Additional Dependencies + +For blockchain providers: + +```bash npm2yarn +# For EVM chains (Ethereum, Polygon, etc.) +npm install --save @web3auth/ethereum-provider + +# For Solana blockchain +npm install --save @web3auth/solana-provider + +# Base package for types +npm install --save @web3auth/base +``` + +## Setup + +:::info Prerequisites + +Before you start, make sure you have: + +1. Registered on the [**Web3Auth Dashboard**](https://dashboard.web3auth.io/) +2. Set up a project with **custom authentication** (mandatory for Node.js SDK) +3. Configured your custom verifier settings + +You can refer to the [Dashboard Setup](/embedded-wallets/dashboard/) guide to learn more. + +::: + +### 1. Custom Verifier Setup (Required) + +The Node.js SDK **only supports custom authentication**. You must create a custom verifier in the Web3Auth Dashboard: + +1. Go to [Web3Auth Dashboard](https://dashboard.web3auth.io/) +2. Select your project +3. Navigate to **Auth Connectors** → **Custom Authentication** +4. Click **Create Verifier** +5. Configure your verifier with these settings: + +```json +{ + "name": "my-backend-verifier", + "description": "Custom verifier for Node.js backend", + "verifierType": "custom", + "clientId": "your-auth-client-id", + "issuer": "https://your-auth-domain.com", + "jwks_endpoint": "https://your-auth-domain.com/.well-known/jwks.json" +} +``` + +### 2. JWT Token Requirements + +Your authentication system must generate JWT tokens with these required claims: + +```json +{ + "iss": "https://your-auth-domain.com", // Issuer (must match verifier) + "aud": "your-web3auth-client-id", // Audience + "sub": "user-unique-identifier", // Subject (user ID) + "iat": 1640995200, // Issued at + "exp": 1641081600, // Expiration + "email": "user@example.com", // User email (optional) + "name": "John Doe" // User name (optional) +} +``` + +### 3. Provider Configuration + +Configure the blockchain provider for your target network: + +```javascript +const { Web3Auth } = require('@web3auth/node-sdk') +const { EthereumPrivateKeyProvider } = require('@web3auth/ethereum-provider') + +// Configure Ethereum provider +const privateKeyProvider = new EthereumPrivateKeyProvider({ + config: { + chainConfig: { + chainNamespace: 'eip155', + chainId: '0x1', // Ethereum Mainnet + rpcTarget: 'https://rpc.ankr.com/eth', + displayName: 'Ethereum Mainnet', + blockExplorerUrl: 'https://etherscan.io/', + ticker: 'ETH', + tickerName: 'Ethereum', + }, + }, +}) +``` + +### 4. SDK Configuration + +Create a Web3Auth instance with your client ID, network configuration, and provider: + +```javascript +const web3auth = new Web3Auth({ + clientId: 'YOUR_CLIENT_ID', // Get your Client ID from Web3Auth Dashboard + web3AuthNetwork: 'sapphire_mainnet', // or 'sapphire_devnet' + privateKeyProvider, +}) +``` + +### 5. Initialize Web3Auth + +Initialize the Web3Auth instance during your application startup: + +```javascript +await web3auth.init() +``` + +### 6. Authenticate Users + +Use the connect method with your custom authentication parameters: + +```javascript +const provider = await web3auth.connect({ + verifier: 'YOUR_VERIFIER_NAME', // Your custom verifier name + verifierId: 'USER_VERIFIER_ID', // User's unique identifier + idToken: 'USER_ID_TOKEN', // JWT token from your auth system +}) +``` + +## Configuration Options + + + + + +```javascript +const { Web3Auth } = require('@web3auth/node-sdk') + +const web3auth = new Web3Auth({ + clientId: 'YOUR_CLIENT_ID', + web3AuthNetwork: 'sapphire_mainnet', // or 'sapphire_devnet' +}) + +await web3auth.init() +``` + + + + + +```javascript +const { Web3Auth } = require('@web3auth/node-sdk') +const { EthereumPrivateKeyProvider } = require('@web3auth/ethereum-provider') + +// Configure provider +const privateKeyProvider = new EthereumPrivateKeyProvider({ + config: { + chainConfig: { + chainNamespace: 'eip155', + chainId: process.env.CHAIN_ID || '0x1', + rpcTarget: process.env.RPC_URL || 'https://rpc.ankr.com/eth', + displayName: 'Ethereum', + blockExplorerUrl: 'https://etherscan.io/', + ticker: 'ETH', + tickerName: 'Ethereum', + }, + }, +}) + +// Production configuration +const web3auth = new Web3Auth({ + clientId: process.env.WEB3AUTH_CLIENT_ID, + web3AuthNetwork: process.env.NODE_ENV === 'production' ? 'sapphire_mainnet' : 'sapphire_devnet', + privateKeyProvider, +}) + +await web3auth.init() +``` + + + + + +```javascript +// Provider configuration +const privateKeyProvider = new EthereumPrivateKeyProvider({ + config: { + chainConfig: { + chainNamespace: 'eip155', + chainId: '0x1', + rpcTarget: 'https://rpc.ankr.com/eth', + displayName: 'Ethereum', + blockExplorerUrl: 'https://etherscan.io/', + ticker: 'ETH', + tickerName: 'Ethereum', + }, + }, +}) + +// Development +const devConfig = { + clientId: 'dev-client-id', + web3AuthNetwork: 'sapphire_devnet', + privateKeyProvider, +} + +// Production +const prodConfig = { + clientId: process.env.WEB3AUTH_CLIENT_ID, + web3AuthNetwork: 'sapphire_mainnet', + privateKeyProvider, +} + +// Testing +const testConfig = { + clientId: 'test-client-id', + web3AuthNetwork: 'sapphire_devnet', + privateKeyProvider, +} + +const web3auth = new Web3Auth(process.env.NODE_ENV === 'production' ? prodConfig : devConfig) +``` + + + + + +## Configuration Parameters + +### Web3Auth Configuration + +| Parameter | Type | Default | Description | +| -------------------- | -------- | -------- | --------------------------------------------------- | +| `clientId` | `string` | Required | Your Web3Auth client ID | +| `web3AuthNetwork` | `string` | Required | Network: 'sapphire_mainnet' or 'sapphire_devnet' | +| `privateKeyProvider` | `object` | Required | Blockchain provider configuration (Ethereum/Solana) | + +### Provider Configuration Parameters + +| Parameter | Type | Description | +| ------------------ | -------- | ------------------------------------------------------- | +| `chainNamespace` | `string` | Blockchain namespace ('eip155' for EVM, 'solana') | +| `chainId` | `string` | Chain ID (e.g., '0x1' for Ethereum, '0x89' for Polygon) | +| `rpcTarget` | `string` | RPC endpoint URL for blockchain communication | +| `displayName` | `string` | Human-readable chain name | +| `blockExplorerUrl` | `string` | Block explorer URL for the chain | +| `ticker` | `string` | Native token symbol (e.g., 'ETH', 'SOL') | +| `tickerName` | `string` | Full name of the native token | + +## Best Practices + +### Environment Variables + +Store sensitive configuration in environment variables: + +```bash +# .env file +WEB3AUTH_CLIENT_ID=your_client_id_here +WEB3AUTH_NETWORK=sapphire_mainnet +SESSION_TIMEOUT=3600 +ENABLE_LOGGING=false +``` + +### Configuration Validation + +```javascript +function validateConfig(config) { + if (!config.clientId) { + throw new Error('Client ID is required') + } + + if (!['sapphire_mainnet', 'sapphire_devnet'].includes(config.web3AuthNetwork)) { + throw new Error('Invalid Web3Auth network') + } + + if (config.sessionTime < 300) { + throw new Error('Session time must be at least 5 minutes') + } + + return config +} + +const config = validateConfig({ + clientId: process.env.WEB3AUTH_CLIENT_ID, + web3AuthNetwork: process.env.WEB3AUTH_NETWORK, + sessionTime: parseInt(process.env.SESSION_TIMEOUT), +}) +``` + +### Dynamic Configuration Manager + +```javascript +class Web3AuthConfigManager { + constructor() { + this.config = this.loadConfig() + } + + loadConfig() { + const baseConfig = { + clientId: process.env.WEB3AUTH_CLIENT_ID, + web3AuthNetwork: process.env.WEB3AUTH_NETWORK || 'sapphire_devnet', + usePnPKey: false, + } + + // Environment-specific overrides + switch (process.env.NODE_ENV) { + case 'production': + return { + ...baseConfig, + enableLogging: false, + sessionTime: 86400, + } + case 'staging': + return { + ...baseConfig, + enableLogging: true, + sessionTime: 3600, + } + default: + return { + ...baseConfig, + enableLogging: true, + sessionTime: 1800, + } + } + } + + getConfig() { + return this.config + } + + updateConfig(updates) { + this.config = { ...this.config, ...updates } + } +} + +// Usage +const configManager = new Web3AuthConfigManager() +const web3auth = new Web3Auth(configManager.getConfig()) +``` + +## Blockchain Integration + +Web3Auth Node SDK supports multiple blockchain networks through different integration methods: + +### EVM Chains (Ethereum, Polygon, BSC, etc.) + +Use the provider with ethers.js or convert to viem: + +```javascript +// With ethers.js +const { ethers } = require('ethers') +const ethProvider = new ethers.providers.Web3Provider(provider) +const signer = ethProvider.getSigner() + +// Get private key directly +const privateKey = await provider.request({ method: 'eth_private_key' }) +``` + +### Solana Integration + +Access Solana wallet functionality: + +```javascript +// Get Solana account info +const solanaWallet = await provider.request({ method: 'solanaWallet' }) +const publicKey = solanaWallet.publicKey + +// Get private key for Solana +const privateKey = await provider.request({ method: 'solanaPrivateKey' }) +``` + +### Other Blockchains + +Access the raw private key for any blockchain integration: + +```javascript +// Get the raw private key +const privateKey = await provider.request({ method: 'private_key' }) + +// Use with your preferred blockchain library +// Example: Bitcoin, Cosmos, etc. +``` + +## Key Features + +### Custom Authentication Only + +The Node.js SDK **only supports custom authentication**. You must: + +1. Set up a custom verifier in the Web3Auth Dashboard +2. Configure your authentication flow +3. Generate valid ID tokens for users +4. Use the verifier name and ID token in the connect method + +### Single Key Share + +The SDK operates with a single key share, making it: + +- **Custodial**: You have direct access to user private keys +- **Stateless**: No session state management required +- **Backend-optimized**: Perfect for server-side operations + +### Private Key Export + +Private key export can be controlled through the Web3Auth Dashboard: + +- Enable/disable private key export +- Control which methods return private keys +- Set up additional security measures + +## Security Considerations + +### Client ID Protection + +- Store client ID in environment variables +- Use different client IDs for different environments +- Rotate client IDs regularly in production + +### Network Configuration + +- Use `sapphire_mainnet` for production +- Use `sapphire_devnet` for development and testing +- Never use mainnet for testing + +### Session Management + +- Set appropriate session timeouts +- Implement session cleanup +- Monitor for unusual session patterns + +## Next Steps + +- **[Usage Guide](./usage)**: Learn about stateless authentication and blockchain operations +- **[Connect Method](./usage/connect)**: Detailed authentication implementation +- **[Private Key Access](./usage/private-key)**: Extract keys for blockchain operations +- **[EVM Integration](./usage/evm-integration)**: Ethereum and EVM-compatible chains +- **[Examples](./examples)**: Complete implementation examples and production patterns + + diff --git a/embedded-wallets/sdk/node/examples.mdx b/embedded-wallets/sdk/node/examples.mdx new file mode 100644 index 00000000000..19ae77adfa0 --- /dev/null +++ b/embedded-wallets/sdk/node/examples.mdx @@ -0,0 +1,799 @@ +--- +title: Examples and Use Cases +description: 'Complete examples and use cases for the MetaMask Embedded Wallets Node.js SDK' +--- + +## Overview + +This section provides complete, production-ready examples for common use cases with the Node.js SDK. Each example includes error handling, security considerations, and best practices. + +## AI Agent Examples + +### 1. Autonomous Trading Bot + +```javascript +const { Web3Auth } = require('@web3auth/node-sdk') +const { ethers } = require('ethers') + +class TradingBot { + constructor(config) { + this.web3auth = new Web3Auth({ + clientId: config.clientId, + web3AuthNetwork: 'sapphire_mainnet', + }) + + this.config = config + this.wallet = null + this.provider = null + } + + async initialize() { + await this.web3auth.init() + + // Authenticate the bot + const provider = await this.web3auth.connect({ + verifier: this.config.verifier, + verifierId: this.config.botId, + idToken: this.config.botToken, + }) + + // Set up wallet + const privateKey = await provider.request({ method: 'eth_private_key' }) + this.wallet = new ethers.Wallet(privateKey) + this.provider = new ethers.providers.JsonRpcProvider(this.config.rpcUrl) + this.wallet = this.wallet.connect(this.provider) + + console.log('Trading bot initialized:', this.wallet.address) + } + + async executeTradeStrategy() { + try { + const balance = await this.wallet.getBalance() + console.log('Current balance:', ethers.utils.formatEther(balance), 'ETH') + + // Example: Simple DEX swap logic + const shouldTrade = await this.analyzeMarket() + + if (shouldTrade) { + await this.executeTrade(shouldTrade) + } + } catch (error) { + console.error('Trade execution failed:', error) + await this.handleTradingError(error) + } + } + + async analyzeMarket() { + // Implement your trading logic here + // This is a simplified example + const price = await this.getCurrentPrice('ETH/USDC') + const trend = await this.getTrend() + + return { + action: 'buy', + amount: '0.1', + token: 'USDC', + confidence: 0.8, + } + } + + async executeTrade(tradeParams) { + console.log('Executing trade:', tradeParams) + + // Example DEX interaction (Uniswap V3) + const uniswapRouter = new ethers.Contract( + '0xE592427A0AEce92De3Edee1F18E0157C05861564', + [ + 'function exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) external payable returns (uint256)', + ], + this.wallet + ) + + const params = { + tokenIn: '0xA0b86a33E6441E51DBF5c4dF02a7b29fAdab0215', // USDC + tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH + fee: 3000, + recipient: this.wallet.address, + deadline: Math.floor(Date.now() / 1000) + 600, + amountIn: ethers.utils.parseUnits(tradeParams.amount, 6), + amountOutMinimum: 0, + sqrtPriceLimitX96: 0, + } + + const tx = await uniswapRouter.exactInputSingle(params) + await tx.wait() + + console.log('Trade completed:', tx.hash) + } + + async getCurrentPrice(pair) { + // Implement price fetching logic + return 2000 // Simplified + } + + async getTrend() { + // Implement trend analysis + return 'bullish' + } + + async handleTradingError(error) { + // Implement error handling and notifications + console.log('Notifying administrators of trading error') + } +} + +// Usage +const bot = new TradingBot({ + clientId: process.env.WEB3AUTH_CLIENT_ID, + verifier: 'trading-bot-verifier', + botId: 'bot-001', + botToken: process.env.BOT_JWT_TOKEN, + rpcUrl: 'https://rpc.ankr.com/eth', +}) + +await bot.initialize() + +// Run trading loop +setInterval(async () => { + await bot.executeTradeStrategy() +}, 60000) // Every minute +``` + +### 2. DeFi Yield Farming Bot + +```javascript +class YieldFarmingBot { + constructor(config) { + this.web3auth = new Web3Auth(config.web3auth) + this.config = config + this.protocols = new Map() // Store protocol interfaces + } + + async initialize() { + await this.web3auth.init() + + const provider = await this.web3auth.connect({ + verifier: this.config.verifier, + verifierId: this.config.botId, + idToken: this.config.botToken, + }) + + const privateKey = await provider.request({ method: 'eth_private_key' }) + this.wallet = new ethers.Wallet(privateKey).connect( + new ethers.providers.JsonRpcProvider(this.config.rpcUrl) + ) + + // Initialize protocol interfaces + await this.initializeProtocols() + } + + async initializeProtocols() { + // Compound + this.protocols.set('compound', { + comptroller: new ethers.Contract( + '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', + ['function enterMarkets(address[] calldata cTokens) external returns (uint[] memory)'], + this.wallet + ), + // Add other protocol contracts + }) + + // Aave + this.protocols.set('aave', { + lendingPool: new ethers.Contract( + '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', + [ + 'function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)', + ], + this.wallet + ), + }) + } + + async optimizeYield() { + try { + // 1. Analyze current positions + const positions = await this.getCurrentPositions() + + // 2. Find best yield opportunities + const opportunities = await this.scanYieldOpportunities() + + // 3. Execute rebalancing if profitable + const rebalancePlan = this.calculateOptimalRebalance(positions, opportunities) + + if (rebalancePlan.profitable) { + await this.executeRebalance(rebalancePlan) + } + } catch (error) { + console.error('Yield optimization failed:', error) + } + } + + async getCurrentPositions() { + const positions = [] + + // Check Compound positions + // Check Aave positions + // Check Uniswap LP positions + + return positions + } + + async scanYieldOpportunities() { + const opportunities = [] + + // Scan lending rates + const compoundRate = await this.getCompoundAPY('USDC') + const aaveRate = await this.getAaveAPY('USDC') + + opportunities.push( + { protocol: 'compound', asset: 'USDC', apy: compoundRate }, + { protocol: 'aave', asset: 'USDC', apy: aaveRate } + ) + + return opportunities.sort((a, b) => b.apy - a.apy) + } + + calculateOptimalRebalance(positions, opportunities) { + // Implement rebalancing logic + return { + profitable: true, + moves: [{ from: 'compound', to: 'aave', asset: 'USDC', amount: '1000' }], + expectedGain: 0.05, // 5% APY improvement + } + } + + async executeRebalance(plan) { + for (const move of plan.moves) { + console.log(`Moving ${move.amount} ${move.asset} from ${move.from} to ${move.to}`) + + // Withdraw from source + await this.withdrawFromProtocol(move.from, move.asset, move.amount) + + // Deposit to destination + await this.depositToProtocol(move.to, move.asset, move.amount) + } + } + + async getCompoundAPY(asset) { + // Implement Compound APY fetching + return 3.5 // 3.5% APY + } + + async getAaveAPY(asset) { + // Implement Aave APY fetching + return 4.2 // 4.2% APY + } +} +``` + +## Backend Service Examples + +### 1. User Wallet Service + +```javascript +const express = require('express') +const { Web3Auth } = require('@web3auth/node-sdk') + +class UserWalletService { + constructor() { + this.app = express() + this.userSessions = new Map() + this.setupMiddleware() + this.setupRoutes() + } + + setupMiddleware() { + this.app.use(express.json()) + this.app.use(this.authMiddleware.bind(this)) + this.app.use(this.rateLimitMiddleware.bind(this)) + } + + async authMiddleware(req, res, next) { + if (req.path.startsWith('/api/public/')) { + return next() + } + + const token = req.headers.authorization?.replace('Bearer ', '') + if (!token) { + return res.status(401).json({ error: 'Authentication required' }) + } + + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET) + req.user = decoded + next() + } catch (error) { + res.status(401).json({ error: 'Invalid token' }) + } + } + + rateLimitMiddleware(req, res, next) { + // Implement rate limiting + next() + } + + setupRoutes() { + // Create wallet for user + this.app.post('/api/wallet/create', async (req, res) => { + try { + const userId = req.user.sub + const { email, name } = req.user + + // Check if wallet already exists + if (this.userSessions.has(userId)) { + return res.status(400).json({ error: 'Wallet already exists' }) + } + + // Create Web3Auth instance for user + const web3auth = new Web3Auth({ + clientId: process.env.WEB3AUTH_CLIENT_ID, + web3AuthNetwork: 'sapphire_mainnet', + }) + + await web3auth.init() + + // Create JWT for user + const idToken = this.createJWTForUser(userId, email, name) + + // Connect with Web3Auth + const provider = await web3auth.connect({ + verifier: 'wallet-service-verifier', + verifierId: userId, + idToken: idToken, + }) + + // Get wallet address + const accounts = await provider.request({ method: 'eth_accounts' }) + const address = accounts[0] + + // Store session + this.userSessions.set(userId, { + web3auth, + provider, + address, + createdAt: new Date(), + }) + + res.json({ + success: true, + address: address, + message: 'Wallet created successfully', + }) + } catch (error) { + console.error('Wallet creation failed:', error) + res.status(500).json({ error: 'Failed to create wallet' }) + } + }) + + // Get wallet info + this.app.get('/api/wallet/info', async (req, res) => { + try { + const userId = req.user.sub + const session = this.userSessions.get(userId) + + if (!session) { + return res.status(404).json({ error: 'Wallet not found' }) + } + + // Get balance + const balance = await session.provider.request({ + method: 'eth_getBalance', + params: [session.address, 'latest'], + }) + + res.json({ + address: session.address, + balance: ethers.utils.formatEther(balance), + createdAt: session.createdAt, + }) + } catch (error) { + console.error('Failed to get wallet info:', error) + res.status(500).json({ error: 'Failed to get wallet info' }) + } + }) + + // Send transaction + this.app.post('/api/wallet/send', async (req, res) => { + try { + const userId = req.user.sub + const { to, amount, data } = req.body + const session = this.userSessions.get(userId) + + if (!session) { + return res.status(404).json({ error: 'Wallet not found' }) + } + + // Validate transaction + if (!ethers.utils.isAddress(to)) { + return res.status(400).json({ error: 'Invalid recipient address' }) + } + + // Create transaction + const tx = { + to: to, + value: ethers.utils.parseEther(amount), + data: data || '0x', + } + + // Sign and send transaction + const txHash = await session.provider.request({ + method: 'eth_sendTransaction', + params: [tx], + }) + + res.json({ + success: true, + transactionHash: txHash, + message: 'Transaction sent successfully', + }) + } catch (error) { + console.error('Transaction failed:', error) + res.status(500).json({ error: 'Transaction failed' }) + } + }) + + // Sign message + this.app.post('/api/wallet/sign', async (req, res) => { + try { + const userId = req.user.sub + const { message } = req.body + const session = this.userSessions.get(userId) + + if (!session) { + return res.status(404).json({ error: 'Wallet not found' }) + } + + const signature = await session.provider.request({ + method: 'personal_sign', + params: [message, session.address], + }) + + res.json({ + signature: signature, + message: message, + address: session.address, + }) + } catch (error) { + console.error('Message signing failed:', error) + res.status(500).json({ error: 'Failed to sign message' }) + } + }) + + // Delete wallet + this.app.delete('/api/wallet', async (req, res) => { + try { + const userId = req.user.sub + const session = this.userSessions.get(userId) + + if (session) { + // Stateless SDK - no logout needed, just remove from memory + this.userSessions.delete(userId) + } + + res.json({ + success: true, + message: 'Wallet deleted successfully', + }) + } catch (error) { + console.error('Wallet deletion failed:', error) + res.status(500).json({ error: 'Failed to delete wallet' }) + } + }) + } + + createJWTForUser(userId, email, name) { + const payload = { + iss: process.env.JWT_ISSUER, + aud: process.env.WEB3AUTH_CLIENT_ID, + sub: userId, + email: email, + name: name, + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 3600, + } + + return jwt.sign(payload, process.env.JWT_SECRET) + } + + start(port = 3000) { + this.app.listen(port, () => { + console.log(`User wallet service running on port ${port}`) + }) + } +} + +// Usage +const walletService = new UserWalletService() +walletService.start() +``` + +### 2. Multi-Chain Portfolio Manager + +```javascript +class PortfolioManager { + constructor(config) { + this.config = config + this.chains = new Map() + this.web3auth = new Web3Auth(config.web3auth) + } + + async initialize() { + await this.web3auth.init() + + // Initialize multiple chains + const chainConfigs = [ + { id: '0x1', name: 'Ethereum', rpc: 'https://rpc.ankr.com/eth' }, + { id: '0x89', name: 'Polygon', rpc: 'https://rpc.ankr.com/polygon' }, + { id: '0x38', name: 'BSC', rpc: 'https://bsc-dataseed.binance.org' }, + ] + + for (const chain of chainConfigs) { + await this.initializeChain(chain) + } + } + + async initializeChain(chainConfig) { + // Connect to chain + const provider = await this.web3auth.connect({ + verifier: this.config.verifier, + verifierId: this.config.userId, + idToken: this.config.userToken, + chainId: chainConfig.id, + }) + + const privateKey = await provider.request({ method: 'eth_private_key' }) + const wallet = new ethers.Wallet(privateKey).connect( + new ethers.providers.JsonRpcProvider(chainConfig.rpc) + ) + + this.chains.set(chainConfig.id, { + config: chainConfig, + provider, + wallet, + }) + } + + async getPortfolioSummary() { + const summary = { + totalValue: 0, + chains: {}, + tokens: {}, + } + + for (const [chainId, chain] of this.chains) { + const chainSummary = await this.getChainSummary(chainId) + summary.chains[chainId] = chainSummary + summary.totalValue += chainSummary.totalValue + } + + return summary + } + + async getChainSummary(chainId) { + const chain = this.chains.get(chainId) + const summary = { + name: chain.config.name, + nativeBalance: 0, + tokens: [], + totalValue: 0, + } + + // Get native token balance + const balance = await chain.wallet.getBalance() + summary.nativeBalance = parseFloat(ethers.utils.formatEther(balance)) + + // Get token balances (implement token detection) + const tokens = await this.getTokenBalances(chainId) + summary.tokens = tokens + + // Calculate total value (implement price fetching) + summary.totalValue = await this.calculateChainValue(summary) + + return summary + } + + async getTokenBalances(chainId) { + // Implement token balance fetching + // This would integrate with token lists and balance checkers + return [] + } + + async calculateChainValue(chainSummary) { + // Implement value calculation with price feeds + return chainSummary.nativeBalance * 2000 // Simplified + } + + async rebalancePortfolio(targetAllocations) { + // Implement cross-chain rebalancing logic + for (const allocation of targetAllocations) { + console.log(`Rebalancing ${allocation.asset} to ${allocation.percentage}%`) + // Execute rebalancing trades + } + } +} +``` + +## Production Deployment Example + +### Docker Configuration + +```dockerfile +# Dockerfile +FROM node:18-alpine + +WORKDIR /app + +# Copy package files +COPY package*.json ./ +RUN npm ci --only=production + +# Copy source code +COPY src/ ./src/ + +# Create non-root user +RUN addgroup -g 1001 -S nodejs +RUN adduser -S web3auth -u 1001 +USER web3auth + +EXPOSE 3000 + +CMD ["node", "src/index.js"] +``` + +### Environment Configuration + +```yaml +# docker-compose.yml +version: '3.8' + +services: + web3auth-service: + build: . + ports: + - '3000:3000' + environment: + - NODE_ENV=production + - WEB3AUTH_CLIENT_ID=${WEB3AUTH_CLIENT_ID} + - JWT_SECRET=${JWT_SECRET} + - JWT_ISSUER=${JWT_ISSUER} + - REDIS_URL=${REDIS_URL} + depends_on: + - redis + restart: unless-stopped + + redis: + image: redis:7-alpine + ports: + - '6379:6379' + volumes: + - redis_data:/data + restart: unless-stopped + +volumes: + redis_data: +``` + +### Kubernetes Deployment + +```yaml +# deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: web3auth-service +spec: + replicas: 3 + selector: + matchLabels: + app: web3auth-service + template: + metadata: + labels: + app: web3auth-service + spec: + containers: + - name: web3auth-service + image: your-registry/web3auth-service:latest + ports: + - containerPort: 3000 + env: + - name: WEB3AUTH_CLIENT_ID + valueFrom: + secretKeyRef: + name: web3auth-secrets + key: client-id + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: web3auth-secrets + key: jwt-secret + resources: + requests: + memory: '256Mi' + cpu: '250m' + limits: + memory: '512Mi' + cpu: '500m' + livenessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /ready + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 +``` + +## Monitoring and Observability + +```javascript +const promClient = require('prom-client') + +class Web3AuthMetrics { + constructor() { + // Create metrics + this.authAttempts = new promClient.Counter({ + name: 'web3auth_authentication_attempts_total', + help: 'Total number of authentication attempts', + labelNames: ['status', 'verifier'], + }) + + this.authDuration = new promClient.Histogram({ + name: 'web3auth_authentication_duration_seconds', + help: 'Duration of authentication attempts', + labelNames: ['verifier'], + }) + + this.activeConnections = new promClient.Gauge({ + name: 'web3auth_active_connections', + help: 'Number of active Web3Auth connections', + }) + + // Register default metrics + promClient.register.setDefaultLabels({ + app: 'web3auth-service', + version: process.env.APP_VERSION || '1.0.0', + }) + + promClient.collectDefaultMetrics() + } + + recordAuthAttempt(status, verifier) { + this.authAttempts.inc({ status, verifier }) + } + + recordAuthDuration(duration, verifier) { + this.authDuration.observe({ verifier }, duration) + } + + setActiveConnections(count) { + this.activeConnections.set(count) + } + + getMetrics() { + return promClient.register.metrics() + } +} + +// Usage in your service +const metrics = new Web3AuthMetrics() + +// In your authentication handler +const startTime = Date.now() +try { + const provider = await web3auth.connect(loginParams) + metrics.recordAuthAttempt('success', loginParams.verifier) +} catch (error) { + metrics.recordAuthAttempt('failure', loginParams.verifier) +} finally { + const duration = (Date.now() - startTime) / 1000 + metrics.recordAuthDuration(duration, loginParams.verifier) +} +``` + +These examples provide a solid foundation for implementing the Web3Auth Node.js SDK in various production scenarios. Each example includes error handling, security considerations, and monitoring capabilities essential for backend services. diff --git a/embedded-wallets/sdk/node/usage/README.mdx b/embedded-wallets/sdk/node/usage/README.mdx new file mode 100644 index 00000000000..d85f92c634d --- /dev/null +++ b/embedded-wallets/sdk/node/usage/README.mdx @@ -0,0 +1,102 @@ +--- +title: Node.js SDK Usage +sidebar_label: Overview +description: 'Learn how to use the MetaMask Embedded Wallets Node.js SDK' +--- + +## Usage Overview + +The MetaMask Embedded Wallets Node.js SDK provides stateless authentication and blockchain operations for backend applications. Unlike frontend SDKs, this is designed for **per-request authentication** without persistent sessions. + +## Core Methods + +### Authentication & Connection + +- **[connect](./connect)** - Authenticate users and establish provider connection (stateless) + +### Blockchain Operations + +- **[Private Key Access](./private-key)** - Extract private keys for blockchain operations +- **[EVM Integration](./evm-integration)** - Work with Ethereum and EVM-compatible chains + +## Important Notes + +### Stateless Design + +The Node.js SDK is **stateless** and **sessionless** by design: + +- No persistent user sessions +- No `getUserInfo()` or `logout()` methods +- Each request requires re-authentication +- Perfect for backend APIs and microservices + +## Common Usage Pattern + +The typical flow when using the Node.js SDK: + +```javascript +const { Web3Auth } = require('@web3auth/node-sdk') +const { EthereumPrivateKeyProvider } = require('@web3auth/ethereum-provider') + +// 1. Configure provider (once, during app startup) +const privateKeyProvider = new EthereumPrivateKeyProvider({ + config: { + chainConfig: { + chainNamespace: 'eip155', + chainId: '0x1', + rpcTarget: 'https://rpc.ankr.com/eth', + }, + }, +}) + +// 2. Initialize Web3Auth (once, during app startup) +const web3auth = new Web3Auth({ + clientId: 'YOUR_CLIENT_ID', + web3AuthNetwork: 'sapphire_mainnet', + privateKeyProvider, +}) + +await web3auth.init() + +// 3. Connect user (per request) +const provider = await web3auth.connect({ + verifier: 'YOUR_VERIFIER', + verifierId: 'user@example.com', + idToken: 'JWT_TOKEN', +}) + +// 4. Use provider for blockchain operations +const privateKey = await provider.request({ method: 'eth_private_key' }) + +// 5. Perform blockchain operations with preferred library +``` + +## Error Handling + +Always implement proper error handling when using the SDK: + +```javascript +try { + const provider = await web3auth.connect({ + verifier: 'YOUR_VERIFIER', + verifierId: 'user@example.com', + idToken: 'JWT_TOKEN', + }) + + // Success - proceed with blockchain operations + const privateKey = await provider.request({ method: 'eth_private_key' }) +} catch (error) { + console.error('Authentication failed:', error.message) + // Handle authentication errors +} +``` + +## Best Practices + +1. **Initialize once**: Call `init()` during application startup, not per request +2. **Stateless requests**: Each user authentication is independent +3. **Handle errors**: Always wrap SDK calls in try-catch blocks +4. **Secure storage**: Protect private keys and never log them +5. **Rate limiting**: Implement rate limiting for authentication endpoints +6. **Token validation**: Validate JWT tokens before passing to connect method +7. **Provider reuse**: Create providers once, reuse for multiple users diff --git a/embedded-wallets/sdk/node/usage/connect.mdx b/embedded-wallets/sdk/node/usage/connect.mdx new file mode 100644 index 00000000000..566f06e6e05 --- /dev/null +++ b/embedded-wallets/sdk/node/usage/connect.mdx @@ -0,0 +1,482 @@ +--- +title: connect +description: 'Authenticate users and establish provider connection' +--- + +## Overview + +The `connect` method is the primary authentication method for the Node.js SDK. It authenticates users using custom authentication and returns a provider for blockchain operations. + +## Usage + +```javascript +const provider = await web3auth.connect(loginParams) +``` + +## Parameters + +### LoginParams + +```typescript +interface LoginParams { + verifier: string // Your custom verifier name + verifierId: string // User's unique identifier + idToken: string // Valid JWT token + chainId?: string // Optional chain ID to connect to +} +``` + +| Parameter | Type | Description | Required | +| ------------ | -------- | ----------------------------------------------------- | -------- | +| `verifier` | `string` | Custom verifier name from Web3Auth Dashboard | ✅ | +| `verifierId` | `string` | Unique identifier for the user (email, user ID, etc.) | ✅ | +| `idToken` | `string` | Valid JWT token from your authentication system | ✅ | +| `chainId` | `string` | Blockchain chain ID to connect to | ❌ | + +## Return Value + +Returns a `SafeEventEmitterProvider` that can be used for blockchain operations. + +## Examples + +### Basic Authentication + +```javascript +const { Web3Auth } = require('@web3auth/node-sdk') + +const web3auth = new Web3Auth({ + clientId: 'YOUR_CLIENT_ID', + web3AuthNetwork: 'sapphire_mainnet', +}) + +await web3auth.init() + +// Authenticate user +const provider = await web3auth.connect({ + verifier: 'my-custom-verifier', + verifierId: 'user@example.com', + idToken: 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...', +}) + +console.log('User authenticated successfully') +``` + +### Authentication with Specific Chain + +```javascript +// Connect to Polygon mainnet +const provider = await web3auth.connect({ + verifier: 'my-custom-verifier', + verifierId: 'user123', + idToken: 'JWT_TOKEN_HERE', + chainId: '0x89', // Polygon mainnet +}) +``` + +### Authentication with Error Handling + +```javascript +async function authenticateUser(userToken, userId) { + try { + const provider = await web3auth.connect({ + verifier: 'my-auth-verifier', + verifierId: userId, + idToken: userToken, + }) + + console.log('Authentication successful') + return provider + } catch (error) { + console.error('Authentication failed:', error.message) + + // Handle specific error types + if (error.message.includes('Invalid token')) { + throw new Error('Invalid authentication token') + } else if (error.message.includes('User not found')) { + throw new Error('User not found in verifier') + } else { + throw new Error('Authentication service unavailable') + } + } +} +``` + +## ID Token Requirements + +The `idToken` must be a valid JWT token that includes: + +### Required Claims + +```json +{ + "iss": "your-issuer", // Token issuer + "aud": "your-audience", // Token audience + "sub": "user-subject-id", // Subject (user ID) + "iat": 1234567890, // Issued at time + "exp": 1234567999 // Expiration time +} +``` + +### Example JWT Payload + +```json +{ + "iss": "https://auth.yourapp.com", + "aud": "your-web3auth-client-id", + "sub": "user123", + "email": "user@example.com", + "name": "John Doe", + "iat": 1640995200, + "exp": 1641081600 +} +``` + +## Chain ID Options + +If no `chainId` is specified, the SDK will use the default chain configured in your Web3Auth Dashboard. + +### Popular Chain IDs + +| Blockchain | Chain ID | Network | +| ---------- | --------- | -------------- | +| Ethereum | `0x1` | Mainnet | +| Ethereum | `0x5` | Goerli Testnet | +| Polygon | `0x89` | Mainnet | +| Polygon | `0x80001` | Mumbai Testnet | +| BSC | `0x38` | Mainnet | +| BSC | `0x61` | Testnet | +| Arbitrum | `0xa4b1` | Mainnet | +| Optimism | `0xa` | Mainnet | + +## Working with the Provider + +Once connected, use the provider for blockchain operations: + +```javascript +// Get private key +const privateKey = await provider.request({ method: 'eth_private_key' }) + +// Get account address +const accounts = await provider.request({ method: 'eth_accounts' }) +const address = accounts[0] + +// Sign a message +const signature = await provider.request({ + method: 'personal_sign', + params: ['Hello Web3Auth!', address], +}) +``` + +## Error Types + +The `connect` method can throw various errors: + +| Error | Description | Solution | +| -------------------- | --------------------------------- | -------------------------------------- | +| `Invalid token` | JWT token is malformed or expired | Refresh and provide a new token | +| `Verifier not found` | Verifier name doesn't exist | Check verifier name in dashboard | +| `User not found` | VerifierId not found in verifier | Ensure user exists in your auth system | +| `Network error` | Connection to Web3Auth failed | Check network connectivity | +| `Invalid client ID` | Client ID is incorrect | Verify client ID in dashboard | + +## Next Steps + +After successful authentication: + +- [Access private keys](./private-key) +- [Integrate with blockchains](./evm-integration) + +## Custom Authentication Implementation + +Since the Node.js SDK only supports custom authentication, here are detailed implementation examples for different scenarios: + +### Creating JWT Tokens + +```javascript +const jwt = require('jsonwebtoken') + +function createJWTForUser(userId, userEmail, userName) { + const payload = { + iss: process.env.JWT_ISSUER, // Your issuer URL + aud: process.env.WEB3AUTH_CLIENT_ID, // Your Web3Auth client ID + sub: userId, // User's unique identifier + email: userEmail, + name: userName, + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 60 * 60, // 1 hour expiration + } + + return jwt.sign(payload, process.env.JWT_SECRET, { algorithm: 'HS256' }) +} +``` + +### Auth0 Integration + +```javascript +const { auth0 } = require('auth0') + +class Auth0Web3AuthIntegration { + constructor(auth0Config, web3authConfig) { + this.auth0 = new auth0.AuthenticationApi({ + domain: auth0Config.domain, + clientId: auth0Config.clientId, + clientSecret: auth0Config.clientSecret, + }) + + this.web3auth = new Web3Auth(web3authConfig) + } + + async initialize() { + await this.web3auth.init() + } + + async authenticateWithAuth0Token(auth0Token) { + try { + // Verify Auth0 token + const userInfo = await this.auth0.getProfile(auth0Token) + + // Create custom JWT for Web3Auth + const customJWT = this.createCustomJWT(userInfo) + + // Authenticate with Web3Auth + const provider = await this.web3auth.connect({ + verifier: 'auth0-verifier', + verifierId: userInfo.sub, + idToken: customJWT, + }) + + return { provider, userInfo } + } catch (error) { + console.error('Auth0 authentication failed:', error) + throw error + } + } + + createCustomJWT(auth0UserInfo) { + const payload = { + iss: process.env.CUSTOM_JWT_ISSUER, + aud: process.env.WEB3AUTH_CLIENT_ID, + sub: auth0UserInfo.sub, + email: auth0UserInfo.email, + name: auth0UserInfo.name, + picture: auth0UserInfo.picture, + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 3600, + } + + return jwt.sign(payload, process.env.JWT_SECRET) + } +} +``` + +### Firebase Integration + +```javascript +const admin = require('firebase-admin') + +class FirebaseWeb3AuthIntegration { + constructor(firebaseConfig, web3authConfig) { + admin.initializeApp({ + credential: admin.credential.cert(firebaseConfig.serviceAccount), + databaseURL: firebaseConfig.databaseURL, + }) + + this.web3auth = new Web3Auth(web3authConfig) + } + + async initialize() { + await this.web3auth.init() + } + + async authenticateWithFirebaseToken(firebaseToken) { + try { + // Verify Firebase ID token + const decodedToken = await admin.auth().verifyIdToken(firebaseToken) + + // Get user record for additional info + const userRecord = await admin.auth().getUser(decodedToken.uid) + + // Create custom JWT for Web3Auth + const customJWT = this.createCustomJWT(decodedToken, userRecord) + + // Authenticate with Web3Auth + const provider = await this.web3auth.connect({ + verifier: 'firebase-verifier', + verifierId: decodedToken.uid, + idToken: customJWT, + }) + + return { provider, firebaseUser: userRecord } + } catch (error) { + console.error('Firebase authentication failed:', error) + throw error + } + } + + createCustomJWT(decodedToken, userRecord) { + const payload = { + iss: process.env.CUSTOM_JWT_ISSUER, + aud: process.env.WEB3AUTH_CLIENT_ID, + sub: decodedToken.uid, + email: userRecord.email, + name: userRecord.displayName, + picture: userRecord.photoURL, + email_verified: userRecord.emailVerified, + firebase: { + sign_in_provider: decodedToken.firebase.sign_in_provider, + identities: decodedToken.firebase.identities, + }, + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 3600, + } + + return jwt.sign(payload, process.env.JWT_SECRET) + } +} +``` + +### Express.js API Integration + +```javascript +const express = require('express') + +const app = express() +app.use(express.json()) + +// Store Web3Auth instances per user (use proper session store in production) +const userSessions = new Map() + +// Initialize Web3Auth +const web3auth = new Web3Auth({ + clientId: process.env.WEB3AUTH_CLIENT_ID, + web3AuthNetwork: 'sapphire_mainnet', +}) + +await web3auth.init() + +// Authentication endpoint +app.post('/api/auth/web3auth', async (req, res) => { + try { + const { userId, userEmail, userName } = req.body + + // Validate user (implement your validation logic) + const isValidUser = await validateUser(userId, userEmail) + if (!isValidUser) { + return res.status(401).json({ error: 'Invalid user credentials' }) + } + + // Create JWT + const idToken = createJWTForUser(userId, userEmail, userName) + + // Authenticate with Web3Auth + const provider = await web3auth.connect({ + verifier: 'api-verifier', + verifierId: userId, + idToken: idToken, + }) + + // Store session + userSessions.set(userId, { provider, timestamp: Date.now() }) + + // Access blockchain operations + const privateKey = await provider.request({ method: 'eth_private_key' }) + + res.json({ + success: true, + address: await provider.request({ method: 'eth_accounts' }), + sessionId: userId, + }) + } catch (error) { + console.error('Authentication error:', error) + res.status(500).json({ error: 'Authentication failed' }) + } +}) + +// Get wallet address endpoint +app.get('/api/wallet/:userId/address', async (req, res) => { + try { + const { userId } = req.params + const session = userSessions.get(userId) + + if (!session) { + return res.status(401).json({ error: 'No active session' }) + } + + const accounts = await session.provider.request({ method: 'eth_accounts' }) + + res.json({ + address: accounts[0], + userId: userId, + }) + } catch (error) { + console.error('Address retrieval error:', error) + res.status(500).json({ error: 'Failed to get address' }) + } +}) + +app.listen(3000, () => { + console.log('Server running on port 3000') +}) +``` + +### JWT Validation + +```javascript +function validateJWTPayload(payload) { + // Required fields + if (!payload.sub) throw new Error('Subject (sub) is required') + if (!payload.iss) throw new Error('Issuer (iss) is required') + if (!payload.aud) throw new Error('Audience (aud) is required') + + // Expiration check + if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) { + throw new Error('Token has expired') + } + + // Issuer validation + if (payload.iss !== process.env.JWT_ISSUER) { + throw new Error('Invalid token issuer') + } + + return true +} +``` + +### Error Handling for Authentication + +```javascript +async function robustAuthentication(userId, userInfo) { + const maxRetries = 3 + let lastError + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + const idToken = createJWTForUser(userId, userInfo.email, userInfo.name) + + const provider = await web3auth.connect({ + verifier: 'robust-verifier', + verifierId: userId, + idToken: idToken, + }) + + console.log(`Authentication successful on attempt ${attempt}`) + return provider + } catch (error) { + lastError = error + console.log(`Authentication attempt ${attempt} failed:`, error.message) + + // Don't retry on certain errors + if (error.message.includes('Invalid verifier') || error.message.includes('Malformed token')) { + throw error + } + + // Wait before retry + if (attempt < maxRetries) { + await new Promise(resolve => setTimeout(resolve, 1000 * attempt)) + } + } + } + + throw new Error(`Authentication failed after ${maxRetries} attempts: ${lastError.message}`) +} +``` diff --git a/embedded-wallets/sdk/node/usage/evm-integration.mdx b/embedded-wallets/sdk/node/usage/evm-integration.mdx new file mode 100644 index 00000000000..f6a04c4f72e --- /dev/null +++ b/embedded-wallets/sdk/node/usage/evm-integration.mdx @@ -0,0 +1,433 @@ +--- +title: EVM Integration +description: 'Integrate with Ethereum and EVM-compatible chains' +--- + +## Overview + +The Node.js SDK provides seamless integration with Ethereum Virtual Machine (EVM) compatible blockchains including Ethereum, Polygon, Binance Smart Chain, Arbitrum, Optimism, and more. + +## Supported EVM Chains + +| Blockchain | Chain ID | RPC Endpoint | +| ---------------- | --------- | ------------------------------------------------ | +| Ethereum Mainnet | `0x1` | `https://rpc.ankr.com/eth` | +| Ethereum Goerli | `0x5` | `https://rpc.ankr.com/eth_goerli` | +| Polygon Mainnet | `0x89` | `https://rpc.ankr.com/polygon` | +| Polygon Mumbai | `0x80001` | `https://rpc.ankr.com/polygon_mumbai` | +| BSC Mainnet | `0x38` | `https://bsc-dataseed.binance.org` | +| BSC Testnet | `0x61` | `https://data-seed-prebsc-1-s1.binance.org:8545` | +| Arbitrum One | `0xa4b1` | `https://arb1.arbitrum.io/rpc` | +| Optimism | `0xa` | `https://mainnet.optimism.io` | + +## Integration with Ethers.js + +### Basic Setup + +```javascript +const { Web3Auth } = require('@web3auth/node-sdk') +const { ethers } = require('ethers') + +// Initialize Web3Auth +const web3auth = new Web3Auth({ + clientId: 'YOUR_CLIENT_ID', + web3AuthNetwork: 'sapphire_mainnet', +}) + +await web3auth.init() + +// Authenticate user +const provider = await web3auth.connect({ + verifier: 'my-custom-verifier', + verifierId: 'user@example.com', + idToken: 'JWT_TOKEN', + chainId: '0x1', // Ethereum mainnet +}) + +// Get private key and create wallet +const privateKey = await provider.request({ method: 'eth_private_key' }) +const wallet = new ethers.Wallet(privateKey) + +// Connect to RPC provider +const rpcProvider = new ethers.providers.JsonRpcProvider('https://rpc.ankr.com/eth') +const connectedWallet = wallet.connect(rpcProvider) + +console.log('Wallet Address:', await connectedWallet.getAddress()) +``` + +### Send Transaction + +```javascript +async function sendTransaction(toAddress, amount) { + try { + // Create transaction + const tx = { + to: toAddress, + value: ethers.utils.parseEther(amount), + gasLimit: 21000, + } + + // Send transaction + const txResponse = await connectedWallet.sendTransaction(tx) + console.log('Transaction Hash:', txResponse.hash) + + // Wait for confirmation + const receipt = await txResponse.wait() + console.log('Transaction confirmed in block:', receipt.blockNumber) + + return receipt + } catch (error) { + console.error('Transaction failed:', error.message) + throw error + } +} + +// Usage +await sendTransaction('0x742d35Cc6635C0532925a3b8138341B0F7E8a4e8', '0.1') +``` + +### Smart Contract Interaction + +```javascript +// ERC-20 Token ABI (simplified) +const erc20Abi = [ + 'function balanceOf(address owner) view returns (uint256)', + 'function transfer(address to, uint256 amount) returns (bool)', + 'function approve(address spender, uint256 amount) returns (bool)', + 'function symbol() view returns (string)', + 'function decimals() view returns (uint8)', +] + +async function interactWithERC20(tokenAddress) { + // Create contract instance + const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, connectedWallet) + + // Get token info + const symbol = await tokenContract.symbol() + const decimals = await tokenContract.decimals() + console.log(`Token: ${symbol}, Decimals: ${decimals}`) + + // Get balance + const balance = await tokenContract.balanceOf(connectedWallet.address) + console.log('Token Balance:', ethers.utils.formatUnits(balance, decimals)) + + // Transfer tokens + const transferTx = await tokenContract.transfer( + '0x742d35Cc6635C0532925a3b8138341B0F7E8a4e8', + ethers.utils.parseUnits('10', decimals) + ) + + await transferTx.wait() + console.log('Transfer completed:', transferTx.hash) +} + +// Usage with USDC on Ethereum +await interactWithERC20('0xA0b86a33E6441E51DBF5c4dF02a7b29fAdab0215') +``` + +## Integration with Viem + +### Basic Setup + +```javascript +const { createWalletClient, createPublicClient, http } = require('viem') +const { privateKeyToAccount } = require('viem/accounts') +const { mainnet, polygon } = require('viem/chains') + +// Get private key from Web3Auth +const privateKey = await provider.request({ method: 'eth_private_key' }) + +// Create account +const account = privateKeyToAccount(privateKey) + +// Create wallet client +const walletClient = createWalletClient({ + account, + chain: mainnet, + transport: http('https://rpc.ankr.com/eth'), +}) + +// Create public client for reading +const publicClient = createPublicClient({ + chain: mainnet, + transport: http('https://rpc.ankr.com/eth'), +}) + +console.log('Account Address:', account.address) +``` + +### Send Transaction with Viem + +```javascript +const { parseEther, formatEther } = require('viem') + +async function sendTransactionViem(toAddress, amount) { + try { + // Get current balance + const balance = await publicClient.getBalance({ + address: account.address, + }) + console.log('Current Balance:', formatEther(balance), 'ETH') + + // Send transaction + const hash = await walletClient.sendTransaction({ + to: toAddress, + value: parseEther(amount), + }) + + console.log('Transaction Hash:', hash) + + // Wait for confirmation + const receipt = await publicClient.waitForTransactionReceipt({ hash }) + console.log('Transaction confirmed in block:', receipt.blockNumber) + + return receipt + } catch (error) { + console.error('Transaction failed:', error.message) + throw error + } +} +``` + +### Smart Contract with Viem + +```javascript +const { getContract, parseUnits, formatUnits } = require('viem') + +// ERC-20 ABI +const erc20Abi = [ + { + inputs: [{ name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + name: 'transfer', + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const + +async function erc20WithViem(tokenAddress) { + // Create contract instance + const contract = getContract({ + address: tokenAddress, + abi: erc20Abi, + publicClient, + walletClient, + }) + + // Read balance + const balance = await contract.read.balanceOf([account.address]) + console.log('Token Balance:', formatUnits(balance, 18)) + + // Write operation + const hash = await contract.write.transfer([ + '0x742d35Cc6635C0532925a3b8138341B0F7E8a4e8', + parseUnits('10', 18), + ]) + + const receipt = await publicClient.waitForTransactionReceipt({ hash }) + console.log('Transfer completed:', receipt.transactionHash) +} +``` + +## Multi-Chain Support + +### Chain Switching + +```javascript +async function connectToChain(chainId, authParams) { + // Connect to specific chain (stateless) + const provider = await web3auth.connect({ + ...authParams, + chainId, + }) + + return provider +} + +// Connect to Polygon +const polygonProvider = await connectToChain('0x89', { + verifier: 'my-custom-verifier', + verifierId: 'user@example.com', + idToken: 'JWT_TOKEN', +}) +``` + +### Multi-Chain Wallet Class + +```javascript +class MultiChainEVMWallet { + constructor(clientId) { + this.web3auth = new Web3Auth({ + clientId, + web3AuthNetwork: 'sapphire_mainnet', + }) + this.wallets = new Map() + } + + async initialize() { + await this.web3auth.init() + } + + async connectToChain(chainId, chainConfig) { + // Connect to specific chain + const provider = await this.web3auth.connect({ + verifier: 'my-custom-verifier', + verifierId: 'user@example.com', + idToken: 'JWT_TOKEN', + chainId, + }) + + // Create wallet for this chain + const privateKey = await provider.request({ method: 'eth_private_key' }) + const wallet = new ethers.Wallet(privateKey) + const connectedWallet = wallet.connect(new ethers.providers.JsonRpcProvider(chainConfig.rpcUrl)) + + this.wallets.set(chainId, { + provider, + wallet: connectedWallet, + config: chainConfig, + }) + + return connectedWallet + } + + getWallet(chainId) { + const walletData = this.wallets.get(chainId) + return walletData ? walletData.wallet : null + } + + async getBalances() { + const balances = {} + + for (const [chainId, walletData] of this.wallets) { + const balance = await walletData.wallet.getBalance() + balances[chainId] = { + balance: ethers.utils.formatEther(balance), + address: walletData.wallet.address, + network: walletData.config.name, + } + } + + return balances + } +} + +// Usage +const multiWallet = new MultiChainEVMWallet('YOUR_CLIENT_ID') +await multiWallet.initialize() + +// Connect to multiple chains +await multiWallet.connectToChain('0x1', { + name: 'Ethereum', + rpcUrl: 'https://rpc.ankr.com/eth', +}) + +await multiWallet.connectToChain('0x89', { + name: 'Polygon', + rpcUrl: 'https://rpc.ankr.com/polygon', +}) + +const balances = await multiWallet.getBalances() +console.log('Multi-chain balances:', balances) +``` + +## Gas Management + +### Estimate Gas + +```javascript +async function estimateAndSendTransaction(toAddress, amount) { + const tx = { + to: toAddress, + value: ethers.utils.parseEther(amount), + } + + // Estimate gas + const gasEstimate = await connectedWallet.estimateGas(tx) + console.log('Estimated Gas:', gasEstimate.toString()) + + // Get gas price + const gasPrice = await connectedWallet.getGasPrice() + console.log('Gas Price:', ethers.utils.formatUnits(gasPrice, 'gwei'), 'gwei') + + // Add gas settings to transaction + tx.gasLimit = gasEstimate.mul(120).div(100) // Add 20% buffer + tx.gasPrice = gasPrice + + // Send transaction + const txResponse = await connectedWallet.sendTransaction(tx) + return txResponse +} +``` + +### EIP-1559 (Type 2) Transactions + +```javascript +async function sendEIP1559Transaction(toAddress, amount) { + // Get fee data + const feeData = await connectedWallet.getFeeData() + + const tx = { + to: toAddress, + value: ethers.utils.parseEther(amount), + type: 2, // EIP-1559 + maxFeePerGas: feeData.maxFeePerGas, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas, + } + + const txResponse = await connectedWallet.sendTransaction(tx) + return txResponse +} +``` + +## Error Handling + +```javascript +async function robustEVMTransaction(toAddress, amount) { + try { + // Check balance first + const balance = await connectedWallet.getBalance() + const amountWei = ethers.utils.parseEther(amount) + + if (balance.lt(amountWei)) { + throw new Error('Insufficient balance') + } + + // Send transaction with retries + let retries = 3 + while (retries > 0) { + try { + const tx = await sendTransaction(toAddress, amount) + return tx + } catch (error) { + retries-- + + if (error.code === 'NONCE_EXPIRED' && retries > 0) { + console.log('Nonce expired, retrying...') + continue + } + + throw error + } + } + } catch (error) { + console.error('EVM transaction failed:', error.message) + throw error + } +} +``` + +## Next Steps + +- [Access private keys](./private-key) +- [Authenticate users](./connect) diff --git a/embedded-wallets/sdk/node/usage/private-key.mdx b/embedded-wallets/sdk/node/usage/private-key.mdx new file mode 100644 index 00000000000..23684f6ce02 --- /dev/null +++ b/embedded-wallets/sdk/node/usage/private-key.mdx @@ -0,0 +1,316 @@ +--- +title: Private Key Access +description: 'Extract private keys for blockchain operations' +--- + +## Overview + +The Node.js SDK provides direct access to user private keys, enabling integration with any blockchain network. This is a key feature that makes the SDK suitable for backend and programmatic use cases. + +:::warning Security Notice + +Private keys provide full control over user assets. Handle them securely: + +- Never log private keys +- Store them encrypted if persistence is needed +- Use secure memory handling +- Implement proper access controls + +::: + +## Available Methods + +The provider offers different methods to access private keys depending on your blockchain needs: + +| Method | Description | Use Case | +| ------------------ | ------------------------------ | ----------------------------------------- | +| `eth_private_key` | Ethereum-formatted private key | EVM chains (Ethereum, Polygon, BSC, etc.) | +| `solanaPrivateKey` | Solana private key | Solana blockchain | +| `private_key` | Raw private key | Any blockchain | + +## EVM Chains (Ethereum, Polygon, BSC, etc.) + +### Get Ethereum Private Key + +```javascript +const privateKey = await provider.request({ method: 'eth_private_key' }) +console.log('Private Key:', privateKey) // 0x1234567890abcdef... +``` + +### Use with Ethers.js + +```javascript +const { ethers } = require('ethers') + +// Get private key +const privateKey = await provider.request({ method: 'eth_private_key' }) + +// Create wallet instance +const wallet = new ethers.Wallet(privateKey) + +// Connect to a provider +const rpcProvider = new ethers.providers.JsonRpcProvider('https://rpc.ankr.com/eth') +const connectedWallet = wallet.connect(rpcProvider) + +// Get address +const address = await connectedWallet.getAddress() +console.log('Wallet Address:', address) + +// Sign a transaction +const tx = { + to: '0x742d35Cc6635C0532925a3b8138341B0F7E8a4e8', + value: ethers.utils.parseEther('0.1'), +} + +const signedTx = await connectedWallet.signTransaction(tx) +const receipt = await connectedWallet.sendTransaction(tx) +``` + +### Use with Viem + +```javascript +const { createWalletClient, createPublicClient, http } = require('viem') +const { privateKeyToAccount } = require('viem/accounts') +const { mainnet } = require('viem/chains') + +// Get private key +const privateKey = await provider.request({ method: 'eth_private_key' }) + +// Create account from private key +const account = privateKeyToAccount(privateKey) + +// Create wallet client +const walletClient = createWalletClient({ + account, + chain: mainnet, + transport: http('https://rpc.ankr.com/eth'), +}) + +// Create public client for reading +const publicClient = createPublicClient({ + chain: mainnet, + transport: http('https://rpc.ankr.com/eth'), +}) + +// Send transaction +const hash = await walletClient.sendTransaction({ + to: '0x742d35Cc6635C0532925a3b8138341B0F7E8a4e8', + value: parseEther('0.1'), +}) +``` + +## Solana Integration + +### Get Solana Private Key + +```javascript +const solanaPrivateKey = await provider.request({ method: 'solanaPrivateKey' }) +console.log('Solana Private Key:', solanaPrivateKey) // Base58 encoded +``` + +### Use with Solana Web3.js + +```javascript +const { + Connection, + Keypair, + PublicKey, + Transaction, + SystemProgram, + LAMPORTS_PER_SOL, +} = require('@solana/web3.js') +const bs58 = require('bs58') + +// Get private key +const solanaPrivateKey = await provider.request({ method: 'solanaPrivateKey' }) + +// Create keypair from private key +const secretKey = bs58.decode(solanaPrivateKey) +const keypair = Keypair.fromSecretKey(secretKey) + +// Connect to Solana +const connection = new Connection('https://api.mainnet-beta.solana.com') + +// Get balance +const balance = await connection.getBalance(keypair.publicKey) +console.log('Balance:', balance / LAMPORTS_PER_SOL, 'SOL') + +// Send transaction +const transaction = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: keypair.publicKey, + toPubkey: new PublicKey('11111111111111111111111111111112'), + lamports: 0.1 * LAMPORTS_PER_SOL, + }) +) + +const signature = await connection.sendTransaction(transaction, [keypair]) +``` + +## Other Blockchains + +### Get Raw Private Key + +```javascript +const rawPrivateKey = await provider.request({ method: 'private_key' }) +console.log('Raw Private Key:', rawPrivateKey) // Hex string +``` + +### Example: Bitcoin Integration + +```javascript +const bitcoin = require('bitcoinjs-lib') + +// Get raw private key +const rawPrivateKey = await provider.request({ method: 'private_key' }) + +// Create Bitcoin keypair +const keyPair = bitcoin.ECPair.fromPrivateKey(Buffer.from(rawPrivateKey.slice(2), 'hex')) + +// Get Bitcoin address +const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey }) +console.log('Bitcoin Address:', address) +``` + +## Complete Integration Example + +Here's a comprehensive example showing multi-chain support: + +```javascript +const { Web3Auth } = require('@web3auth/node-sdk') +const { ethers } = require('ethers') + +class MultiChainWallet { + constructor(clientId) { + this.web3auth = new Web3Auth({ + clientId, + web3AuthNetwork: 'sapphire_mainnet', + }) + } + + async initialize() { + await this.web3auth.init() + } + + async authenticate(verifier, verifierId, idToken) { + this.provider = await this.web3auth.connect({ + verifier, + verifierId, + idToken, + }) + } + + async getEthereumWallet() { + const privateKey = await this.provider.request({ method: 'eth_private_key' }) + return new ethers.Wallet(privateKey) + } + + async getSolanaKeypair() { + const { Keypair } = require('@solana/web3.js') + const bs58 = require('bs58') + + const solanaPrivateKey = await this.provider.request({ method: 'solanaPrivateKey' }) + const secretKey = bs58.decode(solanaPrivateKey) + return Keypair.fromSecretKey(secretKey) + } + + async getRawPrivateKey() { + return await this.provider.request({ method: 'private_key' }) + } + + async getAddresses() { + const [ethWallet, solanaKeypair] = await Promise.all([ + this.getEthereumWallet(), + this.getSolanaKeypair(), + ]) + + return { + ethereum: ethWallet.address, + solana: solanaKeypair.publicKey.toString(), + } + } +} + +// Usage +const wallet = new MultiChainWallet('YOUR_CLIENT_ID') +await wallet.initialize() +await wallet.authenticate('verifier', 'user@example.com', 'jwt_token') + +const addresses = await wallet.getAddresses() +console.log('Addresses:', addresses) +``` + +## Security Best Practices + +### 1. Secure Memory Handling + +```javascript +// Use Buffer.alloc for sensitive data +function securePrivateKeyHandling(privateKey) { + const keyBuffer = Buffer.from(privateKey.slice(2), 'hex') + + try { + // Use the private key + const wallet = new ethers.Wallet(keyBuffer) + return wallet.address + } finally { + // Clear the buffer + keyBuffer.fill(0) + } +} +``` + +### 2. Environment-based Key Access + +```javascript +// Only allow private key access in specific environments +function getPrivateKeySecurely() { + if (process.env.NODE_ENV === 'production' && !process.env.ALLOW_PRIVATE_KEY_ACCESS) { + throw new Error('Private key access not allowed in production') + } + + return provider.request({ method: 'eth_private_key' }) +} +``` + +### 3. Audit Logging + +```javascript +async function auditedPrivateKeyAccess(userId, purpose) { + console.log(`Private key accessed by ${userId} for ${purpose} at ${new Date().toISOString()}`) + + // Log to audit system + await logAuditEvent({ + action: 'private_key_access', + userId, + purpose, + timestamp: new Date(), + }) + + return provider.request({ method: 'eth_private_key' }) +} +``` + +## Disabling Private Key Export + +Private key export can be disabled in the Web3Auth Dashboard: + +1. Go to your project settings +2. Navigate to "Advanced Settings" +3. Toggle "Disable Private Key Export" +4. Save changes + +When disabled, private key methods will throw an error: + +```javascript +try { + const privateKey = await provider.request({ method: 'eth_private_key' }) +} catch (error) { + console.error('Private key export disabled:', error.message) +} +``` + +## Next Steps + +- [EVM Integration Guide](./evm-integration) +- [User authentication](./connect) diff --git a/src/components/EWSDKCards/index.tsx b/src/components/EWSDKCards/index.tsx index 266c16a9911..37c28c44bdd 100644 --- a/src/components/EWSDKCards/index.tsx +++ b/src/components/EWSDKCards/index.tsx @@ -107,6 +107,27 @@ export const pnpweb = ( +
+
+ + + +
+
+

Node.js SDK

+
+ SDK Reference{chevron} + Examples{chevron} +
+
+
) From 5aa0e404feee13e4b83f7b53efc30f93810dfcb4 Mon Sep 17 00:00:00 2001 From: Yashovardhan Agrawal <21066442+yashovardhan@users.noreply.github.com> Date: Tue, 30 Sep 2025 10:13:58 +0800 Subject: [PATCH 2/3] updates --- embedded-wallets/sdk/node/README.mdx | 361 ++++++++++-------- .../sdk/node/{usage => }/connect.mdx | 0 .../sdk/node/{usage => }/evm-integration.mdx | 0 .../sdk/node/{usage => }/private-key.mdx | 0 embedded-wallets/sdk/node/usage/README.mdx | 102 ----- ew-sidebar.js | 64 +++- src/components/NavDropdown/Products.html | 38 +- src/utils/w3a-sdk-map.js | 6 +- 8 files changed, 280 insertions(+), 291 deletions(-) rename embedded-wallets/sdk/node/{usage => }/connect.mdx (100%) rename embedded-wallets/sdk/node/{usage => }/evm-integration.mdx (100%) rename embedded-wallets/sdk/node/{usage => }/private-key.mdx (100%) delete mode 100644 embedded-wallets/sdk/node/usage/README.mdx diff --git a/embedded-wallets/sdk/node/README.mdx b/embedded-wallets/sdk/node/README.mdx index 40b48b83a05..690d90d46d7 100644 --- a/embedded-wallets/sdk/node/README.mdx +++ b/embedded-wallets/sdk/node/README.mdx @@ -35,27 +35,12 @@ Unlike frontend SDKs, the Node.js SDK is **stateless and sessionless**, making i ## Installation -Install the Web3Auth Node SDK and required providers: +Install the Web3Auth Node SDK ```bash npm2yarn npm install --save @web3auth/node-sdk ``` -### Additional Dependencies - -For blockchain providers: - -```bash npm2yarn -# For EVM chains (Ethereum, Polygon, etc.) -npm install --save @web3auth/ethereum-provider - -# For Solana blockchain -npm install --save @web3auth/solana-provider - -# Base package for types -npm install --save @web3auth/base -``` - ## Setup :::info Prerequisites @@ -63,87 +48,37 @@ npm install --save @web3auth/base Before you start, make sure you have: 1. Registered on the [**Web3Auth Dashboard**](https://dashboard.web3auth.io/) -2. Set up a project with **custom authentication** (mandatory for Node.js SDK) -3. Configured your custom verifier settings - -You can refer to the [Dashboard Setup](/embedded-wallets/dashboard/) guide to learn more. +2. Set up a project with a custom **Auth Connection** (mandatory for Node.js SDK) ::: -### 1. Custom Verifier Setup (Required) +### 1. Custom Authentication Setup (Required) -The Node.js SDK **only supports custom authentication**. You must create a custom verifier in the Web3Auth Dashboard: +The Node.js SDK **only supports custom authentication**. You must create a custom auth connection in the Web3Auth Dashboard: 1. Go to [Web3Auth Dashboard](https://dashboard.web3auth.io/) 2. Select your project -3. Navigate to **Auth Connectors** → **Custom Authentication** -4. Click **Create Verifier** -5. Configure your verifier with these settings: - -```json -{ - "name": "my-backend-verifier", - "description": "Custom verifier for Node.js backend", - "verifierType": "custom", - "clientId": "your-auth-client-id", - "issuer": "https://your-auth-domain.com", - "jwks_endpoint": "https://your-auth-domain.com/.well-known/jwks.json" -} -``` - -### 2. JWT Token Requirements - -Your authentication system must generate JWT tokens with these required claims: - -```json -{ - "iss": "https://your-auth-domain.com", // Issuer (must match verifier) - "aud": "your-web3auth-client-id", // Audience - "sub": "user-unique-identifier", // Subject (user ID) - "iat": 1640995200, // Issued at - "exp": 1641081600, // Expiration - "email": "user@example.com", // User email (optional) - "name": "John Doe" // User name (optional) -} -``` - -### 3. Provider Configuration - -Configure the blockchain provider for your target network: +3. Navigate to **Authentication** → **Custom Connections** +4. Click **Create connections** +5. Configure your auth connection with your custom JWT details -```javascript -const { Web3Auth } = require('@web3auth/node-sdk') -const { EthereumPrivateKeyProvider } = require('@web3auth/ethereum-provider') +> You can refer to the [Custom JWT Setup](/embedded-wallets/authentication/custom-connections/custom-jwt/) guide to learn more. -// Configure Ethereum provider -const privateKeyProvider = new EthereumPrivateKeyProvider({ - config: { - chainConfig: { - chainNamespace: 'eip155', - chainId: '0x1', // Ethereum Mainnet - rpcTarget: 'https://rpc.ankr.com/eth', - displayName: 'Ethereum Mainnet', - blockExplorerUrl: 'https://etherscan.io/', - ticker: 'ETH', - tickerName: 'Ethereum', - }, - }, -}) -``` - -### 4. SDK Configuration +### 2. SDK Configuration -Create a Web3Auth instance with your client ID, network configuration, and provider: +Create a Web3Auth instance with your client ID, web3auth network name, and chain information: ```javascript const web3auth = new Web3Auth({ clientId: 'YOUR_CLIENT_ID', // Get your Client ID from Web3Auth Dashboard web3AuthNetwork: 'sapphire_mainnet', // or 'sapphire_devnet' - privateKeyProvider, + defaultChainId: '0x1', // or '0x89' for Polygon }) ``` -### 5. Initialize Web3Auth +> The chain information is optional and will be used to setup the provider for connecting to the chain. If not provided, the first chain in the list will be used. + +### 3. Initialize Web3Auth Initialize the Web3Auth instance during your application startup: @@ -169,8 +104,7 @@ const provider = await web3auth.connect({ defaultValue="basic-config" values={[ { label: "Basic Configuration", value: "basic-config" }, - { label: "Production Configuration", value: "production-config" }, - { label: "Environment-Specific", value: "env-config" }, + { label: "Advanced Configuration", value: "advanced-config" }, ]} > @@ -189,32 +123,15 @@ await web3auth.init() - + ```javascript -const { Web3Auth } = require('@web3auth/node-sdk') -const { EthereumPrivateKeyProvider } = require('@web3auth/ethereum-provider') - -// Configure provider -const privateKeyProvider = new EthereumPrivateKeyProvider({ - config: { - chainConfig: { - chainNamespace: 'eip155', - chainId: process.env.CHAIN_ID || '0x1', - rpcTarget: process.env.RPC_URL || 'https://rpc.ankr.com/eth', - displayName: 'Ethereum', - blockExplorerUrl: 'https://etherscan.io/', - ticker: 'ETH', - tickerName: 'Ethereum', - }, - }, -}) - -// Production configuration const web3auth = new Web3Auth({ - clientId: process.env.WEB3AUTH_CLIENT_ID, - web3AuthNetwork: process.env.NODE_ENV === 'production' ? 'sapphire_mainnet' : 'sapphire_devnet', - privateKeyProvider, + clientId: 'YOUR_CLIENT_ID', + web3AuthNetwork: 'sapphire_mainnet', // or 'sapphire_devnet' + defaultChainId: '0x1', // or '0x89' for Polygon + enableLogging: true, + sessionTime: 3600, }) await web3auth.init() @@ -222,73 +139,101 @@ await web3auth.init() - + -```javascript -// Provider configuration -const privateKeyProvider = new EthereumPrivateKeyProvider({ - config: { - chainConfig: { - chainNamespace: 'eip155', - chainId: '0x1', - rpcTarget: 'https://rpc.ankr.com/eth', - displayName: 'Ethereum', - blockExplorerUrl: 'https://etherscan.io/', - ticker: 'ETH', - tickerName: 'Ethereum', - }, - }, -}) +## Configuration Parameters -// Development -const devConfig = { - clientId: 'dev-client-id', - web3AuthNetwork: 'sapphire_devnet', - privateKeyProvider, -} + -// Production -const prodConfig = { - clientId: process.env.WEB3AUTH_CLIENT_ID, - web3AuthNetwork: 'sapphire_mainnet', - privateKeyProvider, -} + -// Testing -const testConfig = { - clientId: 'test-client-id', - web3AuthNetwork: 'sapphire_devnet', - privateKeyProvider, -} +#### Web3Auth Configuration -const web3auth = new Web3Auth(process.env.NODE_ENV === 'production' ? prodConfig : devConfig) -``` +| Parameter | Type | Default | Description | +| ----------------- | -------- | -------- | ---------------------------------------------------------------- | +| `clientId` | `string` | Required | Your Web3Auth client ID | +| `web3AuthNetwork` | `string` | Required | Network: 'sapphire_mainnet' or 'sapphire_devnet' | +| `defaultChainId` | `string` | Optional | Chain ID to use for the default chain (e.g., '0x1' for Ethereum) | - +#### Advanced Configuration Parameters - +| Parameter | Type | Description | +| ----------------- | --------- | ----------- | ------------------------------------------------------------------- | +| `chains` | `object` | Optional | Chains to use for the authentication. It takes `Chains` as a value. | +| `enableLogging` | `boolean` | Optional | Setting to true will enable logs. Default is false. | +| `usePnPKey` | `boolean` | Optional | Setting to true will use the PnP key. Default is false. | +| `useDKG` | `boolean` | Optional | Setting to true will use the DKG. Default is false. | +| `checkCommitment` | `boolean` | Optional | Setting to true will check the commitment. Default is true. | -## Configuration Parameters + -### Web3Auth Configuration + -| Parameter | Type | Default | Description | -| -------------------- | -------- | -------- | --------------------------------------------------- | -| `clientId` | `string` | Required | Your Web3Auth client ID | -| `web3AuthNetwork` | `string` | Required | Network: 'sapphire_mainnet' or 'sapphire_devnet' | -| `privateKeyProvider` | `object` | Required | Blockchain provider configuration (Ethereum/Solana) | +```javascript +export interface Web3AuthOptions { + /** + * Client id for web3auth. + * You can obtain your client id from the web3auth developer dashboard. + * You can set any random string for this on localhost. + */ + clientId: string; + + /** + * Web3Auth Network to use for login + * @defaultValue mainnet + */ + web3AuthNetwork?: WEB3AUTH_NETWORK_TYPE; + + /** + * multiple chain configurations, + * only provided chains will be used + */ + chains?: CustomChainConfig[]; + + /** + * default chain Id to use + */ + defaultChainId?: string; + + /** + * setting to true will enable logs + * + * @defaultValue false + */ + enableLogging?: boolean; + + /** + * setting this to true returns the same key as web sdk (i.e., plug n play key) + * By default, this sdk returns SFAKey + */ + usePnPKey?: boolean; + + /** + * set this to true when you wants keys/shares to be generated by a dkg network + * + * Default:- false for sapphire network and always true for legacy networks. + * Legacy networks doesnt support non dkg flow. So this is always true for legacy networks. + */ + useDKG?: boolean; + + /** + * setting this to true will check the commitment of the shares + * + * @defaultValue true + */ + checkCommitment?: boolean; +} +``` -### Provider Configuration Parameters + -| Parameter | Type | Description | -| ------------------ | -------- | ------------------------------------------------------- | -| `chainNamespace` | `string` | Blockchain namespace ('eip155' for EVM, 'solana') | -| `chainId` | `string` | Chain ID (e.g., '0x1' for Ethereum, '0x89' for Polygon) | -| `rpcTarget` | `string` | RPC endpoint URL for blockchain communication | -| `displayName` | `string` | Human-readable chain name | -| `blockExplorerUrl` | `string` | Block explorer URL for the chain | -| `ticker` | `string` | Native token symbol (e.g., 'ETH', 'SOL') | -| `tickerName` | `string` | Full name of the native token | + ## Best Practices @@ -481,3 +426,101 @@ Private key export can be controlled through the Web3Auth Dashboard: - **[Examples](./examples)**: Complete implementation examples and production patterns + + +## Usage Overview + +The MetaMask Embedded Wallets Node.js SDK provides stateless authentication and blockchain operations for backend applications. Unlike frontend SDKs, this is designed for **per-request authentication** without persistent sessions. + +## Core Methods + +### Authentication & Connection + +- **[connect](./connect)** - Authenticate users and establish provider connection (stateless) + +### Blockchain Operations + +- **[Private Key Access](./private-key)** - Extract private keys for blockchain operations +- **[EVM Integration](./evm-integration)** - Work with Ethereum and EVM-compatible chains + +## Important Notes + +### Stateless Design + +The Node.js SDK is **stateless** and **sessionless** by design: + +- No persistent user sessions +- No `getUserInfo()` or `logout()` methods +- Each request requires re-authentication +- Perfect for backend APIs and microservices + +## Common Usage Pattern + +The typical flow when using the Node.js SDK: + +```javascript +const { Web3Auth } = require('@web3auth/node-sdk') +const { EthereumPrivateKeyProvider } = require('@web3auth/ethereum-provider') + +// 1. Configure provider (once, during app startup) +const privateKeyProvider = new EthereumPrivateKeyProvider({ + config: { + chainConfig: { + chainNamespace: 'eip155', + chainId: '0x1', + rpcTarget: 'https://rpc.ankr.com/eth', + }, + }, +}) + +// 2. Initialize Web3Auth (once, during app startup) +const web3auth = new Web3Auth({ + clientId: 'YOUR_CLIENT_ID', + web3AuthNetwork: 'sapphire_mainnet', + privateKeyProvider, +}) + +await web3auth.init() + +// 3. Connect user (per request) +const provider = await web3auth.connect({ + verifier: 'YOUR_VERIFIER', + verifierId: 'user@example.com', + idToken: 'JWT_TOKEN', +}) + +// 4. Use provider for blockchain operations +const privateKey = await provider.request({ method: 'eth_private_key' }) + +// 5. Perform blockchain operations with preferred library +``` + +## Error Handling + +Always implement proper error handling when using the SDK: + +```javascript +try { + const provider = await web3auth.connect({ + verifier: 'YOUR_VERIFIER', + verifierId: 'user@example.com', + idToken: 'JWT_TOKEN', + }) + + // Success - proceed with blockchain operations + const privateKey = await provider.request({ method: 'eth_private_key' }) +} catch (error) { + console.error('Authentication failed:', error.message) + // Handle authentication errors +} +``` + +## Best Practices + +1. **Initialize once**: Call `init()` during application startup, not per request +2. **Stateless requests**: Each user authentication is independent +3. **Handle errors**: Always wrap SDK calls in try-catch blocks +4. **Secure storage**: Protect private keys and never log them +5. **Rate limiting**: Implement rate limiting for authentication endpoints +6. **Token validation**: Validate JWT tokens before passing to connect method +7. **Provider reuse**: Create providers once, reuse for multiple users diff --git a/embedded-wallets/sdk/node/usage/connect.mdx b/embedded-wallets/sdk/node/connect.mdx similarity index 100% rename from embedded-wallets/sdk/node/usage/connect.mdx rename to embedded-wallets/sdk/node/connect.mdx diff --git a/embedded-wallets/sdk/node/usage/evm-integration.mdx b/embedded-wallets/sdk/node/evm-integration.mdx similarity index 100% rename from embedded-wallets/sdk/node/usage/evm-integration.mdx rename to embedded-wallets/sdk/node/evm-integration.mdx diff --git a/embedded-wallets/sdk/node/usage/private-key.mdx b/embedded-wallets/sdk/node/private-key.mdx similarity index 100% rename from embedded-wallets/sdk/node/usage/private-key.mdx rename to embedded-wallets/sdk/node/private-key.mdx diff --git a/embedded-wallets/sdk/node/usage/README.mdx b/embedded-wallets/sdk/node/usage/README.mdx deleted file mode 100644 index d85f92c634d..00000000000 --- a/embedded-wallets/sdk/node/usage/README.mdx +++ /dev/null @@ -1,102 +0,0 @@ ---- -title: Node.js SDK Usage -sidebar_label: Overview -description: 'Learn how to use the MetaMask Embedded Wallets Node.js SDK' ---- - -## Usage Overview - -The MetaMask Embedded Wallets Node.js SDK provides stateless authentication and blockchain operations for backend applications. Unlike frontend SDKs, this is designed for **per-request authentication** without persistent sessions. - -## Core Methods - -### Authentication & Connection - -- **[connect](./connect)** - Authenticate users and establish provider connection (stateless) - -### Blockchain Operations - -- **[Private Key Access](./private-key)** - Extract private keys for blockchain operations -- **[EVM Integration](./evm-integration)** - Work with Ethereum and EVM-compatible chains - -## Important Notes - -### Stateless Design - -The Node.js SDK is **stateless** and **sessionless** by design: - -- No persistent user sessions -- No `getUserInfo()` or `logout()` methods -- Each request requires re-authentication -- Perfect for backend APIs and microservices - -## Common Usage Pattern - -The typical flow when using the Node.js SDK: - -```javascript -const { Web3Auth } = require('@web3auth/node-sdk') -const { EthereumPrivateKeyProvider } = require('@web3auth/ethereum-provider') - -// 1. Configure provider (once, during app startup) -const privateKeyProvider = new EthereumPrivateKeyProvider({ - config: { - chainConfig: { - chainNamespace: 'eip155', - chainId: '0x1', - rpcTarget: 'https://rpc.ankr.com/eth', - }, - }, -}) - -// 2. Initialize Web3Auth (once, during app startup) -const web3auth = new Web3Auth({ - clientId: 'YOUR_CLIENT_ID', - web3AuthNetwork: 'sapphire_mainnet', - privateKeyProvider, -}) - -await web3auth.init() - -// 3. Connect user (per request) -const provider = await web3auth.connect({ - verifier: 'YOUR_VERIFIER', - verifierId: 'user@example.com', - idToken: 'JWT_TOKEN', -}) - -// 4. Use provider for blockchain operations -const privateKey = await provider.request({ method: 'eth_private_key' }) - -// 5. Perform blockchain operations with preferred library -``` - -## Error Handling - -Always implement proper error handling when using the SDK: - -```javascript -try { - const provider = await web3auth.connect({ - verifier: 'YOUR_VERIFIER', - verifierId: 'user@example.com', - idToken: 'JWT_TOKEN', - }) - - // Success - proceed with blockchain operations - const privateKey = await provider.request({ method: 'eth_private_key' }) -} catch (error) { - console.error('Authentication failed:', error.message) - // Handle authentication errors -} -``` - -## Best Practices - -1. **Initialize once**: Call `init()` during application startup, not per request -2. **Stateless requests**: Each user authentication is independent -3. **Handle errors**: Always wrap SDK calls in try-catch blocks -4. **Secure storage**: Protect private keys and never log them -5. **Rate limiting**: Implement rate limiting for authentication endpoints -6. **Token validation**: Validate JWT tokens before passing to connect method -7. **Provider reuse**: Create providers once, reuse for multiple users diff --git a/ew-sidebar.js b/ew-sidebar.js index e724be4c3e7..1dd1756762e 100644 --- a/ew-sidebar.js +++ b/ew-sidebar.js @@ -3,6 +3,7 @@ import { vue, android, ios, + node, reactnative, flutter, unity, @@ -40,24 +41,23 @@ function webTopNavButton(selectedSDK) { - Mobile + Mobile & Gaming - - Gaming + + Backend `; } -function gamingTopNavButton(selectedSDK) { - var gamingSDKs = { - [unity]: `/embedded-wallets/sdk/unity`, - [unreal]: `/embedded-wallets/sdk/unreal`, +function backendTopNavButton(selectedSDK) { + var backendSDKs = { + [node]: `/embedded-wallets/sdk/node`, }; - if (gamingSDKs.hasOwnProperty(selectedSDK)) { - delete gamingSDKs[selectedSDK]; + if (backendSDKs.hasOwnProperty(selectedSDK)) { + delete backendSDKs[selectedSDK]; } - var sdkNames = Object.keys(gamingSDKs); - var sdkLinks = Object.values(gamingSDKs); + // var sdkNames = Object.keys(backendSDKs); + // var sdkLinks = Object.values(backendSDKs); var sdkVersion = getPnPVersion(selectedSDK); return ` @@ -66,14 +66,13 @@ function gamingTopNavButton(selectedSDK) { Web - Mobile + Mobile & Gaming
- Gaming + Backend
v${sdkVersion}
@@ -87,6 +86,8 @@ function mobileTopNavButton(selectedSDK) { [ios]: `/embedded-wallets/sdk/ios`, [reactnative]: `/embedded-wallets/sdk/react-native`, [flutter]: `/embedded-wallets/sdk/flutter`, + [unity]: `/embedded-wallets/sdk/unity`, + [unreal]: `/embedded-wallets/sdk/unreal`, }; if (mobileSDKs.hasOwnProperty(selectedSDK)) { delete mobileSDKs[selectedSDK]; @@ -101,19 +102,21 @@ function mobileTopNavButton(selectedSDK) { Web
- Mobile + Mobile & Gaming
v${sdkVersion}
- - Gaming + + Backend
`; } @@ -168,6 +171,7 @@ const sidebar = { { type: "link", label: "React", href: "/embedded-wallets/sdk/react" }, { type: "link", label: "Vue", href: "/embedded-wallets/sdk/vue" }, { type: "link", label: "JavaScript", href: "/embedded-wallets/sdk/js" }, + { type: "link", label: "Node.js", href: "/embedded-wallets/sdk/node" }, { type: "link", label: "Android", href: "/embedded-wallets/sdk/android" }, { type: "link", label: "iOS", href: "/embedded-wallets/sdk/ios" }, { type: "link", label: "React Native", href: "/embedded-wallets/sdk/react-native" }, @@ -745,7 +749,7 @@ const sidebar = { sdk_unity: [ { type: "html", - value: gamingTopNavButton(unity), + value: mobileTopNavButton(unity), defaultStyle: true, }, "sdk/unity/README", @@ -789,10 +793,32 @@ const sidebar = { href: "https://github.com/Web3Auth/web3auth-unity-sdk/releases", }, ], + sdk_node: [ + { + type: "html", + value: backendTopNavButton(node), + defaultStyle: true, + }, + "sdk/node/README", + "sdk/node/connect", + "sdk/node/evm-integration", + "sdk/node/private-key", + "sdk/node/examples", + { + type: "link", + label: "Support Forum", + href: "https://web3auth.io/community/c/help-pnp/pnp-node/21", + }, + { + type: "link", + label: "Release Notes", + href: "https://github.com/Web3Auth/web3auth-backend/releases", + }, + ], sdk_unreal: [ { type: "html", - value: gamingTopNavButton(unreal), + value: mobileTopNavButton(unreal), defaultStyle: true, }, "sdk/unreal/README", diff --git a/src/components/NavDropdown/Products.html b/src/components/NavDropdown/Products.html index 43982840b03..b44cdd8ebc8 100644 --- a/src/components/NavDropdown/Products.html +++ b/src/components/NavDropdown/Products.html @@ -183,30 +183,34 @@

JavaScript SDK

  • - + - - + + + + +
    -

    Unity SDK

    +

    Node SDK

  • - + - +
    -

    Unreal SDK

    +

    Unity SDK

  • @@ -266,6 +270,20 @@

    Flutter SDK

    +
  • + + + + + + +
    +

    Unreal SDK

    +
    +
    +
  • diff --git a/src/utils/w3a-sdk-map.js b/src/utils/w3a-sdk-map.js index 97d7c1365bc..aa7af5d74ce 100644 --- a/src/utils/w3a-sdk-map.js +++ b/src/utils/w3a-sdk-map.js @@ -1,7 +1,7 @@ export const reactJS = 'React' export const angular = 'Angular' export const js = 'Javascript' -export const nodejs = 'Node.js' +export const node = 'Node.js' export const vue = 'Vue' export const web = 'Web' @@ -17,6 +17,7 @@ export const pnpWebVersion = `10.0.x` export const pnpAndroidVersion = `9.1.2` export const pnpIOSVersion = `11.1.0` export const pnpRNVersion = `8.1.x` +export const pnpNodeVersion = `5.0.x` export const pnpFlutterVersion = `6.1.2` export const pnpUnityVersion = `7.0.x` export const pnpUnrealVersion = `4.1.x` @@ -55,6 +56,9 @@ export function getPnPVersion(platform) { if (platform === unreal) { return pnpUnrealVersion } + if (platform === node) { + return pnpNodeVersion + } } export function getSFAVersion(platform) { From 98673151d3967ea5a3fab71d4cdf32e2e0b343f4 Mon Sep 17 00:00:00 2001 From: Yashovardhan Agrawal <21066442+yashovardhan@users.noreply.github.com> Date: Thu, 9 Oct 2025 02:19:28 +0530 Subject: [PATCH 3/3] Update documentation --- embedded-wallets/sdk/node/README.mdx | 358 ++------ .../sdk/node/blockchain-connection.mdx | 139 +++ embedded-wallets/sdk/node/connect.mdx | 535 +++--------- embedded-wallets/sdk/node/evm-integration.mdx | 433 ---------- embedded-wallets/sdk/node/examples.mdx | 802 +----------------- embedded-wallets/sdk/node/private-key.mdx | 126 +-- ew-sidebar.js | 2 +- src/components/EWSDKCards/SDKOptions.ts | 1 + src/components/EWSDKCards/icons.tsx | 16 + src/components/EWSDKCards/index.tsx | 56 +- src/utils/example-maps.tsx | 328 +------ .../banners/node-firebase.png | Bin 0 -> 53590 bytes .../embedded-wallets/banners/node-solana.png | Bin 0 -> 48907 bytes static/img/embedded-wallets/banners/node.png | Bin 0 -> 46735 bytes 14 files changed, 432 insertions(+), 2364 deletions(-) create mode 100644 embedded-wallets/sdk/node/blockchain-connection.mdx delete mode 100644 embedded-wallets/sdk/node/evm-integration.mdx create mode 100644 static/img/embedded-wallets/banners/node-firebase.png create mode 100644 static/img/embedded-wallets/banners/node-solana.png create mode 100644 static/img/embedded-wallets/banners/node.png diff --git a/embedded-wallets/sdk/node/README.mdx b/embedded-wallets/sdk/node/README.mdx index 690d90d46d7..5f414cae743 100644 --- a/embedded-wallets/sdk/node/README.mdx +++ b/embedded-wallets/sdk/node/README.mdx @@ -10,14 +10,14 @@ import SdkTroubleshootingIntro from '../_common/_sdk-troubleshooting-intro.mdx' ## Overview -The MetaMask Embedded Wallets Node.js SDK (formerly Web3Auth Node SDK) is a backend solution designed for server-side authentication and key management. This SDK enables seamless integration of Web3 authentication into backend applications, AI agents, and programmatic use cases. +The MetaMask Embedded Wallets Node.js SDK is a backend solution designed for server-side authentication and key management. This SDK enables seamless integration of Web3 authentication into backend applications, AI agents, and programmatic use cases. Unlike frontend SDKs, the Node.js SDK is **stateless and sessionless**, making it ideal for: - Backend AI agents - Server-side wallet operations - Programmatic blockchain interactions -- Custodial wallet services +- Custodial wallet services, without key management and recovery worries. ## Key Features @@ -62,7 +62,11 @@ The Node.js SDK **only supports custom authentication**. You must create a custo 4. Click **Create connections** 5. Configure your auth connection with your custom JWT details -> You can refer to the [Custom JWT Setup](/embedded-wallets/authentication/custom-connections/custom-jwt/) guide to learn more. +:::info + +You can refer to the [Custom JWT Setup](/embedded-wallets/authentication/custom-connections/custom-jwt/) guide to learn more. + +::: ### 2. SDK Configuration @@ -72,7 +76,6 @@ Create a Web3Auth instance with your client ID, web3auth network name, and chain const web3auth = new Web3Auth({ clientId: 'YOUR_CLIENT_ID', // Get your Client ID from Web3Auth Dashboard web3AuthNetwork: 'sapphire_mainnet', // or 'sapphire_devnet' - defaultChainId: '0x1', // or '0x89' for Polygon }) ``` @@ -86,14 +89,13 @@ Initialize the Web3Auth instance during your application startup: await web3auth.init() ``` -### 6. Authenticate Users +### 4. Authenticate Users Use the connect method with your custom authentication parameters: ```javascript -const provider = await web3auth.connect({ - verifier: 'YOUR_VERIFIER_NAME', // Your custom verifier name - verifierId: 'USER_VERIFIER_ID', // User's unique identifier +const result = await web3auth.connect({ + authConnectionId: 'YOUR_AUTH_CONNECTION_ID', // Your custom authentication connection name idToken: 'USER_ID_TOKEN', // JWT token from your auth system }) ``` @@ -126,6 +128,8 @@ await web3auth.init() ```javascript +const { Web3Auth } = require('@web3auth/node-sdk') + const web3auth = new Web3Auth({ clientId: 'YOUR_CLIENT_ID', web3AuthNetwork: 'sapphire_mainnet', // or 'sapphire_devnet' @@ -153,23 +157,16 @@ await web3auth.init() -#### Web3Auth Configuration - -| Parameter | Type | Default | Description | -| ----------------- | -------- | -------- | ---------------------------------------------------------------- | -| `clientId` | `string` | Required | Your Web3Auth client ID | -| `web3AuthNetwork` | `string` | Required | Network: 'sapphire_mainnet' or 'sapphire_devnet' | -| `defaultChainId` | `string` | Optional | Chain ID to use for the default chain (e.g., '0x1' for Ethereum) | - -#### Advanced Configuration Parameters - -| Parameter | Type | Description | -| ----------------- | --------- | ----------- | ------------------------------------------------------------------- | -| `chains` | `object` | Optional | Chains to use for the authentication. It takes `Chains` as a value. | -| `enableLogging` | `boolean` | Optional | Setting to true will enable logs. Default is false. | -| `usePnPKey` | `boolean` | Optional | Setting to true will use the PnP key. Default is false. | -| `useDKG` | `boolean` | Optional | Setting to true will use the DKG. Default is false. | -| `checkCommitment` | `boolean` | Optional | Setting to true will check the commitment. Default is true. | +| Parameter | Type | Default | Description | +| ----------------- | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `clientId` | `string` | Required | Your Web3Auth client ID | +| `web3AuthNetwork` | `string` | Required | Network: 'sapphire_mainnet' or 'sapphire_devnet' | +| `defaultChainId` | `string` | Optional | Chain ID to use for the default chain (e.g., '0x1' for Ethereum). If not provided, the first chain in the list will be used. | +| `chains` | `object` | Optional | Chains to use for the authentication. It takes `Chains` as a value. | +| `enableLogging` | `boolean` | Optional | Setting to true will enable logs. Default is false. | +| `usePnPKey` | `boolean` | Optional | Setting to true will use the PnP key. Default is false. | +| `useDKG` | `boolean` | Optional | Setting to true will use the DKG. Default is false. | +| `checkCommitment` | `boolean` | Optional | Setting to true will check the commitment. Default is true. | @@ -235,292 +232,51 @@ export interface Web3AuthOptions { -## Best Practices - -### Environment Variables - -Store sensitive configuration in environment variables: - -```bash -# .env file -WEB3AUTH_CLIENT_ID=your_client_id_here -WEB3AUTH_NETWORK=sapphire_mainnet -SESSION_TIMEOUT=3600 -ENABLE_LOGGING=false -``` - -### Configuration Validation - -```javascript -function validateConfig(config) { - if (!config.clientId) { - throw new Error('Client ID is required') - } - - if (!['sapphire_mainnet', 'sapphire_devnet'].includes(config.web3AuthNetwork)) { - throw new Error('Invalid Web3Auth network') - } - - if (config.sessionTime < 300) { - throw new Error('Session time must be at least 5 minutes') - } - - return config -} - -const config = validateConfig({ - clientId: process.env.WEB3AUTH_CLIENT_ID, - web3AuthNetwork: process.env.WEB3AUTH_NETWORK, - sessionTime: parseInt(process.env.SESSION_TIMEOUT), -}) -``` - -### Dynamic Configuration Manager - -```javascript -class Web3AuthConfigManager { - constructor() { - this.config = this.loadConfig() - } - - loadConfig() { - const baseConfig = { - clientId: process.env.WEB3AUTH_CLIENT_ID, - web3AuthNetwork: process.env.WEB3AUTH_NETWORK || 'sapphire_devnet', - usePnPKey: false, - } - - // Environment-specific overrides - switch (process.env.NODE_ENV) { - case 'production': - return { - ...baseConfig, - enableLogging: false, - sessionTime: 86400, - } - case 'staging': - return { - ...baseConfig, - enableLogging: true, - sessionTime: 3600, - } - default: - return { - ...baseConfig, - enableLogging: true, - sessionTime: 1800, - } - } - } - - getConfig() { - return this.config - } - - updateConfig(updates) { - this.config = { ...this.config, ...updates } - } -} - -// Usage -const configManager = new Web3AuthConfigManager() -const web3auth = new Web3Auth(configManager.getConfig()) -``` - -## Blockchain Integration - -Web3Auth Node SDK supports multiple blockchain networks through different integration methods: - -### EVM Chains (Ethereum, Polygon, BSC, etc.) - -Use the provider with ethers.js or convert to viem: +## Usage ```javascript -// With ethers.js -const { ethers } = require('ethers') -const ethProvider = new ethers.providers.Web3Provider(provider) -const signer = ethProvider.getSigner() - -// Get private key directly -const privateKey = await provider.request({ method: 'eth_private_key' }) -``` - -### Solana Integration - -Access Solana wallet functionality: - -```javascript -// Get Solana account info -const solanaWallet = await provider.request({ method: 'solanaWallet' }) -const publicKey = solanaWallet.publicKey - -// Get private key for Solana -const privateKey = await provider.request({ method: 'solanaPrivateKey' }) -``` - -### Other Blockchains - -Access the raw private key for any blockchain integration: - -```javascript -// Get the raw private key -const privateKey = await provider.request({ method: 'private_key' }) - -// Use with your preferred blockchain library -// Example: Bitcoin, Cosmos, etc. -``` - -## Key Features - -### Custom Authentication Only - -The Node.js SDK **only supports custom authentication**. You must: - -1. Set up a custom verifier in the Web3Auth Dashboard -2. Configure your authentication flow -3. Generate valid ID tokens for users -4. Use the verifier name and ID token in the connect method - -### Single Key Share - -The SDK operates with a single key share, making it: - -- **Custodial**: You have direct access to user private keys -- **Stateless**: No session state management required -- **Backend-optimized**: Perfect for server-side operations - -### Private Key Export - -Private key export can be controlled through the Web3Auth Dashboard: - -- Enable/disable private key export -- Control which methods return private keys -- Set up additional security measures - -## Security Considerations - -### Client ID Protection - -- Store client ID in environment variables -- Use different client IDs for different environments -- Rotate client IDs regularly in production - -### Network Configuration - -- Use `sapphire_mainnet` for production -- Use `sapphire_devnet` for development and testing -- Never use mainnet for testing - -### Session Management - -- Set appropriate session timeouts -- Implement session cleanup -- Monitor for unusual session patterns - -## Next Steps - -- **[Usage Guide](./usage)**: Learn about stateless authentication and blockchain operations -- **[Connect Method](./usage/connect)**: Detailed authentication implementation -- **[Private Key Access](./usage/private-key)**: Extract keys for blockchain operations -- **[EVM Integration](./usage/evm-integration)**: Ethereum and EVM-compatible chains -- **[Examples](./examples)**: Complete implementation examples and production patterns - - - - -## Usage Overview - -The MetaMask Embedded Wallets Node.js SDK provides stateless authentication and blockchain operations for backend applications. Unlike frontend SDKs, this is designed for **per-request authentication** without persistent sessions. - -## Core Methods - -### Authentication & Connection - -- **[connect](./connect)** - Authenticate users and establish provider connection (stateless) - -### Blockchain Operations - -- **[Private Key Access](./private-key)** - Extract private keys for blockchain operations -- **[EVM Integration](./evm-integration)** - Work with Ethereum and EVM-compatible chains - -## Important Notes - -### Stateless Design - -The Node.js SDK is **stateless** and **sessionless** by design: - -- No persistent user sessions -- No `getUserInfo()` or `logout()` methods -- Each request requires re-authentication -- Perfect for backend APIs and microservices - -## Common Usage Pattern +// focus-start +const { Web3Auth } = require('@web3auth/node-sdk') +// focus-end -The typical flow when using the Node.js SDK: +// Dashboard Registration +const clientId = + 'BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ' -```javascript -const { Web3Auth } = require('@web3auth/node-sdk') -const { EthereumPrivateKeyProvider } = require('@web3auth/ethereum-provider') - -// 1. Configure provider (once, during app startup) -const privateKeyProvider = new EthereumPrivateKeyProvider({ - config: { - chainConfig: { - chainNamespace: 'eip155', - chainId: '0x1', - rpcTarget: 'https://rpc.ankr.com/eth', - }, - }, -}) +// Auth Connection +const authConnectionId = 'w3a-node-demo' -// 2. Initialize Web3Auth (once, during app startup) +// focus-start const web3auth = new Web3Auth({ - clientId: 'YOUR_CLIENT_ID', + clientId, web3AuthNetwork: 'sapphire_mainnet', - privateKeyProvider, }) await web3auth.init() +// focus-end + +const privateKey = await fs.readFile('privateKey.pem', 'utf8') + +var idToken = jwt.sign( + { + sub: '9fcd68c4-af50-4dd7-adf6-abd12a13cb32', + name: 'Web3Auth DevRel Team', + email: 'devrel@web3auth.io', + aud: 'urn:api-web3auth-io', // -> to be used in Custom Authentication as JWT Field + iss: 'https://web3auth.io', // -> to be used in Custom Authentication as JWT Field + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 60 * 60, + }, + privateKey, + { algorithm: 'RS256', keyid: '2ma4enu1kdvw5bo9xsfpi3gcjzrt6q78yl0h' } +) -// 3. Connect user (per request) -const provider = await web3auth.connect({ - verifier: 'YOUR_VERIFIER', - verifierId: 'user@example.com', - idToken: 'JWT_TOKEN', -}) - -// 4. Use provider for blockchain operations -const privateKey = await provider.request({ method: 'eth_private_key' }) - -// 5. Perform blockchain operations with preferred library -``` - -## Error Handling - -Always implement proper error handling when using the SDK: +console.log('\x1b[33m%s\x1b[0m', 'JWT Token:', idToken) -```javascript -try { - const provider = await web3auth.connect({ - verifier: 'YOUR_VERIFIER', - verifierId: 'user@example.com', - idToken: 'JWT_TOKEN', - }) - - // Success - proceed with blockchain operations - const privateKey = await provider.request({ method: 'eth_private_key' }) -} catch (error) { - console.error('Authentication failed:', error.message) - // Handle authentication errors -} +// focus-start +const result = await web3auth.connect({ + authConnectionId, + idToken, +}) +// focus-end ``` - -## Best Practices - -1. **Initialize once**: Call `init()` during application startup, not per request -2. **Stateless requests**: Each user authentication is independent -3. **Handle errors**: Always wrap SDK calls in try-catch blocks -4. **Secure storage**: Protect private keys and never log them -5. **Rate limiting**: Implement rate limiting for authentication endpoints -6. **Token validation**: Validate JWT tokens before passing to connect method -7. **Provider reuse**: Create providers once, reuse for multiple users diff --git a/embedded-wallets/sdk/node/blockchain-connection.mdx b/embedded-wallets/sdk/node/blockchain-connection.mdx new file mode 100644 index 00000000000..d023052224f --- /dev/null +++ b/embedded-wallets/sdk/node/blockchain-connection.mdx @@ -0,0 +1,139 @@ +--- +title: Blockchain Connection +description: 'Integrate with Blockchain Networks' +--- + +Web3Auth Node SDK supports multiple blockchain networks through different integration methods. On a successful connection, it returns a `result` object containing the provider/ signer for blockchain operations. + +By default you get the standard Web3Auth provider, with the ability to get the private key, if enabled from the dashboard. You additionally get the signer for EVM and Solana chains, to be able to do transactions without the need to use the private key provider directly. + +### Object Structure + +```typescript +import type { TransactionSigner } from '@solana/signers' +import type { Wallet } from 'ethers' +import type { CHAIN_NAMESPACES, IBaseProvider } from '@web3auth/no-modal' +export type PrivateKeyProvider = IBaseProvider + +export type WalletResult = + | { + chainNamespace: typeof CHAIN_NAMESPACES.SOLANA + provider: PrivateKeyProvider + signer: TransactionSigner + } + | { + chainNamespace: typeof CHAIN_NAMESPACES.EIP155 + provider: PrivateKeyProvider + signer: Wallet + } + | { + chainNamespace: typeof CHAIN_NAMESPACES.OTHER + provider: PrivateKeyProvider + signer: null + } +``` + +### EVM Chains (Ethereum, Polygon, BSC, etc.) + +Use the provider with ethers.js or convert to viem: + +```javascript +// focus-next-line +const { ethers } = require('ethers') + +// Get Ethereum Accounts +const getAccounts = async signer => { + try { + // focus-next-line + const address = await signer.getAddress() + console.log('\x1b[33m%s\x1b[0m', 'Accounts:', address) + } catch (error) { + console.error('\x1b[33m%s\x1b[0m', 'Error fetching accounts:', error) + } +} + +// Get Ethereum Balance +const getBalance = async signer => { + try { + // focus-start + const address = await signer.getAddress() + const balance = ethers.formatEther( + await signer.provider.getBalance(address) // Balance is in wei + ) + // focus-end + console.log('\x1b[33m%s\x1b[0m', 'Balance:', balance, 'ETH') + } catch (error) { + console.error('\x1b[33m%s\x1b[0m', 'Error fetching balance:', error) + } +} + +// Sign Ethereum Message +const signMessage = async (signer, message) => { + try { + // focus-next-line + const signature = await signer.signMessage(message) + console.log('\x1b[33m%s\x1b[0m', 'Signed Message:', signature) + } catch (error) { + console.error('\x1b[33m%s\x1b[0m', 'Error signing message:', error) + } +} + +await getAccounts(result.signer) // Get Ethereum Accounts +await getBalance(result.signer) // Get Ethereum Balance +await signMessage(result.signer, 'Hello Web3Auth!') // Sign a Message +``` + +### Solana Integration + +Access Solana wallet functionality: + +```javascript +const { LAMPORTS_PER_SOL, Connection, PublicKey } = require('@solana/web3.js') +const { createSignableMessage } = require('@solana/signers') + +// Get Solana Accounts +const getAccounts = async signer => { + try { + // focus-next-line + const publicKey = signer.address + console.log('\x1b[33m%s\x1b[0m', 'Public Key:', publicKey) + } catch (error) { + console.error('\x1b[33m%s\x1b[0m', 'Error fetching accounts:', error) + } +} + +// Get Solana Balance +const getBalance = async signer => { + try { + // focus-start + const publicKey = new PublicKey(signer.address) + const connection = new Connection('https://api.devnet.solana.com') + const balance = (await connection.getBalance(publicKey)) / LAMPORTS_PER_SOL + // focus-end + console.log('\x1b[33m%s\x1b[0m', 'Balance:', balance, 'SOL') + } catch (error) { + console.error('\x1b[33m%s\x1b[0m', 'Error fetching balance:', error) + } +} + +// Sign Solana Message +const signMessage = async (signer, message) => { + try { + // focus-start + const messageBytes = new TextEncoder().encode(message) + + // Create a SignableMessage object from Uint8Array + const signableMessage = createSignableMessage(messageBytes) + + const [signature] = await signer.signMessages([signableMessage]) + // focus-end + console.log('\x1b[33m%s\x1b[0m', 'Signed Message:', signature) + } catch (error) { + console.error('\x1b[33m%s\x1b[0m', 'Error signing message:', error) + } +} + +await getAccounts(result.signer) // Get Solana Accounts +await getBalance(result.signer) // Get Solana Balance +await signMessage(result.signer, 'Hello Web3Auth!') // Sign a Message +``` diff --git a/embedded-wallets/sdk/node/connect.mdx b/embedded-wallets/sdk/node/connect.mdx index 566f06e6e05..14cc5bd926f 100644 --- a/embedded-wallets/sdk/node/connect.mdx +++ b/embedded-wallets/sdk/node/connect.mdx @@ -1,16 +1,25 @@ --- -title: connect +title: Connect description: 'Authenticate users and establish provider connection' --- ## Overview -The `connect` method is the primary authentication method for the Node.js SDK. It authenticates users using custom authentication and returns a provider for blockchain operations. +The `connect` method is the primary authentication method for the Node.js SDK. It authenticates users using custom authentication and returns a result object containing the provider/ signer for blockchain operations. + +:::warning Prerequisites + +- You need to have a custom authentication setup on the dashboard. +- You need to have a freshly generated JWT token from your authentication system in your backend. + +Read more about [Custom Authentication](/embedded-wallets/authentication/custom-connections/custom-jwt/). + +::: ## Usage ```javascript -const provider = await web3auth.connect(loginParams) +const result = await web3auth.connect(loginParams) ``` ## Parameters @@ -18,465 +27,145 @@ const provider = await web3auth.connect(loginParams) ### LoginParams ```typescript -interface LoginParams { - verifier: string // Your custom verifier name - verifierId: string // User's unique identifier - idToken: string // Valid JWT token - chainId?: string // Optional chain ID to connect to +export type LoginParams = { + idToken: string + authConnectionId: string + userId?: string + userIdField?: string + isUserIdCaseSensitive?: boolean + groupedAuthConnectionId?: string } ``` -| Parameter | Type | Description | Required | -| ------------ | -------- | ----------------------------------------------------- | -------- | -| `verifier` | `string` | Custom verifier name from Web3Auth Dashboard | ✅ | -| `verifierId` | `string` | Unique identifier for the user (email, user ID, etc.) | ✅ | -| `idToken` | `string` | Valid JWT token from your authentication system | ✅ | -| `chainId` | `string` | Blockchain chain ID to connect to | ❌ | +| Parameter | Type | Description | +| -------------------------- | --------- | ----------------------------------------------------------------------------------------- | +| `authConnectionId` | `string` | Custom verifier name from Web3Auth Dashboard | +| `userId` | `string` | Unique identifier for the user (email, user ID, etc.) | +| `idToken?` | `string` | Valid JWT token from your authentication system | +| `groupedAuthConnectionId?` | `string` | Grouped auth connection ID | +| `userIdField?` | `string` | User's unique identifier field name. Add here if not set on the dashboard. | +| `isUserIdCaseSensitive?` | `boolean` | Whether the user id field is case sensitive or not. Add here if not set on the dashboard. | ## Return Value -Returns a `SafeEventEmitterProvider` that can be used for blockchain operations. - -## Examples - -### Basic Authentication - -```javascript -const { Web3Auth } = require('@web3auth/node-sdk') - -const web3auth = new Web3Auth({ - clientId: 'YOUR_CLIENT_ID', - web3AuthNetwork: 'sapphire_mainnet', -}) - -await web3auth.init() - -// Authenticate user -const provider = await web3auth.connect({ - verifier: 'my-custom-verifier', - verifierId: 'user@example.com', - idToken: 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...', -}) - -console.log('User authenticated successfully') -``` - -### Authentication with Specific Chain - -```javascript -// Connect to Polygon mainnet -const provider = await web3auth.connect({ - verifier: 'my-custom-verifier', - verifierId: 'user123', - idToken: 'JWT_TOKEN_HERE', - chainId: '0x89', // Polygon mainnet -}) -``` +Returns a `result` object containing the provider/ signer for blockchain operations. By default you get the standard Web3Auth provider, with the ability to get the private key, if enabled from the dashboard. You additionally get the signer for EVM and Solana chains, to be able to do transactions without the need to use the private key provider directly. -### Authentication with Error Handling +### Object Structure -```javascript -async function authenticateUser(userToken, userId) { - try { - const provider = await web3auth.connect({ - verifier: 'my-auth-verifier', - verifierId: userId, - idToken: userToken, - }) - - console.log('Authentication successful') - return provider - } catch (error) { - console.error('Authentication failed:', error.message) - - // Handle specific error types - if (error.message.includes('Invalid token')) { - throw new Error('Invalid authentication token') - } else if (error.message.includes('User not found')) { - throw new Error('User not found in verifier') - } else { - throw new Error('Authentication service unavailable') +```typescript +import type { TransactionSigner } from '@solana/signers' +import type { Wallet } from 'ethers' +import type { CHAIN_NAMESPACES, IBaseProvider } from '@web3auth/no-modal' +export type PrivateKeyProvider = IBaseProvider + +export type WalletResult = + | { + chainNamespace: typeof CHAIN_NAMESPACES.SOLANA + provider: PrivateKeyProvider + signer: TransactionSigner + } + | { + chainNamespace: typeof CHAIN_NAMESPACES.EIP155 + provider: PrivateKeyProvider + signer: Wallet + } + | { + chainNamespace: typeof CHAIN_NAMESPACES.OTHER + provider: PrivateKeyProvider + signer: null } - } -} -``` - -## ID Token Requirements - -The `idToken` must be a valid JWT token that includes: - -### Required Claims - -```json -{ - "iss": "your-issuer", // Token issuer - "aud": "your-audience", // Token audience - "sub": "user-subject-id", // Subject (user ID) - "iat": 1234567890, // Issued at time - "exp": 1234567999 // Expiration time -} -``` - -### Example JWT Payload - -```json -{ - "iss": "https://auth.yourapp.com", - "aud": "your-web3auth-client-id", - "sub": "user123", - "email": "user@example.com", - "name": "John Doe", - "iat": 1640995200, - "exp": 1641081600 -} ``` -## Chain ID Options - -If no `chainId` is specified, the SDK will use the default chain configured in your Web3Auth Dashboard. - -### Popular Chain IDs - -| Blockchain | Chain ID | Network | -| ---------- | --------- | -------------- | -| Ethereum | `0x1` | Mainnet | -| Ethereum | `0x5` | Goerli Testnet | -| Polygon | `0x89` | Mainnet | -| Polygon | `0x80001` | Mumbai Testnet | -| BSC | `0x38` | Mainnet | -| BSC | `0x61` | Testnet | -| Arbitrum | `0xa4b1` | Mainnet | -| Optimism | `0xa` | Mainnet | - -## Working with the Provider +## Examples -Once connected, use the provider for blockchain operations: +### Custom JWT Token Authentication ```javascript -// Get private key -const privateKey = await provider.request({ method: 'eth_private_key' }) - -// Get account address -const accounts = await provider.request({ method: 'eth_accounts' }) -const address = accounts[0] - -// Sign a message -const signature = await provider.request({ - method: 'personal_sign', - params: ['Hello Web3Auth!', address], -}) -``` - -## Error Types - -The `connect` method can throw various errors: - -| Error | Description | Solution | -| -------------------- | --------------------------------- | -------------------------------------- | -| `Invalid token` | JWT token is malformed or expired | Refresh and provide a new token | -| `Verifier not found` | Verifier name doesn't exist | Check verifier name in dashboard | -| `User not found` | VerifierId not found in verifier | Ensure user exists in your auth system | -| `Network error` | Connection to Web3Auth failed | Check network connectivity | -| `Invalid client ID` | Client ID is incorrect | Verify client ID in dashboard | - -## Next Steps +const { Web3Auth } = require('@web3auth/node-sdk') -After successful authentication: +// Dashboard Registration +const clientId = + 'BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ' -- [Access private keys](./private-key) -- [Integrate with blockchains](./evm-integration) +// focus-start +// Auth Connection +const authConnectionId = 'w3a-node-demo' +// focus-end -## Custom Authentication Implementation +const web3auth = new Web3Auth({ + clientId, + web3AuthNetwork: 'sapphire_mainnet', +}) -Since the Node.js SDK only supports custom authentication, here are detailed implementation examples for different scenarios: +await web3auth.init() -### Creating JWT Tokens +// focus-start +const privateKey = await fs.readFile('privateKey.pem', 'utf8') -```javascript -const jwt = require('jsonwebtoken') - -function createJWTForUser(userId, userEmail, userName) { - const payload = { - iss: process.env.JWT_ISSUER, // Your issuer URL - aud: process.env.WEB3AUTH_CLIENT_ID, // Your Web3Auth client ID - sub: userId, // User's unique identifier - email: userEmail, - name: userName, +var idToken = jwt.sign( + { + sub: '9fcd68c4-af50-4dd7-adf6-abd12a13cb32', + name: 'Web3Auth DevRel Team', + email: 'devrel@web3auth.io', + aud: 'urn:api-web3auth-io', // -> to be used in Custom Authentication as JWT Field + iss: 'https://web3auth.io', // -> to be used in Custom Authentication as JWT Field iat: Math.floor(Date.now() / 1000), - exp: Math.floor(Date.now() / 1000) + 60 * 60, // 1 hour expiration - } - - return jwt.sign(payload, process.env.JWT_SECRET, { algorithm: 'HS256' }) -} -``` - -### Auth0 Integration - -```javascript -const { auth0 } = require('auth0') - -class Auth0Web3AuthIntegration { - constructor(auth0Config, web3authConfig) { - this.auth0 = new auth0.AuthenticationApi({ - domain: auth0Config.domain, - clientId: auth0Config.clientId, - clientSecret: auth0Config.clientSecret, - }) - - this.web3auth = new Web3Auth(web3authConfig) - } - - async initialize() { - await this.web3auth.init() - } - - async authenticateWithAuth0Token(auth0Token) { - try { - // Verify Auth0 token - const userInfo = await this.auth0.getProfile(auth0Token) - - // Create custom JWT for Web3Auth - const customJWT = this.createCustomJWT(userInfo) - - // Authenticate with Web3Auth - const provider = await this.web3auth.connect({ - verifier: 'auth0-verifier', - verifierId: userInfo.sub, - idToken: customJWT, - }) - - return { provider, userInfo } - } catch (error) { - console.error('Auth0 authentication failed:', error) - throw error - } - } - - createCustomJWT(auth0UserInfo) { - const payload = { - iss: process.env.CUSTOM_JWT_ISSUER, - aud: process.env.WEB3AUTH_CLIENT_ID, - sub: auth0UserInfo.sub, - email: auth0UserInfo.email, - name: auth0UserInfo.name, - picture: auth0UserInfo.picture, - iat: Math.floor(Date.now() / 1000), - exp: Math.floor(Date.now() / 1000) + 3600, - } + exp: Math.floor(Date.now() / 1000) + 60 * 60, + }, + privateKey, + { algorithm: 'RS256', keyid: '2ma4enu1kdvw5bo9xsfpi3gcjzrt6q78yl0h' } +) - return jwt.sign(payload, process.env.JWT_SECRET) - } -} -``` - -### Firebase Integration +console.log('\x1b[33m%s\x1b[0m', 'JWT Token:', idToken) -```javascript -const admin = require('firebase-admin') - -class FirebaseWeb3AuthIntegration { - constructor(firebaseConfig, web3authConfig) { - admin.initializeApp({ - credential: admin.credential.cert(firebaseConfig.serviceAccount), - databaseURL: firebaseConfig.databaseURL, - }) - - this.web3auth = new Web3Auth(web3authConfig) - } - - async initialize() { - await this.web3auth.init() - } - - async authenticateWithFirebaseToken(firebaseToken) { - try { - // Verify Firebase ID token - const decodedToken = await admin.auth().verifyIdToken(firebaseToken) - - // Get user record for additional info - const userRecord = await admin.auth().getUser(decodedToken.uid) - - // Create custom JWT for Web3Auth - const customJWT = this.createCustomJWT(decodedToken, userRecord) - - // Authenticate with Web3Auth - const provider = await this.web3auth.connect({ - verifier: 'firebase-verifier', - verifierId: decodedToken.uid, - idToken: customJWT, - }) - - return { provider, firebaseUser: userRecord } - } catch (error) { - console.error('Firebase authentication failed:', error) - throw error - } - } - - createCustomJWT(decodedToken, userRecord) { - const payload = { - iss: process.env.CUSTOM_JWT_ISSUER, - aud: process.env.WEB3AUTH_CLIENT_ID, - sub: decodedToken.uid, - email: userRecord.email, - name: userRecord.displayName, - picture: userRecord.photoURL, - email_verified: userRecord.emailVerified, - firebase: { - sign_in_provider: decodedToken.firebase.sign_in_provider, - identities: decodedToken.firebase.identities, - }, - iat: Math.floor(Date.now() / 1000), - exp: Math.floor(Date.now() / 1000) + 3600, - } - - return jwt.sign(payload, process.env.JWT_SECRET) - } -} +const result = await web3auth.connect({ + authConnectionId, + idToken, +}) +// focus-end ``` -### Express.js API Integration +### Firebase Authentication ```javascript -const express = require('express') +const { Web3Auth } = require('@web3auth/node-sdk') -const app = express() -app.use(express.json()) +// Dashboard Registration +const clientId = + 'BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ' -// Store Web3Auth instances per user (use proper session store in production) -const userSessions = new Map() +// focus-start +// Auth Connection +const authConnectionId = 'w3a-firebase-demo' +// focus-end -// Initialize Web3Auth const web3auth = new Web3Auth({ - clientId: process.env.WEB3AUTH_CLIENT_ID, + clientId, web3AuthNetwork: 'sapphire_mainnet', }) await web3auth.init() -// Authentication endpoint -app.post('/api/auth/web3auth', async (req, res) => { - try { - const { userId, userEmail, userName } = req.body - - // Validate user (implement your validation logic) - const isValidUser = await validateUser(userId, userEmail) - if (!isValidUser) { - return res.status(401).json({ error: 'Invalid user credentials' }) - } - - // Create JWT - const idToken = createJWTForUser(userId, userEmail, userName) - - // Authenticate with Web3Auth - const provider = await web3auth.connect({ - verifier: 'api-verifier', - verifierId: userId, - idToken: idToken, - }) - - // Store session - userSessions.set(userId, { provider, timestamp: Date.now() }) - - // Access blockchain operations - const privateKey = await provider.request({ method: 'eth_private_key' }) - - res.json({ - success: true, - address: await provider.request({ method: 'eth_accounts' }), - sessionId: userId, - }) - } catch (error) { - console.error('Authentication error:', error) - res.status(500).json({ error: 'Authentication failed' }) - } +// focus-start +const app = initializeApp({ + apiKey: 'AIzaSyB0nd9YsPLu-tpdCrsXn8wgsWVAiYEpQ_E', + authDomain: 'web3auth-oauth-logins.firebaseapp.com', + projectId: 'web3auth-oauth-logins', + storageBucket: 'web3auth-oauth-logins.appspot.com', + messagingSenderId: '461819774167', + appId: '1:461819774167:web:e74addfb6cc88f3b5b9c92', }) -// Get wallet address endpoint -app.get('/api/wallet/:userId/address', async (req, res) => { - try { - const { userId } = req.params - const session = userSessions.get(userId) - - if (!session) { - return res.status(401).json({ error: 'No active session' }) - } - - const accounts = await session.provider.request({ method: 'eth_accounts' }) +const auth = getAuth(app) +const res = await signInWithEmailAndPassword(auth, 'custom+jwt@firebase.login', 'Testing@123') +console.log(res) +const idToken = await res.user.getIdToken(true) - res.json({ - address: accounts[0], - userId: userId, - }) - } catch (error) { - console.error('Address retrieval error:', error) - res.status(500).json({ error: 'Failed to get address' }) - } -}) +console.log('\x1b[33m%s\x1b[0m', 'JWT Token:', idToken) -app.listen(3000, () => { - console.log('Server running on port 3000') +const result = await web3auth.connect({ + authConnectionId, + idToken, }) -``` - -### JWT Validation - -```javascript -function validateJWTPayload(payload) { - // Required fields - if (!payload.sub) throw new Error('Subject (sub) is required') - if (!payload.iss) throw new Error('Issuer (iss) is required') - if (!payload.aud) throw new Error('Audience (aud) is required') - - // Expiration check - if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) { - throw new Error('Token has expired') - } - - // Issuer validation - if (payload.iss !== process.env.JWT_ISSUER) { - throw new Error('Invalid token issuer') - } - - return true -} -``` - -### Error Handling for Authentication - -```javascript -async function robustAuthentication(userId, userInfo) { - const maxRetries = 3 - let lastError - - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - const idToken = createJWTForUser(userId, userInfo.email, userInfo.name) - - const provider = await web3auth.connect({ - verifier: 'robust-verifier', - verifierId: userId, - idToken: idToken, - }) - - console.log(`Authentication successful on attempt ${attempt}`) - return provider - } catch (error) { - lastError = error - console.log(`Authentication attempt ${attempt} failed:`, error.message) - - // Don't retry on certain errors - if (error.message.includes('Invalid verifier') || error.message.includes('Malformed token')) { - throw error - } - - // Wait before retry - if (attempt < maxRetries) { - await new Promise(resolve => setTimeout(resolve, 1000 * attempt)) - } - } - } - - throw new Error(`Authentication failed after ${maxRetries} attempts: ${lastError.message}`) -} +// focus-end ``` diff --git a/embedded-wallets/sdk/node/evm-integration.mdx b/embedded-wallets/sdk/node/evm-integration.mdx deleted file mode 100644 index f6a04c4f72e..00000000000 --- a/embedded-wallets/sdk/node/evm-integration.mdx +++ /dev/null @@ -1,433 +0,0 @@ ---- -title: EVM Integration -description: 'Integrate with Ethereum and EVM-compatible chains' ---- - -## Overview - -The Node.js SDK provides seamless integration with Ethereum Virtual Machine (EVM) compatible blockchains including Ethereum, Polygon, Binance Smart Chain, Arbitrum, Optimism, and more. - -## Supported EVM Chains - -| Blockchain | Chain ID | RPC Endpoint | -| ---------------- | --------- | ------------------------------------------------ | -| Ethereum Mainnet | `0x1` | `https://rpc.ankr.com/eth` | -| Ethereum Goerli | `0x5` | `https://rpc.ankr.com/eth_goerli` | -| Polygon Mainnet | `0x89` | `https://rpc.ankr.com/polygon` | -| Polygon Mumbai | `0x80001` | `https://rpc.ankr.com/polygon_mumbai` | -| BSC Mainnet | `0x38` | `https://bsc-dataseed.binance.org` | -| BSC Testnet | `0x61` | `https://data-seed-prebsc-1-s1.binance.org:8545` | -| Arbitrum One | `0xa4b1` | `https://arb1.arbitrum.io/rpc` | -| Optimism | `0xa` | `https://mainnet.optimism.io` | - -## Integration with Ethers.js - -### Basic Setup - -```javascript -const { Web3Auth } = require('@web3auth/node-sdk') -const { ethers } = require('ethers') - -// Initialize Web3Auth -const web3auth = new Web3Auth({ - clientId: 'YOUR_CLIENT_ID', - web3AuthNetwork: 'sapphire_mainnet', -}) - -await web3auth.init() - -// Authenticate user -const provider = await web3auth.connect({ - verifier: 'my-custom-verifier', - verifierId: 'user@example.com', - idToken: 'JWT_TOKEN', - chainId: '0x1', // Ethereum mainnet -}) - -// Get private key and create wallet -const privateKey = await provider.request({ method: 'eth_private_key' }) -const wallet = new ethers.Wallet(privateKey) - -// Connect to RPC provider -const rpcProvider = new ethers.providers.JsonRpcProvider('https://rpc.ankr.com/eth') -const connectedWallet = wallet.connect(rpcProvider) - -console.log('Wallet Address:', await connectedWallet.getAddress()) -``` - -### Send Transaction - -```javascript -async function sendTransaction(toAddress, amount) { - try { - // Create transaction - const tx = { - to: toAddress, - value: ethers.utils.parseEther(amount), - gasLimit: 21000, - } - - // Send transaction - const txResponse = await connectedWallet.sendTransaction(tx) - console.log('Transaction Hash:', txResponse.hash) - - // Wait for confirmation - const receipt = await txResponse.wait() - console.log('Transaction confirmed in block:', receipt.blockNumber) - - return receipt - } catch (error) { - console.error('Transaction failed:', error.message) - throw error - } -} - -// Usage -await sendTransaction('0x742d35Cc6635C0532925a3b8138341B0F7E8a4e8', '0.1') -``` - -### Smart Contract Interaction - -```javascript -// ERC-20 Token ABI (simplified) -const erc20Abi = [ - 'function balanceOf(address owner) view returns (uint256)', - 'function transfer(address to, uint256 amount) returns (bool)', - 'function approve(address spender, uint256 amount) returns (bool)', - 'function symbol() view returns (string)', - 'function decimals() view returns (uint8)', -] - -async function interactWithERC20(tokenAddress) { - // Create contract instance - const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, connectedWallet) - - // Get token info - const symbol = await tokenContract.symbol() - const decimals = await tokenContract.decimals() - console.log(`Token: ${symbol}, Decimals: ${decimals}`) - - // Get balance - const balance = await tokenContract.balanceOf(connectedWallet.address) - console.log('Token Balance:', ethers.utils.formatUnits(balance, decimals)) - - // Transfer tokens - const transferTx = await tokenContract.transfer( - '0x742d35Cc6635C0532925a3b8138341B0F7E8a4e8', - ethers.utils.parseUnits('10', decimals) - ) - - await transferTx.wait() - console.log('Transfer completed:', transferTx.hash) -} - -// Usage with USDC on Ethereum -await interactWithERC20('0xA0b86a33E6441E51DBF5c4dF02a7b29fAdab0215') -``` - -## Integration with Viem - -### Basic Setup - -```javascript -const { createWalletClient, createPublicClient, http } = require('viem') -const { privateKeyToAccount } = require('viem/accounts') -const { mainnet, polygon } = require('viem/chains') - -// Get private key from Web3Auth -const privateKey = await provider.request({ method: 'eth_private_key' }) - -// Create account -const account = privateKeyToAccount(privateKey) - -// Create wallet client -const walletClient = createWalletClient({ - account, - chain: mainnet, - transport: http('https://rpc.ankr.com/eth'), -}) - -// Create public client for reading -const publicClient = createPublicClient({ - chain: mainnet, - transport: http('https://rpc.ankr.com/eth'), -}) - -console.log('Account Address:', account.address) -``` - -### Send Transaction with Viem - -```javascript -const { parseEther, formatEther } = require('viem') - -async function sendTransactionViem(toAddress, amount) { - try { - // Get current balance - const balance = await publicClient.getBalance({ - address: account.address, - }) - console.log('Current Balance:', formatEther(balance), 'ETH') - - // Send transaction - const hash = await walletClient.sendTransaction({ - to: toAddress, - value: parseEther(amount), - }) - - console.log('Transaction Hash:', hash) - - // Wait for confirmation - const receipt = await publicClient.waitForTransactionReceipt({ hash }) - console.log('Transaction confirmed in block:', receipt.blockNumber) - - return receipt - } catch (error) { - console.error('Transaction failed:', error.message) - throw error - } -} -``` - -### Smart Contract with Viem - -```javascript -const { getContract, parseUnits, formatUnits } = require('viem') - -// ERC-20 ABI -const erc20Abi = [ - { - inputs: [{ name: 'owner', type: 'address' }], - name: 'balanceOf', - outputs: [{ name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { name: 'to', type: 'address' }, - { name: 'amount', type: 'uint256' }, - ], - name: 'transfer', - outputs: [{ name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const - -async function erc20WithViem(tokenAddress) { - // Create contract instance - const contract = getContract({ - address: tokenAddress, - abi: erc20Abi, - publicClient, - walletClient, - }) - - // Read balance - const balance = await contract.read.balanceOf([account.address]) - console.log('Token Balance:', formatUnits(balance, 18)) - - // Write operation - const hash = await contract.write.transfer([ - '0x742d35Cc6635C0532925a3b8138341B0F7E8a4e8', - parseUnits('10', 18), - ]) - - const receipt = await publicClient.waitForTransactionReceipt({ hash }) - console.log('Transfer completed:', receipt.transactionHash) -} -``` - -## Multi-Chain Support - -### Chain Switching - -```javascript -async function connectToChain(chainId, authParams) { - // Connect to specific chain (stateless) - const provider = await web3auth.connect({ - ...authParams, - chainId, - }) - - return provider -} - -// Connect to Polygon -const polygonProvider = await connectToChain('0x89', { - verifier: 'my-custom-verifier', - verifierId: 'user@example.com', - idToken: 'JWT_TOKEN', -}) -``` - -### Multi-Chain Wallet Class - -```javascript -class MultiChainEVMWallet { - constructor(clientId) { - this.web3auth = new Web3Auth({ - clientId, - web3AuthNetwork: 'sapphire_mainnet', - }) - this.wallets = new Map() - } - - async initialize() { - await this.web3auth.init() - } - - async connectToChain(chainId, chainConfig) { - // Connect to specific chain - const provider = await this.web3auth.connect({ - verifier: 'my-custom-verifier', - verifierId: 'user@example.com', - idToken: 'JWT_TOKEN', - chainId, - }) - - // Create wallet for this chain - const privateKey = await provider.request({ method: 'eth_private_key' }) - const wallet = new ethers.Wallet(privateKey) - const connectedWallet = wallet.connect(new ethers.providers.JsonRpcProvider(chainConfig.rpcUrl)) - - this.wallets.set(chainId, { - provider, - wallet: connectedWallet, - config: chainConfig, - }) - - return connectedWallet - } - - getWallet(chainId) { - const walletData = this.wallets.get(chainId) - return walletData ? walletData.wallet : null - } - - async getBalances() { - const balances = {} - - for (const [chainId, walletData] of this.wallets) { - const balance = await walletData.wallet.getBalance() - balances[chainId] = { - balance: ethers.utils.formatEther(balance), - address: walletData.wallet.address, - network: walletData.config.name, - } - } - - return balances - } -} - -// Usage -const multiWallet = new MultiChainEVMWallet('YOUR_CLIENT_ID') -await multiWallet.initialize() - -// Connect to multiple chains -await multiWallet.connectToChain('0x1', { - name: 'Ethereum', - rpcUrl: 'https://rpc.ankr.com/eth', -}) - -await multiWallet.connectToChain('0x89', { - name: 'Polygon', - rpcUrl: 'https://rpc.ankr.com/polygon', -}) - -const balances = await multiWallet.getBalances() -console.log('Multi-chain balances:', balances) -``` - -## Gas Management - -### Estimate Gas - -```javascript -async function estimateAndSendTransaction(toAddress, amount) { - const tx = { - to: toAddress, - value: ethers.utils.parseEther(amount), - } - - // Estimate gas - const gasEstimate = await connectedWallet.estimateGas(tx) - console.log('Estimated Gas:', gasEstimate.toString()) - - // Get gas price - const gasPrice = await connectedWallet.getGasPrice() - console.log('Gas Price:', ethers.utils.formatUnits(gasPrice, 'gwei'), 'gwei') - - // Add gas settings to transaction - tx.gasLimit = gasEstimate.mul(120).div(100) // Add 20% buffer - tx.gasPrice = gasPrice - - // Send transaction - const txResponse = await connectedWallet.sendTransaction(tx) - return txResponse -} -``` - -### EIP-1559 (Type 2) Transactions - -```javascript -async function sendEIP1559Transaction(toAddress, amount) { - // Get fee data - const feeData = await connectedWallet.getFeeData() - - const tx = { - to: toAddress, - value: ethers.utils.parseEther(amount), - type: 2, // EIP-1559 - maxFeePerGas: feeData.maxFeePerGas, - maxPriorityFeePerGas: feeData.maxPriorityFeePerGas, - } - - const txResponse = await connectedWallet.sendTransaction(tx) - return txResponse -} -``` - -## Error Handling - -```javascript -async function robustEVMTransaction(toAddress, amount) { - try { - // Check balance first - const balance = await connectedWallet.getBalance() - const amountWei = ethers.utils.parseEther(amount) - - if (balance.lt(amountWei)) { - throw new Error('Insufficient balance') - } - - // Send transaction with retries - let retries = 3 - while (retries > 0) { - try { - const tx = await sendTransaction(toAddress, amount) - return tx - } catch (error) { - retries-- - - if (error.code === 'NONCE_EXPIRED' && retries > 0) { - console.log('Nonce expired, retrying...') - continue - } - - throw error - } - } - } catch (error) { - console.error('EVM transaction failed:', error.message) - throw error - } -} -``` - -## Next Steps - -- [Access private keys](./private-key) -- [Authenticate users](./connect) diff --git a/embedded-wallets/sdk/node/examples.mdx b/embedded-wallets/sdk/node/examples.mdx index 19ae77adfa0..a3aabe2b105 100644 --- a/embedded-wallets/sdk/node/examples.mdx +++ b/embedded-wallets/sdk/node/examples.mdx @@ -1,799 +1,11 @@ --- -title: Examples and Use Cases -description: 'Complete examples and use cases for the MetaMask Embedded Wallets Node.js SDK' +title: Examples +sidebar_label: Examples +description: 'Node Examples | Embedded Wallets' +hide_table_of_contents: true --- -## Overview +import ExampleCards from '@theme/ExampleCards' +import { pnpNodeExamples } from '@site/src/utils/example-maps' -This section provides complete, production-ready examples for common use cases with the Node.js SDK. Each example includes error handling, security considerations, and best practices. - -## AI Agent Examples - -### 1. Autonomous Trading Bot - -```javascript -const { Web3Auth } = require('@web3auth/node-sdk') -const { ethers } = require('ethers') - -class TradingBot { - constructor(config) { - this.web3auth = new Web3Auth({ - clientId: config.clientId, - web3AuthNetwork: 'sapphire_mainnet', - }) - - this.config = config - this.wallet = null - this.provider = null - } - - async initialize() { - await this.web3auth.init() - - // Authenticate the bot - const provider = await this.web3auth.connect({ - verifier: this.config.verifier, - verifierId: this.config.botId, - idToken: this.config.botToken, - }) - - // Set up wallet - const privateKey = await provider.request({ method: 'eth_private_key' }) - this.wallet = new ethers.Wallet(privateKey) - this.provider = new ethers.providers.JsonRpcProvider(this.config.rpcUrl) - this.wallet = this.wallet.connect(this.provider) - - console.log('Trading bot initialized:', this.wallet.address) - } - - async executeTradeStrategy() { - try { - const balance = await this.wallet.getBalance() - console.log('Current balance:', ethers.utils.formatEther(balance), 'ETH') - - // Example: Simple DEX swap logic - const shouldTrade = await this.analyzeMarket() - - if (shouldTrade) { - await this.executeTrade(shouldTrade) - } - } catch (error) { - console.error('Trade execution failed:', error) - await this.handleTradingError(error) - } - } - - async analyzeMarket() { - // Implement your trading logic here - // This is a simplified example - const price = await this.getCurrentPrice('ETH/USDC') - const trend = await this.getTrend() - - return { - action: 'buy', - amount: '0.1', - token: 'USDC', - confidence: 0.8, - } - } - - async executeTrade(tradeParams) { - console.log('Executing trade:', tradeParams) - - // Example DEX interaction (Uniswap V3) - const uniswapRouter = new ethers.Contract( - '0xE592427A0AEce92De3Edee1F18E0157C05861564', - [ - 'function exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) external payable returns (uint256)', - ], - this.wallet - ) - - const params = { - tokenIn: '0xA0b86a33E6441E51DBF5c4dF02a7b29fAdab0215', // USDC - tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH - fee: 3000, - recipient: this.wallet.address, - deadline: Math.floor(Date.now() / 1000) + 600, - amountIn: ethers.utils.parseUnits(tradeParams.amount, 6), - amountOutMinimum: 0, - sqrtPriceLimitX96: 0, - } - - const tx = await uniswapRouter.exactInputSingle(params) - await tx.wait() - - console.log('Trade completed:', tx.hash) - } - - async getCurrentPrice(pair) { - // Implement price fetching logic - return 2000 // Simplified - } - - async getTrend() { - // Implement trend analysis - return 'bullish' - } - - async handleTradingError(error) { - // Implement error handling and notifications - console.log('Notifying administrators of trading error') - } -} - -// Usage -const bot = new TradingBot({ - clientId: process.env.WEB3AUTH_CLIENT_ID, - verifier: 'trading-bot-verifier', - botId: 'bot-001', - botToken: process.env.BOT_JWT_TOKEN, - rpcUrl: 'https://rpc.ankr.com/eth', -}) - -await bot.initialize() - -// Run trading loop -setInterval(async () => { - await bot.executeTradeStrategy() -}, 60000) // Every minute -``` - -### 2. DeFi Yield Farming Bot - -```javascript -class YieldFarmingBot { - constructor(config) { - this.web3auth = new Web3Auth(config.web3auth) - this.config = config - this.protocols = new Map() // Store protocol interfaces - } - - async initialize() { - await this.web3auth.init() - - const provider = await this.web3auth.connect({ - verifier: this.config.verifier, - verifierId: this.config.botId, - idToken: this.config.botToken, - }) - - const privateKey = await provider.request({ method: 'eth_private_key' }) - this.wallet = new ethers.Wallet(privateKey).connect( - new ethers.providers.JsonRpcProvider(this.config.rpcUrl) - ) - - // Initialize protocol interfaces - await this.initializeProtocols() - } - - async initializeProtocols() { - // Compound - this.protocols.set('compound', { - comptroller: new ethers.Contract( - '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', - ['function enterMarkets(address[] calldata cTokens) external returns (uint[] memory)'], - this.wallet - ), - // Add other protocol contracts - }) - - // Aave - this.protocols.set('aave', { - lendingPool: new ethers.Contract( - '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', - [ - 'function deposit(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)', - ], - this.wallet - ), - }) - } - - async optimizeYield() { - try { - // 1. Analyze current positions - const positions = await this.getCurrentPositions() - - // 2. Find best yield opportunities - const opportunities = await this.scanYieldOpportunities() - - // 3. Execute rebalancing if profitable - const rebalancePlan = this.calculateOptimalRebalance(positions, opportunities) - - if (rebalancePlan.profitable) { - await this.executeRebalance(rebalancePlan) - } - } catch (error) { - console.error('Yield optimization failed:', error) - } - } - - async getCurrentPositions() { - const positions = [] - - // Check Compound positions - // Check Aave positions - // Check Uniswap LP positions - - return positions - } - - async scanYieldOpportunities() { - const opportunities = [] - - // Scan lending rates - const compoundRate = await this.getCompoundAPY('USDC') - const aaveRate = await this.getAaveAPY('USDC') - - opportunities.push( - { protocol: 'compound', asset: 'USDC', apy: compoundRate }, - { protocol: 'aave', asset: 'USDC', apy: aaveRate } - ) - - return opportunities.sort((a, b) => b.apy - a.apy) - } - - calculateOptimalRebalance(positions, opportunities) { - // Implement rebalancing logic - return { - profitable: true, - moves: [{ from: 'compound', to: 'aave', asset: 'USDC', amount: '1000' }], - expectedGain: 0.05, // 5% APY improvement - } - } - - async executeRebalance(plan) { - for (const move of plan.moves) { - console.log(`Moving ${move.amount} ${move.asset} from ${move.from} to ${move.to}`) - - // Withdraw from source - await this.withdrawFromProtocol(move.from, move.asset, move.amount) - - // Deposit to destination - await this.depositToProtocol(move.to, move.asset, move.amount) - } - } - - async getCompoundAPY(asset) { - // Implement Compound APY fetching - return 3.5 // 3.5% APY - } - - async getAaveAPY(asset) { - // Implement Aave APY fetching - return 4.2 // 4.2% APY - } -} -``` - -## Backend Service Examples - -### 1. User Wallet Service - -```javascript -const express = require('express') -const { Web3Auth } = require('@web3auth/node-sdk') - -class UserWalletService { - constructor() { - this.app = express() - this.userSessions = new Map() - this.setupMiddleware() - this.setupRoutes() - } - - setupMiddleware() { - this.app.use(express.json()) - this.app.use(this.authMiddleware.bind(this)) - this.app.use(this.rateLimitMiddleware.bind(this)) - } - - async authMiddleware(req, res, next) { - if (req.path.startsWith('/api/public/')) { - return next() - } - - const token = req.headers.authorization?.replace('Bearer ', '') - if (!token) { - return res.status(401).json({ error: 'Authentication required' }) - } - - try { - const decoded = jwt.verify(token, process.env.JWT_SECRET) - req.user = decoded - next() - } catch (error) { - res.status(401).json({ error: 'Invalid token' }) - } - } - - rateLimitMiddleware(req, res, next) { - // Implement rate limiting - next() - } - - setupRoutes() { - // Create wallet for user - this.app.post('/api/wallet/create', async (req, res) => { - try { - const userId = req.user.sub - const { email, name } = req.user - - // Check if wallet already exists - if (this.userSessions.has(userId)) { - return res.status(400).json({ error: 'Wallet already exists' }) - } - - // Create Web3Auth instance for user - const web3auth = new Web3Auth({ - clientId: process.env.WEB3AUTH_CLIENT_ID, - web3AuthNetwork: 'sapphire_mainnet', - }) - - await web3auth.init() - - // Create JWT for user - const idToken = this.createJWTForUser(userId, email, name) - - // Connect with Web3Auth - const provider = await web3auth.connect({ - verifier: 'wallet-service-verifier', - verifierId: userId, - idToken: idToken, - }) - - // Get wallet address - const accounts = await provider.request({ method: 'eth_accounts' }) - const address = accounts[0] - - // Store session - this.userSessions.set(userId, { - web3auth, - provider, - address, - createdAt: new Date(), - }) - - res.json({ - success: true, - address: address, - message: 'Wallet created successfully', - }) - } catch (error) { - console.error('Wallet creation failed:', error) - res.status(500).json({ error: 'Failed to create wallet' }) - } - }) - - // Get wallet info - this.app.get('/api/wallet/info', async (req, res) => { - try { - const userId = req.user.sub - const session = this.userSessions.get(userId) - - if (!session) { - return res.status(404).json({ error: 'Wallet not found' }) - } - - // Get balance - const balance = await session.provider.request({ - method: 'eth_getBalance', - params: [session.address, 'latest'], - }) - - res.json({ - address: session.address, - balance: ethers.utils.formatEther(balance), - createdAt: session.createdAt, - }) - } catch (error) { - console.error('Failed to get wallet info:', error) - res.status(500).json({ error: 'Failed to get wallet info' }) - } - }) - - // Send transaction - this.app.post('/api/wallet/send', async (req, res) => { - try { - const userId = req.user.sub - const { to, amount, data } = req.body - const session = this.userSessions.get(userId) - - if (!session) { - return res.status(404).json({ error: 'Wallet not found' }) - } - - // Validate transaction - if (!ethers.utils.isAddress(to)) { - return res.status(400).json({ error: 'Invalid recipient address' }) - } - - // Create transaction - const tx = { - to: to, - value: ethers.utils.parseEther(amount), - data: data || '0x', - } - - // Sign and send transaction - const txHash = await session.provider.request({ - method: 'eth_sendTransaction', - params: [tx], - }) - - res.json({ - success: true, - transactionHash: txHash, - message: 'Transaction sent successfully', - }) - } catch (error) { - console.error('Transaction failed:', error) - res.status(500).json({ error: 'Transaction failed' }) - } - }) - - // Sign message - this.app.post('/api/wallet/sign', async (req, res) => { - try { - const userId = req.user.sub - const { message } = req.body - const session = this.userSessions.get(userId) - - if (!session) { - return res.status(404).json({ error: 'Wallet not found' }) - } - - const signature = await session.provider.request({ - method: 'personal_sign', - params: [message, session.address], - }) - - res.json({ - signature: signature, - message: message, - address: session.address, - }) - } catch (error) { - console.error('Message signing failed:', error) - res.status(500).json({ error: 'Failed to sign message' }) - } - }) - - // Delete wallet - this.app.delete('/api/wallet', async (req, res) => { - try { - const userId = req.user.sub - const session = this.userSessions.get(userId) - - if (session) { - // Stateless SDK - no logout needed, just remove from memory - this.userSessions.delete(userId) - } - - res.json({ - success: true, - message: 'Wallet deleted successfully', - }) - } catch (error) { - console.error('Wallet deletion failed:', error) - res.status(500).json({ error: 'Failed to delete wallet' }) - } - }) - } - - createJWTForUser(userId, email, name) { - const payload = { - iss: process.env.JWT_ISSUER, - aud: process.env.WEB3AUTH_CLIENT_ID, - sub: userId, - email: email, - name: name, - iat: Math.floor(Date.now() / 1000), - exp: Math.floor(Date.now() / 1000) + 3600, - } - - return jwt.sign(payload, process.env.JWT_SECRET) - } - - start(port = 3000) { - this.app.listen(port, () => { - console.log(`User wallet service running on port ${port}`) - }) - } -} - -// Usage -const walletService = new UserWalletService() -walletService.start() -``` - -### 2. Multi-Chain Portfolio Manager - -```javascript -class PortfolioManager { - constructor(config) { - this.config = config - this.chains = new Map() - this.web3auth = new Web3Auth(config.web3auth) - } - - async initialize() { - await this.web3auth.init() - - // Initialize multiple chains - const chainConfigs = [ - { id: '0x1', name: 'Ethereum', rpc: 'https://rpc.ankr.com/eth' }, - { id: '0x89', name: 'Polygon', rpc: 'https://rpc.ankr.com/polygon' }, - { id: '0x38', name: 'BSC', rpc: 'https://bsc-dataseed.binance.org' }, - ] - - for (const chain of chainConfigs) { - await this.initializeChain(chain) - } - } - - async initializeChain(chainConfig) { - // Connect to chain - const provider = await this.web3auth.connect({ - verifier: this.config.verifier, - verifierId: this.config.userId, - idToken: this.config.userToken, - chainId: chainConfig.id, - }) - - const privateKey = await provider.request({ method: 'eth_private_key' }) - const wallet = new ethers.Wallet(privateKey).connect( - new ethers.providers.JsonRpcProvider(chainConfig.rpc) - ) - - this.chains.set(chainConfig.id, { - config: chainConfig, - provider, - wallet, - }) - } - - async getPortfolioSummary() { - const summary = { - totalValue: 0, - chains: {}, - tokens: {}, - } - - for (const [chainId, chain] of this.chains) { - const chainSummary = await this.getChainSummary(chainId) - summary.chains[chainId] = chainSummary - summary.totalValue += chainSummary.totalValue - } - - return summary - } - - async getChainSummary(chainId) { - const chain = this.chains.get(chainId) - const summary = { - name: chain.config.name, - nativeBalance: 0, - tokens: [], - totalValue: 0, - } - - // Get native token balance - const balance = await chain.wallet.getBalance() - summary.nativeBalance = parseFloat(ethers.utils.formatEther(balance)) - - // Get token balances (implement token detection) - const tokens = await this.getTokenBalances(chainId) - summary.tokens = tokens - - // Calculate total value (implement price fetching) - summary.totalValue = await this.calculateChainValue(summary) - - return summary - } - - async getTokenBalances(chainId) { - // Implement token balance fetching - // This would integrate with token lists and balance checkers - return [] - } - - async calculateChainValue(chainSummary) { - // Implement value calculation with price feeds - return chainSummary.nativeBalance * 2000 // Simplified - } - - async rebalancePortfolio(targetAllocations) { - // Implement cross-chain rebalancing logic - for (const allocation of targetAllocations) { - console.log(`Rebalancing ${allocation.asset} to ${allocation.percentage}%`) - // Execute rebalancing trades - } - } -} -``` - -## Production Deployment Example - -### Docker Configuration - -```dockerfile -# Dockerfile -FROM node:18-alpine - -WORKDIR /app - -# Copy package files -COPY package*.json ./ -RUN npm ci --only=production - -# Copy source code -COPY src/ ./src/ - -# Create non-root user -RUN addgroup -g 1001 -S nodejs -RUN adduser -S web3auth -u 1001 -USER web3auth - -EXPOSE 3000 - -CMD ["node", "src/index.js"] -``` - -### Environment Configuration - -```yaml -# docker-compose.yml -version: '3.8' - -services: - web3auth-service: - build: . - ports: - - '3000:3000' - environment: - - NODE_ENV=production - - WEB3AUTH_CLIENT_ID=${WEB3AUTH_CLIENT_ID} - - JWT_SECRET=${JWT_SECRET} - - JWT_ISSUER=${JWT_ISSUER} - - REDIS_URL=${REDIS_URL} - depends_on: - - redis - restart: unless-stopped - - redis: - image: redis:7-alpine - ports: - - '6379:6379' - volumes: - - redis_data:/data - restart: unless-stopped - -volumes: - redis_data: -``` - -### Kubernetes Deployment - -```yaml -# deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: web3auth-service -spec: - replicas: 3 - selector: - matchLabels: - app: web3auth-service - template: - metadata: - labels: - app: web3auth-service - spec: - containers: - - name: web3auth-service - image: your-registry/web3auth-service:latest - ports: - - containerPort: 3000 - env: - - name: WEB3AUTH_CLIENT_ID - valueFrom: - secretKeyRef: - name: web3auth-secrets - key: client-id - - name: JWT_SECRET - valueFrom: - secretKeyRef: - name: web3auth-secrets - key: jwt-secret - resources: - requests: - memory: '256Mi' - cpu: '250m' - limits: - memory: '512Mi' - cpu: '500m' - livenessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 30 - periodSeconds: 10 - readinessProbe: - httpGet: - path: /ready - port: 3000 - initialDelaySeconds: 5 - periodSeconds: 5 -``` - -## Monitoring and Observability - -```javascript -const promClient = require('prom-client') - -class Web3AuthMetrics { - constructor() { - // Create metrics - this.authAttempts = new promClient.Counter({ - name: 'web3auth_authentication_attempts_total', - help: 'Total number of authentication attempts', - labelNames: ['status', 'verifier'], - }) - - this.authDuration = new promClient.Histogram({ - name: 'web3auth_authentication_duration_seconds', - help: 'Duration of authentication attempts', - labelNames: ['verifier'], - }) - - this.activeConnections = new promClient.Gauge({ - name: 'web3auth_active_connections', - help: 'Number of active Web3Auth connections', - }) - - // Register default metrics - promClient.register.setDefaultLabels({ - app: 'web3auth-service', - version: process.env.APP_VERSION || '1.0.0', - }) - - promClient.collectDefaultMetrics() - } - - recordAuthAttempt(status, verifier) { - this.authAttempts.inc({ status, verifier }) - } - - recordAuthDuration(duration, verifier) { - this.authDuration.observe({ verifier }, duration) - } - - setActiveConnections(count) { - this.activeConnections.set(count) - } - - getMetrics() { - return promClient.register.metrics() - } -} - -// Usage in your service -const metrics = new Web3AuthMetrics() - -// In your authentication handler -const startTime = Date.now() -try { - const provider = await web3auth.connect(loginParams) - metrics.recordAuthAttempt('success', loginParams.verifier) -} catch (error) { - metrics.recordAuthAttempt('failure', loginParams.verifier) -} finally { - const duration = (Date.now() - startTime) / 1000 - metrics.recordAuthDuration(duration, loginParams.verifier) -} -``` - -These examples provide a solid foundation for implementing the Web3Auth Node.js SDK in various production scenarios. Each example includes error handling, security considerations, and monitoring capabilities essential for backend services. + diff --git a/embedded-wallets/sdk/node/private-key.mdx b/embedded-wallets/sdk/node/private-key.mdx index 23684f6ce02..d31db7fa050 100644 --- a/embedded-wallets/sdk/node/private-key.mdx +++ b/embedded-wallets/sdk/node/private-key.mdx @@ -5,7 +5,7 @@ description: 'Extract private keys for blockchain operations' ## Overview -The Node.js SDK provides direct access to user private keys, enabling integration with any blockchain network. This is a key feature that makes the SDK suitable for backend and programmatic use cases. +The Node.js SDK provides direct access to user private keys, enabling integration with any blockchain network. This is a key feature that makes the SDK suitable for backend and programmatic use cases. Note that if the private key export is disabled in the dashboard, the methods will throw an error. :::warning Security Notice @@ -172,125 +172,6 @@ const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey }) console.log('Bitcoin Address:', address) ``` -## Complete Integration Example - -Here's a comprehensive example showing multi-chain support: - -```javascript -const { Web3Auth } = require('@web3auth/node-sdk') -const { ethers } = require('ethers') - -class MultiChainWallet { - constructor(clientId) { - this.web3auth = new Web3Auth({ - clientId, - web3AuthNetwork: 'sapphire_mainnet', - }) - } - - async initialize() { - await this.web3auth.init() - } - - async authenticate(verifier, verifierId, idToken) { - this.provider = await this.web3auth.connect({ - verifier, - verifierId, - idToken, - }) - } - - async getEthereumWallet() { - const privateKey = await this.provider.request({ method: 'eth_private_key' }) - return new ethers.Wallet(privateKey) - } - - async getSolanaKeypair() { - const { Keypair } = require('@solana/web3.js') - const bs58 = require('bs58') - - const solanaPrivateKey = await this.provider.request({ method: 'solanaPrivateKey' }) - const secretKey = bs58.decode(solanaPrivateKey) - return Keypair.fromSecretKey(secretKey) - } - - async getRawPrivateKey() { - return await this.provider.request({ method: 'private_key' }) - } - - async getAddresses() { - const [ethWallet, solanaKeypair] = await Promise.all([ - this.getEthereumWallet(), - this.getSolanaKeypair(), - ]) - - return { - ethereum: ethWallet.address, - solana: solanaKeypair.publicKey.toString(), - } - } -} - -// Usage -const wallet = new MultiChainWallet('YOUR_CLIENT_ID') -await wallet.initialize() -await wallet.authenticate('verifier', 'user@example.com', 'jwt_token') - -const addresses = await wallet.getAddresses() -console.log('Addresses:', addresses) -``` - -## Security Best Practices - -### 1. Secure Memory Handling - -```javascript -// Use Buffer.alloc for sensitive data -function securePrivateKeyHandling(privateKey) { - const keyBuffer = Buffer.from(privateKey.slice(2), 'hex') - - try { - // Use the private key - const wallet = new ethers.Wallet(keyBuffer) - return wallet.address - } finally { - // Clear the buffer - keyBuffer.fill(0) - } -} -``` - -### 2. Environment-based Key Access - -```javascript -// Only allow private key access in specific environments -function getPrivateKeySecurely() { - if (process.env.NODE_ENV === 'production' && !process.env.ALLOW_PRIVATE_KEY_ACCESS) { - throw new Error('Private key access not allowed in production') - } - - return provider.request({ method: 'eth_private_key' }) -} -``` - -### 3. Audit Logging - -```javascript -async function auditedPrivateKeyAccess(userId, purpose) { - console.log(`Private key accessed by ${userId} for ${purpose} at ${new Date().toISOString()}`) - - // Log to audit system - await logAuditEvent({ - action: 'private_key_access', - userId, - purpose, - timestamp: new Date(), - }) - - return provider.request({ method: 'eth_private_key' }) -} -``` - ## Disabling Private Key Export Private key export can be disabled in the Web3Auth Dashboard: @@ -309,8 +190,3 @@ try { console.error('Private key export disabled:', error.message) } ``` - -## Next Steps - -- [EVM Integration Guide](./evm-integration) -- [User authentication](./connect) diff --git a/ew-sidebar.js b/ew-sidebar.js index 1dd1756762e..0e30a155280 100644 --- a/ew-sidebar.js +++ b/ew-sidebar.js @@ -801,7 +801,7 @@ const sidebar = { }, "sdk/node/README", "sdk/node/connect", - "sdk/node/evm-integration", + "sdk/node/blockchain-connection", "sdk/node/private-key", "sdk/node/examples", { diff --git a/src/components/EWSDKCards/SDKOptions.ts b/src/components/EWSDKCards/SDKOptions.ts index e260d96de7f..66f3977fa15 100644 --- a/src/components/EWSDKCards/SDKOptions.ts +++ b/src/components/EWSDKCards/SDKOptions.ts @@ -6,6 +6,7 @@ export const web = 'Web' export const mobile = 'Mobile' export const gaming = 'Gaming' export const enterprise = 'Enterprise' +export const backend = 'Backend' export const android = 'Android' export const ios = 'iOS' export const js = 'Javascript' diff --git a/src/components/EWSDKCards/icons.tsx b/src/components/EWSDKCards/icons.tsx index 41d9f7d1488..804641d5fcc 100644 --- a/src/components/EWSDKCards/icons.tsx +++ b/src/components/EWSDKCards/icons.tsx @@ -270,3 +270,19 @@ export const gamingIcons = ( ) + +export const backendIcons = ( +
    +
    + {/* Node.js */} + + + + + + + + +
    +
    +) diff --git a/src/components/EWSDKCards/index.tsx b/src/components/EWSDKCards/index.tsx index 37c28c44bdd..0b7a5a5bdbc 100644 --- a/src/components/EWSDKCards/index.tsx +++ b/src/components/EWSDKCards/index.tsx @@ -2,8 +2,8 @@ /* eslint-disable jsx-a11y/click-events-have-key-events */ import { useState } from 'react' -import { web, mobile, gaming, enterprise } from './SDKOptions' -import { webIcons, mobileIcons, gamingIcons } from './icons' +import { web, mobile, gaming, backend } from './SDKOptions' +import { webIcons, mobileIcons, gamingIcons, backendIcons } from './icons' import styles from './styles.module.css' const chevron = ( @@ -314,6 +314,52 @@ export const pnpgaming = ( ) +export const pnpbackend = ( +
    +
    +
    +

    Embedded Wallets Backend SDKs

    + {/*
    +
    {pnp} Only
    +
    */} +
    + +

    + Integrate MetaMask Embedded Wallets (Web3Auth) with just 4 lines of code.
    +
    + Seamlessly authenticate users into your Web3 apps with their socials using Web3Auth Backend + SDKs. +

    + {backendIcons} +
    + +
    +) + // export const mpccorekit = ( //
    //
    @@ -406,6 +452,11 @@ export default function QuickNavigation() { onClick={() => setPlatform(gaming)}> {gaming}
    +
    setPlatform(backend)}> + {backend} +
    {/*
    setPlatform(enterprise)} @@ -417,6 +468,7 @@ export default function QuickNavigation() { {platform === web && pnpweb} {platform === mobile && pnpmobile} {platform === gaming && pnpgaming} + {platform === backend && pnpbackend} {/* {platform === enterprise && mpccorekit} */}
    ) diff --git a/src/utils/example-maps.tsx b/src/utils/example-maps.tsx index fcba35f7dc1..8501c0fbec2 100644 --- a/src/utils/example-maps.tsx +++ b/src/utils/example-maps.tsx @@ -806,7 +806,7 @@ export const webExamples: ExamplesInterface[] = [ export const pnpiOSExamples: ExamplesInterface[] = [ { title: 'Web3Auth PnP iOS SDK Quick Start', - description: 'A quick integration of Web3Auth Plug and Play iOS SDK', + description: 'A quick integration of MetaMask Embedded Wallets iOS SDK', image: 'img/embedded-wallets/banners/ios-swift.png', type: QUICK_START, tags: [tags.pnp, tags.ios, tags.evm, 'swift'], @@ -859,7 +859,7 @@ export const pnpiOSExamples: ExamplesInterface[] = [ { title: 'Using Aggregate Verifiers in Web3Auth PnP iOS SDK', description: - 'Combine multiple logins (Google, Facebook and GitHub) using Aggregate Verifiers in Web3Auth Plug and Play iOS SDK', + 'Combine multiple logins (Google, Facebook and GitHub) using Aggregate Verifiers in MetaMask Embedded Wallets iOS SDK', image: 'img/embedded-wallets/banners/ios-auth0.png', type: SAMPLE_APP, tags: [ @@ -883,7 +883,7 @@ export const pnpiOSExamples: ExamplesInterface[] = [ export const pnpAndroidExamples: ExamplesInterface[] = [ { title: 'Web3Auth PnP Android SDK Quick Start', - description: 'A quick integration of Web3Auth Plug and Play Android SDK', + description: 'A quick integration of MetaMask Embedded Wallets Android SDK', image: 'img/embedded-wallets/banners/android.png', type: QUICK_START, tags: [tags.pnp, tags.android, tags.evm, 'kotlin'], @@ -947,7 +947,7 @@ export const pnpAndroidExamples: ExamplesInterface[] = [ { title: 'Using Aggregate Verifiers in Web3Auth PnP Android SDK', description: - 'Combine multiple logins (Google, Facebook and GitHub) using Aggregate Verifiers in Web3Auth Plug and Play Android SDK', + 'Combine multiple logins (Google, Facebook and GitHub) using Aggregate Verifiers in MetaMask Embedded Wallets Android SDK', image: 'img/embedded-wallets/banners/android-auth0.png', type: SAMPLE_APP, tags: [ @@ -972,7 +972,7 @@ export const pnpReactNativeExamples: ExamplesInterface[] = [ { title: 'Web3Auth PnP React Native SDK Quick Start', description: - 'A quick integration of Web3Auth Plug and Play React Native SDK in Android and iOS', + 'A quick integration of MetaMask Embedded Wallets React Native SDK in Android and iOS', image: 'img/embedded-wallets/banners/react-native.png', type: QUICK_START, tags: [tags.pnp, tags.android, tags.ios, tags.evm, tags.reactNative], @@ -984,7 +984,7 @@ export const pnpReactNativeExamples: ExamplesInterface[] = [ { title: 'Using Auth0 with Web3Auth PnP React Native SDK', description: - 'Using Auth0 Single Page App (Implicit Mode) in Web3Auth Plug and Play React Native SDK in Android and iOS', + 'Using Auth0 Single Page App (Implicit Mode) in MetaMask Embedded Wallets React Native SDK in Android and iOS', image: 'img/embedded-wallets/banners/react-native-auth0.png', type: SAMPLE_APP, tags: [tags.pnp, tags.android, tags.ios, tags.reactNative, tags.evm, 'auth0', 'implicit mode'], @@ -996,7 +996,7 @@ export const pnpReactNativeExamples: ExamplesInterface[] = [ { title: 'Using Aggregate Verifiers in Web3Auth PnP React Native SDK', description: - 'Combine multiple logins (Google, Facebook and GitHub) using Aggregate Verifiers in Web3Auth Plug and Play React Native SDK for Android and iOS', + 'Combine multiple logins (Google, Facebook and GitHub) using Aggregate Verifiers in MetaMask Embedded Wallets React Native SDK for Android and iOS', image: 'img/embedded-wallets/banners/react-native-auth0.png', type: SAMPLE_APP, tags: [ @@ -1020,7 +1020,7 @@ export const pnpReactNativeExamples: ExamplesInterface[] = [ }, { title: 'Using Web3Auth PnP React Native SDK in Expo', - description: 'Using Web3Auth Plug and Play React Native SDK in an Expo App', + description: 'Using MetaMask Embedded Wallets React Native SDK in an Expo App', image: 'img/embedded-wallets/banners/expo.png', type: SAMPLE_APP, tags: [tags.pnp, tags.android, tags.ios, tags.evm, tags.reactNative, 'expo'], @@ -1033,7 +1033,7 @@ export const pnpReactNativeExamples: ExamplesInterface[] = [ export const pnpFlutterExamples: ExamplesInterface[] = [ { title: 'Web3Auth PnP Flutter SDK Quick Start', - description: 'A quick integration of Web3Auth Plug and Play Flutter SDK for Android and iOS', + description: 'A quick integration of MetaMask Embedded Wallets Flutter SDK for Android and iOS', image: 'img/embedded-wallets/banners/flutter.png', type: QUICK_START, tags: [tags.pnp, tags.flutter, tags.ios, tags.android, tags.evm, 'dart'], @@ -1120,7 +1120,7 @@ export const pnpFlutterExamples: ExamplesInterface[] = [ { title: 'Using Aggregate Verifiers in Web3Auth PnP Flutter SDK', description: - 'Combine multiple logins (Google, Facebook and GitHub) using Aggregate Verifiers in Web3Auth Plug and Play Flutter SDK for Android and iOS', + 'Combine multiple logins (Google, Facebook and GitHub) using Aggregate Verifiers in MetaMask Embedded Wallets Flutter SDK for Android and iOS', image: 'img/embedded-wallets/banners/flutter-auth0.png', type: SAMPLE_APP, tags: [ @@ -1147,7 +1147,7 @@ export const pnpUnityExamples: ExamplesInterface[] = [ { title: 'Web3Auth PnP Unity SDK Quick Start', description: - 'A quick integration of Web3Auth Plug and Play Unity SDK in Android, iOS and WebGL', + 'A quick integration of MetaMask Embedded Wallets Unity SDK in Android, iOS and WebGL', image: 'img/embedded-wallets/banners/unity.png', type: QUICK_START, tags: [tags.pnp, tags.unity, 'csharp', tags.android, tags.ios, tags.evm, 'webgl'], @@ -1158,7 +1158,7 @@ export const pnpUnityExamples: ExamplesInterface[] = [ { title: 'Using Auth0 with Web3Auth PnP Unity SDK', description: - 'Using Auth0 Single Page App (Implicit Mode) in Web3Auth Plug and Play Unity SDK in Android, iOS and WebGL', + 'Using Auth0 Single Page App (Implicit Mode) in MetaMask Embedded Wallets Unity SDK in Android, iOS and WebGL', image: 'img/embedded-wallets/banners/unity-auth0.png', type: SAMPLE_APP, tags: [ @@ -1179,7 +1179,7 @@ export const pnpUnityExamples: ExamplesInterface[] = [ { title: 'Using Aggregate Verifiers in Web3Auth PnP Unity SDK', description: - 'Combine multiple logins (Google, Facebook and GitHub) using Aggregate Verifiers in Web3Auth Plug and Play Unity SDK for Android, iOS and WebGL', + 'Combine multiple logins (Google, Facebook and GitHub) using Aggregate Verifiers in MetaMask Embedded Wallets Unity SDK for Android, iOS and WebGL', image: 'img/embedded-wallets/banners/unity-auth0.png', type: SAMPLE_APP, tags: [ @@ -1206,7 +1206,7 @@ export const pnpUnityExamples: ExamplesInterface[] = [ export const pnpUnrealExamples: ExamplesInterface[] = [ { title: 'Web3Auth PnP Unreal Engine SDK Quick Start', - description: 'A quick integration of Web3Auth Plug and Play Unreal Engine SDK in Android & iOS', + description: 'A quick integration of MetaMask Embedded Wallets Unreal Engine SDK in Android & iOS', image: 'img/embedded-wallets/banners/unreal.png', type: QUICK_START, tags: [tags.pnp, tags.unreal, 'csharp', tags.android, tags.evm, tags.ios], @@ -1218,7 +1218,7 @@ export const pnpUnrealExamples: ExamplesInterface[] = [ id: 'unreal-auth0-example', title: 'Using Auth0 with Web3Auth PnP Unreal Engine SDK', description: - 'Using Auth0 Single Page App (Implicit Mode) in Web3Auth Plug and Play Unreal Engine SDK in Android & iOS', + 'Using Auth0 Single Page App (Implicit Mode) in MetaMask Embedded Wallets Unreal Engine SDK in Android & iOS', image: 'img/embedded-wallets/banners/unreal-auth0.png', type: SAMPLE_APP, tags: [tags.pnp, tags.unreal, tags.android, tags.ios, 'auth0', tags.evm, 'implicit mode'], @@ -1229,7 +1229,7 @@ export const pnpUnrealExamples: ExamplesInterface[] = [ id: 'unreal-google-example', title: 'Using Google in Web3Auth PnP Unreal Engine SDK', description: - 'Using Google Custom Authentication in Web3Auth Plug and Play Unreal Engine SDK for Android & iOS', + 'Using Google Custom Authentication in MetaMask Embedded Wallets Unreal Engine SDK for Android & iOS', image: 'img/embedded-wallets/banners/unreal-google.png', type: SAMPLE_APP, tags: [ @@ -1250,288 +1250,54 @@ export const pnpUnrealExamples: ExamplesInterface[] = [ }, ] -export const coreKitSfaiOSExamples: ExamplesInterface[] = [ - { - title: 'Web3Auth Single Factor Auth iOS SDK Quick Start', - description: 'A quick integration of Single Factor Auth iOS SDK', - image: 'img/embedded-wallets/banners/ios-swift.png', - type: QUICK_START, - tags: [tags.sfa, 'sfa', tags.ios, tags.evm, 'swift'], - link: 'https://github.com/Web3Auth/web3auth-ios-examples/tree/main/sfa-ios-quick-start', - id: 'sfa-ios-quick-start', - githubLink: 'https://github.com/Web3Auth/web3auth-ios-examples/tree/main/sfa-ios-quick-start', - }, -] -export const coreKitSfaAndroidExamples: ExamplesInterface[] = [ - { - title: 'Web3Auth Single Factor Auth Android SDK Quick Start', - description: 'A quick integration of Web3Auth Single Factor Auth Android SDK', - image: 'img/embedded-wallets/banners/android.png', - type: QUICK_START, - tags: [tags.sfa, 'sfa', tags.android, tags.evm, 'kotlin'], - link: 'https://github.com/Web3Auth/web3auth-android-examples/tree/main/sfa-android-quick-start', - id: 'sfa-ios-quick-start', - githubLink: - 'https://github.com/Web3Auth/web3auth-android-examples/tree/main/sfa-android-quick-start', - }, -] -export const coreKitSfaReactNativeExamples: ExamplesInterface[] = [ - { - id: 'sfa-rn-bare-quick-start', - title: 'Web3Auth Single Factor Auth React Native SDK Quick Start', - description: - 'A quick integration of Web3Auth Single Factor Auth React Native SDK in Android and iOS', - image: 'img/embedded-wallets/banners/react-native.png', - type: QUICK_START, - tags: [tags.sfa, 'sfa', tags.android, tags.ios, tags.evm, tags.reactNative], - link: 'https://github.com/Web3Auth/web3auth-react-native-examples/tree/main/sfa-rn-bare-quick-start', - githubLink: - 'https://github.com/Web3Auth/web3auth-react-native-examples/tree/main/sfa-rn-bare-quick-start', - }, - { - id: 'sfa-rn-expo-auth0-example', - title: 'Using Web3Auth Single Factor Auth React Native SDK in Expo', - description: 'Using Web3Auth Single Factor Auth React Native SDK in an Expo App', - image: 'img/embedded-wallets/banners/expo.png', - type: SAMPLE_APP, - tags: [tags.sfa, 'sfa', tags.android, tags.ios, tags.reactNative, 'expo'], - link: 'https://github.com/Web3Auth/web3auth-react-native-examples/tree/main/sfa-rn-expo-auth0-example', - githubLink: - 'https://github.com/Web3Auth/web3auth-react-native-examples/tree/main/sfa-rn-expo-auth0-example', - }, -] -export const coreKitSfaFlutterExamples: ExamplesInterface[] = [ - { - id: 'sfa_flutter_quick_start', - title: 'Web3Auth Single Factor Auth Flutter SDK Quick Start', - description: - 'A quick integration of Web3Auth Single Factor Auth Flutter SDK for Android and iOS', - image: 'img/embedded-wallets/banners/flutter.png', - type: QUICK_START, - tags: [tags.sfa, 'sfa', tags.flutter, tags.ios, tags.android, tags.evm, 'dart'], - link: 'https://github.com/Web3Auth/web3auth-flutter-examples/tree/main/sfa_flutter_quick_start', - githubLink: - 'https://github.com/Web3Auth/web3auth-flutter-examples/tree/main/sfa_flutter_quick_start', - }, - { - id: 'sfa_flutter_solana', - title: 'Integrate Web3Auth Single Factor Auth Flutter SDK with Solana Blockchain', - description: 'Use Solana Blockchain with Single Factor Auth Flutter SDK for Android and iOS', - image: 'img/embedded-wallets/banners/flutter-solana.png', - type: SAMPLE_APP, - tags: [tags.sfa, 'sfa', tags.flutter, tags.ios, tags.android, 'dart', tags.solana, 'ed25519'], - link: 'https://github.com/Web3Auth/web3auth-flutter-examples/tree/main/sfa_flutter_solana', - githubLink: - 'https://github.com/Web3Auth/web3auth-flutter-examples/tree/main/sfa_flutter_solana', - }, -] -export const coreKitMPCWebExamples: ExamplesInterface[] = [ +export const pnpNodeExamples: ExamplesInterface[] = [ { - title: 'MPC Core Kit React Quick Start', - description: 'A quick integration of Multi Party Computation Core Kit SDK in React', - image: 'img/embedded-wallets/banners/react.png', - type: QUICK_START, - tags: [ - tags.mpcCoreKit, - 'mpc', - tags.web, - tags.mpcCoreKitJS, - tags.evm, - - 'react', - 'id token login', - ], - link: 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-web/quick-starts/mpc-core-kit-react-quick-start', - githubLink: - 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-web/quick-starts/mpc-core-kit-react-quick-start', - id: 'mpc-core-kit-react-quick-start', - }, - { - title: 'MPC Core Kit Angular Quick Start', - description: 'A quick integration of Multi Party Computation Core Kit SDK in angular', - image: 'img/embedded-wallets/banners/angular.png', - type: QUICK_START, - tags: [ - tags.mpcCoreKit, - 'mpc', - tags.web, - tags.mpcCoreKitJS, - tags.evm, - - 'angular', - 'id token login', - ], - link: 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-web/quick-starts/mpc-core-kit-angular-quick-start', - githubLink: - 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-web/quick-starts/mpc-core-kit-angular-quick-start', - id: 'mpc-core-kit-angular-quick-start', - }, - { - title: 'MPC Core Kit Vue Quick Start', - description: 'A quick integration of Multi Party Computation Core Kit SDK in Vue', - image: 'img/embedded-wallets/banners/vue.png', - type: QUICK_START, - tags: [tags.mpcCoreKit, 'mpc', tags.web, tags.mpcCoreKitJS, tags.evm, 'vue', 'id token login'], - link: 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-web/quick-starts/mpc-core-kit-vue-quick-start', - githubLink: - 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-web/quick-starts/mpc-core-kit-vue-quick-start', - id: 'mpc-core-kit-vue-quick-start', - }, - { - title: 'MPC Core Kit NextJS Quick Start', - description: 'A quick integration of Multi Party Computation Core Kit SDK in NextJS', - image: 'img/embedded-wallets/banners/next.js.png', - type: QUICK_START, - tags: [ - tags.mpcCoreKit, - 'mpc', - tags.web, - tags.mpcCoreKitJS, - tags.evm, - - 'nextjs', - 'id token login', - ], - link: 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-web/quick-starts/mpc-core-kit-nextjs-quick-start', - githubLink: - 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-web/quick-starts/mpc-core-kit-nextjs-quick-start', - id: 'mpc-core-kit-nextjs-quick-start', - }, - { - title: 'Use Aggregate Verifiers in MPC Core Kit SDK', - description: - 'Aggregate Google, Auth0 GitHub & Email Passwordless in Multi Party Computation Core Kit SDK', - image: 'img/embedded-wallets/banners/auth0.png', - type: SAMPLE_APP, - tags: [ - tags.mpcCoreKit, - 'mpc', - tags.web, - tags.mpcCoreKitJS, - tags.evm, - - 'aggregate verifier', - 'google', - 'github', - 'email passwordless', - 'auth0', - 'id token login', - ], - link: 'https://github.com/Web3Auth/mpc-core-kit-examples/blob/main/mpc-core-kit-web/mpc-core-kit-aggregate-verifier-example/', - id: 'mpc-core-kit-aggregate-verifier-example', - githubLink: - 'https://github.com/Web3Auth/mpc-core-kit-examples/blob/main/mpc-core-kit-web/mpc-core-kit-aggregate-verifier-example/', - }, - { - title: 'Integrate Farcaster Login in MPC Core Kit SDK', - description: 'Use Farcaster with Multi Party Computation Core Kit SDK', - image: 'img/embedded-wallets/banners/farcaster.png', - type: SAMPLE_APP, - tags: [ - tags.mpcCoreKit, - 'mpc', - tags.web, - tags.mpcCoreKitJS, - tags.evm, - - 'farcaster', - 'id token login', - ], - link: 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-web/mpc-core-kit-farcaster', - id: 'mpc-core-kit-farcaster', - githubLink: - 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-web/mpc-core-kit-farcaster', - }, - { - title: 'Integrate MPC Core Kit SDK with Solana Blockchain', - description: 'Use Solana with MPC Core Kit SDK', - image: 'img/embedded-wallets/banners/solana.png', - type: SAMPLE_APP, - tags: [tags.mpcCoreKit, tags.web, tags.mpcCoreKitJS, tags.evm, tags.solana, 'ed25519'], - link: 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-web/mpc-core-kit-solana', - id: 'mpc-core-kit-solana', - githubLink: - 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-web/mpc-core-kit-solana', - }, -] -export const coreKitMPCReactNativeExamples: ExamplesInterface[] = [ - { - id: 'mpc-core-kit-rn-quick-start', - title: 'Web3Auth MPC Core Kit React Native Quick Start', + title: 'Web3Auth PnP Node SDK Quick Start', description: - 'A quick integration of Web3Auth Multi Party Computation Core Kit in React Native for Android and iOS', - image: 'img/embedded-wallets/banners/react-native.png', + 'A quick integration of MetaMask Embedded Wallets Node SDK', + image: 'img/embedded-wallets/banners/node.png', type: QUICK_START, - tags: [tags.mpcCoreKit, 'mpc', tags.android, tags.evm, tags.ios, tags.reactNative], - link: 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-react-native/mpc-core-kit-rn-quick-start', - githubLink: - 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-react-native/mpc-core-kit-rn-quick-start', + tags: [tags.pnp, tags.node, 'nodejs', tags.evm,], + link: 'https://github.com/Web3Auth/web3auth-node-examples/tree/main/evm-quick-start', + id: 'node-quick-start', + githubLink: 'https://github.com/Web3Auth/web3auth-node-examples/tree/main/evm-quick-start', }, { - id: 'mpc-core-kit-rn-auth0', - title: 'Using Auth0 with MPC Core Kit React Native', + title: 'Using Firebase with Web3Auth PnP Node SDK', description: - 'Integrate Auth0 with Web3Auth Multi Party Computation Core Kit in React Native for Android and iOS', - image: 'img/embedded-wallets/banners/react-native-auth0.png', + 'Using Firebase Backend Login in MetaMask Embedded Wallets Node SDK', + image: 'img/embedded-wallets/banners/node-firebase.png', type: SAMPLE_APP, tags: [ - tags.mpcCoreKit, - 'mpc', + tags.pnp, + tags.node, - tags.android, - tags.ios, - tags.reactNative, - tags.evm, - 'auth0', - 'id token login', - ], - link: 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-react-native/mpc-core-kit-rn-auth0', - githubLink: - 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-react-native/mpc-core-kit-rn-auth0', - }, - { - id: 'mpc-core-kit-rn-solana', - title: 'Using Solana MPC Core Kit SDK React Native', - description: 'Integrate Solana with Web3Auth MPC Core Kit in React Native for Android and iOS', - image: 'img/embedded-wallets/banners/solana.png', - type: SAMPLE_APP, - tags: [ - tags.mpcCoreKit, - 'mpc', + 'firebase', - tags.android, - tags.ios, - tags.reactNative, tags.evm, - 'auth0', - 'id token login', ], - link: 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-react-native/mpc-core-kit-rn-solana', - githubLink: - 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-react-native/mpc-core-kit-rn-solana', + link: 'https://github.com/Web3Auth/web3auth-node-examples/tree/main/firebase-quick-start', + id: 'node-firebase-example', + githubLink: 'https://github.com/Web3Auth/web3auth-node-examples/tree/main/firebase-quick-start', }, { - id: 'mpc-core-kit-rn-expo-auth0', - title: 'Using MPC Core Kit SDK in Expo', + title: 'Using Solana with Web3Auth PnP Node SDK', description: - 'Integrate Auth0 with Web3Auth MPC Core Kit in React Native Expo for Android and iOS', - image: 'img/embedded-wallets/banners/expo.png', + 'Using Solana with MetaMask Embedded Wallets Node SDK', + image: 'img/embedded-wallets/banners/node-solana.png', type: SAMPLE_APP, tags: [ - tags.mpcCoreKit, - 'mpc', - - tags.android, - tags.ios, - tags.reactNative, - tags.evm, - 'auth0', - 'id token login', + tags.pnp, + tags.node, + 'nodejs', + tags.solana, + 'ed25519', ], - link: 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-react-native/mpc-core-kit-rn-expo-auth0', + link: 'https://github.com/Web3Auth/web3auth-node-examples/tree/main/solana-quick-start', + id: 'node-solana-example', githubLink: - 'https://github.com/Web3Auth/mpc-core-kit-examples/tree/main/mpc-core-kit-react-native/mpc-core-kit-rn-expo-auth0', + 'https://github.com/Web3Auth/web3auth-node-examples/tree/main/solana-quick-start', }, ] @@ -1543,12 +1309,6 @@ export const exampleMap: ExamplesInterface[] = [ ...pnpFlutterExamples, ...pnpUnityExamples, ...pnpUnrealExamples, - ...coreKitSfaiOSExamples, - ...coreKitSfaAndroidExamples, - ...coreKitSfaReactNativeExamples, - ...coreKitSfaFlutterExamples, - ...coreKitMPCWebExamples, - ...coreKitMPCReactNativeExamples, ] function arrayToObjectById(array) { diff --git a/static/img/embedded-wallets/banners/node-firebase.png b/static/img/embedded-wallets/banners/node-firebase.png new file mode 100644 index 0000000000000000000000000000000000000000..2fdd111574f48def2d2b1580328d00df1f5700da GIT binary patch literal 53590 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%0D~_c{6hQrBQo+ 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 b+tKAu01l^!XWgy}0Q8X-mlvxMF%0@YCYg9L literal 0 HcmV?d00001 diff --git a/static/img/embedded-wallets/banners/node.png b/static/img/embedded-wallets/banners/node.png new file mode 100644 index 0000000000000000000000000000000000000000..620275a6ead9d05bcba73f77c647622d2c4fc944 GIT binary patch literal 46735 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