Skip to content

Reimplement Pirate Chain plugin over react-native-pirate-wallet - #1055

Open
j0ntz wants to merge 10 commits into
masterfrom
agent/1214721783909451
Open

Reimplement Pirate Chain plugin over react-native-pirate-wallet#1055
j0ntz wants to merge 10 commits into
masterfrom
agent/1214721783909451

Conversation

@j0ntz

@j0ntz j0ntz commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Technical Design Document

piratechain-sdk-v115-reconcile.md, committed on this branch. It holds the design, every decision with its rejected alternatives, the phase history, and the test evidence. Read it instead of a long description here.

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

Asana: https://app.asana.com/0/1215088146871429/1214721783909451

Reimplements the Pirate Chain plugin over react-native-pirate-wallet (unified wallet v1.1.6, npm 0.3.0), the Pirate team's replacement for the react-native-piratechain module Edge wrote. In the old module the Swift and Rust layers each opened the wallet's SQLite database and the app crashed when they overlapped, which is why unrelated work has to set piratechain: false in the GUI's corePlugins.ts. The new SDK keeps all database access in Rust, and this branch retires that workaround.

The plugin's public shape and the engine's transaction mapping are unchanged. What is new:

  • One device-scoped SDK wallet registry holds every ARRR wallet, keyed by wallet-id alias and unlocked by a random per-device passphrase kept in the plugin's local storage.
  • Sync comes from the SDK's polling synchronizer and history from list_transactions. Amounts cross the wire as decimal strings, and sends go through the SDK's send().
  • Each wallet is pointed at Edge's own lightwalletd node with set_lightd_endpoint. Without it a wallet scans against the SDK's baked-in default, which now refuses connections outright, and the app sits at 0% sync with no error anywhere.
  • getLatestNetworkHeight takes the chain tip from a registered wallet or from test_node, falling back to the create-and-delete probe only when the registry is empty. Running that probe while a synchronizer is live panics the native service and aborts the app.

Paired with EdgeApp/edge-react-gui#6021, which swaps the GUI dependency and has to land after this one publishes.

Verified on the iOS simulator: a funded ARRR send reached the transaction-success scene (txid 8d2e25d0e624cf714057b65311347a8a5d21cafbbae8f579e714fb979e94a579, 2.083 seconds from build to broadcast), both wallets sync, a resync ran at about 47,700 blocks/sec, and the mocha suite is 490 passing. Section 7 of the doc has the full numbers and what stayed uncovered.


Note

High Risk
This is a full native SDK and wallet-storage swap for ARRR—sync, spend, registry passphrase handling, and lightwalletd routing all change—and incorrect endpoint or registry behavior can strand wallets at 0% sync or block spends; Ironwood readiness depends on pinning v1.1.6.

Overview
Replaces react-native-piratechain with react-native-pirate-wallet (unified wallet v1.1.6, peer ^0.3.0) and rewrites the native IO bridge and engine around the SDK’s encrypted wallet registry and polling synchronizer.

Device-scoped registry: One configureAccountStorage namespace (edge-pirate-device) holds every ARRR wallet keyed by wallet-id alias; a random 32-byte passphrase is minted on the core side (piratechainDeviceStorage.ts) and passed to the bridge via setDevicePassphrase. Multiple wallets can sync concurrently on a shared block cache without namespace switching.

Sync and history: Transaction history no longer uses block-range paging in otherData; the engine processes the SDK’s full list_transactions list with a per-txid height cache. Balances and txs use string amounts end-to-end; sends go through walletSdk.send() instead of raw build/sign/broadcast invoke. A one-time getStatus() backstop avoids staying stuck at STOPPED if SYNCED fires before subscriptions attach.

lightwalletd: Each wallet gets set_lightd_endpoint with the plugin’s lightwalletdUrl (http://lightd1.pirate.black:9067 plain gRPC), because the SDK ignores plugin networkInfo and its baked-in default node is unreachable or non-serving.

Adds local rnPirateWallet.d.ts typings and a long design doc; package-lock drops the old piratechain dev dependency.

Reviewed by Cursor Bugbot for commit 25992e1. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread src/piratechain/PiratechainEngine.ts Outdated
Comment thread src/piratechain/piratechainIo.ts Outdated
Comment thread src/piratechain/piratechainIo.ts
@j0ntz
j0ntz force-pushed the agent/1214721783909451 branch from 9df27fb to c26cdcb Compare June 5, 2026 20:16
Comment thread src/piratechain/PiratechainEngine.ts
Comment thread src/piratechain/piratechainIo.ts
@j0ntz

j0ntz commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Simulator-tested update (force-pushed as part of the single commit):

  • App passphrase unlock: the SDK's SQLCipher store rejects every wallet call with Security error: App is locked until set_app_passphrase/unlock_app runs. The io bridge now unlocks (or provisions) the passphrase store before any wallet operation. Verified on the iOS sim: registry restore, viewing-key derivation, and zs1… receive address all work.
  • Direct tunnel: the SDK transports through Tor by default, which never bootstraps inside Edge — sync sat at stage: Headers, target_height: 0 forever. The bridge now issues set_tunnel {mode: Direct} after unlock, matching every other Edge plugin's connectivity. Verified the sync config flips to transport: Direct against http://64.23.167.130:9067 (server healthy; tip 3,990,043 via grpcurl).

Upstream blocker found while testing: block sync still cannot progress through this binding. pirate-ffi-native builds a throwaway current-thread tokio runtime per invoke, and sync_control::start_sync spawns the sync engine onto it — the task is destroyed when the call returns. The Flutter app works only because flutter_rust_bridge keeps a persistent runtime; the RN/iOS/Android SDK bindings all share this defect, and the bounded start_background_sync API is not exposed through the JSON dispatcher. Needs a Pirate-team fix (persistent global runtime in pirate-ffi-native, or expose start_background_sync via invoke). Until then balances/sends can't be exercised end-to-end.

@j0ntz
j0ntz force-pushed the agent/1214721783909451 branch from 7049511 to 0481ded Compare June 5, 2026 23:23
@j0ntz

j0ntz commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

End-to-end sync + send verified on the iOS simulator (with a locally rebuilt pirate core carrying the one-line persistent-runtime fix):

  • Funded wallet (254.547 ARRR) restored from its Edge mnemonic + birthday and fully scanned ~940k blocks to chain tip; SDK balance matched Edge's cached pre-upgrade balance to the arrrtoshi.
  • Sent 3.261 ARRR wallet-to-wallet in the same account: txid b44367658e7c92cd916eccbdee3dc8f6861a48985683037040a8ead2035c9881, broadcast accepted by http://64.23.167.130:9067.

Two more findings, both addressed in the latest force-push:

  1. Wrapper send bug (upstream): sdk.send()/signTransaction() camelize the build_tx result and feed it back into sign_tx, which rejects it (Invalid request JSON: missing field 'total_amount'). The bridge now drives build_tx → sign_tx → broadcast_tx over the raw invoke channel so the pending/signed payloads round-trip untouched.
  2. Runtime defect confirmed + fix validated: rebuilding libpirate_ffi_native with a persistent global tokio runtime in WalletService::execute_blocking (instead of a per-call current-thread runtime) makes foreground sync work through the json-invoke bindings. That's the patch to upstream to the Pirate team — without it the stock release binaries cannot sync outside their Flutter app.

Comment thread src/piratechain/PiratechainEngine.ts

@peachbits peachbits left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two non-blocking suggestions on the new react-native-pirate-wallet integration, both at the native-bridge boundary. The plugin reimplementation reads cleanly otherwise.

Comment thread src/piratechain/rnPirateWallet.d.ts Outdated
Comment thread src/piratechain/PiratechainEngine.ts
Comment thread src/piratechain/piratechainIo.ts Outdated
Comment thread src/piratechain/piratechainIo.ts Outdated
Comment thread src/piratechain/PiratechainEngine.ts Outdated
Comment thread src/piratechain/PiratechainEngine.ts
Comment thread src/piratechain/PiratechainEngine.ts
@j0ntz
j0ntz force-pushed the agent/1214721783909451 branch 2 times, most recently from a0927f1 to beaae5d Compare July 29, 2026 01:06
Comment thread src/piratechain/piratechainIo.ts Outdated
@j0ntz

j0ntz commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

📸 Test evidence: e2e ARRR send (iOS sim)

arrr my pirate2 synced

arrr my pirate2 synced

arrr send dest zaddr

arrr send dest zaddr

arrr send confirm

arrr send confirm

arrr send success

arrr send success

arrr send txid

arrr send txid

Captured by the agent's in-app test run (build-and-test).

Comment thread src/piratechain/piratechainIo.ts Outdated
j0ntz added 4 commits August 3, 2026 17:13
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.
@j0ntz
j0ntz force-pushed the agent/1214721783909451 branch from 2cbed88 to 6d4e32d Compare August 4, 2026 03:04
Comment thread src/piratechain/piratechainDeviceStorage.ts
j0ntz added 2 commits August 3, 2026 21:31
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.
@j0ntz
j0ntz force-pushed the agent/1214721783909451 branch from 6d4e32d to 76bb601 Compare August 4, 2026 04:33
@j0ntz

j0ntz commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

📸 Test evidence

arrr synced

arrr synced

arrr send ready

arrr send ready

arrr sync stalled

arrr sync stalled

Captured by the agent's in-app test run (build-and-test).

Comment thread src/piratechain/PiratechainEngine.ts Outdated
Comment thread src/piratechain/PiratechainEngine.ts Outdated
j0ntz added 2 commits August 11, 2026 20:36
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.
@j0ntz
j0ntz force-pushed the agent/1214721783909451 branch from 59e95bb to a58a7ed Compare August 12, 2026 03:40
@j0ntz

j0ntz commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

📸 Test evidence: react-native-pirate-wallet 0.3.0 (unified wallet v1.1.6), funded ARRR send

arrr synced 0.3.0

arrr synced 0.3.0

arrr send ready

arrr send ready

arrr send success

arrr send success

arrr balance after send

arrr balance after send

Captured by the agent's in-app test run (build-and-test).

Comment thread package.json
@j0ntz
j0ntz force-pushed the agent/1214721783909451 branch from 25992e1 to 983e6b0 Compare August 17, 2026 22:13
Comment thread src/piratechain/piratechainDeviceStorage.ts
Comment thread src/piratechain/piratechainIo.ts
Comment thread src/piratechain/piratechainIo.ts
Comment thread src/piratechain/piratechainIo.ts Outdated
Comment thread src/piratechain/piratechainIo.ts
@j0ntz
j0ntz force-pushed the agent/1214721783909451 branch from 983e6b0 to 70ea0d9 Compare August 17, 2026 22:43

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 70ea0d9. Configure here.

Comment thread src/piratechain/PiratechainEngine.ts
The Pirate team published unified wallet v1.1.6 as react-native-pirate-wallet
0.3.0, with all four native sidecars. It sets the Ironwood activation height,
ships their Supernova sync engine, and fixes several sync issues. The wrapper's
JS surface is byte-identical to 0.2.1, so the plugin needs no reconciliation:
the whole delta is native, and the peer range is the only thing that has to
move.
@j0ntz
j0ntz force-pushed the agent/1214721783909451 branch 3 times, most recently from 494248a to b9d763e Compare August 20, 2026 00:02
Records the 0.3.0 round: the broadcast timeout is gone (2.08s end to end,
against roughly five minutes ending in a gRPC timeout on 0.2.1), sync runs
about 27x faster on the Supernova engine, and the release's whole delta is
native. Adds sections for Ironwood activation, which is what makes shipping
this pair time-sensitive, and for the arm64 link ceiling that still blocks the
merge. Retires the stale non-goal that deferred the v1.1.6 bump.
@j0ntz
j0ntz force-pushed the agent/1214721783909451 branch from b9d763e to 54b8a1f Compare August 20, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants