A self-custody web3 wallet with a built-in dApp browser for HarmonyOS NEXT, written entirely in ArkTS. No ohpm dependencies — all cryptography (BIP-39, BIP-32, Keccak-256, secp256k1, RLP, EIP-155/EIP-1559 transaction encoding, EIP-712) is implemented in pure ArkTS in this repo, so the whole wallet stack is auditable in one place.
- Wallet creation & import — 256-bit BIP-39 mnemonic generation (via
cryptoFrameworksecure RNG) or import of an existing 12/15/18/21/24-word phrase, with optional passphrase. Backup screen shows the mnemonic once. - Secure key storage — the mnemonic is stored in the system credential
area via Asset Store Kit (
@kit.AssetStoreKit), gated onDEVICE_UNLOCKEDaccessibility,REQUIRE_PASSWORD_SET,SYNC_TYPE: NEVER. The derived private key exists only in process memory while unlocked. - EVM chains — Ethereum, Sepolia, Polygon, BNB Chain, Arbitrum One, OP Mainnet and Harmony Shard 0. Native-balance display, send (legacy + EIP-1559, gas estimation, nonce management) and receive (QR code + copy-to-clipboard).
- dApp browser — a
Webcomponent with URL bar that injects an EIP-1193 provider (window.ethereum, plus an EIP-6963 announcement).eth_requestAccounts,personal_sign,eth_sign,eth_signTypedData*,eth_sendTransactionandwallet_switchEthereumChainall surface a native approval dialog (connect / sign / send / switch) before touching the key. Read-only methods pass through to the chain's JSON-RPC.
entry/src/main/ets/
wallet/
crypto/ # pure-ArkTS crypto: Bytes, Sha256/512, Hmac, Pbkdf2,
# Keccak, Secp256k1, Base58, Bip39, Bip32, Rlp, EthTx,
# Eip712, Units, wordlists/English (2048 BIP-39 words)
Chains.ets # chain registry (chainId, RPC, explorer, EIP-1559 flag)
SecureStorage.ets # Asset Store Kit wrapper
RpcClient.ets # JSON-RPC over @kit.NetworkKit http
WalletManager.ets # unlock/sign/send, nonce + gas management
provider/
InjectedScript.ets # JS shim injected into every page (EIP-1193/EIP-6963)
ProviderBridge.ets # method routing + RPC error codes (4001/4200/4902)
ApprovalQueue.ets # pending-approval queue feeding the UI overlay
pages/ # Onboarding, MnemonicBackup, Index (tabs: Wallet+Browser),
# SendPage, ReceivePage, BrowserPage (Web + approval overlay)
entryability/EntryAbility.ets
test/
crypto.test.mjs # Node test harness — runs the real .ets sources
sync.sh # copies .ets sources into test/build/*.ts for Node
- Install DevEco Studio 5.0+ with HarmonyOS NEXT SDK (API 12+).
- Open this directory. DevEco will sync
oh-package.json5automatically. - Configure a signing profile: File → Project Structure → Signing
Configs (or add a
signingConfigsblock inbuild-profile.json5). The checked-inbuild-profile.json5intentionally ships with"signingConfigs": []— unsigned builds cannot be installed on a device. - Run 'entry' on an emulator or device.
Download the official Command Line Tools (commandline-tools-*) for your
platform — e.g. commandline-tools-linux-x64 — from Huawei's repo:
https://repo.huaweicloud.com/harmonyos/ohpm/<version>/commandline-tools-linux-x64-<version>.zip
This project was built and verified on Linux x64 with
commandline-tools-linux-x64-5.1.0.840 (hvigor 5.18.5, ohpm 5.1.3, bundled
Node 18.20.1, HarmonyOS SDK 5.1.0 / API 18):
cd <unpacked>/command-line-tools/bin
./ohpm install --all # run inside the project root
./hvigorw assembleHap --mode module -p product=default -p buildMode=debug --no-daemon
./codelinter entry/src/main/ets # optional linthvigorw locates DEVECO_NODE_HOME/DEVECO_SDK_HOME itself when run from
the bin/ directory of the tool distribution.
All verification ran on Linux — no HarmonyOS device or emulator was available, so nothing below claims device-level correctness.
| Check | Result |
|---|---|
test/crypto.test.mjs under Node 24 |
117/117 assertions pass |
hvigorw assembleHap (ArkTS compile + package) |
BUILD SUCCESSFUL — entry-default-unsigned.hap produced |
codelinter on entry/src/main/ets |
0 errors, 11 warnings |
The Node harness (test/sync.sh + crypto.test.mjs) compiles and executes
the actual .ets crypto sources:
- BIP-39 — all 24 official vectors (entropy → mnemonic, mnemonic → seed)
- BIP-32 — official test vector 1 master key +
m/0'xprv derivation - SHA-256 / SHA-512 / HMAC / Keccak-256 — published vectors
- secp256k1 — keypair from known keys (privkey 1, anvil test account 0)
- Transaction signing — the EIP-155 spec example transaction byte-for-byte,
an EIP-1559 sign/serialize roundtrip,
personal_sign+ecrecover - EIP-712 — the canonical "Mail" example from the EIP digest-matched
- Base58 — roundtrip
codelinter warnings are all the same informational perf rule
(@performance/hp-arkui-use-local-var-to-replace-state-var) on @State
fields that genuinely drive UI reactivity (busy/error flags) — left as-is.
- On-device / emulator execution — no device, emulator, or
hdctarget was available on the build host. App lifecycle, navigation, and rendering are unverified. - Asset Store Kit behavior —
addSync/querySync/removeSyncagainst a real credential area, lock-screen-gated accessibility, and behavior across reboots. The calls compile and follow the SDK docs, but were never run. - The dApp browser end-to-end —
javaScriptProxy+ injectedwindow.ethereumagainst a real dApp,__harmonyResolve/__harmonyNotifycallbacks, and the approval overlay in a runningWebcomponent. - Live chain interaction — RPC calls, gas estimation, and transaction
broadcast were never executed against a real node;
RpcClientpaths are exercised only by the compiler. - Signing — the produced
.hapis unsigned; install requires a signing profile (debug or release) configured in DevEco Studio /build-profile.json5. - Compatibility — built against HarmonyOS SDK 5.1.0 targeting
compatibleSdkVersion 5.0.0 (API 12). HMSgenerateBarcode(ScanKit) is used for the receive QR; it may be unavailable on pure OpenHarmony devices (the app degrades gracefully to text-only address display).
- The private key is re-derived in memory on unlock; only the mnemonic is persisted, via Asset Store Kit, never in preferences or files.
- Possible hardening (not implemented): per-access biometric/lock-screen
authentication via
AUTH_TYPE: ANY+userAuth, or wrapping the mnemonic with a HUKS AES key before storing. - The injected provider only signs what the user approves; connected origins are remembered for the session (in-memory only) and reset on app restart.
eth_sign(raw hash signing) is supported but shows an explicit warning — it can be used to drain wallets and many providers disable it entirely.wallet_addEthereumChainandwallet_watchAssetare rejected (error 4200);wallet_switchEthereumChainworks for the built-in chain list.
./test/sync.sh # copy .ets → test/build/*.ts
node --experimental-strip-types test/crypto.test.mjs # Node 22.6+
# or simply `node test/crypto.test.mjs` on Node 24+MIT — see source headers. This is a reference implementation; audit before using it with real funds.