v1.0.16web3_webview
web3_webview एक शक्तिशाली पुल है जो वेबव्यू में चल रहे DApp और आपके फ्लटर एप्लिकेशन के बीच सुरक्षित और निरंतर द्विदिश संचार की अनुमति देता है।
Web3 इंजेक्टेड वेबव्यू फ्लटर
यह अंग्रेज़ी मूल स्नैपशॉट है। नवीनतम सामग्री GitHub पर देखें।
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,
PermissionRequestdefault-deny, non-blockingeth_sendTransaction, and correct EIP-712 padding. See Security.
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 |
eth_requestAccounts; returns [] on eth_accounts until connectedwindow.ethereum + window.web3.currentProvider, isMetaMask=true, EIP-6963 discoveryprivateKey/signer and DApp still loads; eth_call, eth_getBalance, eth_blockNumber etc. work, signing throws 4100Web3Signer for WalletConnect, Secure Enclave, biometrics – no private key in RAMeth_estimateGas buffered, non-blocking hash return, fire-and-forget receipt polling (opt-in blocking)personal_sign (EIP-191 prefix), eth_sign (raw keccak), eth_signTypedData/v1/v3/v4 (EIP-712)wallet_switchEthereumChain/wallet_addEthereumChain with idempotency checksflutter_inappwebview callHandler ↔ evaluateJavascript, XSS-safe via jsonEncodeNetworkConfig>=3.24.0, Dart ^3.5.0flutter_inappwebview ^6.1.5, web3dart ^2.7.3dependencies:
web3_webview: ^1.0.11
import 'package:web3_webview/web3_webview.dart';
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 useWeb3Signerto avoid storing keys at all. See Security.
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}
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/')),
)
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.
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(...);
}
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.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.Web3Signer and pass Web3WalletConfig(signer: ...). Dialogs (showConnectWallet/showSignMessage/showTransactionConfirm) are still shown by EthereumProvider before delegating; your signer only does crypto/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.
lib/exceptions.dart:2)WalletException.code follows EIP-1193 + JSON-RPC:
4001 – user rejected4100 – unauthorized (no signer / read-only)4200 – unsupported method4900 – disconnected-32601 – method not found-32602 – invalid params-32603 – internalWeb3WebView (lib/web3_webview_eip1193.dart:825) maps WalletException.code into the thrown {code,message} consumed by provider_script.dart:127 _processError.
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.UserScript at AT_DOCUMENT_START; on Android re-evaluated in onLoadStart.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).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.lib/transaction/transaction_handler.dart:1)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.// 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
)
WalletDialogService checks Theme.of(context).brightness – no extra code needed; BottomSheetDialog barrier 0.42, cards adapt (surface #1E293B, border #334155, text #F1F5F9).FilledButton 52h/14 radius + OutlinedButton, ExpansionTile hex.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.
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);
BuildContext (for showDialog/Navigator), host, address/message/txParams, controller (for getUrl()), and the effective WalletDialogTheme (already resolved for light/dark + your custom colors).Future<bool?> – true = confirm, false/null = reject/cancel (maps to EIP-1193 4001).null builder falls back to the premium default; you can override only connect and keep sign premium.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.
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).jsonEncode (lib/provider/provider_script.dart:13, lib/web3_js_bridge_callback.dart:5, lib/ethereum/ethereum_provider.dart:841).controller.getUrl().host; consider allowlist / phishing check before showConnectWallet.rpcUrls.first is used; use HTTPS + API key, pin certs via onReceivedServerTrustAuthRequest.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).EthereumProvider – not multi-account, not multi-chain concurrent.ethers.min.js (464 KB) bundled at packages/web3_webview/assets/ethers.min.js, injected twice.eth_subscribe/eth_getLogs polling; unsupported methods throw 4200.LoadingHelper is global ref-counted overlay (lib/utils/loading.dart:3).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 (BSD).PositionExchange/flutter-web3-provider, ethers.js v6 docs.निर्भरता सूची 12 आइटम
{"sdk":"flutter"}^6.1.5^1.3.0>=2.7.3 <3.0.0^3.0.6^3.0.0^0.20.2^0.2.0^4.5.1^1.0.2{"sdk":"flutter"}^6.0.0उपयोग के अनुसार छाँटें, गतिविधि की तुलना करें और अपनाने से पहले मूल प्रोजेक्ट की आवश्यकताएँ जाँचें।
“A repository is the most basic element of GitHub.”
GitHub Docs — About repositories ↗