libwallet 0.4.5 copy "libwallet: ^0.4.5" to clipboard
libwallet: ^0.4.5 copied to clipboard

unlisted

Multi-chain TSS cryptocurrency wallet client. Supports EVM, Bitcoin, Solana and Monacoin via direct FFI to the Go libwallet core.

0.4.5 #

  • Fixed: every Solana swap quote crashed with 404 Not Found from Jupiter Ultra. Jupiter's /ultra/v1/order endpoint accepts only GET with query parameters; we'd been POSTing JSON since the swap feature shipped. Switched to GET with url.Values; /execute stays 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 upstream errorMessage instead of the generic "Jupiter returned an empty order".

0.4.4 #

  • New: Token:listCurated endpoint + 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 — notably DRtvTCzfiKGhCVREmBbZdN9sB8PHeq9KdRZ3VmFhpump ("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 shape Asset.network returns — e.g. "evm.1", "solana.mainnet").
  • New Dart model CuratedToken with chainKey, address, symbol, name, decimals, type, logoUri, coingeckoId, cmcId, tags + isStablecoin / isWrapped convenience 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:list now reads name / symbol from the curated registry when the mint is well-known. USDC on Solana used to surface as Symbol="EPjFWd" / Name="EPjFWdd5..."; now surfaces as Symbol="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:list crashed with -32602 Invalid param: WrongSize on Solana for any wallet whose account is secp256k1 (EVM-flavoured). Account.UpdateAddressForNetwork was blindly base58-encoding the 33-byte compressed secp256k1 pubkey as if it were a 32-byte ed25519 point; Solana's getBalance then rejected the oversized pubkey. Non-ed25519 accounts on a Solana network now resolve to Address="N/A" (same convention EVM / Bitcoin use for ed25519 accounts) and the balance call is skipped.
  • Fixed: Swap:availability reported every Solana wallet as unsupported_chain. The live Solana network row uses ChainId="mainnet" (set by wltnet/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_sendTransaction approval crashed with "failed to get env" before signing. The transaction-sign approval handler was passing *env (whose embedded context is the bare psql sqlCtx) to Transaction.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_ecRecover returned RPC error -32601 ("method does not exist"). The previous default-relay path forwarded it to the chain's JSON-RPC node, but personal_ecRecover is 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_switchEthereumChain crashed loading the approval back out of psql with math/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 the any roundtrip the request loader does, then failed to unmarshal. Account now has custom MarshalJSON / UnmarshalJSON that 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 a hasActivity flag. Host UI uses this to auto-select which chains to migrate; per-candidate RPC errors land on row.error so 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 via ecckd.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/0 address-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:probeActivity then Wallet:promoteMnemonic to 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}) — accepts 0x-prefixed hex, bare hex, or Bitcoin-family WIF (auto-sniffed; WIF only for secp256k1). The imported wallet is stored as a 1-of-1 share with a new RawKey content type and is signable immediately — no TSS rounds, just direct crypto/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 KeyDescription encryption layer (Password, StoreKey, RemoteKey, Plain) — RawKey / Mnemonic are content types, the encryption-at-rest mechanism is orthogonal. So importing with Keys: [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's prepare_command now wraps the per-SDK static archives into libwallet.xcframework via xcodebuild -create-xcframework, and the spec switches from vendored_libraries to vendored_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_load is still required because the FFI entry points are dlsym'd, but it now reaches into the xcframework's source slice (which exists from pod install onward) — 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 / _v4 returned 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 new wltacct.SignEthereumDigest helper, which post-processes the TSS signer's DER output (parse → bruteforce recovery code → repack). Same fix applied to the host-direct Account:signMessage flow with Mode: "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:version now exposes the release tag. New version field alongside dateTag / gitTag, populated from the v* tag the binary was built from (empty on dev / non-tagged builds). The Dart wrapper got a typed client.info.versionInfo() returning a VersionInfo (version + gitTag + dateTag) for diagnostics; the long-broken client.info.version() now correctly returns the release-tag string instead of the toString'd map.

  • Runtime version-mismatch detection (with release-mode rejection). LibwalletClient.initialize asynchronously calls Info:version and compares it against the hardcoded libwalletPackageVersion constant. Result is exposed as client.ready (a Future<void>):

    • Debug/test Dart VM (dart.vm.product=false): mismatch logs an actionable warning via dart:developer and ready completes 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): ready rejects with a StateError carrying the same message. Apps should await client.ready after initialize and surface a fatal-error UI on failure — operating with mismatched wire shapes is what causes events to arrive as UnknownPendingRequest, 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.

  • Fixed: build-time -X ldflags targeted the wrong package. The Makefile and build.yml had been passing -X main.dateTag=... / -X main.gitTag=... for the entire history of the project, but those variables live in wltbase. The -X was a silent no-op — every release binary shipped with empty dateTag / gitTag. Corrected to use the fully qualified package path; release binaries built on or after this commit return populated values from Info: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 / explicit X.Y.Z) rewrites both pubspec.yaml and lib/src/version.dart — the two files that have to move in lockstep for the runtime mismatch check to work. CI runs the script's --check mode on every push so a release commit that only touches pubspec.yaml fails fast.

0.3.29 #

  • CI publish workflow: install BOTH Flutter and Dart. v0.3.28 switched from dart-lang/setup-dart to subosito/flutter-action so 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, so dart pub publish --force hung 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-dart rejects dart pub get with "libwallet requires the Flutter SDK, version solving failed". Switch the publish step to subosito/flutter-action so 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.platforms is 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.dart cache filename was version-agnostic (liblibwallet-android-arm64.so), so a previously cached binary kept serving stale code after dart pub upgrade — the Dart layer would decode events using the new shape while the loaded .so / .dylib still emitted the old shape. Most visible failure: post-0.3.24 signing events arriving with pre-unification type strings like sign_typed_data instead of message_sign, falling through to UnknownPendingRequest. Cached filenames now embed the package version (liblibwallet-android-arm64-v0.3.26.so); the next dart pub get after this upgrade re-downloads the matching binary.

  • iOS: ship as a Flutter FFI plugin (fixes external dlsym failure). The build hook's LinkMode = LookupInProcess() for the iOS .a archive 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 and dlsym failed at runtime with "symbol not found". This release adds flutter.plugin.platforms.ios.ffiPlugin: true to pubspec.yaml and ships ios/libwallet.podspec, which downloads the matching per-SDK static archives from the GitHub Release at pod install time and force-loads them into the host app target via per-SDK OTHER_LDFLAGS. Both device + simulator slices are pulled, combined into a single fat simulator archive via lipo, and the per-SDK xcconfig picks the correct one per build configuration. No code changes for app authors — flutter pub upgrade then a fresh pod install is 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, and WatchAssetRequest:
    • ConnectRequest now carries method (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"), and requestedPermissions (EIP-2255).
    • AddNetworkRequest flags phishing vectors: isKnown (chainId in the static chain registry), knownName (the canonical name — compare to network.name to detect impersonation), alreadyExists (no-op approval), and the nameMismatch convenience getter.
    • WatchAssetRequest gains typed EIP-747 accessors: assetType, address, symbol, decimals, image, tokenId, plus addressLooksInvalid and isAlreadyTracked heuristics. The old asset raw-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 single TransactionSignRequest. Every arbitrary-data signing flow (personal_sign, eth_signTypedData*, solana_signMessage, mpurse_signMessage) comes through a single MessageSignRequest. Branch on req.method (and req.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:simulate call to render an approval sheet. TransactionSignRequest carries:

    • decodedMethod / decodedArgs — recognised top-level operation (native_transfer, erc20_transfer, erc20_approve, …)
    • effects — every transfer / approve at any call depth
    • balanceChanges — signed native-balance deltas per address
    • warnings — stable-coded advisories (recipient_is_contract, erc20_approve_unlimited, net_loss_exceeds_amount, …)
    • willRevert / revertReason
    • feeAmount + 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 simulateBitcoin decoder.

  • MessageSignRequest decoded payload:

    • messageBytes (raw) + messageText (UTF-8 try)
    • structuredData / structuredPrimaryType / structuredDomain for EIP-712
    • Auto-detected SIWE / SIWS (isSiwe / isSiws) with parsed siweFields (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 from package: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 single ChainSwitchRequest event. Two shapes distinguished by which fields are populated:

    • Pre-specified target (req.targetNetwork != null): dApp named a specific chain. Render a confirm sheet. When req.isNewNetwork is true, the chain isn't in the wallet yet and approval implies Add + Switch. Approve with accounts: [accountId] only — network is taken from the request.
    • Picker (req.targetNetwork == null, req.candidateNetworks populated): dApp triggered a cross-family action. Render a network + account picker. Approve with both network: pickedId and accounts: [pickedId].

    ChangeNetworkRequest and AddAndSwitchNetworkRequest are removed. Hosts pattern-matching on those need to switch to ChainSwitchRequest and branch on req.targetNetwork != null. See dart/doc/webview_integration.md for the new example.

    AddNetworkRequest is unchanged — pure add (no switch) is a distinct intent from wallet_addEthereumChain.

    On approval libwallet still does Save (when new) + SetCurrent + implicit connect server-side. Hosts do not need a separate client.networks.setCurrent(...) call.

0.3.22 #

  • EIP-2255 wire shape fix. wallet_requestPermissions and wallet_getPermissions now return one permission entry per capability (the EIP-2255 shape) with all authorised addresses in a single restrictReturnedAccounts caveat — instead of one entry per account with a missing parentCapability field. dApps that read perm.parentCapability (etherscan.io, MetaMask test-dapp, most wallet UI kits) now get "eth_accounts" instead of undefined.
  • eth_accounts is 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 [], not null.
  • 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 a chain_switch approval that lets the user pick BOTH the target network AND an account in one prompt. On approve, the wallet switches network and saves a ConnectedSite for (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:request recognises wallet_revokePermissions (EIP-2255 revoke). Was unhandled in pre-0.3.21 builds and fell through to the chain RPC relay producing failed to decode response … invalid character 'm'. Already fixed in 0.3.21; the docs now call this out explicitly.
  • wallet_switchEthereumChain accepts both spec + bare-string param shapes. Etherscan and other EIP-3326-compliant dApps no longer 500 with failed to convert map[string]interface {} to string. When the target chain isn't registered yet but is in libwallet's static metadata, emits a combined add_and_switch_network approval 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 calls SetCurrent itself before returning to the dApp. Hosts no longer need to call client.networks.setCurrent(...) after requests.approve(...) — the workaround is redundant on 0.3.22+.
  • NFTs on non-mainnet EVM chains return [] instead of 500. Nft:list on 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, and WatchAssetRequest with 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 as invalid character 'm' looking for beginning of value on etherscan.io and any other dApp that calls it. Now revoking eth_accounts drops every ConnectedSite row for the requesting host (same effect as solana_disconnect on 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_switchEthereumChain params. 0.3.19 decoded the first param as a string, which failed with failed to convert map[string]interface {} to string on 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_switchEthereumChain for a chain the wallet hasn't seen yet but which libwallet recognizes from its static chain metadata, emits a single add_and_switch_network approval request (new AddAndSwitchNetworkRequest subtype). 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 through wallet_addEthereumChain. Unknown-to-libwallet chains still return 4902.
  • Breaking: rawRequest / rawRequestWithProgress removed from LibwalletClient. 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 once OneInchAPIKey is compiled in; missing_api_key until 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 / chainId getters split network for 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:list now supports cursor pagination + filters that the docs always promised. Up to 0.3.16 the handler was hardcoded to 50 rows and the From / Network query params were silently ignored on this path (only DELETE Transaction honoured them). New before (RFC3339Nano cursor on Created) and limit (default 50, capped 200) params drive an infinite-scroll pattern — the response stays a flat list, clients derive the next cursor from last.created. Dart transactions.list() gains the new parameters with an example in the docstring.

0.3.16 #

  • Fix: Transaction:signAndSend now backfills Fee server-side before saving. The new typed UnsignedTransaction deliberately omits fee (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 with null Fee in tx history. Same formulas Validate already uses (gas × gasPrice on EVM, 5000 + ceil(cuLimit*cuPrice/1e6) on Solana). No client change needed.
  • Transaction:maxSendable no longer takes network — it's derived from the asset key's <type>.<chainId>. prefix (the same shape Asset:list returns). Empty / bare NATIVE falls 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 a SwapQuote with expected output, min output after slippage, route breakdown, and a 90 s quoteId; execute(quoteId, keys) signs and broadcasts, returning a SwapResult with 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 via eth_call and populates SwapQuote.requiresApproval, approvalSpender, currentAllowance, and neededAllowance. When requiresApproval is true, call the new swap.buildApproval() — it returns a rich ApprovalPreview (token, spender label, amount, isUnlimited flag, current allowance, network fee, plus the validated Transaction to 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. Pass approvalAmount: 'max' to opt into the classic unlimited approve, or a decimal string for a custom cap — surface the trade-off via preview.isUnlimited in the UI.
  • Richer Quote payload for UI approval sheets: SwapQuote now carries providerLabel (human-friendly name), referralFee (the 50 bps platform fee as an absolute amount in the input token's units), and networkFee (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.OneInchAPIKey to enable EVM swaps.
    • No token resolver yet: callers pass SwapTokenRef with address + decimals fully resolved (the data is already available from Asset:list).

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 → get insufficient 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:validate now 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 advisory Warnings 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 / priorityLevel fields on UnsignedTransaction. Set priorityLevel: "low" | "medium" | "high" to have validate pick a percentile of recent on-chain prioritization fees; or pin computeUnitPrice (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.01 in their wallet instead of 0.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). maxSendable still 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 underlying log.Printf writes to the Go runtime's os.Stderr — which Flutter+iOS swallows entirely, and Flutter+Android filters out of flutter logs by default. End result: testers saw no output even with logLevel: "debug". Fixed by routing every wltlog emission through the apirouter broadcast channel (same pipe Web3 requests / balance changes already use).

  • New LogEvent + client.logs stream: subscribe once at startup and forward to developer.log / print so 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',
    );
    

    LogEvent is also emitted on the general client.events stream 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 by setWalletInfo: new LogLevel field on WalletInfo. 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 through wltlog.{Debugf,Infof,Warnf, Errorf}; lines are prefixed [debug] / [info] / [warn] / [error] so testers can grep by level regardless of the host's logger. getWalletInfo also returns effectiveLogLevel so 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, WalletById failed, wrong curve, no keys, decrypt failed, empty want, already-correct), visible at debug. The actual repair (Pubkey/Address flip) logs at info. Combined with the always-on want vs acct.Pubkey vs wallet.Pubkey dump, a tester flipping logLevel: "debug" gets everything needed to pin down why a Solana send fails.
  • FindAccount now runs check() 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 calling acct.check(e) after the by-Address fetch.
  • Pre-broadcast Ed25519 verify: Transaction:signAndSend on Solana now runs ed25519.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 helper wltacct.EnsureEd25519PubkeyOnAccount is now called from Account:signMessage (solana mode), Account:signTransaction, Account:signAndSendTransaction, and Web3 solana_sign_message / solana_sign_transaction / solana_sign_send_transaction. The helper also saves the repaired Account row synchronously so the dApp's next window.solana.publicKey read returns the corrected address.
  • Per-RPC timing logs (at debug): every Network.DoRPC / DoRPCNamed emits rpc: chain=X method=Y OK in Nms (B bytes) or FAIL in Nms: err. Quiet at info; noisy but invaluable when reproducing a bug.
  • Per-key-decrypt timing (wallet-sign logs, at debug): entry line with wallet id/threshold/keys/msg_len; per-key "decrypted in N ms (type=Password|StoreKey|…)". Pubkey mismatch detected during sign logs at warn.

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:signAndSendTransaction and the Web3 solana_sign_{message,transaction,send_transaction} approvers bypassed the repair. Extracted the fix into wltacct.EnsureEd25519PubkeyOnAccount and wired it into every Solana-capable sign path, including Account:signMessage. The helper also saves the repaired Account row synchronously (not just via the async wallet:pubkey_repaired handler) so the next FindAccount / window.solana.publicKey read returns the corrected address in the same request lifecycle.
  • Visibility log: self-heal now emits ed25519-repair: account <id> (wallet <id>) pubkey/address repaired: ... to log.Printf when it fires. If affected users report that sends still fail after upgrading, grep logs for ed25519-repair: — presence confirms the native binary upgrade landed and the repair ran; absence means the app is still running a pre-0.3.9 liblibwallet.<ext> from the package cache.
  • Regression test: TestEdDSAWalletCreate now asserts the stored Wallet.Pubkey byte-matches the canonical compressed-Y Ed25519 form, and that stdlib ed25519.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 patches acct.Pubkey in-memory, so the first send on an upgraded install succeeds instead of needing a failed-then-retry cycle. New exported helper wltwallet.EnsureEd25519Pubkey is 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 sendTransaction failed with "Transaction did not pass signature verification". Fixed at wallet creation via ToEd25519PubKey().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 on Account:setCurrent / Network:setCurrent / env init. First tries modchain_historyByAddress, falls back to Otterscan's ots_searchTransactionsAfter (erigon v3). New client.txHistoryUpdates stream fires when new rows land.
  • Immediate balance refresh after sends: every Transaction: signAndSend / Account:signAndSendTransaction / mpurse_sendRawTransaction / solana_sign_send_transaction now 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.balanceChanges stream yields a BalancesChangedEvent (full {network, account, assets} snapshot) every 60 s when the current account / network balances change. Lifecycle-aware — pauses under Lifecycle:update('background') / paused, resumes with an immediate poll on foreground / resumed / active.
  • RPC timeouts (reliability fix): all Network.DoRPC / DoRPCNamed calls 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 existing DoRPCCtx / DoRPCNamedCtx. Fixes an iOS CI hang.
  • Network:testRPC extended: now accepts type = evm / solana / bitcoin and probes the right health method per family. EVM is still the default; RpcTestResult gained solanaVersion / solanaCluster / bitcoinChain / bitcoinBlocks fields + isEvm / isSolana / isBitcoin getters.
  • 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=16384 and verifies it with readelf. 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. The clientId is sent as the Sec-ClientId HTTP header on every Crypto/WalletSign:* call, which the WalletSign backend uses to pick branded SMS / email copy, apply per-app rate limits, and tag audit logs. name / version are 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. rdns is the stable identifier dApps key off, not uuid.
  • 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. Transport interface stays (test mocks still work) but has one implementation.

0.3.6 #

  • WalletConnect v2: full wallet-side implementation. client.walletConnect covers pair / sessions / approveSession / rejectSession / respond / respondError / emitEvent / disconnect. Two sugar streams (walletConnectProposals, walletConnectRequests) deliver typed WcSessionProposal / WcSessionRequest objects. 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. See doc/walletconnect_integration.md.
  • Transaction simulation + decoding: new client.transactions.simulate. On EVM (erigon v3 backend), uses debug_traceCall with the callTracer to walk the full call frame tree and return every ERC-20 Transfer + Approval and every value-carrying CALL at any depth as TransactionSimulation.effects. Second pass with prestateTracer (diff mode) returns per-address native-balance deltas as balanceChanges. Top-level calldata decoded into decodedMethod + decodedArgs (native_transfer / erc20_transfer / erc20_approve / unknown). Revert reasons decoded from standard Error(string) ABI. Solana wraps simulateTransaction (logs + unitsConsumed + err). Bitcoin parses via outscript.BtcTx (inputs + outputs + fee).
  • WebView injection: new client.web3.injectionScript(...) generates a JS blob exposing libwallet as window.ethereum (EIP-1193 + EIP-6963), window.solana (Wallet Standard), and window.mpurse (Monacoin — github.com/tadajam/mpurse). Full wiring walkthrough in doc/webview_integration.md.
  • Bitcoin-family message signing (via mpurse): mpurse_signMessage signs 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 Core signmessage format). mpurse_signRawTransaction parses the hex, matches inputs to the user's xpub via modchain_lookupTxoBIP32, signs each input, and returns the signed hex. mpurse_sendRawTransaction is a direct passthrough to sendrawtransaction. mpurse_sendAsset still errors (Counterparty server interaction is out of scope).
  • Monacoin network support: bitcoinAddress recognizes monacoin chain id and emits the bech32 mona1... address via outscript.Out.Address("monacoin").
  • Typed pending-request flow: PendingRequest is now sealed with one subtype per Web3 request kind (ConnectRequest, PersonalSignRequest, SignTypedDataRequest, SolanaSign* / Mpurse* / …, UnknownPendingRequest). The request event now carries the full request object so consumers can render the prompt on first paint without a follow-up Request/<id> fetch. New client.pendingRequests stream yields fully-parsed requests ready for pattern matching.
  • Example package layout: new example/libwallet_example.dart CLI 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, signAndSendTransaction endpoints 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. New Account.isViewOnly getter.
  • Progress redesign: progress events are now a single 0..1 fraction instead 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 dynamic returns and raw Map<String, dynamic> param inputs across the API surface. New typed models: SignedMessage, RemoteKeySession, RemoteKeyValidation, NftListing, WalletBackupEntry, RpcTestResult, UnsignedTransaction. Methods like transactions.signAndSend(UnsignedTransaction), wallets.backup(), remoteKeys.validate() now return proper model instances. Raw param maps on contacts.update, networks.update, tokens.update replaced with named parameters.
  • Validation: reject wallet Curve values outside {secp256k1, ed25519}; reject account type/curve mismatches (e.g. solana on secp256k1, ethereum on 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 (and remoteKeys.create) now accept an email address in addition to phone numbers. Pass either number or email — the EllipX backend routes SMS vs email verification based on whether the value contains @.

0.3.3 #

  • Bitcoin balance fix: modchain_assets returns balance as a decimal-formatted number ("0.00000000"), not int64. Decode via outscript.BtcAmount which 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. New Network.DoRPCNamed() helper. Previously failed with: invalid type: map, expected a string.
  • Bitcoin UTXO decode: modchain_lookupTxoBIP32 response uses the same BtcAmount serialization for amt and balance fields. Type switched from int64 to outscript.BtcAmount across wlttx/bitcoin.go.
  • EVM NFT lookup hardening: type assertions on the modchain_assets response 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 iphoneos vs iphonesimulator SDK 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) and liblibwallet-iossimulator-x64.a (Intel Mac simulators) alongside the existing liblibwallet-ios-arm64.a device binary.

0.3.1 #

  • Bitcoin HD address support: bitcoin-type accounts now derive multi-address HD trees under their account xpub. Balance queries call modchain_assets(xpub) which scans 0..lastI+20 child 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.Address now points to m/0/0 (first receive address) instead of m/0 for Bitcoin-family accounts. BTC/LTC/DOGE/BCH supported.

0.3.0 #

  • EIP-1559 transactions: Auto-selected when the chain supports it. New maxFeePerGas and maxPriorityFeePerGas fields on Transaction.
  • ERC-20 transfers: New erc20_transfer transaction type. Pass a token XUID in Asset, recipient in To, and amount — libwallet encodes the transfer(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.dart now prefers a local testserver/liblibwallet.<ext> over downloading from GitHub Releases.
  • Fix: macOS dylibs built with -headerpad_max_install_names so 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.listener for 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
0
likes
0
points
2.08k
downloads

Publisher

verified publisheratonline.com

Weekly Downloads

Multi-chain TSS cryptocurrency wallet client. Supports EVM, Bitcoin, Solana and Monacoin via direct FFI to the Go libwallet core.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

code_assets, ffi, hooks

More

Packages that depend on libwallet

Packages that implement libwallet