Repository navigation
Reimplement Pirate Chain plugin over react-native-pirate-wallet - #1055
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
2cbed88 to
6d4e32d
Compare
6d4e32d to
76bb601
Compare
59e95bb to
a58a7ed
Compare
25992e1 to
983e6b0
Compare
983e6b0 to
70ea0d9
Compare
5128245 to
e71029a
Compare
There was a problem hiding this comment.
Stale comment
Agentic security review for this head found one medium issue: signing protection is not applied at restore and fail-opens if the signing-session RPCs throw, which can leave ARRR spending keys wrapped only by the device registry credential.
Sent by Cursor Security Agent: Security Reviewer
6ba9f1b to
04fdfd6
Compare
04fdfd6 to
4ba24aa
Compare
peachbits
left a comment
There was a problem hiding this comment.
Local review of the head commit. The first two comments (rnPirateWallet.d.ts and package.json) are one coupled change — the hand-written ambient declaration wins over node_modules, so the devDependency only helps once the file is gone. Everything else stands alone. I skipped the queryMutex boolean, which cursor already raised at line 123.
4ba24aa to
866a558
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 866a558. Configure here.
855af01 to
359221e
Compare
peachbits
left a comment
There was a problem hiding this comment.
Re-review at 359221e5. All nine earlier threads are fixed as described; tsc and eslint pass with types now resolved from node_modules/react-native-pirate-wallet/src/index.d.ts and @types/create-hmac. Two new comments on code the fixes introduced — the first also corrects a premise in my earlier getMaxSpendable comment.
428d199 to
fda1f6c
Compare
The Pirate team's unified wallet replaces the zcash-cloned react-native-piratechain module, in which the Swift and Rust layers each opened the wallet's SQLite database and the app crashed when they overlapped. The new SDK keeps every database access in Rust and holds the lightwalletd endpoint, checkpoints and spending keys inside its native core. Rebuild the engine, tools and yaob io bridge on that API, against react-native-pirate-wallet 0.3.4 from npm as a peer dependency. The typings in rnPirateWallet.d.ts declare the 0.3.4 surface the plugin consumes. Registry: the SDK's namespace is selected globally, and switching cancels any running sync and clears the registry and block caches, so the bridge configures one device-scoped registry once at init and holds every ARRR wallet inside it under its Edge walletId alias, each wallet's synchronizer running concurrently over the shared block cache. The native module mints the registry credential and holds it in the iOS Keychain or the Android Keystore through configureSecureAccountStorage, so it never crosses the bridge. Wallet-free reads use the same registry, and the chain tip comes from a registered wallet's sync status, then from test_node against the configured node, before the create-and-delete probe that the native service cannot survive while a synchronizer runs. Nodes: the SDK bakes in a default lightwalletd and never consults the plugin's networkInfo, so the bridge points each wallet at the plugin's own nodes before its synchronizer starts: lightwalletd2.cryptoforge.cc over TLS as the primary, with lightwalletd1 as the alternate in an automatic-failover pool through set_lightd_endpoint_pool. The SDK refuses a pool that mixes transports or names a host it does not recognize, and a rejected pool falls back to the single endpoint. The info server can replace the list without an app release. Sync progress comes from the SDK's polling synchronizer, with the status read once after the engine subscribes so an early SYNCED cannot strand the engine at STOPPED. Amounts cross the wire as decimal strings so balances above 2^53-1 arrrtoshi keep full precision, and sends go through the SDK's send(), which builds, signs and broadcasts through the registry wallet instead of passing the mnemonic per spend. The design, its decisions with their rejected alternatives, the test evidence and the phase history are in src/docs/piratechain-unified-wallet-sdk.md.
A Pirate Chain wallet reports SYNCED before its spend anchor is usable, so a send in that window built, was confirmed by the user, and then failed inside the SDK with ERR_SYNC_FINALIZING, which the app rendered as a network error. makeSpend now reads get_spendability_status and throws PendingFundsError while the wallet cannot spend, so the send scene holds no transaction and keeps its confirm slider disabled. The status's reasonCode is a closed set (OK, ERR_SYNC_FINALIZING, ERR_WITNESS_REPAIR_QUEUED, ERR_RESCAN_REQUIRED) and the message branches on it, with the two booleans as the fallback for a status that arrives without one. A broadcast that still fails with one of the two retryable codes is restated the same way, so the advice is to wait and retry rather than to check the network. A status the plugin cannot read falls through to the SYNCED-only gate.
SDK 0.3.4 can wrap a wallet's seed and spending keys under a second key derived from a credential the host supplies, holding that key in memory only, so a locked registry still syncs but cannot sign. Edge has no account-session secret to hand a currency plugin, so the credential is an HMAC of the wallet mnemonic under a fixed domain: the engine holds the mnemonic exactly while the account is unlocked, which puts the SDK's lock on Edge's schedule. Protection is enabled once per wallet, unlocked on every synchronizer start and again before each send, and locked when the synchronizer stops. Sync never depends on it, so a failed signing call logs and a later send reports the SDK's own reason.
SDK 0.3.4 adds a live probe of the configured lightwalletd pool that reports which node it would select and why it rejected the rest. A wallet that never syncs otherwise looks the same whether every node is down or the pool was refused, so the engine logs the probe once per start, off the sync path, at warn level so it reaches the app's persisted logs and ships with a user's log export.
fda1f6c to
057ad63
Compare



Technical Design Document
piratechain-unified-wallet-sdk.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?
Dependencies
none
Description
Asana: https://app.asana.com/0/1215088146871429/1214721783909451
Reimplements the Pirate Chain plugin over
react-native-pirate-wallet(npm0.3.4), the Pirate team's replacement for thereact-native-piratechainmodule 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 setpiratechain: falsein the GUI'scorePlugins.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:
list_transactions. Amounts cross the wire as decimal strings, and sends go through the SDK'ssend().set_lightd_endpoint. Without it a wallet scans against the SDK's baked-in default, which no longer answers at all, and the app sits at 0% sync with no error anywhere. That node is nowhttps://lightwalletd2.cryptoforge.cc:443: TLS instead of plain gRPC, and measurably faster under0.3.2(below).getLatestNetworkHeighttakes the chain tip from a registered wallet or fromtest_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.set_lightd_endpoint_pool, and it now ships with an alternate (lightwalletd1.cryptoforge.cc) rather than empty. The SDK validates a pool and rejects one that names an unrecognized host or mixes transports; a rejected pool falls back to the single endpoint rather than failing the wallet, which both rejections were observed doing on device. The info server can still replace the list without an app release.The plugin compiles against the package's own
index.d.ts:react-native-pirate-walletis a devDependency like the other native SDKs, the hand-written ambient declaration is gone,create-hmacis a declared dependency with@types/create-hmac, and the endpoint calls go through the typedtestLightdEndpoint,setLightdEndpointandsetLightdEndpointPool; rawinvokeremains forset_tunnelonly. The bridge exposesstartand the engine calls it after subscribing, so the first status change is never missed.getMaxSpendablerefuses for the same reasonmakeSpenddoes, and a transaction whose processing throws is retried on the next poll.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, with the mocha suite at 490 passing.
A controlled cold-sync A/B settled whether to ship the
0.3.2bump. One wallet 1,056,918 blocks behind, every other ARRR wallet archived, the same simulator and session, each side forced cold and confirmed cold by watching the block cache grow from zero, and the SDK swapped by replacinglibpirate_ffi_native.aand relinking so the static archive was the only variable:0.3.0http://lightd1.pirate.black:90670.3.2http://lightd1.pirate.black:90670.3.2https://lightwalletd2.cryptoforge.cc:443The first two rows read as a 20-40x regression in
0.3.2, which was the working verdict until the third row was measured.0.3.2is not slow; the pair of0.3.2andlightd1.pirate.blackis. So this branch ships the bump and moves the endpoint, which also retires the plaintext transport the plugin's own comment had flagged as an exposure.The plugin also gates spending on the SDK's
get_spendability_status. A wallet reportsSYNCEDbefore its spend anchor is usable, and a send in that window used to build, get confirmed by the user, and only then fail inside the SDK, which the app rendered as "check your network connection".makeSpendnow reads the status and throwsPendingFundsErrorwhile the wallet cannot spend, so the send scene holds no transaction and its confirm slider stays disabled. A status the plugin cannot read falls through to the oldSYNCED-only gate rather than blocking the send. A wallet that has not reachedSYNCEDis refused with the samePendingFundsError, so the amount modal and the send scene's error card show one message for every wait.Testing showed the status is necessary and not sufficient, so
broadcastTxrestatesERR_SYNC_FINALIZINGandERR_WITNESS_REPAIR_QUEUEDthe same way: on 2026-08-31 a wallet readingspendable: true, repairQueued: false, reasonCode: OKwith a validated anchor still had four broadcasts rejected by the node on anchor grounds, and the status did not move afterwards. No principal or fee was lost. Section 10 of the doc carries that back to the Pirate Chain team as the open question.Both configurations then broadcast a funded ARRR send to the transaction-success scene:
0.3.0at txidd4bc2294a427e9c0f197c5ccc21d4ac2687d28fc5f4f3a718f77c757bc130585, and the shipping configuration at txid239ab8743afbed4f8047ed4e0b05d2aa3e39ebfa7ab9824ddfa52c72c47c4205. Section 7 of the doc has the full matrix, the node survey, the SDK's pool-validation rules, and what stayed uncovered (automatic failover under a stalling primary, which the SDK's recognized-host rule makes untestable with a local proxy). Section 10 is a report for the Pirate Chain team.Note
High Risk
This replaces the entire ARRR native stack and changes registry credentials, signing-key lifecycle, sync endpoints, and send/broadcast paths—core wallet and funds-handling behavior.
Overview
Pirate Chain (ARRR) moves off the crash-prone
react-native-piratechainmodule onto the Pirate team's unifiedreact-native-pirate-wallet(^0.3.4). The engine and IO bridge are rebuilt around that SDK while keeping the plugin's outward API the same.Sync and network behavior now force Edge's TLS lightwalletd (
lightwalletd2.cryptoforge.cc) with an optional failover pool (lightwalletdFailoverUrls, overridable from the info server). Wallets use a single device registry (configureSecureAccountStorage/ Keychain or Keystore) plus signing protection unlocked only duringsendvia an HMAC-derived credential from the mnemonic.Spending is gated on SDK spendability (
get_spendability_status) and pre-sync checks throwPendingFundsErrorwith wait-and-retry copy;broadcastTxmapsERR_SYNC_FINALIZING/ERR_WITNESS_REPAIR_QUEUEDthe same way. Sends use the SDKsend()path with string amounts; history comes from the poller's full list with per-tx cleaning.start()runs only after subscriptions attach, and chain-tip lookup avoids mutating a live registry (empty-registry probe only).Dependencies add
create-hmac; peer/dev dependency swapsreact-native-pirate-wallet. A largepiratechain-unified-wallet-sdk.mddesign doc and CHANGELOG entries document the migration.Reviewed by Cursor Bugbot for commit 057ad63. Bugbot is set up for automated code reviews on this repo. Configure here.
Test evidence
390ef73Rebuild Pirate Chain on react-native-pirate-wallet
arrr my pirate2 synced
arrr send dest zaddr
arrr send confirm
arrr send success
arrr send txid
arrr synced
arrr send ready
arrr sync stalled
arrr synced 0.3.0
arrr send ready
arrr send success
arrr balance after send
arrr synced 0.3.2 tls pool
arrr synced 0.3.0
arrr send ready
arrr send success
arrr txid
arrr send success 0.3.2 tls
arrr txid 0.3.2 tls
synced max fee slider armed
transaction history
8118f87Gate ARRR sends on SDK spendability
before generic error
gate passed slider armed
not spendable error
arrr sync in progress
presync refusal error card
428d199Log the ARRR endpoint pool diagnostics
🪓 forced the bridge's getSpendability in the updot-baked plugin copy to return spendable:false with reasonCode ERR_WITNESS_REPAIR_QUEUED (frames 04, 05) then ERR_SYNC_FINALIZING (frame 06); reverted, git status clean.
keychain registry syncing
gate passed slider armed
transaction success
txid
max fills spendable balance
send armed
transaction success
🪓 max refused witness repair
🪓 not spendable witness repair
🪓 not spendable sync finalizing
send armed fresh registry
transaction success fresh registry