Skip to content

Commit 2f94194

Browse files
committed
Expose async persistence in FFI
This exposes the async persistence methods in payjoin-ffi and accompanying unit tests in the downstream languages.
1 parent 06f3314 commit 2f94194

9 files changed

Lines changed: 607 additions & 24 deletions

File tree

Cargo-minimal.lock

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2451,6 +2451,7 @@ dependencies = [
24512451
name = "payjoin-ffi"
24522452
version = "0.24.0"
24532453
dependencies = [
2454+
"async-trait",
24542455
"bdk",
24552456
"bitcoin-ohttp",
24562457
"getrandom 0.2.15",
@@ -4055,7 +4056,7 @@ dependencies = [
40554056
[[package]]
40564057
name = "uniffi-dart"
40574058
version = "0.1.0"
4058-
source = "git+https://github.com/Uniffi-Dart/uniffi-dart.git?rev=5bdcc79#5bdcc790c3fc99845e7b4a61d7a4f6e1460896e9"
4059+
source = "git+https://github.com/Uniffi-Dart/uniffi-dart.git?rev=f830323#f830323646fb6fbca89f9798dcf425f339f166ca"
40594060
dependencies = [
40604061
"anyhow",
40614062
"camino",
@@ -4125,7 +4126,7 @@ dependencies = [
41254126
[[package]]
41264127
name = "uniffi_dart_macro"
41274128
version = "0.1.0"
4128-
source = "git+https://github.com/Uniffi-Dart/uniffi-dart.git?rev=5bdcc79#5bdcc790c3fc99845e7b4a61d7a4f6e1460896e9"
4129+
source = "git+https://github.com/Uniffi-Dart/uniffi-dart.git?rev=f830323#f830323646fb6fbca89f9798dcf425f339f166ca"
41294130
dependencies = [
41304131
"futures",
41314132
"proc-macro2",

Cargo-recent.lock

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2451,6 +2451,7 @@ dependencies = [
24512451
name = "payjoin-ffi"
24522452
version = "0.24.0"
24532453
dependencies = [
2454+
"async-trait",
24542455
"bdk",
24552456
"bitcoin-ohttp",
24562457
"getrandom 0.2.15",
@@ -4055,7 +4056,7 @@ dependencies = [
40554056
[[package]]
40564057
name = "uniffi-dart"
40574058
version = "0.1.0"
4058-
source = "git+https://github.com/Uniffi-Dart/uniffi-dart.git?rev=5bdcc79#5bdcc790c3fc99845e7b4a61d7a4f6e1460896e9"
4059+
source = "git+https://github.com/Uniffi-Dart/uniffi-dart.git?rev=f830323#f830323646fb6fbca89f9798dcf425f339f166ca"
40594060
dependencies = [
40604061
"anyhow",
40614062
"camino",
@@ -4125,7 +4126,7 @@ dependencies = [
41254126
[[package]]
41264127
name = "uniffi_dart_macro"
41274128
version = "0.1.0"
4128-
source = "git+https://github.com/Uniffi-Dart/uniffi-dart.git?rev=5bdcc79#5bdcc790c3fc99845e7b4a61d7a4f6e1460896e9"
4129+
source = "git+https://github.com/Uniffi-Dart/uniffi-dart.git?rev=f830323#f830323646fb6fbca89f9798dcf425f339f166ca"
41294130
dependencies = [
41304131
"futures",
41314132
"proc-macro2",

payjoin-ffi/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ name = "uniffi-bindgen"
2222
path = "uniffi-bindgen.rs"
2323

2424
[dependencies]
25+
async-trait = "0.1"
2526
getrandom = "0.2"
2627
lazy_static = "1.5.0"
2728
ohttp = { package = "bitcoin-ohttp", version = "0.6.0" }
@@ -32,7 +33,7 @@ serde_json = "1.0.142"
3233
thiserror = "2.0.14"
3334
tokio = { version = "1.47.1", features = ["full"], optional = true }
3435
uniffi = { version = "0.30.0", features = ["cli"] }
35-
uniffi-dart = { git = "https://github.com/Uniffi-Dart/uniffi-dart.git", rev = "5bdcc79", optional = true }
36+
uniffi-dart = { git = "https://github.com/Uniffi-Dart/uniffi-dart.git", rev = "f830323", optional = true }
3637
url = "2.5.4"
3738

3839
# getrandom is ignored here because it's required by the wasm_js feature

payjoin-ffi/dart/test/test_payjoin_unit_test.dart

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,54 @@ class InMemorySenderPersister implements payjoin.JsonSenderSessionPersister {
5050
}
5151
}
5252

53+
class InMemoryReceiverPersisterAsync
54+
implements payjoin.JsonReceiverSessionPersisterAsync {
55+
final String id;
56+
final List<String> events = [];
57+
bool closed = false;
58+
59+
InMemoryReceiverPersisterAsync(this.id);
60+
61+
@override
62+
Future<void> save(String event) async {
63+
events.add(event);
64+
}
65+
66+
@override
67+
Future<List<String>> load() async {
68+
return events;
69+
}
70+
71+
@override
72+
Future<void> close() async {
73+
closed = true;
74+
}
75+
}
76+
77+
class InMemorySenderPersisterAsync
78+
implements payjoin.JsonSenderSessionPersisterAsync {
79+
final String id;
80+
final List<String> events = [];
81+
bool closed = false;
82+
83+
InMemorySenderPersisterAsync(this.id);
84+
85+
@override
86+
Future<void> save(String event) async {
87+
events.add(event);
88+
}
89+
90+
@override
91+
Future<List<String>> load() async {
92+
return events;
93+
}
94+
95+
@override
96+
Future<void> close() async {
97+
closed = true;
98+
}
99+
}
100+
53101
void main() {
54102
group('Test URIs', () {
55103
test('Test todo url encoded', () {
@@ -153,4 +201,56 @@ void main() {
153201
);
154202
});
155203
});
204+
205+
group("Test Async Persistence", () {
206+
test("Test receiver async persistence", () async {
207+
var persister = InMemoryReceiverPersisterAsync("1");
208+
await payjoin.ReceiverBuilder(
209+
"tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4",
210+
"https://example.com",
211+
payjoin.OhttpKeys.decode(
212+
Uint8List.fromList(
213+
hex.decode(
214+
"01001604ba48c49c3d4a92a3ad00ecc63a024da10ced02180c73ec12d8a7ad2cc91bb483824fe2bee8d28bfe2eb2fc6453bc4d31cd851e8a6540e86c5382af588d370957000400010003",
215+
),
216+
),
217+
),
218+
).build().saveAsync(persister);
219+
final result = await payjoin.replayReceiverEventLogAsync(persister);
220+
expect(
221+
result,
222+
isA<payjoin.ReplayResult>(),
223+
reason: "persistence should return a replay result",
224+
);
225+
});
226+
227+
test("Test sender async persistence", () async {
228+
var receiver_persister = InMemoryReceiverPersisterAsync("1");
229+
var receiver = await payjoin.ReceiverBuilder(
230+
"2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK",
231+
"https://example.com",
232+
payjoin.OhttpKeys.decode(
233+
Uint8List.fromList(
234+
hex.decode(
235+
"01001604ba48c49c3d4a92a3ad00ecc63a024da10ced02180c73ec12d8a7ad2cc91bb483824fe2bee8d28bfe2eb2fc6453bc4d31cd851e8a6540e86c5382af588d370957000400010003",
236+
),
237+
),
238+
),
239+
).build().saveAsync(receiver_persister);
240+
var uri = receiver.pjUri();
241+
242+
var sender_persister = InMemorySenderPersisterAsync("1");
243+
var psbt =
244+
"cHNidP8BAHMCAAAAAY8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////AtyVuAUAAAAAF6kUHehJ8GnSdBUOOv6ujXLrWmsJRDCHgIQeAAAAAAAXqRR3QJbbz0hnQ8IvQ0fptGn+votneofTAAAAAAEBIKgb1wUAAAAAF6kU3k4ekGHKWRNbA1rV5tR5kEVDVNCHAQcXFgAUx4pFclNVgo1WWAdN1SYNX8tphTABCGsCRzBEAiB8Q+A6dep+Rz92vhy26lT0AjZn4PRLi8Bf9qoB/CMk0wIgP/Rj2PWZ3gEjUkTlhDRNAQ0gXwTO7t9n+V14pZ6oljUBIQMVmsAaoNWHVMS02LfTSe0e388LNitPa1UQZyOihY+FFgABABYAFEb2Giu6c4KO5YW0pfw3lGp9jMUUAAA=";
245+
final result = await payjoin.SenderBuilder(
246+
psbt,
247+
uri,
248+
).buildRecommended(1000).saveAsync(sender_persister);
249+
expect(
250+
result,
251+
isA<payjoin.WithReplyKey>(),
252+
reason: "persistence should return a reply key",
253+
);
254+
});
255+
});
156256
}

payjoin-ffi/javascript/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

payjoin-ffi/javascript/test/unit.test.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,54 @@ class InMemorySenderPersister {
5454
}
5555
}
5656

57+
class InMemoryReceiverPersisterAsync {
58+
id: number;
59+
events: any[];
60+
closed: boolean;
61+
62+
constructor(id: number) {
63+
this.id = id;
64+
this.events = [];
65+
this.closed = false;
66+
}
67+
68+
async save(event: any): Promise<void> {
69+
this.events.push(event);
70+
}
71+
72+
async load(): Promise<any[]> {
73+
return this.events;
74+
}
75+
76+
async close(): Promise<void> {
77+
this.closed = true;
78+
}
79+
}
80+
81+
class InMemorySenderPersisterAsync {
82+
id: number;
83+
events: any[];
84+
closed: boolean;
85+
86+
constructor(id: number) {
87+
this.id = id;
88+
this.events = [];
89+
this.closed = false;
90+
}
91+
92+
async save(event: any): Promise<void> {
93+
this.events.push(event);
94+
}
95+
96+
async load(): Promise<any[]> {
97+
return this.events;
98+
}
99+
100+
async close(): Promise<void> {
101+
this.closed = true;
102+
}
103+
}
104+
57105
describe("URI tests", () => {
58106
test("URL encoded payjoin parameter", () => {
59107
const uri =
@@ -172,6 +220,76 @@ describe("Persistence tests", () => {
172220
});
173221
});
174222

223+
describe("Async Persistence tests", () => {
224+
test("receiver async persistence", async () => {
225+
const persister = new InMemoryReceiverPersisterAsync(1);
226+
const address = "tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4";
227+
const ohttpKeys = payjoin.OhttpKeys.decode(
228+
new Uint8Array([
229+
0x01, 0x00, 0x16, 0x04, 0xba, 0x48, 0xc4, 0x9c, 0x3d, 0x4a,
230+
0x92, 0xa3, 0xad, 0x00, 0xec, 0xc6, 0x3a, 0x02, 0x4d, 0xa1,
231+
0x0c, 0xed, 0x02, 0x18, 0x0c, 0x73, 0xec, 0x12, 0xd8, 0xa7,
232+
0xad, 0x2c, 0xc9, 0x1b, 0xb4, 0x83, 0x82, 0x4f, 0xe2, 0xbe,
233+
0xe8, 0xd2, 0x8b, 0xfe, 0x2e, 0xb2, 0xfc, 0x64, 0x53, 0xbc,
234+
0x4d, 0x31, 0xcd, 0x85, 0x1e, 0x8a, 0x65, 0x40, 0xe8, 0x6c,
235+
0x53, 0x82, 0xaf, 0x58, 0x8d, 0x37, 0x09, 0x57, 0x00, 0x04,
236+
0x00, 0x01, 0x00, 0x03,
237+
]).buffer,
238+
);
239+
240+
const builder = new payjoin.ReceiverBuilder(
241+
address,
242+
"https://example.com",
243+
ohttpKeys,
244+
);
245+
await builder.build().saveAsync(persister);
246+
247+
const result = await payjoin.replayReceiverEventLogAsync(persister);
248+
const state = result.state();
249+
250+
assert.strictEqual(
251+
state.tag,
252+
"Initialized",
253+
"State should be Initialized",
254+
);
255+
});
256+
257+
test("sender async persistence", async () => {
258+
const persister = new InMemoryReceiverPersisterAsync(1);
259+
const address = "2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK";
260+
const ohttpKeys = payjoin.OhttpKeys.decode(
261+
new Uint8Array([
262+
0x01, 0x00, 0x16, 0x04, 0xba, 0x48, 0xc4, 0x9c, 0x3d, 0x4a,
263+
0x92, 0xa3, 0xad, 0x00, 0xec, 0xc6, 0x3a, 0x02, 0x4d, 0xa1,
264+
0x0c, 0xed, 0x02, 0x18, 0x0c, 0x73, 0xec, 0x12, 0xd8, 0xa7,
265+
0xad, 0x2c, 0xc9, 0x1b, 0xb4, 0x83, 0x82, 0x4f, 0xe2, 0xbe,
266+
0xe8, 0xd2, 0x8b, 0xfe, 0x2e, 0xb2, 0xfc, 0x64, 0x53, 0xbc,
267+
0x4d, 0x31, 0xcd, 0x85, 0x1e, 0x8a, 0x65, 0x40, 0xe8, 0x6c,
268+
0x53, 0x82, 0xaf, 0x58, 0x8d, 0x37, 0x09, 0x57, 0x00, 0x04,
269+
0x00, 0x01, 0x00, 0x03,
270+
]).buffer,
271+
);
272+
273+
const receiver = await new payjoin.ReceiverBuilder(
274+
address,
275+
"https://example.com",
276+
ohttpKeys,
277+
)
278+
.build()
279+
.saveAsync(persister);
280+
const uri = receiver.pjUri();
281+
282+
const senderPersister = new InMemorySenderPersisterAsync(1);
283+
const psbt =
284+
"cHNidP8BAHMCAAAAAY8nutGgJdyYGXWiBEb45Hoe9lWGbkxh/6bNiOJdCDuDAAAAAAD+////AtyVuAUAAAAAF6kUHehJ8GnSdBUOOv6ujXLrWmsJRDCHgIQeAAAAAAAXqRR3QJbbz0hnQ8IvQ0fptGn+votneofTAAAAAAEBIKgb1wUAAAAAF6kU3k4ekGHKWRNbA1rV5tR5kEVDVNCHAQcXFgAUx4pFclNVgo1WWAdN1SYNX8tphTABCGsCRzBEAiB8Q+A6dep+Rz92vhy26lT0AjZn4PRLi8Bf9qoB/CMk0wIgP/Rj2PWZ3gEjUkTlhDRNAQ0gXwTO7t9n+V14pZ6oljUBIQMVmsAaoNWHVMS02LfTSe0e388LNitPa1UQZyOihY+FFgABABYAFEb2Giu6c4KO5YW0pfw3lGp9jMUUAAA=";
285+
const withReplyKey = await new payjoin.SenderBuilder(psbt, uri)
286+
.buildRecommended(BigInt(1000))
287+
.saveAsync(senderPersister);
288+
289+
assert.ok(withReplyKey, "Sender should be created successfully");
290+
});
291+
});
292+
175293
describe("Validation", () => {
176294
test("receiver builder rejects bad address", () => {
177295
assert.throws(() => {

0 commit comments

Comments
 (0)