Pakete, Repositories und App-Kategorien suchen

GitHub-Repositorydatit309309/web3_webview

web3_webview

Web3-injizierter WebView Flutter

Projektcover von web3_webview
Sterne
3
Forks
1
Letzter Push (UTC)
09.09.2026
Projektstatus
Aktiv

Original-README

Englischer Projektschnappschuss. Aktuelle Inhalte auf GitHub.

web3_webview

web3_webview is a Flutter bridge between a DApp running inside an InAppWebView and your host app. It injects an EIP-1193 / EIP-6963 compliant window.ethereum provider (via ethers.min.js + generated script) and routes all wallet operations – connect, sign, sendTransaction, chain management – to native Dart dialogs and web3dart.

Security-first fork: private-key validation, XSS-safe JS injection, EIP-1193 error codes, PermissionRequest default-deny, non-blocking eth_sendTransaction, and correct EIP-712 padding. See Security.


Architecture

flowchart LR
  DApp -- window.ethereum.request --> JSBridge[JS Provider\nprovider_script.dart]
  JSBridge -- flutter_inappwebview callHandler\nethereumRequest --> WebView[Web3WebView\nweb3_webview_eip1193.dart]
  WebView --> Provider[EthereumProvider\nsingleton + Web3Client]
  Provider --> Signer[Web3Signer\nPrivateKeySigner | external]
  Signer --> Signing[SigningHandler\nEIP-191 / EIP-712]
  Signer --> Tx[TransactionHandler\nsend + estimate + gas]
  Provider --> Dialog[WalletDialogService\nbottom sheet]
  Provider -- jsonEncode + evaluateJavascript --> JSBridge

Core modules:

Module File Responsibility
Web3WebView lib/web3_webview_eip1193.dart Wraps flutter_inappwebview, injects ethers.min.js + provider script at AT_DOCUMENT_START (Android re-inject on onLoadStart), supports read-only when no signer, forwards onPermissionRequest as DENY by default
EthereumProvider lib/ethereum/ethereum_provider.dart Singleton, Web3Client + Client lifecycle, handleRequest dispatcher, 12s eth_blockNumber cache, chain registry, event emit via jsonEncode. New: Web3Signer? abstraction – PrivateKeySigner / external / null (read-only)
Web3Signer lib/signer/web3_signer.dart Abstract address, signMessage(method,from,msg,pw), sendTransaction(tx); implement for WalletConnect / Secure Enclave etc.
PrivateKeySigner lib/signer/private_key_signer.dart Local EthPrivateKey impl via SigningHandler + TransactionHandler, auto-updates on chain switch via getters
ProviderScriptGenerator lib/provider/provider_script.dart Generates JS EthereumProvider extends EventTarget, jsonEncode-escapes chainId/accounts/isConnected/info, handles request/send/sendAsync, EIP-6963 announce
SigningHandler lib/signing/signing_handler.dart personal_sign/eth_sign/eth_signTypedData* with full EIP-712 v3/v4 validation, struct hashing, 32-byte padding for bool/address/bytesN
TransactionHandler lib/transaction/transaction_handler.dart Validate to/value/data, estimateGas + 20% BigInt buffer, signTransaction(chainId), sendRawTransaction returns hash immediately (EIP-1193), optional waitForConfirmation
WalletDialogService lib/ethereum/wallet_dialog_service.dart Bottom sheets for connect/sign/tx/switch/add, requestFrom = controller.getUrl().host, WalletDialogTheme

Key Features

  • Wallet Connection – user-confirmed eth_requestAccounts; returns [] on eth_accounts until connected
  • DApp Integration – window.ethereum + window.web3.currentProvider, isMetaMask=true, EIP-6963 discovery
  • Read-only mode – omit privateKey/signer and DApp still loads; eth_call, eth_getBalance, eth_blockNumber etc. work, signing throws 4100
  • External signer – implement Web3Signer for WalletConnect, Secure Enclave, biometrics – no private key in RAM
  • Transaction Lifecycle – confirmation dialog, eth_estimateGas buffered, non-blocking hash return, fire-and-forget receipt polling (opt-in blocking)
  • Message Signing – personal_sign (EIP-191 prefix), eth_sign (raw keccak), eth_signTypedData/v1/v3/v4 (EIP-712)
  • Chain Management – wallet_switchEthereumChain/wallet_addEthereumChain with idempotency checks
  • Two-way Bridge – flutter_inappwebview callHandler ↔ evaluateJavascript, XSS-safe via jsonEncode
  • Multi-chain – any EVM via NetworkConfig

Requirements

  • Flutter >=3.24.0, Dart ^3.5.0
  • flutter_inappwebview ^6.1.5, web3dart ^2.7.3

Installation

dependencies:
  web3_webview: ^1.0.11
import 'package:web3_webview/web3_webview.dart';

Quick Start

privateKey is now optional. If omitted and no signer is provided, the WebView runs in read-only mode (only RPC reads succeed; signing throws 4100). signer takes precedence over privateKey.

Do not hardcode keys. Load from flutter_secure_storage / Keychain / Keystore and clear on lock. Or use Web3Signer to avoid storing keys at all. See Security.

A. Read-only (no signer)
Web3WebView(
  web3WalletConfig: Web3WalletConfig(
    currentNetwork: ethMainnet,
    supportNetworks: [ethMainnet, bscMainnet],
    name: 'MyDApp',
  ),
  initialUrlRequest: URLRequest(url: WebUri('https://metamask.github.io/test-dapp/')),
)
// DApp loads, eth_call/eth_getBalance work, eth_sendTransaction -> {code:4100}
B. Local private key (legacy, still supported)
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class DappScreen extends StatefulWidget {
  const DappScreen({super.key});
  @override State<DappScreen> createState() => _DappScreenState();
}
class _DappScreenState extends State<DappScreen> {
  String? _pk;
  @override void initState() {
    super.initState();
    const storage = FlutterSecureStorage();
    storage.read(key: 'evm_pk').then((v) => setState(() => _pk = v));
  }
  @override Widget build(BuildContext context) {
    if (_pk == null) return const Center(child: CircularProgressIndicator());
    return Web3WebView(
      web3WalletConfig: Web3WalletConfig(
        privateKey: _pk!, // 0x + 64 hex, validated -32602 on invalid
        currentNetwork: ethMainnet,
        supportNetworks: [ethMainnet, bscMainnet],
        name: 'MyDApp Wallet',
        id: 'com.example.mydapp',
        dialogTheme: WalletDialogTheme(primaryColor: Color(0xFF3B82F6)),
        onError: (method, params, message) => debugPrint('[$method] $message'),
      ),
      initialUrlRequest: URLRequest(url: WebUri('https://metamask.github.io/test-dapp/')),
      onPermissionRequest: (ctrl, req) async => PermissionResponse(
        resources: req.resources, action: PermissionResponseAction.DENY,
      ),
    );
  }
}
C. External signer (WalletConnect / Secure Enclave) — recommended
import 'package:web3_webview/web3_webview.dart';

class WalletConnectSigner extends Web3Signer {
  final String _addr;
  WalletConnectSigner(this._addr);
  @override String get address => _addr;
  @override Future<String> signMessage(String method, String from, dynamic message, String pw) {
    // delegate to WalletConnect / enclave
    return walletConnect.sign(method, from, message);
  }
  @override Future<String> sendTransaction(Map<String, dynamic> tx) {
    return walletConnect.sendTransaction(tx);
  }
}

Web3WebView(
  web3WalletConfig: Web3WalletConfig(
    signer: WalletConnectSigner('0xAbc...'),
    currentNetwork: ethMainnet,
  ),
  initialUrlRequest: URLRequest(url: WebUri('https://app.uniswap.org/')),
)
Handling Web3WalletConfig
Field Type Required Notes
privateKey String? no* 0x + 64 hex or 64 hex; validated with -32602 on invalid. *Required only for local signing; omit for read-only / external signer
signer Web3Signer? no* external signer (WalletConnect etc.), takes precedence over privateKey. If both null → read-only
currentNetwork NetworkConfig? no defaults to 0x1
supportNetworks List<NetworkConfig>? no defaults to [eth, bsc]
name/icon/id String? no EIP-6963 info (icon is data-URI), id → rdns
dialogTheme WalletDialogTheme? no colors, text styles, paddings
onError void Function(JsonRpcMethod, List?, String)? no invoked before JS error is thrown, receives EIP-1193 code in message

NetworkConfig { chainId (0x hex), chainName, nativeCurrency?, rpcUrls, blockExplorerUrls? }

Read-only check: config.isReadOnly / config.hasSigner; provider: EthereumProvider().isReadOnly.


Signer & Read-only mode

lib/signer/web3_signer.dart:14:

abstract class Web3Signer {
  String get address; // EIP-55
  Future<String> signMessage(String method, String from, dynamic message, String password);
  Future<String> sendTransaction(Map<String, dynamic> txParams); // -> tx hash
  Future<Uint8List> signTransaction(Transaction tx, int chainId) => throw UnsupportedError(...);
}
  • Read-only: Web3WalletConfig() with no privateKey/signer → EthereumProvider.isReadOnly==true, window.ethereum.isConnected==false, eth_accounts==[]. eth_call, eth_getBalance, eth_blockNumber, eth_chainId, eth_estimateGas, wallet_switchEthereumChain etc. still work via Web3Client. Signing methods throw 4100 (unauthorized) which surfaces as code:4100 in DApp.
  • PrivateKey: PrivateKeySigner lib/signer/private_key_signer.dart:11 is auto-created from privateKey; uses SigningHandler/TransactionHandler internally and auto-updates chainId/Web3Client on network switch.
  • External: implement Web3Signer and pass Web3WalletConfig(signer: ...). Dialogs (showConnectWallet/showSignMessage/showTransactionConfirm) are still shown by EthereumProvider before delegating; your signer only does crypto/RPC.

Supported JSON-RPC

EthereumProvider.handleRequest (lib/ethereum/ethereum_provider.dart:189) dispatches:

Method Params Behaviour
eth_requestAccounts [] Bottom sheet → connect + accountsChanged events, returns [address] or 4001; throws 4100 in read-only
eth_accounts [] [] until connected (or read-only)
eth_chainId [] state.chainId (hex)
net_version [] decimal chainId
eth_blockNumber [] 12s cached HexUtils.numberToHex(blockNumber)
eth_call [tx, block] makeRPCCall('eth_call') (no signing)
eth_sendTransaction [tx] Dialog → signer.sendTransaction → hash immediately, polling fire-and-forget; 4100 in read-only
eth_getBalance [addr, tag] getBalance → hex
eth_getBlockByNumber/Hash [id, withTx] makeRPCCall → Map?
eth_getTransactionByHash/Receipt [hash] makeRPCCall → Map?
eth_getCode/StorageAt/TransactionCount [...] getCode/getStorage/getTransactionCount or RPC fallback
eth_gasPrice / eth_estimateGas []/[tx] hex; estimate buffered +20% via BigInt (works in read-only)
personal_sign [hexMsg, addr, pw?] dialog → signer.signMessage; 4100 in read-only
eth_sign [addr, hexMsg] raw keccak256; 4100 in read-only
eth_signTypedData[*_v1/v3/v4] [addr, typed] EIP-712; 4100 in read-only
wallet_switchEthereumChain [{chainId}] idempotent, dialog if needed → chainChanged (works in read-only)
wallet_addEthereumChain [NetworkConfig] dialog → add + auto-switch
wallet_getPermissions [] ['eth_accounts','eth_chainId','personal_sign']
wallet_revokePermissions [] true
other throws UnsupportedMethodException(4200)

Unknown method surfaces to JS as {code:4200, message} and to onError.

Error codes (lib/exceptions.dart:2)

WalletException.code follows EIP-1193 + JSON-RPC:

  • 4001 – user rejected
  • 4100 – unauthorized (no signer / read-only)
  • 4200 – unsupported method
  • 4900 – disconnected
  • -32601 – method not found
  • -32602 – invalid params
  • -32603 – internal

Web3WebView (lib/web3_webview_eip1193.dart:825) maps WalletException.code into the thrown {code,message} consumed by provider_script.dart:127 _processError.


Permissions & WebView

  • onPermissionRequest defaults to DENY (lib/web3_webview_eip1193.dart:1019). Override Web3WebView.onPermissionRequest to grant selectively, never GRANT all.
  • shouldInterceptRequest/onReceivedServerTrustAuthRequest are forwarded; implement cert pinning in host if needed.
  • Provider injected as two UserScript at AT_DOCUMENT_START; on Android re-evaluated in onLoadStart.

Signing details (lib/signing/signing_handler.dart:1)

  • personal_sign accepts 0x hex or UTF-8, decodes for UI, signs prefixed.
  • eth_sign signs keccak256(hexOrUtf8) directly (dangerous – dialog still shown).
  • Typed data: types must contain EIP712Domain; checks primaryType, domain whitelist (name,version,chainId,verifyingContract,salt), circular deps, array lengths, and ABI 32-byte padding for bool/address/bytesN/uint/int.

Transaction details (lib/transaction/transaction_handler.dart:1)

  • Requires to (0x 42 chars), validates value/data as 0x hex.
  • getTransactionCount(from) as nonce, estimateGas +20% with BigInt (*120/100), getGasPrice() or gasPrice override.
  • handleTransaction({waitForConfirmation=false}) – default returns hash; set true to block up to 30×10s polling and throw on receipt.status==false or timeout.

Theming (light + auto dark)

// Light (default)
final light = WalletDialogTheme(primaryColor: Color(0xFF6366F1));
// Dark – auto-used when Theme.brightness == dark, or pass custom
final dark = WalletDialogTheme.dark(primaryColor: Color(0xFF818CF8));

Web3WalletConfig(
  dialogTheme: light,
  darkDialogTheme: dark, // optional – if null auto-derived from light
)
  • Auto dark: WalletDialogService checks Theme.of(context).brightness – no extra code needed; BottomSheetDialog barrier 0.42, cards adapt (surface #1E293B, border #334155, text #F1F5F9).
  • Premium redesign (1.0.12): 20 radius, 16 card, handle 36x4, origin chip with avatar, hero amount card, warning amber/red, FilledButton 52h/14 radius + OutlinedButton, ExpansionTile hex.
  • Custom per-dialog still via WalletDialogTheme(primaryColor:…, borderRadius:…), consumed by WalletDialogService (lib/ethereum/wallet_dialog_service.dart:7).

Try the example: AppBar dark toggle → all eth_requestAccounts / personal_sign / eth_sendTransaction dialogs switch instantly.

Custom UI (fully replace any dialog)

You can keep the premium dialogs (theming only) or completely replace them to match your app branding – no fork needed.

// 1. Define builders once (or per-WebView)
final myBuilders = WalletDialogBuilders(
  connect: (ctx, {required address, required host, required appName, required controller, required theme}) async {
    return showDialog<bool>(
      context: ctx,
      barrierDismissible: false,
      builder: (c) => AlertDialog(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        title: Text('$appName muốn kết nối', style: theme.headerStyle),
        content: Text('Host: $host\nWallet: $address'),
        actions: [
          TextButton(onPressed: () => Navigator.pop(c, false), child: Text('Từ chối')),
          FilledButton(onPressed: () => Navigator.pop(c, true), style: FilledButton.styleFrom(backgroundColor: theme.primaryColor), child: Text('Kết nối')),
        ],
      ),
    );
  },
  // sign, transaction, switchNetwork, addNetwork – same pattern
  sign: (ctx, {required message, required address, required host, required controller, required theme}) => ...,
  transaction: (ctx, {required txParams, required host, required controller, required theme}) => ...,
);

// 2. Pass via config (per-WebView) – takes precedence
Web3WebView(
  web3WalletConfig: Web3WalletConfig(
    privateKey: pk,
    dialogTheme: light,
    darkDialogTheme: dark,
    dialogBuilders: myBuilders, // <-- all dialogs now use your widgets
  ),
)

// 3. Or globally: WalletDialogService.instance.setBuilders(myBuilders);
  • What you receive: BuildContext (for showDialog/Navigator), host, address/message/txParams, controller (for getUrl()), and the effective WalletDialogTheme (already resolved for light/dark + your custom colors).
  • What to return: Future<bool?> – true = confirm, false/null = reject/cancel (maps to EIP-1193 4001).
  • Fallback: any null builder falls back to the premium default; you can override only connect and keep sign premium.
  • Theming still works: your custom widget can use theme.primaryColor / textColor / borderColor to stay consistent with dialogTheme/darkDialogTheme.

Example with 4 tabs (read-only / privateKey / signer / custom) is in example/lib/main.dart:56 – the “Custom” tab uses AlertDialog as above.


Security

  • Private key / Signer: never log, never commit, store in flutter_secure_storage with biometrics, wipe on logout. Prefer Web3Signer (WalletConnect / Secure Enclave) over embedding keys. EthereumProvider.initialize (lib/ethereum/ethereum_provider.dart:63) validates privateKey if provided; Web3WebView shows error, not WebView, on invalid format. Read-only mode needs no key. Clipboard uses flutter/services without logging (lib/utils/app_utils.dart:5).
  • XSS: all JS interpolation uses jsonEncode (lib/provider/provider_script.dart:13, lib/web3_js_bridge_callback.dart:5, lib/ethereum/ethereum_provider.dart:841).
  • Origin: dialogs display controller.getUrl().host; consider allowlist / phishing check before showConnectWallet.
  • RPC: rpcUrls.first is used; use HTTPS + API key, pin certs via onReceivedServerTrustAuthRequest.
  • Singleton: EthereumProvider is a singleton; multiple Web3WebView share state – avoid mounting two simultaneously, call dispose() (lib/ethereum/ethereum_provider.dart:259) on screen dispose (already done in Web3WebView).

Limitations

  • Singleton EthereumProvider – not multi-account, not multi-chain concurrent.
  • ethers.min.js (464 KB) bundled at packages/web3_webview/assets/ethers.min.js, injected twice.
  • No eth_subscribe/eth_getLogs polling; unsupported methods throw 4200.
  • LoadingHelper is global ref-counted overlay (lib/utils/loading.dart:3).

Changelog

See CHANGELOG.md. 1.0.13 adds WalletDialogBuilders for fully custom UI (dialogBuilders), 1.0.12 premium + auto dark, 1.0.11 Web3Signer + read-only.


License & Thanks

  • License: see LICENSE (BSD).
  • Thanks: PositionExchange/flutter-web3-provider, ethers.js v6 docs.
datit309309 GitHub avatar
GitHub-Autor

datit309309 ↗

SprachenDart

Von diesem Repository veröffentlichte Pakete

Verwendete Abhängigkeiten

Abhängigkeiten 12 Einträge

Wie wähle ich ein passendes Repository?

Nach Anwendungsfall filtern, Aktivität vergleichen und vor der Nutzung das Originalprojekt prüfen.

  1. Nach Name, Autor oder Beschreibung suchen und nach Thema eingrenzen.
  2. Sterne, Forks und letzte Pushes vergleichen. Sterne zeigen Interesse, garantieren aber keine Qualität.
  3. Vor der Nutzung README, Lizenz und Issues auf GitHub prüfen.

Quellen und Pflege

“A repository is the most basic element of GitHub.”

GitHub Docs — About repositories ↗