web3_webview 1.0.11 copy "web3_webview: ^1.0.11" to clipboard
web3_webview: ^1.0.11 copied to clipboard

web3_webview is a powerful bridge between a DApp running inside a WebView and your Flutter application, enabling secure and seamless two-way communication between them

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 Integrationwindow.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 Signingpersonal_sign (EIP-191 prefix), eth_sign (raw keccak), eth_signTypedData/v1/v3/v4 (EIP-712)
  • Chain Managementwallet_switchEthereumChain/wallet_addEthereumChain with idempotency checks
  • Two-way Bridgeflutter_inappwebview callHandlerevaluateJavascript, 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,
      ),
    );
  }
}
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), idrdns
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/signerEthereumProvider.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.sendTransactionhash immediately, polling fire-and-forget; 4100 in read-only
eth_getBalance [addr, tag] getBalance → hex
eth_getBlockByNumber/Hash [id, withTx] makeRPCCallMap?
eth_getTransactionByHash/Receipt [hash] makeRPCCallMap?
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 #

WalletDialogTheme(
  primaryColor: Color(0xFF3B82F6),
  textColor: Color(0xFF1F2937),
  borderRadius: 16,
  dialogPadding: EdgeInsets.all(24),
  buttonConfirmStyle: ButtonConfig(backgroundColor: ...),
)

Passed via Web3WalletConfig.dialogTheme, consumed by WalletDialogService (lib/ethereum/wallet_dialog_service.dart:1).


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.11 adds Web3Signer abstraction + read-only mode, makes privateKey optional, fixes eth_call/net_version for read-only.


License & Thanks #

  • License: see LICENSE (BSD).
  • Thanks: PositionExchange/flutter-web3-provider, ethers.js v6 docs.
7
likes
0
points
62
downloads

Publisher

verified publisherdatit309.is-a.dev

Weekly Downloads

web3_webview is a powerful bridge between a DApp running inside a WebView and your Flutter application, enabling secure and seamless two-way communication between them

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

crypto, flutter, flutter_inappwebview, hex, http, intl, modal_bottom_sheet, uuid, web3dart

More

Packages that depend on web3_webview