Skip to content

Commit d314c0c

Browse files
Add FFI bindings for async receive interface
1 parent 65f8c39 commit d314c0c

3 files changed

Lines changed: 596 additions & 58 deletions

File tree

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

Lines changed: 212 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@ class MempoolAcceptanceCallback implements payjoin.CanBroadcast {
7777
}
7878
}
7979

80+
class MempoolAcceptanceCallbackAsync implements payjoin.CanBroadcastAsync {
81+
private inner: MempoolAcceptanceCallback;
82+
83+
constructor(connection: testUtils.RpcClient) {
84+
this.inner = new MempoolAcceptanceCallback(connection);
85+
}
86+
87+
async callback(tx: ArrayBuffer): Promise<boolean> {
88+
return this.inner.callback(tx);
89+
}
90+
}
91+
8092
class IsScriptOwnedCallback implements payjoin.IsScriptOwned {
8193
private connection: testUtils.RpcClient;
8294

@@ -143,6 +155,18 @@ class IsScriptOwnedCallback implements payjoin.IsScriptOwned {
143155
}
144156
}
145157

158+
class IsScriptOwnedCallbackAsync implements payjoin.IsScriptOwnedAsync {
159+
private inner: IsScriptOwnedCallback;
160+
161+
constructor(connection: testUtils.RpcClient) {
162+
this.inner = new IsScriptOwnedCallback(connection);
163+
}
164+
165+
async callback(script: ArrayBuffer): Promise<boolean> {
166+
return this.inner.callback(script);
167+
}
168+
}
169+
146170
class CheckInputsNotSeenCallback implements payjoin.IsOutputKnown {
147171
private connection: testUtils.RpcClient;
148172

@@ -155,6 +179,18 @@ class CheckInputsNotSeenCallback implements payjoin.IsOutputKnown {
155179
}
156180
}
157181

182+
class CheckInputsNotSeenCallbackAsync implements payjoin.IsOutputKnownAsync {
183+
private inner: CheckInputsNotSeenCallback;
184+
185+
constructor(connection: testUtils.RpcClient) {
186+
this.inner = new CheckInputsNotSeenCallback(connection);
187+
}
188+
189+
async callback(outpoint: payjoin.PlainOutPoint): Promise<boolean> {
190+
return this.inner.callback(outpoint);
191+
}
192+
}
193+
158194
class ProcessPsbtCallback implements payjoin.ProcessPsbt {
159195
private connection: testUtils.RpcClient;
160196

@@ -170,6 +206,18 @@ class ProcessPsbtCallback implements payjoin.ProcessPsbt {
170206
}
171207
}
172208

209+
class ProcessPsbtCallbackAsync implements payjoin.ProcessPsbtAsync {
210+
private inner: ProcessPsbtCallback;
211+
212+
constructor(connection: testUtils.RpcClient) {
213+
this.inner = new ProcessPsbtCallback(connection);
214+
}
215+
216+
async callback(psbt: string): Promise<string> {
217+
return this.inner.callback(psbt);
218+
}
219+
}
220+
173221
function createReceiverContext(
174222
address: string,
175223
directory: string,
@@ -244,10 +292,21 @@ function processProvisionalProposal(
244292
receiver: testUtils.RpcClient,
245293
recvPersister: InMemoryReceiverPersister,
246294
): payjoin.PayjoinProposal {
247-
const payjoinProposal = proposal
295+
return proposal
248296
.finalizeProposal(new ProcessPsbtCallback(receiver))
249297
.save(recvPersister) as payjoin.PayjoinProposal;
250-
return payjoinProposal;
298+
}
299+
300+
async function processProvisionalProposalAsync(
301+
proposal: payjoin.ProvisionalProposal,
302+
receiver: testUtils.RpcClient,
303+
recvPersister: InMemoryReceiverPersister,
304+
): Promise<payjoin.PayjoinProposal> {
305+
return (
306+
await proposal.finalizeProposalAsync(
307+
new ProcessPsbtCallbackAsync(receiver),
308+
)
309+
).save(recvPersister) as payjoin.PayjoinProposal;
251310
}
252311

253312
function processWantsFeeRange(
@@ -265,6 +324,21 @@ function processWantsFeeRange(
265324
);
266325
}
267326

327+
async function processWantsFeeRangeAsync(
328+
proposal: payjoin.WantsFeeRange,
329+
receiver: testUtils.RpcClient,
330+
recvPersister: InMemoryReceiverPersister,
331+
): Promise<payjoin.PayjoinProposal> {
332+
const provisionalProposal = proposal
333+
.applyFeeRange(1n, 10n)
334+
.save(recvPersister) as payjoin.ProvisionalProposal;
335+
return processProvisionalProposalAsync(
336+
provisionalProposal,
337+
receiver,
338+
recvPersister,
339+
);
340+
}
341+
268342
function processWantsInputs(
269343
proposal: payjoin.WantsInputs,
270344
receiver: testUtils.RpcClient,
@@ -277,6 +351,18 @@ function processWantsInputs(
277351
return processWantsFeeRange(wantsFeeRange, receiver, recvPersister);
278352
}
279353

354+
async function processWantsInputsAsync(
355+
proposal: payjoin.WantsInputs,
356+
receiver: testUtils.RpcClient,
357+
recvPersister: InMemoryReceiverPersister,
358+
): Promise<payjoin.PayjoinProposal> {
359+
const wantsFeeRange = proposal
360+
.contributeInputs(getInputs(receiver))
361+
.commitInputs()
362+
.save(recvPersister) as payjoin.WantsFeeRange;
363+
return processWantsFeeRangeAsync(wantsFeeRange, receiver, recvPersister);
364+
}
365+
280366
function processWantsOutputs(
281367
proposal: payjoin.WantsOutputs,
282368
receiver: testUtils.RpcClient,
@@ -288,6 +374,17 @@ function processWantsOutputs(
288374
return processWantsInputs(wantsInputs, receiver, recvPersister);
289375
}
290376

377+
async function processWantsOutputsAsync(
378+
proposal: payjoin.WantsOutputs,
379+
receiver: testUtils.RpcClient,
380+
recvPersister: InMemoryReceiverPersister,
381+
): Promise<payjoin.PayjoinProposal> {
382+
const wantsInputs = proposal
383+
.commitOutputs()
384+
.save(recvPersister) as payjoin.WantsInputs;
385+
return processWantsInputsAsync(wantsInputs, receiver, recvPersister);
386+
}
387+
291388
function processOutputsUnknown(
292389
proposal: payjoin.OutputsUnknown,
293390
receiver: testUtils.RpcClient,
@@ -299,6 +396,19 @@ function processOutputsUnknown(
299396
return processWantsOutputs(wantsOutputs, receiver, recvPersister);
300397
}
301398

399+
async function processOutputsUnknownAsync(
400+
proposal: payjoin.OutputsUnknown,
401+
receiver: testUtils.RpcClient,
402+
recvPersister: InMemoryReceiverPersister,
403+
): Promise<payjoin.PayjoinProposal> {
404+
const wantsOutputs = (
405+
await proposal.identifyReceiverOutputsAsync(
406+
new IsScriptOwnedCallbackAsync(receiver),
407+
)
408+
).save(recvPersister) as payjoin.WantsOutputs;
409+
return processWantsOutputsAsync(wantsOutputs, receiver, recvPersister);
410+
}
411+
302412
function processMaybeInputsSeen(
303413
proposal: payjoin.MaybeInputsSeen,
304414
receiver: testUtils.RpcClient,
@@ -310,6 +420,19 @@ function processMaybeInputsSeen(
310420
return processOutputsUnknown(outputsUnknown, receiver, recvPersister);
311421
}
312422

423+
async function processMaybeInputsSeenAsync(
424+
proposal: payjoin.MaybeInputsSeen,
425+
receiver: testUtils.RpcClient,
426+
recvPersister: InMemoryReceiverPersister,
427+
): Promise<payjoin.PayjoinProposal> {
428+
const outputsUnknown = (
429+
await proposal.checkNoInputsSeenBeforeAsync(
430+
new CheckInputsNotSeenCallbackAsync(receiver),
431+
)
432+
).save(recvPersister) as payjoin.OutputsUnknown;
433+
return processOutputsUnknownAsync(outputsUnknown, receiver, recvPersister);
434+
}
435+
313436
function processMaybeInputsOwned(
314437
proposal: payjoin.MaybeInputsOwned,
315438
receiver: testUtils.RpcClient,
@@ -321,6 +444,23 @@ function processMaybeInputsOwned(
321444
return processMaybeInputsSeen(maybeInputsSeen, receiver, recvPersister);
322445
}
323446

447+
async function processMaybeInputsOwnedAsync(
448+
proposal: payjoin.MaybeInputsOwned,
449+
receiver: testUtils.RpcClient,
450+
recvPersister: InMemoryReceiverPersister,
451+
): Promise<payjoin.PayjoinProposal> {
452+
const maybeInputsSeen = (
453+
await proposal.checkInputsNotOwnedAsync(
454+
new IsScriptOwnedCallbackAsync(receiver),
455+
)
456+
).save(recvPersister) as payjoin.MaybeInputsSeen;
457+
return processMaybeInputsSeenAsync(
458+
maybeInputsSeen,
459+
receiver,
460+
recvPersister,
461+
);
462+
}
463+
324464
function processUncheckedProposal(
325465
proposal: payjoin.UncheckedOriginalPayload,
326466
receiver: testUtils.RpcClient,
@@ -335,10 +475,29 @@ function processUncheckedProposal(
335475
return processMaybeInputsOwned(maybeInputsOwned, receiver, recvPersister);
336476
}
337477

478+
async function processUncheckedProposalAsync(
479+
proposal: payjoin.UncheckedOriginalPayload,
480+
receiver: testUtils.RpcClient,
481+
recvPersister: InMemoryReceiverPersister,
482+
): Promise<payjoin.PayjoinProposal> {
483+
const maybeInputsOwned = (
484+
await proposal.checkBroadcastSuitabilityAsync(
485+
undefined,
486+
new MempoolAcceptanceCallbackAsync(receiver),
487+
)
488+
).save(recvPersister) as payjoin.MaybeInputsOwned;
489+
return processMaybeInputsOwnedAsync(
490+
maybeInputsOwned,
491+
receiver,
492+
recvPersister,
493+
);
494+
}
495+
338496
async function retrieveReceiverProposal(
339497
receiver: payjoin.Initialized,
340498
recvPersister: InMemoryReceiverPersister,
341499
ohttpRelay: string,
500+
useAsync: boolean,
342501
): Promise<payjoin.PayjoinProposal | null> {
343502
const request = receiver.createPollRequest(ohttpRelay);
344503
const response = await fetch(request.request.url, {
@@ -355,11 +514,17 @@ async function retrieveReceiverProposal(
355514
return null;
356515
} else if (res instanceof payjoin.InitializedTransitionOutcome.Progress) {
357516
const proposal = res.inner.inner as payjoin.UncheckedOriginalPayload;
358-
return processUncheckedProposal(
359-
proposal,
360-
recvPersister.connection!,
361-
recvPersister,
362-
);
517+
return useAsync
518+
? processUncheckedProposalAsync(
519+
proposal,
520+
recvPersister.connection!,
521+
recvPersister,
522+
)
523+
: processUncheckedProposal(
524+
proposal,
525+
recvPersister.connection!,
526+
recvPersister,
527+
);
363528
}
364529

365530
throw new Error(`Unknown initialized transition outcome`);
@@ -380,42 +545,63 @@ async function processReceiverProposal(
380545
receiverRpc: testUtils.RpcClient,
381546
recvPersister: InMemoryReceiverPersister,
382547
ohttpRelay: string,
548+
useAsync: boolean = false,
383549
): Promise<payjoin.PayjoinProposal | null> {
384550
if (receiver instanceof payjoin.Initialized) {
385-
const res = await retrieveReceiverProposal(
551+
return retrieveReceiverProposal(
386552
receiver,
387553
recvPersister,
388554
ohttpRelay,
555+
useAsync,
389556
);
390-
if (res === null) {
391-
return null;
392-
}
393-
return res;
394557
}
395-
396558
if (receiver instanceof payjoin.UncheckedOriginalPayload) {
397-
return processUncheckedProposal(receiver, receiverRpc, recvPersister);
559+
return useAsync
560+
? processUncheckedProposalAsync(
561+
receiver,
562+
receiverRpc,
563+
recvPersister,
564+
)
565+
: processUncheckedProposal(receiver, receiverRpc, recvPersister);
398566
}
399567
if (receiver instanceof payjoin.MaybeInputsOwned) {
400-
return processMaybeInputsOwned(receiver, receiverRpc, recvPersister);
568+
return useAsync
569+
? processMaybeInputsOwnedAsync(receiver, receiverRpc, recvPersister)
570+
: processMaybeInputsOwned(receiver, receiverRpc, recvPersister);
401571
}
402572
if (receiver instanceof payjoin.MaybeInputsSeen) {
403-
return processMaybeInputsSeen(receiver, receiverRpc, recvPersister);
573+
return useAsync
574+
? processMaybeInputsSeenAsync(receiver, receiverRpc, recvPersister)
575+
: processMaybeInputsSeen(receiver, receiverRpc, recvPersister);
404576
}
405577
if (receiver instanceof payjoin.OutputsUnknown) {
406-
return processOutputsUnknown(receiver, receiverRpc, recvPersister);
578+
return useAsync
579+
? processOutputsUnknownAsync(receiver, receiverRpc, recvPersister)
580+
: processOutputsUnknown(receiver, receiverRpc, recvPersister);
407581
}
408582
if (receiver instanceof payjoin.WantsOutputs) {
409-
return processWantsOutputs(receiver, receiverRpc, recvPersister);
583+
return useAsync
584+
? processWantsOutputsAsync(receiver, receiverRpc, recvPersister)
585+
: processWantsOutputs(receiver, receiverRpc, recvPersister);
410586
}
411587
if (receiver instanceof payjoin.WantsInputs) {
412-
return processWantsInputs(receiver, receiverRpc, recvPersister);
588+
return useAsync
589+
? processWantsInputsAsync(receiver, receiverRpc, recvPersister)
590+
: processWantsInputs(receiver, receiverRpc, recvPersister);
413591
}
414592
if (receiver instanceof payjoin.WantsFeeRange) {
415-
return processWantsFeeRange(receiver, receiverRpc, recvPersister);
593+
return useAsync
594+
? processWantsFeeRangeAsync(receiver, receiverRpc, recvPersister)
595+
: processWantsFeeRange(receiver, receiverRpc, recvPersister);
416596
}
417597
if (receiver instanceof payjoin.ProvisionalProposal) {
418-
return processProvisionalProposal(receiver, receiverRpc, recvPersister);
598+
return useAsync
599+
? processProvisionalProposalAsync(
600+
receiver,
601+
receiverRpc,
602+
recvPersister,
603+
)
604+
: processProvisionalProposal(receiver, receiverRpc, recvPersister);
419605
}
420606
if (receiver instanceof payjoin.PayjoinProposal) {
421607
return receiver;
@@ -524,7 +710,7 @@ function testFfiValidation(): void {
524710
}, /AmountOutOfRange/);
525711
}
526712

527-
async function testIntegrationV2ToV2(): Promise<void> {
713+
async function testIntegrationV2ToV2(useAsync: boolean = false): Promise<void> {
528714
const env = testUtils.initBitcoindSenderReceiver();
529715
const receiver = env.getReceiver();
530716
const sender = env.getSender();
@@ -557,6 +743,7 @@ async function testIntegrationV2ToV2(): Promise<void> {
557743
receiver,
558744
recvPersister,
559745
ohttpRelay,
746+
useAsync,
560747
);
561748
assert.strictEqual(
562749
processResponse,
@@ -586,6 +773,7 @@ async function testIntegrationV2ToV2(): Promise<void> {
586773
receiver,
587774
recvPersister,
588775
ohttpRelay,
776+
useAsync,
589777
);
590778
assert.notStrictEqual(
591779
payjoinProposal,
@@ -634,7 +822,6 @@ async function testIntegrationV2ToV2(): Promise<void> {
634822
}
635823
attempts += 1;
636824
if (attempts >= 3) {
637-
// Receiver not ready yet; mirror Dart/Python tolerance.
638825
return;
639826
}
640827
}
@@ -677,7 +864,8 @@ async function testIntegrationV2ToV2(): Promise<void> {
677864
async function runTests(): Promise<void> {
678865
await uniffiInitAsync();
679866
testFfiValidation();
680-
await testIntegrationV2ToV2();
867+
await testIntegrationV2ToV2(false); // sync callbacks
868+
await testIntegrationV2ToV2(true); // async callbacks
681869
}
682870

683871
runTests().catch((error: unknown) => {

0 commit comments

Comments
 (0)