Reimplement Pirate Chain plugin over react-native-pirate-wallet - #1055
Reimplement Pirate Chain plugin over react-native-pirate-wallet#1055j0ntz wants to merge 8 commits into
Conversation
9df27fb to
c26cdcb
Compare
c26cdcb to
c2a610d
Compare
c2a610d to
7049511
Compare
|
Simulator-tested update (force-pushed as part of the single commit):
Upstream blocker found while testing: block sync still cannot progress through this binding. |
7049511 to
0481ded
Compare
|
End-to-end sync + send verified on the iOS simulator (with a locally rebuilt pirate core carrying the one-line persistent-runtime fix):
Two more findings, both addressed in the latest force-push:
|
peachbits
left a comment
There was a problem hiding this comment.
Two non-blocking suggestions on the new react-native-pirate-wallet integration, both at the native-bridge boundary. The plugin reimplementation reads cleanly otherwise.
a0927f1 to
beaae5d
Compare
The piratechain team's orchard upgrade replaces the zcash-cloned react-native-piratechain SDK with a wallet-registry based SDK (react-native-pirate-wallet) whose lightwalletd endpoint, checkpoints, and spending keys live inside the native core. Rebuild the engine, tools, and yaob io bridge on that API: wallets are restored into the SDK registry under the Edge walletId alias, sync progress comes from the SDK's polling synchronizer, transactions map from signed fee-inclusive amounts, and sends go through the registry wallet instead of passing the mnemonic per spend.
The Pirate Unified Light Wallet v1.1.5 release ships the merged upstream fixes and finalizes the wire format the plugin targets. Serialize amounts as decimal strings end to end so balances and sends above 2^53-1 arrrtoshi keep full precision, and drive sends through the SDK's send() now that it keeps the opaque build/sign/broadcast payloads verbatim. Replace the removed global app-passphrase flow with per-wallet configureAccountStorage so each local wallet lives in its own registry namespace unlocked by a passphrase derived from that wallet's seed instead of a shared hardcoded one. Type native bridge errors as unknown to match what the RN bridge actually delivers.
2cbed88 to
6d4e32d
Compare
The SDK's configureAccountStorage selects the wallet registry globally: one namespace is active at a time, and switching cancels any running sync and clears the registry and block caches. Per-wallet namespaces therefore left only the last-selected ARRR wallet syncing, with the others' pollers reading the wrong namespace, and gave up the shared block cache. Configure a single device-scoped registry once, key every wallet by alias inside it, and let each wallet's synchronizer run concurrently. Its passphrase is a random per-device secret minted from io.random and kept in the plugin's local storage, replacing the seed-derived HMAC, which cannot key a registry that holds many wallets. Wallet-free reads use the device registry, so the throwaway probe namespace is gone. Also expose the synchronizer status from the bridge and read it once after the engine subscribes, so a SYNCED that fires before subscription cannot strand the engine at STOPPED.
Rewrite the storage section and decisions 1 and 3 to the shipped model, and record the phase-4 divergence from the per-wallet namespaces phase 2 built.
6d4e32d to
76bb601
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 59e95bb. Configure here.
The SDK bakes in a default lightwalletd and never consults the plugin's networkInfo, so every wallet scanned against that default. When that node stops serving blocks the failure is silent and total: test_node still succeeds and the chain tip still resolves, so the app shows 'Sync in Progress, 0% Complete' forever with no error, while the scan sits in the Headers stage at zero blocks per second. Pass the configured node down to makeSynchronizer and apply it with set_lightd_endpoint before the synchronizer starts. The configured port moves from 443 to the node's plain gRPC port, which is what the SDK speaks; test_node fails against https on 443 and succeeds on 9067.
59e95bb to
a58a7ed
Compare









CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
none
Description
Asana: https://app.asana.com/0/1215088146871429/1214721783909451
The piratechain team released a new SDK family for their orchard upgrade (Pirate-Unified-Light-Wallet), including
react-native-pirate-wallet, which replaces thereact-native-piratechainwrapper Edge wrote. The new SDK is not a drop-in replacement, so this PR reimplements the piratechain plugin on top of it.Architecture changes driven by the new SDK:
sync_status/get_balance/list_transactionsnatively, so the io bridge forwards its snapshots as the existingupdate/statusChanged/erroryaob events. Block progress maps fromlocalHeight/targetHeight.list_transactionsreplaces block-range queries (theblockRangewindowing inotherDatais gone). Amounts are signed and fee-inclusive for sends, which matches Edge'snativeAmountsemantics directly. Recipient addresses are not exposed by the SDK, so sends are detected by sign.broadcastTxsends outputs (plus the explicit default fee) through the SDK instead of passing the mnemonic per spend.current_receive_address(still saplingzs1…— orchard is not active on mainnet).derivePublicKeyexports the sapling viewing key after registering the wallet. New-wallet birthday heights are probed via a throwawaycreate_wallet, whose null-birthday path resolves the live chain tip with a checkpoint fallback.rpcNodeinnetworkInfois no longer consumed (kept for info-server payload compatibility).The plugin compiles against local typings (
rnPirateWallet.d.ts) becausereact-native-pirate-walletis not published to npm; it only exists as a 473 MB zip asset on the PirateNetwork GitHub release (prebuilt xcframework + jniLibs). GUI integration is blocked on a packaging decision — npm/yarn cannot install zips, so the package needs to be hosted as a tgz (e.g. thezano-utils-jsEdgeApp-release pattern) or published beforeedge-react-guican swapreact-native-piratechainfor it. Mnemonic/seed derivation is BIP-39-compatible with the old SDK (same sapling addresses), so existing wallets restore in place; first launch re-restores into the registry from the stored mnemonic + birthday and rescans from the wallet's birthday height.Note
High Risk
Complete rewrite of ARRR wallet storage, sync, and send paths, including a new device-scoped encrypted registry passphrase and plaintext gRPC lightwalletd traffic.
Overview
Replaces the crashing
react-native-piratechainmodule with the unifiedreact-native-pirate-walletSDK so ARRR wallets can sync and spend without the old SQLite double-open crash.Storage and sync move to one device-scoped encrypted registry (
edge-pirate-device) keyed by a random per-device passphrase in local storage. Every ARRR wallet lives in that registry and runs its own synchronizer concurrently over a shared block cache. Amounts are string-encoded end to end for full precision, and sends go through the SDKsend()API.Also fixes silent sync stalls by pointing each wallet at the plugin's own lightwalletd node (
http://lightd1.pirate.black:9067) viaset_lightd_endpoint, since the SDK ignores plugin config and its default node had stopped serving blocks.Reviewed by Cursor Bugbot for commit a58a7ed. Bugbot is set up for automated code reviews on this repo. Configure here.
Upstream SDK fixes
Two defects in the upstream SDK had to be fixed for this plugin to sync and send. They are now filed upstream as code (not just a prose recipe): PirateNetwork/Pirate-Unified-Light-Wallet#19.
WalletService::execute_blockingdropped its tokio runtime as soon as each call returned, aborting the background sync engine spawned bystart_sync. A host that drives the service throughexecute_blocking(the RN binding) never advanced past the birthday height. PR Properly call onTransactionsChanged on new ETH txs #19 uses a process-wide persistent runtime.pendingpayload frombuild_txbefore feeding it back intosign_tx, which rejected it (missing field total_amount). This plugin works around it here via rawbuild_tx/sign_tx/broadcast_txinvoke; PR Properly call onTransactionsChanged on new ETH txs #19 fixes it at the source so the wrapper'ssend()works too.Until those land in an upstream release, the vendored
react-native-pirate-walletused by EdgeApp/edge-react-gui#6021 carries the patched runtime slice.v1.1.5 reconciliation (2026-07-28)
Reconciled to the officially released Pirate Unified Light Wallet v1.1.5 (RN binding 0.2.0), which merges the upstream fixes (PR #19) and finalizes the wire format:
AmountString), so balances and sends above 2^53-1 arrrtoshi keep full precision. DroppedsafeParseInton the send path.send()now that it preserves the opaque build/sign/broadcast payloads and normalizes amounts, replacing the manual raw-invoke camelize workaround.configureAccountStorage, replacing the removed global app-passphrase flow. This phase used one namespace per wallet; the device-scoped model that superseded it is described below.onErrortypedunknown, and theorchardtoironwoodrename in the SDK type surface.react-native-pirate-walletpeerDependency bumped to^0.2.0.Design doc: src/docs/piratechain-sdk-v115-reconcile.md
On-device verification (iOS sim, edge-funds): the app runs with piratechain enabled (no corePlugins disable), ARRR wallets sync the shielded chain with no crash (the old module's SQLite double-open crash is retired), and a real ARRR send broadcast to the transaction-success scene, txid
34ba68b0fee76668790ef7dae32f374c7f378da589022a1034f1112e234e49cd.Device-scoped registry (2026-08-03)
Rebased onto
master(4.82.1 to 4.87.0, including the yarn to npm migration) and restructured storage after the Pirate Chain team confirmed the intended model.configure_wallet_storageis global: one registry namespace is active at a time, and switching it cancels any running sync and clears the registry and block caches. The per-wallet namespaces above therefore could not support more than one ARRR wallet, which reproduced on device: only the last-selected wallet synced and stayed spendable, while the others' pollers read the wrong namespace. The intended model is one namespace per device holding many wallets, with concurrency coming from wallet-scoped synchronizers.configureAccountStorageruns once, atDEVICE_ACCOUNT_ID = 'edge-pirate-device', memoized so concurrent wallet starts share a single setup. Wallets are keyed by alias inside it. The throwaway probe namespace is gone; wallet-free reads use the device registry.base16(io.random(32)), minted on first use and persisted topiratechain/devicePassphrase.jsonon the core disklet (newpiratechainDeviceStorage.ts, replacingpiratechainCrypto.ts). A seed-derived HMAC cannot key a registry that holds many wallets without arbitrarily privileging one wallet's key material. Existence is checked before reading, so a transient read failure cannot silently mint a new secret and orphan the registry.PiratechainTools.ensureDevicePassphrase()hands the bridge the secret once, awaited by every path that reaches storage. It deliberately does not run inmakeCurrencyTools, so constructing tools never depends on the native module being linked.SYNCEDbackstop (the deferred Bugbot finding): the bridge exposesgetStatus(), and the engine reads it once after subscribing, adopting it only when no event has arrived. AstatusChangedthat fired betweenstart()and subscription can no longer strand the engine atSTOPPED.getLatestNetworkHeightnow readsgetSyncStatus(walletId).targetHeightfrom a wallet that is already registered, and keeps the old create-and-delete probe only for an empty registry. Found on device: with the registry shared, the probe'screate_walletmutates it underneath running synchronizers and the native service panics insidepirate_wallet_service_invoke_json, aborting the app (panic_cannot_unwind->SIGABRT). Creating a new ARRR wallet reproduced it twice and succeeds after the fix.Old per-wallet registries are abandoned rather than migrated: wallets re-restore from their seeds on first run.
Design doc updated: src/docs/piratechain-sdk-v115-reconcile.md (decision 1 and decision 3 rewritten, phase 4 recorded).