libwallet 0.4.15
libwallet: ^0.4.15 copied to clipboard
Multi-chain TSS cryptocurrency wallet client. Supports EVM, Bitcoin, Solana and Monacoin via direct FFI to the Go libwallet core.
0.4.15 #
- Fixed:
transactions.maxSendablereturned 0 right after a send. The bitcoin path was still scanning onlym/0whilesignAndSendhad moved to both chains in 0.4.10 — change UTXOs landed onm/1and were invisible to maxSendable. Switched to the same combined fetchsignAndSenduses. - Performance: collapsed two bitcoin UTXO fetchers into one.
Coin selection, max-sendable, listUTXOs, sign-raw, and the
simulate dry-run all now read from a single
modchain_assetscall. Previously some paths additionally hitmodchain_lookupTxoBIP32per chain just to discover the next change index — which becomes meaningful overhead on wallets with hundreds of historical outputs. Next-change- index is now derived from the same unspent set'sm/1entries (seenextChangeIndexfor the fully-spent-history caveat).
0.4.14 #
- Fixed: every bitcoin-family
signAndSendfailed with-25 bad-txns-inputs-missingorspentsince BTC support shipped on Apr 14.parseTxoRefreversed the txid bytes before storing them inBtcTxInput.TXID, but outscript also reverses TXID at marshal time (its convention is "TXID stored in displayable / big-endian form, wire reversal happens at serialize"). The double-reverse meant every broadcast tx referenced a bogus txid that was the modchain-reported id with its bytes flipped — and the litecoin / bitcoin / dogecoin / monacoin / bitcoin- cash node correctly rejected it because no such UTXO exists. All previousbad-txns-inputs-missingorspentreports attributed to "modchain reindex lag" or "stale UTXO state" were really this bug. The 0.4.12 in-memory tracker and 0.4.12 spent-filter remain useful but were never the actual fix. - Same byte-order bug fixed in
SignRawBitcoinTx(mpurse / Counterparty path): wire-decoded TXIDs were reversed before the modchain ownership lookup, making every input report as "not owned by this account". Removed the reverse. - Regression test pins the byte-order convention so a future
refactor that flips
parseTxoRefback to the old behaviour fires loudly at test time, not in production.
0.4.13 #
- Diagnostics: bitcoin broadcast errors now carry the inputs +
raw tx hex. When
sendrawtransactionrejects a tx (e.g.bad-txns-inputs-missingorspent) the error message now includes the full(txid:vout)list libwallet selected and the hex-encoded transaction. Without these, reproducing the failure required guessing which UTXO modchain reported but the bitcoin node disagreed about. Format:sendrawtransaction: <upstream> (inputs=a:0,b:1 rawhex=...).
0.4.12 #
- Fixed: bitcoin-family
signAndSenderrored with-25 bad-txns-inputs-missingorspentin two distinct cases:- modchain returning a
txo[]entry with a non-nullspentfield. Fetch path now drops anything with a populatedspentvalue before coin selection sees it. - Back-to-back sends. Send #1 spends UTXO A and creates change UTXO B; send #2 issued seconds later picked A again because modchain hadn't reindexed past send #1's mempool tx, and the broadcast failed because the bitcoin node knew A was already spent.
- modchain returning a
- New: in-memory UTXO tracker that bridges the modchain-reindex gap. After every successful broadcast it records the inputs that were just spent + the change UTXO that was just created (with the broadcast tx hash filled in). The next coin-selection call layers this on top of modchain's response — drops the spent ones, injects the pending change. TTL: 1 hour. When modchain catches up the matching pending entry auto-prunes (the modchain ground truth replaces our local copy). Process-local state — survives within one libwallet session, lost on restart (by which time modchain is current anyway). Lets a user fire several bitcoin sends in a row without waiting for confirmations between them.
0.4.11 #
- *Fixed: bitcoin-family
signAndSenderrored with "pubkey of type ecdsa.PublicKey does not support pubkey:comp export" on the first spend that touched a non-p2wpkhUTXO (legacyp2pkhor wrapped-segwitp2sh:p2wpkh). The TSS input signer'sPublic()returned*ecdsa.PublicKey, but outscript's witness builder requires a type that implementsSerializeCompressed(). Returns*secp256k1.PublicKeydirectly now (which has the method natively). Latent since forever; surfaced by 0.4.10's m/0+m/1 coin selection because before then we only ever spent receive-chain p2wpkh outputs.
0.4.10 #
- Fixed: bitcoin-family
signAndSendcould only spend receive- chain UTXOs. Coin selection used to fetch onlym/0— anything that landed on a change address (m/1/i) showed up in the balance but couldn't be spent until it happened to land on a future receive address.buildBitcoinTxnow scans both chains via a singlemodchain_assetscall and signs each input with the right key derived from its own path (m/0/*vsm/1/*). - Fixed: bitcoin-family fee under-payment with mixed-shape
inputs. Per-input vsize is now read from the actual script
type (
p2wpkh≈ 68 vb,p2sh:p2wpkh≈ 91,p2pkh≈ 148) instead of assuming every input isp2wpkh. A wallet that received funds at a legacy address mixed with segwit no longer broadcasts an under-paid tx that stalls in the mempool. account.findAccountfallback by network-derived address. Frontends often hand back the displayedAccount.address(which is the current network's derived form, e.g.ltc1...) asfrom, but the DB column still holds the creation-time address.FindAccountnow derives each candidate's address for the current network and matches — fixesLibwalletException(404): file does not existonTransaction:maxSendable(and any otherfrom-resolving endpoint) when called from a non-EVM chain.- Fixed: bitcoin-family
Asset:list(cont.) — reverted the 0.4.9 lookupTxoBIP32 sum back tomodchain_assetsnow that the backend merges receive + change correctly. Single RPC, same source of truth as the rest of the bitcoin path. txo.pathaccepted alongside legacyi/branch. Newer modchain emitspath: "m/0/0"only; the wallet now reads the trailing segment for the BIP32 child index and falls back to the legacyifield when present.- New:
accounts.listUTXOs(id, network: ...)— returns every spendable UTXO the bitcoin-family account holds across receive + change, ordered largest amount first. Each entry carriestxo,path,amount,script,address, andheight. Pair with the newutxosfield onUnsignedTransactionto power a manual coin-selection picker. - New:
UnsignedTransaction.utxos: List<String>?— when set on abitcoin_transfer, libwallet skips greedy auto-selection and uses exactly the supplied"<txid>:<vout>"entries (each verified to be owned). Empty / null preserves the auto-selection behaviour. transactions.simulate(...)for bitcoin: dry-run preview. When the tx hasn't been built yet (noraw), the simulator runs the same coin-selection + fee mathsignAndSendwould, stops short of signing, and returns the planned shape: inputs with resolvedamount+address+path, recipient + change outputs with addresses, fee in sats, and the newbitcoinChangeandbitcoinVSizefields. Honours the manualutxosselection so a manually-picked spend previews exactly what it'll send. Decode-from-rawpath unchanged.
0.4.9 #
-
Fixed: Bitcoin-family
Asset:listreported zero balance even when the xpub held funds.bitcoinBalancewas callingmodchain_assetswith the account xpub. That endpoint can returnbalance:0 / txo:nullfor some xpubs even when spendable UTXOs exist at the standardm/0/m/1paths (observed on Litecoin: a wallet with 0.1 LTC atm/0/0surfaced as 0 inAsset:listwhile every other libwallet bitcoin path — sign, max-sendable, next-address — saw the funds correctly because they all usemodchain_lookupTxoBIP32).Switched xpub balance queries to sum
modchain_lookupTxoBIP32(m/0)+(m/1), which is now the single source of truth for bitcoin-family balance / signing / max across the whole library. Plain-address fallback (used by view-only accounts with no xpub) keepsmodchain_assetssince that path works for single addresses. Amounts stay in satoshis viaoutscript.BtcAmount, so no float drift.
0.4.8 #
-
New:
accounts.addressFormats(id, network: ...)— returns every receive-address shape available for a Bitcoin-family account on the given chain, ordered by display preference (modern first). Use it to power a "show my address as Native SegWit / SegWit-wrapped / Legacy / …" picker, or to display every form a counterparty might use to send funds (the backend already watches every key type, so funds received at any of these forms land in the same balance).Per-chain coverage:
- bitcoin → Native SegWit (
bc1...), SegWit-wrapped (3...), Legacy (1...) - litecoin → Native SegWit (
ltc1...), SegWit-wrapped (M...), Legacy (L...) - monacoin → Native SegWit (
mona1...), Legacy (M...) - bitcoin-cash → CashAddr (
bitcoincash:...) - dogecoin → Standard (
D...)
The first entry's
isDefaultis true and matches the address shown inAccount.addressfor that chain — so a frontend switching fromAccount.addressto the picker sees the same primary address. Pinned by a Go test that asserts byte-equality betweenAddressFormats[0].addressand the canonicalbitcoinAddress()output, so the default entry can never silently drift from the rest of libwallet. - bitcoin → Native SegWit (
-
New Dart models
AddressFormatandAddressFormatsResult, exported from the top-level barrel.
0.4.7 #
- Fixed: every EVM
signAndSendwithout a priorvalidatecall errored with "invalid maxFeePerGas". The fee-population block (MaxFeePerGas / MaxPriorityFeePerGas / Nonce / Gas) lived only insideTransaction:validate, so a Dart caller that built anUnsignedTransactionand shipped it straight tosignAndSendreached signing with empty fee fields.signAndSendnow runsvalidateas its first step (idempotent — only fills empty fields). Side benefit: closes a latent bug where anerc20_transferbuilt withoutvalidatewould have signed with the recipient address astoinstead of the token contract. - Fixed:
swap.maxSpendablefor SOL → SPL handed back amounts Jupiter would reject when the user didn't already hold the output mint. Jupiter / dFlow auto-injectcreateAssociatedTokenAccountfor the destination, costing ~2,039,280 lamports of rent paid by the taker. The previous max reserved only the system-account rent, leaving the wallet too tight to cover the new ATA — Jupiter then returned HTTP 400 "Failed to get quotes". Now: when the input is native SOL and the user has no ATA for the output mint, the output ATA's rent-exempt minimum is subtracted from the resolved max (probed live viagetTokenAccountsByOwner+getMinimumBalanceForRentExemption, with a canonical fallback). Doesn't apply to non-Solana chains, native→wSOL, or when the user already holds the output mint.
0.4.6 #
- New:
swap.maxSpendable(...)— returns the sameSwapQuoteshape asswap.quote(), automatically resolved to the largesttokenInamount the account can spend.quote.amountIncarries the resolved value so the UI can render "MAX → 1.234 SOL" alongside the standard quote display. Native input reserves the network fee + (Solana) rent-exempt minimums; token input returns the full balance because gas is paid in the chain's native currency. Returnsinvalid_requestif the resolved max is zero. - New:
"MAX"sentinel onswap.quote(amountIn: ...). Same end result asswap.maxSpendable— the libwallet side resolves the max amount before issuing the upstream quote, so a Max button in a swap form can wire straight toquote()without branching on a separate code path. transactions.maxSendable()now supports tokens. Previously errored with "v1 supports native assets only" for any token asset. SPL (Solana) and ERC-20 (EVM) now returnmax == balance(fees are paid in native currency, so the full token balance is spendable). Thefeefield reports the native-currency fee a token transfer would cost so the UI can warn when the user doesn't have enough native to cover gas.
0.4.5 #
- Fixed: every Solana swap quote crashed with
404 Not Foundfrom Jupiter Ultra. Jupiter's/ultra/v1/orderendpoint accepts onlyGETwith query parameters; we'd been POSTing JSON since the swap feature shipped. Switched to GET withurl.Values;/executestays POST (the signed transaction blob doesn't fit a query string). The httptest-backed adapter test is now pinned to GET so a future regression fires loudly instead of silently 404'ing in production. - Improved: surface Jupiter routing errors verbatim. When
Jupiter returns HTTP 200 with
transaction:""(insufficient funds, no route, slippage too tight) the adapter now passes through the upstreamerrorMessageinstead of the generic "Jupiter returned an empty order".
0.4.4 #
- New:
Token:listCuratedendpoint +tokens.listCurated(network)Dart method. Returns a vetted list of well-known tokens per chain (USDT / USDC / DAI / WBTC / WETH / LINK / UNI on EVM mainnet, USDC / USDT / SOL / mSOL / JUP and ~650 other Jupiter- verified mints above $1M mcap on Solana mainnet, plus hand-curated entries upstream feeds don't carry — notablyDRtvTCzfiKGhCVREmBbZdN9sB8PHeq9KdRZ3VmFhpump("Tibane Thecat", $ChiefPussy)). Frontend use cases:- "Swap to X" dropdown without asking the user to paste a contract address.
- Map an unrecognized mint / contract in the user's balances to
its
symbol+logoURI+tags. - Pass
"<type>.<chainId>"form (same shapeAsset.networkreturns — e.g."evm.1","solana.mainnet").
- New Dart model
CuratedTokenwithchainKey,address,symbol,name,decimals,type,logoUri,coingeckoId,cmcId,tags+isStablecoin/isWrappedconvenience getters. Exported from the top-level barrel. - Data source: embedded JSON per chain (go:embed), refreshed at
release time via
go generate ./wlttoken/curated/...which pulls Uniswap's default list for EVM and Jupiter's verified feed for Solana. No runtime external fetch, no API keys. Hand-curated overlays merge on top of the generated base. - SPL balance enrichment: on Solana,
Asset:listnow readsname/symbolfrom the curated registry when the mint is well-known. USDC on Solana used to surface asSymbol="EPjFWd"/Name="EPjFWdd5..."; now surfaces asSymbol="USDC"/Name="USD Coin". Unlisted mints keep the previous truncated display. - Chains seeded on day 1: EVM 1 / 10 / 56 / 137 / 324 / 8453 / 42161 / 43114 + Solana mainnet. Gnosis (100), Fantom (250), Linea (59144) are registered with empty lists — Uniswap doesn't cover them; to be filled by an alternative feed or overlay in a follow-up.
0.4.3 #
- Fixed:
Asset:listcrashed with-32602 Invalid param: WrongSizeon Solana for any wallet whose account is secp256k1 (EVM-flavoured).Account.UpdateAddressForNetworkwas blindly base58-encoding the 33-byte compressed secp256k1 pubkey as if it were a 32-byte ed25519 point; Solana'sgetBalancethen rejected the oversized pubkey. Non-ed25519 accounts on a Solana network now resolve toAddress="N/A"(same convention EVM / Bitcoin use for ed25519 accounts) and the balance call is skipped. - Fixed:
Swap:availabilityreported every Solana wallet asunsupported_chain. The live Solana network row usesChainId="mainnet"(set bywltnet/api.go), but the availability gate only accepted Solana's internal cluster name"mainnet-beta". The Swap button was hidden on Solana as a result. Gate now matches the real stored ChainId.
0.4.2 #
- Fixed:
eth_sendTransactionapproval crashed with "failed to get env" before signing. The transaction-sign approval handler was passing*env(whose embedded context is the bare psql sqlCtx) toTransaction.SignAndSend, which expects an apirouter context so it can extract the env. Now passes the original apirouter context. This also clears the cascading "unexpected end of JSON input" the dApp side reported — that was the dApp's parser choking on the stringified upstream error. - Fixed:
personal_ecRecoverreturned RPC error -32601 ("method does not exist"). The previous default-relay path forwarded it to the chain's JSON-RPC node, butpersonal_ecRecoveris a wallet- side operation and most public nodes don't implement it. Now handled locally: applies the EIP-191 prefix, runs ECDSA recovery, returns the EIP-55 address. Accepts both{27, 28}and{0, 1}v bytes, matching MetaMask / ethers / viem tolerance. - Fixed:
wallet_switchEthereumChaincrashed loading the approval back out of psql withmath/big: cannot unmarshal "1.74…e+76" into a *big.Int.Account.IL(the BIP32 intermediate value) was emitted as a raw JSON number, lost precision through float64 in theanyroundtrip the request loader does, then failed to unmarshal.Accountnow has customMarshalJSON/UnmarshalJSONthat emit IL as a JSON string and parse the string-or-number / scientific-notation forms on the way back in.
0.4.1 #
Wallet:probeActivity— new endpoint for mnemonic-backed wallets. Walks the BIP44 standard derivation paths for every supported chain (BTC BIP44 + BIP84, LTC BIP84, MONA BIP44 + BIP84, BCH BIP44, DOGE BIP44, EVM mainnet, Solana Sollet + Phantom conventions) and probes each candidate's RPC in parallel for on-chain activity. Returns one row per candidate with the derived address, pubkey, raw balance, and ahasActivityflag. Host UI uses this to auto-select which chains to migrate; per-candidate RPC errors land onrow.errorso one upstream failure doesn't fail the whole scan.Wallet:promoteMnemonic— new endpoint. Migrates a mnemonic wallet into N fresh MPC wallets, one per chain the caller picked from the probe output. Each migration derives the mnemonic at the chain's BIP32 path (full hardened BIP32 for secp256k1 viaecckd.Derive; Sollet seed[:32] / SLIP-0010 for ed25519) and runs TSS resharing on the resulting privkey. The source mnemonic wallet is NOT modified — the caller validates each migrated wallet, then deletes the source separately. secp256k1 source only in this release; ed25519 mnemonic migration is a follow-up.- Dart models added:
ProbeActivityRow,ChainMigration.ChainMigration.fromProbeRow(row, stripAddressSuffix: true)drops the trailing/0/0address-suffix so migration lands at the BIP44 account level (m/44'/60'/0') instead of a specific leaf address — preserves the ability to derive child receive / change addresses from the new MPC wallet. - Test vectors locked in (
wltwallet/bip44_vectors_test.go,derivation_test.go): the user-supplied BTC / EVM / BTC-segwit / Solana addresses for two reference mnemonics pin the derivation math so a future change can't silently land imports on the wrong address and lose user funds. - Existing mnemonic import sign path unchanged in this release —
the mnemonic wallet still signs at the BIP32 master, so direct
signing from a mnemonic wallet won't match MetaMask / Phantom
addresses. Users hitting that: run
Wallet:probeActivitythenWallet:promoteMnemonicto get proper per-chain MPC wallets, and sign from those. The direct-sign path will be rewritten to use Account-level derivation paths in a follow-up.
0.4.0 #
-
Import existing wallets: raw private keys + BIP39 mnemonics, with promote-to-MPC. Three new endpoints on
client.wallets:-
importPrivateKey({privateKey, curve, name, keys})— accepts0x-prefixed hex, bare hex, or Bitcoin-family WIF (auto-sniffed; WIF only forsecp256k1). The imported wallet is stored as a 1-of-1 share with a newRawKeycontent type and is signable immediately — no TSS rounds, just directcrypto/ecdsa/crypto/ed25519. -
importMnemonic({mnemonic, passphrase, curve, name, keys})— auto-detects the BIP39 wordlist (English, Japanese, Korean, Spanish, Chinese Simplified / Traditional, French, Italian, Czech) and stores the decoded entropy + the detected language tag, NOT the raw mnemonic string. That lets the same backup be re-rendered in any other language for display, while keeping the seed derivation stable (BIP39's PBKDF2 is sensitive to the literal mnemonic, so the original language must drive sign-time derivation). Optional BIP39 passphrase supported. -
promote(walletId, {oldKeys, newKeys, threshold})— converts an imported 1-of-1 wallet into a normal N-of-T TSS wallet via tss-lib's resharing protocol. The master pubkey and chaincode are preserved (the wallet's address does NOT change) — only the storage of the signing key changes from "single share, full privkey" to "M shares with T-threshold reconstruction". After promote the imported share row is deleted; the wallet looks identical to a freshly-created TSS wallet. secp256k1 only in this release; ed25519 promote is a follow-up.
All three reuse the existing
KeyDescriptionencryption layer (Password,StoreKey,RemoteKey,Plain) —RawKey/Mnemonicare content types, the encryption-at-rest mechanism is orthogonal. So importing withKeys: [Password(...)]works out of the box.Both curves on day 1 for the import path:
secp256k1(EVM / Bitcoin family) +ed25519(Solana). -
0.3.32 #
- iOS: ship as
.xcframework(Apple's recommended format). The podspec'sprepare_commandnow wraps the per-SDK static archives intolibwallet.xcframeworkviaxcodebuild -create-xcframework, and the spec switches fromvendored_librariestovendored_frameworks. CocoaPods picks the right slice for the active SDK at build time, eliminating the wrong-SDK "ignoring file ... built for iOS [Simulator]" warning the previous layout emitted on every link.-force_loadis still required because the FFI entry points are dlsym'd, but it now reaches into the xcframework's source slice (which exists frompod installonward) — Xcode's link-phase input validation runs before the CocoaPods Copy XCFrameworks build phase, so referencing the build-time copy path errored with "Build input file cannot be found" even though the file would exist by link time.
0.3.31 #
- Fixed:
personal_sign/eth_signTypedData_v3/_v4returned DER, not Ethereum wire format. ecrecover, viem.verifyTypedData, ethers.verifyMessage, OpenSea, Snapshot, Permit2, MetaMask test-dapp's Recover button — every off-chain Ethereum signature verifier — would reject the output with "Invalid signature v value" or a silent address mismatch. Now produces the canonical 65-byte form R(32) || S(32) || V(1) where V ∈ {27, 28} via the newwltacct.SignEthereumDigesthelper, which post-processes the TSS signer's DER output (parse → bruteforce recovery code → repack). Same fix applied to the host-directAccount:signMessageflow withMode: "evm"/"personal_sign". EIP-155 chain-id adjustment is intentionally NOT applied — that lives in the on-chain transaction signing path; off-chain flows always use legacy v.
0.3.30 #
-
Info:versionnow exposes the release tag. Newversionfield alongsidedateTag/gitTag, populated from thev*tag the binary was built from (empty on dev / non-tagged builds). The Dart wrapper got a typedclient.info.versionInfo()returning aVersionInfo(version + gitTag + dateTag) for diagnostics; the long-brokenclient.info.version()now correctly returns the release-tag string instead of the toString'd map. -
Runtime version-mismatch detection (with release-mode rejection).
LibwalletClient.initializeasynchronously callsInfo:versionand compares it against the hardcodedlibwalletPackageVersionconstant. Result is exposed asclient.ready(aFuture<void>):- Debug/test Dart VM (
dart.vm.product=false): mismatch logs an actionable warning viadart:developerandreadycompletes normally — debug runs of the in-tree test app can iterate against a locally-built binary without ceremony. - Release Dart VM (AOT,
dart.vm.product=true):readyrejects with aStateErrorcarrying the same message. Apps shouldawait client.readyafterinitializeand surface a fatal-error UI on failure — operating with mismatched wire shapes is what causes events to arrive asUnknownPendingRequest, sign approvals to error with "keys are required", etc.
Catches the same class of bug across iOS (skipped
pod install), Android (stale build-hook cache), and macOS/Linux/Windows. - Debug/test Dart VM (
-
Fixed: build-time
-Xldflags targeted the wrong package. TheMakefileandbuild.ymlhad been passing-X main.dateTag=.../-X main.gitTag=...for the entire history of the project, but those variables live inwltbase. The-Xwas a silent no-op — every release binary shipped with emptydateTag/gitTag. Corrected to use the fully qualified package path; release binaries built on or after this commit return populated values fromInfo:version. Also propagated the ldflags to the c-shared / c-archive Dart-FFI builds, which had no version metadata at all. -
Release tooling:
dart/tools/bump_version.dart. One command (dart run tools/bump_version.dart --patch/--minor/--major/ explicitX.Y.Z) rewrites bothpubspec.yamlandlib/src/version.dart— the two files that have to move in lockstep for the runtime mismatch check to work. CI runs the script's--checkmode on every push so a release commit that only touchespubspec.yamlfails fast.
0.3.29 #
- CI publish workflow: install BOTH Flutter and Dart. v0.3.28
switched from
dart-lang/setup-darttosubosito/flutter-actionso the publish job had Flutter on PATH (needed once the package pinned a Flutter SDK lower bound). But Flutter's bundled Dart does not configure pub.dev OIDC credentials, sodart pub publish --forcehung indefinitely waiting for an interactive auth flow. Install the Dart SDK action after Flutter so its credential plumbing is the one in effect.
0.3.28 #
- CI publish workflow: install Flutter alongside Dart. Once the
package declares a Flutter SDK lower bound (added in 0.3.27), plain
dart-lang/setup-dartrejectsdart pub getwith "libwallet requires the Flutter SDK, version solving failed". Switch the publish step tosubosito/flutter-actionso the Flutter+Dart SDK pair is available on PATH. Cuts a no-op release after v0.3.27 because the workflow file used at publish time is the one snapshot-bound to the triggering tag, so the fix only takes effect on a fresh tag push.
0.3.27 #
- pubspec: declare a Flutter SDK constraint. Required by pub.dev
whenever
flutter.plugin.platformsis set; published as a no-op release after v0.3.26 was rejected at validation. v0.3.26's GitHub Release assets remain valid for any pinned consumer.
0.3.26 #
-
Build hook: invalidate the binary cache on a Dart upgrade. The
hook/build.dartcache filename was version-agnostic (liblibwallet-android-arm64.so), so a previously cached binary kept serving stale code afterdart pub upgrade— the Dart layer would decode events using the new shape while the loaded.so/.dylibstill emitted the old shape. Most visible failure: post-0.3.24 signing events arriving with pre-unification type strings likesign_typed_datainstead ofmessage_sign, falling through toUnknownPendingRequest. Cached filenames now embed the package version (liblibwallet-android-arm64-v0.3.26.so); the nextdart pub getafter this upgrade re-downloads the matching binary. -
iOS: ship as a Flutter FFI plugin (fixes external
dlsymfailure). The build hook'sLinkMode = LookupInProcess()for the iOS.aarchive worked for the in-tree test app, but Flutter's iOS pipeline did not reliably pass the static archive through to Xcode's linker for external consumers — the FFI symbols (LibwalletInit, …) got dead-stripped anddlsymfailed at runtime with "symbol not found". This release addsflutter.plugin.platforms.ios.ffiPlugin: truetopubspec.yamland shipsios/libwallet.podspec, which downloads the matching per-SDK static archives from the GitHub Release atpod installtime and force-loads them into the host app target via per-SDKOTHER_LDFLAGS. Both device + simulator slices are pulled, combined into a single fat simulator archive vialipo, and the per-SDK xcconfig picks the correct one per build configuration. No code changes for app authors —flutter pub upgradethen a freshpod installis enough.
0.3.25 #
- Rich payloads on the remaining approval events. Same
decode-at-emit philosophy as 0.3.24's sign events, now applied
to
ConnectRequest,AddNetworkRequest, andWatchAssetRequest:ConnectRequestnow carriesmethod(which RPC asked),family(evm/solana/bitcoin),availableAccounts(curve-compatible accounts pre-fetched for the picker),alreadyConnectedIds(pre-check in picker; render "Reconnect" vs "Connect"), andrequestedPermissions(EIP-2255).AddNetworkRequestflags phishing vectors:isKnown(chainId in the static chain registry),knownName(the canonical name — compare tonetwork.nameto detect impersonation),alreadyExists(no-op approval), and thenameMismatchconvenience getter.WatchAssetRequestgains typed EIP-747 accessors:assetType,address,symbol,decimals,image,tokenId, plusaddressLooksInvalidandisAlreadyTrackedheuristics. The oldassetraw-map getter still works for backward compat.
0.3.24 #
-
BREAKING: unified signing events. Every on-chain transaction signing flow (
eth_sendTransaction,solana_signTransaction,solana_signAndSendTransaction,mpurse_signRawTransaction) now comes through a singleTransactionSignRequest. Every arbitrary-data signing flow (personal_sign,eth_signTypedData*,solana_signMessage,mpurse_signMessage) comes through a singleMessageSignRequest. Branch onreq.method(andreq.chain) for chain-specific copy.Removed Dart classes:
SignRequest,PersonalSignRequest,SignTypedDataRequest,SolanaSignMessageRequest,SolanaSignTransactionRequest,SolanaSignAndSendTransactionRequest,MpurseSignMessageRequest,MpurseSignTransactionRequest. -
Rich decoded payload on every event — host UIs no longer need a follow-up
Transaction:simulatecall to render an approval sheet.TransactionSignRequestcarries:decodedMethod/decodedArgs— recognised top-level operation (native_transfer,erc20_transfer,erc20_approve, …)effects— every transfer / approve at any call depthbalanceChanges— signed native-balance deltas per addresswarnings— stable-coded advisories (recipient_is_contract,erc20_approve_unlimited,net_loss_exceeds_amount, …)willRevert/revertReasonfeeAmount+feeDecimals+feeSymbol,networkName,sizeBytes,raw- chain-specific extras:
evmTransaction,solanaUnitsConsumed,solanaLogs,bitcoinInputs,bitcoinOutputs,bitcoinFeeSats
EVM gets the full simulate decoder run at request-emit time; Solana decodes the common System Program transfer locally; Bitcoin runs through the existing
simulateBitcoindecoder. -
MessageSignRequestdecoded payload:messageBytes(raw) +messageText(UTF-8 try)structuredData/structuredPrimaryType/structuredDomainfor EIP-712- Auto-detected SIWE / SIWS (
isSiwe/isSiws) with parsedsiweFields(domain,address,uri,version,chainid,nonce,issuedat,expirationtime, …) — UIs can render a friendly "Login to example.com" prompt instead of a raw message body. warnings(e.g. message contains a URL)
-
New helper types for the rich payload:
TxSignEffect,TxSignBalanceChange,TxSignWarning,TxSignBitcoinIO— exported frompackage:libwallet/libwallet.dart.
0.3.23 #
-
BREAKING: unified network-switch approval. Every network switch —
wallet_switchEthereumChain(with or without an unknown-but-recognized chain) AND cross-family action methods — now flows through a singleChainSwitchRequestevent. Two shapes distinguished by which fields are populated:- Pre-specified target (
req.targetNetwork != null): dApp named a specific chain. Render a confirm sheet. Whenreq.isNewNetworkis true, the chain isn't in the wallet yet and approval implies Add + Switch. Approve withaccounts: [accountId]only —networkis taken from the request. - Picker (
req.targetNetwork == null,req.candidateNetworkspopulated): dApp triggered a cross-family action. Render a network + account picker. Approve with bothnetwork: pickedIdandaccounts: [pickedId].
ChangeNetworkRequestandAddAndSwitchNetworkRequestare removed. Hosts pattern-matching on those need to switch toChainSwitchRequestand branch onreq.targetNetwork != null. Seedart/doc/webview_integration.mdfor the new example.AddNetworkRequestis unchanged — pure add (no switch) is a distinct intent fromwallet_addEthereumChain.On approval libwallet still does Save (when new) + SetCurrent + implicit connect server-side. Hosts do not need a separate
client.networks.setCurrent(...)call. - Pre-specified target (
0.3.22 #
- EIP-2255 wire shape fix.
wallet_requestPermissionsandwallet_getPermissionsnow return one permission entry per capability (the EIP-2255 shape) with all authorised addresses in a singlerestrictReturnedAccountscaveat — instead of one entry per account with a missingparentCapabilityfield. dApps that readperm.parentCapability(etherscan.io, MetaMask test-dapp, most wallet UI kits) now get"eth_accounts"instead ofundefined. eth_accountsis filtered to EVM-compatible addresses. Solana / Monacoin accounts that happen to be connected to the same dApp no longer leak through, and"N/A"placeholders (from ed25519 accounts re-derived for an EVM network) are dropped. Empty result is[], notnull.- Cross-chain auto-switch (
ChainSwitchRequest). When a dApp calls an action method (sign / send / connect) on a chain family different from the wallet's current network, libwallet now emits achain_switchapproval that lets the user pick BOTH the target network AND an account in one prompt. On approve, the wallet switches network and saves aConnectedSitefor(host, account)so the original method runs with the dApp already connected. Skip the prompt automatically when the wallet has no compatible network or account for the requested family — original handler errors more informatively in that case. Web3:requestrecogniseswallet_revokePermissions(EIP-2255 revoke). Was unhandled in pre-0.3.21 builds and fell through to the chain RPC relay producingfailed to decode response … invalid character 'm'. Already fixed in 0.3.21; the docs now call this out explicitly.wallet_switchEthereumChainaccepts both spec + bare-string param shapes. Etherscan and other EIP-3326-compliant dApps no longer 500 withfailed to convert map[string]interface {} to string. When the target chain isn't registered yet but is in libwallet's static metadata, emits a combinedadd_and_switch_networkapproval instead of bouncing 4902.- Network change happens server-side. When the user approves
any network-changing request (
change_network,add_and_switch_network,chain_switch), libwallet callsSetCurrentitself before returning to the dApp. Hosts no longer need to callclient.networks.setCurrent(...)afterrequests.approve(...)— the workaround is redundant on 0.3.22+. - NFTs on non-mainnet EVM chains return
[]instead of 500.Nft:liston Sepolia (and any other non-mainnet EVM chain not covered by the modchain provider) now returns an empty list so the wallet UI just renders "no NFTs" instead of failing the whole asset view. - Doc: webview integration guide gained a "Network + permission
semantics" section answering who switches the network on
approval, who switches the account, what the EIP-2255 wire shape
is, and where the typical workarounds were masking real bugs.
Step 4's example switch covers
AddNetworkRequest,ChangeNetworkRequest,AddAndSwitchNetworkRequest,ChainSwitchRequest, andWatchAssetRequestwith concrete approve calls.
0.3.21 #
- Fix:
wallet_revokePermissions(EIP-2255). Was unhandled in 0.3.20 and fell through to the chain-RPC relay, surfacing asinvalid character 'm' looking for beginning of valueon etherscan.io and any other dApp that calls it. Now revokingeth_accountsdrops everyConnectedSiterow for the requesting host (same effect assolana_disconnecton the Solana side) and returns null. Unknown permissions are silently ignored for forward-compat (matches MetaMask).
0.3.20 #
- Fix:
swap.availability()404 on 0.3.19. The handler signature included a spurious*struct{}second arg that apirouter's dispatcher didn't match, so the endpoint never resolved at call time. Realigned to the idiomatic zero-param shape used by other parameterless handlers. - Fix:
wallet_switchEthereumChainparams. 0.3.19 decoded the first param as a string, which failed withfailed to convert map[string]interface {} to stringon EIP-3326-compliant dApps like etherscan.io. Now accepts both the spec shape[{chainId: "0x…"}]and the bare-string form some non-compliant dApps still send. - New: combined add + switch approval flow. When a dApp calls
wallet_switchEthereumChainfor a chain the wallet hasn't seen yet but which libwallet recognizes from its static chain metadata, emits a singleadd_and_switch_networkapproval request (newAddAndSwitchNetworkRequestsubtype). The UI can render "etherscan.io wants to add Polygon and switch to it" in one prompt instead of bouncing the dApp with a 4902 error and forcing it to retry throughwallet_addEthereumChain. Unknown-to-libwallet chains still return 4902. - Breaking:
rawRequest/rawRequestWithProgressremoved fromLibwalletClient. The typed API namespaces cover the feature surface; the raw door encouraged callers to hardcode paths and couple themselves to internal wire shapes, which meant every server-side rename silently broke them. Migrate to the equivalent typed call (client.info.ping(),client.transactions.list(), etc).
0.3.19 #
- New
swap.availability()endpoint — UI feature-flag for the "Swap" button. No RPC calls; returns{available, network, providers, reason}in a couple of ms. Gate per specific<type>.<chainId>(e.g."evm.1"/"solana.mainnet-beta") so devnet / testnet / unsupported EVM chains don't render the Swap affordance:- Solana mainnet → available (Jupiter + dFlow).
- Solana devnet / testnet →
unsupported_chain. - EVM chains covered by 1inch (
1,10,56,100,137,250,324,8453,42161,43114,59144) → available onceOneInchAPIKeyis compiled in;missing_api_keyuntil then. - Other EVM chains →
unsupported_chain(1inch doesn't cover them — would 404 upstream). - Bitcoin-family (bitcoin, bitcoin-cash, litecoin, dogecoin,
monacoin, …) →
unsupported_chain. SwapAvailability.chainFamily/chainIdgetters splitnetworkfor apps that need the family vs. the specific id.
0.3.18 #
accounts.delete()now cascade-removes Web3 connections that reference the deleted account. Before 0.3.18 those rows were left behind pointing at a non-existent account id — every list of connected sites for that account would keep returning stale data until the user manually deleted each one. Done synchronously, so no orphan window exists. Scoped: unrelated connections for other accounts are untouched. Transactions are intentionally NOT cascaded (tx history outlives the originating account by design).
0.3.17 #
Transaction:listnow supports cursor pagination + filters that the docs always promised. Up to 0.3.16 the handler was hardcoded to 50 rows and theFrom/Networkquery params were silently ignored on this path (onlyDELETE Transactionhonoured them). Newbefore(RFC3339Nano cursor onCreated) andlimit(default 50, capped 200) params drive an infinite-scroll pattern — the response stays a flat list, clients derive the next cursor fromlast.created. Darttransactions.list()gains the new parameters with an example in the docstring.
0.3.16 #
- Fix:
Transaction:signAndSendnow backfillsFeeserver-side before saving. The new typedUnsignedTransactiondeliberately omitsfee(server is the source of truth) but signAndSend wasn't recomputing it on its own — apps that went straight to signAndSend (or used the typed shape) ended up withnullFee in tx history. Same formulas Validate already uses (gas × gasPrice on EVM,5000 + ceil(cuLimit*cuPrice/1e6)on Solana). No client change needed. Transaction:maxSendableno longer takesnetwork— it's derived from the asset key's<type>.<chainId>.prefix (the same shapeAsset:listreturns). Empty / bareNATIVEfalls back to the current network. Pre-release cleanup; 0.3.15 was not consumed externally with the old shape.
0.3.15 #
- Swap API (
SwapApi/client.swap): token swaps on Solana (Jupiter Ultra primary, dFlow fallback) and EVM (1inch). Two-step flow:quote()returns aSwapQuotewith expected output, min output after slippage, route breakdown, and a 90 s quoteId;execute(quoteId, keys)signs and broadcasts, returning aSwapResultwith the on-chain tx hash + explorer URL. All providers are wired with a 50 bps referral fee to libwallet's fee accounts (Solana:BF436…, EVM:0x17Ab…). - Approval detection + tight default on EVM swaps: for ERC-20
input tokens on 1inch,
quote()now reads the router's current allowance viaeth_calland populatesSwapQuote.requiresApproval,approvalSpender,currentAllowance, andneededAllowance. WhenrequiresApprovalis true, call the newswap.buildApproval()— it returns a richApprovalPreview(token, spender label, amount,isUnlimitedflag, current allowance, network fee, plus the validatedTransactionto sign) ready to drop into an approval sheet. Default approval amount is exactly the swap's input amount, so a compromised router can only drain what the user already agreed to. PassapprovalAmount: 'max'to opt into the classic unlimited approve, or a decimal string for a custom cap — surface the trade-off viapreview.isUnlimitedin the UI. - Richer Quote payload for UI approval sheets:
SwapQuotenow carriesproviderLabel(human-friendly name),referralFee(the 50 bps platform fee as an absolute amount in the input token's units), andnetworkFee(estimated chain gas in native currency). Apps no longer have to compute these from bps or gas*gasPrice themselves. - Known limitations in v1:
- The 1inch API key ships empty in this build; populate
wltswap.OneInchAPIKeyto enable EVM swaps. - No token resolver yet: callers pass
SwapTokenRefwithaddress+decimalsfully resolved (the data is already available fromAsset:list).
- The 1inch API key ships empty in this build; populate
0.3.14 #
Transaction:maxSendable(TransactionApi.maxSendable): new cross-chain endpoint that returns the largest amount safely sendable from an account, with a breakdown of the fee and (on Solana) rent-exempt reservations. Fixes the "tap Max → getinsufficient funds for rent" bug: apps can now pre-compute the right amount instead of letting the broadcast fail. EVM and Bitcoin supported; token (ERC-20 / SPL) assets return an explicit error — full token balance is always sendable, fees paid in native.- Solana native-send preflight:
Transaction:validatenow runs a balance / fee / rent check for Solana native transfers before signing. Typed codes:insufficient_balance,below_sender_rent,recipient_rent_not_funded— apps see a structured error with the exact shortfall instead of Solana's opaque simulator rejection. TransactionSimulation.warnings: simulate now returns a list of advisoryWarnings with stable codes. Non-blocking — the tx can still be signed; apps decide whether to confirm with the user. Initial codes:recipient_is_contract(EVM native send to a contract address),recipient_new_account(Solana recipient doesn't exist yet),erc20_approve_unlimited(approve with top bit set — drainer vector),priority_fee_recommended(Solana median priority fee > 0 but tx has no ComputeBudget).- Solana priority fees (opt-in): new
computeUnitLimit/computeUnitPrice/priorityLevelfields onUnsignedTransaction. SetpriorityLevel: "low" | "medium" | "high"to havevalidatepick a percentile of recent on-chain prioritization fees; or pincomputeUnitPrice(microlamports/CU) directly."none"opts out explicitly. Empty (default) preserves the legacy 5000-lamport flat fee — the serialized message is byte-identical to pre-0.3.14 for unchanged callers. - Solana displayed balance excludes rent reserve: a user who
receives 0.01 SOL now sees
0.01in their wallet instead of0.01089(the extra ~0.00089 is the rent-exempt minimum the account needs to stay alive on-chain and is never spendable without closing the account).maxSendablestill reports the raw balance and breakdown for apps that want to show "0.01 spendable- 0.00089 reserved + 0.000005 fee".
0.3.13 #
-
Logs routed over the event channel (fixes 0.3.12's silent logging on iOS). 0.3.12 wired every internal diagnostic through
wltlog, but the underlyinglog.Printfwrites to the Go runtime'sos.Stderr— which Flutter+iOS swallows entirely, and Flutter+Android filters out offlutter logsby default. End result: testers saw no output even withlogLevel: "debug". Fixed by routing every wltlog emission through the apirouter broadcast channel (same pipe Web3 requests / balance changes already use). -
New
LogEvent+client.logsstream: subscribe once at startup and forward todeveloper.log/printso the logs show up in Flutter's log output on every platform:import 'dart:developer' as developer; client.logs.listen((e) { developer.log(e.message, name: 'libwallet.${e.level}'); }); await client.info.setWalletInfo( clientId: '...', logLevel: kDebugMode ? 'debug' : 'off', );LogEventis also emitted on the generalclient.eventsstream for hosts that want a single subscription. -
Sink safety: a panic inside the sink falls back to stderr with no rethrow, so a broken logging pipeline can never take down a send.
0.3.12 #
- Leveled logging (
wltlog) controlled bysetWalletInfo: newLogLevelfield onWalletInfo. Valid:"debug" | "info" | "warn" | "error" | "off"; empty resolves to libwallet's auto-default —"debug"on dev binaries (gitTag empty),"info"on release binaries. Typical pattern:logLevel: kDebugMode ? "debug" : "off". Every log call site routes throughwltlog.{Debugf,Infof,Warnf, Errorf}; lines are prefixed[debug] / [info] / [warn] / [error]so testers can grep by level regardless of the host's logger.getWalletInfoalso returnseffectiveLogLevelso the host can see what libwallet actually resolved""to. - Ed25519 self-heal diagnostics: the self-heal now logs a
specific skip reason at every gate (nil account, no wallet,
GetEnv(ctx)nil,WalletByIdfailed, wrong curve, no keys, decrypt failed, empty want, already-correct), visible atdebug. The actual repair (Pubkey/Address flip) logs atinfo. Combined with the always-onwantvsacct.Pubkeyvswallet.Pubkeydump, a tester flippinglogLevel: "debug"gets everything needed to pin down why a Solana send fails. FindAccountnow runscheck()on the address-lookup path: previously only the ID-lookup branch refreshed Curve / Address — tx.From is almost always an address, so account records with an empty Curve (rare but possible) would silently short-circuit the Ed25519 self-heal's curve gate. Fixed by callingacct.check(e)after the by-Address fetch.- Pre-broadcast Ed25519 verify:
Transaction:signAndSendon Solana now runsed25519.Verify(fee_payer, message, sig)locally before sending to the RPC. Catches pubkey/key-share mismatches with a specific error message ("TSS key shares may be inconsistent with stored pubkey") instead of the generic Solana-side rejection. - Extended pre-flight repair to every Solana sign path: 0.3.11
put the pre-flight only in
Transaction:signAndSend. The shared helperwltacct.EnsureEd25519PubkeyOnAccountis now called fromAccount:signMessage(solana mode),Account:signTransaction,Account:signAndSendTransaction, and Web3solana_sign_message/solana_sign_transaction/solana_sign_send_transaction. The helper also saves the repaired Account row synchronously so the dApp's nextwindow.solana.publicKeyread returns the corrected address. - Per-RPC timing logs (at
debug): everyNetwork.DoRPC/DoRPCNamedemitsrpc: chain=X method=Y OK in Nms (B bytes)orFAIL in Nms: err. Quiet atinfo; noisy but invaluable when reproducing a bug. - Per-key-decrypt timing (
wallet-signlogs, atdebug): entry line with wallet id/threshold/keys/msg_len; per-key "decrypted in N ms (type=Password|StoreKey|…)". Pubkey mismatch detected during sign logs atwarn.
0.3.11 #
- Solana ed25519 self-heal across every sign path: 0.3.10 only
repaired the legacy pubkey encoding inside
Transaction:signAndSend. A tester reported sends still failing on 0.3.10, which turned out to be a different entry point:Account:signAndSendTransactionand the Web3solana_sign_{message,transaction,send_transaction}approvers bypassed the repair. Extracted the fix intowltacct.EnsureEd25519PubkeyOnAccountand wired it into every Solana-capable sign path, includingAccount:signMessage. The helper also saves the repaired Account row synchronously (not just via the asyncwallet:pubkey_repairedhandler) so the nextFindAccount/window.solana.publicKeyread returns the corrected address in the same request lifecycle. - Visibility log: self-heal now emits
ed25519-repair: account <id> (wallet <id>) pubkey/address repaired: ...tolog.Printfwhen it fires. If affected users report that sends still fail after upgrading, grep logs fored25519-repair:— presence confirms the native binary upgrade landed and the repair ran; absence means the app is still running a pre-0.3.9liblibwallet.<ext>from the package cache. - Regression test:
TestEdDSAWalletCreatenow asserts the storedWallet.Pubkeybyte-matches the canonical compressed-Y Ed25519 form, and that stdlibed25519.Verify(storedPubkey, msg, sig)accepts the TSS signature. Either assert would have caught the original 0.3.9 encoding bug locally — same rejection Solana does on-chain.
0.3.10 #
- Solana ed25519 self-heal now actually runs (follow-up to 0.3.9):
the self-heal path in 0.3.9 had a wrong type assertion against the
signing context — it silently never triggered, so affected wallets
kept failing every send attempt. Fixed to use
wltintf.GetEnv(ctx). Additionally, added a pre-flight repair step in the Solana send path that decrypts one key share BEFORE building the transaction and patchesacct.Pubkeyin-memory, so the first send on an upgraded install succeeds instead of needing a failed-then-retry cycle. New exported helperwltwallet.EnsureEd25519Pubkeyis a no-op when the wallet is already correct.
0.3.9 #
- Solana ed25519 pubkey fix (breaking for existing Solana wallets):
ed25519 wallets created pre-0.3.9 stored the X coordinate of the
Edwards point (big-endian) as the "public key" instead of the
standard compressed encoding (Y little-endian with X's sign bit in
the MSB of byte 31). Consequences: the displayed Solana address was
wrong, balance queries hit a different address from the one the
TSS signs with, and every
sendTransactionfailed with "Transaction did not pass signature verification". Fixed at wallet creation viaToEd25519PubKey().Serialize(). Existing broken wallets self-heal on the first sign attempt (which fails once, then the repair propagates to the wallet + linked accounts and the retry succeeds). - On-chain tx history backfill (EVM):
client.transactions.list()now includes on-chain activity, not just txs this install built. Triggered in the background onAccount:setCurrent/Network:setCurrent/ env init. First triesmodchain_historyByAddress, falls back to Otterscan'sots_searchTransactionsAfter(erigon v3). Newclient.txHistoryUpdatesstream fires when new rows land. - Immediate balance refresh after sends: every
Transaction: signAndSend/Account:signAndSendTransaction/mpurse_sendRawTransaction/solana_sign_send_transactionnow nudges the background balance poller. Users see the new balance within ~1 s instead of up to 60 s.
0.3.8 #
- Background balance polling: new
client.balanceChangesstream yields aBalancesChangedEvent(full{network, account, assets}snapshot) every 60 s when the current account / network balances change. Lifecycle-aware — pauses underLifecycle:update('background')/paused, resumes with an immediate poll onforeground/resumed/active. - RPC timeouts (reliability fix): all
Network.DoRPC/DoRPCNamedcalls are now bounded by a 30 s default deadline. A misbehaving upstream (dead Ethereum public RPC, stale Solana endpoint, etc.) can no longer wedge a goroutine forever. The balance poller uses a tighter 15 s cap. Callers that need a specific deadline can use the existingDoRPCCtx/DoRPCNamedCtx. Fixes an iOS CI hang. - Network:testRPC extended: now accepts
type=evm/solana/bitcoinand probes the right health method per family. EVM is still the default;RpcTestResultgainedsolanaVersion/solanaCluster/bitcoinChain/bitcoinBlocksfields +isEvm/isSolana/isBitcoingetters. - Android 16 KB page alignment: CI now builds every Android
.so(both the AAR and the Dart FFI set) with-Wl,-z,max-page-size=16384and verifies it withreadelf. Required for Android 15+ devices with 16 KB page size (Pixel 8+).
0.3.7 #
- Wallet-identity plumbing: new
client.info.setWalletInfo(clientId:, name?, version?)registers the host wallet with libwallet. TheclientIdis sent as theSec-ClientIdHTTP header on everyCrypto/WalletSign:*call, which the WalletSign backend uses to pick branded SMS / email copy, apply per-app rate limits, and tag audit logs.name/versionare stored for future use (untrusted display strings, diagnostics). Called once at startup; backward- compatible (header not sent if not configured). - EIP-6963 UUID fix: webview injection docs corrected — generate a
fresh UUIDv4 per page load (spec requirement), do NOT persist
across launches.
rdnsis the stable identifier dApps key off, notuuid. - Drop Unix-socket transport fallback: FFI is the only supported
transport now. Removed
LibwalletClient.connect(socketPath)/.fromSocket(socket),JsonRpcConnection, request framing helper, and the socket-based testserver binary.Transportinterface stays (test mocks still work) but has one implementation.
0.3.6 #
- WalletConnect v2: full wallet-side implementation.
client.walletConnectcovers pair / sessions / approveSession / rejectSession / respond / respondError / emitEvent / disconnect. Two sugar streams (walletConnectProposals,walletConnectRequests) deliver typedWcSessionProposal/WcSessionRequestobjects. Sessions persist across restarts (SQL-backed); relay reconnects with backoff. Protocol pieces implemented: X25519 + HKDF + ChaCha20-Poly1305 envelopes, Ed25519 relay JWT auth, CAIP-10/-2 namespace handling,wc_sessionPropose/Settle/Request/Event/Delete. Seedoc/walletconnect_integration.md. - Transaction simulation + decoding: new
client.transactions.simulate. On EVM (erigon v3 backend), usesdebug_traceCallwith thecallTracerto walk the full call frame tree and return every ERC-20 Transfer + Approval and every value-carrying CALL at any depth asTransactionSimulation.effects. Second pass withprestateTracer(diff mode) returns per-address native-balance deltas asbalanceChanges. Top-level calldata decoded intodecodedMethod+decodedArgs(native_transfer/erc20_transfer/erc20_approve/unknown). Revert reasons decoded from standardError(string)ABI. Solana wrapssimulateTransaction(logs + unitsConsumed + err). Bitcoin parses viaoutscript.BtcTx(inputs + outputs + fee). - WebView injection: new
client.web3.injectionScript(...)generates a JS blob exposing libwallet aswindow.ethereum(EIP-1193 + EIP-6963),window.solana(Wallet Standard), andwindow.mpurse(Monacoin — github.com/tadajam/mpurse). Full wiring walkthrough indoc/webview_integration.md. - Bitcoin-family message signing (via mpurse):
mpurse_signMessagesigns with the TSS key over the standard "\x18Bitcoin Signed Message:\n" / "\x19Monacoin Signed Message:\n" / etc. prefix, returning the 65-byte compact signature (base64, Bitcoin Coresignmessageformat).mpurse_signRawTransactionparses the hex, matches inputs to the user's xpub viamodchain_lookupTxoBIP32, signs each input, and returns the signed hex.mpurse_sendRawTransactionis a direct passthrough tosendrawtransaction.mpurse_sendAssetstill errors (Counterparty server interaction is out of scope). - Monacoin network support:
bitcoinAddressrecognizesmonacoinchain id and emits the bech32mona1...address viaoutscript.Out.Address("monacoin"). - Typed pending-request flow:
PendingRequestis now sealed with one subtype per Web3 request kind (ConnectRequest, PersonalSignRequest, SignTypedDataRequest, SolanaSign* / Mpurse* / …, UnknownPendingRequest). Therequestevent now carries the full request object so consumers can render the prompt on first paint without a follow-upRequest/<id>fetch. Newclient.pendingRequestsstream yields fully-parsed requests ready for pattern matching. - Example package layout: new
example/libwallet_example.dartCLI sample covering init / wallet create with live progress / account / balance / pendingRequests subscription. Satisfies pub.dev's example requirement. - pubspec: description trimmed to 129 chars (was 212) to satisfy pub.dev's metadata scan.
0.3.5 #
- Direct account signing: new
Account.signMessage,signTransaction,signAndSendTransactionendpoints let wallet-host apps sign directly without routing through the Web3 pending-request/approve flow. Removes ~80 lines of async listener code from the typical Dart integration. - View accounts (read-only):
accounts.createView(type:, address:, xpub:)creates accounts with no backing wallet — suitable for watching a counterparty address or an HD tree (xpub, bitcoin-family). Balance and NFT queries work; signing is rejected. NewAccount.isViewOnlygetter. - Progress redesign: progress events are now a single 0..1
fractioninstead of{count, running}. ECDSA wallet creation now emits fine- grained ticks during Paillier / NTilde safe-prime generation (one per prime found out of 4, per key) — previously the UI was blind for 20+ seconds per key share. Requires tss-lib v2.2.4+. - Typed-API cleanup: removed
dynamicreturns and rawMap<String, dynamic>param inputs across the API surface. New typed models:SignedMessage,RemoteKeySession,RemoteKeyValidation,NftListing,WalletBackupEntry,RpcTestResult,UnsignedTransaction. Methods liketransactions.signAndSend(UnsignedTransaction),wallets.backup(),remoteKeys.validate()now return proper model instances. Raw param maps oncontacts.update,networks.update,tokens.updatereplaced with named parameters. - Validation: reject wallet
Curvevalues outside{secp256k1, ed25519}; reject account type/curve mismatches (e.g.solanaon secp256k1,ethereumon ed25519). Bitcoin accounts now derive on BIP-44 coin_type 0 (m/44/0/0/i) instead of Ethereum's coin_type 60.
0.3.4 #
- Email 2FA:
RemoteKey:new(andremoteKeys.create) now accept an email address in addition to phone numbers. Pass eithernumberoremail— the EllipX backend routes SMS vs email verification based on whether the value contains@.
0.3.3 #
- Bitcoin balance fix:
modchain_assetsreturnsbalanceas a decimal-formatted number ("0.00000000"), not int64. Decode viaoutscript.BtcAmountwhich handles both forms. Previously failed with:json: cannot unmarshal number 0.00000000 into Go struct field. - Solana NFT fix:
getAssetsByOwner(Helius DAS API) requires named JSON-RPC params, not positional. NewNetwork.DoRPCNamed()helper. Previously failed with:invalid type: map, expected a string. - Bitcoin UTXO decode:
modchain_lookupTxoBIP32response uses the sameBtcAmountserialization foramtandbalancefields. Type switched from int64 to outscript.BtcAmount across wlttx/bitcoin.go. - EVM NFT lookup hardening: type assertions on the
modchain_assetsresponse in wltnet/nft.go could panic if any field was missing or the wrong type. Replaced with comma-ok form. - iOS Dart Tests CI timeout bumped 45 → 60 minutes (Xcode build slow).
0.3.2 #
- iOS simulator support: build hook now detects
iphoneosvsiphonesimulatorSDK and downloads the correct binary. Previously the simulator would try to link the device-only binary and fail. - Release now includes
liblibwallet-iossimulator-arm64.a(Apple Silicon) andliblibwallet-iossimulator-x64.a(Intel Mac simulators) alongside the existingliblibwallet-ios-arm64.adevice binary.
0.3.1 #
- Bitcoin HD address support:
bitcoin-type accounts now derive multi-address HD trees under their account xpub. Balance queries callmodchain_assets(xpub)which scans0..lastI+20child keys (BIP-44 style gap limit) server-side. - New
AccountApi.xpub(id): returns the BIP-32 extended public key. - New
AccountApi.nextAddress(id): returns the next clean receive (or change) address based on on-chain scan. - New
AccountApi.allAddresses(id): lists all HD addresses across receive and change chains with activity markers. Account.Addressnow points tom/0/0(first receive address) instead ofm/0for Bitcoin-family accounts. BTC/LTC/DOGE/BCH supported.
0.3.0 #
- EIP-1559 transactions: Auto-selected when the chain supports it. New
maxFeePerGasandmaxPriorityFeePerGasfields onTransaction. - ERC-20 transfers: New
erc20_transfertransaction type. Pass a token XUID inAsset, recipient inTo, and amount — libwallet encodes thetransfer(address,uint256)call automatically. - ENS / SNS name resolution: New
client.names.resolve('vitalik.eth')API. Auto-detects.eth(Ethereum) and.sol(Solana) suffixes. - Solana devnet: Routes to the correct Helius devnet RPC endpoint
when using a Solana network with
chainId: "devnet". - Local dev:
hook/build.dartnow prefers a localtestserver/liblibwallet.<ext>over downloading from GitHub Releases. - Fix: macOS dylibs built with
-headerpad_max_install_namesso Dart can bundle them without relinking.
0.2.0 #
- Auto-download native binaries from GitHub Releases at build time
- CI testing on macOS, Android emulator, and iOS simulator
- 43 integration tests covering all API endpoints
- Full dartdoc on all model fields (~130 fields)
- Comprehensive README with usage examples
0.1.0 #
- Initial release
- FFI transport with
NativeCallable.listenerfor Go→Dart callbacks - 17 typed API classes covering all libwallet endpoints
- 15 model classes with full dartdoc
- Socket transport as legacy fallback
- Native asset hook for pub.dev binary distribution