Skip to content

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

Open
j0ntz wants to merge 1 commit into
masterfrom
agent/1214721783909451
Open

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

Conversation

@j0ntz
Copy link
Copy Markdown
Contributor

@j0ntz j0ntz commented Jun 5, 2026

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

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 the react-native-piratechain wrapper 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:

  • Wallet registry instead of per-seed synchronizers. The native core persists wallets in its own encrypted registry. The io bridge restores each Edge wallet into the registry under the existing walletId alias name (idempotent lookup-by-name, serialized to avoid duplicate restores) and all wallet-scoped calls use the resulting registry wallet id.
  • Polling synchronizer. The SDK's JS synchronizer polls sync_status/get_balance/list_transactions natively, so the io bridge forwards its snapshots as the existing update/statusChanged/error yaob events. Block progress maps from localHeight/targetHeight.
  • Transaction history. list_transactions replaces block-range queries (the blockRange windowing in otherData is gone). Amounts are signed and fee-inclusive for sends, which matches Edge's nativeAmount semantics directly. Recipient addresses are not exposed by the SDK, so sends are detected by sign.
  • Sends. The registry wallet holds the spending keys, so broadcastTx sends outputs (plus the explicit default fee) through the SDK instead of passing the mnemonic per spend.
  • Addresses / keys. Receive addresses come from current_receive_address (still sapling zs1… — orchard is not active on mainnet). derivePublicKey exports the sapling viewing key after registering the wallet. New-wallet birthday heights are probed via a throwaway create_wallet, whose null-birthday path resolves the live chain tip with a checkpoint fallback.
  • Endpoints. The lightwalletd endpoint and checkpoints are baked into the native core, so rpcNode in networkInfo is no longer consumed (kept for info-server payload compatibility).

The plugin compiles against local typings (rnPirateWallet.d.ts) because react-native-pirate-wallet is 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. the zano-utils-js EdgeApp-release pattern) or published before edge-react-gui can swap react-native-piratechain for 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
Large rewrite of sync, balance, transaction mapping, and spend paths for a privacy coin; incorrect handling could affect funds display or broadcasts until GUI ships the new native dependency.

Overview
Reimplements the Pirate Chain (ARRR) plugin on react-native-pirate-wallet, replacing react-native-piratechain (peer dependency and lockfile updated; local rnPirateWallet.d.ts added because the SDK is not on npm).

piratechainIo is rewritten around the unified SDK: lazy SDK init, fixed app passphrase + Direct tunnel (not Tor), serialized wallet registry restore keyed by the Edge wallet id alias, and a polling synchronizer that maps snapshots to existing yaob update / statusChanged / error events. Sends use raw build_txsign_txbroadcast_tx invoke because the JS send helper mishandles field casing.

PiratechainEngine drops block-range paging and otherData.blockRange; history comes from full getTransactions() lists with processedTxHeights deduping. Balances use spendable / total; txs map PirateTransaction fields (signed amounts, per-tx fees). broadcastTx no longer passes the mnemonic—outputs + fee only. Critical synchronizer errors no longer restart the engine. PiratechainTools uses piratechainIo for validation, birthday height, and registry registration when deriving the sapling viewing key.

Reviewed by Cursor Bugbot for commit 0481ded. 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
Copy link
Copy Markdown
Contributor Author

j0ntz commented Jun 5, 2026

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.

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.
@j0ntz j0ntz force-pushed the agent/1214721783909451 branch from 7049511 to 0481ded Compare June 5, 2026 23:23
@j0ntz
Copy link
Copy Markdown
Contributor Author

j0ntz commented Jun 5, 2026

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.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 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 0481ded. Configure here.

Comment thread src/piratechain/PiratechainEngine.ts
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.

1 participant