@bennyblader/ddk-rn 0.4.0 → 1.0.0-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,6 +24,8 @@ Prebuilt platform coverage:
24
24
 
25
25
  The Intel Mac simulator (`x86_64`) is not included.
26
26
 
27
+ Prereleases publish to the `next` dist-tag: `npm install @bennyblader/ddk-rn@next`.
28
+
27
29
  ## Quick Start
28
30
 
29
31
  ```typescript
@@ -32,18 +34,70 @@ import { version, createFundTxLockingScript } from '@bennyblader/ddk-rn';
32
34
  console.log(`DDK Version: ${version()}`);
33
35
  ```
34
36
 
35
- For complete API documentation, see the [main README](../README.md#api-reference).
37
+ ### The contract API
38
+
39
+ The whole DLC lifecycle runs on device, with no contract store — every
40
+ transaction is rebuilt from the offer/accept/sign wire messages when it is
41
+ needed, and funding secret keys stay inside Rust behind a `ContractKeyProvider`.
42
+
43
+ ```typescript
44
+ import {
45
+ ContractKeyProvider,
46
+ chainHashFromNetwork,
47
+ createOffer,
48
+ acceptOffer,
49
+ signAccept,
50
+ finalizeSign,
51
+ signContractCet,
52
+ } from '@bennyblader/ddk-rn';
53
+
54
+ const keys = ContractKeyProvider.fromMnemonic(MNEMONIC, undefined, 'regtest');
55
+ const temporaryContractId = new Uint8Array(32).fill(1).buffer;
56
+
57
+ const offer = createOffer({
58
+ chainHash: chainHashFromNetwork('regtest'),
59
+ temporaryContractId,
60
+ contractInfo: CONTRACT_INFO,
61
+ offerCollateralSats: 50_000n,
62
+ party: {
63
+ fundingPubkey: keys.fundingPubkey(temporaryContractId),
64
+ fundingInputs: [FUNDING_INPUT],
65
+ payoutSpk: SPK,
66
+ changeSpk: SPK,
67
+ },
68
+ feeRatePerVb: 2n,
69
+ cetLocktime: 0,
70
+ refundLocktime: 1_700_000_000,
71
+ contractFlags: 0,
72
+ });
73
+ // …acceptOffer → sign the funding PSBT → signAccept → finalizeSign →
74
+ // signContractCet / signContractRefund
75
+ ```
76
+
77
+ Bytes cross the JSI boundary as `ArrayBuffer` (`ddk-ts` uses `Buffer`).
78
+
79
+ `example/src/App.tsx` runs the complete flow on device — offline, deterministic,
80
+ and asserted by the Maestro E2E.
81
+
82
+ For complete API documentation, see the [main README](../README.md#-the-contract-api).
36
83
 
37
84
  ## Development
38
85
 
39
86
  ### Prerequisites
40
87
 
41
- - Node.js >= 14
88
+ - Node.js >= 20
42
89
  - Rust >= 1.70
43
90
  - pnpm
44
- - UniFFI React Native: `npm install -g uniffi-bindgen-react-native`
91
+ - UniFFI React Native, installed globally at the version pinned in
92
+ `package.json`: `pnpm add -g uniffi-bindgen-react-native@<version>`
45
93
  - iOS: Xcode 14+, CocoaPods
46
- - Android: Android Studio, NDK
94
+ - Android: Android Studio, NDK 27.1.12297006
95
+
96
+ > The `uniffi` crate, this package's `uniffi-bindgen-react-native` dependency,
97
+ > and the globally installed binary must all be the same release — the `just`
98
+ > recipes call the binary on `$PATH`, not the one in `node_modules`. A skew shows
99
+ > up as TypeScript errors like "Expected 2 arguments, but got 1" on every
100
+ > generated `.lower()` call.
47
101
 
48
102
  ### Building from Source
49
103
 
@@ -51,30 +105,26 @@ For complete API documentation, see the [main README](../README.md#api-reference
51
105
  # Install dependencies
52
106
  pnpm install
53
107
 
54
- # Generate all bindings and build native libraries
55
- just uniffi
56
-
57
- # Note: After building, manually fix the include path in cpp/bennyblader-ddk-rn.cpp
58
- # Change: #include "/ddk_ffi.hpp"
59
- # To: #include "ddk_ffi.hpp"
108
+ # Generate all bindings and build the native libraries
109
+ just build
60
110
  ```
61
111
 
62
112
  ### Just Commands
63
113
 
64
114
  ```bash
65
- # Complete build (generates bindings + builds iOS/Android)
66
- just uniffi
115
+ # JSI + TurboModule bindings, the iOS XCFramework, and ddk-ts
116
+ just build
67
117
 
68
- # Generate JSI bindings only
118
+ # Generate JSI bindings only (TypeScript + C++)
69
119
  just uniffi-jsi
70
120
 
71
121
  # Generate TurboModule specifications
72
122
  just uniffi-turbo
73
123
 
74
- # Build iOS libraries
124
+ # Build the iOS XCFramework (release, stripped)
75
125
  just build-ios
76
126
 
77
- # Build Android libraries
127
+ # Build the Android JNI libraries (needs the NDK)
78
128
  just build-android
79
129
 
80
130
  # Setup example app
@@ -86,51 +136,101 @@ just example-ios
86
136
  # Android example build
87
137
  just example-android
88
138
 
139
+ # Report the size of every binary destined for the npm package
140
+ just binary-sizes
141
+
89
142
  # Clean all generated files
90
143
  just clean
91
-
92
- # Release new version
93
- just release
94
-
95
- # Create release archives
96
- just release-archives
97
144
  ```
98
145
 
146
+ > Adding a new `#[uniffi::export]` needs `just build-ios`, not just
147
+ > `just uniffi-jsi`. `uniffi-jsi` regenerates the TypeScript and C++, but the C++
148
+ > calls into the XCFramework, which only `build-ios` rebuilds — skip it and the
149
+ > app fails at link with `Undefined symbols … _uniffi_ddk_ffi_fn_func_<name>`.
150
+ > Nothing earlier in the chain catches this.
151
+
99
152
  ### Project Structure
100
153
 
101
154
  ```
102
155
  ddk-rn/
103
156
  ├── src/ # Generated TypeScript bindings
104
157
  │ ├── index.tsx # Main entry point (generated)
105
- │ └── ddk_ffi*.ts # Type definitions (generated)
158
+ │ ├── ddk_ffi.ts # The API consumers import (generated)
159
+ │ ├── ddk_ffi-ffi.ts # Native JSI symbol declarations (generated)
160
+ │ └── __tests__/ # Hand-written — the only non-generated code in src/
106
161
  ├── cpp/ # Generated C++ JSI bindings
107
162
  │ ├── ddk_ffi.hpp # C++ header (generated)
108
163
  │ ├── ddk_ffi.cpp # C++ implementation (generated)
109
164
  │ └── *.cpp/.h # TurboModule files (generated)
110
165
  ├── ios/ # iOS native module
111
- │ ├── DdkRn.xcframework/ # iOS framework (generated)
112
- │ └── *.swift/.m # iOS bridge code
166
+ │ ├── DdkRn.xcframework/ # Prebuilt iOS framework (generated, shipped)
167
+ │ └── *.h/.mm # iOS bridge code
113
168
  ├── android/ # Android native module
114
169
  │ └── src/main/
115
170
  │ ├── java/ # Kotlin/Java bridge code
116
- │ └── jniLibs/ # Native libraries (generated)
117
- ├── example/ # Example React Native app
118
- ├── scripts/ # Build and release scripts
171
+ │ └── jniLibs/ # Prebuilt native libraries (generated, shipped)
172
+ ├── example/ # Example React Native app + Maestro flows
173
+ ├── patches/ # pnpm patch for an unreleased ubrn fix
174
+ ├── scripts/ # Package verification
119
175
  └── ubrn.config.yaml # UniFFI React Native configuration
120
176
  ```
121
177
 
178
+ Everything in `src/` except `src/__tests__/` is generated — don't hand-edit it,
179
+ and don't "fix" lint errors inside it (those files are in `eslintIgnore` for
180
+ exactly that reason).
181
+
182
+ ### Testing
183
+
184
+ ```bash
185
+ pnpm test # jest
186
+ pnpm typecheck # tsc
187
+ pnpm lint # eslint
188
+ ```
189
+
190
+ `src/__tests__/contractBindings.test.js` is a binding-surface test: the JSI
191
+ bindings can't execute under Node, so it asserts instead that generation was
192
+ complete — every function, record, and constructor present in both the
193
+ TypeScript surface and the native symbol layer. It catches the classic
194
+ regression where `ddk-ffi` changed but `ddk-rn` wasn't regenerated. Runtime
195
+ behavior is covered by the shared Rust (`ddk-ffi` unit tests and the `ddk-ts`
196
+ suite exercise the same functions).
197
+
198
+ ### End-to-end, on a device
199
+
200
+ CI installs the example app on an iOS simulator and an Android emulator and
201
+ drives the full contract flow with Maestro. It is the only layer that exercises
202
+ the real JSI bindings, and the only one that catches an app that never finishes
203
+ launching.
204
+
205
+ | command | needs | time |
206
+ |---|---|---|
207
+ | `just e2e-flows` | nothing — no device, no app | ~15s |
208
+ | `just e2e-ios-test` | the app already installed | ~10s |
209
+ | `just e2e-ios` | full: build + install + run | ~5-10m |
210
+
211
+ Android needs a one-time `just e2e-android-setup` (~1.5GB) to create the
212
+ emulator and AVD, then `just e2e-android`.
213
+
214
+ While editing a flow, stay in `e2e-ios-test`; only rebuild when the app itself
215
+ changes (JS, Rust, or the generated bindings).
216
+
122
217
  ### Platform Support
123
218
 
124
- | Platform | Architecture | Status |
125
- | ------------- | ------------ | ------------ |
126
- | iOS | ARM64 | ✅ Supported |
127
- | iOS Simulator | x64/ARM64 | ✅ Supported |
128
- | Android | ARM64-v8a | ✅ Supported |
129
- | Android | ARMv7 | ✅ Supported |
130
- | Android | x86_64 | ✅ Supported |
219
+ | Platform | Architecture | Status |
220
+ | ------------- | ---------------- | --------------- |
221
+ | iOS | ARM64 | ✅ Supported |
222
+ | iOS Simulator | ARM64 | ✅ Supported |
223
+ | iOS Simulator | x64 (Intel Mac) | ❌ Not included |
224
+ | Android | ARM64-v8a | ✅ Supported |
225
+ | Android | ARMv7 | ✅ Supported |
226
+ | Android | x86 / x86_64 | ✅ Supported |
131
227
 
132
228
  ### Building the Example App
133
229
 
230
+ The example is pinned to React Native 0.80 / React 19.1 — the release these
231
+ bindings are supported against, so the E2E exercises the toolchain consuming
232
+ apps actually build with.
233
+
134
234
  #### iOS
135
235
 
136
236
  ```bash
@@ -155,35 +255,60 @@ npx react-native run-android
155
255
 
156
256
  ### Release Process
157
257
 
158
- To release a new version:
258
+ `ddk-ts` and `ddk-rn` are versioned and released together, from the repo root:
159
259
 
160
260
  ```bash
161
- just release
261
+ just release 0.5.0
162
262
  ```
163
263
 
164
264
  This will:
165
265
 
166
- 1. Build all native bindings
167
- 2. Run tests
168
- 3. Update version
169
- 4. Create release commit and tag
170
- 5. Push to GitHub
171
- 6. Publish to npm
266
+ 1. Check the working directory is clean
267
+ 2. Set the version in `ddk-ts/package.json`, `ddk-rn/package.json` and `ddk-ffi/Cargo.toml`
268
+ 3. Commit, tag as `v0.5.0` and push
269
+
270
+ Publishing happens in CI — pushing the tag is what publishes. The iOS
271
+ XCFramework is built on `macos-latest`, the Android JNI libraries on
272
+ `ubuntu-latest` with the NDK, and a final job assembles both and verifies the
273
+ binaries are in the tarball before publishing. Nothing is published from a
274
+ developer machine; `prepublishOnly` refuses a hand-run `npm publish` that would
275
+ ship without binaries.
276
+
277
+ Update the `[Unreleased]` section of [CHANGELOG.md](./CHANGELOG.md) **before**
278
+ releasing — the release script refuses a dirty tree, so it can't be part of the
279
+ release commit.
172
280
 
173
281
  ### API Compatibility
174
282
 
175
- The React Native bindings maintain 100% API compatibility with the UniFFI definitions. All functions are generated directly from the UDL file ensuring consistency across platforms.
283
+ All bindings are generated from the compiled `ddk-ffi` library, whose Rust
284
+ source — annotated with UniFFI proc-macros, with no `.udl` file — is the single
285
+ source of truth for the interface.
176
286
 
177
- ### Known Issues
287
+ `@bennyblader/ddk-ts` is hand-written NAPI over the same crates and is kept in
288
+ parity by CI. Two differences to know: bytes are `ArrayBuffer` here and `Buffer`
289
+ there, and a dozen transaction operations are methods on a record here
290
+ (`TxOutput.isDust`, `Transaction.signCet`, …) but free functions there. See the
291
+ [comparison table](../README.md#where-the-two-packages-differ).
178
292
 
179
- 1. **Manual Include Path Fix**: After running `just uniffi`, you must manually fix the include path in `cpp/bennyblader-ddk-rn.cpp`:
180
- - Change: `#include "/ddk_ffi.hpp"`
181
- - To: `#include "ddk_ffi.hpp"`
293
+ ### Known Issues
182
294
 
183
- 2. **New Architecture**: The library requires React Native's new architecture to be enabled:
295
+ 1. **New Architecture**: The library requires React Native's new architecture to be enabled:
184
296
  - iOS: Set `RCT_NEW_ARCH_ENABLED=1`
185
297
  - Android: Set `newArchEnabled=true` in `gradle.properties`
186
298
 
299
+ 2. **Android C++ builds need an unreleased ubrn fix.** The generated
300
+ `android/CMakeLists.txt` resolves the ubrn package with
301
+ `require.resolve('uniffi-bindgen-react-native/package.json')`, which throws
302
+ `ERR_PACKAGE_PATH_NOT_EXPORTED` because ubrn's `exports` map doesn't expose
303
+ that subpath. The include dir silently becomes `/cpp/includes` and the build
304
+ dies with `'UniffiCallInvoker.h' file not found`. This repo patches it
305
+ (`patches/uniffi-bindgen-react-native@0.31.0-3.patch`, applied by
306
+ `pnpm install`), but a pnpm patch only applies to this repo — building an
307
+ Android app against the published package hits the original error until ubrn
308
+ ships the fix ([#404](https://github.com/jhugman/uniffi-bindgen-react-native/issues/404),
309
+ fixed on `main`, release tracked in
310
+ [#421](https://github.com/jhugman/uniffi-bindgen-react-native/issues/421)).
311
+
187
312
  ## Troubleshooting
188
313
 
189
314
  ### iOS Build Issues
@@ -212,7 +337,17 @@ If bindings are missing, regenerate them:
212
337
 
213
338
  ```bash
214
339
  just clean
215
- just uniffi
340
+ just build
341
+ ```
342
+
343
+ ### A modified `android/build.gradle` after building
344
+
345
+ `just build-android <abi>` narrows `abiFilters` to the ABIs you passed and wipes
346
+ `jniLibs/` for the rest. Never commit that — a single-ABI `build.gradle` ships an
347
+ app that only runs on one architecture:
348
+
349
+ ```bash
350
+ git checkout android/build.gradle
216
351
  ```
217
352
 
218
353
  ## License