@hyperscale0/hsx 2.1.1 → 2.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.
- package/CHANGELOG.md +28 -7
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +39 -5
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +0 -1
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +15 -1
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +3 -3
- package/dist/src/cost.d.ts.map +1 -1
- package/dist/src/cost.js +22 -12
- package/dist/src/cost.js.map +1 -1
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +10 -14
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +20 -84
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/typecheck.d.ts.map +1 -1
- package/dist/src/typecheck.js +171 -39
- package/dist/src/typecheck.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/README.md +4 -2
- package/docs/guide/01-first-program.md +1 -1
- package/docs/guide/06-schedules.md +5 -1
- package/docs/guide/08-writing-a-module.md +48 -0
- package/docs/llms-full.txt +1928 -421
- package/docs/llms.txt +2 -3
- package/docs/reference/cli.md +6 -5
- package/docs/reference/diagnostics.md +9 -9
- package/docs/reference/grammar.md +2 -3
- package/docs/reference/std/advance.md +94 -17
- package/docs/reference/std/cancellable_booking.md +138 -17
- package/docs/reference/std/captured_payment.md +96 -24
- package/docs/reference/std/conditional_disbursement.md +82 -14
- package/docs/reference/std/credit_facility.md +89 -16
- package/docs/reference/std/held_payment.md +155 -55
- package/docs/reference/std/instant_transfer.md +79 -12
- package/docs/reference/std/metered.md +71 -9
- package/docs/reference/std/pooled_split.md +76 -7
- package/docs/reference/std/premium_forward.md +100 -20
- package/docs/reference/std/reconciled_payout.md +84 -14
- package/docs/reference/std/rotating_pool.md +112 -24
- package/docs/reference/std/scheduled.md +117 -32
- package/docs/reference/std/security_deposit.md +119 -27
- package/docs/reference/std/settlement_batch.md +105 -24
- package/docs/reference/std/swap.md +113 -26
- package/docs/reference/std/threshold_pool.md +120 -29
- package/docs/reference/std/weighted_distribution.md +117 -21
- package/docs/reference/types.md +15 -15
- package/docs/reference/udl-output.md +6 -6
- package/examples/01-first-program/README.md +1 -1
- package/examples/{02-imports-and-archetypes → 02-imports-and-modules}/README.md +1 -1
- package/examples/{02-imports-and-archetypes → 02-imports-and-modules}/photo-booth.hsx +1 -1
- package/examples/04-complete-product/README.md +1 -1
- package/examples/05-authored-instrument/README.md +5 -0
- package/examples/05-authored-instrument/payment.hsx +37 -0
- package/examples/05-watch-club/watch-club.hsx +0 -1
- package/examples/README.md +1 -2
- package/examples/advance/advance.udl +29 -6
- package/examples/cancellable_booking/cancellable_booking.udl +19 -0
- package/examples/captured_payment/captured_payment.hsx +0 -4
- package/examples/captured_payment/captured_payment.udl +6 -0
- package/examples/conditional_disbursement/conditional_disbursement.hsx +0 -2
- package/examples/conditional_disbursement/conditional_disbursement.udl +4 -0
- package/examples/cost-table.json +136 -8
- package/examples/credit_facility/credit_facility.hsx +0 -3
- package/examples/credit_facility/credit_facility.udl +17 -1
- package/examples/held_payment/held_payment.udl +16 -0
- package/examples/instant_transfer/instant_transfer.udl +6 -0
- package/examples/metered/metered.udl +5 -1
- package/examples/pooled_split/pooled_split.udl +4 -0
- package/examples/premium_forward/premium_forward.udl +4 -0
- package/examples/reconciled_payout/reconciled_payout.udl +7 -0
- package/examples/rotating_pool/rotating_pool.udl +8 -0
- package/examples/scheduled/scheduled.udl +18 -3
- package/examples/security_deposit/security_deposit.udl +13 -0
- package/examples/settlement_batch/settlement_batch.udl +4 -0
- package/examples/swap/swap.udl +10 -0
- package/examples/threshold_pool/threshold_pool.udl +11 -0
- package/examples/weighted_distribution/weighted_distribution.udl +9 -0
- package/package.json +10 -10
- package/skills/hsx/SKILL.md +2 -36
- package/src/cli.ts +41 -5
- package/src/compile.ts +14 -6
- package/src/cost.ts +13 -16
- package/src/parse.ts +15 -10
- package/src/std-bundle.ts +21 -88
- package/src/typecheck.ts +207 -34
- package/src/version.ts +1 -1
- package/std/SEMANTICS.md +33 -128
- package/std/money_flows/advance.hsx +39 -26
- package/std/money_flows/cancellable_booking.hsx +275 -8
- package/std/money_flows/captured_payment.hsx +1 -9
- package/std/money_flows/conditional_disbursement.hsx +0 -6
- package/std/money_flows/credit_facility.hsx +0 -9
- package/std/money_flows/held_payment.hsx +39 -1
- package/std/money_flows/index.hsx +2 -1
- package/std/money_flows/metered.hsx +1 -5
- package/std/money_flows/scheduled.hsx +29 -119
- package/std/money_flows/threshold_pool.hsx +40 -5
- package/std/money_flows/weighted_distribution.hsx +47 -5
- package/docs/reference/std/recurring_collection.md +0 -25
- package/examples/recurring_collection/README.md +0 -3
- package/examples/recurring_collection/recurring_collection.hsx +0 -21
- package/examples/recurring_collection/recurring_collection.udl +0 -1135
- package/std/money_flows/recurring_collection.hsx +0 -72
|
@@ -42,6 +42,8 @@ module std.money_flows.held_payment
|
|
|
42
42
|
// A zero charge keeps the flow with a zero fee.
|
|
43
43
|
// - `cancel_offer_life`: ISO 8601 duration a cancellation quote stays open, required with `cancel_charge_bps`.
|
|
44
44
|
//
|
|
45
|
+
// - `private_actions`: Suppress automatic aliases. Publish chosen actions with `expose`.
|
|
46
|
+
//
|
|
45
47
|
// ### Decision ports
|
|
46
48
|
// - `release`: Port deciding release authorization, answered by allowed parties declared in the port.
|
|
47
49
|
//
|
|
@@ -61,7 +63,7 @@ module std.money_flows.held_payment
|
|
|
61
63
|
// }
|
|
62
64
|
// port confirm_delivery { allowed: [buyer] }
|
|
63
65
|
// ```
|
|
64
|
-
export instrument held_payment<C>(payer: party, payee: party, amount: money<C>, release: condition, fees: optional<block>, on_cancel: optional<block>, derived_amount: optional<block>, release_to: optional<party>, whole_amount: optional<block>, release_action: optional<text>, whole_fee: optional<money<C>>, reference: optional<text>, upstream: optional<ref>, id_prefix_override: optional<text>, cancel_charge_bps: optional<integer>, cancel_offer_life: optional<text>) {
|
|
66
|
+
export instrument held_payment<C>(payer: party, payee: party, amount: money<C>, release: condition, fees: optional<block>, on_cancel: optional<block>, derived_amount: optional<block>, release_to: optional<party>, whole_amount: optional<block>, release_action: optional<text>, whole_fee: optional<money<C>>, reference: optional<text>, upstream: optional<ref>, id_prefix_override: optional<text>, cancel_charge_bps: optional<integer>, cancel_offer_life: optional<text>, private_actions: optional<boolean>) {
|
|
65
67
|
let(release_name): release;
|
|
66
68
|
let(release_actor_text): if_eq(len(release_allowed), 1, words(at(release_allowed, 1)), concat("a party allowed by ", release));
|
|
67
69
|
let(payer_account_field): camel(concat(payer, "_account_id"));
|
|
@@ -300,6 +302,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
300
302
|
when(payee_fee) {
|
|
301
303
|
when(on_cancel) {
|
|
302
304
|
action fund_piece_2 {
|
|
305
|
+
when(private_actions) { public: none; }
|
|
303
306
|
let(metadata_instrument): "metadata.instrumentId";
|
|
304
307
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
305
308
|
let(metadata_phase): "metadata.phase";
|
|
@@ -310,6 +313,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
310
313
|
steps: [];
|
|
311
314
|
}
|
|
312
315
|
action fund_piece_3 {
|
|
316
|
+
when(private_actions) { public: none; }
|
|
313
317
|
let(metadata_instrument): "metadata.instrumentId";
|
|
314
318
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
315
319
|
let(metadata_phase): "metadata.phase";
|
|
@@ -321,6 +325,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
321
325
|
}
|
|
322
326
|
when_eq(payer_fee_kind, percent) {
|
|
323
327
|
action collect_service_fee {
|
|
328
|
+
when(private_actions) { public: none; }
|
|
324
329
|
let(metadata_instrument): "metadata.instrumentId";
|
|
325
330
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
326
331
|
let(metadata_phase): "metadata.phase";
|
|
@@ -331,6 +336,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
331
336
|
}
|
|
332
337
|
}
|
|
333
338
|
action release_piece_2 {
|
|
339
|
+
when(private_actions) { public: none; }
|
|
334
340
|
let(metadata_instrument): "metadata.instrumentId";
|
|
335
341
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
336
342
|
let(metadata_phase): "metadata.phase";
|
|
@@ -342,6 +348,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
342
348
|
steps: [];
|
|
343
349
|
}
|
|
344
350
|
action release_piece_3 {
|
|
351
|
+
when(private_actions) { public: none; }
|
|
345
352
|
let(metadata_instrument): "metadata.instrumentId";
|
|
346
353
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
347
354
|
let(metadata_phase): "metadata.phase";
|
|
@@ -351,6 +358,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
351
358
|
steps: [];
|
|
352
359
|
}
|
|
353
360
|
action refund_piece_2 {
|
|
361
|
+
when(private_actions) { public: none; }
|
|
354
362
|
let(metadata_instrument): "metadata.instrumentId";
|
|
355
363
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
356
364
|
let(metadata_phase): "metadata.phase";
|
|
@@ -362,6 +370,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
362
370
|
steps: [];
|
|
363
371
|
}
|
|
364
372
|
action refund_piece_3 {
|
|
373
|
+
when(private_actions) { public: none; }
|
|
365
374
|
let(metadata_instrument): "metadata.instrumentId";
|
|
366
375
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
367
376
|
let(metadata_phase): "metadata.phase";
|
|
@@ -371,6 +380,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
371
380
|
steps: [];
|
|
372
381
|
}
|
|
373
382
|
action unfund_piece_1 {
|
|
383
|
+
when(private_actions) { public: none; }
|
|
374
384
|
let(metadata_instrument): "metadata.instrumentId";
|
|
375
385
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
376
386
|
let(metadata_phase): "metadata.phase";
|
|
@@ -380,6 +390,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
380
390
|
steps: [];
|
|
381
391
|
}
|
|
382
392
|
action unfund_piece_2 {
|
|
393
|
+
when(private_actions) { public: none; }
|
|
383
394
|
let(metadata_instrument): "metadata.instrumentId";
|
|
384
395
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
385
396
|
let(metadata_phase): "metadata.phase";
|
|
@@ -390,6 +401,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
390
401
|
}
|
|
391
402
|
when_eq(payer_fee_kind, percent) {
|
|
392
403
|
action unfund_piece_3 {
|
|
404
|
+
when(private_actions) { public: none; }
|
|
393
405
|
let(metadata_instrument): "metadata.instrumentId";
|
|
394
406
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
395
407
|
let(metadata_phase): "metadata.phase";
|
|
@@ -430,6 +442,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
430
442
|
computes derived: { field: derived_field; source_field: derived_source; rounding: floor; rule: { kind: percentage_of; bps: derived_rule; }; };
|
|
431
443
|
}
|
|
432
444
|
action create {
|
|
445
|
+
when(private_actions) { public: none; }
|
|
433
446
|
let(owner_id): "owner.id";
|
|
434
447
|
let(owner_type): "owner.type";
|
|
435
448
|
agent_description: concat("Open the ", words(instrument), " and provision the escrow account that holds the funds. This is the first call, and every funding and release action moves money through that account. It does not move the held amount itself.");
|
|
@@ -444,6 +457,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
444
457
|
steps: [{ operation: "account.escrow.provision"; bind: { currency: { from: instance; path: fields.currency; }; [owner_id]: { from: instance; path: productId; }; [owner_type]: { from: const; value: product; }; productId: { from: instance; path: productId; }; role: { from: const; value: product_escrow; }; }; capture: { escrowAccountId: accountId; }; }];
|
|
445
458
|
}
|
|
446
459
|
action fund_piece_1 {
|
|
460
|
+
when(private_actions) { public: none; }
|
|
447
461
|
let(metadata_instrument): "metadata.instrumentId";
|
|
448
462
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
449
463
|
let(metadata_phase): "metadata.phase";
|
|
@@ -465,6 +479,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
465
479
|
steps: [];
|
|
466
480
|
}
|
|
467
481
|
action [release_name] {
|
|
482
|
+
when(private_actions) { public: none; }
|
|
468
483
|
let(metadata_instrument): "metadata.instrumentId";
|
|
469
484
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
470
485
|
let(metadata_phase): "metadata.phase";
|
|
@@ -498,6 +513,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
498
513
|
}
|
|
499
514
|
when(release_deadline) {
|
|
500
515
|
action release_on_deadline {
|
|
516
|
+
when(private_actions) { public: none; }
|
|
501
517
|
let(metadata_instrument): "metadata.instrumentId";
|
|
502
518
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
503
519
|
let(metadata_phase): "metadata.phase";
|
|
@@ -514,6 +530,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
514
530
|
}
|
|
515
531
|
when(on_cancel) {
|
|
516
532
|
action cancel {
|
|
533
|
+
when(private_actions) { public: none; }
|
|
517
534
|
let(metadata_instrument): "metadata.instrumentId";
|
|
518
535
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
519
536
|
let(metadata_phase): "metadata.phase";
|
|
@@ -530,6 +547,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
530
547
|
}
|
|
531
548
|
when(has_cancel_quote) {
|
|
532
549
|
action quote_cancellation {
|
|
550
|
+
when(private_actions) { public: none; }
|
|
533
551
|
agent_description: concat("Price the cancellation and hold that price open. No money moves. The charge the ", words(payee), " keeps and the refund the ", words(payer), " gets back are worked out here and frozen for ", cancel_offer_life, ", after which a fresh call prices it again. Call cancel to spend the quote.");
|
|
534
552
|
summary: "Price the cancellation and hold that price open";
|
|
535
553
|
when(release_deadline) { deadline { field: release_deadline; }; }
|
|
@@ -546,6 +564,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
546
564
|
steps: [];
|
|
547
565
|
}
|
|
548
566
|
action cancel {
|
|
567
|
+
when(private_actions) { public: none; }
|
|
549
568
|
let(metadata_instrument): "metadata.instrumentId";
|
|
550
569
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
551
570
|
let(metadata_phase): "metadata.phase";
|
|
@@ -557,6 +576,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
557
576
|
steps: [];
|
|
558
577
|
}
|
|
559
578
|
action retain_cancellation_charge {
|
|
579
|
+
when(private_actions) { public: none; }
|
|
560
580
|
let(metadata_instrument): "metadata.instrumentId";
|
|
561
581
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
562
582
|
let(metadata_phase): "metadata.phase";
|
|
@@ -574,6 +594,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
574
594
|
when(payee_fee) {
|
|
575
595
|
when_not(on_cancel) {
|
|
576
596
|
action fund_piece_2 {
|
|
597
|
+
when(private_actions) { public: none; }
|
|
577
598
|
let(metadata_instrument): "metadata.instrumentId";
|
|
578
599
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
579
600
|
let(metadata_phase): "metadata.phase";
|
|
@@ -585,6 +606,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
585
606
|
steps: [];
|
|
586
607
|
}
|
|
587
608
|
action release_piece_2 {
|
|
609
|
+
when(private_actions) { public: none; }
|
|
588
610
|
let(metadata_instrument): "metadata.instrumentId";
|
|
589
611
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
590
612
|
let(metadata_phase): "metadata.phase";
|
|
@@ -595,6 +617,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
595
617
|
steps: [];
|
|
596
618
|
}
|
|
597
619
|
action unfund_piece_1 {
|
|
620
|
+
when(private_actions) { public: none; }
|
|
598
621
|
let(metadata_instrument): "metadata.instrumentId";
|
|
599
622
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
600
623
|
let(metadata_phase): "metadata.phase";
|
|
@@ -608,6 +631,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
608
631
|
when_not(payee_fee) {
|
|
609
632
|
when(cancel_splits) {
|
|
610
633
|
action fund_piece_2 {
|
|
634
|
+
when(private_actions) { public: none; }
|
|
611
635
|
let(metadata_instrument): "metadata.instrumentId";
|
|
612
636
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
613
637
|
let(metadata_phase): "metadata.phase";
|
|
@@ -618,6 +642,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
618
642
|
steps: [];
|
|
619
643
|
}
|
|
620
644
|
action release_piece_2 {
|
|
645
|
+
when(private_actions) { public: none; }
|
|
621
646
|
let(metadata_instrument): "metadata.instrumentId";
|
|
622
647
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
623
648
|
let(metadata_phase): "metadata.phase";
|
|
@@ -627,6 +652,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
627
652
|
steps: [];
|
|
628
653
|
}
|
|
629
654
|
action refund_piece_2 {
|
|
655
|
+
when(private_actions) { public: none; }
|
|
630
656
|
let(metadata_instrument): "metadata.instrumentId";
|
|
631
657
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
632
658
|
let(metadata_phase): "metadata.phase";
|
|
@@ -636,6 +662,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
636
662
|
steps: [];
|
|
637
663
|
}
|
|
638
664
|
action unfund_piece_1 {
|
|
665
|
+
when(private_actions) { public: none; }
|
|
639
666
|
let(metadata_instrument): "metadata.instrumentId";
|
|
640
667
|
let(metadata_instance): "metadata.instrumentInstanceId";
|
|
641
668
|
let(metadata_phase): "metadata.phase";
|
|
@@ -647,6 +674,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
647
674
|
}
|
|
648
675
|
}
|
|
649
676
|
action abandon {
|
|
677
|
+
when(private_actions) { public: none; }
|
|
650
678
|
agent_description: concat("Close a ", words(instrument), " that was never funded. The escrow account must be empty, so this refuses once any piece is in it. It moves no money and the settlement ends here with no way back.");
|
|
651
679
|
summary: "Abandon the settlement before any money is held";
|
|
652
680
|
requires drained: { path: refs.escrowAccountId; };
|
|
@@ -654,6 +682,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
654
682
|
steps: [];
|
|
655
683
|
}
|
|
656
684
|
action dispute {
|
|
685
|
+
when(private_actions) { public: none; }
|
|
657
686
|
agent_description: concat("Freeze a funded ", words(instrument), " so neither release nor refund can run until the parties settle the disagreement. No money moves and the whole balance stays put. Only a funded ", words(instrument), " disputes.");
|
|
658
687
|
description: concat("Freezes funded ", words(instrument), ", preserving the entire balance while the parties decide what happens next.");
|
|
659
688
|
summary: concat("Dispute a ", words(instrument), " settlement");
|
|
@@ -662,6 +691,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
662
691
|
steps: [];
|
|
663
692
|
}
|
|
664
693
|
action resume {
|
|
694
|
+
when(private_actions) { public: none; }
|
|
665
695
|
agent_description: concat("Lift the dispute hold and put the ", words(instrument), " back where it was, funded and ready to release. No money moves and the balance was never touched. Only a disputed ", words(instrument), " resumes. Use this when the parties settled without a refund.");
|
|
666
696
|
description: concat("Ends the dispute hold without moving money and returns the fully funded ", words(instrument), " to its first milestone.");
|
|
667
697
|
summary: concat("Resume a ", words(instrument), " settlement");
|
|
@@ -707,6 +737,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
707
737
|
parties { beneficiary: payee_account_field; payer: payer_account_field; }
|
|
708
738
|
required: [payer_account_field, payee_account_field, platformAccountId, amount, whole_fee_field, currency, release_deadline];
|
|
709
739
|
action create {
|
|
740
|
+
when(private_actions) { public: none; }
|
|
710
741
|
let(owner_id): "owner.id";
|
|
711
742
|
let(owner_type): "owner.type";
|
|
712
743
|
agent_description: concat("Open the ", words(instrument), " and provision the escrow account that holds the money. No money moves on this call. ", release_deadline, " is fixed here and never moves again, so one date governs the early release, the cancellation, and the automatic payout.");
|
|
@@ -715,6 +746,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
715
746
|
steps: [{ operation: "account.escrow.provision"; bind: { currency: { from: instance; path: fields.currency; }; [owner_id]: { from: instance; path: productId; }; [owner_type]: { from: const; value: product; }; productId: { from: instance; path: productId; }; role: { from: const; value: product_escrow; }; }; capture: { escrowAccountId: accountId; }; }];
|
|
716
747
|
}
|
|
717
748
|
action fund {
|
|
749
|
+
when(private_actions) { public: none; }
|
|
718
750
|
agent_description: concat("Move ", amount, " from the ", words(payer), " into escrow and charge ", whole_fee_field, " on top to the platform account, as one linked batch. This moves money and the ", words(payer), " pays both. Only a created hold funds. The fee never enters escrow, so it is non-refundable however the hold ends.");
|
|
719
751
|
summary: "Fund the hold and collect the custody fee";
|
|
720
752
|
moves: [
|
|
@@ -725,6 +757,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
725
757
|
steps: [];
|
|
726
758
|
}
|
|
727
759
|
action [release_action] {
|
|
760
|
+
when(private_actions) { public: none; }
|
|
728
761
|
agent_description: concat("Pay the ", words(payee), " the whole ", amount, " out of escrow ahead of ", release_deadline, ". This moves money and does not reverse. Only a funded hold releases, the caller must act as ", words(at(release_allowed, 1)), ", and the call is refused once ", release_deadline, " passes. The custody fee stays posted.");
|
|
729
762
|
summary: concat("Release the hold to the ", words(payee), " early");
|
|
730
763
|
deadline: { field: release_deadline; };
|
|
@@ -734,6 +767,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
734
767
|
steps: [];
|
|
735
768
|
}
|
|
736
769
|
action release_on_deadline {
|
|
770
|
+
when(private_actions) { public: none; }
|
|
737
771
|
summary: concat("Release the hold to the ", words(payee), " on its release date");
|
|
738
772
|
due: { field: release_deadline; };
|
|
739
773
|
event_name: concat(instrument, ".released_on_deadline");
|
|
@@ -741,6 +775,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
741
775
|
steps: [];
|
|
742
776
|
}
|
|
743
777
|
action cancel {
|
|
778
|
+
when(private_actions) { public: none; }
|
|
744
779
|
agent_description: concat("Return the whole ", amount, " from escrow to the ", words(payer), " and end the hold. This moves money and does not reverse. Only a funded hold cancels, and only before ", release_deadline, " passes. The custody fee stays posted because the custody it paid for was already provided.");
|
|
745
780
|
summary: "Cancel the hold and return the held amount";
|
|
746
781
|
deadline: { field: release_deadline; };
|
|
@@ -749,6 +784,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
749
784
|
steps: [];
|
|
750
785
|
}
|
|
751
786
|
action abandon {
|
|
787
|
+
when(private_actions) { public: none; }
|
|
752
788
|
agent_description: concat("Close a ", words(instrument), " that was opened and never funded. No money has moved and the escrow must be empty, so this is a lifecycle transition only. A funded hold refuses. Release it to the ", words(payee), " or cancel it back to the ", words(payer), " instead.");
|
|
753
789
|
summary: "Abandon the hold before it is funded";
|
|
754
790
|
moves: [];
|
|
@@ -756,6 +792,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
756
792
|
steps: [];
|
|
757
793
|
}
|
|
758
794
|
action dispute {
|
|
795
|
+
when(private_actions) { public: none; }
|
|
759
796
|
agent_description: concat("Freeze a funded ", words(instrument), " so neither release nor refund can run until the parties settle the disagreement. No money moves and the whole balance stays put. Only a funded ", words(instrument), " disputes.");
|
|
760
797
|
description: concat("Freezes funded ", words(instrument), ", preserving the entire balance while the parties decide what happens next.");
|
|
761
798
|
summary: concat("Dispute a ", words(instrument), " settlement");
|
|
@@ -764,6 +801,7 @@ export instrument held_payment<C>(payer: party, payee: party, amount: money<C>,
|
|
|
764
801
|
steps: [];
|
|
765
802
|
}
|
|
766
803
|
action resume {
|
|
804
|
+
when(private_actions) { public: none; }
|
|
767
805
|
agent_description: concat("Lift the dispute hold and put the ", words(instrument), " back where it was, funded and ready to release. No money moves and the balance was never touched. Only a disputed ", words(instrument), " resumes. Use this when the parties settled without a refund.");
|
|
768
806
|
description: concat("Ends the dispute hold without moving money and returns the fully funded ", words(instrument), " to its first milestone.");
|
|
769
807
|
summary: concat("Resume a ", words(instrument), " settlement");
|
|
@@ -11,10 +11,6 @@ module std.money_flows.metered
|
|
|
11
11
|
// - vs `scheduled`: `metered` charges variable amounts per event calculated from consumed units and committed
|
|
12
12
|
// rate card prices. `scheduled` executes transfers on fixed calendar recurrence intervals (`every`, `first_due`)
|
|
13
13
|
// with predetermined amounts or installments.
|
|
14
|
-
// - vs `recurring_collection`: `metered` calculates and executes real ledger transfers for each usage charge.
|
|
15
|
-
// `recurring_collection` is a lightweight status tracker for collection runs without balance transfers or rate cards.
|
|
16
|
-
// - vs `instant_transfer`: `instant_transfer` executes a single immediate payment. `metered` manages a stateful
|
|
17
|
-
// open billing cycle accepting multiple usage charges under a fixed rate card.
|
|
18
14
|
//
|
|
19
15
|
// ### Parameters
|
|
20
16
|
// - `payer`: The customer being billed for usage.
|
|
@@ -49,7 +45,7 @@ export instrument metered(
|
|
|
49
45
|
) {
|
|
50
46
|
let(instrument_words): words(instrument);
|
|
51
47
|
title: sentence(instrument);
|
|
52
|
-
agent_description: "Reach for metered when the {payer} is billed per unit at a rate card fixed when the period opens, and the period closes on {close_by}. Every charge is its own transfer. Pick
|
|
48
|
+
agent_description: "Reach for metered when the {payer} is billed per unit at a rate card fixed when the period opens, and the period closes on {close_by}. Every charge is its own transfer. Pick scheduled for a flat repeating amount and instant transfer for a one-off payment.";
|
|
53
49
|
summary: "Metered charges from {payer} on a committed rate card";
|
|
54
50
|
description: "Metered usage: the {payer} is charged per unit at the committed rate card until the period closes on its stored end date";
|
|
55
51
|
|
|
@@ -5,6 +5,7 @@ module std.money_flows.scheduled
|
|
|
5
5
|
// ### Purpose
|
|
6
6
|
// `scheduled` automates time-anchored payment series between one payer and one payee.
|
|
7
7
|
// It supports three modes:
|
|
8
|
+
//
|
|
8
9
|
// 1. Installment plan: Partitions a fixed total amount into `count` installments, each collected on its own stored date.
|
|
9
10
|
// 2. Open recurring subscription: Charges a recurring amount on an interval `every` until an `until` port fires.
|
|
10
11
|
// 3. Debt obligation: Manages legally binding multi-installment debt with delinquency tracking, mandate evidence, and child payment records.
|
|
@@ -12,8 +13,6 @@ module std.money_flows.scheduled
|
|
|
12
13
|
// ### Selection guidance
|
|
13
14
|
// - vs `metered`: `scheduled` executes calendar-based recurring charges or fixed installment plans.
|
|
14
15
|
// `metered` bills variable usage per event based on a committed rate card.
|
|
15
|
-
// - vs `recurring_collection`: `scheduled` actively moves ledger balances and partitions amounts.
|
|
16
|
-
// `recurring_collection` is a lightweight status tracker for collection runs without balance transfers.
|
|
17
16
|
// - vs `rotating_pool`: `scheduled` coordinates one payer to one payee on a calendar.
|
|
18
17
|
// `rotating_pool` coordinates a multi-party peer circle where members rotate turns receiving the entire pot.
|
|
19
18
|
//
|
|
@@ -73,8 +72,6 @@ export instrument scheduled<C>(
|
|
|
73
72
|
agent_description: "Reach for scheduled when one payer owes one payee on a calendar. It covers a plan of fixed installments partitioning one total, an obligation whose installments are paid through child payment records and can go delinquent, and an open period charge that repeats until a cancel port fires. Rotating pool is for a group taking turns.";
|
|
74
73
|
when_not(mode) {
|
|
75
74
|
let(instrument_words): words(instrument);
|
|
76
|
-
let(first_percent): if_eq(count, 3, "33.34%", if_eq(count, 2, "50%", "25%"));
|
|
77
|
-
let(other_percent): if_eq(count, 3, "33.33%", if_eq(count, 2, "50%", "25%"));
|
|
78
75
|
|
|
79
76
|
title: sentence(instrument);
|
|
80
77
|
summary: "{count}-installment schedule from {payer} to {payee}";
|
|
@@ -91,11 +88,10 @@ export instrument scheduled<C>(
|
|
|
91
88
|
description: "Due date of the first installment; installment k falls {every} after its predecessor";
|
|
92
89
|
}
|
|
93
90
|
for i in count {
|
|
94
|
-
let(piece_percent): if_eq(i, 1, first_percent, other_percent);
|
|
95
91
|
let(remainder_words): if_eq(i, 1, " (carries the integer-division remainder)", "");
|
|
96
92
|
installment[i]Amount: {
|
|
97
93
|
type: money<C>;
|
|
98
|
-
description: "Installment {i} of {count}{remainder_words}:
|
|
94
|
+
description: "Installment {i} of {count}{remainder_words}: a stored share of {amount}, collected on its own stored-date anchor";
|
|
99
95
|
}
|
|
100
96
|
}
|
|
101
97
|
}
|
|
@@ -153,13 +149,7 @@ export instrument scheduled<C>(
|
|
|
153
149
|
let(parent_ref_field): camel(concat(instrument, "_id"));
|
|
154
150
|
let(has_advance): advance_to;
|
|
155
151
|
let(same_debtor): if_eq(payer, debtor, true, false);
|
|
156
|
-
let(parent_statuses):
|
|
157
|
-
["active", "installment_1_delinquent", "installment_2_delinquent"],
|
|
158
|
-
if_eq(count, 3,
|
|
159
|
-
["active", "installment_1_delinquent", "installment_2_delinquent", "installment_3_delinquent"],
|
|
160
|
-
["active", "installment_1_delinquent", "installment_2_delinquent", "installment_3_delinquent", "installment_4_delinquent"]
|
|
161
|
-
)
|
|
162
|
-
);
|
|
152
|
+
let(parent_statuses): concat_lists(["active"], names("installment_", count, "_delinquent"));
|
|
163
153
|
let(delinquent_statuses): names("installment_", count, "_delinquent");
|
|
164
154
|
let(last_offset): scale_duration(every, sub(count, 1));
|
|
165
155
|
let(reference_filter_key): "x-hyperscale-reference-filter";
|
|
@@ -196,51 +186,13 @@ export instrument scheduled<C>(
|
|
|
196
186
|
parent_field: concat("installment", i, "Amount");
|
|
197
187
|
};
|
|
198
188
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
)
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
when(same_debtor) {
|
|
210
|
-
when_not(derived_amount) {
|
|
211
|
-
required: if_eq(count, 3,
|
|
212
|
-
[advance_field, payee_field, payer_field, "currency", amount_field, first_due_field, "installment1Amount", "installment2Amount", "installment3Amount"],
|
|
213
|
-
[advance_field, payee_field, payer_field, "currency", amount_field, first_due_field, "installment1Amount", "installment2Amount", "installment3Amount", "installment4Amount"]
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
when(derived_amount) {
|
|
217
|
-
required: if_eq(count, 2,
|
|
218
|
-
[advance_field, payee_field, payer_field, "platformAccountId", "currency", amount_field, first_due_field, "installment1Amount", "installment2Amount"],
|
|
219
|
-
[advance_field, payee_field, payer_field, "platformAccountId", "currency", amount_field, first_due_field, "installment1Amount", "installment2Amount", "installment3Amount", "installment4Amount"]
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
when_not(has_advance) {
|
|
225
|
-
when_not(same_debtor) {
|
|
226
|
-
required: if_eq(count, 2,
|
|
227
|
-
[payee_field, payer_field, debtor_field, "currency", amount_field, first_due_field, "installment1Amount", "installment2Amount"],
|
|
228
|
-
if_eq(count, 3,
|
|
229
|
-
[payee_field, payer_field, debtor_field, "currency", amount_field, first_due_field, "installment1Amount", "installment2Amount", "installment3Amount"],
|
|
230
|
-
[payee_field, payer_field, debtor_field, "currency", amount_field, first_due_field, "installment1Amount", "installment2Amount", "installment3Amount", "installment4Amount"]
|
|
231
|
-
)
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
when(same_debtor) {
|
|
235
|
-
required: if_eq(count, 2,
|
|
236
|
-
[payee_field, payer_field, "currency", amount_field, first_due_field, "installment1Amount", "installment2Amount"],
|
|
237
|
-
if_eq(count, 3,
|
|
238
|
-
[payee_field, payer_field, "currency", amount_field, first_due_field, "installment1Amount", "installment2Amount", "installment3Amount"],
|
|
239
|
-
[payee_field, payer_field, "currency", amount_field, first_due_field, "installment1Amount", "installment2Amount", "installment3Amount", "installment4Amount"]
|
|
240
|
-
)
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
189
|
+
required: concat_lists(
|
|
190
|
+
[payee_field, payer_field, "currency", amount_field, first_due_field],
|
|
191
|
+
names("installment", count, "Amount"),
|
|
192
|
+
if_eq(has_advance, false, [], [advance_field]),
|
|
193
|
+
if_eq(same_debtor, true, [], [debtor_field]),
|
|
194
|
+
if_eq(derived_amount, false, [], ["platformAccountId"])
|
|
195
|
+
);
|
|
244
196
|
|
|
245
197
|
fields {
|
|
246
198
|
when(has_advance) {
|
|
@@ -315,68 +267,26 @@ export instrument scheduled<C>(
|
|
|
315
267
|
when_not(same_debtor) { payer: payer; }
|
|
316
268
|
}
|
|
317
269
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
on
|
|
332
|
-
|
|
333
|
-
on
|
|
334
|
-
on complete: active|installment_1_delinquent|installment_2_delinquent -> repaid;
|
|
335
|
-
when(has_advance) { on write_off: draft|approved|active|installment_1_delinquent|installment_2_delinquent -> written_off; }
|
|
336
|
-
when_not(has_advance) { on write_off: draft|active|installment_1_delinquent|installment_2_delinquent -> written_off; }
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
when(count_is_3) {
|
|
340
|
-
lifecycle {
|
|
341
|
-
states draft;
|
|
342
|
-
when(has_advance) { states approved; }
|
|
343
|
-
states active installment_1_delinquent installment_2_delinquent installment_3_delinquent written_off repaid;
|
|
344
|
-
initial draft;
|
|
345
|
-
when(has_advance) { on approve: draft -> approved; }
|
|
346
|
-
when_not(has_advance) { on approve: draft -> active; }
|
|
347
|
-
when(has_advance) { on advance: approved -> active; }
|
|
348
|
-
on collect_installment_1: installment_1_delinquent|installment_2_delinquent|installment_3_delinquent -> active;
|
|
349
|
-
on collect_installment_2: installment_1_delinquent|installment_2_delinquent|installment_3_delinquent -> active;
|
|
350
|
-
on collect_installment_3: installment_1_delinquent|installment_2_delinquent|installment_3_delinquent -> active;
|
|
351
|
-
on mark_installment_1_delinquent: active|installment_2_delinquent|installment_3_delinquent -> installment_1_delinquent;
|
|
352
|
-
on mark_installment_2_delinquent: active|installment_1_delinquent|installment_3_delinquent -> installment_2_delinquent;
|
|
353
|
-
on mark_installment_3_delinquent: active|installment_1_delinquent|installment_2_delinquent -> installment_3_delinquent;
|
|
354
|
-
on complete: active|installment_1_delinquent|installment_2_delinquent|installment_3_delinquent -> repaid;
|
|
355
|
-
when(has_advance) { on write_off: draft|approved|active|installment_1_delinquent|installment_2_delinquent|installment_3_delinquent -> written_off; }
|
|
356
|
-
when_not(has_advance) { on write_off: draft|active|installment_1_delinquent|installment_2_delinquent|installment_3_delinquent -> written_off; }
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
when(count_is_4) {
|
|
360
|
-
lifecycle {
|
|
361
|
-
states draft;
|
|
362
|
-
when(has_advance) { states approved; }
|
|
363
|
-
states active installment_1_delinquent installment_2_delinquent installment_3_delinquent installment_4_delinquent written_off repaid;
|
|
364
|
-
initial draft;
|
|
365
|
-
when(has_advance) { on approve: draft -> approved; }
|
|
366
|
-
when_not(has_advance) { on approve: draft -> active; }
|
|
367
|
-
when(has_advance) { on advance: approved -> active; }
|
|
368
|
-
on collect_installment_1: installment_1_delinquent|installment_2_delinquent|installment_3_delinquent|installment_4_delinquent -> active;
|
|
369
|
-
on collect_installment_2: installment_1_delinquent|installment_2_delinquent|installment_3_delinquent|installment_4_delinquent -> active;
|
|
370
|
-
on collect_installment_3: installment_1_delinquent|installment_2_delinquent|installment_3_delinquent|installment_4_delinquent -> active;
|
|
371
|
-
on collect_installment_4: installment_1_delinquent|installment_2_delinquent|installment_3_delinquent|installment_4_delinquent -> active;
|
|
372
|
-
on mark_installment_1_delinquent: active|installment_2_delinquent|installment_3_delinquent|installment_4_delinquent -> installment_1_delinquent;
|
|
373
|
-
on mark_installment_2_delinquent: active|installment_1_delinquent|installment_3_delinquent|installment_4_delinquent -> installment_2_delinquent;
|
|
374
|
-
on mark_installment_3_delinquent: active|installment_1_delinquent|installment_2_delinquent|installment_4_delinquent -> installment_3_delinquent;
|
|
375
|
-
on mark_installment_4_delinquent: active|installment_1_delinquent|installment_2_delinquent|installment_3_delinquent -> installment_4_delinquent;
|
|
376
|
-
on complete: active|installment_1_delinquent|installment_2_delinquent|installment_3_delinquent|installment_4_delinquent -> repaid;
|
|
377
|
-
when(has_advance) { on write_off: draft|approved|active|installment_1_delinquent|installment_2_delinquent|installment_3_delinquent|installment_4_delinquent -> written_off; }
|
|
378
|
-
when_not(has_advance) { on write_off: draft|active|installment_1_delinquent|installment_2_delinquent|installment_3_delinquent|installment_4_delinquent -> written_off; }
|
|
270
|
+
lifecycle {
|
|
271
|
+
states draft;
|
|
272
|
+
when(has_advance) { states approved; }
|
|
273
|
+
states active;
|
|
274
|
+
let(states_key): "states";
|
|
275
|
+
[states_key]: delinquent_statuses;
|
|
276
|
+
states written_off repaid;
|
|
277
|
+
initial draft;
|
|
278
|
+
when(has_advance) { on approve: draft -> approved; on advance: approved -> active; }
|
|
279
|
+
when_not(has_advance) { on approve: draft -> active; }
|
|
280
|
+
for i in count {
|
|
281
|
+
let(target): concat("installment_", i, "_delinquent");
|
|
282
|
+
let(other_states): without(parent_statuses, target);
|
|
283
|
+
on collect_installment_[i]: delinquent_statuses -> active;
|
|
284
|
+
let(mark_action): concat("mark_installment_", i, "_delinquent");
|
|
285
|
+
on [mark_action]: other_states -> target;
|
|
379
286
|
}
|
|
287
|
+
on complete: parent_statuses -> repaid;
|
|
288
|
+
when(has_advance) { on write_off: draft|approved|parent_statuses -> written_off; }
|
|
289
|
+
when_not(has_advance) { on write_off: draft|parent_statuses -> written_off; }
|
|
380
290
|
}
|
|
381
291
|
|
|
382
292
|
action create {
|
|
@@ -14,7 +14,7 @@ module std.money_flows.threshold_pool
|
|
|
14
14
|
// `weighted_distribution` pays funds outward from one source pool to many weighted recipients.
|
|
15
15
|
//
|
|
16
16
|
// ### Parameters
|
|
17
|
-
// - `contributor`: The
|
|
17
|
+
// - `contributor`: The party representing individual contributors.
|
|
18
18
|
// - `beneficiary`: The beneficiary party receiving the settled pool if the target is met.
|
|
19
19
|
// - `target`: Total target funding threshold in minor units of currency `C`.
|
|
20
20
|
// - `commitment`: Binding name for individual contributor commitment amounts.
|
|
@@ -51,6 +51,13 @@ module std.money_flows.threshold_pool
|
|
|
51
51
|
// fail_policy: whole_commitment_refund
|
|
52
52
|
// }
|
|
53
53
|
// ```
|
|
54
|
+
// ### Funding join
|
|
55
|
+
// funding_join configures the flat contribution-instrument mode for a priced
|
|
56
|
+
// obligation. Opening freezes minimum ticket, investor cap, profit and schedule
|
|
57
|
+
// terms. Closing binds the obligation, freezes committed ticket identities and
|
|
58
|
+
// moves the principal once into its funding account. A ticket's engine-owned
|
|
59
|
+
// collection action has no independent payment or clock trigger.
|
|
60
|
+
//
|
|
54
61
|
export instrument threshold_pool<C>(
|
|
55
62
|
contributor: optional<party>,
|
|
56
63
|
beneficiary: optional<party>,
|
|
@@ -66,6 +73,7 @@ export instrument threshold_pool<C>(
|
|
|
66
73
|
memo: optional<text>,
|
|
67
74
|
contribution_instrument: optional<block>,
|
|
68
75
|
wording: optional<block>,
|
|
76
|
+
funding_join: optional<block>,
|
|
69
77
|
) {
|
|
70
78
|
when(wording) {
|
|
71
79
|
let(wording_unknown_keys): keys_except(wording, "pool_summary", "pool_description", "create_summary", "commitment_create_summary", "commitment_cancel_summary", "commitment_refund_summary");
|
|
@@ -217,6 +225,7 @@ export instrument threshold_pool<C>(
|
|
|
217
225
|
let(beneficiary_account_field): camel(beneficiary_account);
|
|
218
226
|
let(target_field): camel(target);
|
|
219
227
|
let(close_field): camel(close_by);
|
|
228
|
+
let(obligation_type): get(funding_join, "obligation");
|
|
220
229
|
|
|
221
230
|
fields {
|
|
222
231
|
[beneficiary_account_field] { type: account<C>; description: beneficiary_account_description; "x-hyperscale-reference-filter": { column: role; values: [customer_balance]; }; }
|
|
@@ -224,6 +233,18 @@ export instrument threshold_pool<C>(
|
|
|
224
233
|
currency { type: text; description: currency_description; maxLength: 3; minLength: 3; pattern: "^[A-Z]{3}$"; }
|
|
225
234
|
when(memo) { [memo] { type: text; optional: true; maxLength: 180; minLength: 1; } }
|
|
226
235
|
[target_field] { type: money<C>; description: target_description; }
|
|
236
|
+
when(funding_join) {
|
|
237
|
+
fundingAccountId { type: account<C>; "x-hyperscale-reference-filter": { column: role; values: [customer_balance]; }; }
|
|
238
|
+
obligationId { type: ref<obligation_type>; optional: true; }
|
|
239
|
+
minimumTicket: money<C>;
|
|
240
|
+
investorCapBps { type: integer; minimum: 1; maximum: 9999; }
|
|
241
|
+
maximumTicket { type: money<C>; optional: true; }
|
|
242
|
+
fixedProfit: money<C>;
|
|
243
|
+
installmentCount { type: integer; minimum: 1; maximum: 360; }
|
|
244
|
+
earningRule { type: text; enum: [per_slice_on_due, on_disbursement]; }
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
when(funding_join) { computes derived: { field: maximumTicket; source_field: target_field; rule: { kind: percentage_of; bps: { field: investorCapBps; }; }; rounding: floor; };
|
|
227
248
|
}
|
|
228
249
|
lifecycle {
|
|
229
250
|
states open active failed settled;
|
|
@@ -233,14 +254,15 @@ export instrument threshold_pool<C>(
|
|
|
233
254
|
on fail: open -> failed;
|
|
234
255
|
}
|
|
235
256
|
parties { beneficiary: beneficiary_account_field; }
|
|
236
|
-
required: [beneficiary_account_field, target_field, currency, close_field];
|
|
257
|
+
when_not(funding_join) { required: [beneficiary_account_field, target_field, currency, close_field]; }
|
|
258
|
+
when(funding_join) { required: [beneficiary_account_field, target_field, currency, close_field, fundingAccountId, minimumTicket, investorCapBps, fixedProfit, installmentCount, earningRule]; }
|
|
237
259
|
aggregate_invariants { childField: contribution_amount_field; childInstrumentId: contribution_id; childRefField: contribution_ref_field; childStatuses: [committed_status, collected_status]; parentField: target_field; }
|
|
238
260
|
caller_parked_states { active: active_parked; }
|
|
239
261
|
id_prefix: instrument_id_prefix;
|
|
240
262
|
action create {
|
|
241
263
|
public: none;
|
|
242
264
|
agent_description: "Open the pool before any contribution exists. The caller supplies the target, the currency, the beneficiary account, and the close anchor, and the pool provisions its escrow account. No money moves.";
|
|
243
|
-
examples: create_examples;
|
|
265
|
+
when_not(funding_join) { examples: create_examples; }
|
|
244
266
|
moves: [];
|
|
245
267
|
steps: [{ operation: "account.escrow.provision"; bind: { currency: { from: instance; path: "fields.currency"; }; "owner.id": { from: instance; path: productId; }; "owner.type": { from: const; value: product; }; productId: { from: instance; path: productId; }; role: { from: const; value: product_escrow; }; }; capture: { [custody_ref]: accountId; }; }];
|
|
246
268
|
}
|
|
@@ -263,8 +285,21 @@ export instrument threshold_pool<C>(
|
|
|
263
285
|
action close {
|
|
264
286
|
public: none;
|
|
265
287
|
agent_description: "Settle the pool once every contribution has been collected into the beneficiary account. No money moves. Refused while any contribution is still uncollected, so wait for the per-contribution collections to run rather than retrying.";
|
|
266
|
-
examples: close_examples;
|
|
267
|
-
requires aggregate { instrumentId: contribution_id; over: children; refField: contribution_ref_field; statuses: [collected_status]; check: { kind: all_in; }; }
|
|
288
|
+
when_not(funding_join) { examples: close_examples; }
|
|
289
|
+
when_not(funding_join) { requires aggregate { instrumentId: contribution_id; over: children; refField: contribution_ref_field; statuses: [collected_status]; check: { kind: all_in; }; } }
|
|
290
|
+
when(funding_join) {
|
|
291
|
+
input: { type: object; additionalProperties: false; properties: { obligationId: { type: string; pattern: get(funding_join, "obligation_pattern"); }; }; required: [obligationId]; };
|
|
292
|
+
updates: [obligationId];
|
|
293
|
+
requires refs: { field: obligationId; statuses: [created]; match: { "fields.fundingAccountId": fields.fundingAccountId; "fields.currency": fields.currency; }; };
|
|
294
|
+
funding: {
|
|
295
|
+
obligation_field: obligationId; ticket_instrument_id: contribution_id; ticket_ref_field: contribution_ref_field;
|
|
296
|
+
ticket_amount_field: contribution_amount_field; ticket_investor_field: get(funding_join, "investor_field"); ticket_account_field: get(funding_join, "wallet_account_field");
|
|
297
|
+
ticket_status: committed_status; collect_action: collect; principal_field: target_field;
|
|
298
|
+
source_account_path: concat("refs.", custody_ref); destination_account_field: fundingAccountId;
|
|
299
|
+
terms: { [target_field]: principal; fixedProfit: fixedProfit; installmentCount: installmentCount; earningRule: earningRule; [beneficiary_account_field]: borrowerAccountId; currency: currency; };
|
|
300
|
+
capture: fundingSnapshot;
|
|
301
|
+
};
|
|
302
|
+
}
|
|
268
303
|
moves: [];
|
|
269
304
|
steps: [];
|
|
270
305
|
}
|