ecash-agora 4.1.0 → 4.2.0

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.
@@ -0,0 +1,870 @@
1
+ // Copyright (c) 2026 The Bitcoin developers
2
+ // Distributed under the MIT software license, see the accompanying
3
+ // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
+
5
+ import { expect, use } from 'chai';
6
+ import chaiAsPromised from 'chai-as-promised';
7
+ import { ChronikClient } from 'chronik-client';
8
+ import {
9
+ ALP_TOKEN_TYPE_STANDARD,
10
+ DEFAULT_DUST_SATS,
11
+ Ecc,
12
+ SLP_NFT1_CHILD,
13
+ SLP_TOKEN_TYPE_FUNGIBLE,
14
+ SLP_TOKEN_TYPE_NFT1_CHILD,
15
+ SLP_TOKEN_TYPE_NFT1_GROUP,
16
+ Script,
17
+ fromHex,
18
+ payment,
19
+ shaRmd160,
20
+ slpSend,
21
+ } from 'ecash-lib';
22
+ import { TestRunner } from 'ecash-lib/dist/test/testRunner.js';
23
+ import { Wallet } from 'ecash-wallet';
24
+
25
+ import { Agora } from '../src/agora.js';
26
+ import { AgoraOneshot } from '../src/oneshot.js';
27
+
28
+ use(chaiAsPromised);
29
+
30
+ const NUM_COINS = 500;
31
+ const COIN_VALUE = 1100000000n;
32
+
33
+ const FINALIZATION_TIMEOUT_ERROR =
34
+ 'Error: Failed getting /broadcast-txs: 504: Transaction(s) failed to finalize within 1s';
35
+
36
+ const expectFinalizationTimedOut = async (
37
+ chronikClient: ChronikClient,
38
+ result: {
39
+ success: boolean;
40
+ broadcasted: string[];
41
+ unbroadcasted?: string[];
42
+ errors?: string[];
43
+ },
44
+ broadcastedCount: number,
45
+ ) => {
46
+ expect(result.success).to.equal(false);
47
+ expect(result.broadcasted.length).to.equal(broadcastedCount);
48
+ expect(result.unbroadcasted).to.deep.equal([]);
49
+ expect(result.errors).to.deep.equal([FINALIZATION_TIMEOUT_ERROR]);
50
+ for (const txid of result.broadcasted) {
51
+ const tx = await chronikClient.tx(txid);
52
+ expect(tx.isFinal).to.equal(false);
53
+ }
54
+ };
55
+
56
+ describe('finalizationTimeoutSecs', () => {
57
+ let runner: TestRunner;
58
+ let chronik: ChronikClient;
59
+ const ecc = new Ecc();
60
+
61
+ before(async () => {
62
+ runner = await TestRunner.setup('setup_scripts/ecash-agora_base');
63
+ chronik = runner.chronik;
64
+ await runner.setupCoins(NUM_COINS, COIN_VALUE);
65
+ });
66
+
67
+ after(() => {
68
+ runner.stop();
69
+ });
70
+
71
+ it('AgoraPartial.list() returns expected error when the ALP listing tx does not finalize within 1s', async () => {
72
+ const agora = new Agora(chronik);
73
+
74
+ const makerSk = fromHex('f6'.repeat(32));
75
+ const makerWallet = Wallet.fromSk(makerSk, chronik);
76
+ const makerPk = ecc.derivePubkey(makerSk);
77
+ const makerScript = Script.p2pkh(shaRmd160(makerPk));
78
+
79
+ await runner.sendToScript(50000n, makerScript);
80
+ await makerWallet.sync();
81
+
82
+ const genesisResult = await makerWallet
83
+ .action({
84
+ outputs: [
85
+ { sats: 0n },
86
+ {
87
+ sats: 546n,
88
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
89
+ script: makerWallet.script,
90
+ atoms: 1000000n,
91
+ },
92
+ {
93
+ sats: DEFAULT_DUST_SATS,
94
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
95
+ script: makerWallet.script,
96
+ isMintBaton: true,
97
+ atoms: 0n,
98
+ },
99
+ ],
100
+ tokenActions: [
101
+ {
102
+ type: 'GENESIS',
103
+ tokenType: ALP_TOKEN_TYPE_STANDARD,
104
+ genesisInfo: {
105
+ tokenTicker: 'FIN ALP',
106
+ decimals: 4,
107
+ },
108
+ },
109
+ ],
110
+ })
111
+ .build()
112
+ .broadcast();
113
+ const tokenId = genesisResult.broadcasted[0];
114
+ await makerWallet.sync();
115
+
116
+ const agoraPartial = await agora.selectParams(
117
+ {
118
+ offeredAtoms: 100000n,
119
+ priceNanoSatsPerAtom: 1_000_000_000n,
120
+ minAcceptedAtoms: 546n,
121
+ makerPk,
122
+ tokenId,
123
+ tokenType: ALP_TOKEN_TYPE_STANDARD.number,
124
+ tokenProtocol: 'ALP',
125
+ },
126
+ 64n,
127
+ );
128
+
129
+ const listResult = await agoraPartial.list({
130
+ wallet: makerWallet,
131
+ finalizationTimeoutSecs: 1,
132
+ });
133
+
134
+ await expectFinalizationTimedOut(chronik, listResult, 1);
135
+ });
136
+
137
+ it('AgoraPartial.list() returns expected error when SLP listing txs do not finalize within 1s', async () => {
138
+ const agora = new Agora(chronik);
139
+
140
+ const makerSk = fromHex('f7'.repeat(32));
141
+ const makerWallet = Wallet.fromSk(makerSk, chronik);
142
+ const makerPk = ecc.derivePubkey(makerSk);
143
+ const makerScript = Script.p2pkh(shaRmd160(makerPk));
144
+
145
+ await runner.sendToScript(50000n, makerScript);
146
+ await makerWallet.sync();
147
+
148
+ const genesisResult = await makerWallet
149
+ .action({
150
+ outputs: [
151
+ { sats: 0n },
152
+ {
153
+ sats: 20000n,
154
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
155
+ script: makerWallet.script,
156
+ atoms: 1000000n,
157
+ },
158
+ ],
159
+ tokenActions: [
160
+ {
161
+ type: 'GENESIS',
162
+ tokenType: SLP_TOKEN_TYPE_FUNGIBLE,
163
+ genesisInfo: {
164
+ tokenTicker: 'FIN SLP',
165
+ decimals: 4,
166
+ },
167
+ },
168
+ ],
169
+ })
170
+ .build()
171
+ .broadcast();
172
+ const tokenId = genesisResult.broadcasted[0];
173
+ await makerWallet.sync();
174
+
175
+ const agoraPartial = await agora.selectParams(
176
+ {
177
+ offeredAtoms: 100000n,
178
+ priceNanoSatsPerAtom: 1_000_000_000n,
179
+ minAcceptedAtoms: 546n,
180
+ makerPk,
181
+ tokenId,
182
+ tokenType: SLP_TOKEN_TYPE_FUNGIBLE.number,
183
+ tokenProtocol: 'SLP',
184
+ },
185
+ 64n,
186
+ );
187
+
188
+ const listResult = await agoraPartial.list({
189
+ wallet: makerWallet,
190
+ finalizationTimeoutSecs: 1,
191
+ });
192
+
193
+ await expectFinalizationTimedOut(chronik, listResult, 2);
194
+ });
195
+
196
+ it('AgoraOneshot.list() returns expected error when listing txs do not finalize within 1s', async () => {
197
+ const sellerSk = fromHex('f8'.repeat(32));
198
+ const sellerWallet = Wallet.fromSk(sellerSk, chronik);
199
+ const sellerPk = ecc.derivePubkey(sellerSk);
200
+ const sellerP2pkh = Script.p2pkh(shaRmd160(sellerPk));
201
+
202
+ await runner.sendToScript(50000n, sellerP2pkh);
203
+ await sellerWallet.sync();
204
+
205
+ const groupGenesisResult = await sellerWallet
206
+ .action({
207
+ outputs: [
208
+ { sats: 0n },
209
+ {
210
+ sats: 10000n,
211
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
212
+ script: sellerWallet.script,
213
+ atoms: 1n,
214
+ },
215
+ ],
216
+ tokenActions: [
217
+ {
218
+ type: 'GENESIS',
219
+ tokenType: SLP_TOKEN_TYPE_NFT1_GROUP,
220
+ genesisInfo: {
221
+ tokenTicker: 'FIN GRP',
222
+ decimals: 0,
223
+ },
224
+ },
225
+ ],
226
+ })
227
+ .build()
228
+ .broadcast();
229
+ const groupTokenId = groupGenesisResult.broadcasted[0];
230
+
231
+ await sellerWallet.sync();
232
+ const childGenesisResult = await sellerWallet
233
+ .action({
234
+ outputs: [
235
+ { sats: 0n },
236
+ {
237
+ sats: 8000n,
238
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
239
+ script: sellerWallet.script,
240
+ atoms: 1n,
241
+ },
242
+ ],
243
+ tokenActions: [
244
+ {
245
+ type: 'GENESIS',
246
+ tokenType: SLP_TOKEN_TYPE_NFT1_CHILD,
247
+ genesisInfo: {
248
+ tokenTicker: 'FIN NFT',
249
+ decimals: 0,
250
+ },
251
+ groupTokenId,
252
+ },
253
+ ],
254
+ })
255
+ .build()
256
+ .broadcast();
257
+ const childTokenId = childGenesisResult.broadcasted[0];
258
+ await sellerWallet.sync();
259
+
260
+ const agoraOneshot = new AgoraOneshot({
261
+ enforcedOutputs: [
262
+ {
263
+ sats: 0n,
264
+ script: slpSend(childTokenId, SLP_NFT1_CHILD, [0n, 1n]),
265
+ },
266
+ {
267
+ sats: 30000n,
268
+ script: sellerP2pkh,
269
+ },
270
+ ],
271
+ cancelPk: sellerPk,
272
+ });
273
+
274
+ const listResult = await agoraOneshot.list({
275
+ wallet: sellerWallet,
276
+ tokenId: childTokenId,
277
+ tokenType: SLP_TOKEN_TYPE_NFT1_CHILD,
278
+ finalizationTimeoutSecs: 1,
279
+ });
280
+
281
+ await expectFinalizationTimedOut(chronik, listResult, 2);
282
+ });
283
+
284
+ it('AgoraOffer.take() returns expected error when ALP partial accept tx does not finalize within 1s', async () => {
285
+ const agora = new Agora(chronik);
286
+
287
+ const makerSk = fromHex('a1'.repeat(32));
288
+ const makerWallet = Wallet.fromSk(makerSk, chronik);
289
+ const makerPk = ecc.derivePubkey(makerSk);
290
+ const makerScript = Script.p2pkh(shaRmd160(makerPk));
291
+
292
+ const takerSk = fromHex('a2'.repeat(32));
293
+ const takerWallet = Wallet.fromSk(takerSk, chronik);
294
+ const takerPk = ecc.derivePubkey(takerSk);
295
+
296
+ await runner.sendToScript(50000n, makerScript);
297
+ await makerWallet.sync();
298
+
299
+ const genesisResult = await makerWallet
300
+ .action({
301
+ outputs: [
302
+ { sats: 0n },
303
+ {
304
+ sats: 546n,
305
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
306
+ script: makerWallet.script,
307
+ atoms: 1000000n,
308
+ },
309
+ {
310
+ sats: DEFAULT_DUST_SATS,
311
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
312
+ script: makerWallet.script,
313
+ isMintBaton: true,
314
+ atoms: 0n,
315
+ },
316
+ ],
317
+ tokenActions: [
318
+ {
319
+ type: 'GENESIS',
320
+ tokenType: ALP_TOKEN_TYPE_STANDARD,
321
+ genesisInfo: {
322
+ tokenTicker: 'FIN TAKE ALP',
323
+ decimals: 4,
324
+ },
325
+ },
326
+ ],
327
+ })
328
+ .build()
329
+ .broadcast();
330
+ const tokenId = genesisResult.broadcasted[0];
331
+ await makerWallet.sync();
332
+
333
+ const agoraPartial = await agora.selectParams(
334
+ {
335
+ offeredAtoms: 100000n,
336
+ priceNanoSatsPerAtom: 1_000_000_000n,
337
+ minAcceptedAtoms: 546n,
338
+ makerPk,
339
+ tokenId,
340
+ tokenType: ALP_TOKEN_TYPE_STANDARD.number,
341
+ tokenProtocol: 'ALP',
342
+ },
343
+ 64n,
344
+ );
345
+
346
+ const listResult = await agoraPartial.list({ wallet: makerWallet });
347
+ expect(listResult.success).to.equal(true);
348
+
349
+ const offers = await agora.activeOffersByTokenId(tokenId);
350
+ expect(offers.length).to.equal(1);
351
+
352
+ await runner.sendToScript(200000n, Script.p2pkh(shaRmd160(takerPk)));
353
+ await takerWallet.sync();
354
+
355
+ const takeResult = await offers[0].take({
356
+ wallet: takerWallet,
357
+ covenantSk: takerSk,
358
+ covenantPk: takerPk,
359
+ acceptedAtoms: 50000n,
360
+ finalizationTimeoutSecs: 1,
361
+ });
362
+
363
+ await expectFinalizationTimedOut(chronik, takeResult, 1);
364
+ });
365
+
366
+ it('AgoraOffer.take() returns expected error when SLP partial accept tx does not finalize within 1s', async () => {
367
+ const agora = new Agora(chronik);
368
+
369
+ const makerSk = fromHex('a3'.repeat(32));
370
+ const makerWallet = Wallet.fromSk(makerSk, chronik);
371
+ const makerPk = ecc.derivePubkey(makerSk);
372
+ const makerScript = Script.p2pkh(shaRmd160(makerPk));
373
+
374
+ const takerSk = fromHex('a4'.repeat(32));
375
+ const takerWallet = Wallet.fromSk(takerSk, chronik);
376
+ const takerPk = ecc.derivePubkey(takerSk);
377
+
378
+ await runner.sendToScript(50000n, makerScript);
379
+ await makerWallet.sync();
380
+
381
+ const genesisResult = await makerWallet
382
+ .action({
383
+ outputs: [
384
+ { sats: 0n },
385
+ {
386
+ sats: 20000n,
387
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
388
+ script: makerWallet.script,
389
+ atoms: 1000000n,
390
+ },
391
+ ],
392
+ tokenActions: [
393
+ {
394
+ type: 'GENESIS',
395
+ tokenType: SLP_TOKEN_TYPE_FUNGIBLE,
396
+ genesisInfo: {
397
+ tokenTicker: 'FIN TAKE SLP',
398
+ decimals: 4,
399
+ },
400
+ },
401
+ ],
402
+ })
403
+ .build()
404
+ .broadcast();
405
+ const tokenId = genesisResult.broadcasted[0];
406
+ await makerWallet.sync();
407
+
408
+ const agoraPartial = await agora.selectParams(
409
+ {
410
+ offeredAtoms: 100000n,
411
+ priceNanoSatsPerAtom: 1_000_000_000n,
412
+ minAcceptedAtoms: 546n,
413
+ makerPk,
414
+ tokenId,
415
+ tokenType: SLP_TOKEN_TYPE_FUNGIBLE.number,
416
+ tokenProtocol: 'SLP',
417
+ },
418
+ 64n,
419
+ );
420
+
421
+ const listResult = await agoraPartial.list({ wallet: makerWallet });
422
+ expect(listResult.success).to.equal(true);
423
+
424
+ const offers = await agora.activeOffersByTokenId(tokenId);
425
+ expect(offers.length).to.equal(1);
426
+
427
+ await runner.sendToScript(200000n, Script.p2pkh(shaRmd160(takerPk)));
428
+ await takerWallet.sync();
429
+
430
+ const takeResult = await offers[0].take({
431
+ wallet: takerWallet,
432
+ covenantSk: takerSk,
433
+ covenantPk: takerPk,
434
+ acceptedAtoms: 50000n,
435
+ finalizationTimeoutSecs: 1,
436
+ });
437
+
438
+ await expectFinalizationTimedOut(chronik, takeResult, 1);
439
+ });
440
+
441
+ it('AgoraOffer.take() returns expected error when oneshot accept tx does not finalize within 1s', async () => {
442
+ const agora = new Agora(chronik);
443
+
444
+ const sellerSk = fromHex('a5'.repeat(32));
445
+ const sellerWallet = Wallet.fromSk(sellerSk, chronik);
446
+ const sellerPk = ecc.derivePubkey(sellerSk);
447
+ const sellerP2pkh = Script.p2pkh(shaRmd160(sellerPk));
448
+
449
+ const buyerSk = fromHex('a6'.repeat(32));
450
+ const buyerWallet = Wallet.fromSk(buyerSk, chronik);
451
+ const buyerPk = ecc.derivePubkey(buyerSk);
452
+
453
+ await runner.sendToScript(50000n, sellerP2pkh);
454
+ await sellerWallet.sync();
455
+
456
+ const groupGenesisResult = await sellerWallet
457
+ .action({
458
+ outputs: [
459
+ { sats: 0n },
460
+ {
461
+ sats: 10000n,
462
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
463
+ script: sellerWallet.script,
464
+ atoms: 1n,
465
+ },
466
+ ],
467
+ tokenActions: [
468
+ {
469
+ type: 'GENESIS',
470
+ tokenType: SLP_TOKEN_TYPE_NFT1_GROUP,
471
+ genesisInfo: {
472
+ tokenTicker: 'FIN TAKE GRP',
473
+ decimals: 0,
474
+ },
475
+ },
476
+ ],
477
+ })
478
+ .build()
479
+ .broadcast();
480
+ const groupTokenId = groupGenesisResult.broadcasted[0];
481
+
482
+ await sellerWallet.sync();
483
+ const childGenesisResult = await sellerWallet
484
+ .action({
485
+ outputs: [
486
+ { sats: 0n },
487
+ {
488
+ sats: 8000n,
489
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
490
+ script: sellerWallet.script,
491
+ atoms: 1n,
492
+ },
493
+ ],
494
+ tokenActions: [
495
+ {
496
+ type: 'GENESIS',
497
+ tokenType: SLP_TOKEN_TYPE_NFT1_CHILD,
498
+ genesisInfo: {
499
+ tokenTicker: 'FIN TAKE NFT',
500
+ decimals: 0,
501
+ },
502
+ groupTokenId,
503
+ },
504
+ ],
505
+ })
506
+ .build()
507
+ .broadcast();
508
+ const childTokenId = childGenesisResult.broadcasted[0];
509
+ await sellerWallet.sync();
510
+
511
+ const agoraOneshot = new AgoraOneshot({
512
+ enforcedOutputs: [
513
+ {
514
+ sats: 0n,
515
+ script: slpSend(childTokenId, SLP_NFT1_CHILD, [0n, 1n]),
516
+ },
517
+ {
518
+ sats: 30000n,
519
+ script: sellerP2pkh,
520
+ },
521
+ ],
522
+ cancelPk: sellerPk,
523
+ });
524
+
525
+ const listResult = await agoraOneshot.list({
526
+ wallet: sellerWallet,
527
+ tokenId: childTokenId,
528
+ tokenType: SLP_TOKEN_TYPE_NFT1_CHILD,
529
+ });
530
+ expect(listResult.success).to.equal(true);
531
+
532
+ const offers = await agora.activeOffersByTokenId(childTokenId);
533
+ expect(offers.length).to.equal(1);
534
+
535
+ await runner.sendToScript(50000n, Script.p2pkh(shaRmd160(buyerPk)));
536
+ await buyerWallet.sync();
537
+
538
+ const takeResult = await offers[0].take({
539
+ wallet: buyerWallet,
540
+ covenantSk: buyerSk,
541
+ covenantPk: buyerPk,
542
+ finalizationTimeoutSecs: 1,
543
+ });
544
+
545
+ await expectFinalizationTimedOut(chronik, takeResult, 1);
546
+ });
547
+
548
+ it('AgoraOffer.cancel() returns expected error when ALP partial cancel tx does not finalize within 1s', async () => {
549
+ const agora = new Agora(chronik);
550
+
551
+ const makerSk = fromHex('b1'.repeat(32));
552
+ const makerWallet = Wallet.fromSk(makerSk, chronik);
553
+ const makerPk = ecc.derivePubkey(makerSk);
554
+ const makerScript = Script.p2pkh(shaRmd160(makerPk));
555
+
556
+ await runner.sendToScript(100000n, makerScript);
557
+ await makerWallet.sync();
558
+
559
+ const genesisResult = await makerWallet
560
+ .action({
561
+ outputs: [
562
+ { sats: 0n },
563
+ {
564
+ sats: 546n,
565
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
566
+ script: makerWallet.script,
567
+ atoms: 1000000n,
568
+ },
569
+ {
570
+ sats: DEFAULT_DUST_SATS,
571
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
572
+ script: makerWallet.script,
573
+ isMintBaton: true,
574
+ atoms: 0n,
575
+ },
576
+ ],
577
+ tokenActions: [
578
+ {
579
+ type: 'GENESIS',
580
+ tokenType: ALP_TOKEN_TYPE_STANDARD,
581
+ genesisInfo: {
582
+ tokenTicker: 'FIN CAN ALP',
583
+ decimals: 4,
584
+ },
585
+ },
586
+ ],
587
+ })
588
+ .build()
589
+ .broadcast();
590
+ const tokenId = genesisResult.broadcasted[0];
591
+ await makerWallet.sync();
592
+
593
+ const agoraPartial = await agora.selectParams(
594
+ {
595
+ offeredAtoms: 100000n,
596
+ priceNanoSatsPerAtom: 1_000_000_000n,
597
+ minAcceptedAtoms: 546n,
598
+ makerPk,
599
+ tokenId,
600
+ tokenType: ALP_TOKEN_TYPE_STANDARD.number,
601
+ tokenProtocol: 'ALP',
602
+ },
603
+ 64n,
604
+ );
605
+
606
+ const listResult = await agoraPartial.list({ wallet: makerWallet });
607
+ expect(listResult.success).to.equal(true);
608
+
609
+ const offers = await agora.activeOffersByTokenId(tokenId);
610
+ expect(offers.length).to.equal(1);
611
+
612
+ const cancelResult = await offers[0].cancel({
613
+ wallet: makerWallet,
614
+ finalizationTimeoutSecs: 1,
615
+ });
616
+
617
+ await expectFinalizationTimedOut(chronik, cancelResult, 1);
618
+ });
619
+
620
+ it('AgoraOffer.cancel() returns expected error when SLP partial cancel tx does not finalize within 1s', async () => {
621
+ const agora = new Agora(chronik);
622
+
623
+ const makerSk = fromHex('b2'.repeat(32));
624
+ const makerWallet = Wallet.fromSk(makerSk, chronik);
625
+ const makerPk = ecc.derivePubkey(makerSk);
626
+ const makerScript = Script.p2pkh(shaRmd160(makerPk));
627
+
628
+ await runner.sendToScript(100000n, makerScript);
629
+ await makerWallet.sync();
630
+
631
+ const genesisResult = await makerWallet
632
+ .action({
633
+ outputs: [
634
+ { sats: 0n },
635
+ {
636
+ sats: 20000n,
637
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
638
+ script: makerWallet.script,
639
+ atoms: 1000000n,
640
+ },
641
+ ],
642
+ tokenActions: [
643
+ {
644
+ type: 'GENESIS',
645
+ tokenType: SLP_TOKEN_TYPE_FUNGIBLE,
646
+ genesisInfo: {
647
+ tokenTicker: 'FIN CAN SLP',
648
+ decimals: 4,
649
+ },
650
+ },
651
+ ],
652
+ })
653
+ .build()
654
+ .broadcast();
655
+ const tokenId = genesisResult.broadcasted[0];
656
+ await makerWallet.sync();
657
+
658
+ const agoraPartial = await agora.selectParams(
659
+ {
660
+ offeredAtoms: 100000n,
661
+ priceNanoSatsPerAtom: 1_000_000_000n,
662
+ minAcceptedAtoms: 546n,
663
+ makerPk,
664
+ tokenId,
665
+ tokenType: SLP_TOKEN_TYPE_FUNGIBLE.number,
666
+ tokenProtocol: 'SLP',
667
+ },
668
+ 64n,
669
+ );
670
+
671
+ const listResult = await agoraPartial.list({ wallet: makerWallet });
672
+ expect(listResult.success).to.equal(true);
673
+
674
+ const offers = await agora.activeOffersByTokenId(tokenId);
675
+ expect(offers.length).to.equal(1);
676
+
677
+ const cancelResult = await offers[0].cancel({
678
+ wallet: makerWallet,
679
+ finalizationTimeoutSecs: 1,
680
+ });
681
+
682
+ await expectFinalizationTimedOut(chronik, cancelResult, 1);
683
+ });
684
+
685
+ it('AgoraOffer.cancel() returns expected error when oneshot cancel tx does not finalize within 1s', async () => {
686
+ const agora = new Agora(chronik);
687
+
688
+ const sellerSk = fromHex('b3'.repeat(32));
689
+ const sellerWallet = Wallet.fromSk(sellerSk, chronik);
690
+ const sellerPk = ecc.derivePubkey(sellerSk);
691
+ const sellerP2pkh = Script.p2pkh(shaRmd160(sellerPk));
692
+
693
+ await runner.sendToScript(100000n, sellerP2pkh);
694
+ await sellerWallet.sync();
695
+
696
+ const groupGenesisResult = await sellerWallet
697
+ .action({
698
+ outputs: [
699
+ { sats: 0n },
700
+ {
701
+ sats: 10000n,
702
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
703
+ script: sellerWallet.script,
704
+ atoms: 1n,
705
+ },
706
+ ],
707
+ tokenActions: [
708
+ {
709
+ type: 'GENESIS',
710
+ tokenType: SLP_TOKEN_TYPE_NFT1_GROUP,
711
+ genesisInfo: {
712
+ tokenTicker: 'FIN CAN GRP',
713
+ decimals: 0,
714
+ },
715
+ },
716
+ ],
717
+ })
718
+ .build()
719
+ .broadcast();
720
+ const groupTokenId = groupGenesisResult.broadcasted[0];
721
+
722
+ await sellerWallet.sync();
723
+ const childGenesisResult = await sellerWallet
724
+ .action({
725
+ outputs: [
726
+ { sats: 0n },
727
+ {
728
+ sats: 8000n,
729
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
730
+ script: sellerWallet.script,
731
+ atoms: 1n,
732
+ },
733
+ ],
734
+ tokenActions: [
735
+ {
736
+ type: 'GENESIS',
737
+ tokenType: SLP_TOKEN_TYPE_NFT1_CHILD,
738
+ genesisInfo: {
739
+ tokenTicker: 'FIN CAN NFT',
740
+ decimals: 0,
741
+ },
742
+ groupTokenId,
743
+ },
744
+ ],
745
+ })
746
+ .build()
747
+ .broadcast();
748
+ const childTokenId = childGenesisResult.broadcasted[0];
749
+ await sellerWallet.sync();
750
+
751
+ const agoraOneshot = new AgoraOneshot({
752
+ enforcedOutputs: [
753
+ {
754
+ sats: 0n,
755
+ script: slpSend(childTokenId, SLP_NFT1_CHILD, [0n, 1n]),
756
+ },
757
+ {
758
+ sats: 30000n,
759
+ script: sellerP2pkh,
760
+ },
761
+ ],
762
+ cancelPk: sellerPk,
763
+ });
764
+
765
+ const listResult = await agoraOneshot.list({
766
+ wallet: sellerWallet,
767
+ tokenId: childTokenId,
768
+ tokenType: SLP_TOKEN_TYPE_NFT1_CHILD,
769
+ });
770
+ expect(listResult.success).to.equal(true);
771
+
772
+ const offers = await agora.activeOffersByTokenId(childTokenId);
773
+ expect(offers.length).to.equal(1);
774
+
775
+ const cancelResult = await offers[0].cancel({
776
+ wallet: sellerWallet,
777
+ finalizationTimeoutSecs: 1,
778
+ });
779
+
780
+ await expectFinalizationTimedOut(chronik, cancelResult, 1);
781
+ });
782
+
783
+ it('AgoraOffer.relist() returns expected error when ALP relist tx does not finalize within 1s', async () => {
784
+ const agora = new Agora(chronik);
785
+
786
+ const makerSk = fromHex('c1'.repeat(32));
787
+ const makerWallet = Wallet.fromSk(makerSk, chronik);
788
+ const makerPk = ecc.derivePubkey(makerSk);
789
+ const makerScript = Script.p2pkh(shaRmd160(makerPk));
790
+
791
+ await runner.sendToScript(450000n, makerScript);
792
+ await makerWallet.sync();
793
+
794
+ const genesisResult = await makerWallet
795
+ .action({
796
+ outputs: [
797
+ { sats: 0n },
798
+ {
799
+ sats: 546n,
800
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
801
+ script: makerWallet.script,
802
+ atoms: 1000000n,
803
+ },
804
+ {
805
+ sats: DEFAULT_DUST_SATS,
806
+ tokenId: payment.GENESIS_TOKEN_ID_PLACEHOLDER,
807
+ script: makerWallet.script,
808
+ isMintBaton: true,
809
+ atoms: 0n,
810
+ },
811
+ ],
812
+ tokenActions: [
813
+ {
814
+ type: 'GENESIS',
815
+ tokenType: ALP_TOKEN_TYPE_STANDARD,
816
+ genesisInfo: {
817
+ tokenTicker: 'FIN RELIST',
818
+ decimals: 4,
819
+ },
820
+ },
821
+ ],
822
+ })
823
+ .build()
824
+ .broadcast();
825
+ const tokenId = genesisResult.broadcasted[0];
826
+ await makerWallet.sync();
827
+
828
+ const agoraPartialInitial = await agora.selectParams(
829
+ {
830
+ offeredAtoms: 100000n,
831
+ priceNanoSatsPerAtom: 1_000_000_000n,
832
+ minAcceptedAtoms: 546n,
833
+ makerPk,
834
+ tokenId,
835
+ tokenType: ALP_TOKEN_TYPE_STANDARD.number,
836
+ tokenProtocol: 'ALP',
837
+ },
838
+ 64n,
839
+ );
840
+
841
+ const listResult = await agoraPartialInitial.list({
842
+ wallet: makerWallet,
843
+ });
844
+ expect(listResult.success).to.equal(true);
845
+
846
+ const offers = await agora.activeOffersByTokenId(tokenId);
847
+ expect(offers.length).to.equal(1);
848
+
849
+ const updatedPartial = await agora.selectParams(
850
+ {
851
+ offeredAtoms: 80000n,
852
+ priceNanoSatsPerAtom: 2_000_000_000n,
853
+ minAcceptedAtoms: 546n,
854
+ makerPk,
855
+ tokenId,
856
+ tokenType: ALP_TOKEN_TYPE_STANDARD.number,
857
+ tokenProtocol: 'ALP',
858
+ },
859
+ 64n,
860
+ );
861
+
862
+ const relistResult = await offers[0].relist({
863
+ wallet: makerWallet,
864
+ updatedPartial,
865
+ finalizationTimeoutSecs: 1,
866
+ });
867
+
868
+ await expectFinalizationTimedOut(chronik, relistResult, 1);
869
+ });
870
+ });