@hyperscale0/hsx 2.4.0 → 3.0.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 +2 -400
- package/README.md +3 -92
- package/dist/src/ast.d.ts +99 -310
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/ast.js +16 -69
- package/dist/src/ast.js.map +1 -1
- package/dist/src/cli.d.ts +6 -19
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +76 -315
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +23 -73
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +1113 -148
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +8 -77
- package/dist/src/cost.d.ts.map +1 -1
- package/dist/src/cost.js +13 -422
- package/dist/src/cost.js.map +1 -1
- package/dist/src/format.d.ts +8 -7
- package/dist/src/format.d.ts.map +1 -1
- package/dist/src/format.js +9 -175
- package/dist/src/format.js.map +1 -1
- package/dist/src/headers.d.ts +35 -0
- package/dist/src/headers.d.ts.map +1 -0
- package/dist/src/headers.js +108 -0
- package/dist/src/headers.js.map +1 -0
- package/dist/src/index.d.ts +6 -12
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -9
- package/dist/src/index.js.map +1 -1
- package/dist/src/lex.d.ts +8 -28
- package/dist/src/lex.d.ts.map +1 -1
- package/dist/src/lex.js +88 -146
- package/dist/src/lex.js.map +1 -1
- package/dist/src/parse.d.ts +4 -17
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +402 -1040
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +12 -20
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/std-library.d.ts +1 -1
- package/dist/src/std-library.d.ts.map +1 -1
- package/dist/src/std-library.js +1 -5
- package/dist/src/std-library.js.map +1 -1
- package/dist/src/tunables.d.ts +7 -0
- package/dist/src/tunables.d.ts.map +1 -0
- package/dist/src/tunables.js +26 -0
- package/dist/src/tunables.js.map +1 -0
- package/dist/src/version.d.ts +2 -4
- package/dist/src/version.d.ts.map +1 -1
- package/dist/src/version.js +2 -4
- package/dist/src/version.js.map +1 -1
- package/docs/README.md +179 -41
- package/docs/headers.md +44 -0
- package/examples/cost-table.json +99 -751
- package/examples/library.hsx +59 -0
- package/package.json +9 -17
- package/src/ast.ts +82 -441
- package/src/cli.ts +77 -362
- package/src/compile.ts +1535 -247
- package/src/cost.ts +28 -671
- package/src/format.ts +12 -256
- package/src/headers.ts +126 -0
- package/src/index.ts +7 -39
- package/src/lex.ts +99 -195
- package/src/parse.ts +403 -1209
- package/src/std-bundle.ts +12 -20
- package/src/std-library.ts +2 -7
- package/src/tunables.ts +31 -0
- package/src/version.ts +2 -5
- package/std/approvals.hsx +17 -0
- package/std/cards.hsx +63 -0
- package/std/collections.hsx +31 -0
- package/std/escrow.hsx +43 -0
- package/std/financing.hsx +372 -0
- package/std/insurance.hsx +59 -0
- package/std/lending.hsx +115 -0
- package/std/marketplace.hsx +24 -0
- package/std/money.hsx +195 -0
- package/std/savings.hsx +44 -0
- package/std/travel.hsx +98 -0
- package/std/wallet.hsx +41 -0
- package/dist/src/diagnostics.d.ts +0 -13
- package/dist/src/diagnostics.d.ts.map +0 -1
- package/dist/src/diagnostics.js +0 -587
- package/dist/src/diagnostics.js.map +0 -1
- package/dist/src/emit.d.ts +0 -51
- package/dist/src/emit.d.ts.map +0 -1
- package/dist/src/emit.js +0 -192
- package/dist/src/emit.js.map +0 -1
- package/dist/src/entry-overrides.d.ts +0 -58
- package/dist/src/entry-overrides.d.ts.map +0 -1
- package/dist/src/entry-overrides.js +0 -284
- package/dist/src/entry-overrides.js.map +0 -1
- package/dist/src/ir.d.ts +0 -73
- package/dist/src/ir.d.ts.map +0 -1
- package/dist/src/ir.js +0 -15
- package/dist/src/ir.js.map +0 -1
- package/dist/src/limits.d.ts +0 -23
- package/dist/src/limits.d.ts.map +0 -1
- package/dist/src/limits.js +0 -23
- package/dist/src/limits.js.map +0 -1
- package/dist/src/lsp/server.d.ts +0 -32
- package/dist/src/lsp/server.d.ts.map +0 -1
- package/dist/src/lsp/server.js +0 -391
- package/dist/src/lsp/server.js.map +0 -1
- package/dist/src/modules.d.ts +0 -38
- package/dist/src/modules.d.ts.map +0 -1
- package/dist/src/modules.js +0 -372
- package/dist/src/modules.js.map +0 -1
- package/dist/src/typecheck.d.ts +0 -8
- package/dist/src/typecheck.d.ts.map +0 -1
- package/dist/src/typecheck.js +0 -4131
- package/dist/src/typecheck.js.map +0 -1
- package/docs/guide/01-first-program.md +0 -24
- package/docs/guide/02-money.md +0 -25
- package/docs/guide/03-instruments.md +0 -93
- package/docs/guide/04-lifecycles.md +0 -40
- package/docs/guide/05-fees-and-splits.md +0 -45
- package/docs/guide/06-schedules.md +0 -26
- package/docs/guide/07-composition.md +0 -52
- package/docs/guide/08-writing-a-module.md +0 -85
- package/docs/guide/09-cost.md +0 -24
- package/docs/guide/10-diagnostics.md +0 -27
- package/docs/llms-full.txt +0 -3684
- package/docs/llms.txt +0 -44
- package/docs/piece-plans.md +0 -120
- package/docs/playground.md +0 -55
- package/docs/reference/cli.md +0 -36
- package/docs/reference/diagnostics.md +0 -901
- package/docs/reference/grammar.md +0 -38
- package/docs/reference/std/advance.md +0 -112
- package/docs/reference/std/cancellable_booking.md +0 -159
- package/docs/reference/std/captured_payment.md +0 -115
- package/docs/reference/std/conditional_disbursement.md +0 -101
- package/docs/reference/std/credit_facility.md +0 -108
- package/docs/reference/std/held_payment.md +0 -173
- package/docs/reference/std/instant_transfer.md +0 -97
- package/docs/reference/std/metered.md +0 -89
- package/docs/reference/std/pooled_split.md +0 -95
- package/docs/reference/std/premium_forward.md +0 -119
- package/docs/reference/std/reconciled_payout.md +0 -103
- package/docs/reference/std/rotating_pool.md +0 -130
- package/docs/reference/std/scheduled.md +0 -136
- package/docs/reference/std/security_deposit.md +0 -138
- package/docs/reference/std/settlement_batch.md +0 -123
- package/docs/reference/std/swap.md +0 -133
- package/docs/reference/std/threshold_pool.md +0 -138
- package/docs/reference/std/weighted_distribution.md +0 -135
- package/docs/reference/types.md +0 -61
- package/docs/reference/udl-output.md +0 -13
- package/examples/01-first-program/README.md +0 -45
- package/examples/01-first-program/tip-jar.hsx +0 -17
- package/examples/02-imports-and-modules/README.md +0 -96
- package/examples/02-imports-and-modules/photo-booth.hsx +0 -37
- package/examples/03-diagnostics/README.md +0 -68
- package/examples/03-diagnostics/corner-shop-fixed.hsx +0 -22
- package/examples/03-diagnostics/corner-shop.hsx +0 -19
- package/examples/04-complete-product/README.md +0 -84
- package/examples/04-complete-product/study-hall.hsx +0 -67
- package/examples/05-authored-instrument/README.md +0 -5
- package/examples/05-authored-instrument/payment.hsx +0 -37
- package/examples/05-watch-club/README.md +0 -6
- package/examples/05-watch-club/watch-club.hsx +0 -123
- package/examples/README.md +0 -48
- package/examples/advance/README.md +0 -3
- package/examples/advance/advance.hsx +0 -13
- package/examples/advance/advance.udl +0 -367
- package/examples/cancellable_booking/README.md +0 -3
- package/examples/cancellable_booking/cancellable_booking.hsx +0 -14
- package/examples/cancellable_booking/cancellable_booking.udl +0 -413
- package/examples/captured_payment/README.md +0 -3
- package/examples/captured_payment/captured_payment.hsx +0 -17
- package/examples/captured_payment/captured_payment.udl +0 -686
- package/examples/conditional_disbursement/README.md +0 -3
- package/examples/conditional_disbursement/conditional_disbursement.hsx +0 -15
- package/examples/conditional_disbursement/conditional_disbursement.udl +0 -418
- package/examples/credit_facility/README.md +0 -3
- package/examples/credit_facility/credit_facility.hsx +0 -24
- package/examples/credit_facility/credit_facility.udl +0 -1396
- package/examples/held_payment/README.md +0 -3
- package/examples/held_payment/held_payment.hsx +0 -13
- package/examples/held_payment/held_payment.udl +0 -569
- package/examples/instant_transfer/README.md +0 -3
- package/examples/instant_transfer/instant_transfer.hsx +0 -16
- package/examples/instant_transfer/instant_transfer.udl +0 -376
- package/examples/metered/README.md +0 -3
- package/examples/metered/metered.hsx +0 -13
- package/examples/metered/metered.udl +0 -253
- package/examples/pooled_split/README.md +0 -3
- package/examples/pooled_split/pooled_split.hsx +0 -15
- package/examples/pooled_split/pooled_split.udl +0 -432
- package/examples/premium_forward/README.md +0 -3
- package/examples/premium_forward/premium_forward.hsx +0 -19
- package/examples/premium_forward/premium_forward.udl +0 -604
- package/examples/reconciled_payout/README.md +0 -3
- package/examples/reconciled_payout/reconciled_payout.hsx +0 -13
- package/examples/reconciled_payout/reconciled_payout.udl +0 -312
- package/examples/rotating_pool/README.md +0 -3
- package/examples/rotating_pool/rotating_pool.hsx +0 -18
- package/examples/rotating_pool/rotating_pool.udl +0 -4370
- package/examples/scheduled/README.md +0 -3
- package/examples/scheduled/scheduled.hsx +0 -12
- package/examples/scheduled/scheduled.udl +0 -355
- package/examples/security_deposit/README.md +0 -3
- package/examples/security_deposit/security_deposit.hsx +0 -21
- package/examples/security_deposit/security_deposit.udl +0 -320
- package/examples/settlement_batch/README.md +0 -3
- package/examples/settlement_batch/settlement_batch.hsx +0 -19
- package/examples/settlement_batch/settlement_batch.udl +0 -757
- package/examples/swap/README.md +0 -3
- package/examples/swap/swap.hsx +0 -19
- package/examples/swap/swap.udl +0 -892
- package/examples/threshold_pool/README.md +0 -3
- package/examples/threshold_pool/threshold_pool.hsx +0 -16
- package/examples/threshold_pool/threshold_pool.udl +0 -680
- package/examples/weighted_distribution/README.md +0 -3
- package/examples/weighted_distribution/weighted_distribution.hsx +0 -20
- package/examples/weighted_distribution/weighted_distribution.udl +0 -348
- package/skills/hsx/SKILL.md +0 -449
- package/src/diagnostics.ts +0 -620
- package/src/emit.ts +0 -282
- package/src/entry-overrides.ts +0 -428
- package/src/ir.ts +0 -113
- package/src/limits.ts +0 -22
- package/src/lsp/server.ts +0 -460
- package/src/modules.ts +0 -452
- package/src/typecheck.ts +0 -5381
- package/std/SEMANTICS.md +0 -44
- package/std/money_flows/advance.hsx +0 -235
- package/std/money_flows/cancellable_booking.hsx +0 -402
- package/std/money_flows/captured_payment.hsx +0 -220
- package/std/money_flows/conditional_disbursement.hsx +0 -226
- package/std/money_flows/credit_facility.hsx +0 -214
- package/std/money_flows/held_payment.hsx +0 -812
- package/std/money_flows/index.hsx +0 -4
- package/std/money_flows/instant_transfer.hsx +0 -231
- package/std/money_flows/metered.hsx +0 -104
- package/std/money_flows/pooled_split.hsx +0 -109
- package/std/money_flows/premium_forward.hsx +0 -307
- package/std/money_flows/reconciled_payout.hsx +0 -119
- package/std/money_flows/rotating_pool.hsx +0 -305
- package/std/money_flows/scheduled.hsx +0 -599
- package/std/money_flows/security_deposit.hsx +0 -248
- package/std/money_flows/settlement_batch.hsx +0 -173
- package/std/money_flows/swap.hsx +0 -1192
- package/std/money_flows/threshold_pool.hsx +0 -307
- package/std/money_flows/weighted_distribution.hsx +0 -237
package/src/std-bundle.ts
CHANGED
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
// Generated by open/hsx/scripts/bundle-std.ts. Do not edit by hand.
|
|
2
2
|
|
|
3
3
|
export const BUNDLED_STD_FILES: ReadonlyMap<string, string> = new Map<string, string>([
|
|
4
|
-
["
|
|
5
|
-
["money_flows/advance.hsx", "module std.money_flows.advance\n\n// Upfront capital disbursement repaid by carving future held payment releases or over scheduled installments.\n//\n// ### Purpose\n// `advance` provides working capital, merchant cash advances, or contractor pre-funding from a funder to a recipient.\n// Repayment occurs either automatically by intercepting (carving) releases from a linked escrow hold (`against`),\n// or over calendar-anchored installment repayments (`count`, `every`, `first_due`).\n//\n// ### Selection guidance\n// - vs `credit_facility`: `advance` disburses a single upfront lump-sum principal that is repaid over time.\n// `credit_facility` establishes a reusable revolving credit line with multiple draws up to a limit,\n// where repayments restore available borrowing capacity.\n// - vs `conditional_disbursement`: `advance` expects repayment of the advanced principal plus optional fees.\n// `conditional_disbursement` disburses non-repayable grants, claims, or milestone payments against external evidence.\n// - vs `held_payment`: `held_payment` holds customer funds in escrow until delivery. An `advance` can carve repayments\n// directly out of a `held_payment`'s release using `against`.\n//\n// ### Parameters\n// - `funder`: The party providing the upfront capital.\n// - `to`: The party receiving the advance and responsible for repayment.\n// - `amount`: Total advanced principal in minor units of currency `C`.\n// - `fee`: Optional markup percentage fee charged on the advance (basis points precision).\n// - `count`: Optional number of scheduled installment repayments.\n// - `every`: Optional recurrence duration between installments (e.g. `\"P30D\"`).\n// - `first_due`: Optional date for the first installment repayment.\n// - `repayment_source`: Optional party repaying when distinct from the capital recipient.\n// - `profit_to`: Optional recipient of profit, separated from principal repayments.\n// - `dated`: Require an explicit signed date for each repayment instead of duration offsets.\n// - `against`: Optional reference to a hold instrument whose release will be carved to repay the advance.\n//\n// ### Decision ports\n// None. Repayment is driven by linked hold releases or scheduled calendar dates.\n//\n// ### Example\n// ```hsx\n// program advance_example \"Advance example\"\n// import { advance } from \"std/money_flows\"\n// party funder: business\n// party recipient: business\n// settlement advance_payment = advance {\n// funder: funder\n// to: recipient\n// amount: principal: money(SAR)\n// fee: 2.5%\n// count: 2\n// every: P30D\n// first_due: firstDueAt\n// }\n// ```\nexport instrument advance<C>(\n funder: party,\n to: party,\n amount: money<C>,\n fee: optional<percent>,\n count: optional<integer>,\n every: optional<text>,\n first_due: optional<date>,\n against: optional<ref>,\n repayment_source: optional<party>,\n profit_to: optional<party>,\n dated: optional<boolean>,\n) {\n let(amount_path): concat(\"fields.\", amount);\n let(currency_path): \"fields.currency\";\n let(funder_field): camel(concat(funder, \"_account_id\"));\n let(to_field): camel(concat(to, \"_account_id\"));\n\n agent_description: \"Reach for advance when the {funder} pays the {to} up front and is repaid afterwards, either out of a carved hold release or over scheduled repayments. Pick deposit for a refundable reservation and instant transfer when nobody repays the payer.\";\n title: sentence(instrument);\n id_prefix: prefix(instrument);\n parties {\n payer: funder;\n beneficiary: to;\n when(repayment_source) { repayment: repayment_source; }\n when(profit_to) { profit: profit_to; }\n }\n\n when(against) {\n let(hold_name): owner(against);\n summary: \"Advance to the {to} repaid by carving the {hold_name} release\";\n description: \"Advance: the {funder} disburses {amount} to the {to} and is repaid out of the {hold_name} release, which pays the {funder} in the {to}'s place\";\n fields {\n amount {\n type: money<C>;\n description: \"The advanced amount in {C} minor units, disbursed to the {to} up front\";\n }\n carve_hold_id {\n type: ref<hold_name>;\n description: \"The {hold_name} bound to this advance\";\n pattern: concat(\"^\", prefix(hold_name), \"_(sandbox|live)_[a-z0-9]{8,64}$\");\n }\n carve_recourse_1_id {\n type: ref<recourse>;\n description: \"The recourse bound to this advance\";\n pattern: \"^reco_(sandbox|live)_[a-z0-9]{8,64}$\";\n }\n }\n required: [to_field, funder_field, amount, carveHoldId, carveRecourse1Id, currency];\n lifecycle {\n states created advanced repaid;\n initial created;\n on disburse: created -> advanced;\n on settle: advanced -> repaid;\n }\n action create {\n agent_description: \"Open the advance row before any money moves. The caller supplies the {amount}, both party accounts, and the ids of the {hold_name} and the recourse that back repayment. Nothing is disbursed here.\";\n summary: concat(\"Create a \", words(instrument));\n steps: [];\n moves: [];\n }\n action disburse {\n agent_description: \"Pay the {amount} from the {funder} to the {to}. The advance must be created, the bound {hold_name} funded, and the recourse active, both matching this amount and currency. The transfer posts straight out and does not unwind.\";\n summary: \"Disburse the {amount} to the {to}\";\n requires refs: [{\n field: carveHoldId;\n match: {\n [currency_path]: currency_path;\n [amount_path]: amount_path;\n };\n statuses: [funded];\n }, {\n field: carveRecourse1Id;\n match: {\n [currency_path]: currency_path;\n [amount_path]: amount_path;\n };\n statuses: [active];\n }];\n moves: [{ amount: amount; from: payer; to: beneficiary; key: transfer; operation: create; }];\n steps: [];\n }\n action settle {\n agent_description: \"Close the advance once the {hold_name} has released to the {funder} in the {to}'s place. The advance must already be disbursed. No money moves here, the release did the repaying.\";\n summary: concat(\"Close the advance once the \", hold_name, \" has released to the \", funder);\n steps: [];\n moves: [];\n }\n }\n\n when_not(against) {\n let(repayment_role): if_eq(repayment_source, false, \"beneficiary\", \"repayment\");\n when(fee) { computes derived: { field: feeAmount; source_field: amount; rule: { kind: percentage_of; bps: basis_points(fee); }; rounding: floor; }; }\n let(repayment_fields): names(\"repayment\", count, \"Amount\");\n let(first_target): if_eq(count, 1, \"repaid\", \"repaying_1\");\n let(states_key): \"states\";\n let(first_due_field): camel(first_due);\n summary: concat(\"Advance to \", to, \" repaid over \", count, \" anchors\");\n description: concat(\"Advance: the \", funder, \" disburses \", amount, \" to the \", to, \", repaid over \", count, \" scheduled repayments plus the funder's discount\");\n fields {\n amount {\n type: money<C>;\n description: \"The advanced amount in {C} minor units, disbursed to the {to} up front\";\n }\n fee_amount {\n type: money<C>;\n when(fee) { optional: true; allow_zero: true; }\n description: concat(fee, \" of \", amount, \", the funder's discount repaid on top of the advance\");\n }\n repayable_amount {\n type: money<C>;\n description: concat(amount, \" + feeAmount: the total the repayment fields below partition exactly\");\n }\n [first_due_field] {\n type: date;\n description: concat(\"Due date of the first repayment; repayment k falls \", every, \" after its predecessor\");\n }\n for i in count {\n repayment[i]Amount { type: money<C>; description: \"Stored total for repayment {i}\"; }\n when(profit_to) {\n repayment[i]Principal { type: money<C>; description: \"Principal returned to the funder by repayment {i}\"; }\n repayment[i]Profit { type: money<C>; description: \"Profit paid to the named recipient by repayment {i}\"; }\n }\n when(dated) { repayment[i]DueAt { type: date; description: \"Signed calendar due date for repayment {i}\"; } }\n }\n }\n lifecycle {\n states created advanced;\n [states_key]: [first_target];\n initial created;\n on disburse: created -> advanced;\n on collect_repayment_1: advanced -> [first_target];\n for repayment in sub(count, 1) {\n let(index): add(repayment, 1);\n let(previous): concat(\"repaying_\", repayment);\n let(target): if_eq(index, count, \"repaid\", concat(\"repaying_\", index));\n [states_key]: [target];\n on collect_repayment_[index]: [previous] -> [target];\n }\n }\n partitions: [{\n piece_fields: repayment_fields;\n total_field: repayableAmount;\n }, {\n piece_fields: [amount, feeAmount];\n total_field: repayableAmount;\n }];\n when(profit_to) {\n partitions: { total_field: amount; piece_fields: names(\"repayment\", count, \"Principal\"); };\n partitions: { total_field: feeAmount; piece_fields: names(\"repayment\", count, \"Profit\"); };\n for i in count {\n partitions: { total_field: concat(\"repayment\", i, \"Amount\"); piece_fields: [concat(\"repayment\", i, \"Principal\"), concat(\"repayment\", i, \"Profit\")]; };\n }\n }\n action create {\n agent_description: \"Open the advance row before any money moves. The caller supplies the {amount}, the repayable total, and repayment dates. A declared fee derives the fee amount from principal. The repayment fields must partition the repayable total exactly. Nothing is disbursed here.\";\n summary: concat(\"Create a \", words(instrument));\n steps: [];\n moves: [];\n }\n action disburse {\n agent_description: \"Pay the {amount} from the {funder} to the {to} and start the repayment schedule. The advance must be created. The transfer posts straight out and does not unwind. Each repayment is collected on its own stored date anchor.\";\n summary: \"Disburse the {amount} to the {to}\";\n moves: [{ amount: amount; from: payer; to: beneficiary; key: transfer; operation: create; }];\n steps: [];\n }\n for i in count {\n let(repayment_field): concat(\"repayment\", i, \"Amount\");\n action collect_repayment_[i] {\n agent_description: \"Collect repayment {i} once on its stored due anchor, returning principal to the funder and any separately routed profit to its recipient.\";\n summary: \"Collect repayment {i} of {count}\";\n when(dated) { due: { field: concat(\"repayment\", i, \"DueAt\"); }; }\n when_not(dated) { due: { field: first_due_field; offset: scale_duration(every, sub(i, 1)); }; }\n when_not(profit_to) { moves: [{ amount: repayment_field; from: repayment_role; to: payer; key: transfer; }]; }\n when(profit_to) {\n moves: [\n { amount: concat(\"repayment\", i, \"Principal\"); from: repayment_role; to: payer; key: principal; },\n { amount: concat(\"repayment\", i, \"Profit\"); from: repayment_role; to: profit; key: profit; }\n ];\n }\n steps: [];\n }\n }\n }\n}\n"],
|
|
6
|
-
["money_flows/cancellable_booking.hsx", "module std.money_flows.cancellable_booking\n\n// Time-anchored reservation holding booking funds in escrow with dynamically quoted cancellation penalties.\n//\n// ### Purpose\n// `cancellable_booking` holds booking fees in escrow from a guest for a host until a scheduled start date.\n// It fits hospitality, travel reservations, studio rentals, event tickets, and appointments where guests\n// can cancel prior to start, but cancellation penalties depend on how close to the start date cancellation occurs.\n//\n// ### Selection guidance\n// - vs `held_payment`: Both hold money in escrow and both quote a cancellation before it is spent.\n// `cancellable_booking` prices the penalty against the time left before `starts_at`, so the charge moves\n// as the start approaches; `held_payment` has no start date and quotes one flat charge.\n// Choose `cancellable_booking` whenever cancellation fees are time-sensitive.\n// - vs `security_deposit`: `security_deposit` holds funds to cover damages claimed by the holder.\n// `cancellable_booking` holds the service fee itself and releases to the host upon `starts_at` or refunds net of penalty.\n//\n// ### Parameters\n// - `guest`: The booking customer paying the fee and receiving refunds.\n// - `host`: The service provider receiving the payout or retained cancellation penalty.\n// - `amount`: Total booking price in minor units of currency `C`.\n// - `starts_at`: Stored ISO 8601 date when the booking begins.\n// - `late_penalty_bps`: Penalty in basis points applied when cancelling within the late window.\n// - `late_within`: ISO 8601 duration defining the late window before `starts_at` (e.g. `\"P2D\"` for 2 days).\n// - `early_penalty_bps`: Penalty in basis points applied when cancelling before the late window.\n// - `offer_life`: ISO 8601 duration defining how long a quoted cancellation offer remains valid (e.g. `\"PT30M\"`).\n//\n// ### Decision ports\n// None. Action transitions are driven by guest API calls (`take`, `cancel`, `confirm`) and scheduled completion (`complete` due at `starts_at`).\n//\n// ### Example\n// ```hsx\n// program studio_booking \"Studio booking\"\n// import { cancellable_booking } from \"std/money_flows\"\n// party guest: person\n// party studio: business\n// settlement studio_session = cancellable_booking {\n// guest: guest\n// host: studio\n// amount: sessionPrice: money(SAR)\n// starts_at: startsAt\n// late_penalty_bps: 5000\n// late_within: \"P2D\"\n// early_penalty_bps: 1000\n// offer_life: \"PT30M\"\n// }\n// ```\n// ### Finite cancellation bands\n// Supply cancel_bands to select deposit/balance custody instead of quoted cancellation.\n// Each band has name, refund (full, balance or none), starts_at after the first band,\n// and supplier_bps when money is retained. The unpaid_band index selects a retained\n// deposit policy. Omit it to return the deposit; a full-refund band also returns it. price_value and supplier_cost fix the price; deposit_bps derives\n// the deposit. balance_before and confirmation_window are positive fixed durations\n// relative to departure. tax_bps applies to margin after the supplier share.\n// authority_type and supplier_role bind release to a consumed confirmation decision.\n// collects optionally cascades named actions to linked instruments on deposit.\n// acceptance optionally supplies an aggregate gate. All actions are private until\n// expose selects them. Ordered band starts and deadlines prevent a late sweep from\n// extending an earlier band's cancellation rights. Full timeout refund returns price.\n// Seven derivations suffice for three bands; larger finite policies use up to the\n// UDL bound of 64 derived amounts. shift_date(date, duration, \"before\") subtracts a\n// fixed duration; omitting the direction adds it. Invalid dates or durations refuse.\n//\nexport instrument cancellable_booking<C>(\n guest: optional<party>,\n host: optional<party>,\n amount: optional<money<C>>,\n starts_at: optional<date>,\n late_penalty_bps: optional<integer>,\n late_within: optional<text>,\n early_penalty_bps: optional<integer>,\n offer_life: optional<text>,\n cancel_bands: optional<list<block>>,\n price_value: optional<text>, supplier_cost: optional<text>, departure: optional<text>,\n balance_before: optional<text>, confirmation_window: optional<text>, unpaid_band: optional<integer>,\n acceptance: optional<block>, deposit_bps: optional<integer>, tax_bps: optional<integer>,\n authority_type: optional<text>, supplier_role: optional<text>,\n collects: optional<json>,\n) {\n when_not(cancel_bands) {\n let(guest_account_field): camel(concat(guest, \"_account_id\"));\n let(host_account_field): camel(concat(host, \"_account_id\"));\n let(reference_filter_key): \"x-hyperscale-reference-filter\";\n let(owner_id_key): \"owner.id\";\n let(owner_type_key): \"owner.type\";\n fields {\n amount { type: money<C>; description: concat(\"Booking price in minor units, held away from both the \", words(guest), \" and the \", words(host), \" until the booking ends\"); }\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n starts_at { type: date; description: \"When the booking starts; the cancellation penalty is priced against the time left before it\"; }\n [guest_account_field] { type: account<C>; description: concat(\"The \", words(guest), \" account the booking is taken from and the refund returns to\"); [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n [host_account_field] { type: account<C>; description: concat(\"The \", words(host), \" account the booking and any penalty end up in\"); [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n }\n lifecycle {\n states created held cancellation_quoted canceled settled completed;\n initial created;\n on take: created -> held;\n on complete: held | cancellation_quoted -> completed;\n on cancel: held -> cancellation_quoted;\n on confirm: cancellation_quoted -> canceled;\n on retain: canceled -> settled;\n }\n parties { beneficiary: host_account_field; payer: guest_account_field; }\n required: [amount, currency, guest_account_field, host_account_field, starts_at];\n title: sentence(instrument);\n agent_description: concat(\"Reach for cancellable booking when the \", words(guest), \" pays up front and can back out at a price they see before they agree to it. The money sits in a holding account, the penalty is priced against the time left before the start, and the quote is frozen until it expires. Pick held payment when there is no cancellation price to quote.\");\n id_prefix: prefix(instrument);\n summary: concat(\"Booking the \", words(guest), \" can cancel at a price they see before they agree to it\");\n description: \"A booking whose cancellation is quoted rather than argued: the penalty is priced against the time left before the start, frozen with the terms it was read from, and paid only if the guest confirms while the offer is still alive\";\n action create {\n agent_description: concat(\"Open the booking and provision the holding account the price sits in. No money moves. The price, the currency, the start moment, and both party accounts are fixed here. Call take next to collect from the \", words(guest), \".\");\n summary: concat(\"Open a \", words(instrument), \" and its holding account\");\n moves: [];\n steps: [{ operation: \"account.escrow.provision\"; bind: { currency: { from: instance; path: fields.currency; }; [owner_id_key]: { from: instance; path: productId; }; [owner_type_key]: { from: const; value: product; }; productId: { from: instance; path: productId; }; role: { from: const; value: product_escrow; }; }; capture: { bookingHoldingAccountId: accountId; }; }];\n }\n action take {\n agent_description: concat(\"Move the whole booking price out of the \", words(guest), \" account into the holding account. This moves money. Only a created booking takes. Neither party can spend the money until the booking completes or a cancellation is confirmed.\");\n summary: concat(\"Take the booking price from the \", words(guest), \" into the holding account\");\n moves: [{ key: booking; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.bookingHoldingAccountId; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", guest_account_field); }; }; capture: { bookingTransferId: transferId; }; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n action complete {\n summary: concat(\"Release the whole booking to the \", words(host), \" once the stay has started, even with a cancellation quote parked un-acted\");\n due { field: starts_at; }\n moves: [{ key: release; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", host_account_field); }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.bookingHoldingAccountId; }; }; capture: { releaseTransferId: transferId; }; }];\n steps: [];\n }\n action cancel {\n agent_description: \"Price the cancellation and freeze that price. No money moves. The penalty is computed from the time left before the start, and the quote expires on its own, after which a new cancel prices it again. Call confirm to spend the quote.\";\n summary: \"Price the cancellation and hold that price open\";\n quote {\n anchorField: starts_at;\n baseField: amount;\n chargeRef: cancellationPenaltyAmount;\n charges: [{ bps: late_penalty_bps; withinOffset: late_within; }, { bps: early_penalty_bps; }];\n expires { offset: offer_life; }\n fixes: [amount, guest_account_field];\n netDestinationField: guest_account_field;\n netRef: cancellationRefundAmount;\n }\n moves: []; steps: [];\n }\n action confirm {\n agent_description: concat(\"Spend the frozen quote and return the net refund to the \", words(guest), \". This moves money and does not reverse. It requires an un-expired quote from cancel, and the penalty the refund left behind is paid to the \", words(host), \" by retain.\");\n summary: concat(\"Spend the quoted cancellation and return the net to the \", words(guest));\n commit: cancel;\n moves: [{ key: refund; operation: internal_transfer.create; bind: { amount: { from: instance; path: refs.cancellationRefundAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", guest_account_field); }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.bookingHoldingAccountId; }; }; capture: { cancellationTransferId: transferId; }; }];\n steps: [];\n }\n action retain {\n agent_description: concat(\"Pay the quoted cancellation penalty out of the holding account to the \", words(host), \". This moves money and does not reverse. The booking must already be canceled, so the penalty figure is the one the quote fixed.\");\n summary: concat(\"Pay the quoted penalty that the refund left behind to the \", words(host));\n moves: [{ key: penalty; operation: internal_transfer.create; bind: { amount: { from: instance; path: refs.cancellationPenaltyAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", host_account_field); }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.bookingHoldingAccountId; }; }; capture: { penaltyTransferId: transferId; }; }];\n steps: [];\n }\n }\n when(cancel_bands) {\n let(band_count): len(cancel_bands);\n for band in cancel_bands {\n let(refund): get(band, \"refund\");\n let(not_full): if_eq(refund, \"full\", false, true);\n let(not_balance): if_eq(refund, \"balance\", false, true);\n let(not_none): if_eq(refund, \"none\", false, true);\n when(not_full) { when(not_balance) { when(not_none) {\n unsupported { code: HSX1407; message: \"Unknown cancellation refund policy\"; fix: \"Choose full, balance or none\"; }\n } } }\n }\n let(deposit_states): names(\"deposit_\", len(cancel_bands), \"\");\n let(full_states): names(\"full_\", len(cancel_bands), \"\");\n fields {\n bookingKey { type: text; min_length: 1; }\n signedTermsReference { type: text; min_length: 1; }\n currency { type: text; const: C; }\n customerAccountId { type: account<C>; \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n supplierAccountId { type: account<C>; \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n operatorAccountId { type: account<C>; \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n taxAccountId { type: account<C>; \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n authorityId { type: ref<authority_type>; optional: true; }\n supplierRole { type: text; const: supplier_role; }\n travellerCount { type: integer; minimum: 1; maximum: 20; }\n departureAt { type: date; const: departure; }\n balanceDueAt { type: date; const: shift_date(departure, balance_before, \"before\"); }\n confirmationDeadline { type: date; const: shift_date(departure, confirmation_window); }\n price { type: money<C>; const: price_value; }\n supplierCost { type: money<C>; const: supplier_cost; }\n depositAmount { type: money<C>; optional: true; allow_zero: true; }\n balanceAmount: money<C>;\n margin: money<C>;\n operatorNet: money<C>;\n marginVat { type: money<C>; optional: true; allow_zero: true; }\n for i in len(cancel_bands) {\n let(band): at(cancel_bands, i);\n let(refund_mode): get(band, \"refund\");\n let(full_refund): if_eq(refund_mode, \"full\", true, false);\n let(balance_refund): if_eq(refund_mode, \"balance\", true, false);\n let(retains): if_eq(refund_mode, \"full\", false, true);\n let(later): if_eq(i, 1, false, true);\n when(later) { band[i]Start { type: date; const: get(band, \"starts_at\"); } }\n\n when(retains) {\n band[i]Supplier { type: money<C>; optional: true; allow_zero: true; }\n let(portions): if_eq(refund_mode, \"balance\", [\"deposit\"], [\"deposit\", \"full\"]);\n for paid in portions {\n let(is_full): if_eq(paid, \"full\", true, false);\n let(portion): if_eq(refund_mode, \"balance\", \"deposit\", paid);\n let(margin_field): concat(\"band\", i, title(portion), \"Margin\");\n let(net_field): concat(\"band\", i, title(portion), \"Net\");\n let(vat_field): concat(\"band\", i, title(portion), \"Vat\");\n [margin_field]: money<C>;\n [net_field]: money<C>;\n [vat_field] { type: money<C>; optional: true; allow_zero: true; }\n }\n }\n }\n }\n parties { payer: customerAccountId; beneficiary: supplierAccountId; fee: operatorAccountId; tax: taxAccountId; }\n distinct_parties: true;\n computes derived: { field: depositAmount; source_field: price; rule: { kind: percentage_of; bps: deposit_bps; }; rounding: floor; };\n computes derived: { field: marginVat; source_field: margin; rule: { kind: percentage_of; bps: tax_bps; }; rounding: floor; };\n for i in len(cancel_bands) {\n let(band): at(cancel_bands, i);\n let(refund_mode): get(band, \"refund\");\n let(balance_refund): if_eq(refund_mode, \"balance\", true, false);\n when(balance_refund) {\n partitions: { total_field: price; piece_fields: [balanceAmount, concat(\"band\", i, \"Supplier\"), concat(\"band\", i, \"DepositNet\"), concat(\"band\", i, \"DepositVat\")]; };\n }\n }\n partitions: { total_field: price; piece_fields: [supplierCost, operatorNet, marginVat]; };\n partitions: { total_field: price; piece_fields: [depositAmount, balanceAmount]; };\n partitions: { total_field: price; piece_fields: [supplierCost, margin]; };\n partitions: { total_field: margin; piece_fields: [operatorNet, marginVat]; };\n date order: { before_field: balanceDueAt; after_field: departureAt; operator: \"<\"; };\n date order: { before_field: departureAt; after_field: confirmationDeadline; operator: \"<\"; };\n for i in len(cancel_bands) {\n let(band): at(cancel_bands, i);\n let(refund_mode): get(band, \"refund\");\n let(full_refund): if_eq(refund_mode, \"full\", true, false);\n let(balance_refund): if_eq(refund_mode, \"balance\", true, false);\n let(retains): if_eq(refund_mode, \"full\", false, true);\n let(later): if_eq(i, 1, false, true);\n let(band_start): concat(\"band\", i, \"Start\");\n when(later) {\n date order: { before_field: band_start; after_field: departureAt; operator: \"<\"; };\n let(after_second): if_eq(i, 2, false, true);\n when(after_second) { date order: { before_field: concat(\"band\", sub(i, 1), \"Start\"); after_field: band_start; operator: \"<\"; }; }\n }\n\n when(retains) {\n let(supplier_field): concat(\"band\", i, \"Supplier\");\n computes derived: { field: supplier_field; source_field: supplierCost; rule: { kind: percentage_of; bps: get(band, \"supplier_bps\"); }; rounding: floor; };\n let(portions): if_eq(refund_mode, \"balance\", [\"deposit\"], [\"deposit\", \"full\"]);\n for paid in portions {\n let(is_full): if_eq(paid, \"full\", true, false);\n let(portion): if_eq(refund_mode, \"balance\", \"deposit\", paid);\n let(margin_field): concat(\"band\", i, title(portion), \"Margin\");\n let(net_field): concat(\"band\", i, title(portion), \"Net\");\n let(vat_field): concat(\"band\", i, title(portion), \"Vat\");\n let(retained): if_eq(paid, \"deposit\", \"depositAmount\", if_eq(refund_mode, \"balance\", \"depositAmount\", \"price\"));\n computes derived: { field: vat_field; source_field: margin_field; rule: { kind: percentage_of; bps: tax_bps; }; rounding: floor; };\n partitions: { total_field: retained; piece_fields: [supplier_field, net_field, vat_field]; };\n partitions: { total_field: retained; piece_fields: [supplier_field, margin_field]; };\n partitions: { total_field: margin_field; piece_fields: [net_field, vat_field]; };\n }\n }\n }\n agent_description: \"Hold deposit and balance in one booking account. Apply a finite dated cancellation policy and release the supplier, operator and tax partition only after the named authority confirms. Refund the entire price when confirmation times out.\";\n lifecycle {\n states created awaiting_confirmation confirmed released refunded canceled expired;\n let(states_key): \"states\";\n [states_key]: concat_lists(deposit_states, full_states);\n initial created;\n on deposit: created -> deposit_1;\n on expire: created -> expired;\n for i in len(cancel_bands) {\n let(band): at(cancel_bands, i);\n let(refund_mode): get(band, \"refund\");\n let(full_refund): if_eq(refund_mode, \"full\", true, false);\n let(balance_refund): if_eq(refund_mode, \"balance\", true, false);\n let(retains): if_eq(refund_mode, \"full\", false, true);\n let(band_name): get(band, \"name\");\n let(deposit_state): concat(\"deposit_\", i);\n let(full_state): concat(\"full_\", i);\n let(balance_action): concat(\"balance_\", band_name);\n let(cancel_deposit): concat(\"cancel_deposit_\", band_name);\n let(cancel_full): concat(\"cancel_full_\", band_name);\n on [balance_action]: deposit_state -> full_state;\n on [cancel_deposit]: deposit_state -> canceled;\n on [cancel_full]: full_state -> canceled;\n let(later): if_eq(i, 1, false, true);\n when(later) {\n let(previous_deposit): names(\"deposit_\", sub(i, 1), \"\");\n let(previous_full): names(\"full_\", sub(i, 1), \"\");\n let(open_deposit): concat(\"open_deposit_\", band_name);\n let(open_full): concat(\"open_full_\", band_name);\n on [open_deposit]: previous_deposit -> deposit_state;\n on [open_full]: previous_full -> full_state;\n }\n }\n on cancel_unpaid: deposit_states -> canceled;\n on await_confirmation: full_states -> awaiting_confirmation;\n on confirm: awaiting_confirmation -> confirmed;\n on release: confirmed -> released;\n on refund_unconfirmed: full_states | awaiting_confirmation -> refunded;\n }\n caller_parked_states: { confirmed: \"Release pays the confirmed supplier and frozen partition.\"; };\n action create {\n public: none;\n agent_description: \"Freeze the booking terms and provision its holding account.\";\n unique: { namespace: instrument; by_fields: [bookingKey]; };\n deadline: { field: balanceDueAt; };\n steps: { operation: \"account.escrow.provision\"; bind: { currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; \"owner.id\": { from: instance; path: productId; }; \"owner.type\": { from: const; value: product; }; role: { from: const; value: product_escrow; }; }; capture: { heldAccountId: accountId; }; };\n }\n action deposit {\n public: none;\n agent_description: \"Collect the derived deposit after the acceptance requirements pass.\";\n when(collects) { cascade: collects; }\n when(acceptance) { requires aggregate: acceptance; }\n deadline: { field: balanceDueAt; }; port: { allowedParties: [payer]; };\n moves: { key: deposit; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", \"depositAmount\"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"fields.customerAccountId\"; }; destinationAccountId: { from: instance; path: \"refs.heldAccountId\"; }; }; };\n steps: [];\n }\n action expire { public: none; agent_description: \"Close an unfunded booking.\"; due: { field: balanceDueAt; }; requires_drained: { path: refs.heldAccountId; }; steps: []; }\n for i in len(cancel_bands) {\n let(band): at(cancel_bands, i);\n let(refund_mode): get(band, \"refund\");\n let(full_refund): if_eq(refund_mode, \"full\", true, false);\n let(balance_refund): if_eq(refund_mode, \"balance\", true, false);\n let(retains): if_eq(refund_mode, \"full\", false, true);\n let(band_name): get(band, \"name\");\n let(balance_action): concat(\"balance_\", band_name);\n let(next_index): add(i, 1);\n let(next_field): concat(\"band\", next_index, \"Start\");\n let(band_end): if_eq(i, band_count, \"departureAt\", next_field);\n let(later): if_eq(i, 1, false, true);\n action [balance_action] {\n public: none; agent_description: \"Collect the remaining balance once before its deadline.\";\n deadline: { field: balanceDueAt; }; port: { allowedParties: [payer]; };\n moves: { key: balance; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", \"balanceAmount\"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"fields.customerAccountId\"; }; destinationAccountId: { from: instance; path: \"refs.heldAccountId\"; }; }; };\n steps: [];\n }\n for paid in [\"deposit\", \"full\"] {\n let(is_full): if_eq(paid, \"full\", true, false);\n when(later) {\n let(open_action): concat(\"open_\", paid, \"_\", band_name);\n action [open_action] { public: none; agent_description: \"Open the next dated cancellation band.\"; due: { field: concat(\"band\", i, \"Start\"); }; steps: []; }\n }\n let(portion): if_eq(refund_mode, \"balance\", \"deposit\", paid);\n let(cancel_action): concat(\"cancel_\", paid, \"_\", band_name);\n action [cancel_action] {\n public: none; agent_description: \"Settle this paid amount under the active cancellation band.\";\n deadline: { field: band_end; }; port: { allowedParties: [payer]; };\n when(full_refund) { moves: { key: refund; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", if_eq(paid, \"deposit\", \"depositAmount\", \"price\")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.customerAccountId\"; }; }; }; }\n when(retains) {\n when(is_full) { when(balance_refund) { moves: { key: refund; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", \"balanceAmount\"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.customerAccountId\"; }; }; }; } }\n moves: { key: supplier; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", concat(\"band\", i, \"Supplier\")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.supplierAccountId\"; }; }; };\n moves: { key: operator; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", concat(\"band\", i, title(portion), \"Net\")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.operatorAccountId\"; }; }; };\n moves: { key: vat; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", concat(\"band\", i, title(portion), \"Vat\")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.taxAccountId\"; }; }; };\n }\n steps: [];\n }\n }\n }\n action cancel_unpaid {\n public: none; agent_description: \"Settle the deposit under the declared unpaid-balance band and end the remaining obligation.\";\n due: { field: balanceDueAt; };\n when_not(unpaid_band) { moves: { key: refund; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: \"fields.depositAmount\"; }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.customerAccountId\"; }; }; }; }\n when(unpaid_band) {\n let(unpaid_policy): at(cancel_bands, unpaid_band);\n let(unpaid_full_refund): if_eq(get(unpaid_policy, \"refund\"), \"full\", true, false);\n when(unpaid_full_refund) { moves: { key: refund; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: \"fields.depositAmount\"; }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.customerAccountId\"; }; }; }; }\n when_not(unpaid_full_refund) {\n moves: { key: supplier; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", concat(\"band\", unpaid_band, \"Supplier\")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.supplierAccountId\"; }; }; };\n moves: { key: operator; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", concat(\"band\", unpaid_band, \"DepositNet\")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.operatorAccountId\"; }; }; };\n moves: { key: vat; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", concat(\"band\", unpaid_band, \"DepositVat\")); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.taxAccountId\"; }; }; };\n }\n }\n steps: [];\n }\n action await_confirmation { public: none; agent_description: \"Open confirmation at the booked departure.\"; due: { field: departureAt; }; steps: []; }\n action confirm {\n public: none; agent_description: \"Consume the named supplier's approved decision for this booking before timeout.\";\n deadline: { field: confirmationDeadline; };\n input: { type: object; additionalProperties: false; properties: { authorityId: { type: string; pattern: concat(\"^\", prefix(authority_type), \"_(sandbox|live)_[a-z0-9]{8,64}$\"); }; }; required: [authorityId]; };\n updates: [authorityId];\n requires refs: { field: authorityId; statuses: [approved]; match: { instrumentInstanceId: fields.requestInstanceId; \"fields.supplierAccountId\": fields.decidingActorAccountId; \"fields.supplierRole\": fields.decidingRole; }; attests: { instrument: fields.requestInstrumentId; party: decider; action: fields.requestAction; digest: fields.inputDigest; role: fields.decidingRole; expires_at: fields.expiresAt; consume: consume; }; };\n steps: [];\n }\n action release {\n public: none; agent_description: \"Pay supplier cost, operator net and VAT after confirmation.\";\n moves: { key: supplier; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", \"supplierCost\"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.supplierAccountId\"; }; }; };\n moves: { key: operator; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", \"operatorNet\"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.operatorAccountId\"; }; }; };\n moves: { key: vat; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", \"marginVat\"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.taxAccountId\"; }; }; };\n steps: [];\n }\n action refund_unconfirmed {\n public: none; agent_description: \"Return the entire booking price when supplier confirmation times out.\";\n due: { field: confirmationDeadline; };\n moves: { key: refund; operation: \"internal_transfer.create\"; bind: { amount: { from: instance; path: concat(\"fields.\", \"price\"); }; currency: { from: instance; path: fields.currency; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.heldAccountId\"; }; destinationAccountId: { from: instance; path: \"fields.customerAccountId\"; }; }; };\n steps: [];\n }\n }\n}\n"],
|
|
7
|
-
["money_flows/captured_payment.hsx", "module std.money_flows.captured_payment\n\n// Two-phase authorization and capture payment flow for card and merchant processing.\n//\n// ### Purpose\n// `captured_payment` reserves funds against a payer's account and allows the payee to capture the authorized\n// balance in one or multiple slices before a reservation expiry date (`reserve_until`). It fits ecommerce checkouts,\n// card processing, hotel authorizations, and pay-at-pump fuel payments where final amounts vary or settle later.\n//\n// ### Selection guidance\n// - vs `instant_transfer`: `instant_transfer` immediately transfers money from payer to payee in a single irreversible\n// step without reservation or settlement delays. `captured_payment` separates authorization from capture, allowing\n// incremental captures, voids, amount corrections via the `correction` port, and external network reversals via `external_reversal`.\n// - vs `held_payment`: `held_payment` holds the full amount in third-party escrow pending release. `captured_payment`\n// reserves funds directly on payer balance and settles incrementally directly to payee.\n//\n// ### Parameters\n// - `payer`: Party whose account balance is reserved during authorization.\n// - `payee`: Beneficiary party capturing authorized funds.\n// - `amount`: Maximum authorized reservation in minor units of currency `C`.\n// - `reserve_until`: Expiration date for the authorization hold.\n// - `correction`: Condition port allowing post-settlement amount corrections.\n// - `external_reversal`: Condition port allowing external processor chargebacks/reversals.\n// - `derived_amount`: Optional block declaring percentage fee calculation.\n//\n// ### Decision ports\n// - `correction`: Condition allowing payee or processor to submit an amount correction after settlement.\n// - `external_reversal`: Condition allowing bank or card network to execute an external reversal.\n//\n// ### Example\n// ```hsx\n// program captured_payment_example \"Captured payment example\"\n// import { captured_payment } from \"std/money_flows\"\n// party payer: person\n// party payee: business\n// settlement card_payment = captured_payment {\n// payer: payer\n// payee: payee\n// amount: authorizedAmount: money(SAR)\n// reserve_until: reserveUntil\n// correction: port correct_capture\n// external_reversal: port reverse_capture within P14D\n// }\n// port correct_capture { allowed: [payee] }\n// port reverse_capture {\n// allowed: [payee]\n// shape: { externalReference: text }\n// }\n// ```\nexport instrument captured_payment<C>(payer: party, payee: party, amount: money<C>, reserve_until: date, correction: condition, external_reversal: condition, derived_amount: optional<block>) {\n let(correction_name): correction;\n let(reversal_name): external_reversal;\n let(reversal_window): external_reversal_within;\n let(instrument_words): words(instrument);\n when(derived_amount) {\n let(derived_field): get(derived_amount, \"field\");\n let(derived_source): get(derived_amount, \"source\");\n let(derived_rule): get(derived_amount, \"rule\");\n let(derived_bearer): get(derived_amount, \"bearer\");\n }\n agent_description: \"Reach for captured payment when the {payee} reserves against the {payer} first and then takes the money in slices before {reserve_until}. Pick instant transfer when the whole amount moves at once, and deposit when the reservation is claimed or returned whole.\";\n summary: \"Capture reservation from {payer} to {payee}\";\n title: sentence(instrument);\n description: \"Payer reservation captured by the payee in slices within a fixed window\";\n fields {\n when(derived_amount) {\n let(fee_collection_key): \"x-hyperscale-fee-collection-port\";\n let(reference_filter_key): \"x-hyperscale-reference-filter\";\n platformAccountId {\n type: account<C>;\n description: \"The product fee collection account\";\n [fee_collection_key]: true;\n [reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; };\n }\n }\n amount { type: money<C>; description: \"Maximum captured amount in {C} minor units\"; }\n reserve_until { type: date; description: \"Reservation expiry that releases any uncaptured remainder\"; }\n reversalUntil { type: date; description: \"Machine-owned external reversal cutoff anchored when settlement completes\"; optional: true; }\n when(derived_amount) {\n [derived_field] { type: money<C>; description: \"Machine-computed {derived_rule} of {derived_source}; callers never supply it\"; pattern: \"^[1-9][0-9]{0,17}$\"; optional: true; }\n }\n }\n lifecycle {\n states created authorized partially_captured settled voided expired corrected reversed;\n initial created;\n on authorize: created -> authorized;\n on capture: authorized -> partially_captured;\n on capture_more: partially_captured -> partially_captured;\n on settle: authorized | partially_captured -> settled;\n on void: authorized -> voided;\n on expire: authorized -> expired;\n on settle_on_expiry: partially_captured -> settled;\n on [correction_name]: settled -> corrected;\n on [reversal_name]: settled -> reversed;\n }\n parties {\n payer: payer;\n beneficiary: payee;\n }\n when(derived_amount) {\n computes derived: { field: derived_field; source_field: derived_source; rounding: floor; rule: { kind: percentage_of; bps: derived_rule; }; };\n }\n action create {\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: \"Open the payment row before anything is reserved. The caller supplies the {amount}, the currency, and both party accounts. This reserves nothing, call authorize next.\";\n summary: \"Create a {instrument_words}\";\n when(derived_amount) {\n moves: [{ key: \"derived_amount\"; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", derived_field); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: create; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", camel(derived_bearer), \"AccountId\"); }; }; capture: { createDerivedAmountTransferId: transferId; }; }];\n }\n steps: [];\n }\n action authorize {\n agent_description: \"Reserve the {amount} against the {payer} in the {payee}'s favor until {reserve_until}. The payment must be created. The money is held, not paid: capture and settle post it, void and expiry release it.\";\n summary: \"Reserve the {amount} until {reserve_until}\";\n moves: [{ key: reservation; amount: amount; from: payer; to: beneficiary; operation: reserve; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n action capture {\n agent_description: \"Post the first capture slice out of the live reservation. The payment must be authorized and the caller passes captureAmount in minor units. The slice settles for good and void can no longer release the reservation. Call before {reserve_until}.\";\n summary: \"Post one strict partial capture slice\";\n deadline { field: reserve_until; };\n input { type: object; additional_properties: false; properties: { captureAmount: { description: \"Positive partial capture amount in minor units\"; pattern: \"^[1-9][0-9]{0,17}$\"; type: string; }; }; required: [captureAmount]; }\n moves: [{ key: post; operation: internal_transfer.post; bind: { amount: { from: input; path: captureAmount; }; currency: { from: instance; path: fields.currency; }; postMode: { from: const; value: partial_only; }; transferId: { from: instance; path: refs.authorizeReservationTransferId; }; }; capture: { capturedAmount: postedAmount; }; }];\n sandbox_failure_point: \"release\";\n steps: [];\n }\n action capture_more {\n agent_description: \"Post another capture slice out of a partly captured reservation. Call this only after capture took the first slice. Each slice settles for good, and the uncaptured remainder still releases at {reserve_until}.\";\n summary: \"Post another strict partial capture slice\";\n deadline { field: reserve_until; };\n input { type: object; additional_properties: false; properties: { captureAmount: { description: \"Positive partial capture amount in minor units\"; pattern: \"^[1-9][0-9]{0,17}$\"; type: string; }; }; required: [captureAmount]; }\n moves: [{ key: post; operation: internal_transfer.post; bind: { amount: { from: input; path: captureAmount; }; currency: { from: instance; path: fields.currency; }; postMode: { from: const; value: partial_only; }; transferId: { from: instance; path: refs.authorizeReservationTransferId; }; }; capture: { capturedAmount: postedAmount; }; }];\n steps: [];\n }\n action settle {\n agent_description: \"Post the whole reserved remainder and finish the payment. The payment must be authorized or partly captured. The money settles and the external reversal window opens on reversalUntil.\";\n summary: \"Post the full reserved remainder and settle\";\n deadline { field: reserve_until; };\n sets(at) { field: reversalUntil; offset: reversal_window; };\n moves: [{ key: post; operation: internal_transfer.post; bind: { transferId: { from: instance; path: refs.authorizeReservationTransferId; }; }; capture: { capturedAmount: postedAmount; }; }];\n steps: [];\n }\n action void {\n agent_description: \"Release the reservation whole and cancel the payment. Only an authorized reservation with nothing captured can void. Once any slice is captured, settle it or let it expire instead.\";\n summary: \"Release an entirely uncaptured reservation\";\n moves: [{ key: void; operation: internal_transfer.void; bind: { reason: { from: const; value: \"Reservation voided before any capture\"; }; transferId: { from: instance; path: refs.authorizeReservationTransferId; }; }; }];\n steps: [];\n }\n action expire {\n summary: \"Release an uncaptured reservation at expiry\";\n due { field: reserve_until; };\n moves: [{ key: void; operation: internal_transfer.void; bind: { reason: { from: const; value: \"Uncaptured reservation expired\"; }; transferId: { from: instance; path: refs.authorizeReservationTransferId; }; }; }];\n steps: [];\n }\n action settle_on_expiry {\n summary: \"Release the uncaptured remainder and settle captured slices\";\n due { field: reserve_until; };\n sets(at) { field: reversalUntil; offset: reversal_window; };\n moves: [{ key: void; operation: internal_transfer.void; bind: { reason: { from: const; value: \"Uncaptured remainder released at expiry\"; }; transferId: { from: instance; path: refs.authorizeReservationTransferId; }; }; }];\n steps: [];\n }\n action [correction_name] {\n let(metadata_clawback): \"metadata.clawbackOfTransferId\";\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n let(correction_capture): camel(concat(correction_name, \"_transfer_transfer_id\"));\n agent_description: \"Return the whole captured amount to the {payer} after the payment settled. Only a settled payment corrects, and only the party the product authorized may call it. The money moves back and there is no undo.\";\n summary: \"Return the full captured amount on payee correction\";\n when(correction_fields) {\n input {\n type: object;\n additional_properties: false;\n properties {\n for input_name in keys(correction_fields) {\n [input_name]: get(correction_fields, input_name);\n }\n }\n required: keys(correction_fields);\n }\n capture_input: {\n for item in keys(correction_fields) { [item]: item; }\n };\n }\n port { allowed_parties: correction_allowed; };\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: refs.capturedAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payer, \"AccountId\"); }; [metadata_clawback]: { from: instance; path: refs.authorizeReservationTransferId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: correction_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payee, \"AccountId\"); }; }; capture: { [correction_capture]: transferId; }; }];\n steps: [];\n }\n action [reversal_name] {\n let(metadata_clawback): \"metadata.clawbackOfTransferId\";\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n let(reversal_capture): camel(concat(reversal_name, \"_transfer_transfer_id\"));\n agent_description: \"Return the whole captured amount to the {payer} on an external reversal. The payment must be settled and the call must land before reversalUntil. The caller passes externalReference, which stays in the receipt. There is no undo.\";\n summary: \"Return the full captured amount on an external reversal\";\n deadline { field: reversalUntil; };\n when(external_reversal_fields) {\n capture_input: {\n for item in keys(external_reversal_fields) { [item]: item; }\n };\n input {\n type: object;\n additional_properties: false;\n properties {\n for input_name in keys(external_reversal_fields) {\n [input_name]: get(external_reversal_fields, input_name);\n }\n }\n required: keys(external_reversal_fields);\n }\n }\n port { allowed_parties: external_reversal_allowed; };\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: refs.capturedAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payer, \"AccountId\"); }; [metadata_clawback]: { from: instance; path: refs.authorizeReservationTransferId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: reversal_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payee, \"AccountId\"); }; }; capture: { [reversal_capture]: transferId; }; }];\n steps: [];\n }\n}\n"],
|
|
8
|
-
["money_flows/conditional_disbursement.hsx", "module std.money_flows.conditional_disbursement\n\n// Capped disbursement from a source party to a destination party gated on stored external decision evidence.\n//\n// ### Purpose\n// `conditional_disbursement` manages evidence-contingent payouts subject to a cumulative cap.\n// It fits insurance claim settlements, grant tranches, subsidy distributions, and escrow milestones\n// where each approved payment requires explicit external evidence and the total paid must not exceed `cap`.\n//\n// ### Selection guidance\n// - vs `advance`: `conditional_disbursement` disburses non-repayable funds against external evidence\n// up to a declared cap. `advance` pays money up front with the expectation of repayment through carved\n// hold releases or scheduled installments.\n// - vs `instant_transfer`: `instant_transfer` moves money immediately with no evidence gate or cap.\n// `conditional_disbursement` requires an external decision port and evidence reference before any child amount moves.\n//\n// ### Parameters\n// - `source`: The funding party providing the money.\n// - `destination`: The beneficiary party receiving approved disbursements.\n// - `cap`: Maximum total amount that can be disbursed across all child approvals in minor units of currency `C`.\n// - `amount`: Binding name for child approval amount values.\n// - `decision`: Port conditioning approval, requiring evidence reference.\n//\n// ### Decision ports\n// - `decision`: External port providing decision evidence required to approve child disbursement amounts.\n//\n// ### Example\n// ```hsx\n// program conditional_disbursement_example \"Conditional disbursement example\"\n// import { conditional_disbursement } from \"std/money_flows\"\n// party source: business\n// party claimant: person\n// settlement claim_payment = conditional_disbursement {\n// source: source\n// destination: claimant\n// cap: policyLimit: money(SAR)\n// amount: approvedAmount: money(SAR)\n// decision: port approve_claim\n// }\n// port approve_claim {\n// allowed: [source]\n// shape: { evidenceReference: text }\n// }\n// ```\nexport instrument conditional_disbursement<C>(\n source: party,\n destination: party,\n cap: money<C>,\n amount: money<C>,\n decision: condition,\n) {\n let(child_id): concat(instrument, \"_approved_amount\");\n let(child_amount): \"amount\";\n let(parent_field): camel(concat(instrument, \"_id\"));\n let(source_field): camel(concat(source, \"_account_id\"));\n let(destination_field): camel(concat(destination, \"_account_id\"));\n let(currency_path): \"fields.currency\";\n let(source_path): concat(\"fields.\", source_field);\n let(destination_path): concat(\"fields.\", destination_field);\n\n title: sentence(instrument);\n agent_description: \"Reach for conditional disbursement when the {source} pays the {destination} only against stored external evidence and the payouts must stay under {cap}. Each approved amount is its own child row. Pick instant transfer when no evidence gates the payment.\";\n summary: \"Capped disbursement to {destination}\";\n description: \"Capped disbursement controlled by stored external evidence\";\n id_prefix: prefix(instrument);\n fields {\n cap {\n type: money<C>;\n description: \"Disbursement cap in {C} minor units\";\n }\n }\n required: [destination_field, source_field, currency, cap];\n lifecycle {\n states submitted denied;\n initial submitted;\n on deny: submitted -> denied;\n }\n parties {\n beneficiary: destination;\n payer: source;\n }\n action create {\n agent_description: \"Open the capped disbursement before any amount is approved. The caller supplies the {cap}, the currency, and the {source} and {destination} accounts. No money moves and nothing is approved yet.\";\n summary: \"Submit the capped disbursement\";\n steps: [];\n moves: [];\n }\n action deny {\n agent_description: \"Refuse the whole disbursement without moving money. Every child amount under it must still be unapproved, so call this before any approval lands. The caller passes evidenceReference, which stays in the receipt. Denial is final.\";\n summary: \"Record a denial without moving money\";\n when(decision_fields) {\n input {\n type: object;\n additional_properties: false;\n properties {\n for input_name in keys(decision_fields) {\n [input_name]: get(decision_fields, input_name);\n }\n }\n required: keys(decision_fields);\n }\n capture_input: {\n for item in keys(decision_fields) { [item]: item; }\n };\n }\n port { allowed_parties: decision_allowed; };\n requires aggregate: {\n check: { kind: all_in; };\n instrument_id: child_id;\n over: children;\n ref_field: parent_field;\n statuses: [created];\n };\n steps: [];\n moves: [];\n }\n\n instruments {\n instrument {\n id: child_id;\n generatedPrefix: true;\n title: concat(sentence(instrument), \" Approved Amount\");\n agent_description: concat(\"Reach for this child row to hold one evidence-gated amount under a \", words(instrument), \". The parent carries the {cap}, this row carries one approved amount and pays it once.\");\n summary: \"Approved amount under {instrument}\";\n description: \"One evidence-gated amount under {instrument}\";\n fields {\n [child_amount] {\n type: money<C>;\n description: \"Approved amount under the parent cap\";\n }\n currency {\n type: text;\n description: \"ISO 4217 currency code\";\n max_length: 3;\n min_length: 3;\n pattern: \"^[A-Z]{3}$\";\n }\n [parent_field] {\n type: ref<instrument>;\n description: \"The exact {instrument}\";\n pattern: concat(\"^\", prefix(instrument), \"_(sandbox|live)_[a-z0-9]{8,64}$\");\n }\n }\n required: [destination_field, source_field, child_amount, currency, parent_field];\n lifecycle {\n states created approved paid;\n initial created;\n on approve: created -> approved;\n on pay: approved -> paid;\n }\n parties {\n beneficiary: destination;\n payer: source;\n }\n action create {\n agent_description: concat(\"Create one candidate amount under the parent \", words(instrument), \". The parent must still be submitted and the currency and both accounts must match it. No money moves and the amount is not approved yet.\");\n summary: \"Create one candidate amount under the parent\";\n requires refs: {\n bind: {\n currency: currency_path;\n [destination_field]: destination_path;\n [source_field]: source_path;\n };\n field: parent_field;\n statuses: [submitted];\n unique: true;\n };\n steps: [];\n moves: [];\n }\n action approve {\n agent_description: \"Store one externally approved amount under the parent cap. The row must be created, the parent still submitted, and the already approved and paid amounts plus this one must stay within {cap}. The caller passes evidenceReference. No money moves yet.\";\n summary: \"Store one externally approved amount under the cap\";\n input: {\n additional_properties: false;\n properties: {\n evidence_reference: {\n description: \"Required decision reference retained in the operation receipt\";\n max_length: 180;\n min_length: 1;\n type: \"string\";\n };\n };\n required: [evidenceReference];\n type: \"object\";\n };\n capture_input: { decision_evidence_reference: evidenceReference; };\n port: { allowed_parties: decision_allowed; };\n requires exposure: {\n amount_field: child_amount;\n anchor_field: parent_field;\n cap_field: cap;\n cap_on_anchor: true;\n child_instrument_id: child_id;\n statuses: [approved, paid];\n };\n requires refs: {\n field: parent_field;\n match: {\n [currency_path]: currency_path;\n [destination_path]: destination_path;\n [source_path]: source_path;\n };\n statuses: [submitted];\n };\n steps: [];\n moves: [];\n }\n action pay {\n agent_description: \"Pay the stored approved amount to the {destination} once. The row must be approved and the parent still submitted. The transfer posts straight out of the {source} and does not unwind.\";\n summary: \"Pay the stored approved amount once\";\n requires refs: {\n field: parent_field;\n match: {\n [currency_path]: currency_path;\n [destination_path]: destination_path;\n [source_path]: source_path;\n };\n statuses: [submitted];\n };\n moves: [{ amount: child_amount; from: payer; to: beneficiary; key: payout; operation: create; }];\n steps: [];\n }\n }\n }\n}\n"],
|
|
9
|
-
["
|
|
10
|
-
["money_flows/held_payment.hsx", "module std.money_flows.held_payment\n\n// Escrow settlement holding funds from a payer before releasing to a payee upon a verified condition or deadline.\n//\n// ### Purpose\n// `held_payment` holds customer funds in a dedicated product escrow account away from both payer and payee.\n// It fits milestone-gated commerce, vehicle escrow, contractor holdbacks, and goods purchases where funds\n// must remain reserved until delivery confirmation or inspection.\n//\n// ### Selection guidance\n// - vs `cancellable_booking`: Both hold funds in custody and both can quote a cancellation before it is\n// spent. `held_payment` quotes one flat charge declared by `cancel_charge_bps`, because it has no scheduled\n// start to price against, and `on_cancel` remains the way to unwind it on static splits instead.\n// Choose `cancellable_booking` when the charge must follow the time left before a scheduled start date.\n// - vs `security_deposit`: `held_payment` releases or cancels the principal according to predefined splits.\n// Choose `security_deposit` when the holder must assess damages and claim an arbitrary partial amount\n// via a `decided amount` clause while returning the unspent remainder to the payer.\n// - vs `swap`: `held_payment` is a one-way transfer from payer to payee. Choose `swap` for bilateral or\n// multi-party atomic exchanges where all parties must fund their legs into escrow before simultaneous release.\n// - vs `premium_forward`: Choose `premium_forward` for insurance premium collection requiring carrier policy\n// binding conditions, broker commission retention, policy endorsements, and lapse schedules.\n//\n// ### Parameters\n// - `payer`: The funding party providing the money.\n// - `payee`: The beneficiary party receiving the released funds.\n// - `amount`: Total amount in minor units of currency `C`.\n// - `release`: Condition required to release funds. Supports decision ports (`port <name>`), date deadlines\n// (`at(<date>)`), or disjunctions (`port <name> | at(<date>)`).\n// - `fees`: Optional block declaring percentage or fixed fee cuts, e.g. `{ buyer: 1%, seller: 2% }`.\n// - `on_cancel`: Optional block defining refund splits if cancelled while funded, e.g. `(funded) { buyer: 90%, seller: 10% }`.\n// - `derived_amount`: Optional block declaring machine-derived fees calculated as a percentage of another field.\n// - `release_to`: Optional third-party destination for release.\n// - `whole_amount`: Optional block enabling single-action funding and release of principal plus on-top fee.\n// - `release_action`: Optional custom name for the release action.\n// - `whole_fee`: Optional money amount for the whole-amount fee.\n// - `reference`: Optional string reference stored on the instance.\n// - `upstream`: Optional reference to a parent instrument.\n// - `id_prefix_override`: Optional custom prefix for generated instrument IDs.\n// - `cancel_charge_bps`: Optional cancellation charge in basis points. Declaring it gives the settlement a\n// quoted cancellation: `quote_cancellation` prices the charge and the refund and freezes both,\n// `cancel` pays the refund to the payer, and `retain_cancellation_charge` pays the charge to the payee.\n// A zero charge keeps the flow with a zero fee.\n// - `cancel_offer_life`: ISO 8601 duration a cancellation quote stays open, required with `cancel_charge_bps`.\n//\n// - `private_actions`: Suppress automatic aliases. Publish chosen actions with `expose`.\n//\n// ### Decision ports\n// - `release`: Port deciding release authorization, answered by allowed parties declared in the port.\n//\n// ### Example\n// ```hsx\n// program held_payment_example \"Held payment example\"\n// import { held_payment } from \"std/money_flows\"\n// party buyer: person\n// party seller: business\n// settlement sale = held_payment {\n// payer: buyer\n// payee: seller\n// amount: price: money(SAR)\n// fees { buyer: 1% }\n// on_cancel(funded) { buyer: 100% }\n// release: port confirm_delivery | at(releaseDueAt)\n// }\n// port confirm_delivery { allowed: [buyer] }\n// ```\nexport 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>) {\n let(release_name): release;\n let(release_actor_text): if_eq(len(release_allowed), 1, words(at(release_allowed, 1)), concat(\"a party allowed by \", release));\n let(payer_account_field): camel(concat(payer, \"_account_id\"));\n let(payee_account_field): camel(concat(payee, \"_account_id\"));\n let(release_to_account_field): camel(concat(release_to, \"_account_id\"));\n let(payer_fee): get(fees, payer);\n let(payee_fee): get(fees, payee);\n let(payer_fee_kind): kind(payer_fee);\n let(payee_fee_kind): kind(payee_fee);\n let(has_cancel_quote): if_eq(kind(cancel_charge_bps), \"boolean\", false, true);\n let(has_charge): if_eq(cancel_charge_bps, 0, false, true);\n when_eq(payee_fee_kind, binding) {\n when(on_cancel) {\n unsupported {\n code: HSX1110;\n message: \"exact payee fees cannot combine with on_cancel yet\";\n fix: \"remove on_cancel or use a percentage payee fee\";\n }\n }\n }\n when(has_cancel_quote) {\n when(on_cancel) {\n unsupported {\n code: HSX1110;\n message: \"a quoted cancellation cannot combine with on_cancel splits\";\n fix: \"drop on_cancel and let cancel_charge_bps price the cancellation\";\n }\n }\n when(payee_fee) {\n unsupported {\n code: HSX1110;\n message: \"a quoted cancellation cannot combine with a payee fee\";\n fix: \"drop the payee fee, or unwind through on_cancel splits instead of a quote\";\n }\n }\n when(whole_amount) {\n unsupported {\n code: HSX1110;\n message: \"a quoted cancellation cannot combine with whole_amount\";\n fix: \"drop whole_amount, which cancels the whole hold without quoting it\";\n }\n }\n when_not(cancel_offer_life) {\n unsupported {\n code: HSX1110;\n message: \"a quoted cancellation needs cancel_offer_life\";\n fix: \"declare how long the quote stays open, for example PT30M\";\n }\n }\n }\n when(cancel_offer_life) {\n when_not(has_cancel_quote) {\n unsupported {\n code: HSX1110;\n message: \"cancel_offer_life needs cancel_charge_bps to quote\";\n fix: \"declare the cancellation charge in basis points, or drop cancel_offer_life\";\n }\n }\n }\n let(payer_bps): basis_points(payer_fee);\n let(payee_bps): basis_points(payee_fee);\n let(net_bps): sub(10000, payee_fee);\n let(net_percent): percent_text(net_bps);\n let(cancel_payer): get(on_cancel, payer);\n let(cancel_payee): get(on_cancel, payee);\n let(cancel_payer_bps): basis_points(cancel_payer);\n let(cancel_payee_bps): basis_points(cancel_payee);\n let(cancel_splits): lt(1, len(on_cancel));\n let(piece1_bps): min(net_bps, cancel_payer_bps);\n let(piece3_bps): min(payee_bps, cancel_payee_bps);\n let(piece2_bps): sub(sub(10000, piece1_bps), piece3_bps);\n let(piece1_percent): percent_text(piece1_bps);\n let(piece2_percent): percent_text(piece2_bps);\n let(piece3_percent): percent_text(piece3_bps);\n let(carved_spans): lt(cancel_payee_bps, payee_bps);\n let(derived_field): get(derived_amount, \"field\");\n let(derived_source): get(derived_amount, \"source\");\n let(derived_rule): get(derived_amount, \"rule\");\n let(derived_bearer): get(derived_amount, \"bearer\");\n agent_description: concat(\"Reach for \", words(instrument), \" when the \", words(payer), \" must commit money up front and nobody is paid until \", release, \" fires. Funding moves \", amount, \" into this settlement's own escrow account, where it stays until a release or an unwind pays it out. A straight-through payment with no custody step is a different shape.\");\n when_not(whole_amount) {\n summary: concat(\"Escrow-held payment from \", words(payer), \" to \", words(payee));\n title: sentence(instrument);\n when_not(release_to) { description: concat(\"Held payment: the \", words(payer), \" funds \", amount, \" into this settlement's own escrow; \", release_actor_text, \" confirms through \", release, \" to release\"); }\n when(release_to) { description: concat(\"Held payment: the \", words(payer), \" funds \", amount, \" into this settlement's own escrow; \", release_actor_text, \" confirms through \", release, \" to release to the \", words(release_to), \", whose advance the \", words(payee), \" repays out of it\"); }\n fields {\n let(fee_collection_key): \"x-hyperscale-fee-collection-port\";\n let(reference_filter_key): \"x-hyperscale-reference-filter\";\n when(release_to) {\n [release_to_account_field] { type: account<C>; description: concat(\"The \", words(release_to), \" account\"); [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n }\n platformAccountId {\n type: account<C>;\n description: \"The product fee collection account\";\n [fee_collection_key]: true;\n [reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; };\n }\n when_not(payee_fee) {\n when_not(cancel_splits) { amount { type: money<C>; description: \"The held amount in {C} minor units, funded and paid out whole\"; } }\n when(cancel_splits) {\n amount { type: money<C>; description: \"The gross held amount in {C} minor units; the piece fields below partition it exactly\"; }\n piece1Amount { type: money<C>; description: concat(cancel_payer, \" of \", amount, \" (carries the integer-division remainder): released to the \", payee, \"; on cancellation to the \", payer, \". Computed as floor(\", amount, \" * \", cancel_payer_bps, \" / 10000) in \", C, \" minor units\"); pattern: \"^[1-9][0-9]{0,17}$\"; }\n piece2Amount { type: money<C>; description: concat(cancel_payee, \" of \", amount, \": released to the \", payee, \"; on cancellation to the \", payee, \". Computed as floor(\", amount, \" * \", cancel_payee_bps, \" / 10000) in \", C, \" minor units\"); pattern: \"^[1-9][0-9]{0,17}$\"; }\n }\n }\n when(payee_fee) {\n amount { type: money<C>; description: \"The gross held amount in {C} minor units; the piece fields below partition it exactly\"; }\n when_not(on_cancel) {\n when_eq(payee_fee_kind, percent) {\n piece1Amount { type: money<C>; description: concat(net_percent, \" of \", amount, \" (carries the integer-division remainder): released to the \", payee, \". Computed as floor(\", amount, \" * \", net_bps, \" / 10000) in \", C, \" minor units\"); pattern: \"^[1-9][0-9]{0,17}$\"; }\n piece2Amount { type: money<C>; description: concat(payee_fee, \" of \", amount, \": released to the platform. Computed as floor(\", amount, \" * \", payee_bps, \" / 10000) in \", C, \" minor units\"); pattern: \"^[1-9][0-9]{0,17}$\"; optional: true; }\n }\n when_eq(payee_fee_kind, binding) {\n piece1Amount { type: money<C>; description: concat(\"The remainder of \", amount, \" after the exact \", payee_fee, \" fee; released to the \", payee); pattern: \"^[1-9][0-9]{0,17}$\"; }\n [payee_fee] { type: payee_fee; description: concat(\"Immutable exact \", payee, \" fee in \", C, \" minor units; released to the platform\"); pattern: \"^[1-9][0-9]{0,17}$\"; }\n }\n }\n when(on_cancel) {\n piece1Amount { type: money<C>; description: concat(piece1_percent, \" of \", amount, \" (carries the integer-division remainder): released to the \", payee, \"; on cancellation to the \", payer, \". Computed as floor(\", amount, \" * \", piece1_bps, \" / 10000) in \", C, \" minor units\"); pattern: \"^[1-9][0-9]{0,17}$\"; }\n when(carved_spans) {\n piece2Amount { type: money<C>; description: concat(piece2_percent, \" of \", amount, \": released to the platform; on cancellation to the \", payer, \". Computed as floor(\", amount, \" * \", piece2_bps, \" / 10000) in \", C, \" minor units\"); pattern: \"^[1-9][0-9]{0,17}$\"; }\n piece3Amount { type: money<C>; description: concat(piece3_percent, \" of \", amount, \": released to the platform; on cancellation to the \", payee, \". Computed as floor(\", amount, \" * \", piece3_bps, \" / 10000) in \", C, \" minor units\"); pattern: \"^[1-9][0-9]{0,17}$\"; }\n carvedFeeAmount { type: money<C>; description: \"Machine-owned total of the 2 carved platform fee pieces\"; pattern: \"^[1-9][0-9]{0,17}$\"; optional: true; }\n }\n when_not(carved_spans) {\n piece2Amount { type: money<C>; description: concat(piece2_percent, \" of \", amount, \": released to the \", payee, \"; on cancellation to the \", payee, \". Computed as floor(\", amount, \" * \", piece2_bps, \" / 10000) in \", C, \" minor units\"); pattern: \"^[1-9][0-9]{0,17}$\"; }\n piece3Amount { type: money<C>; description: concat(piece3_percent, \" of \", amount, \": released to the platform; on cancellation to the \", payee, \". Computed as floor(\", amount, \" * \", piece3_bps, \" / 10000) in \", C, \" minor units\"); pattern: \"^[1-9][0-9]{0,17}$\"; optional: true; }\n }\n }\n }\n when_eq(payer_fee_kind, percent) { serviceFeeAmount { type: money<C>; description: concat(\"Machine-owned \", payer_fee, \" service fee computed from \", amount, \"; charged on top at funding\"); pattern: \"^[1-9][0-9]{0,17}$\"; optional: true; } }\n when_eq(payer_fee_kind, binding) { [payer_fee] { type: payer_fee; description: concat(\"Immutable exact \", payer, \" fee in \", C, \" minor units; charged on top at funding\"); pattern: \"^[1-9][0-9]{0,17}$\"; } }\n when(release_deadline) {\n when_not(release_to) { [release_deadline] { type: date; description: concat(\"The date an undecided hold releases to the \", words(payee), \" on; \", release, \" and cancellation decide only before it\"); } }\n when(release_to) { [release_deadline] { type: date; description: concat(\"The date an undecided hold releases to the \", words(release_to), \" on; \", release, \" and cancellation decide only before it\"); } }\n }\n when(derived_amount) { [derived_field] { type: money<C>; description: \"Machine-computed {derived_rule} of {derived_source}; callers never supply it\"; pattern: \"^[1-9][0-9]{0,17}$\"; optional: true; } }\n }\n lifecycle {\n when_not(payee_fee) {\n when_not(cancel_splits) {\n states created funded disputed released;\n when(on_cancel) { states cancelled; }\n when(has_cancel_quote) { states cancellation_quoted cancelled settled; }\n states abandoned;\n }\n when(cancel_splits) { states created funding_1 funded disputed releasing_1 released cancelling_1 cancelled abandoned; }\n }\n when(payee_fee) {\n when_not(on_cancel) { states created funding_1 funded disputed releasing_1 released abandoned; }\n when(on_cancel) {\n when_eq(payer_fee_kind, percent) { states created funding_1 funding_2 funding_3 funded disputed releasing_1 releasing_2 released cancelling_1 cancelling_2 cancelled abandoning_1 abandoned abandoning_2; }\n when_eq(payer_fee_kind, none) { states created funding_1 funding_2 funded disputed releasing_1 releasing_2 released cancelling_1 cancelling_2 cancelled abandoning_1 abandoned; }\n when_eq(payer_fee_kind, binding) { states created funding_1 funding_2 funded disputed releasing_1 releasing_2 released cancelling_1 cancelling_2 cancelled abandoning_1 abandoned; }\n }\n }\n initial created;\n when_not(payee_fee) {\n when_not(cancel_splits) {\n on fund_piece_1: created -> funded;\n when_not(has_cancel_quote) {\n on [release_name]: funded -> released;\n when(release_deadline) { on release_on_deadline: funded -> released; }\n }\n when(has_cancel_quote) {\n on [release_name]: funded | cancellation_quoted -> released;\n when(release_deadline) { on release_on_deadline: funded | cancellation_quoted -> released; }\n on quote_cancellation: funded | cancellation_quoted -> cancellation_quoted;\n on cancel: cancellation_quoted -> cancelled;\n on retain_cancellation_charge: cancelled -> settled;\n }\n when(on_cancel) { on cancel: funded -> cancelled; }\n }\n when(cancel_splits) {\n on fund_piece_1: created -> funding_1;\n on fund_piece_2: funding_1 -> funded;\n on [release_name]: funded -> releasing_1;\n on release_piece_2: releasing_1 -> released;\n on cancel: funded -> cancelling_1;\n on refund_piece_2: cancelling_1 -> cancelled;\n on unfund_piece_1: funding_1 -> abandoned;\n }\n }\n when(payee_fee) {\n when_not(on_cancel) {\n on fund_piece_1: created -> funding_1;\n on fund_piece_2: funding_1 -> funded;\n on [release_name]: funded -> releasing_1;\n when(release_deadline) { on release_on_deadline: funded -> releasing_1; }\n on release_piece_2: releasing_1 -> released;\n on unfund_piece_1: funding_1 -> abandoned;\n }\n when(on_cancel) {\n on fund_piece_1: created -> funding_1;\n on fund_piece_2: funding_1 -> funding_2;\n when_eq(payer_fee_kind, percent) {\n on fund_piece_3: funding_2 -> funding_3;\n on collect_service_fee: funding_3 -> funded;\n }\n when_eq(payer_fee_kind, none) { on fund_piece_3: funding_2 -> funded; }\n when_eq(payer_fee_kind, binding) { on fund_piece_3: funding_2 -> funded; }\n on [release_name]: funded -> releasing_1;\n on release_piece_2: releasing_1 -> releasing_2;\n on release_piece_3: releasing_2 -> released;\n on cancel: funded -> cancelling_1;\n on refund_piece_2: cancelling_1 -> cancelling_2;\n on refund_piece_3: cancelling_2 -> cancelled;\n when_eq(payer_fee_kind, percent) {\n on unfund_piece_3: funding_3 -> abandoning_2;\n on unfund_piece_2: funding_2 | abandoning_2 -> abandoning_1;\n }\n when_eq(payer_fee_kind, none) { on unfund_piece_2: funding_2 -> abandoning_1; }\n when_eq(payer_fee_kind, binding) { on unfund_piece_2: funding_2 -> abandoning_1; }\n on unfund_piece_1: funding_1 | abandoning_1 -> abandoned;\n }\n }\n when_not(has_cancel_quote) {\n on dispute: funded -> disputed;\n }\n when(has_cancel_quote) {\n on dispute: funded | cancellation_quoted -> disputed;\n }\n on resume: disputed -> funded;\n on abandon: created -> abandoned;\n }\n parties {\n payer: payer;\n beneficiary: payee;\n }\n when_eq(payer_fee_kind, percent) {\n computes fees: { amount_field: serviceFeeAmount; base_field: amount; bearer_field: payer_account_field; position: on_top; rule: { kind: bps; bps: payer_bps; }; };\n }\n when_eq(payer_fee_kind, binding) {\n computes fees: { amount_field: camel(payer_fee); base_field: amount; bearer_field: payer_account_field; position: on_top; rule: { kind: exact; field: camel(payer_fee); currency_field: currency; }; };\n }\n when(payee_fee) {\n when(on_cancel) {\n action fund_piece_2 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Move piece 2 of the split amount from the \", words(payer), \" into escrow. Piece 1 must be in escrow first. It debits the \", words(payer), \" for real, and the hold is still short of funded until piece 3 lands.\");\n summary: \"Fund piece 2 of the held amount into escrow\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n action fund_piece_3 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Move piece 3 of the split amount from the \", words(payer), \" into escrow. Pieces 1 and 2 must be in escrow first. It debits the \", words(payer), \" for real. Where the product charges a percentage payer fee, one more call collects that fee before the hold counts as funded.\");\n summary: \"Fund piece 3 of the held amount into escrow\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { fundPiece3TransferTransferId: transferId; }; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n when_eq(payer_fee_kind, percent) {\n action collect_service_fee {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Charge the \", words(payer), \" the percentage service fee on top of the held amount and pay it to the platform account. All three funding pieces must be in escrow first. It debits the \", words(payer), \" for real and it is the last step of funding.\");\n summary: \"Collect the payer-side service fee\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: collect_service_fee; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { collectServiceFeeTransferTransferId: transferId; }; }];\n steps: [];\n }\n }\n action release_piece_2 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Pay piece 2 out of escrow once the release decision has run. Its destination follows the product fee split, either the platform account or the \", words(payee), \". Money leaves escrow on this call and does not come back.\");\n summary: \"Release piece 2 of the held amount\";\n when(carved_spans) { let(release_piece_2_destination): \"fields.platformAccountId\"; }\n when_not(carved_spans) { let(release_piece_2_destination): concat(\"fields.\", payee_account_field); }\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: release_piece_2_destination; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n action release_piece_3 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Pay piece 3 out of escrow to the platform account, which closes the release. Piece 2 must be released first. Money leaves escrow on this call and does not come back.\");\n summary: \"Release piece 3 of the held amount\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece3TransferTransferId: transferId; }; }];\n steps: [];\n }\n action refund_piece_2 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Return piece 2 out of escrow once cancel has run. Its destination follows the product fee split, either the \", words(payer), \" or the \", words(payee), \". Money leaves escrow on this call and does not come back.\");\n summary: \"Return piece 2 on cancellation\";\n when(carved_spans) { let(refund_piece_2_destination): concat(\"fields.\", payer_account_field); }\n when_not(carved_spans) { let(refund_piece_2_destination): concat(\"fields.\", payee_account_field); }\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refund_piece_2_destination; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: refund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { refundPiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n action refund_piece_3 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Return piece 3 out of escrow to the \", words(payee), \", which closes the cancellation. Piece 2 must be returned first. Money leaves escrow on this call and does not come back.\");\n summary: \"Return piece 3 on cancellation\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: refund_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { refundPiece3TransferTransferId: transferId; }; }];\n steps: [];\n }\n action unfund_piece_1 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Return piece 1 from escrow to the \", words(payer), \" and abandon the \", words(instrument), \". It runs when only piece 1 was ever funded, or after the later pieces have already gone back. The settlement ends abandoned.\");\n summary: \"Return piece 1 to the {payer} on abandonment\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece1TransferTransferId: transferId; }; }];\n steps: [];\n }\n action unfund_piece_2 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Return piece 2 from escrow to the \", words(payer), \" while abandoning a part-funded \", words(instrument), \". Funding must have stopped at piece 2, or piece 3 must already be back. Piece 1 still has to be returned after this.\");\n summary: \"Return piece 2 to the {payer} on abandonment\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n when_eq(payer_fee_kind, percent) {\n action unfund_piece_3 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Return piece 3 from escrow to the \", words(payer), \" while abandoning a \", words(instrument), \" funded as far as piece 3. Pieces 2 and 1 still have to be returned after this, in that order.\");\n summary: \"Return piece 3 to the {payer} on abandonment\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece3Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_3; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece3TransferTransferId: transferId; }; }];\n steps: [];\n }\n }\n }\n when_not(on_cancel) {\n when_eq(payee_fee_kind, percent) {\n computes fees: { amount_field: piece2Amount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_bps; }; };\n partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount]; };\n }\n when_eq(payee_fee_kind, binding) {\n computes fees: { amount_field: camel(payee_fee); base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: exact; field: camel(payee_fee); currency_field: currency; }; };\n partitions: { total_field: amount; piece_fields: [piece1Amount, camel(payee_fee)]; };\n }\n }\n when(on_cancel) {\n when_eq(payee_fee_kind, percent) {\n when(carved_spans) {\n computes fees: { amount_field: carvedFeeAmount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_bps; }; };\n }\n when_not(carved_spans) {\n computes fees: { amount_field: piece3Amount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_bps; }; };\n }\n }\n partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount, piece3Amount]; };\n when(carved_spans) { partitions: { total_field: carvedFeeAmount; piece_fields: [piece2Amount, piece3Amount]; }; }\n }\n }\n when_not(payee_fee) {\n when(cancel_splits) { partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount]; }; }\n }\n when(derived_amount) {\n computes derived: { field: derived_field; source_field: derived_source; rounding: floor; rule: { kind: percentage_of; bps: derived_rule; }; };\n }\n action create {\n when(private_actions) { public: none; }\n let(owner_id): \"owner.id\";\n let(owner_type): \"owner.type\";\n 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.\");\n summary: concat(\"Create a \", words(instrument), \" settlement\");\n when_not(derived_amount) { moves: []; }\n when(derived_amount) {\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n moves: [{ key: \"derived_amount\"; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", derived_field); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: create; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", camel(derived_bearer), \"AccountId\"); }; }; capture: { createDerivedAmountTransferId: transferId; }; }];\n }\n 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; }; }];\n }\n action fund_piece_1 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Move the \", words(payer), \"'s money into this settlement's escrow. Call it after create and before any release. It debits the \", words(payer), \" for real, and where the product charges a payer-side fee that fee is taken on top in the same call. Only a later release or unwind moves the money again.\");\n summary: \"Fund piece 1 of the held amount into escrow\";\n when_not(payee_fee) {\n when_not(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { fundPiece1TransferTransferId: transferId; }; }]; }\n when(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { fundPiece1TransferTransferId: transferId; }; }]; }\n }\n when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { fundPiece1TransferTransferId: transferId; }; }]; }\n when_eq(payer_fee_kind, percent) {\n when_not(payee_fee) { moves: [{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { fundPiece1ServiceFeeTransferId: transferId; }; }]; }\n when(payee_fee) {\n when_not(on_cancel) { moves: [{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { fundPiece1ServiceFeeTransferId: transferId; }; }]; }\n }\n }\n when_eq(payer_fee_kind, binding) { moves: [{ key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", payer_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { fundPiece1ServiceFeeTransferId: transferId; }; }]; }\n sandbox_failure_point: funding;\n steps: [];\n }\n action [release_name] {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n let(release_capture): camel(concat(release_name, \"_transfer_transfer_id\"));\n agent_description: concat(\"Confirm \", release, \" and start paying the held money out of escrow. The \", words(instrument), \" must be funded first, and the caller must be \", release_actor_text, \". Where the product requires a decision reference, supply it here. Money leaves escrow on this call and does not come back.\");\n summary: concat(\"Confirm through \", release, \" and start the release payout\");\n when(release_deadline) { deadline { field: release_deadline; }; }\n when(release_fields) {\n input {\n type: object;\n additional_properties: false;\n properties {\n for input_name in keys(release_fields) {\n [input_name]: get(release_fields, input_name);\n }\n }\n required: keys(release_fields);\n }\n }\n port { allowed_parties: release_allowed; };\n when_not(payee_fee) {\n when_not(cancel_splits) {\n when_not(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }\n when(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", release_to_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }\n }\n when(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }\n }\n when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { [release_capture]: transferId; }; }]; }\n sandbox_failure_point: \"release\";\n steps: [];\n }\n when(release_deadline) {\n action release_on_deadline {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n when_not(release_to) { summary: \"Release to the {payee} when {release_deadline} arrives undecided\"; }\n when(release_to) { summary: \"Release to the {release_to} when {release_deadline} arrives undecided\"; }\n due { field: release_deadline; };\n when_not(payee_fee) {\n when_not(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_on_deadline; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseOnDeadlineTransferTransferId: transferId; }; }]; }\n when(release_to) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", release_to_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_on_deadline; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseOnDeadlineTransferTransferId: transferId; }; }]; }\n }\n when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_on_deadline; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseOnDeadlineTransferTransferId: transferId; }; }]; }\n steps: [];\n }\n }\n when(on_cancel) {\n action cancel {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Cancel the \", words(instrument), \" and start returning the held money to the \", words(payer), \". It must be funded and not yet released. Cancellation is final, and a cancelled settlement never releases. Where the product sets a release deadline, this call is refused once that date passes.\");\n summary: \"Cancel the settlement and start the unwind\";\n when(release_deadline) { deadline { field: release_deadline; }; }\n when_not(payee_fee) {\n when_not(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }]; }\n when(cancel_splits) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }]; }\n }\n when(payee_fee) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }]; }\n steps: [];\n }\n }\n when(has_cancel_quote) {\n action quote_cancellation {\n when(private_actions) { public: none; }\n 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.\");\n summary: \"Price the cancellation and hold that price open\";\n when(release_deadline) { deadline { field: release_deadline; }; }\n quote {\n baseField: amount;\n chargeRef: cancellationChargeAmount;\n charges: [{ bps: cancel_charge_bps; }];\n expires { offset: cancel_offer_life; }\n fixes: [amount, payer_account_field];\n netDestinationField: payer_account_field;\n netRef: cancellationRefundAmount;\n }\n moves: [];\n steps: [];\n }\n action cancel {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Spend the frozen quote and return the net refund to the \", words(payer), \". This moves money and does not reverse. It needs an un-expired quote from quote_cancellation, and the charge that refund left behind goes to the \", words(payee), \" through retain_cancellation_charge.\");\n summary: concat(\"Spend the quoted cancellation and return the net to the \", words(payer));\n commit: quote_cancellation;\n when(release_deadline) { deadline { field: release_deadline; }; }\n moves: [{ key: refund; operation: internal_transfer.create; bind: { amount: { from: instance; path: refs.cancellationRefundAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: cancel; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelTransferTransferId: transferId; }; }];\n steps: [];\n }\n action retain_cancellation_charge {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Pay the quoted cancellation charge out of escrow to the \", words(payee), \". This moves money and does not reverse. The settlement must already be cancelled, so the figure is the one the quote fixed and the escrow ends empty.\");\n summary: concat(\"Pay the quoted cancellation charge to the \", words(payee));\n when(has_charge) {\n moves: [{ key: charge; operation: internal_transfer.create; bind: { amount: { from: instance; path: refs.cancellationChargeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: retain_cancellation_charge; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { retainCancellationChargeTransferId: transferId; }; }];\n }\n when_not(has_charge) {\n moves: [];\n }\n steps: [];\n }\n }\n when(payee_fee) {\n when_not(on_cancel) {\n action fund_piece_2 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Move the carved platform fee piece from the \", words(payer), \" into escrow, which completes funding. Piece 1 must be in escrow first. It debits the \", words(payer), \" for real, and the whole held amount then sits in escrow.\");\n summary: \"Fund piece 2 of the held amount into escrow\";\n when_eq(payee_fee_kind, percent) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }]; }\n when_eq(payee_fee_kind, binding) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", payee_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }]; }\n sandbox_failure_point: funding;\n steps: [];\n }\n action release_piece_2 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Pay the carved platform fee out of escrow to the platform account, which closes the release. The release decision must have run first. Money leaves escrow on this call and does not come back.\");\n summary: \"Release piece 2 of the held amount\";\n when_eq(payee_fee_kind, percent) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }]; }\n when_eq(payee_fee_kind, binding) { moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", payee_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }]; }\n steps: [];\n }\n action unfund_piece_1 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Return piece 1 from escrow to the \", words(payer), \" and abandon the \", words(instrument), \". It runs only while funding stopped after piece 1, before the release. The settlement ends abandoned.\");\n summary: \"Return piece 1 to the {payer} on abandonment\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece1TransferTransferId: transferId; }; }];\n steps: [];\n }\n }\n }\n when_not(payee_fee) {\n when(cancel_splits) {\n action fund_piece_2 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Move piece 2 of the split amount from the \", words(payer), \" into escrow, which completes funding. Piece 1 must be in escrow first. It debits the \", words(payer), \" for real, and the whole held amount then sits in escrow.\");\n summary: \"Fund piece 2 of the held amount into escrow\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: fund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { fundPiece2TransferTransferId: transferId; }; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n action release_piece_2 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Pay piece 2 out of escrow to the \", words(payee), \", which closes the release. The release decision must have run first. Money leaves escrow on this call and does not come back.\");\n summary: \"Release piece 2 of the held amount\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: release_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releasePiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n action refund_piece_2 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Return piece 2 out of escrow to the \", words(payee), \", which closes the cancellation. Cancel must have run first. Money leaves escrow on this call and does not come back.\");\n summary: \"Return piece 2 on cancellation\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: refund_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { refundPiece2TransferTransferId: transferId; }; }];\n steps: [];\n }\n action unfund_piece_1 {\n when(private_actions) { public: none; }\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Return piece 1 from escrow to the \", words(payer), \" and abandon the \", words(instrument), \". It runs only while funding stopped after piece 1, before the release. The settlement ends abandoned.\");\n summary: \"Return piece 1 to the {payer} on abandonment\";\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: unfund_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { unfundPiece1TransferTransferId: transferId; }; }];\n steps: [];\n }\n }\n }\n action abandon {\n when(private_actions) { public: none; }\n 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.\");\n summary: \"Abandon the settlement before any money is held\";\n requires drained: { path: refs.escrowAccountId; };\n moves: [];\n steps: [];\n }\n action dispute {\n when(private_actions) { public: none; }\n 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.\");\n description: concat(\"Freezes funded \", words(instrument), \", preserving the entire balance while the parties decide what happens next.\");\n summary: concat(\"Dispute a \", words(instrument), \" settlement\");\n when(release_deadline) { deadline { field: release_deadline; }; }\n moves: [];\n steps: [];\n }\n action resume {\n when(private_actions) { public: none; }\n 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.\");\n description: concat(\"Ends the dispute hold without moving money and returns the fully funded \", words(instrument), \" to its first milestone.\");\n summary: concat(\"Resume a \", words(instrument), \" settlement\");\n moves: [];\n steps: [];\n }\n }\n when(whole_amount) {\n let(fee_collection_key): \"x-hyperscale-fee-collection-port\";\n let(reference_filter_key): \"x-hyperscale-reference-filter\";\n let(whole_fee_field): camel(whole_fee);\n title: sentence(instrument);\n id_prefix: if_eq(id_prefix_override, false, prefix(instrument), id_prefix_override);\n summary: concat(\"Escrow-held payment from \", words(payer), \" to \", words(payee));\n description: concat(\"Held payment: the \", words(payer), \" funds \", amount, \" into this settlement's own escrow; \", release_actor_text, \" confirms through \", release, \" to release\");\n distinct_parties: true;\n caller_parked_states: {\n created: \"The tenant either funds the hold or abandons it before any money moves.\";\n disputed: \"Dispute resolution is a human judgment; the parties resume the hold to continue.\";\n };\n fields {\n currency { type: text; description: \"One currency for the hold and its fee\"; const: C; }\n amount { type: money<C>; description: \"Amount held in custody, in {C} minor units\"; }\n [payee]AccountId { type: account<C>; description: \"Party the hold protects and pays on release\"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n [payer]AccountId { type: account<C>; description: \"Party whose money is held until the hold resolves\"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n platformAccountId { type: account<C>; description: \"Product revenue or customer balance account collecting the custody fee\"; [fee_collection_key]: true; [reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; }; }\n when(reference) { reference { type: text; description: \"The tenant's own identifier for what is held: invoice number, project code, contract reference\"; optional: true; max_length: 180; min_length: 1; } }\n [release_deadline] { type: date; description: \"Date the hold releases to the payee on its own; fixed at creation and the cutoff for both the release port and cancellation\"; }\n whole_fee { type: money<C>; description: \"Custody fee charged on top at funding and never placed in escrow\"; }\n when(upstream) { upstream { type: ref<instrument>; description: \"Hold this one is carved out of, when a chain of holds passes the same money down\"; optional: true; pattern: concat(\"^\", id_prefix_override, \"_(sandbox|live)_[a-z0-9]{8,64}$\"); } }\n }\n lifecycle {\n states created funded disputed released cancelled abandoned;\n initial created;\n on abandon: created -> abandoned;\n on cancel: funded -> cancelled;\n on dispute: funded -> disputed;\n on fund: created -> funded;\n on [release_action]: funded -> released;\n on release_on_deadline: funded -> released;\n on resume: disputed -> funded;\n }\n parties { beneficiary: payee_account_field; payer: payer_account_field; }\n required: [payer_account_field, payee_account_field, platformAccountId, amount, whole_fee_field, currency, release_deadline];\n action create {\n when(private_actions) { public: none; }\n let(owner_id): \"owner.id\";\n let(owner_type): \"owner.type\";\n 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.\");\n summary: concat(\"Create a \", words(instrument), \" settlement\");\n moves: [];\n 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; }; }];\n }\n action fund {\n when(private_actions) { public: none; }\n 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.\");\n summary: \"Fund the hold and collect the custody fee\";\n moves: [\n { key: principal; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: refs.escrowAccountId; }; \"metadata.instrumentId\": { from: const; value: instrument; }; \"metadata.instrumentInstanceId\": { from: instance; path: instrumentInstanceId; }; \"metadata.phase\": { from: const; value: fund; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { fundingTransferId: transferId; }; },\n { key: service_fee; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", whole_fee_field); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; \"metadata.instrumentId\": { from: const; value: instrument; }; \"metadata.instrumentInstanceId\": { from: instance; path: instrumentInstanceId; }; \"metadata.phase\": { from: const; value: service_fee; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { serviceFeeTransferId: transferId; }; }\n ];\n sandbox_failure_point: funding;\n steps: [];\n }\n action [release_action] {\n when(private_actions) { public: none; }\n 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.\");\n summary: concat(\"Release the hold to the \", words(payee), \" early\");\n deadline: { field: release_deadline; };\n port: { allowed_parties: [at(release_allowed, 1)]; };\n moves: [{ key: release_action; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; \"metadata.instrumentId\": { from: const; value: instrument; }; \"metadata.instrumentInstanceId\": { from: instance; path: instrumentInstanceId; }; \"metadata.phase\": { from: const; value: release_action; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { releaseTransferId: transferId; }; }];\n sandbox_failure_point: \"release\";\n steps: [];\n }\n action release_on_deadline {\n when(private_actions) { public: none; }\n summary: concat(\"Release the hold to the \", words(payee), \" on its release date\");\n due: { field: release_deadline; };\n event_name: concat(instrument, \".released_on_deadline\");\n moves: [{ key: release_action; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; \"metadata.instrumentId\": { from: const; value: instrument; }; \"metadata.instrumentInstanceId\": { from: instance; path: instrumentInstanceId; }; \"metadata.phase\": { from: const; value: deadline_release; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { deadlineReleaseTransferId: transferId; }; }];\n steps: [];\n }\n action cancel {\n when(private_actions) { public: none; }\n 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.\");\n summary: \"Cancel the hold and return the held amount\";\n deadline: { field: release_deadline; };\n event_name: concat(instrument, \".cancelled\");\n moves: [{ key: refund; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; \"metadata.instrumentId\": { from: const; value: instrument; }; \"metadata.instrumentInstanceId\": { from: instance; path: instrumentInstanceId; }; \"metadata.phase\": { from: const; value: cancel_refund; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: refs.escrowAccountId; }; }; capture: { cancelRefundTransferId: transferId; }; }];\n steps: [];\n }\n action abandon {\n when(private_actions) { public: none; }\n 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.\");\n summary: \"Abandon the hold before it is funded\";\n moves: [];\n requires_drained: { path: refs.escrowAccountId; };\n steps: [];\n }\n action dispute {\n when(private_actions) { public: none; }\n 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.\");\n description: concat(\"Freezes funded \", words(instrument), \", preserving the entire balance while the parties decide what happens next.\");\n summary: concat(\"Dispute a \", words(instrument), \" settlement\");\n deadline: { field: release_deadline; };\n moves: [];\n steps: [];\n }\n action resume {\n when(private_actions) { public: none; }\n 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.\");\n description: concat(\"Ends the dispute hold without moving money and returns the fully funded \", words(instrument), \" to its first milestone.\");\n summary: concat(\"Resume a \", words(instrument), \" settlement\");\n moves: [];\n steps: [];\n }\n }\n}\n"],
|
|
11
|
-
["
|
|
12
|
-
["money_flows/instant_transfer.hsx", "module std.money_flows.instant_transfer\n\n// Direct, single-step push transfer from payer to payee with optional tiered or percentage fees.\n//\n// ### Purpose\n// `instant_transfer` debits a payer and credits a payee immediately in a single execution step.\n// It fits peer-to-peer transfers, direct disbursements, instant payouts, wallet reloads, and simple point-of-sale payments.\n//\n// ### Selection guidance\n// - vs `captured_payment`: `instant_transfer` moves money in one atomic step without prior authorization holds,\n// incremental captures, voids, or post-settlement correction ports. Use `captured_payment` when authorization must precede capture.\n// - vs `conditional_disbursement`: `instant_transfer` executes immediately without external decision evidence\n// or cumulative cap tracking. Use `conditional_disbursement` when payments require evidence submission or aggregate milestone caps.\n// - vs `held_payment`: `instant_transfer` never holds funds in an intermediary escrow account.\n//\n// ### Parameters\n// - `payer`: Funding party whose account is debited.\n// - `payee`: Recipient party whose account is credited.\n// - `amount`: Transfer amount in minor units of currency `C`.\n// - `fees`: Optional fee configuration block specifying flat fees, percentage fees, or bracketed fee tiers for payer and/or payee.\n// - `derived_amount`: Optional block declaring derived platform fees calculated from the transfer amount.\n//\n// ### Decision ports\n// None. Transfers execute immediately upon invocation.\n//\n// ### Example\n// ```hsx\n// program instant_transfer_example \"Instant transfer example\"\n// import { instant_transfer } from \"std/money_flows\"\n// party customer: person\n// party merchant: business\n// settlement transfer = instant_transfer {\n// payer: customer\n// payee: merchant\n// amount: transferAmount: money(SAR)\n// fees {\n// customer: checkoutFee: money(SAR)\n// merchant {\n// tier { from: 0, to: 10000, fee: 1% }\n// tier { from: 10000, fee: highValueFee: money(SAR) }\n// }\n// }\n// }\n// ```\nexport instrument instant_transfer<C>(payer: party, payee: party, amount: money<C>, fees: optional<block>, derived_amount: optional<block>) {\n let(payer_fee): get(fees, payer);\n let(payee_fee): get(fees, payee);\n let(payer_fee_kind): kind(payer_fee);\n let(payee_fee_kind): kind(payee_fee);\n let(payer_account_field): camel(concat(payer, \"_account_id\"));\n let(payee_account_field): camel(concat(payee, \"_account_id\"));\n let(net_bps): sub(10000, payee_fee);\n let(net_percent): percent_text(net_bps);\n let(payee_bps): basis_points(payee_fee);\n let(payee_tiers): values(payee_fee);\n let(first_tier): at(payee_tiers, 1);\n let(second_tier): at(payee_tiers, 2);\n let(second_tier_fee): get(second_tier, \"fee\");\n when(derived_amount) {\n let(derived_field): get(derived_amount, \"field\");\n let(derived_source): get(derived_amount, \"source\");\n let(derived_rule): get(derived_amount, \"rule\");\n let(derived_bearer): get(derived_amount, \"bearer\");\n }\n agent_description: concat(\"Reach for instant transfer when the \", words(payer), \" pays the \", words(payee), \" straight through with no custody step. The money leaves and lands in one call. Pick deposit when the money must be held first, and captured payment when the payee takes it in slices.\");\n summary: concat(\"Instant payment from \", words(payer), \" to \", words(payee));\n title: sentence(instrument);\n description: concat(\"Instant transfer: the \", words(payer), \" pays \", amount, \" straight through to the \", words(payee), \", no custody\");\n fields {\n when_not(fees) {\n when(derived_amount) {\n let(fee_collection_key): \"x-hyperscale-fee-collection-port\";\n let(reference_filter_key): \"x-hyperscale-reference-filter\";\n platformAccountId {\n type: account<C>;\n description: \"The product fee collection account\";\n [fee_collection_key]: true;\n [reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; };\n }\n }\n }\n when(fees) {\n let(fee_collection_key): \"x-hyperscale-fee-collection-port\";\n let(reference_filter_key): \"x-hyperscale-reference-filter\";\n platformAccountId {\n type: account<C>;\n description: \"The product fee collection account\";\n [fee_collection_key]: true;\n [reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; };\n }\n }\n when_not(fees) {\n amount { type: money<C>; description: \"The amount in {C} minor units, paid through whole\"; }\n }\n when(fees) {\n amount { type: money<C>; description: \"The gross amount in {C} minor units; the piece fields below partition it exactly\"; }\n when_eq(payee_fee_kind, percent) {\n piece1Amount { type: money<C>; description: concat(net_percent, \" of \", amount, \" (carries the integer-division remainder): released to the \", payee, \". Computed as floor(\", amount, \" * \", net_bps, \" / 10000) in \", C, \" minor units\"); pattern: \"^[1-9][0-9]{0,17}$\"; }\n piece2Amount { type: money<C>; description: concat(payee_fee, \" of \", amount, \": released to the platform. Computed as floor(\", amount, \" * \", payee_bps, \" / 10000) in \", C, \" minor units\"); pattern: \"^[1-9][0-9]{0,17}$\"; optional: true; }\n }\n when_eq(payee_fee_kind, block) {\n netOfCarvedFeeAmount { type: money<C>; description: \"The remainder of {amount} after the carved platform fee\"; pattern: \"^[1-9][0-9]{0,17}$\"; }\n carvedFeeAmount { type: money<C>; description: \"The carved platform fee selected from {amount}\"; pattern: \"^[1-9][0-9]{0,17}$\"; optional: true; }\n }\n when_eq(payer_fee_kind, percent) {\n serviceFeeAmount { type: money<C>; description: \"Machine-owned {payer_fee} service fee computed from {amount}; charged on top\"; pattern: \"^[1-9][0-9]{0,17}$\"; optional: true; }\n }\n when_eq(payer_fee_kind, binding) {\n [payer_fee] { type: payer_fee; description: \"Immutable exact {payer} fee in {C} minor units\"; pattern: \"^[1-9][0-9]{0,17}$\"; }\n }\n when_eq(payee_fee_kind, block) {\n [second_tier_fee] { type: second_tier_fee; description: \"Immutable exact {payee} fee in {C} minor units\"; pattern: \"^[1-9][0-9]{0,17}$\"; }\n }\n }\n when(derived_amount) {\n [derived_field] { type: money<C>; description: \"Machine-computed {derived_rule} of {derived_source}; callers never supply it\"; pattern: \"^[1-9][0-9]{0,17}$\"; optional: true; }\n }\n }\n lifecycle {\n when_not(fees) { states created paid; }\n when(fees) { states created paying_1 paying_2 paid; }\n initial created;\n when_not(fees) { on pay_piece_1: created -> paid; }\n when(fees) {\n on pay_piece_1: created -> paying_1;\n on pay_piece_2: paying_1 -> paying_2;\n on collect_service_fee: paying_2 -> paid;\n }\n }\n parties {\n payer: payer;\n beneficiary: payee;\n }\n when(derived_amount) {\n computes derived: { field: derived_field; source_field: derived_source; rounding: floor; rule: { kind: percentage_of; bps: derived_rule; }; };\n }\n when(fees) {\n when_eq(payer_fee_kind, percent) {\n computes fees: { amount_field: serviceFeeAmount; base_field: amount; bearer_field: payer_account_field; position: on_top; rule: { kind: bps; bps: payer_fee; }; };\n }\n when_eq(payer_fee_kind, binding) {\n computes fees: { amount_field: camel(payer_fee); base_field: amount; bearer_field: payer_account_field; position: on_top; rule: { kind: exact; field: camel(payer_fee); currency_field: currency; }; };\n }\n when_eq(payee_fee_kind, percent) {\n computes fees: { amount_field: piece2Amount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: bps; bps: payee_fee; }; };\n partitions: { total_field: amount; piece_fields: [piece1Amount, piece2Amount]; };\n }\n when_eq(payee_fee_kind, block) {\n computes fees: { amount_field: carvedFeeAmount; base_field: amount; bearer_field: payee_account_field; position: carved; rule: { kind: tiered; tiers: [{ from_inclusive: concat(\"\", get(first_tier, \"from\")); to_exclusive: concat(\"\", get(first_tier, \"to\")); rule: { kind: bps; bps: get(first_tier, \"fee\"); }; }, { from_inclusive: concat(\"\", get(second_tier, \"from\")); rule: { kind: exact; field: camel(second_tier_fee); currency_field: currency; }; }]; }; };\n partitions: { total_field: amount; piece_fields: [netOfCarvedFeeAmount, carvedFeeAmount]; };\n }\n }\n action create {\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: \"Open the payment row before anything moves. The caller supplies the {amount}, the currency, and both party accounts. Fee and derived pieces are computed from the {amount}, callers never supply them. Call pay piece 1 to move the money.\";\n summary: concat(\"Create a \", words(instrument), \" payment\");\n when(derived_amount) {\n moves: [{\n key: \"derived_amount\";\n operation: internal_transfer.create;\n bind: {\n amount: { from: instance; path: concat(\"fields.\", derived_field); };\n currency: { from: instance; path: fields.currency; };\n destinationAccountId: { from: instance; path: fields.platformAccountId; };\n [metadata_instrument]: { from: const; value: instrument; };\n [metadata_instance]: { from: instance; path: instrumentInstanceId; };\n [metadata_phase]: { from: const; value: create; };\n productId: { from: instance; path: productId; };\n sourceAccountId: { from: instance; path: concat(\"fields.\", camel(derived_bearer), \"AccountId\"); };\n };\n capture: { createDerivedAmountTransferId: transferId; };\n }];\n }\n steps: [];\n }\n action pay_piece_1 {\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Move the first piece from the \", words(payer), \" to the \", words(payee), \". The payment must be created. This moves the whole amount unless the product carves a payee fee, in which case it moves the net share and pay piece 2 takes the fee.\");\n summary: \"Pay piece 1 of the amount through\";\n when_not(fees) {\n moves: [{ key: transfer; amount: amount; from: payer; to: beneficiary; }];\n }\n when(fees) {\n when_eq(payee_fee_kind, none) {\n moves: [{ key: transfer; amount: amount; from: payer; to: beneficiary; }];\n }\n when_eq(payee_fee_kind, percent) {\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece1Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: pay_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { payPiece1TransferTransferId: transferId; }; }];\n }\n when_eq(payee_fee_kind, block) {\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.netOfCarvedFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payee_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: pay_piece_1; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { payPiece1TransferTransferId: transferId; }; }];\n }\n }\n sandbox_failure_point: funding;\n steps: [];\n }\n when(fees) {\n action pay_piece_2 {\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Move the carved platform fee out of the amount. Call this after pay piece 1. When the product carves no \", words(payee), \" fee this only steps the payment forward and moves nothing.\");\n summary: \"Pay piece 2 of the amount through\";\n when_eq(payee_fee_kind, percent) {\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.piece2Amount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: pay_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { payPiece2TransferTransferId: transferId; }; }];\n }\n when_eq(payee_fee_kind, block) {\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.carvedFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: pay_piece_2; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { payPiece2TransferTransferId: transferId; }; }];\n }\n steps: [];\n }\n action collect_service_fee {\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n agent_description: concat(\"Finish the payment and collect the service fee the \", words(payer), \" owes on top of the amount. Both pieces must already be paid. When the product charges no payer fee this only closes the payment out.\");\n summary: \"Collect the payer-side service fee\";\n when_eq(payer_fee_kind, percent) {\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: fields.serviceFeeAmount; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: collect_service_fee; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { collectServiceFeeTransferTransferId: transferId; }; }];\n }\n when_eq(payer_fee_kind, binding) {\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", payer_fee); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: fields.platformAccountId; }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: collect_service_fee; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { collectServiceFeeTransferTransferId: transferId; }; }];\n }\n steps: [];\n }\n }\n}\n"],
|
|
13
|
-
["
|
|
14
|
-
["
|
|
15
|
-
["money_flows/premium_forward.hsx", "module std.money_flows.premium_forward\n\n// Insurance premium escrow holding customer funds until policy binding, then partitioning net carrier premium and commission.\n//\n// ### Purpose\n// `premium_forward` manages insurance premium collection, broker commission retention, and carrier remittance.\n// A policyholder funds the premium into dedicated escrow. The funds remain held until the policy binds via the `bind` port.\n// Upon binding, the gross premium is automatically partitioned: the platform fee/commission is retained, and the net\n// balance forwards to the carrier. It also supports policy endorsements and renewal schedules.\n//\n// ### Selection guidance\n// - vs `held_payment`: `premium_forward` is tailored for insurance lifecycles, featuring automatic commission splits\n// upon binding and policy endorsement tracking. `held_payment` is general commercial escrow without insurance\n// underwriting binding semantics or gross-to-net fee partitioning.\n// - vs `conditional_disbursement`: `premium_forward` collects and forwards inbound policy premiums to carriers.\n// `conditional_disbursement` pays outbound claim settlements to claimants against stored evidence.\n//\n// ### Parameters\n// - `payer`: The policyholder paying the insurance premium.\n// - `carrier`: The insurance carrier underwriting the policy.\n// - `amount`: Total gross premium in minor units of currency `C`.\n// - `bind`: Condition port triggering policy binding and premium forwarding.\n// - `commission`: Platform commission percentage retained from the gross premium.\n// - `policy_ref`: Optional policy identifier string.\n// - `renewal_due`: Optional date anchor when the policy is due for renewal.\n// - `endorsement`: Optional condition port for recording policy endorsements.\n//\n// ### Decision ports\n// - `bind`: Port authorizing policy binding, triggering carrier payout and commission retention.\n// - `endorsement`: Port allowing carrier endorsement evidence to be recorded.\n//\n// ### Example\n// ```hsx\n// program premium_forward_example \"Premium forward example\"\n// import { premium_forward } from \"std/money_flows\"\n// party policyholder: person\n// party carrier: business\n// settlement premium = premium_forward {\n// payer: policyholder\n// carrier: carrier\n// amount: premiumAmount: money(SAR)\n// commission: 2%\n// bind: port bind_policy\n// policy_ref: policyReference\n// renewal_due: renewalDueAt\n// endorsement: port record_endorsement\n// }\n// port bind_policy { allowed: [policyholder, carrier] }\n// port record_endorsement {\n// allowed: [carrier]\n// shape: { evidenceReference: text }\n// }\n// ```\nexport instrument premium_forward<C>(\n payer: party,\n carrier: party,\n amount: money<C>,\n bind: condition,\n commission: percent,\n policy_ref: optional<text>,\n renewal_due: optional<date>,\n endorsement: optional<condition>,\n) {\n let(instrument_words): words(instrument);\n let(bind_name): concat(bind);\n let(payer_field): camel(concat(payer, \"_account_id\"));\n let(carrier_field): camel(concat(carrier, \"_account_id\"));\n let(amount_field): concat(amount);\n let(net_bps): sub(10000, commission);\n let(fee_bps): add(commission, 0);\n let(net_percent): percent_text(net_bps);\n let(fee_percent): percent_text(fee_bps);\n let(owner_id_key): \"owner.id\";\n let(owner_type_key): \"owner.type\";\n let(metadata_instrument_key): \"metadata.instrumentId\";\n let(metadata_instance_key): \"metadata.instrumentInstanceId\";\n let(metadata_phase_key): \"metadata.phase\";\n let(bind_capture_key): camel(concat(bind, \"_transfer_transfer_id\"));\n let(fee_port_key): \"x-hyperscale-fee-collection-port\";\n let(reference_filter_key): \"x-hyperscale-reference-filter\";\n\n title: sentence(instrument);\n agent_description: \"Reach for {instrument_words} when the {payer} pays an insurance premium ahead of cover and the {carrier} must not be paid until the policy binds. The {amount_field} sits in this settlement's own escrow until {bind_name} fires. Binding then forwards {net_percent} to the {carrier} and {fee_percent} to the platform.\";\n summary: \"Premium held for the {carrier} until the policy binds\";\n description: \"Premium forward: the {payer} funds the {amount_field} into this settlement's own escrow; binding through {bind_name} forwards it to the {carrier} exactly once, minus the platform commission\";\n partitions: { total_field: amount_field; piece_fields: [piece1Amount, piece2Amount]; };\n\n fields {\n platform_account_id: {\n type: account;\n description: \"The product fee collection account\";\n [fee_port_key]: true;\n [reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; };\n }\n amount: {\n type: money<C>;\n description: \"The gross held amount in {C} minor units; the piece fields below partition it exactly\";\n }\n piece1_amount: {\n type: money<C>;\n description: \"{net_percent} of {amount_field} (carries the integer-division remainder): released to the {carrier}. Computed as floor({amount_field} * {net_bps} / 10000) in {C} minor units\";\n }\n piece2_amount: {\n type: money<C>;\n description: \"{fee_percent} of {amount_field}: released to the platform. Computed as floor({amount_field} * {fee_bps} / 10000) in {C} minor units\";\n }\n when(policy_ref) {\n policy_ref: {\n type: text;\n description: \"Immutable external policy reference recorded with this forward\";\n max_length: 180;\n min_length: 1;\n }\n }\n when(renewal_due) {\n renewal_due: {\n type: date;\n description: \"Stored renewal due condition for the forwarded policy\";\n }\n }\n }\n\n parties { payer: payer; beneficiary: carrier; }\n\n lifecycle {\n states created funding_1 funded releasing_1 released abandoned;\n when(endorsement) { states endorsed lapsed; }\n initial created;\n on fund_piece_1: created -> funding_1;\n on fund_piece_2: funding_1 -> funded;\n on [bind]: funded -> releasing_1;\n on forward_piece_2: releasing_1 -> released;\n on unfund_piece_1: funding_1 -> abandoned;\n on abandon: created -> abandoned;\n when(endorsement) {\n on [endorsement]: released -> endorsed;\n on lapse: released|endorsed -> lapsed;\n }\n }\n\n action create {\n agent_description: \"Open the {instrument_words} and provision the escrow account that holds the funds. This is the first call on the settlement, and funding refuses until it lands. It moves no money.\";\n summary: \"Create a {instrument_words} settlement\";\n steps: [{\n operation: \"account.escrow.provision\";\n bind: {\n currency: { from: instance; path: fields.currency; };\n [owner_id_key]: { from: instance; path: productId; };\n [owner_type_key]: { from: const; value: product; };\n product_id: { from: instance; path: productId; };\n role: { from: const; value: product_escrow; };\n };\n capture: { escrow_account_id: accountId; };\n }];\n }\n\n action fund_piece_1 {\n agent_description: \"Move the {carrier}'s share of the premium from the {payer} into escrow. The settlement must exist and be unfunded. It debits the {payer} for real, and piece 2 still has to fund before the policy can bind.\";\n summary: \"Fund piece 1 of the held amount into escrow\";\n moves: [{\n key: transfer; operation: \"internal_transfer.create\";\n bind: {\n amount: { from: instance; path: fields.piece1Amount; };\n currency: { from: instance; path: fields.currency; };\n destination_account_id: { from: instance; path: refs.escrowAccountId; };\n source_account_id: { from: instance; path: concat(\"fields.\", payer_field); };\n [metadata_instrument_key]: { from: const; value: instrument; };\n [metadata_instance_key]: { from: instance; path: instrumentInstanceId; };\n [metadata_phase_key]: { from: const; value: fund_piece_1; };\n product_id: { from: instance; path: productId; };\n };\n capture: { fund_piece_1_transfer_transfer_id: transferId; };\n }];\n sandbox_failure_point: funding;\n steps: [];\n }\n\n action fund_piece_2 {\n agent_description: \"Move the platform commission piece from the {payer} into escrow, which completes funding. Piece 1 must be in escrow first. It debits the {payer} for real, and the premium is then held whole.\";\n summary: \"Fund piece 2 of the held amount into escrow\";\n moves: [{\n key: transfer; operation: \"internal_transfer.create\";\n bind: {\n amount: { from: instance; path: fields.piece2Amount; };\n currency: { from: instance; path: fields.currency; };\n destination_account_id: { from: instance; path: refs.escrowAccountId; };\n source_account_id: { from: instance; path: concat(\"fields.\", payer_field); };\n [metadata_instrument_key]: { from: const; value: instrument; };\n [metadata_instance_key]: { from: instance; path: instrumentInstanceId; };\n [metadata_phase_key]: { from: const; value: fund_piece_2; };\n product_id: { from: instance; path: productId; };\n };\n capture: { fund_piece_2_transfer_transfer_id: transferId; };\n }];\n sandbox_failure_point: funding;\n steps: [];\n }\n\n action [bind] {\n agent_description: \"Bind the policy through {bind_name} and forward the {carrier}'s share out of escrow. The premium must be fully funded first, and the call carries the decision reference the product asks for. Money leaves escrow here and does not come back.\";\n summary: \"Confirm through {bind_name} and start the forward payout\";\n input: {\n additional_properties: false;\n properties: {\n for item in keys(bind_fields) {\n [item]: get(bind_fields, item);\n }\n };\n required: keys(bind_fields);\n type: object;\n };\n port: { allowed_parties: bind_allowed; };\n moves: [{\n key: transfer; operation: \"internal_transfer.create\";\n bind: {\n amount: { from: instance; path: fields.piece1Amount; };\n currency: { from: instance; path: fields.currency; };\n destination_account_id: { from: instance; path: concat(\"fields.\", carrier_field); };\n source_account_id: { from: instance; path: refs.escrowAccountId; };\n [metadata_instrument_key]: { from: const; value: instrument; };\n [metadata_instance_key]: { from: instance; path: instrumentInstanceId; };\n [metadata_phase_key]: { from: const; value: bind; };\n product_id: { from: instance; path: productId; };\n };\n capture: { [bind_capture_key]: transferId; };\n }];\n sandbox_failure_point: \"release\";\n steps: [];\n }\n\n action forward_piece_2 {\n agent_description: \"Pay the platform commission out of escrow to the fee collection account, which closes the forward. {bind_name} must have run first. Money leaves escrow here and does not come back.\";\n summary: \"Forward piece 2 of the held amount\";\n moves: [{\n key: transfer; operation: \"internal_transfer.create\";\n bind: {\n amount: { from: instance; path: fields.piece2Amount; };\n currency: { from: instance; path: fields.currency; };\n destination_account_id: { from: instance; path: fields.platformAccountId; };\n source_account_id: { from: instance; path: refs.escrowAccountId; };\n [metadata_instrument_key]: { from: const; value: instrument; };\n [metadata_instance_key]: { from: instance; path: instrumentInstanceId; };\n [metadata_phase_key]: { from: const; value: forward_piece_2; };\n product_id: { from: instance; path: productId; };\n };\n capture: { forward_piece_2_transfer_transfer_id: transferId; };\n }];\n steps: [];\n }\n\n action unfund_piece_1 {\n agent_description: \"Return the {carrier}'s share from escrow to the {payer} and abandon the forward. It runs only while funding stopped after piece 1, before the policy binds. The settlement ends abandoned.\";\n summary: \"Return piece 1 to the {payer} on abandonment\";\n moves: [{\n key: transfer; operation: \"internal_transfer.create\";\n bind: {\n amount: { from: instance; path: fields.piece1Amount; };\n currency: { from: instance; path: fields.currency; };\n destination_account_id: { from: instance; path: concat(\"fields.\", payer_field); };\n source_account_id: { from: instance; path: refs.escrowAccountId; };\n [metadata_instrument_key]: { from: const; value: instrument; };\n [metadata_instance_key]: { from: instance; path: instrumentInstanceId; };\n [metadata_phase_key]: { from: const; value: unfund_piece_1; };\n product_id: { from: instance; path: productId; };\n };\n capture: { unfund_piece_1_transfer_transfer_id: transferId; };\n }];\n steps: [];\n }\n\n action abandon {\n agent_description: \"Close a {instrument_words} 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.\";\n summary: \"Abandon the settlement before any money is held\";\n requires drained: { path: refs.escrowAccountId; };\n steps: [];\n }\n\n when(endorsement) {\n action [endorsement] {\n agent_description: \"Record one endorsement against a policy that already forwarded, from evidence the caller supplies. The forward must be released first, and only the {carrier} may call it. It moves no money and it does not change the premium.\";\n summary: \"Record one non-money endorsement from external evidence\";\n when(endorsement_fields) {\n input: {\n additional_properties: false;\n properties: {\n for item in keys(endorsement_fields) {\n [item]: get(endorsement_fields, item);\n }\n };\n required: keys(endorsement_fields);\n type: object;\n };\n capture_input: {\n for item in keys(endorsement_fields) { [item]: item; }\n };\n }\n port { allowed_parties: endorsement_allowed; };\n steps: [];\n }\n action lapse {\n summary: \"Mark the forwarded policy lapsed at its stored renewal due condition\";\n due: { field: renewal_due; };\n requires drained: { path: refs.escrowAccountId; };\n steps: [];\n }\n }\n}\n"],
|
|
16
|
-
["money_flows/reconciled_payout.hsx", "module std.money_flows.reconciled_payout\n\n// Outbound bank payout instruction with end-to-end reconciliation against external bank statement feeds.\n//\n// ### Purpose\n// `reconciled_payout` manages high-assurance payouts to external suppliers, partners, or customers where payment\n// is not complete until confirmed by bank statement data. An instruction is dispatched to the beneficiary and an expectation\n// record is opened. Incoming statement debit lines match against the expectation within configurable tolerance thresholds\n// (`matched_within`, `matched_ceiling`). If the statement debit does not match before `settle_by`, a formal break row is raised.\n//\n// ### Selection guidance\n// - vs `settlement_batch`: `reconciled_payout` executes and reconciles an individual bank payout instruction.\n// `settlement_batch` aggregates periodic captures, fees, and signed adjustments to calculate a net payable batch.\n// - vs `instant_transfer`: `instant_transfer` executes an immediate internal ledger transfer between platform accounts.\n// `reconciled_payout` dispatches funds across external banking rails with reconciliation tolerances and break tracking.\n//\n// ### Parameters\n// - `payer`: The funding party providing the payout.\n// - `beneficiary`: The beneficiary party receiving the external payout.\n// - `amount`: Instructed payout amount in minor units of currency `C`.\n// - `beneficiary_ref`: Registered external beneficiary ID for bank routing.\n// - `settle_by`: Cut-off date when unmatched expectation amounts become formal break records.\n// - `matched_within`: Match tolerance window in basis points or minor units.\n// - `matched_ceiling`: Maximum acceptable tolerance ceiling between instructed amount and settled debit.\n//\n// ### Decision ports\n// None. Payout dispatch and settlement matching follow the declared schedule, bank statement lines, and tolerance rules.\n//\n// ### Example\n// ```hsx\n// program reconciled_payout_example \"Reconciled payout example\"\n// import { reconciled_payout } from \"std/money_flows\"\n// party treasury: business\n// party supplier: business\n// settlement supplier_payout = reconciled_payout {\n// payer: treasury\n// beneficiary: supplier\n// amount: netPayable: money(SAR)\n// beneficiary_ref: supplierBeneficiaryId\n// settle_by: settleBy\n// matched_within: 100\n// matched_ceiling: 500\n// }\n// ```\nexport instrument reconciled_payout<C>(\n payer: party,\n beneficiary: party,\n amount: money<C>,\n beneficiary_ref: text,\n settle_by: date,\n matched_within: integer,\n matched_ceiling: integer,\n) {\n let(payer_account_field): camel(concat(payer, \"_account_id\"));\n let(beneficiary_account_field): camel(concat(beneficiary, \"_account_id\"));\n let(reference_filter_key): \"x-hyperscale-reference-filter\";\n fields {\n amount { type: money<C>; description: concat(\"Amount instructed to the \", words(beneficiary), \" in minor units\"); }\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n settle_by { type: date; description: \"Instant the expectation stops waiting and the unmatched amount becomes a break\"; }\n beneficiary_ref { type: text; description: \"Beneficiary ID for the payout instruction\"; pattern: \"^ben_(sandbox|live)_[a-z0-9]{8,64}$\"; }\n [beneficiary_account_field] { type: account<C>; description: concat(\"The \", words(beneficiary), \" account\"); [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n [payer_account_field] { type: account<C>; description: concat(\"The \", words(payer), \" account the payout leaves\"); [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n }\n lifecycle {\n states created instructed settled;\n initial created;\n on instruct: created -> instructed;\n on settle: instructed -> settled;\n }\n parties { beneficiary: beneficiary_account_field; payer: payer_account_field; }\n required: [amount, beneficiary_account_field, beneficiary_ref, currency, payer_account_field, settle_by];\n title: sentence(instrument);\n agent_description: concat(\"Reach for reconciled payout when a payment to the \", words(beneficiary), \" is not finished until the bank confirms the debit. The instruction goes out, the expectation waits for a matching statement line, and anything still unmatched at the settle date becomes a break row instead of disappearing. Pick instant transfer when no bank confirmation is involved.\");\n id_prefix: prefix(instrument);\n summary: concat(\"Payout from \", words(payer), \" to \", words(beneficiary), \" that is not done until the bank agrees\");\n description: \"A payout the instrument owns end to end: one instruction, one expectation about the debit it will produce, and one break when the debit never matches\";\n dials: [{ key: settlement_tolerance; kind: reconcile_tolerance; maxMinorUnits: matched_ceiling; summary: \"Largest difference between the instructed amount and the settled debit that still counts as matched.\"; title: \"Settlement tolerance\"; }];\n action create { summary: concat(\"Create a \", words(instrument)); agent_description: concat(\"Open the payout row with the \", words(payer), \" and \", words(beneficiary), \" accounts, the amount, the beneficiary reference, and the settle date. No money moves and no instruction goes out yet. Call instruct next.\"); moves: []; steps: []; }\n action instruct {\n agent_description: concat(\"Send the payout instruction and start the expectation clock. This commits the amount to the \", words(beneficiary), \" and the payout is not reversible from here. The bank debit is matched later by settle, which runs on its own at the settle date.\");\n summary: concat(\"Instruct the payout to the \", words(beneficiary));\n payout { amount: concat(\"fields.\", amount); beneficiaryField: beneficiary_ref; beneficiaryPartyField: beneficiary_account_field; capture: payoutId; currencyField: currency; sourceAccountField: payer_account_field; speed: standard; }\n moves: []; steps: [];\n }\n action settle {\n summary: \"Match the bank's debit against what the instruction promised\";\n due { field: settle_by; }\n reconcile {\n amount: concat(\"fields.\", amount); capture: settlementEvidenceId; counterpartyRef: payoutId;\n currencyField: currency; direction: debit; evidence: statement_line;\n exception { amountField: unmatchedAmount; childInstrumentId: concat(instrument, \"_break\"); maxOpen: 1; reasonField: breakReason; refField: concat(camel(instrument), \"Id\"); }\n match { law: tolerance; dial: settlement_tolerance; minorUnits: matched_within; }\n within { field: settle_by; }\n }\n moves: []; steps: [];\n }\n instruments {\n instrument {\n let(parent_name): instrument;\n id: concat(parent_name, \"_break\");\n id_prefix: concat(prefix(parent_name), \"bk\");\n fields {\n unmatchedAmount { type: money<C>; description: \"Difference between the instructed amount and the evidence, in minor units\"; }\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n breakReason { type: text; description: \"Why the expectation went unmatched\"; minLength: 1; maxLength: 180; }\n [parent_name]Id { type: ref<parent_name>; description: \"Payout whose expectation broke\"; pattern: concat(\"^\", prefix(parent_name), \"_(sandbox|live)_[a-z0-9]{8,64}$\"); }\n }\n lifecycle { states created carried; initial created; on carry: created -> carried; }\n required: [breakReason, currency, unmatchedAmount, concat(camel(parent_name), \"Id\")];\n title: concat(sentence(parent_name), \" Break\");\n agent_description: concat(\"Reach for this child row when a \", words(parent_name), \" expectation reached its window with no matching bank debit. The break carries the unmatched amount and the reason so the difference is owned by someone rather than silently dropped.\");\n summary: \"Unmatched settlement expectation carried as its own row\";\n description: \"One break raised when a payout expectation reached its window with no matching debit; it is carried, never silently dropped\";\n action create { summary: \"Raise a break against an instructed payout\"; agent_description: \"Raise a break against an instructed payout, recording the unmatched amount and why the expectation went unmatched. No money moves. The payout must still be instructed, and only one open break exists per payout.\"; requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [instructed]; match: { \"fields.currency\": \"fields.currency\"; }; } moves: []; steps: []; }\n action carry { summary: \"Carry the break to whoever owns it\"; agent_description: \"Hand the break to whoever owns resolving it. No money moves. The payout must still be instructed. This is the only exit from a raised break, so a break is never closed by dropping it.\"; requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [instructed]; match: { \"fields.currency\": \"fields.currency\"; }; } moves: []; steps: []; }\n }\n }\n}\n"],
|
|
17
|
-
["money_flows/rotating_pool.hsx", "module std.money_flows.rotating_pool\n\n// Rotating savings and credit association (ROSCA) pool where members contribute fixed amounts and take turns receiving the pot.\n//\n// ### Purpose\n// `rotating_pool` coordinates peer savings circles, chit funds, tandas, and committee savings groups.\n// A fixed group of members contributes an identical contribution amount each cycle. In each cycle, one designated member\n// receives the entire pooled pot according to a predefined `payout_order` until all members have taken their turn.\n//\n// ### Selection guidance\n// - vs `threshold_pool`: `rotating_pool` coordinates recurring multi-party peer savings with rotating payouts.\n// `threshold_pool` is all-or-nothing capital accumulation toward a single threshold for one beneficiary.\n// - vs `scheduled`: `scheduled` coordinates a single payer to a single payee. `rotating_pool` orchestrates\n// a closed circular group of members taking sequential turns.\n//\n// ### Parameters\n// - `members`: List of parties belonging to the rotating group.\n// - `contribution`: Fixed contribution amount required from each member per cycle in minor units of currency `C`.\n// - `count`: Total number of cycles in the rotation (matching the member count).\n// - `every`: Recurrence interval between contribution cycles (e.g. `\"P30D\"`).\n// - `first_due`: Due date for the first cycle's contribution.\n// - `payout_order`: Ordered list of member parties defining the cycle payout sequence.\n// - `default_policy`: Policy for handling missed contributions (`due_condition`).\n// - `guarantee_policy`: Policy for backing defaulted contributions (`funded_only`).\n// - `guarantor`: Optional guarantor party covering member defaults.\n// - `exit_policy`: Policy governing member departures (`before_activation_only`).\n// - `memo`: Optional memo text stored on the pool.\n// - `membership`: Optional custom membership configuration block.\n//\n// ### Decision ports\n// None. Cycle advancement and pot payouts follow the declared schedule and membership actions.\n//\n// ### Example\n// ```hsx\n// program rotating_pool_example \"Rotating pool example\"\n// import { rotating_pool } from \"std/money_flows\"\n// party member_a: person\n// party member_b: person\n// party member_c: person\n// party guarantor: business\n// settlement pool = rotating_pool {\n// members: [member_a, member_b, member_c]\n// contribution: contributionAmount: money(SAR)\n// count: 3\n// every: P30D\n// first_due: firstContributionAt\n// payout_order: [member_b, member_c, member_a]\n// default_policy: due_condition\n// guarantee_policy: funded_only\n// guarantor: guarantor\n// exit_policy: before_activation_only\n// }\n// ```\nexport instrument rotating_pool<C>(members: optional<list<party>>, contribution: money<C>, count: integer, every: optional<text>, first_due: date, payout_order: optional<list<party>>, default_policy: optional<text>, guarantee_policy: optional<text>, guarantor: optional<party>, exit_policy: optional<text>, memo: optional<text>, membership: optional<block>) {\n when_not(membership) {\n let(member_fields): suffix_each(members, \"AccountId\");\n let(guarantor_field): concat(camel(guarantor), \"AccountId\");\n let(contribution_field): camel(contribution);\n let(first_due_field): camel(first_due);\n let(parent_ref_field): concat(camel(instrument), \"Id\");\n let(reference_filter): \"x-hyperscale-reference-filter\";\n fields {\n for member in members { [member]AccountId { type: account<C>; description: concat(\"The \", words(member), \" account\"); [reference_filter]: { column: role; values: [customer_balance]; }; } }\n [guarantor_field] { type: account<C>; description: concat(\"The \", words(guarantor), \" account\"); [reference_filter]: { column: role; values: [customer_balance]; }; }\n [contribution_field] { type: money<C>; description: \"Exact contribution in {C} minor units\"; }\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n [first_due_field] { type: date; description: \"Stored first contribution due date\"; }\n one { type: integer; description: \"Exact fixed member-row count\"; const: 1; }\n }\n lifecycle {\n states forming cancelled;\n for cycle in count {\n let(active_state): concat(\"active_cycle_\", cycle);\n let(ready_state): concat(\"cycle_\", cycle, \"_ready\");\n let(states_key): \"states\";\n [states_key]: [active_state, ready_state];\n }\n states completed;\n initial forming;\n on cancel: forming -> cancelled;\n on activate: forming -> active_cycle_1;\n for cycle in count {\n let(active_state): concat(\"active_cycle_\", cycle);\n let(ready_state): concat(\"cycle_\", cycle, \"_ready\");\n on ready_cycle_[cycle]: active_state -> ready_state;\n let(next_state): if_eq(cycle, count, \"completed\", concat(\"active_cycle_\", add(cycle, 1)));\n on advance_cycle_[cycle]: ready_state -> next_state;\n }\n }\n parties { payer: guarantor_field; }\n required: concat_lists(member_fields, [guarantor_field, \"currency\", contribution_field, first_due_field, \"one\"]);\n title: sentence(instrument);\n id_prefix: prefix(instrument);\n agent_description: \"Reach for rotating pool when a fixed group pays the same contribution every cycle and one member takes the whole pot each cycle, in a declared order. The roster freezes at creation, one contribution row per member, and the pot rotates until every member has been paid. Scheduled is the one payer, one payee shape on a calendar.\";\n summary: concat(count, \"-member rotating pool\");\n description: \"Fixed rotating contribution and payout order\";\n action create { agent_description: \"Create the pool with its frozen roster, cycle count, contribution amount, and payout order. No money moves. Each member's contribution row is created separately, and the pool cannot activate until every row exists.\"; summary: \"Create the fixed roster before activation\"; moves: []; steps: []; }\n action cancel { agent_description: \"Cancel the pool before it activates. Only a forming pool cancels, no money has moved yet, and there is nothing to refund. Once the pool activates this exit is gone.\"; summary: \"Cancel before activation without moving money\"; moves: []; steps: []; }\n action activate {\n agent_description: \"Start cycle one. Refused unless every member on the roster has exactly one contribution row waiting at the first cycle, so a pool can never run a member short. No money moves.\";\n summary: \"Activate only after every fixed member row exists once\";\n for member in members { requires aggregate { instrumentId: concat(instrument, \"_\", member, \"_contribution\"); over: children; refField: parent_ref_field; statuses: [cycle_1_due]; check: { kind: count_equals_field; field: one; }; } }\n moves: [];\n steps: [];\n }\n for cycle in count {\n action ready_cycle_[cycle] {\n agent_description: concat(\"Lock cycle \", cycle, \" once every member row is funded or covered by the guarantor. Refused while any row is still due or defaulted. No money moves here. Locking the cycle is what lets its payouts run.\");\n summary: concat(\"Lock cycle \", cycle, \" only after every member row is funded or guaranteed\");\n let(funded_state): concat(\"cycle_\", cycle, \"_funded\");\n let(guaranteed_state): concat(\"cycle_\", cycle, \"_guaranteed\");\n for member in members { requires aggregate { instrumentId: concat(instrument, \"_\", member, \"_contribution\"); over: children; refField: parent_ref_field; statuses: [funded_state, guaranteed_state]; check: { kind: all_in; }; } }\n moves: [];\n steps: [];\n }\n action advance_cycle_[cycle] {\n let(target_status): if_eq(cycle, count, \"completed\", concat(\"cycle_\", add(cycle, 1), \"_due\"));\n agent_description: concat(\"Close cycle \", cycle, \" once every member row has paid into that cycle pot, moving the pool to the next cycle or to completed on the last one. Refused while any row is unpaid. No money moves here. The money moved on the member rows.\");\n summary: if_eq(cycle, count, \"Complete after the final shared pot pays\", concat(\"Advance after every cycle \", cycle, \" contribution pays\"));\n for member in members { requires aggregate { instrumentId: concat(instrument, \"_\", member, \"_contribution\"); over: children; refField: parent_ref_field; statuses: [target_status]; check: { kind: all_in; }; } }\n moves: [];\n steps: [];\n }\n }\n instruments {\n for member in members {\n instrument {\n let(parent_name): instrument;\n let(child_id): concat(parent_name, \"_\", member, \"_contribution\");\n id: child_id;\n generatedPrefix: true;\n title: concat(sentence(parent_name), \" \", sentence(member), \" Contribution\");\n agent_description: concat(\"One seat in the pool. The \", words(member), \" pays the same contribution every cycle and takes the whole pot on their turn. Every contribution, default, guarantee, and payout for that member happens on this row, never on the pool itself.\");\n summary: concat(words(member), \" contribution row\");\n description: concat(\"Fixed contribution row for \", words(member));\n fields {\n for roster_member in payout_order { [roster_member]AccountId { type: account<C>; description: concat(\"The \", words(roster_member), \" account\"); [reference_filter]: { column: role; values: [customer_balance]; }; } }\n [guarantor_field] { type: account<C>; description: concat(\"The \", words(guarantor), \" account\"); [reference_filter]: { column: role; values: [customer_balance]; }; }\n [contribution_field] { type: money<C>; description: \"Exact contribution amount shared by every cycle\"; }\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n [first_due_field] { type: date; description: \"First due anchor derived from the pool\"; }\n [parent_ref_field] { type: ref<parent_name>; description: concat(\"The exact \", parent_name); pattern: concat(\"^\", prefix(parent_name), \"_(sandbox|live)_[a-z0-9]{8,64}$\"); }\n }\n lifecycle {\n for cycle in count {\n let(states_key): \"states\";\n let(due_state): concat(\"cycle_\", cycle, \"_due\");\n let(funded_state): concat(\"cycle_\", cycle, \"_funded\");\n let(defaulted_state): concat(\"cycle_\", cycle, \"_defaulted\");\n let(guaranteed_state): concat(\"cycle_\", cycle, \"_guaranteed\");\n [states_key]: [due_state, funded_state, defaulted_state, guaranteed_state];\n }\n states final_paid completed;\n initial cycle_1_due;\n for cycle in count {\n let(due_state): concat(\"cycle_\", cycle, \"_due\");\n let(funded_state): concat(\"cycle_\", cycle, \"_funded\");\n let(defaulted_state): concat(\"cycle_\", cycle, \"_defaulted\");\n let(guaranteed_state): concat(\"cycle_\", cycle, \"_guaranteed\");\n on contribute_cycle_[cycle]: due_state -> funded_state;\n on mark_default_cycle_[cycle]: due_state -> defaulted_state;\n on guarantee_cycle_[cycle]: defaulted_state -> guaranteed_state;\n let(next_state): if_eq(cycle, count, \"final_paid\", concat(\"cycle_\", add(cycle, 1), \"_due\"));\n on pay_cycle_[cycle]: funded_state -> next_state;\n on pay_guaranteed_cycle_[cycle]: guaranteed_state -> next_state;\n }\n on close: final_paid -> completed;\n }\n parties { beneficiary: concat(camel(member), \"AccountId\"); payer: guarantor_field; }\n action create {\n agent_description: concat(\"Create the contribution row for the \", words(member), \" and provision its escrow account. The pool must still be forming, and every member needs exactly one row before the pool can activate. No money moves.\");\n summary: concat(\"Create the fixed contribution row for \", words(member));\n requires refs { field: parent_ref_field; statuses: [forming]; unique: true; bind: { [contribution_field]: concat(\"fields.\", contribution_field); currency: \"fields.currency\"; [first_due_field]: concat(\"fields.\", first_due_field); [guarantor_field]: concat(\"fields.\", guarantor_field); for roster_member in members { let(roster_field): concat(camel(roster_member), \"AccountId\"); [roster_field]: concat(\"fields.\", roster_field); } }; }\n moves: [];\n 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; }; }];\n }\n for cycle in count {\n let(active_state): concat(\"active_cycle_\", cycle);\n let(ready_state): concat(\"cycle_\", cycle, \"_ready\");\n let(due_offset): scale_duration(every, sub(cycle, 1));\n let(contribute_name): concat(\"contribute_cycle_\", cycle);\n let(guarantee_name): concat(\"guarantee_cycle_\", cycle);\n let(pay_name): concat(\"pay_cycle_\", cycle);\n let(pay_guaranteed_name): concat(\"pay_guaranteed_cycle_\", cycle);\n let(contribute_capture): concat(\"contributeCycle\", cycle, \"ContributionTransferId\");\n let(guarantee_capture): concat(\"guaranteeCycle\", cycle, \"GuaranteeTransferId\");\n let(pay_capture): concat(\"payCycle\", cycle, \"PayoutTransferId\");\n let(pay_guaranteed_capture): concat(\"payGuaranteedCycle\", cycle, \"PayoutTransferId\");\n let(payout_member): at(payout_order, cycle);\n let(payout_field): concat(camel(payout_member), \"AccountId\");\n action contribute_cycle_[cycle] {\n summary: concat(\"Fund \", words(member), \"'s cycle \", cycle, \" contribution\");\n let(first): if_eq(cycle, 1, true, false);\n when(first) { due: { field: first_due_field; }; sandbox_failure_point: funding; }\n when_not(first) { due: { field: first_due_field; offset: due_offset; }; }\n requires refs { field: parent_ref_field; statuses: [active_state]; }\n moves: [{ key: \"contribution\"; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", contribution_field); }; currency: { from: instance; path: \"fields.currency\"; }; destinationAccountId: { from: instance; path: \"refs.escrowAccountId\"; }; \"metadata.instrumentId\": { from: const; value: child_id; }; \"metadata.instrumentInstanceId\": { from: instance; path: instrumentInstanceId; }; \"metadata.phase\": { from: const; value: contribute_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", camel(member), \"AccountId\"); }; }; capture: { [contribute_capture]: transferId; }; }];\n steps: [];\n }\n action mark_default_cycle_[cycle] {\n summary: concat(\"Mark the stored cycle \", cycle, \" due condition\");\n let(first): if_eq(cycle, 1, true, false);\n when(first) { due: { field: first_due_field; }; }\n when_not(first) { due: { field: first_due_field; offset: due_offset; }; }\n requires refs { field: parent_ref_field; statuses: [active_state]; }\n moves: [];\n steps: [];\n }\n action guarantee_cycle_[cycle] {\n agent_description: concat(\"Have the guarantor fund cycle \", cycle, \" for this member after the row defaulted. The row must be marked defaulted for that cycle. This moves the guarantor's money into the row escrow so the pot still pays in full.\");\n summary: concat(\"Fund the defaulted cycle \", cycle, \" amount before payout\");\n requires refs { field: parent_ref_field; statuses: [active_state]; }\n moves: [{ key: guarantee; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", contribution_field); }; currency: { from: instance; path: \"fields.currency\"; }; destinationAccountId: { from: instance; path: \"refs.escrowAccountId\"; }; \"metadata.instrumentId\": { from: const; value: child_id; }; \"metadata.instrumentInstanceId\": { from: instance; path: instrumentInstanceId; }; \"metadata.phase\": { from: const; value: guarantee_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", guarantor_field); }; }; capture: { [guarantee_capture]: transferId; }; }];\n steps: [];\n }\n action pay_cycle_[cycle] {\n agent_description: concat(\"Pay this row's funded cycle \", cycle, \" contribution out of escrow to whoever holds the pot that cycle. The cycle must be locked first and the row must be funded rather than guaranteed. This moves money and does not reverse.\");\n summary: concat(\"Pay this member's stored contribution into cycle \", cycle, \"'s shared pot recipient\");\n requires refs { field: parent_ref_field; statuses: [ready_state]; }\n moves: [{ key: payout; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", contribution_field); }; currency: { from: instance; path: \"fields.currency\"; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payout_field); }; \"metadata.instrumentId\": { from: const; value: child_id; }; \"metadata.instrumentInstanceId\": { from: instance; path: instrumentInstanceId; }; \"metadata.phase\": { from: const; value: pay_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.escrowAccountId\"; }; }; capture: { [pay_capture]: transferId; }; }];\n let(first): if_eq(cycle, 1, true, false);\n when(first) { sandbox_failure_point: \"release\"; }\n steps: [];\n }\n action pay_guaranteed_cycle_[cycle] {\n agent_description: concat(\"Pay this row's guarantor-funded cycle \", cycle, \" contribution out of escrow to whoever holds the pot that cycle. The cycle must be locked first and the row must be guaranteed rather than funded. This moves money and does not reverse.\");\n summary: concat(\"Pay the funded default into cycle \", cycle, \"'s stored recipient\");\n requires refs { field: parent_ref_field; statuses: [ready_state]; }\n moves: [{ key: payout; operation: internal_transfer.create; bind: { amount: { from: instance; path: concat(\"fields.\", contribution_field); }; currency: { from: instance; path: \"fields.currency\"; }; destinationAccountId: { from: instance; path: concat(\"fields.\", payout_field); }; \"metadata.instrumentId\": { from: const; value: child_id; }; \"metadata.instrumentInstanceId\": { from: instance; path: instrumentInstanceId; }; \"metadata.phase\": { from: const; value: pay_guaranteed_name; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: \"refs.escrowAccountId\"; }; }; capture: { [pay_guaranteed_capture]: transferId; }; }];\n steps: [];\n }\n }\n action close { agent_description: \"Retire this member row after its final payout. Refused while the row escrow account still holds anything, so a row can never close over stranded money. No money moves.\"; summary: \"Complete after the final payout drains this member custody\"; requires drained: { path: \"refs.escrowAccountId\"; }; moves: []; steps: []; }\n }\n }\n }\n }\n\n when(membership) {\n let(membership_instrument): get(membership, \"instrument\");\n let(membership_ref_field): get(membership, \"ref_field\");\n let(joined_status): get(membership, \"joined_status\");\n let(active_status): get(membership, \"active_status\");\n let(paid_status): get(membership, \"paid_status\");\n let(custody_ref): get(membership, \"custody_ref\");\n let(instrument_id_prefix): get(membership, \"id_prefix\");\n let(active_parked): get(membership, \"active_parked\");\n let(min_members): get(membership, \"min_members\");\n let(max_members): get(membership, \"max_members\");\n let(contribution_description): get(membership, \"contribution_description\");\n let(currency_description): get(membership, \"currency_description\");\n let(create_examples): get(membership, \"create_examples\");\n let(activate_examples): get(membership, \"activate_examples\");\n let(disband_examples): get(membership, \"disband_examples\");\n let(close_examples): get(membership, \"close_examples\");\n let(contribution_field): camel(contribution);\n let(count_field): camel(count);\n let(first_due_field): camel(first_due);\n\n fields {\n [contribution_field] { type: money<C>; description: contribution_description; }\n currency { type: text; description: currency_description; maxLength: 3; minLength: 3; pattern: \"^[A-Z]{3}$\"; }\n [count_field] { type: integer; maximum: max_members; minimum: min_members; }\n when(memo) { [memo] { type: text; optional: true; maxLength: 180; minLength: 1; } }\n [first_due_field] { type: date; }\n }\n lifecycle {\n states forming active completed disbanded;\n initial forming;\n on activate: forming -> active;\n on close: active -> completed;\n on disband: forming -> disbanded;\n }\n required: [count_field, contribution_field, currency, first_due_field];\n aggregate_invariants { childInstrumentId: membership_instrument; childRefField: membership_ref_field; childStatuses: [joined_status, active_status, paid_status]; count: true; parentField: count_field; }\n caller_parked_states { active: active_parked; }\n agent_description: concat(\"Reach for \", words(instrument), \" when a fixed group pays the same contribution every cycle and one member takes the whole pot each cycle, and the product publishes its own membership rows. This shape owns the pot account and the seat count. Every contribution and payout happens on the membership row that references it, never here.\");\n id_prefix: instrument_id_prefix;\n action create {\n public: none;\n agent_description: concat(\"Open the \", words(instrument), \" and provision the pot account every contribution lands in. No money moves. The roster is still empty, so the pool stays forming until one membership row exists per seat.\");\n examples: create_examples;\n moves: [];\n 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; }; }];\n }\n action activate {\n public: none;\n due { field: first_due_field; }\n examples: activate_examples;\n requires aggregate { instrumentId: membership_instrument; over: children; refField: membership_ref_field; statuses: [joined_status]; check: { kind: count_equals_field; field: count_field; }; }\n moves: [];\n steps: [];\n }\n action disband { public: none; agent_description: concat(\"Close a \", words(instrument), \" that never filled its roster. Only a forming pool disbands, no money has moved, and there is nothing to refund. Once the pool activates this exit is gone.\"); examples: disband_examples; moves: []; steps: []; }\n action close {\n public: none;\n agent_description: concat(\"Retire the \", words(instrument), \" after every membership row has been paid. Refused while any row is unpaid or while the pot account still holds anything, so a pool can never close over stranded money. No money moves.\");\n examples: close_examples;\n requires aggregate { instrumentId: membership_instrument; over: children; refField: membership_ref_field; statuses: [paid_status]; check: { kind: all_in; }; }\n requires drained { path: concat(\"refs.\", custody_ref); }\n moves: [];\n steps: [];\n }\n }\n}\n"],
|
|
18
|
-
["money_flows/scheduled.hsx", "module std.money_flows.scheduled\n\n// Calendar-anchored payments supporting fixed installment plans, recurring subscriptions, or debt obligations.\n//\n// ### Purpose\n// `scheduled` automates time-anchored payment series between one payer and one payee.\n// It supports three modes:\n//\n// 1. Installment plan: Partitions a fixed total amount into `count` installments, each collected on its own stored date.\n// 2. Open recurring subscription: Charges a recurring amount on an interval `every` until an `until` port fires.\n// 3. Debt obligation: Manages legally binding multi-installment debt with delinquency tracking, mandate evidence, and child payment records.\n//\n// ### Selection guidance\n// - vs `metered`: `scheduled` executes calendar-based recurring charges or fixed installment plans.\n// `metered` bills variable usage per event based on a committed rate card.\n// - vs `rotating_pool`: `scheduled` coordinates one payer to one payee on a calendar.\n// `rotating_pool` coordinates a multi-party peer circle where members rotate turns receiving the entire pot.\n//\n// ### Parameters\n// - `payer`: The paying party (or repayment source).\n// - `payee`: The beneficiary party receiving installment funds.\n// - `amount`: Total amount to partition into installments or recurring charge amount in minor units of currency `C`.\n// - `count`: Optional number of installments for installment or obligation modes.\n// - `every`: Recurrence cadence duration string (e.g. `\"P30D\"`, `\"P1M\"`).\n// - `first_due`: Stored date anchor for the first installment or charge.\n// - `mode`: Optional mode selector (`obligation`). Omit for standard installment plans.\n// - `debtor`: Optional debtor party when distinct from the payment source in obligation mode.\n// - `advance_to`: Optional third-party receiving upfront advance disbursements in obligation mode.\n// - `until`: Optional condition port triggering cancellation of recurring subscription runs.\n// - `month_end`: Optional handling policy for calendar month-end adjustments.\n// - `period_liability`: Optional liability policy for billing periods.\n// - `termination_drain`: Optional policy for draining balances on early termination.\n// - `mandate`: Optional condition port supplying formal debit mandate evidence.\n// - `derived_amount`: Optional calculated markup or fee rules.\n//\n// ### Decision ports\n// - `until`: Port terminating recurring subscriptions.\n// - `mandate`: Port supplying mandate verification evidence for direct debit obligations.\n//\n// ### Example\n// ```hsx\n// program scheduled_example \"Scheduled example\"\n// import { scheduled } from \"std/money_flows\"\n// party payer: business\n// party payee: business\n// settlement installments = scheduled {\n// payer: payer\n// payee: payee\n// amount: totalAmount: money(SAR)\n// count: 3\n// every: P30D\n// first_due: firstDueAt\n// }\n// ```\nexport instrument scheduled<C>(\n payer: party,\n payee: party,\n amount: money<C>,\n count: optional<integer>,\n every: text,\n first_due: date,\n mode: optional<text>,\n debtor: optional<party>,\n advance_to: optional<party>,\n until: optional<condition>,\n month_end: optional<text>,\n period_liability: optional<text>,\n termination_drain: optional<text>,\n mandate: optional<condition>,\n derived_amount: optional<unknown>\n) {\n 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.\";\n when_not(mode) {\n let(instrument_words): words(instrument);\n\n title: sentence(instrument);\n summary: \"{count}-installment schedule from {payer} to {payee}\";\n description: \"Scheduled payment: the {payer} pays {amount} to the {payee} in {count} installments, one every {every}\";\n partitions: { totalField: amount; pieceFields: names(\"installment\", count, \"Amount\"); };\n\n fields {\n amount: {\n type: money<C>;\n description: \"The total scheduled amount in {C} minor units; the installment fields below partition it exactly\";\n }\n first_due: {\n type: date;\n description: \"Due date of the first installment; installment k falls {every} after its predecessor\";\n }\n for i in count {\n let(remainder_words): if_eq(i, 1, \" (carries the integer-division remainder)\", \"\");\n installment[i]Amount: {\n type: money<C>;\n description: \"Installment {i} of {count}{remainder_words}: a stored share of {amount}, collected on its own stored-date anchor\";\n }\n }\n }\n\n parties {\n payer: payer;\n beneficiary: payee;\n }\n\n lifecycle {\n states active;\n for i in sub(count, 1) { states collecting_[i]; }\n states settled;\n initial active;\n for i in count {\n let(from_state): if_eq(i, 1, \"active\", concat(\"collecting_\", sub(i, 1)));\n let(to_state): if_eq(i, count, \"settled\", concat(\"collecting_\", i));\n on pay_installment_[i]: from_state -> to_state;\n }\n }\n\n action create {\n agent_description: \"Create the installment plan. No money moves. The total and its per-installment amounts are fixed here, and each installment collects on its own stored due date from then on.\";\n summary: \"Create a {instrument_words} plan\";\n steps: [];\n }\n\n for i in count {\n action pay_installment_[i] {\n let(first): if_eq(i, 1, true, false);\n let(offset): scale_duration(every, sub(i, 1));\n let(installment_field): concat(\"installment\", i, \"Amount\");\n summary: \"Collect installment {i} of {count}\";\n when(first) { due: { field: first_due; }; sandbox_failure_point: funding; }\n when_not(first) { due: { field: first_due; offset: offset; }; }\n moves: [{ amount: installment_field; from: payer; to: beneficiary; key: transfer; }];\n steps: [];\n }\n }\n }\n\n when_eq(mode, obligation) {\n when(count) {\n let(instrument_words): words(instrument);\n let(amount_field): concat(amount);\n let(first_due_field): concat(first_due);\n let(payer_field): camel(concat(payer, \"_account_id\"));\n let(payer_words): words(payer);\n let(payee_field): camel(concat(payee, \"_account_id\"));\n let(payee_words): words(payee);\n let(debtor_field): camel(concat(debtor, \"_account_id\"));\n let(debtor_words): words(debtor);\n let(advance_field): camel(concat(advance_to, \"_account_id\"));\n let(advance_words): words(advance_to);\n let(parent_ref_field): camel(concat(instrument, \"_id\"));\n let(has_advance): advance_to;\n let(same_debtor): if_eq(payer, debtor, true, false);\n let(parent_statuses): concat_lists([\"active\"], names(\"installment_\", count, \"_delinquent\"));\n let(delinquent_statuses): names(\"installment_\", count, \"_delinquent\");\n let(last_offset): scale_duration(every, sub(count, 1));\n let(reference_filter_key): \"x-hyperscale-reference-filter\";\n let(fee_port_key): \"x-hyperscale-fee-collection-port\";\n let(derived_field): concat(get(derived_amount, \"field\"));\n let(derived_source): concat(get(derived_amount, \"source\"));\n let(derived_rule): get(derived_amount, \"rule\");\n let(derived_bps): add(derived_rule, 0);\n let(derived_percent): concat(derived_rule);\n let(derived_bearer): get(derived_amount, \"bearer\");\n let(derived_bearer_field): camel(concat(derived_bearer, \"_account_id\"));\n let(metadata_instrument_key): \"metadata.instrumentId\";\n let(metadata_instance_key): \"metadata.instrumentInstanceId\";\n let(metadata_phase_key): \"metadata.phase\";\n\n title: sentence(instrument);\n summary: \"{count}-anchor obligation for {debtor_words}\";\n description: \"Installment obligation for {debtor_words}; {payer_words} pays {payee_words} against exact stored anchors\";\n partitions: { total_field: amount_field; piece_fields: names(\"installment\", count, \"Amount\"); };\n when(derived_amount) {\n computes derived: {\n field: derived_field;\n source_field: derived_source;\n rule: { kind: percentage_of; bps: derived_bps; };\n rounding: floor;\n };\n }\n for i in count {\n aggregate_invariants: {\n child_field: \"amount\";\n child_instrument_id: concat(instrument, \"_installment_\", i, \"_payment\");\n child_ref_field: parent_ref_field;\n child_statuses: [paid];\n parent_field: concat(\"installment\", i, \"Amount\");\n };\n }\n required: concat_lists(\n [payee_field, payer_field, \"currency\", amount_field, first_due_field],\n names(\"installment\", count, \"Amount\"),\n if_eq(has_advance, false, [], [advance_field]),\n if_eq(same_debtor, true, [], [debtor_field]),\n if_eq(derived_amount, false, [], [\"platformAccountId\"])\n );\n\n fields {\n when(has_advance) {\n [payee_field]: {\n type: account;\n description: \"The {payee_words} account\";\n [reference_filter_key]: { column: role; values: [customer_balance]; };\n }\n }\n when(same_debtor) {\n [payer_field]: {\n type: account;\n description: \"The {payer_words} account\";\n [reference_filter_key]: { column: role; values: [customer_balance]; };\n }\n }\n when_not(same_debtor) {\n [debtor_field]: {\n type: account;\n description: \"The {debtor_words} account\";\n [reference_filter_key]: { column: role; values: [customer_balance]; };\n }\n }\n when(derived_amount) {\n platform_account_id: {\n type: account;\n description: \"The product fee collection account\";\n [fee_port_key]: true;\n [reference_filter_key]: { column: role; values: [customer_balance, product_revenue]; };\n }\n }\n currency: {\n type: text;\n description: \"ISO 4217 currency code\";\n max_length: 3;\n min_length: 3;\n pattern: \"^[A-Z]{3}$\";\n }\n amount: {\n type: money<C>;\n description: \"The principal in {C} minor units; the stored installment anchors partition it exactly\";\n }\n first_due: {\n type: date;\n description: \"Due date of the first installment; later anchors use fixed offsets of {every}\";\n }\n when(derived_amount) {\n [derived_field]: {\n type: money<C>;\n optional: true;\n pattern: \"^[1-9][0-9]{0,17}$\";\n description: \"Machine-computed {derived_percent} of {derived_source}; callers never supply it\";\n }\n }\n for i in count {\n let(remainder_words): if_eq(i, 1, \" (carries the integer-division remainder)\", \"\");\n installment[i]Amount: {\n type: money<C>;\n description: \"Stored amount for installment {i} of {count}{remainder_words}\";\n }\n installment[i]DelinquentAfter: {\n type: date;\n optional: true;\n description: \"Machine-set marker proving installment {i} reached its stored due date while unpaid\";\n }\n }\n }\n\n parties {\n when(has_advance) { beneficiary: advance_to; }\n when_not(has_advance) { beneficiary: payee; }\n when_not(same_debtor) { payer: payer; }\n }\n\n lifecycle {\n states draft;\n when(has_advance) { states approved; }\n states active;\n let(states_key): \"states\";\n [states_key]: delinquent_statuses;\n states written_off repaid;\n initial draft;\n when(has_advance) { on approve: draft -> approved; on advance: approved -> active; }\n when_not(has_advance) { on approve: draft -> active; }\n for i in count {\n let(target): concat(\"installment_\", i, \"_delinquent\");\n let(other_states): without(parent_statuses, target);\n on collect_installment_[i]: delinquent_statuses -> active;\n let(mark_action): concat(\"mark_installment_\", i, \"_delinquent\");\n on [mark_action]: other_states -> target;\n }\n on complete: parent_statuses -> repaid;\n when(has_advance) { on write_off: draft|approved|parent_statuses -> written_off; }\n when_not(has_advance) { on write_off: draft|parent_statuses -> written_off; }\n }\n\n action create {\n agent_description: \"Create the obligation in draft with its principal split across stored installment anchors. Approve is the next call. Where the product declares a derived fee, creating the obligation also moves that fee from its bearer to the platform account.\";\n summary: \"Create a {instrument_words} obligation\";\n when(derived_amount) {\n moves: [{\n key: \"derived_amount\";\n operation: \"internal_transfer.create\";\n bind: {\n amount: { from: instance; path: concat(\"fields.\", derived_field); };\n currency: { from: instance; path: fields.currency; };\n destination_account_id: { from: instance; path: fields.platformAccountId; };\n source_account_id: { from: instance; path: concat(\"fields.\", derived_bearer_field); };\n [metadata_instrument_key]: { from: const; value: instrument; };\n [metadata_instance_key]: { from: instance; path: instrumentInstanceId; };\n [metadata_phase_key]: { from: const; value: create; };\n product_id: { from: instance; path: productId; };\n };\n capture: { create_derived_amount_transfer_id: transferId; };\n }];\n }\n steps: [];\n }\n action approve {\n agent_description: \"Approve the draft obligation and freeze its principal partition and due anchors. The obligation must be in draft. No money moves. Where the product advances funds, approve is what makes advance available.\";\n summary: \"Approve the immutable principal partition and stored anchors\";\n steps: [];\n }\n when(has_advance) {\n action advance {\n agent_description: \"Pay the principal out to the advance recipient and put the obligation into repayment. The obligation must be approved. This moves the whole principal and does not reverse, so approve is the last point to stop.\";\n summary: \"Advance the principal to the {advance_words}; the internal ledger receipt is the confirmation\";\n moves: [{ amount: amount_field; from: payee; to: beneficiary; key: advance; }];\n steps: [];\n }\n }\n for i in count {\n let(child_id): concat(instrument, \"_installment_\", i, \"_payment\");\n let(target_field): concat(\"installment\", i, \"Amount\");\n let(marker_field): concat(\"installment\", i, \"DelinquentAfter\");\n let(delinquent_action): concat(\"mark_installment_\", i, \"_delinquent\");\n let(offset): scale_duration(every, sub(i, 1));\n action collect_installment_[i] {\n let(first): if_eq(i, 1, true, false);\n summary: \"Close delinquent installment {i} after linked payments reach its stored amount\";\n when(first) { due: { field: first_due_field; }; }\n when_not(first) { due: { field: first_due_field; offset: offset; }; }\n requires aggregate: {\n check: { amount_field: \"amount\"; kind: sum_exactly; target_field: target_field; };\n instrument_id: child_id;\n over: children;\n ref_field: parent_ref_field;\n statuses: [paid];\n };\n steps: [];\n }\n action [delinquent_action] {\n let(first): if_eq(i, 1, true, false);\n summary: \"Mark installment {i} delinquent only when its due anchor is unmet\";\n when(first) { due: { field: first_due_field; }; }\n when_not(first) { due: { field: first_due_field; offset: offset; }; }\n requires aggregate: {\n check: { amount_field: \"amount\"; kind: sum_below; target_field: target_field; };\n instrument_id: child_id;\n over: children;\n ref_field: parent_ref_field;\n statuses: [paid];\n };\n sets_at: { field: marker_field; marker: true; offset: PT1S; };\n steps: [];\n }\n }\n action complete {\n summary: \"Close the obligation only after every stored anchor is paid exactly\";\n due: { field: first_due_field; offset: last_offset; };\n for i in count {\n requires aggregate: {\n check: { amount_field: \"amount\"; kind: sum_exactly; target_field: concat(\"installment\", i, \"Amount\"); };\n instrument_id: concat(instrument, \"_installment_\", i, \"_payment\");\n over: children;\n ref_field: parent_ref_field;\n statuses: [paid];\n };\n }\n steps: [];\n }\n action write_off {\n agent_description: \"Write off the remaining exposure and stop the obligation. No money moves and nothing is refunded. This is the bookkeeping exit for debt that will not be collected. A written-off obligation never returns to repayment.\";\n summary: \"Write off the remaining exposure without moving money\";\n steps: [];\n }\n\n instruments {\n for i in count {\n instrument {\n let(has_mandate): mandate;\n id: concat(instrument, \"_installment_\", i, \"_payment\");\n generatedPrefix: true;\n title: concat(sentence(instrument), \" Installment \", i, \" Payment\");\n agent_description: concat(\"One payment against installment \", i, \" of the obligation. Payments can be partial, so create a record, repay it, and the parent installment closes once its paid records sum to the stored anchor amount.\");\n summary: concat(\"Anchor-bound payment for installment \", i);\n description: concat(\"One partial or full payment bound to installment \", i, \" of \", instrument, \"; the operation name fixes the anchor and \", parent_ref_field, \" fixes the obligation\");\n fields {\n amount: {\n type: money<C>;\n description: concat(\"Positive payment amount capped with its paid siblings at installment\", i, \"Amount\");\n }\n currency: {\n type: text;\n description: \"ISO 4217 currency code\";\n max_length: 3;\n min_length: 3;\n pattern: \"^[A-Z]{3}$\";\n }\n [parent_ref_field]: {\n type: ref<instrument>;\n description: \"The exact {instrument_words} this payment belongs to\";\n pattern: concat(\"^\", prefix(instrument), \"_(sandbox|live)_[a-z0-9]{8,64}$\");\n }\n }\n parties { beneficiary: payee; payer: payer; }\n lifecycle {\n states created paid refunded;\n initial created;\n on repay: created -> paid;\n on refund: paid -> refunded;\n }\n action create {\n agent_description: concat(\"Open a payment record against installment \", i, \". The parent obligation must be live. No money moves yet. Repay is what pays.\");\n summary: concat(\"Create a payment record for installment \", i);\n requires refs: {\n field: parent_ref_field;\n statuses: parent_statuses;\n bind: {\n currency: fields.currency;\n [payer_field]: concat(\"fields.\", payer_field);\n [payee_field]: concat(\"fields.\", payee_field);\n };\n };\n steps: [];\n }\n action repay {\n agent_description: concat(\"Pay this record's amount toward installment \", i, \". The record must be created and its obligation live. Refused when this amount plus the records already paid against that installment would pass the stored amount. This moves the payer's money. Where a mandate applies, its declared parties may call it and an evidence reference is required.\");\n summary: concat(\"Pay a partial or full amount against installment \", i);\n when(has_mandate) {\n input: {\n additional_properties: false;\n properties: {\n evidence_reference: {\n description: \"Required decision reference retained in the operation receipt\";\n max_length: 180;\n min_length: 1;\n type: string;\n };\n };\n required: [evidenceReference];\n type: object;\n };\n capture_input: { mandate_evidence_reference: evidenceReference; };\n port: { allowed_parties: mandate_allowed; };\n }\n requires refs: {\n field: parent_ref_field;\n statuses: parent_statuses;\n match: {\n let(payer_match): concat(\"fields.\", payer_field);\n let(payee_match): concat(\"fields.\", payee_field);\n let(currency_match): \"fields.currency\";\n [payer_match]: payer_match;\n [payee_match]: payee_match;\n [currency_match]: currency_match;\n };\n };\n requires exposure: {\n amount_field: \"amount\";\n anchor_field: parent_ref_field;\n cap_field: concat(\"installment\", i, \"Amount\");\n cap_on_anchor: true;\n child_instrument_id: concat(instrument, \"_installment_\", i, \"_payment\");\n statuses: [paid];\n };\n moves: [{ amount: \"amount\"; from: payer; to: beneficiary; key: repayment; }];\n steps: [];\n }\n action refund {\n agent_description: concat(\"Return one whole installment \", i, \" payment to the payer. The record must be paid, and the refund is always its full amount, so split a payment across records when a partial refund could be needed. This moves money back.\");\n summary: concat(\"Refund this one stored installment \", i, \" payment whole\");\n requires refs: {\n field: parent_ref_field;\n statuses: parent_statuses;\n match: {\n let(payer_match): concat(\"fields.\", payer_field);\n let(payee_match): concat(\"fields.\", payee_field);\n let(currency_match): \"fields.currency\";\n [payer_match]: payer_match;\n [payee_match]: payee_match;\n [currency_match]: currency_match;\n };\n };\n moves: [{ amount: \"amount\"; from: beneficiary; to: payer; key: refund; }];\n steps: [];\n }\n }\n }\n }\n }\n\n when_not(count) {\n let(amount_field): concat(amount);\n let(until_name): concat(until);\n title: sentence(instrument);\n summary: \"Open {every} obligation for {payer}\";\n description: \"Open monthly obligation for {payer}; one period liability at a time\";\n\n fields {\n amount: {\n type: money<C>;\n description: \"Exact charge for one {every} period in {C} minor units\";\n }\n currency: {\n type: text;\n const: C;\n description: \"ISO 4217 currency code\";\n }\n first_due: {\n type: date;\n description: \"First period anchor; missing Gregorian month days clamp to the target month's final day\";\n }\n }\n\n parties { payer: payer; beneficiary: payee; }\n\n lifecycle {\n states active period_open canceled;\n initial active;\n on open_period: active -> period_open;\n on collect_period: period_open -> active;\n on [until]: active|period_open -> canceled;\n }\n\n action create {\n agent_description: \"Create the open obligation before its first period anchor. No money moves. The charge for one period and the first anchor are fixed here, and periods then open on their own.\";\n summary: \"Create the open obligation before its first anchor\";\n steps: [];\n }\n\n action open_period {\n summary: \"Open exactly one period liability without moving money\";\n due: {\n field: first_due;\n every: {\n period: { calendar: gregorian; month_end: month_end; months: 1; };\n until_action: until_name;\n liability: period_liability;\n delinquency: parent_policy;\n drain_action: termination_drain;\n };\n };\n steps: [];\n }\n\n action collect_period {\n agent_description: \"Collect one open period charge from the payer, with the mandate evidence reference kept in the receipt. A period must be open. The mandate's declared parties may call it, or the payer when no mandate is wired. This moves the period amount and does not reverse.\";\n summary: \"Collect one authored period charge with mandate evidence\";\n input: {\n additional_properties: false;\n properties: {\n evidence_reference: {\n description: \"Required decision reference retained in the operation receipt\";\n max_length: 180;\n min_length: 1;\n type: string;\n };\n };\n required: [evidenceReference];\n type: object;\n };\n capture_input: { mandate_evidence_reference: evidenceReference; };\n when(mandate) { port: { allowed_parties: mandate_allowed; }; }\n when_not(mandate) { port: { allowed_parties: [payer]; }; }\n moves: [{ amount: amount_field; from: payer; to: beneficiary; key: period_collection; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n\n action [until] {\n agent_description: \"Cancel the open schedule for good and drain any period liability still standing. Only the payer may call it, and the reference fields the port declares are required. No further period opens and there is no reactivation.\";\n summary: \"Cancel the open schedule and drain any period liability\";\n when(until_fields) {\n input: {\n additional_properties: false;\n properties: {\n for item in keys(until_fields) {\n [item]: get(until_fields, item);\n }\n };\n required: keys(until_fields);\n type: object;\n };\n capture_input: {\n for item in keys(until_fields) { [item]: item; }\n };\n }\n port { allowed_parties: until_allowed; };\n steps: [];\n }\n }\n }\n}\n"],
|
|
19
|
-
["money_flows/security_deposit.hsx", "module std.money_flows.security_deposit\n\n// Collateral deposit reserved against a payer's account in favor of a holder, returned in full or claimed against damages.\n//\n// ### Purpose\n// `security_deposit` reserves collateral funds for vehicle rentals, property leasing, equipment hire, and security bonds.\n// Funds remain held until the rental or lease concludes. The holder can either return the deposit in full\n// or assess damages, claim a decided partial amount, and return the exact unspent remainder.\n//\n// ### Selection guidance\n// - vs `held_payment`: `security_deposit` holds collateral where the standard outcome is returning 100% of the funds\n// to the payer, and claims are partial damage assessments. `held_payment` holds payment consideration where the\n// standard outcome is releasing 100% of the funds to the payee upon delivery.\n// - vs `cancellable_booking`: `cancellable_booking` holds booking fees and applies time-based cancellation penalties.\n// `security_deposit` holds damage collateral and supports arbitrary damage claims with remainder refund.\n//\n// ### Parameters\n// - `payer`: The customer providing the deposit collateral.\n// - `holder`: The merchant or owner holding the deposit rights.\n// - `amount`: Total reserved deposit amount in minor units of currency `C`.\n// - `claim`: Optional condition port triggering a damage claim.\n// - `return`: Optional condition port triggering full return of the deposit.\n// - `claim_amount`: Optional block defining partial claim logic (`field`, `bound`, `remainder`).\n// - `deadline`: Optional stored date anchor for automatic deposit release or expiry.\n// - `claim_to`: Optional alternate destination for claimed funds.\n// - `return_to`: Optional alternate destination for returned funds.\n// - `memo`: Optional memo text stored on the deposit record.\n// - `claim_input`: Optional custom input schema for the claim action.\n// - `claim_capture`: Optional capture mappings for claim evidence.\n// - `fund_failure_point`: Optional configuration for deposit funding failure.\n// - `id_prefix_override`: Optional custom prefix for generated instrument IDs.\n//\n// ### Decision ports\n// - `claim`: Port permitting the holder to submit a damage claim.\n// - `return`: Port permitting the holder to return the deposit to the payer.\n//\n// ### Example\n// ```hsx\n// program deposit_example \"Deposit example\"\n// import { security_deposit } from \"std/money_flows\"\n// party renter: person\n// party owner: business\n// settlement security_deposit = security_deposit {\n// payer: renter\n// holder: owner\n// amount: depositAmount: money(SAR)\n// claim: port assess_damage\n// claim_amount: decided {\n// field: damageAmount\n// bound: depositAmount\n// remainder: return\n// }\n// return: port return_deposit\n// }\n// port assess_damage {\n// allowed: [owner]\n// shape: { damageAmount: money(SAR), evidence: text }\n// }\n// port return_deposit { allowed: [owner] }\n// ```\nexport instrument security_deposit<C>(payer: party, holder: party, amount: money<C>, claim: optional<condition>, return: optional<condition>, claim_amount: optional<block>, deadline: optional<date>, claim_to: optional<party>, return_to: optional<party>, memo: optional<text>, claim_input: optional<block>, claim_capture: optional<block>, fund_failure_point: optional<text>, id_prefix_override: optional<text>) {\n let(claim_name): claim;\n let(return_name): return;\n let(payer_account_field): camel(concat(payer, \"_account_id\"));\n let(holder_account_field): camel(concat(holder, \"_account_id\"));\n let(claim_field): get(claim_amount, \"field\");\n let(claim_bound): get(claim_amount, \"bound\");\n let(claim_remainder): get(claim_amount, \"remainder\");\n let(deadline_field): concat(deadline);\n let(memo_field): concat(memo);\n agent_description: \"Reach for security deposit when money is reserved against the {payer} in the {holder}'s favor and later claimed or returned. The funds stay reserved and move only on the claim. Pick captured payment when the holder takes the money in slices, and instant transfer when nothing is held.\";\n summary: \"Refundable deposit from {payer} held for {holder}\";\n title: sentence(instrument);\n when(id_prefix_override) { id_prefix: id_prefix_override; }\n when_not(deadline) {\n when_not(claim_amount) {\n description: concat(\"Deposit: the \", amount, \" is reserved against the \", payer, \"'s account in the \", holder, \"'s favor, then claimed or returned in full\");\n }\n when(claim_amount) {\n description: concat(\"Deposit: the \", amount, \" is reserved against the \", payer, \"'s account in the \", holder, \"'s favor, then \", claim_field, \" is claimed and the exact remainder returned, or the deposit is returned in full\");\n }\n }\n fields {\n when_not(deadline) {\n amount { type: money<C>; description: \"The deposit amount in {C} minor units, reserved in full and fully accounted on claim or return\"; }\n }\n when(deadline) {\n let(reference_filter_key): \"x-hyperscale-reference-filter\";\n amount { type: money<C>; description: \"Positive minor-unit integer amount serialized as a string (at most 18 digits)\"; }\n [deadline_field] { type: date; }\n [holder_account_field] { type: account<C>; description: \"Hyperscale account ID\"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n [payer_account_field] { type: account<C>; description: \"Hyperscale account ID\"; [reference_filter_key]: { column: role; values: [customer_balance]; }; }\n when(memo) { [memo_field] { type: text; max_length: 180; min_length: 1; optional: true; } }\n }\n }\n lifecycle {\n when_not(deadline) {\n states created held claimed returned;\n initial created;\n on place_deposit: created -> held;\n on [claim_name]: held -> claimed;\n on [return_name]: held -> returned;\n }\n when(deadline) {\n states created held claimed returned expired canceled;\n initial created;\n on cancel: created -> canceled;\n on claim: held -> claimed;\n on expire: held -> expired;\n on fund: created -> held;\n on return: held -> returned;\n }\n }\n parties {\n payer: payer;\n beneficiary: holder;\n }\n when_not(deadline) {\n action create {\n agent_description: \"Open the deposit row before anything is reserved. The caller supplies the {amount}, the currency, and the {payer} and {holder} accounts. Nothing is reserved yet, call place deposit next.\";\n summary: concat(\"Create a \", words(instrument));\n moves: [];\n steps: [];\n }\n action place_deposit {\n agent_description: \"Reserve the {amount} against the {payer}'s account in the {holder}'s favor. The deposit must be created. The money is held, not paid: the claim posts it to the {holder} and the return releases it back.\";\n let(metadata_instrument): \"metadata.instrumentId\";\n let(metadata_instance): \"metadata.instrumentInstanceId\";\n let(metadata_phase): \"metadata.phase\";\n summary: \"Reserve the {amount} against the {payer}'s account\";\n moves: [{ key: reservation; operation: internal_transfer.reserve; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", holder_account_field); }; [metadata_instrument]: { from: const; value: instrument; }; [metadata_instance]: { from: instance; path: instrumentInstanceId; }; [metadata_phase]: { from: const; value: place_deposit; }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { placeDepositReservationTransferId: transferId; }; }];\n sandbox_failure_point: funding;\n steps: [];\n }\n action [claim_name] {\n agent_description: \"Claim the held deposit for the {holder}. The deposit must be in held. The reservation settles here, whole or up to the decided amount with the exact remainder released back to the {payer}. There is no return afterwards.\";\n summary: \"Claim the deposit for the {holder} through {claim}\";\n when(claim_allowed) { port { allowed_parties: claim_allowed; }; }\n when_not(claim_amount) {\n moves: [{ key: post; operation: internal_transfer.post; bind: { transferId: { from: instance; path: refs.placeDepositReservationTransferId; }; }; }];\n }\n when(claim_amount) {\n let(decided_clause): \"decided amount\";\n input {\n type: object;\n additional_properties: false;\n properties {\n for input_name in keys(claim_fields) {\n [input_name]: get(claim_fields, input_name);\n }\n }\n required: keys(claim_fields);\n }\n [decided_clause]: { field: claim_field; bound_field: claim_bound; remainder_action: return_name; };\n moves: [{ key: post; operation: internal_transfer.post; bind: { amount: { from: input; path: claim_field; }; currency: { from: instance; path: fields.currency; }; postMode: { from: const; value: partial_only; }; transferId: { from: instance; path: refs.placeDepositReservationTransferId; }; }; }, { key: remainder; operation: internal_transfer.void; bind: { reason: { from: const; value: \"Return the unclaimed deposit remainder\"; }; transferId: { from: instance; path: refs.placeDepositReservationTransferId; }; }; }];\n }\n steps: [];\n }\n action [return_name] {\n agent_description: \"Return the held deposit to the {payer}. The deposit must be in held and unclaimed. This releases the reservation whole and the {holder} receives nothing.\";\n summary: \"Return the deposit to the {payer} through {return}\";\n when(return_fields) {\n input {\n type: object;\n additional_properties: false;\n properties {\n for input_name in keys(return_fields) {\n [input_name]: get(return_fields, input_name);\n }\n }\n required: keys(return_fields);\n }\n capture_input: {\n for item in keys(return_fields) { [item]: item; }\n };\n }\n port { allowed_parties: return_allowed; };\n moves: [{ key: void; operation: internal_transfer.void; bind: { reason: { from: const; value: \"Deposit returned in full\"; }; transferId: { from: instance; path: refs.placeDepositReservationTransferId; }; }; }];\n steps: [];\n }\n }\n when(deadline) {\n required: [payer_account_field, holder_account_field, amount, currency, deadline_field];\n caller_parked_states: { created: \"funding or canceling an unfunded hold is the payer's decision\"; };\n distinct_parties: true;\n dials: [{ field: deadline_field; key: holding_window; kind: window; maxOffset: \"P90D\"; summary: \"Maximum time before the machine returns an unclaimed hold.\"; title: \"Holding window\"; }];\n action create {\n agent_description: \"Open the hold by recording the {payer}, the {holder}, the {amount}, and the deadline. No money moves here, so a created hold costs the {payer} nothing until fund runs. The deadline is when the machine returns anything still unclaimed.\";\n deadline: { field: deadline_field; };\n summary: concat(\"Create a \", words(instrument));\n moves: [];\n steps: [];\n }\n action fund {\n agent_description: \"Reserve the whole {amount} out of the {payer}'s balance for the {holder}. This moves money and the {payer} can no longer spend it. Only a created hold funds. The reservation carries no expiry of its own, so the hold's deadline governs when it comes back.\";\n deadline: { field: deadline_field; };\n moves: [{ key: reservation; operation: internal_transfer.reserve; bind: { amount: { from: instance; path: concat(\"fields.\", amount); }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", holder_account_field); }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", payer_account_field); }; }; capture: { holdTransferId: transferId; }; }];\n when(fund_failure_point) { sandbox_failure_point: fund_failure_point; }\n when_not(fund_failure_point) { sandbox_failure_point: funding; }\n steps: [];\n summary: \"Fund the security deposit\";\n }\n action claim {\n when_not(claim_amount) {\n agent_description: \"Post the whole reservation to the {holder}. This moves money and does not reverse. Only a funded hold claims.\";\n moves: [{ key: \"claim\"; operation: internal_transfer.post; bind: { transferId: { from: instance; path: refs.holdTransferId; }; }; }];\n }\n deadline: { field: deadline_field; };\n when(claim_capture) { capture_input: claim_capture; }\n when(claim_amount) {\n agent_description: \"Post the {holder}'s decided amount out of the reservation and void the remainder back to the {payer} in one linked group. This moves money and does not reverse. Only a funded hold claims, and the claimed amount cannot exceed the held {amount}.\";\n decided_amount: { field: claim_field; bound_field: claim_bound; remainder_action: claim_remainder; };\n sandbox_failure_point: \"release\";\n moves: [{ key: \"claim\"; operation: internal_transfer.post; bind: { amount: { from: input; path: claim_field; }; currency: { from: instance; path: fields.currency; }; postMode: { from: const; value: partial_only; }; transferId: { from: instance; path: refs.holdTransferId; }; }; }, { key: remainder; operation: internal_transfer.void; bind: { reason: { from: const; value: \"Return unclaimed deposit remainder\"; }; transferId: { from: instance; path: refs.holdTransferId; }; }; }];\n }\n when(claim_input) { input: claim_input; }\n when(claim_allowed) { port { allowed_parties: claim_allowed; }; }\n when_not(claim_allowed) {\n when(claim_to) { port { allowed_parties: [claim_to]; }; }\n }\n steps: [];\n summary: \"Claim the security deposit\";\n }\n action return {\n agent_description: \"Void the whole reservation so the {payer} gets every unit back. Use this when the {holder} decides to keep nothing. This moves money and does not reverse. Only a funded hold returns.\";\n deadline: { field: deadline_field; };\n when(return_allowed) { port { allowed_parties: return_allowed; }; }\n when_not(return_allowed) {\n when(return_to) { port { allowed_parties: [return_to]; }; }\n }\n moves: [{ key: \"return\"; operation: internal_transfer.void; bind: { reason: { from: const; value: \"Deposit returned in full\"; }; transferId: { from: instance; path: refs.holdTransferId; }; }; }];\n steps: [];\n summary: \"Return the security deposit\";\n }\n action expire {\n due: { field: deadline_field; };\n moves: [{ key: expire; operation: internal_transfer.void; bind: { reason: { from: const; value: \"Deposit hold expired\"; }; transferId: { from: instance; path: refs.holdTransferId; }; }; }];\n steps: [];\n summary: \"Expire the security deposit\";\n }\n action cancel {\n agent_description: \"Close a hold before anyone funds it. No money has moved, so this is a lifecycle transition only. Only a created hold cancels. A funded hold refuses, because its money is already reserved. Claim it or return it instead.\";\n moves: [];\n steps: [];\n summary: \"Cancel the security deposit\";\n }\n }\n}\n"],
|
|
20
|
-
["money_flows/settlement_batch.hsx", "module std.money_flows.settlement_batch\n\n// Periodic aggregation of capture lineage, fee entries, and adjustments into a single net calculated payout.\n//\n// ### Purpose\n// `settlement_batch` settles merchant balances, marketplace vendor earnings, and partner clearing accounts.\n// Individual payment captures, platform fees, and reversal adjustments accumulate into an open batch over a period.\n// On `close_trigger`, the batch freezes. The platform calculates the signed net payable from gross captures plus credit\n// adjustments minus debit adjustments. If positive, an outbound bank payout is instructed and acknowledged.\n//\n// ### Selection guidance\n// - vs `reconciled_payout`: `settlement_batch` aggregates multiple transactions and adjustments over a billing cycle\n// to compute a single net payable. `reconciled_payout` manages bank instruction dispatch and statement line reconciliation\n// for an individual payout amount.\n// - vs `weighted_distribution`: `settlement_batch` consolidates many inbound transactions into one net outbound payout.\n// `weighted_distribution` splits one funding pool into many recipient payouts.\n//\n// ### Parameters\n// - `settlement_account`: The source clearing account holding captured funds and paying the batch.\n// - `source_capture_refs`: Binding name for gross capture references included in the batch.\n// - `fee_entries`: Binding name for fee deductions applied to the batch.\n// - `external_reversal_offsets`: Binding name for reversal adjustments applied to the batch.\n// - `close_trigger`: Date when the batch closes and ceases accepting new transaction entries.\n// - `payout_destination`: The merchant or partner account receiving the net payout.\n// - `negative_position`: Policy when calculated net payable is zero or negative (`reject`).\n// - `payout_acknowledgement`: Condition port confirming bank receipt of the payout.\n// - `payout_beneficiary_ref`: Beneficiary identifier for external bank dispatch.\n//\n// ### Decision ports\n// - `payout_acknowledgement`: Port recording external bank or partner receipt confirmation.\n//\n// ### Example\n// ```hsx\n// program settlement_batch_example \"Settlement batch example\"\n// import { settlement_batch } from \"std/money_flows\"\n// party settlement_account: business\n// party payout_destination: business\n// settlement batch = settlement_batch {\n// settlement_account: settlement_account\n// source_capture_refs: captureReference\n// fee_entries: feeReference\n// external_reversal_offsets: reversalReference\n// close_trigger: closeAt\n// payout_destination: payout_destination\n// negative_position: reject\n// payout_acknowledgement: port acknowledge_payout\n// payout_beneficiary_ref: payoutBeneficiaryId\n// }\n// port acknowledge_payout {\n// allowed: [payout_destination]\n// shape: { acknowledgementReference: text }\n// }\n// ```\nexport instrument settlement_batch<C>(\n settlement_account: party,\n source_capture_refs: text,\n fee_entries: text,\n external_reversal_offsets: text,\n close_trigger: date,\n payout_destination: party,\n negative_position: text,\n payout_acknowledgement: condition,\n payout_beneficiary_ref: text,\n) {\n fields {\n close_trigger { type: date; description: \"Date the open batch freezes against later entries\"; }\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n payout_beneficiary_ref { type: text; description: \"Beneficiary ID for the payout instruction\"; pattern: \"^ben_(sandbox|live)_[a-z0-9]{8,64}$\"; }\n [payout_destination]AccountId { type: account<C>; description: concat(\"The \", words(payout_destination), \" account\"); \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n [settlement_account]AccountId { type: account<C>; description: concat(\"The \", words(settlement_account), \" account\"); \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n }\n lifecycle {\n states open closed calculated approved instructed acknowledged reconciled;\n initial open;\n on close: open -> closed;\n on calculate: closed -> calculated;\n on approve: calculated -> approved;\n on instruct: approved -> instructed;\n on [payout_acknowledgement]: instructed -> acknowledged;\n on reconcile: instructed|acknowledged -> reconciled;\n }\n parties { beneficiary: concat(camel(payout_destination), \"AccountId\"); payer: concat(camel(settlement_account), \"AccountId\"); }\n required: [concat(camel(payout_destination), \"AccountId\"), concat(camel(settlement_account), \"AccountId\"), close_trigger, currency, payout_beneficiary_ref];\n title: sentence(instrument);\n id_prefix: prefix(instrument);\n agent_description: \"Reach for settlement batch when many charges over a period roll up into one payout. Capture entries and adjustments accrue against an open batch, the batch freezes on its close date, calculate proves one signed net payable, and instruct sends a single external payout. Weighted distribution goes the other way, one amount out to many recipients.\";\n summary: concat(\"Payout batch from \", words(settlement_account), \" to \", words(payout_destination));\n description: \"Immutable batch of capture lineage and signed adjustments that creates one payout\";\n action create { agent_description: \"Open a batch that capture entries and adjustments can accrue into. No money moves. The batch keeps taking entries until its close date freezes it.\"; summary: concat(\"Open a \", words(instrument)); moves: []; steps: []; }\n action close { summary: \"Freeze the batch and stop all new entries\"; due { field: close_trigger; } moves: []; steps: []; }\n action calculate {\n agent_description: \"Prove and freeze the one net payable from gross captures plus credit adjustments minus debit adjustments. The batch must be closed first. A zero or negative net is refused, so a batch in deficit never reaches a payout. No money moves.\";\n summary: \"Prove and freeze the one signed net payable amount\";\n computes signed_sum {\n amountRef: netPayable; onNegative: refuse; onZero: refuse;\n sources: [\n { amountField: amount; instrumentId: concat(instrument, \"_capture_entry\"); refField: concat(camel(instrument), \"Id\"); sign: add; statuses: [accrued]; subtotalRef: grossCaptureAmount; },\n { amountField: amount; instrumentId: concat(instrument, \"_credit_adjustment\"); refField: concat(camel(instrument), \"Id\"); sign: add; statuses: [applied]; subtotalRef: creditAdjustmentAmount; },\n { amountField: amount; instrumentId: concat(instrument, \"_debit_adjustment\"); refField: concat(camel(instrument), \"Id\"); sign: subtract; statuses: [applied]; subtotalRef: debitAdjustmentAmount; },\n ];\n }\n moves: []; steps: [];\n }\n action approve { agent_description: \"Sign off on the frozen net payable. The batch must already be calculated. Nothing is recomputed and no money moves. This is the gate instruct waits on.\"; summary: \"Approve the frozen payable without recomputing it\"; moves: []; steps: []; }\n action instruct {\n agent_description: \"Create one external payout for the frozen net payable, drawn on the settlement account and sent to the stored beneficiary. The batch must be approved. This moves money off the platform and is idempotent, so a retry never pays twice.\";\n summary: \"Create one idempotent payout from the frozen net payable\";\n payout { amount: \"refs.netPayable\"; beneficiaryField: payout_beneficiary_ref; beneficiaryPartyField: concat(camel(payout_destination), \"AccountId\"); capture: payoutId; currencyField: currency; sourceAccountField: concat(camel(settlement_account), \"AccountId\"); speed: standard; }\n moves: []; steps: [];\n }\n action [payout_acknowledgement] {\n agent_description: \"Record the beneficiary confirmation that the payout landed, with the reference kept in the receipt. The batch must already be instructed. Only the payout destination may call it. No money moves.\";\n summary: \"Record the tenant's payout acknowledgement in the receipt\";\n port { allowedParties: [beneficiary]; }\n input { type: object; properties: { acknowledgementReference: { type: string; description: \"Required decision reference retained in the operation receipt\"; minLength: 1; maxLength: 180; }; }; required: [acknowledgementReference]; additionalProperties: false; }\n capture_input { acknowledgementReference: acknowledgementReference; }\n moves: []; steps: [];\n }\n action reconcile {\n summary: \"Match durable evidence that the payout settled\";\n due { field: close_trigger; offset: \"P30D\"; }\n reconcile {\n amount: \"refs.netPayable\"; capture: settlementEvidenceId; counterpartyRef: payoutId;\n currencyField: currency; direction: debit; evidence: statement_line;\n exception { amountField: amount; childInstrumentId: concat(instrument, \"_debit_adjustment\"); maxOpen: 1; reasonField: adjustmentReference; refField: concat(camel(instrument), \"Id\"); }\n match { law: exact; }\n within { offset: \"P30D\"; }\n }\n moves: []; steps: [];\n }\n instruments {\n instrument {\n let(parent_name): instrument;\n id: concat(parent_name, \"_capture_entry\");\n id_prefix: concat(prefix(parent_name), \"ce\");\n fields {\n amount { type: money<C>; description: \"Gross captured amount in minor units\"; }\n [source_capture_refs] { type: text; description: \"Immutable source capture reference\"; minLength: 1; maxLength: 180; }\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n [parent_name]Id { type: ref<parent_name>; description: \"Open batch this capture entry accrues into\"; pattern: concat(\"^\", prefix(parent_name), \"_(sandbox|live)_[a-z0-9]{8,64}$\"); }\n }\n lifecycle { states created accrued; initial created; on accrue: created -> accrued; }\n required: [amount, currency, concat(camel(parent_name), \"Id\"), source_capture_refs];\n agent_description: \"One gross capture that belongs to an open batch. Record one per source capture so the net payable keeps its lineage back to the original charge.\";\n title: concat(sentence(parent_name), \" Capture Entry\"); summary: \"Gross capture lineage entry\"; description: \"One gross capture entry linked to an open payout batch\";\n action create { agent_description: \"Record one capture with its source reference against an open batch. The batch must be open and share the currency. No money moves, and the entry counts toward the net payable only once it accrues.\"; summary: \"Create a capture lineage entry on an open batch\"; requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [open]; match: { \"fields.currency\": \"fields.currency\"; }; } moves: []; steps: []; }\n action accrue { agent_description: \"Count this capture toward the batch gross total. The entry must be created and its batch still open, because a closed batch admits nothing new. No money moves.\"; summary: \"Accrue the capture entry into the open batch\"; requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [open]; match: { \"fields.currency\": \"fields.currency\"; }; } moves: []; steps: []; }\n }\n for adjustment_kind in [credit, debit] {\n instrument {\n let(parent_name): instrument;\n id: concat(parent_name, \"_\", adjustment_kind, \"_adjustment\");\n id_prefix: concat(prefix(parent_name), if_eq(adjustment_kind, \"credit\", \"ca\", \"da\"));\n fields {\n adjustmentReference { type: text; description: \"Immutable explicit adjustment reference\"; minLength: 1; maxLength: 180; }\n amount { type: money<C>; description: concat(title(adjustment_kind), \" adjustment amount in minor units\"); }\n [source_capture_refs] { type: text; description: \"Original capture reference that this adjustment corrects\"; minLength: 1; maxLength: 180; }\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n [external_reversal_offsets] { type: text; description: \"Optional externally decided reversal reference\"; minLength: 1; maxLength: 180; optional: true; }\n [fee_entries] { type: text; description: \"Optional fee entry reference\"; minLength: 1; maxLength: 180; optional: true; }\n [parent_name]Id { type: ref<parent_name>; description: \"Open batch this adjustment applies to\"; pattern: concat(\"^\", prefix(parent_name), \"_(sandbox|live)_[a-z0-9]{8,64}$\"); }\n }\n lifecycle { states created applied; initial created; on adjust: created -> applied; on correct: created -> applied; }\n required: [amount, currency, adjustmentReference, concat(camel(parent_name), \"Id\"), source_capture_refs];\n agent_description: concat(\"One \", adjustment_kind, \" adjustment against an open batch, carrying the capture reference it corrects. Credit adjustments add to the net payable and debit adjustments subtract from it, so a late correction lands on the current open batch instead of reopening a closed one.\");\n title: concat(sentence(parent_name), \" \", title(adjustment_kind), \" Adjustment\"); summary: concat(title(adjustment_kind), \" adjustment with capture lineage\"); description: concat(\"One \", adjustment_kind, \" adjustment linked to an open payout batch; closed batches stay unchanged\");\n action create { agent_description: concat(\"Record a \", adjustment_kind, \" adjustment on an open batch, naming the capture reference it corrects. The batch must be open and share the currency. No money moves, and the adjustment counts only once it is applied.\"); summary: concat(\"Create a \", adjustment_kind, \" adjustment on an open batch\"); requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [open]; match: { \"fields.currency\": \"fields.currency\"; }; } moves: []; steps: []; }\n action adjust { agent_description: concat(\"Apply the \", adjustment_kind, \" adjustment so calculate counts it. The adjustment must be created and its batch still open. No money moves. The effect shows up in the batch net payable.\"); summary: concat(\"Apply the \", adjustment_kind, \" adjustment to the open batch\"); requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [open]; match: { \"fields.currency\": \"fields.currency\"; }; } moves: []; steps: []; }\n action correct { agent_description: \"Apply this adjustment as a correction on the open batch it names. Use it when the batch carrying the original entry has already closed, because a closed batch never changes. The adjustment must be created and no money moves.\"; summary: \"Record a later correction on this open batch instead of changing the closed source batch\"; requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [open]; match: { \"fields.currency\": \"fields.currency\"; }; } moves: []; steps: []; }\n }\n }\n }\n}\n"],
|
|
21
|
-
["money_flows/swap.hsx", "module std.money_flows.swap;\n\n// Atomic two-sided trade between two parties where neither leg settles alone.\n//\n// ### Purpose\n// `swap` executes atomic delivery-versus-payment (DvP) or payment-versus-payment (PvP) exchanges between two named parties.\n// Both sides fund their declared principal amounts and platform fees into a shared escrow. The exchange settles atomically\n// upon triggering the `release` condition port, paying each party the other's funded amount. If a `dispute` occurs,\n// both legs unwind and refund simultaneously. Half-funded or half-released states cannot occur.\n//\n// ### Selection guidance\n// - vs `held_payment`: `swap` is a bilateral trade where both sides must deposit funds into escrow and receive each other's\n// disbursements simultaneously. `held_payment` is unilateral escrow where a single payer funds a payment held for a payee.\n// - vs `instant_transfer`: `instant_transfer` executes an immediate one-way transfer. `swap` coordinates two reciprocal transfers\n// held atomically in escrow until release confirmation.\n//\n// ### Parameters\n// - `between`: List containing exactly two trade participant parties (`[side_a, side_b]`).\n// - `amounts`: Block declaring the principal money amounts for each party.\n// - `fees`: Block declaring the platform fee money amounts for each party.\n// - `release`: Condition port required to execute the atomic swap release.\n// - `dispute`: Condition port triggering unwinding and refunding of both trade legs.\n// - `side_names`: Optional custom naming block for the two sides (`first`, `second`).\n// - `lifecycle_state_order`: Optional custom ordering for lifecycle states.\n// - `action_bindings`: Optional custom action names.\n// - `parked_states`: Optional configuration for parked states.\n// - `fixed_prefix`: Optional custom prefix for generated instrument IDs.\n//\n// ### Decision ports\n// - `release`: Port authorizing atomic settlement of both trade legs.\n// - `dispute`: Port triggering atomic cancellation and refund of both parties.\n//\n// ### Example\n// ```hsx\n// program swap_example \"Swap example\"\n// import { swap } from \"std/money_flows\"\n// party buyer: business\n// party seller: business\n// settlement exchange = swap {\n// between: [buyer, seller]\n// amounts {\n// buyer: buyerAmount: money(SAR)\n// seller: sellerAmount: money(SAR)\n// }\n// fees {\n// buyer: buyerFee: money(SAR)\n// seller: sellerFee: money(SAR)\n// }\n// release: port release_exchange\n// dispute: port dispute_exchange within P7D\n// }\n// port release_exchange { allowed: [buyer, seller] }\n// port dispute_exchange { allowed: [buyer, seller] }\n// ```\nexport instrument swap<C>(between: list<party>, amounts: block, fees: block, release: condition, dispute: condition, side_names: optional<block>, lifecycle_state_order: optional<list<text>>, action_bindings: optional<block>, parked_states: optional<block>, fixed_prefix: optional<text>) {\n let(side_a): at(between, 1);\n let(side_b): at(between, 2);\n let(side_a_amount): get(amounts, side_a);\n let(side_b_amount): get(amounts, side_b);\n let(side_a_fee): get(fees, side_a);\n let(side_b_fee): get(fees, side_b);\n when(side_names) {\n let(side_a_name): get(side_names, \"first\");\n let(side_b_name): get(side_names, \"second\");\n }\n when_not(side_names) {\n let(side_a_name): side_a;\n let(side_b_name): side_b;\n }\n let(side_a_account): concat(camel(side_a_name), \"AccountId\");\n let(side_b_account): concat(camel(side_b_name), \"AccountId\");\n agent_description: \"Reach for swap when two named parties pay each other in one atomic trade and neither leg may settle alone. Both principals fund one escrow together, both cross-payments reserve together, and one dispute unwinds both. A single payer paying a single payee is instant transfer instead.\";\n title: sentence(instrument);\n when(fixed_prefix) {\n id_prefix: fixed_prefix;\n }\n when_not(fixed_prefix) {\n id_prefix: prefix(instrument);\n }\n summary: concat(\"Two-party atomic trade between \", words(side_a), \" and \", words(side_b));\n description: concat(\"Atomic swap between \", words(side_a), \" and \", words(side_b), \"; half-funded and half-released states do not exist\");\n distinct_parties: true;\n when(side_names) {\n parties {\n payer: { party: side_a; account: side_a_account; };\n beneficiary: { party: side_b; account: side_b_account; };\n }\n }\n when_not(side_names) {\n parties {\n payer: side_a;\n beneficiary: side_b;\n }\n }\n required: [side_a_account, side_b_account, platformAccountId, concat(side_a_amount), concat(side_b_amount), concat(side_a_fee), concat(side_b_fee), currency];\n when_not(side_names) {\n computes fees {\n amountField: concat(side_a_fee);\n baseField: concat(side_a_amount);\n bearerField: side_a_account;\n position: on_top;\n rule {\n currencyField: currency;\n field: concat(side_a_fee);\n kind: exact;\n }\n }\n computes fees {\n amountField: concat(side_b_fee);\n baseField: concat(side_b_amount);\n bearerField: side_b_account;\n position: on_top;\n rule {\n currencyField: currency;\n field: concat(side_b_fee);\n kind: exact;\n }\n }\n }\n when(parked_states) {\n caller_parked_states: parked_states;\n }\n fields {\n when(side_names) {\n clawbackAt {\n type: date;\n description: \"Machine-owned absolute end of the release clawback window\";\n optional: true;\n }\n currency {\n type: text;\n description: \"One currency for both trade legs and both fees\";\n const: C;\n }\n \"[side_a_account]\" {\n type: account<C>;\n description: \"First trade party and funder\";\n \"x-hyperscale-reference-filter\" {\n column: role;\n values: [customer_balance];\n }\n }\n \"[side_a_amount]\" {\n type: get(amounts, side_a);\n description: concat(\"Amount side A holds against the whole exchange, in \", C, \" minor units\");\n }\n \"[side_a_fee]\" {\n type: get(fees, side_a);\n description: \"Side A custody fee charged on top and never placed in escrow\";\n }\n \"[side_b_account]\" {\n type: account<C>;\n description: \"Second trade party and funder\";\n \"x-hyperscale-reference-filter\" {\n column: role;\n values: [customer_balance];\n }\n }\n \"[side_b_amount]\" {\n type: get(amounts, side_b);\n description: concat(\"Amount side B holds against the whole exchange, in \", C, \" minor units\");\n }\n \"[side_b_fee]\" {\n type: get(fees, side_b);\n description: \"Side B custody fee charged on top and never placed in escrow\";\n }\n }\n when_not(side_names) {\n clawbackAt {\n type: date;\n description: concat(\"Machine-owned end of the \", dispute_within, \" whole-trade dispute window\");\n }\n \"[side_a_amount]\" {\n type: get(amounts, side_a);\n description: \"Side A amount in SAR minor units, held against the whole trade\";\n }\n \"[side_b_amount]\" {\n type: get(amounts, side_b);\n description: \"Side B amount in SAR minor units, held against the whole trade\";\n }\n \"[side_a_fee]\" {\n type: get(fees, side_a);\n description: concat(\"Immutable exact \", words(side_a), \" service fee in SAR minor units\");\n }\n \"[side_b_fee]\" {\n type: get(fees, side_b);\n description: concat(\"Immutable exact \", words(side_b), \" service fee in SAR minor units\");\n }\n }\n platformAccountId {\n type: account<C>;\n when(side_names) {\n description: \"Product revenue or customer balance account collecting trade service fees\";\n }\n when_not(side_names) {\n description: \"The product fee collection account\";\n }\n \"x-hyperscale-fee-collection-port\": true;\n \"x-hyperscale-reference-filter\" {\n column: role;\n values: [customer_balance, product_revenue];\n }\n }\n }\n when(lifecycle_state_order) {\n let(states_key): \"states\";\n lifecycle {\n \"[states_key]\": lifecycle_state_order;\n initial created;\n on abandon: created -> abandoned;\n on cancel: funded -> cancelled;\n on dispute: released -> clawed_back;\n on fund: created -> funded;\n on post: released -> settled;\n on release: funded -> released;\n }\n }\n when_not(lifecycle_state_order) {\n lifecycle {\n states created abandoned funded cancelled released settled clawed_back;\n initial created;\n on abandon: created -> abandoned;\n on cancel: funded -> cancelled;\n on dispute: released -> clawed_back;\n on fund: created -> funded;\n on post: released -> settled;\n on release: funded -> released;\n }\n }\n when_not(action_bindings) {\n action abandon {\n agent_description: \"Walk away from a trade that never funded. Only a created trade abandons, and its escrow account must hold nothing, so there is nothing to refund. No money moves.\";\n summary: \"Abandon the trade before its atomic funding batch\";\n public_action: concat(\"abandon\", title(camel(instrument)));\n requires drained {\n path: \"refs.escrowAccountId\";\n }\n moves: [];\n steps: [];\n }\n action cancel {\n agent_description: \"Unwind a funded trade before release. Both principals return from escrow to their own sides in one batch. The trade must be funded and not yet released. The service fees taken at funding stay with the platform.\";\n summary: \"Cancel and return both trade principals atomically\";\n public_action: concat(\"cancel\", title(camel(instrument)));\n moves: [{\n amount: concat(side_a_amount);\n from: \"refs.escrowAccountId\";\n to: side_a;\n key: side_a_refund;\n }, {\n amount: concat(side_b_amount);\n from: \"refs.escrowAccountId\";\n to: side_b;\n key: side_b_refund;\n }];\n steps: [];\n }\n action create {\n agent_description: \"Open a trade and provision its escrow account. No money moves. Both principals and both exact service fees are fixed here and cannot change afterwards.\";\n summary: concat(\"Create a \", words(instrument), \" atomic trade\");\n public_action: concat(\"create\", title(camel(instrument)));\n moves: [];\n steps: [{\n operation: \"account.escrow.provision\";\n bind {\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n \"owner.id\" {\n from: instance;\n path: productId;\n }\n \"owner.type\" {\n from: const;\n value: product;\n }\n productId {\n from: instance;\n path: productId;\n }\n role {\n from: const;\n value: product_escrow;\n }\n }\n capture {\n escrowAccountId: accountId;\n }\n }];\n }\n action dispute {\n agent_description: \"Claw back a released trade inside its dispute window. Both cross-payment reservations void and both principals return from escrow in one batch. The trade must be released and the clawback moment must not have passed. The dispute port's declared parties may call it.\";\n summary: \"Void both reservations, then refund both principals atomically\";\n public_action: concat(\"dispute\", title(camel(instrument)));\n deadline {\n field: clawbackAt;\n }\n when(dispute_fields) {\n input {\n type: object;\n additional_properties: false;\n properties {\n for input_name in keys(dispute_fields) {\n [input_name]: get(dispute_fields, input_name);\n }\n }\n required: keys(dispute_fields);\n }\n capture_input: {\n for item in keys(dispute_fields) { [item]: item; }\n };\n }\n port { allowedParties: dispute_allowed; }\n moves: [{\n bind {\n reason {\n from: const;\n value: \"Whole trade disputed inside the clawback window\";\n }\n transferId {\n from: instance;\n path: \"refs.releaseSideATransferId\";\n }\n }\n key: side_a_void;\n operation: \"internal_transfer.void\";\n }, {\n bind {\n reason {\n from: const;\n value: \"Whole trade disputed inside the clawback window\";\n }\n transferId {\n from: instance;\n path: \"refs.releaseSideBTransferId\";\n }\n }\n key: side_b_void;\n operation: \"internal_transfer.void\";\n }, {\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_a_amount);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: concat(\"fields.\", side_a_account);\n }\n \"metadata.clawbackOfTransferId\" {\n from: instance;\n path: \"refs.releaseSideATransferId\";\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: \"dispute\";\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: \"refs.escrowAccountId\";\n }\n }\n capture {\n disputeSideARefundTransferId: transferId;\n }\n key: side_a_refund;\n operation: \"internal_transfer.create\";\n }, {\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_b_amount);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: concat(\"fields.\", side_b_account);\n }\n \"metadata.clawbackOfTransferId\" {\n from: instance;\n path: \"refs.releaseSideBTransferId\";\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: \"dispute\";\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: \"refs.escrowAccountId\";\n }\n }\n capture {\n disputeSideBRefundTransferId: transferId;\n }\n key: side_b_refund;\n operation: \"internal_transfer.create\";\n }];\n steps: [];\n }\n action fund {\n agent_description: \"Fund both sides at once. Each side pays its principal into escrow and its service fee to the platform account, four transfers in one batch. The trade must be created and unfunded. This moves real money from both parties.\";\n summary: \"Fund both trade sides and collect on-top fees atomically\";\n public_action: concat(\"fund\", title(camel(instrument)));\n sandbox_failure_point: \"funding\";\n moves: [{\n amount: concat(side_a_amount);\n from: side_a;\n to: \"refs.escrowAccountId\";\n key: side_a_principal;\n }, {\n amount: concat(side_b_amount);\n from: side_b;\n to: \"refs.escrowAccountId\";\n key: side_b_principal;\n }, {\n amount: concat(side_a_fee);\n from: side_a;\n to: platformAccountId;\n key: side_a_service_fee;\n }, {\n amount: concat(side_b_fee);\n from: side_b;\n to: platformAccountId;\n key: side_b_service_fee;\n }];\n steps: [];\n }\n action post {\n summary: \"Post both trade reservations when the clawback window matures\";\n due {\n field: clawbackAt;\n }\n moves: [{\n bind {\n transferId {\n from: instance;\n path: \"refs.releaseSideATransferId\";\n }\n }\n key: \"side_a\";\n operation: \"internal_transfer.post\";\n }, {\n bind {\n transferId {\n from: instance;\n path: \"refs.releaseSideBTransferId\";\n }\n }\n key: \"side_b\";\n operation: \"internal_transfer.post\";\n }];\n steps: [];\n }\n action release {\n agent_description: \"Reserve each side's principal for the other side and start the dispute window. The trade must be funded. Money is reserved, not settled. It posts on its own when the window matures, and dispute unwinds it before then. The release port's declared parties may call it.\";\n summary: \"Reserve both cross-payments for the whole-trade clawback window\";\n public_action: concat(\"release\", title(camel(instrument)));\n port {\n allowedParties: release_allowed;\n }\n sandbox_failure_point: \"release\";\n sets_at {\n field: clawbackAt;\n offset: dispute_within;\n }\n moves: [{\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_a_amount);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: concat(\"fields.\", side_b_account);\n }\n \"metadata.clawbackAt\" {\n from: instance;\n path: \"fields.clawbackAt\";\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: \"release\";\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: \"refs.escrowAccountId\";\n }\n }\n capture {\n releaseSideATransferId: transferId;\n }\n key: \"side_a\";\n operation: \"internal_transfer.reserve\";\n }, {\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_b_amount);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: concat(\"fields.\", side_a_account);\n }\n \"metadata.clawbackAt\" {\n from: instance;\n path: \"fields.clawbackAt\";\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: \"release\";\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: \"refs.escrowAccountId\";\n }\n }\n capture {\n releaseSideBTransferId: transferId;\n }\n key: \"side_b\";\n operation: \"internal_transfer.reserve\";\n }];\n steps: [];\n }\n }\n when(action_bindings) {\n let(event_prefix): get(action_bindings, \"event_prefix\");\n let(fund_principal_capture): get(action_bindings, \"fund_principal_capture\");\n let(fund_fee_capture): get(action_bindings, \"fund_fee_capture\");\n let(fund_principal_phase): get(action_bindings, \"fund_principal_phase\");\n let(fund_fee_phase): get(action_bindings, \"fund_fee_phase\");\n let(release_capture): get(action_bindings, \"release_capture\");\n let(release_phase): get(action_bindings, \"release_phase\");\n let(release_key): get(action_bindings, \"release_key\");\n let(post_key): get(action_bindings, \"post_key\");\n let(dispute_capture): get(action_bindings, \"dispute_capture\");\n let(dispute_phase): get(action_bindings, \"dispute_phase\");\n let(dispute_reason): get(action_bindings, \"dispute_reason\");\n let(cancel_capture): get(action_bindings, \"cancel_capture\");\n let(cancel_phase): get(action_bindings, \"cancel_phase\");\n let(side_a_fund_capture): camel(concat(side_a_name, \"_\", fund_principal_capture, \"_transfer_id\"));\n let(side_b_fund_capture): camel(concat(side_b_name, \"_\", fund_principal_capture, \"_transfer_id\"));\n let(side_a_fee_capture): camel(concat(side_a_name, \"_\", fund_fee_capture, \"_transfer_id\"));\n let(side_b_fee_capture): camel(concat(side_b_name, \"_\", fund_fee_capture, \"_transfer_id\"));\n let(side_a_release_capture): camel(concat(side_a_name, \"_\", release_capture, \"_id\"));\n let(side_b_release_capture): camel(concat(side_b_name, \"_\", release_capture, \"_id\"));\n let(side_a_release_ref): concat(\"refs.\", side_a_release_capture);\n let(side_b_release_ref): concat(\"refs.\", side_b_release_capture);\n let(side_a_dispute_capture): camel(concat(side_a_name, \"_\", dispute_capture, \"_transfer_id\"));\n let(side_b_dispute_capture): camel(concat(side_b_name, \"_\", dispute_capture, \"_transfer_id\"));\n let(side_a_cancel_capture): camel(concat(side_a_name, \"_\", cancel_capture, \"_transfer_id\"));\n let(side_b_cancel_capture): camel(concat(side_b_name, \"_\", cancel_capture, \"_transfer_id\"));\n action create {\n agent_description: \"Open a trade and provision its escrow account. No money moves. Both principals and both exact service fees are fixed here and cannot change afterwards.\";\n summary: concat(\"Create a \", words(instrument), \" atomic trade\");\n public_action: concat(\"create\", title(camel(instrument)));\n moves: [];\n steps: [{\n operation: \"account.escrow.provision\";\n bind {\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n \"owner.id\" {\n from: instance;\n path: productId;\n }\n \"owner.type\" {\n from: const;\n value: product;\n }\n productId {\n from: instance;\n path: productId;\n }\n role {\n from: const;\n value: product_escrow;\n }\n }\n capture {\n escrowAccountId: accountId;\n }\n }];\n }\n action fund {\n agent_description: \"Fund both sides at once. Each side pays its principal into escrow and its service fee to the platform account, four transfers in one batch. The trade must be created and unfunded. This moves real money from both parties.\";\n summary: \"Fund both trade sides and collect on-top fees atomically\";\n public_action: concat(\"fund\", title(camel(instrument)));\n event_name: concat(event_prefix, \".funded\");\n sandbox_failure_point: \"funding\";\n moves: [{\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_a_amount);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: \"refs.escrowAccountId\";\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: fund_principal_phase;\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: concat(\"fields.\", side_a_account);\n }\n }\n capture {\n \"[side_a_fund_capture]\": transferId;\n }\n key: side_a_principal;\n operation: \"internal_transfer.create\";\n }, {\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_b_amount);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: \"refs.escrowAccountId\";\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: fund_principal_phase;\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: concat(\"fields.\", side_b_account);\n }\n }\n capture {\n \"[side_b_fund_capture]\": transferId;\n }\n key: side_b_principal;\n operation: \"internal_transfer.create\";\n }, {\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_a_fee);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: \"fields.platformAccountId\";\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: fund_fee_phase;\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: concat(\"fields.\", side_a_account);\n }\n }\n capture {\n \"[side_a_fee_capture]\": transferId;\n }\n key: side_a_service_fee;\n operation: \"internal_transfer.create\";\n }, {\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_b_fee);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: \"fields.platformAccountId\";\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: fund_fee_phase;\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: concat(\"fields.\", side_b_account);\n }\n }\n capture {\n \"[side_b_fee_capture]\": transferId;\n }\n key: side_b_service_fee;\n operation: \"internal_transfer.create\";\n }];\n steps: [];\n }\n action release {\n agent_description: \"Reserve each side's principal for the other side and start the dispute window. The trade must be funded. Money is reserved, not settled. It posts on its own when the window matures, and dispute unwinds it before then. The release port's declared parties may call it.\";\n summary: \"Reserve both cross-payments for the whole-trade clawback window\";\n public_action: concat(\"release\", title(camel(instrument)));\n event_name: concat(event_prefix, \".released\");\n port {\n allowedParties: release_allowed;\n }\n sandbox_failure_point: \"release\";\n sets_at {\n field: clawbackAt;\n offset: dispute_within;\n }\n moves: [{\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_a_amount);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: concat(\"fields.\", side_b_account);\n }\n \"metadata.clawbackAt\" {\n from: instance;\n path: \"fields.clawbackAt\";\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: release_phase;\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: \"refs.escrowAccountId\";\n }\n }\n capture {\n \"[side_a_release_capture]\": transferId;\n }\n key: concat(\"side_a_\", release_key);\n operation: \"internal_transfer.reserve\";\n }, {\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_b_amount);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: concat(\"fields.\", side_a_account);\n }\n \"metadata.clawbackAt\" {\n from: instance;\n path: \"fields.clawbackAt\";\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: release_phase;\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: \"refs.escrowAccountId\";\n }\n }\n capture {\n \"[side_b_release_capture]\": transferId;\n }\n key: concat(\"side_b_\", release_key);\n operation: \"internal_transfer.reserve\";\n }];\n steps: [];\n }\n action post {\n summary: \"Post both trade reservations when the clawback window matures\";\n event_name: concat(event_prefix, \".posted\");\n due {\n field: clawbackAt;\n }\n moves: [{\n bind {\n transferId {\n from: instance;\n path: side_a_release_ref;\n }\n }\n key: concat(\"side_a_\", post_key);\n operation: \"internal_transfer.post\";\n }, {\n bind {\n transferId {\n from: instance;\n path: side_b_release_ref;\n }\n }\n key: concat(\"side_b_\", post_key);\n operation: \"internal_transfer.post\";\n }];\n steps: [];\n }\n action dispute {\n agent_description: \"Claw back a released trade inside its dispute window. Both cross-payment reservations void and both principals return from escrow in one batch. The trade must be released and the clawback moment must not have passed. The dispute port's declared parties may call it.\";\n summary: \"Void both reservations, then refund both principals atomically\";\n public_action: concat(\"dispute\", title(camel(instrument)));\n event_name: concat(event_prefix, \".clawed_back\");\n deadline {\n field: clawbackAt;\n }\n when(dispute_fields) {\n input {\n type: object;\n additional_properties: false;\n properties {\n for input_name in keys(dispute_fields) {\n [input_name]: get(dispute_fields, input_name);\n }\n }\n required: keys(dispute_fields);\n }\n capture_input: {\n for item in keys(dispute_fields) { [item]: item; }\n };\n }\n port { allowedParties: dispute_allowed; }\n moves: [{\n bind {\n reason {\n from: const;\n value: dispute_reason;\n }\n transferId {\n from: instance;\n path: side_a_release_ref;\n }\n }\n key: side_a_void;\n operation: \"internal_transfer.void\";\n }, {\n bind {\n reason {\n from: const;\n value: dispute_reason;\n }\n transferId {\n from: instance;\n path: side_b_release_ref;\n }\n }\n key: side_b_void;\n operation: \"internal_transfer.void\";\n }, {\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_a_amount);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: concat(\"fields.\", side_a_account);\n }\n \"metadata.clawbackOfTransferId\" {\n from: instance;\n path: side_a_release_ref;\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: dispute_phase;\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: \"refs.escrowAccountId\";\n }\n }\n capture {\n \"[side_a_dispute_capture]\": transferId;\n }\n key: side_a_refund;\n operation: \"internal_transfer.create\";\n }, {\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_b_amount);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: concat(\"fields.\", side_b_account);\n }\n \"metadata.clawbackOfTransferId\" {\n from: instance;\n path: side_b_release_ref;\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: dispute_phase;\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: \"refs.escrowAccountId\";\n }\n }\n capture {\n \"[side_b_dispute_capture]\": transferId;\n }\n key: side_b_refund;\n operation: \"internal_transfer.create\";\n }];\n steps: [];\n }\n action cancel {\n agent_description: \"Unwind a funded trade before release. Both principals return from escrow to their own sides in one batch. The trade must be funded and not yet released. The service fees taken at funding stay with the platform.\";\n summary: \"Cancel and return both trade principals atomically\";\n public_action: concat(\"cancel\", title(camel(instrument)));\n event_name: concat(event_prefix, \".cancelled\");\n moves: [{\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_a_amount);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: concat(\"fields.\", side_a_account);\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: cancel_phase;\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: \"refs.escrowAccountId\";\n }\n }\n capture {\n \"[side_a_cancel_capture]\": transferId;\n }\n key: side_a_refund;\n operation: \"internal_transfer.create\";\n }, {\n bind {\n amount {\n from: instance;\n path: concat(\"fields.\", side_b_amount);\n }\n currency {\n from: instance;\n path: \"fields.currency\";\n }\n destinationAccountId {\n from: instance;\n path: concat(\"fields.\", side_b_account);\n }\n \"metadata.instrumentId\" {\n from: const;\n value: instrument;\n }\n \"metadata.instrumentInstanceId\" {\n from: instance;\n path: instrumentInstanceId;\n }\n \"metadata.phase\" {\n from: const;\n value: cancel_phase;\n }\n productId {\n from: instance;\n path: productId;\n }\n sourceAccountId {\n from: instance;\n path: \"refs.escrowAccountId\";\n }\n }\n capture {\n \"[side_b_cancel_capture]\": transferId;\n }\n key: side_b_refund;\n operation: \"internal_transfer.create\";\n }];\n steps: [];\n }\n action abandon {\n agent_description: \"Walk away from a trade that never funded. Only a created trade abandons, and its escrow account must hold nothing, so there is nothing to refund. No money moves.\";\n summary: \"Abandon the trade before its atomic funding batch\";\n public_action: concat(\"abandon\", title(camel(instrument)));\n event_name: concat(event_prefix, \".abandoned\");\n requires drained {\n path: \"refs.escrowAccountId\";\n }\n moves: [];\n steps: [];\n }\n }\n}\n"],
|
|
22
|
-
["money_flows/threshold_pool.hsx", "module std.money_flows.threshold_pool\n\n// All-or-nothing capital accumulation pool collecting commitments toward a target amount before a close deadline.\n//\n// ### Purpose\n// `threshold_pool` powers all-or-nothing crowdfunding, capital calls, collective purchasing, and consortium rounds.\n// Multiple contributors pledge commitments toward a monetary `target`. Commitments sit in escrow until the pool\n// reaches the target before `close_by` (activating and settling funds to the beneficiary), or fails (triggering full refunds).\n//\n// ### Selection guidance\n// - vs `rotating_pool`: `threshold_pool` is all-or-nothing fundraising where many contributors fund a single beneficiary.\n// `rotating_pool` is a peer savings circle where members contribute identically in each cycle and rotate who receives the pot.\n// - vs `weighted_distribution`: `threshold_pool` pools funds inward from many contributors to one beneficiary.\n// `weighted_distribution` pays funds outward from one source pool to many weighted recipients.\n//\n// ### Parameters\n// - `contributor`: The party representing individual contributors.\n// - `beneficiary`: The beneficiary party receiving the settled pool if the target is met.\n// - `target`: Total target funding threshold in minor units of currency `C`.\n// - `commitment`: Binding name for individual contributor commitment amounts.\n// - `max_contributors`: Optional maximum count of admitted contributors.\n// - `close_by`: Stored date deadline by which the target must be met.\n// - `close_policy`: Policy determining pool close behavior (`threshold`).\n// - `overfund_policy`: Policy on commitments exceeding the target (`reject`).\n// - `cancel_policy`: Contributor withdrawal policy before close (`before_close`).\n// - `fail_policy`: Refund policy if the pool fails to hit target (`whole_commitment_refund`).\n// - `beneficiary_account`: Optional direct account reference for the beneficiary.\n// - `memo`: Optional memo text stored on the pool.\n// - `contribution_instrument`: Optional custom contribution child instrument block.\n// - `wording`: Optional custom UI wording block.\n//\n// ### Decision ports\n// None. Lifecycle transitions (`activate`, `fail`, `close`) are driven by target threshold evaluation and the `close_by` date.\n//\n// ### Example\n// ```hsx\n// program capital_pool_example \"Capital pool example\"\n// import { threshold_pool } from \"std/money_flows\"\n// party contributor: person\n// party company: business\n// settlement round = threshold_pool {\n// contributor: contributor\n// beneficiary: company\n// target: targetAmount: money(SAR)\n// commitment: commitmentAmount: money(SAR)\n// max_contributors: 100\n// close_by: closeBy\n// close_policy: threshold\n// overfund_policy: reject\n// cancel_policy: before_close\n// fail_policy: whole_commitment_refund\n// }\n// ```\n// ### Funding join\n// funding_join configures the flat contribution-instrument mode for a priced\n// obligation. Opening freezes minimum ticket, investor cap, profit and schedule\n// terms. Closing binds the obligation, freezes committed ticket identities and\n// moves the principal once into its funding account. A ticket's engine-owned\n// collection action has no independent payment or clock trigger.\n//\nexport instrument threshold_pool<C>(\n contributor: optional<party>,\n beneficiary: optional<party>,\n target: money<C>,\n commitment: money<C>,\n max_contributors: optional<integer>,\n close_by: date,\n close_policy: optional<text>,\n overfund_policy: optional<text>,\n cancel_policy: optional<text>,\n fail_policy: optional<text>,\n beneficiary_account: optional<account<C>>,\n memo: optional<text>,\n contribution_instrument: optional<block>,\n wording: optional<block>,\n funding_join: optional<block>,\n) {\n when(wording) {\n let(wording_unknown_keys): keys_except(wording, \"pool_summary\", \"pool_description\", \"create_summary\", \"commitment_create_summary\", \"commitment_cancel_summary\", \"commitment_refund_summary\");\n when(wording_unknown_keys) {\n unsupported {\n code: HSX1407;\n message: \"threshold_pool wording contains an unknown key\";\n fix: \"use only the six declared threshold_pool wording keys\";\n }\n }\n }\n agent_description: \"Reach for threshold pool when many contributors commit toward one target and nothing is collected unless the pool reaches it by the close date. Commitments sit in escrow until the pool activates or fails. Pick weighted distribution to pay a pool out and instant transfer for a single payment.\";\n when_not(contribution_instrument) {\n fields {\n [beneficiary]AccountId { type: account<C>; description: concat(\"The \", words(beneficiary), \" account\"); \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n close_by { type: date; description: \"Stored close anchor\"; }\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n maxContributors { type: integer; description: concat(\"Exactly \", max_contributors, \" admitted contributors\"); const: max_contributors; }\n target { type: money<C>; description: \"Funding target in {C} minor units\"; }\n }\n lifecycle {\n states open active failed settled;\n initial open;\n on activate: open -> active;\n on fail: open -> failed;\n on close: active -> settled;\n }\n parties { beneficiary: concat(camel(beneficiary), \"AccountId\"); }\n required: [concat(camel(beneficiary), \"AccountId\"), currency, target, close_by, maxContributors];\n title: sentence(instrument);\n id_prefix: prefix(instrument);\n let(pool_summary): get(wording, \"pool_summary\");\n when_not(pool_summary) { summary: concat(\"Threshold contribution pool for \", words(beneficiary)); }\n when(pool_summary) { summary: pool_summary; }\n let(pool_description): get(wording, \"pool_description\");\n when_not(pool_description) { description: \"All-or-nothing aggregate contribution threshold\"; }\n when(pool_description) { description: pool_description; }\n aggregate_invariants { childField: amount; childInstrumentId: concat(instrument, \"_commitment\"); childRefField: concat(camel(instrument), \"Id\"); childStatuses: [committed]; parentField: target; }\n aggregate_invariants { childInstrumentId: concat(instrument, \"_commitment\"); childRefField: concat(camel(instrument), \"Id\"); childStatuses: [committed]; count: true; parentField: maxContributors; }\n action create {\n agent_description: \"Open the pool before any commitment exists. The caller supplies the target, the currency, the beneficiary account, the close anchor, and the pinned contributor count. No money moves and the pool opens for commitments.\";\n let(create_summary): get(wording, \"create_summary\");\n when_not(create_summary) { summary: \"Open the contribution pool\"; }\n when(create_summary) { summary: create_summary; }\n moves: [];\n steps: [];\n }\n action activate {\n summary: \"Activate when commitments meet the target\";\n due { field: close_by; }\n requires aggregate { instrumentId: concat(instrument, \"_commitment\"); over: children; refField: concat(camel(instrument), \"Id\"); statuses: [committed]; check: { kind: sum_at_least; amountField: amount; targetField: target; }; }\n moves: [];\n steps: [];\n }\n action fail {\n summary: \"Fail when commitments remain below target\";\n due { field: close_by; }\n requires aggregate { instrumentId: concat(instrument, \"_commitment\"); over: children; refField: concat(camel(instrument), \"Id\"); statuses: [committed]; check: { kind: sum_below; amountField: amount; targetField: target; }; }\n moves: [];\n steps: [];\n }\n action close {\n agent_description: \"Settle the pool once every admitted commitment is collected or was cancelled before activation. The pool must be active. No money moves here, collect already paid the beneficiary.\";\n summary: \"Settle after every admitted row is collected or was cancelled before activation\";\n requires aggregate { instrumentId: concat(instrument, \"_commitment\"); over: children; refField: concat(camel(instrument), \"Id\"); statuses: [cancelled, collected]; check: { kind: all_in; }; }\n moves: [];\n steps: [];\n }\n instruments {\n instrument {\n let(parent_name): instrument;\n id: concat(parent_name, \"_commitment\");\n generatedPrefix: true;\n fields {\n amount { type: money<C>; description: \"One whole commitment amount\"; }\n [beneficiary]AccountId { type: account<C>; description: concat(\"The \", words(beneficiary), \" account\"); \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n [parent_name]Id { type: ref<parent_name>; description: concat(\"The exact \", parent_name); pattern: concat(\"^\", prefix(parent_name), \"_(sandbox|live)_[a-z0-9]{8,64}$\"); }\n [contributor]AccountId { type: account<C>; description: concat(\"The \", words(contributor), \" account\"); \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n }\n lifecycle {\n states committed cancelled collected refunded;\n initial committed;\n on cancel: committed -> cancelled;\n on collect: committed -> collected;\n on refund: committed -> refunded;\n }\n parties { beneficiary: concat(camel(beneficiary), \"AccountId\"); payer: concat(camel(contributor), \"AccountId\"); }\n required: [concat(camel(beneficiary), \"AccountId\"), concat(camel(contributor), \"AccountId\"), amount, currency, concat(camel(parent_name), \"Id\")];\n title: concat(sentence(parent_name), \" Commitment\");\n agent_description: concat(\"Reach for this child row to record one whole commitment into a \", words(parent_name), \". Each contributor commits once, the money sits in the product escrow, and whether it is collected or refunded depends on the pool reaching its target.\");\n summary: concat(\"Whole commitment to \", parent_name);\n description: concat(\"One whole commitment linked to \", parent_name);\n action create {\n agent_description: \"Commit one whole amount into the pool escrow. The pool must still be open, the currency and beneficiary account must match it, and the committed total plus this amount must stay within the target. The money leaves the contributor now.\";\n let(commitment_create_summary): get(wording, \"commitment_create_summary\");\n when_not(commitment_create_summary) { summary: \"Store one whole commitment without exceeding the pool target\"; }\n when(commitment_create_summary) { summary: commitment_create_summary; }\n requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [open]; bind: { [beneficiary]AccountId: concat(\"fields.\", camel(beneficiary), \"AccountId\"); currency: \"fields.currency\"; }; }\n requires exposure { amountField: amount; anchorField: concat(camel(parent_name), \"Id\"); capField: target; capOnAnchor: true; childInstrumentId: concat(parent_name, \"_commitment\"); statuses: [committed]; }\n moves: [{ amount: amount; from: payer; to: refs.escrowAccountId; key: commit; }];\n 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; }; }];\n }\n action cancel {\n agent_description: \"Pull one commitment back out of escrow to the contributor. The pool must still be open, so this is unavailable once the pool activates or fails. The money returns and the commitment is closed.\";\n let(commitment_cancel_summary): get(wording, \"commitment_cancel_summary\");\n when_not(commitment_cancel_summary) { summary: \"Cancel one commitment while the pool is open\"; }\n when(commitment_cancel_summary) { summary: commitment_cancel_summary; }\n requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [open]; match: { \"fields.[beneficiary]AccountId\": concat(\"fields.\", camel(beneficiary), \"AccountId\"); \"fields.currency\": \"fields.currency\"; }; }\n moves: [{ amount: amount; from: refs.escrowAccountId; to: payer; key: cancel; }];\n steps: [];\n }\n action collect {\n agent_description: \"Release one commitment out of escrow to the beneficiary. The pool must be active, which happens only once commitments meet the target. The money moves for good.\";\n summary: \"Collect one successful commitment whole\";\n requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [active]; match: { \"fields.[beneficiary]AccountId\": concat(\"fields.\", camel(beneficiary), \"AccountId\"); \"fields.currency\": \"fields.currency\"; }; }\n moves: [{ amount: amount; from: refs.escrowAccountId; to: beneficiary; key: collect; }];\n steps: [];\n }\n action refund {\n agent_description: \"Return one commitment out of escrow to the contributor after the pool failed to reach its target. The pool must be in failed. The money returns and the commitment is closed.\";\n let(commitment_refund_summary): get(wording, \"commitment_refund_summary\");\n when_not(commitment_refund_summary) { summary: \"Refund one failed-pool commitment whole\"; }\n when(commitment_refund_summary) { summary: commitment_refund_summary; }\n requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [failed]; match: { \"fields.[beneficiary]AccountId\": concat(\"fields.\", camel(beneficiary), \"AccountId\"); \"fields.currency\": \"fields.currency\"; }; }\n moves: [{ amount: amount; from: refs.escrowAccountId; to: payer; key: refund; }];\n steps: [];\n }\n }\n }\n }\n\n when(contribution_instrument) {\n let(contribution_id): get(contribution_instrument, \"instrument\");\n let(contribution_ref_field): get(contribution_instrument, \"ref_field\");\n let(contribution_amount_field): get(contribution_instrument, \"amount_field\");\n let(committed_status): get(contribution_instrument, \"committed_status\");\n let(collected_status): get(contribution_instrument, \"collected_status\");\n let(instrument_id_prefix): get(contribution_instrument, \"id_prefix\");\n let(active_parked): get(contribution_instrument, \"active_parked\");\n let(custody_ref): get(contribution_instrument, \"custody_ref\");\n let(beneficiary_account_description): get(contribution_instrument, \"beneficiary_account_description\");\n let(target_description): get(contribution_instrument, \"target_description\");\n let(currency_description): get(contribution_instrument, \"currency_description\");\n let(create_examples): get(contribution_instrument, \"create_examples\");\n let(activate_examples): get(contribution_instrument, \"activate_examples\");\n let(fail_examples): get(contribution_instrument, \"fail_examples\");\n let(close_examples): get(contribution_instrument, \"close_examples\");\n let(beneficiary_account_field): camel(beneficiary_account);\n let(target_field): camel(target);\n let(close_field): camel(close_by);\n let(obligation_type): get(funding_join, \"obligation\");\n\n fields {\n [beneficiary_account_field] { type: account<C>; description: beneficiary_account_description; \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n [close_field] { type: date; }\n currency { type: text; description: currency_description; maxLength: 3; minLength: 3; pattern: \"^[A-Z]{3}$\"; }\n when(memo) { [memo] { type: text; optional: true; maxLength: 180; minLength: 1; } }\n [target_field] { type: money<C>; description: target_description; }\n when(funding_join) {\n fundingAccountId { type: account<C>; \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n obligationId { type: ref<obligation_type>; optional: true; }\n minimumTicket: money<C>;\n investorCapBps { type: integer; minimum: 1; maximum: 9999; }\n maximumTicket { type: money<C>; optional: true; }\n fixedProfit: money<C>;\n installmentCount { type: integer; minimum: 1; maximum: 360; }\n earningRule { type: text; enum: [per_slice_on_due, on_disbursement]; }\n }\n }\n when(funding_join) { computes derived: { field: maximumTicket; source_field: target_field; rule: { kind: percentage_of; bps: { field: investorCapBps; }; }; rounding: floor; };\n }\n lifecycle {\n states open active failed settled;\n initial open;\n on activate: open -> active;\n on close: active -> settled;\n on fail: open -> failed;\n }\n parties { beneficiary: beneficiary_account_field; }\n when_not(funding_join) { required: [beneficiary_account_field, target_field, currency, close_field]; }\n when(funding_join) { required: [beneficiary_account_field, target_field, currency, close_field, fundingAccountId, minimumTicket, investorCapBps, fixedProfit, installmentCount, earningRule]; }\n aggregate_invariants { childField: contribution_amount_field; childInstrumentId: contribution_id; childRefField: contribution_ref_field; childStatuses: [committed_status, collected_status]; parentField: target_field; }\n caller_parked_states { active: active_parked; }\n id_prefix: instrument_id_prefix;\n action create {\n public: none;\n 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.\";\n when_not(funding_join) { examples: create_examples; }\n moves: [];\n 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; }; }];\n }\n action activate {\n public: none;\n due { field: close_field; }\n examples: activate_examples;\n requires aggregate { instrumentId: contribution_id; over: children; refField: contribution_ref_field; statuses: [committed_status]; check: { kind: sum_at_least; amountField: contribution_amount_field; targetField: target_field; }; }\n moves: [];\n steps: [];\n }\n action fail {\n public: none;\n due { field: close_field; }\n examples: fail_examples;\n requires aggregate { instrumentId: contribution_id; over: children; refField: contribution_ref_field; statuses: [committed_status]; check: { kind: sum_below; amountField: contribution_amount_field; targetField: target_field; }; }\n moves: [];\n steps: [];\n }\n action close {\n public: none;\n 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.\";\n when_not(funding_join) { examples: close_examples; }\n when_not(funding_join) { requires aggregate { instrumentId: contribution_id; over: children; refField: contribution_ref_field; statuses: [collected_status]; check: { kind: all_in; }; } }\n when(funding_join) {\n input: { type: object; additionalProperties: false; properties: { obligationId: { type: string; pattern: get(funding_join, \"obligation_pattern\"); }; }; required: [obligationId]; };\n updates: [obligationId];\n requires refs: { field: obligationId; statuses: [created]; match: { \"fields.fundingAccountId\": fields.fundingAccountId; \"fields.currency\": fields.currency; }; };\n funding: {\n obligation_field: obligationId; ticket_instrument_id: contribution_id; ticket_ref_field: contribution_ref_field;\n ticket_amount_field: contribution_amount_field; ticket_investor_field: get(funding_join, \"investor_field\"); ticket_account_field: get(funding_join, \"wallet_account_field\");\n ticket_status: committed_status; collect_action: collect; principal_field: target_field;\n source_account_path: concat(\"refs.\", custody_ref); destination_account_field: fundingAccountId;\n terms: { [target_field]: principal; fixedProfit: fixedProfit; installmentCount: installmentCount; earningRule: earningRule; [beneficiary_account_field]: borrowerAccountId; currency: currency; };\n capture: fundingSnapshot;\n };\n }\n moves: [];\n steps: [];\n }\n }\n}\n"],
|
|
23
|
-
["money_flows/weighted_distribution.hsx", "module std.money_flows.weighted_distribution\n\n// Frozen largest-remainder distribution splitting one pool across dynamic recipients by recorded weights.\n//\n// ### Purpose\n// `weighted_distribution` distributes dividend pools, creator royalties, liquidation proceeds, and investment returns.\n// Child entitlement rows are recorded for each recipient with their respective weight. Once all recipients are recorded,\n// an evidence-backed snapshot freezes the entitlement set. Each recipient is then paid their exact largest-remainder\n// share such that all payouts sum exactly to `amount` without rounding leaks.\n//\n// ### Selection guidance\n// - vs `pooled_split`: `weighted_distribution` handles dynamic recipient rosters recorded as child rows and frozen\n// via snapshot. `pooled_split` hardcodes a fixed set of recipients and static percentages at definition time.\n// - vs `settlement_batch`: `weighted_distribution` splits one funding pool out to many recipients.\n// `settlement_batch` aggregates many inbound charges and adjustments into a single net payout.\n//\n// ### Parameters\n// - `source`: The funding party providing the distribution pool.\n// - `recipient`: The party representing entitled recipients.\n// - `amount`: Total distribution pool in minor units of currency `C`.\n// - `record_at`: Date when entitlement eligibility is established.\n// - `weight`: Binding name for individual recipient weight amounts.\n// - `max_recipients`: Exact number of entitlement rows required before snapshotting.\n// - `snapshot`: Port freezing the entitlement set against stored decision evidence.\n// - `rounding_policy`: Mathematical rounding policy (`largest_remainder`).\n// - `withholding_policy`: Tax or withholding policy (`refuse`).\n// - `correction_policy`: Error correction policy (`new_distribution`).\n// - `flat`: Optional flat distribution configuration block.\n// - `id_prefix_override`: Optional custom prefix for generated instrument IDs.\n//\n// ### Decision ports\n// - `snapshot`: Port freezing the entitlement set with an evidence reference, preventing further entries.\n//\n// ### Example\n// ```hsx\n// program weighted_distribution_example \"Weighted distribution example\"\n// import { weighted_distribution } from \"std/money_flows\"\n// party distribution_source: business\n// party recipient: business\n// settlement proceeds = weighted_distribution {\n// source: distribution_source\n// recipient: recipient\n// amount: distributableAmount: money(SAR)\n// weight: entitlementWeight: money(SAR)\n// max_recipients: 12\n// record_at: recordAt\n// snapshot: port snapshot_entitlements\n// rounding_policy: largest_remainder\n// withholding_policy: refuse\n// correction_policy: new_distribution\n// }\n// port snapshot_entitlements {\n// allowed: [distribution_source]\n// shape: { evidenceReference: text }\n// }\n// ```\n// ### Receipt-backed distributions\n// receipts selects repeated distributions from immutable allocation receipts.\n// It names round, receipt, path, mode (cash or loss), fee_bps and vat_bps.\n// The funding snapshot owns ticket weights and original wallet destinations.\n// Cash floors each ratio after fee and fee VAT; a named account receives residual.\n// Loss assigns principal by largest remainder without cash, fee or VAT.\n// Each receipt is consumed once; a new receipt may fund a new distribution record.\n// This mode does not use caller-entered weights or a once-only settled principal pool.\n//\nexport instrument weighted_distribution<C>(\n source: optional<party>,\n recipient: optional<party>,\n amount: optional<money<C>>,\n record_at: optional<date>,\n weight: optional<money<C>>,\n max_recipients: optional<integer>,\n snapshot: optional<condition>,\n rounding_policy: optional<text>,\n withholding_policy: optional<text>,\n correction_policy: optional<text>,\n flat: optional<block>,\n receipts: optional<block>,\n id_prefix_override: optional<text>,\n) {\n when_not(receipts) {\n when_not(flat) {\n fields {\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n amount { type: money<C>; description: \"Distribution pool in {C} minor units\"; }\n [source]AccountId { type: account<C>; description: concat(\"The \", words(source), \" account\"); \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n maxRecipients { type: integer; description: concat(\"Exactly \", max_recipients, \" frozen entitlement rows\"); const: max_recipients; }\n record_at { type: date; description: \"Stored record date\"; }\n }\n lifecycle { states open snapshotted; initial open; on [snapshot]: open -> snapshotted; }\n parties { payer: concat(camel(source), \"AccountId\"); }\n required: [concat(camel(source), \"AccountId\"), currency, amount, record_at, maxRecipients];\n title: sentence(instrument);\n id_prefix: prefix(instrument);\n agent_description: \"Reach for weighted distribution when one pool splits across many recipients by declared weights. Record one entitlement per recipient, freeze the set with the snapshot port, then each entitlement pays its own largest-remainder share, so the shares add back to the pool exactly. Settlement batch goes the other way, many charges into one payout.\";\n summary: \"Frozen largest-remainder distribution\";\n description: \"Evidence-frozen weighted distribution\";\n aggregate_invariants { childInstrumentId: concat(instrument, \"_entitlement\"); childRefField: concat(camel(instrument), \"Id\"); childStatuses: [recorded, paid]; count: true; parentField: maxRecipients; }\n action create { agent_description: \"Open a distribution and start recording entitlements. No money moves. The pool amount, the record date, and the exact recipient count are fixed here.\"; summary: \"Open entitlement recording\"; moves: []; steps: []; }\n action [snapshot] {\n agent_description: \"Freeze the entitlement set against a stated evidence reference. The distribution must still be open and its recorded entitlements must match the declared recipient count. Nothing can be recorded afterwards and there is no unfreeze. No money moves.\";\n summary: \"Freeze the entitlement set from stored evidence\";\n port { allowedParties: [payer]; }\n input { type: object; properties: { evidenceReference: { type: string; description: \"Required decision reference retained in the operation receipt\"; minLength: 1; maxLength: 180; }; }; required: [evidenceReference]; additionalProperties: false; }\n capture_input { snapshotEvidenceReference: evidenceReference; }\n moves: [];\n steps: [];\n }\n instruments {\n instrument {\n let(parent_name): instrument;\n id: concat(parent_name, \"_entitlement\");\n generatedPrefix: true;\n fields {\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n [source]AccountId { type: account<C>; description: concat(\"The \", words(source), \" account\"); \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n [weight] { type: money<C>; description: \"Stored non-negative entitlement weight\"; }\n [parent_name]Id { type: ref<parent_name>; description: concat(\"The exact \", parent_name); pattern: concat(\"^\", prefix(parent_name), \"_(sandbox|live)_[a-z0-9]{8,64}$\"); }\n [recipient]AccountId { type: account<C>; description: concat(\"The \", words(recipient), \" account\"); \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n }\n lifecycle { states recorded paid; initial recorded; on payout: recorded -> paid; }\n parties { beneficiary: concat(camel(recipient), \"AccountId\"); payer: concat(camel(source), \"AccountId\"); }\n required: [concat(camel(recipient), \"AccountId\"), concat(camel(source), \"AccountId\"), currency, concat(camel(parent_name), \"Id\"), weight];\n title: concat(sentence(parent_name), \" Entitlement\");\n agent_description: concat(\"One recipient weighted claim on \", parent_name, \". Record one per recipient before the snapshot. Its share is derived from its weight against the frozen set, never supplied by the caller.\");\n summary: concat(\"Frozen entitlement in \", parent_name);\n description: concat(\"One frozen weighted entitlement in \", parent_name);\n action create {\n agent_description: \"Record one recipient weight against an open distribution. The parent distribution must still be open, and the currency and source account are copied from it. No money moves. Do not supply a share. It is derived later from the frozen set.\";\n summary: \"Record one entitlement before snapshot\";\n requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [open]; bind: { currency: \"fields.currency\"; [source]AccountId: concat(\"fields.\", camel(source), \"AccountId\"); }; }\n moves: [];\n steps: [];\n }\n action payout {\n agent_description: \"Pay this recipient their largest-remainder share of the pool. The parent distribution must already be snapshotted. The amount is computed from the recorded weights, not supplied by the caller, and a zero share moves nothing. This moves money and does not reverse.\";\n summary: \"Pay the deterministic largest-remainder share once\";\n requires refs { field: concat(camel(parent_name), \"Id\"); statuses: [snapshotted]; match: { \"fields.currency\": \"fields.currency\"; \"fields.[source]AccountId\": concat(\"fields.\", camel(source), \"AccountId\"); }; }\n computes distribute { amountRef: payoutShare; onZero: skip_steps; pool: { from: parent; path: concat(\"fields.\", amount); }; refField: concat(camel(parent_name), \"Id\"); statuses: [recorded, paid]; weightField: weight; }\n moves: [{ amount: refs.payoutShare; from: payer; to: beneficiary; key: payout; }];\n steps: [];\n }\n }\n }\n }\n when(flat) {\n let(pool): get(flat, \"pool\");\n let(pool_ref): get(pool, \"field\");\n let(pool_ref_field): camel(pool_ref);\n let(pool_instrument): get(pool, \"instrument\");\n let(pool_create_statuses): get(pool, \"create_statuses\");\n let(pool_payout_statuses): get(pool, \"payout_statuses\");\n let(pool_bind): get(pool, \"bind\");\n let(pool_match): get(pool, \"match\");\n let(pool_amount_path): get(pool, \"amount_path\");\n let(weight_source): get(flat, \"weight_source\");\n let(weight_ref): get(weight_source, \"field\");\n let(weight_ref_field): camel(weight_ref);\n let(weight_statuses): get(weight_source, \"statuses\");\n let(weight_bind): get(weight_source, \"bind\");\n let(weight_match): get(weight_source, \"match\");\n let(unique_weight_source): get(weight_source, \"unique\");\n let(group): get(flat, \"group\");\n let(group_ref): get(group, \"field\");\n let(source_account_field): camel(concat(source, \"_account_id\"));\n let(recipient_account_field): camel(concat(recipient, \"_account_id\"));\n fields {\n [amount] { type: money<C>; description: get(flat, \"amount_description\"); }\n [record_at] { type: date; }\n [pool_ref] { type: pool_ref; description: get(pool, \"description\"); pattern: get(pool, \"pattern\"); }\n currency { type: text; description: \"ISO 4217 currency code\"; minLength: 3; maxLength: 3; pattern: \"^[A-Z]{3}$\"; }\n [weight_ref] { type: weight_ref; description: get(weight_source, \"description\"); pattern: get(weight_source, \"pattern\"); }\n [group_ref] { type: group_ref; description: get(group, \"description\"); pattern: get(group, \"pattern\"); }\n [recipient]AccountId { type: account<C>; description: get(flat, \"recipient_account_description\"); \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n [source]AccountId { type: account<C>; description: get(flat, \"source_account_description\"); \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n }\n lifecycle { states claimed paid; initial claimed; on payout: claimed -> paid; }\n parties { beneficiary: recipient_account_field; }\n required: get(flat, \"required\");\n title: sentence(instrument);\n id_prefix: if_eq(id_prefix_override, false, prefix(instrument), id_prefix_override);\n agent_description: concat(\"One weighted claim on a distribution pool that another instrument owns. Record one claim per recipient before the pool settles. Its share is derived from its weight against the settled pool, never supplied by the caller, and the shares add back to the pool exactly.\");\n summary: \"Flat largest-remainder distribution claim\";\n description: \"One weighted claim on a settled distribution pool\";\n action create {\n public: none;\n agent_description: concat(\"Record one recipient weight against a pool that is still open. Both the pool and the weight source must be in an accepting state, and the same weight source cannot be claimed twice. No money moves. Do not supply a share. It is derived at payout from the settled pool.\");\n summary: \"Open one weighted distribution claim\";\n requires_refs: [\n { field: pool_ref_field; statuses: pool_create_statuses; bind: pool_bind; match: pool_match; },\n { field: weight_ref_field; statuses: weight_statuses; bind: weight_bind; match: weight_match; unique: unique_weight_source; }\n ];\n moves: [];\n steps: [];\n }\n action payout {\n public: none;\n summary: \"Pay one largest-remainder share from the settled pool\";\n due { field: record_at; whenParentStatus: { instrumentId: pool_instrument; refField: pool_ref_field; statuses: pool_payout_statuses; }; }\n requires refs { field: pool_ref_field; statuses: pool_payout_statuses; }\n computes distribute { amountRef: payoutShare; onZero: skip_steps; pool: { from: parent; path: pool_amount_path; }; refField: pool_ref_field; statuses: [claimed, paid]; weightField: amount; }\n moves: [{ key: transfer; operation: internal_transfer.create; bind: { amount: { from: instance; path: refs.payoutShare; }; currency: { from: instance; path: fields.currency; }; destinationAccountId: { from: instance; path: concat(\"fields.\", recipient_account_field); }; productId: { from: instance; path: productId; }; sourceAccountId: { from: instance; path: concat(\"fields.\", source_account_field); }; }; capture: { payoutTransferId: transferId; }; }];\n steps: [];\n }\n }\n }\n when(receipts) {\n let(round_type): get(receipts, \"round\");\n let(receipt_type): get(receipts, \"receipt\");\n fields {\n roundId: ref<round_type>;\n receiptId: ref<receipt_type>;\n currency { type: text; pattern: \"^[A-Z]{3}$\"; minLength: 3; maxLength: 3; }\n feeAccountId { type: account<C>; \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n taxAccountId { type: account<C>; \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n residualAccountId { type: account<C>; \"x-hyperscale-reference-filter\": { column: role; values: [customer_balance]; }; }\n }\n parties { fee: feeAccountId; tax: taxAccountId; beneficiary: residualAccountId; }\n lifecycle { states distributed; initial distributed; }\n title: sentence(instrument);\n summary: \"Receipt-backed frozen-weight distribution\";\n agent_description: \"Consume one allocation receipt against the round's frozen tickets. Cash distributions carve fee and VAT, floor each ticket share and pay the residual to its named owner. Loss mode records principal loss without cash.\";\n action create {\n public: none;\n agent_description: \"Distribute one receipt once. All amounts and sources come from the consumed receipt and frozen funding snapshot.\";\n requires refs: { field: roundId; statuses: [settled]; match: { \"fields.currency\": fields.currency; }; };\n receipt distribution: {\n round_field: roundId; snapshot_ref: fundingSnapshot;\n receipt_field: receiptId; receipt_path: get(receipts, \"path\"); mode: get(receipts, \"mode\");\n fee_bps: get(receipts, \"fee_bps\"); vat_bps: get(receipts, \"vat_bps\");\n fee_account_field: feeAccountId; tax_account_field: taxAccountId; residual_account_field: residualAccountId;\n capture: distributionReceipt;\n };\n steps: [];\n }\n }\n}\n"],
|
|
4
|
+
["approvals.hsx", "header approvals\n\ninstrument decision(for: ref, approved_by: party, action: text = \"approve\") {\n summary: \"One authenticated decision bound to an immutable target and expiry.\"\n fields { target: ref<for>, expiresAt: date }\n lifecycle { states: [pending, approved, declined, expired], initial: pending }\n action create {}\n action approve {\n from: pending, to: approved, actor: { party: approved_by }, deadline: { at: self.expiresAt }\n approval: { target: self.target, action: action, party: approved_by, expires: self.expiresAt, input: \"material\", decision: \"approved\" }\n }\n action decline {\n from: pending, to: declined, actor: { party: approved_by }, deadline: { at: self.expiresAt }\n approval: { target: self.target, action: action, party: approved_by, expires: self.expiresAt, input: \"material\", decision: \"declined\" }\n }\n action expire { from: pending, to: expired, actor: clock, due: { at: self.expiresAt } }\n}\n"],
|
|
5
|
+
["cards.hsx", "header cards\n\ninstrument cardholder(holder: party) {\n fields { holder: account of holder }\n lifecycle { states: [pending, active, suspended, closed], initial: pending }\n action create {}\n action activate { from: pending, to: active }\n action suspend { from: active, to: suspended }\n action resume { from: suspended, to: active }\n action close { from: [active, suspended], to: closed }\n}\n\ninstrument card(holder: ref<cards.cardholder>, spend_limit: money) {\n fields { holder: ref<holder>, spendLimit: money = spend_limit }\n lifecycle { states: [issued, active, frozen, cancelled], initial: issued }\n action create { requires self.holder in [active] }\n action activate { from: issued, to: active, requires self.holder in [active] }\n action freeze { from: active, to: frozen }\n action unfreeze { from: frozen, to: active, requires self.holder in [active] }\n action cancel { from: [issued, active, frozen], to: cancelled }\n}\n\ninstrument authorization(card: ref<cards.card>, merchant: party, approval: approval) {\n fields { card: ref<card>, merchant: account of merchant, amount: money, expiresAt: date, reservation: text? }\n lifecycle { states: [pending, approved, captured, declined, reversed, expired], initial: pending }\n action create {\n requires self.card in [active]\n requires self.amount <= self.card.spendLimit\n }\n action approve {\n from: pending, to: approved, deadline: { at: self.expiresAt }\n requires approval by approval; requires self.card in [active]\n moves reserve self.amount from self.card.holder.holder to merchant capture \"reservation\"\n }\n action capture { from: approved, to: captured, deadline: { at: self.expiresAt }, moves post self.reservation }\n action reverse { from: approved, to: reversed, moves void self.reservation }\n action decline { from: pending, to: declined, requires approval by approval for \"approve\" is \"declined\" }\n action expire { from: approved, to: expired, actor: clock, due: { at: self.expiresAt }, moves void self.reservation }\n action abandon { from: pending, to: expired, actor: clock, due: { at: self.expiresAt } }\n}\n\ninstrument transaction(authorization: ref<cards.authorization>) {\n fields { authorization: ref<authorization>, amount: money = self.authorization.amount }\n lifecycle { states: [posted, refunded], initial: posted }\n action create { requires self.authorization in [captured]; requires unique \"captured_authorization\" on [self.authorization] }\n action refund {\n from: posted, to: refunded, input { reason: text }\n moves self.amount from self.authorization.merchant to self.authorization.card.holder.holder\n }\n}\n\ninstrument dispute(transaction: ref<cards.transaction>, approved_by: approval, within: duration = 90d) {\n fields { transaction: ref<transaction>, evidence: text, expiresAt: date = after(self.createdAt, within) }\n lifecycle { states: [filed, reviewing, won, lost], initial: filed }\n action create { requires self.transaction in [posted]; requires unique \"disputed_transaction\" on [self.transaction] }\n action review { from: filed, to: reviewing }\n action win {\n from: reviewing, to: won, deadline: { at: self.expiresAt }\n requires approval by approved_by\n invoke: [{ reference: self.transaction, action: refund, input: { reason: { field: self.evidence } } }]\n }\n action lose { from: [filed, reviewing], to: lost, actor: clock, due: { at: self.expiresAt } }\n}\n"],
|
|
6
|
+
["collections.hsx", "header collections\n\ninstrument case(on: ref<financing.installments>[], agency: approval, overdue: duration = 3d, fee: percent = 20%) {\n summary: \"Refer an overdue installment without creating another debt.\"\n fields { plan: ref<on>, slice: ref<on.slice>, payment: ref<on.payment>, eligibleAt: date = after(self.slice.dueAt, overdue), agencyFee: money = rate(self.payment.amount, fee) }\n lifecycle { states: [opened, assigned, recovered, recalled, returned_uncollected], initial: opened }\n action create {\n due: { at: self.eligibleAt }\n requires self.plan == self.slice.plan\n requires self.slice.principalReceivable.balance > \"0\"\n requires unique \"overdue_referral\" on [self.slice]\n }\n action assign { from: opened, to: assigned, requires approval by agency }\n action recover { from: assigned, to: recovered, actor: { party: agency }, requires self.payment.plan == self.plan, invoke: [{ reference: self.payment, action: pay, input: {} }, { reference: self.id, action: pay_fee, input: {} }] }\n action pay_fee { from: recovered, to: recovered, actor: { parent: current() }, moves self.agencyFee from self.plan.capital to agency }\n action recall { from: [opened, assigned], to: recalled }\n action return_uncollected { from: assigned, to: returned_uncollected, actor: { party: agency } }\n}\n\ninstrument contact(case: ref<collections.case>[], agency: party, max_contacts: integer = 10, window: duration = 30d, contact_from: integer(0, 23) = 8, contact_until: integer(1, 23) = 20, timezone: text = \"Asia/Riyadh\") {\n constraints { contact_from: less_than(contact_until) }\n summary: \"An agency contact admitted against the locked rolling count and local hours.\"\n fields { case: ref<case>, attemptedAt: date?, evidence: text, maximum: integer = max_contacts }\n lifecycle { states: [recorded], initial: recorded }\n invariants count of { instrument: current(), reference: \"case\", anchor: self.case, states: [recorded], limit: 366, window: { field: attemptedAt, milliseconds: window } } <= self.maximum\n action create {\n actor: { party: agency }\n set: { attemptedAt: { field: self.now } }\n requires self.case in [assigned]; requires hours self.now between contact_from and contact_until timezone timezone\n }\n}\n"],
|
|
7
|
+
["escrow.hsx", "header escrow\n\ninstrument hold(payer: party, payee: party, for: ref<marketplace.order>, accept_within: duration = 48h, fee: fee = { seller: 0%, tax: 0% }, dispute: policy = { refund_after: return_verified }) {\n summary: \"Hold an order's price through delivery, acceptance and verified return.\"\n fields { payer: account of payer, payee: account of payee, order: ref<for>, price: money, held: account of self, deliveredAt: date? }\n lifecycle { states: [pending, funded, delivered, disputed, return_verified, released, refunded, cancelled], initial: pending }\n action create {\n requires self.payer == self.order.buyer; requires self.payee == self.order.listing.seller; requires self.price == self.order.price\n }\n action fund {\n from: pending, to: funded, actor: { party: payer }\n requires self.order in [placed]\n moves self.price from payer to self.held\n }\n action confirm {\n from: pending, to: funded\n requires self.held.balance == self.price\n }\n action deliver {\n from: funded, to: delivered, actor: { party: payee }\n set: { deliveredAt: { field: self.now } }\n }\n action accept {\n from: delivered, to: released, actor: { party: payer }\n input { acceptanceReference: text, titleReference: text, coverReference: text }\n deadline: { at: self.deliveredAt, offset: accept_within }\n moves self.price from self.held to payee fee fee\n }\n action expire_acceptance {\n from: delivered, to: disputed, actor: clock\n due: { at: self.deliveredAt, offset: accept_within }\n }\n action dispute {\n from: delivered, to: disputed, actor: { party: payer }\n deadline: { at: self.deliveredAt, offset: accept_within }\n }\n action verify_return { from: disputed, to: return_verified, input { returnReference: text } }\n action refund {\n from: dispute.refund_after, to: refunded\n moves self.price from self.held to payer\n }\n action cancel { from: pending, to: cancelled }\n}\n"],
|
|
8
|
+
["financing.hsx", "header financing\n\ninstrument installments(months: integer(1, 366), profit: percent, disburse_to: enum(funds, borrower) = funds, profit_earned: enum(on_payment, by_schedule, at_disbursement) = on_payment, apply: enum(fines_profit_principal, principal_profit, pro_rata) = fines_profit_principal, payoff_rebate: percent = 100%, down_payment: percent = 0%, funds: ref, approval: approval, borrower: party = party(person), capital: party = programOperator, profit_to: party = programOperator, limits: ref<financing.limits> = object(financing.limits)) {\n summary: \"Funding, profit recognition and payment order chosen by the program.\"\n fields {\n funds: ref<funds>, borrower: account of borrower, settlementIdentity: text, borrowerLimit: ref<limits>, portfolioLimit: ref<limits.portfolio>\n months: integer = months, dates: list(date, months)\n capital: account(capital, cash, \"capital\"), profitIncome: account(profit_to, cash, \"profitIncome\"), loss: account(capital, claim, \"loss\")\n principalReturned: money?, profitReturned: money?, lossRecorded: money?\n price: money = self.funds.price\n downPayment: money = rate(self.price, down_payment)\n principal: money = subtract(self.price, self.downPayment)\n profit: money = rate(self.principal, profit)\n total: money = sum(self.principal, self.profit)\n }\n lifecycle { states: [quoted, signed, active, paid, written_off, cancelled], initial: quoted }\n invariants count of { instrument: all(financing.installments), reference: \"borrowerLimit\", anchor: self.borrowerLimit, states: [active], limit: 366 } <= self.borrowerLimit.activePlans\n invariants sum \"principalReceivable.balance\" of { instrument: all(financing.installments.slice), reference: \"borrowerLimit\", anchor: self.borrowerLimit, states: [pending, due, paid], limit: 366 } <= self.borrowerLimit.limit\n invariants sum \"principalReceivable.balance\" of { instrument: all(financing.installments.slice), reference: \"portfolioLimit\", anchor: self.portfolioLimit, states: [pending, due, paid], limit: 366 } <= self.portfolioLimit.limit\n action create { requires self.borrower == self.funds.payer; requires self.borrowerLimit.borrower == self.funds.payer }\n action sign {\n from: quoted, to: signed, input { signedOffer: text, affordabilityReference: text }\n requires count of { instrument: slice, reference: \"plan\", anchor: self.id, states: [draft], limit: 366 } == self.months\n }\n action disburse {\n from: signed, to: active\n requires approval by approval\n requires self.borrowerLimit in [approved]\n requires self.portfolioLimit in [approved]\n when disburse_to is funds {\n requires self.funds in [pending]\n moves self.downPayment from self.funds.payer to self.funds.held\n moves self.principal from self.capital to self.funds.held\n }\n when disburse_to is borrower { moves self.principal from self.capital to self.borrower }\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.id, states: [draft], limit: 366, order: [position] }, action: activate, input: {} }]\n when disburse_to is funds { invoke: [{ reference: self.funds, action: confirm, input: {} }] }\n }\n action complete {\n from: active, to: paid\n requires count of { instrument: slice, reference: \"plan\", anchor: self.id, states: [pending, due], limit: 366 } == 0\n }\n action restore { from: [active, paid], to: active, actor: { parent: payment } }\n action payoff {\n from: active, to: paid\n calculate: [\n { target: \"principalReturned\", op: \"aggregate\", selection: { instrument: slice, reference: \"plan\", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: \"principalReceivable.balance\" } }\n ]\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: payoff, input: { identity: { field: self.settlementIdentity } } }, { reference: self.id, action: record_payoff, input: {} }]\n }\n action record_payoff {\n from: paid, to: paid, actor: { parent: current() }\n calculate: [{ target: profitReturned, op: aggregate, selection: { instrument: slice, reference: \"plan\", anchor: self.id, states: [paid], limit: 366, where: { payoffIdentity: { field: self.settlementIdentity } } }, measure: { sum: \"payoffProfit\" } }]\n invoke: [{ instrument: settlement, action: create, input: { plan: { field: self.id }, identity: { field: self.settlementIdentity }, principal: { field: self.principalReturned }, profit: { field: self.profitReturned }, loss: { literal: \"0\" }, kind: { literal: \"cash\" } } }]\n }\n action write_off {\n from: active, to: written_off\n input { overdueSlice: ref<slice> }\n calculate: [{ target: \"lossRecorded\", op: \"aggregate\", selection: { instrument: slice, reference: \"plan\", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: \"principalReceivable.balance\" } }]\n due: { at: input.overdueSlice.lossEligibleAt }\n requires approval by approval; requires input.overdueSlice.plan == self.id; requires input.overdueSlice.principalReceivable.balance > \"0\"\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: write_off, input: {} }, { instrument: settlement, action: create, input: { plan: { field: self.id }, identity: { field: self.settlementIdentity }, principal: { literal: \"0\" }, profit: { literal: \"0\" }, loss: { field: self.lossRecorded }, kind: { literal: \"loss\" } } }]\n }\n action record_payment {\n from: active, to: active, actor: { parent: payment }\n input { identity: text, principal: money, profit: money }\n invoke: [{ instrument: settlement, action: create, input: { plan: { field: self.id }, identity: { field: input.identity }, principal: { field: input.principal }, profit: { field: input.profit }, loss: { literal: \"0\" }, kind: { literal: \"cash\" } } }]\n }\n action cancel { from: quoted, to: cancelled }\n records {\n settlement: {\n fields { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss)\n capital: account(capital, cash, \"capital\"), profitIncome: account(profit_to, cash, \"profitIncome\"), lossAccount: account(capital, claim, \"loss\") }\n lifecycle { states: [recorded], initial: recorded }\n action create {\n actor: { parent: parent }\n input { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss) }\n requires unique \"settlement_identity\" on [self.plan, self.identity]\n }\n }\n slice: {\n fields {\n plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<limits.portfolio>, position: integer(1, months)\n dueAt: date = at(self.plan.dates, self.position), lossEligibleAt: date = after(self.dueAt, 90d)\n principalBase: money = divide(self.plan.principal, months)\n principalAllocated: money = multiply(self.principalBase, months)\n principalRemainder: money = subtract(self.plan.principal, self.principalAllocated)\n profitBase: money = divide(self.plan.profit, months)\n profitAllocated: money = multiply(self.profitBase, months)\n profitRemainder: money = subtract(self.plan.profit, self.profitAllocated)\n first: integer = divide(1, self.position)\n principalAdjustment: money = multiply(self.principalRemainder, self.first)\n profitAdjustment: money = multiply(self.profitRemainder, self.first)\n principal: money = sum(self.principalBase, self.principalAdjustment)\n profit: money = sum(self.profitBase, self.profitAdjustment)\n principalReceivable: account(self, claim), profitReceivable: account(self, claim), profitEarned: account(self, claim)\n payoffIdentity: text?, payoffRebate: money?, payoffProfitTotal: money?, payoffProfit: money?\n paymentBudget: money?, principalPayment: money?, principalBasePayment: money?, principalRatio: money?, profitPayment: money?, profitBudget: money?, collectibleProfit: money?, collectibleTotal: money?, ratioDenominator: integer = sum(10000, profit)\n debt: account(borrower, claim, contra, \"debt\"), loss: account(capital, claim, \"loss\")\n }\n lifecycle { states: [draft, pending, due, paid, written_off], initial: draft }\n action create {\n requires self.plan in [quoted]; requires unique \"signed_position\" on [self.plan, self.position]; requires self.borrowerLimit == self.plan.borrowerLimit; requires self.portfolioLimit == self.plan.portfolioLimit\n }\n action activate {\n from: draft, to: pending, actor: { parent: parent }\n moves self.principal from self.debt to self.principalReceivable\n moves self.profit from self.debt to self.profitReceivable\n when profit_earned is at_disbursement { moves self.profit from self.profitReceivable to self.profitEarned }\n when profit_earned is by_schedule { invoke: [{ instrument: recognition, action: create, input: { slice: { field: self.id } } }] }\n }\n action mature {\n from: pending, to: due, actor: clock, due: { at: self.dueAt }\n }\n action collect_principal {\n from: [pending, due, paid], to: due, actor: { parent: payment }\n input { payment: ref<payment> }\n requires input.payment.plan == self.plan\n invoke: [{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }]\n }\n action collect_profit {\n from: [pending, due, paid], to: due, actor: { parent: payment }\n input { payment: ref<payment> }\n requires input.payment.plan == self.plan\n when profit_earned is on_payment { invoke: [{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitReceivable.balance } } }] }\n when profit_earned is by_schedule { invoke: [{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitEarned.balance } } }] }\n when profit_earned is at_disbursement { invoke: [{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitEarned.balance } } }] }\n }\n action collect_pro_rata {\n from: [pending, due, paid], to: due, actor: { parent: payment }, input { payment: ref<payment> }\n requires input.payment.plan == self.plan\n when profit_earned is on_payment { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitReceivable.balance }] }] }\n when profit_earned is by_schedule { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }\n when profit_earned is at_disbursement { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }\n calculate: [\n { target: collectibleTotal, op: sum, values: [{ field: self.principalReceivable.balance }, { field: self.collectibleProfit }] }\n { target: paymentBudget, op: minimum, values: [{ field: input.payment.held.balance }, { field: self.collectibleTotal }] }\n { target: principalRatio, op: ratio, amount: { field: self.paymentBudget }, numerator: { literal: 10000 }, denominator: { field: self.ratioDenominator }, rounding: floor }\n { target: principalBasePayment, op: minimum, values: [{ field: self.principalRatio }, { field: self.principalReceivable.balance }] }\n { target: profitBudget, op: subtract, base: { field: self.paymentBudget }, subtract: [{ field: self.principalBasePayment }] }\n { target: profitPayment, op: minimum, values: [{ field: self.profitBudget }, { field: self.collectibleProfit }] }\n { target: principalPayment, op: subtract, base: { field: self.paymentBudget }, subtract: [{ field: self.profitPayment }] }\n ]\n invoke: [{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalPayment } } }, { instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitPayment } } }]\n }\n action complete {\n from: [pending, due], to: paid\n requires self.principalReceivable.balance == \"0\"; requires self.profitReceivable.balance == \"0\"; requires self.profitEarned.balance == \"0\"\n }\n action restore { from: [pending, due, paid], to: due, actor: { parent: payment } }\n action payoff {\n from: [pending, due], to: paid, actor: { parent: parent }, input { identity: text }\n set: { payoffIdentity: { field: input.identity } }\n calculate: [{ target: payoffRebate, op: rate, base: { field: self.profitReceivable.balance }, bps: { literal: payoff_rebate }, rounding: floor }, { target: payoffProfitTotal, op: sum, values: [{ field: self.profitReceivable.balance }, { field: self.profitEarned.balance }] }, { target: payoffProfit, op: subtract, base: { field: self.payoffProfitTotal }, subtract: [{ field: self.payoffRebate }] }]\n moves self.principalReceivable.balance from self.plan.borrower to self.plan.capital\n moves self.payoffProfit from self.plan.borrower to self.plan.profitIncome\n moves self.principalReceivable.balance from self.principalReceivable to self.debt\n moves self.profitReceivable.balance from self.profitReceivable to self.debt\n moves self.profitEarned.balance from self.profitEarned to self.debt\n }\n action write_off {\n from: [pending, due], to: written_off, actor: { parent: parent }\n moves self.principalReceivable.balance from self.principalReceivable to self.loss; moves self.profitReceivable.balance from self.profitReceivable to self.debt; moves self.profitEarned.balance from self.profitEarned to self.loss\n }\n }\n recognition: {\n fields { slice: ref<slice>, dueAt: date = after(self.slice.dueAt, 0) }\n lifecycle { states: [pending, recognised], initial: pending }\n action create { actor: { parent: slice }, input { slice: ref<slice> }, requires unique \"profit_recognition\" on [self.slice] }\n action mature { from: pending, to: recognised, actor: clock, due: { at: self.dueAt }, moves self.slice.profitReceivable.balance from self.slice.profitReceivable to self.slice.profitEarned }\n }\n payment: {\n fields { plan: ref<parent>, amount: money, paymentIdentity: text, held: account of self\n capital: account(capital, cash, \"capital\"), profitIncome: account(profit_to, cash, \"profitIncome\"), loss: account(capital, claim, \"loss\")\n outstandingPrincipal: money?, outstandingProfit: money?, outstandingFines: money?, outstandingCosts: money?, outstandingTotal: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money? }\n\n lifecycle { states: [pending, paid, refunded], initial: pending }\n action create { input { plan: ref<parent>, amount: money, paymentIdentity: text } requires self.plan in [active]; requires unique \"payment_identity\" on [self.plan, self.paymentIdentity] }\n action pay {\n from: pending, to: paid\n calculate: [{ target: outstandingPrincipal, op: aggregate, selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: \"principalReceivable.balance\" } }]\n when profit_earned is on_payment { calculate: [{ target: outstandingProfit, op: aggregate, selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: \"profitReceivable.balance\" } }] }\n when profit_earned is by_schedule { calculate: [{ target: outstandingProfit, op: aggregate, selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: \"profitEarned.balance\" } }] }\n when profit_earned is at_disbursement { calculate: [{ target: outstandingProfit, op: aggregate, selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: \"profitEarned.balance\" } }] }\n when apply is fines_profit_principal {\n calculate: [{ target: outstandingFines, op: aggregate, selection: { instrument: all(financing.late_charge), reference: \"plan\", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, measure: { sum: \"receivable.balance\" } }, { target: outstandingCosts, op: aggregate, selection: { instrument: all(financing.late_charge.cost), reference: \"plan\", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, measure: { sum: \"receivable.balance\" } }, { target: outstandingTotal, op: sum, values: [{ field: self.outstandingPrincipal }, { field: self.outstandingProfit }, { field: self.outstandingFines }, { field: self.outstandingCosts }] }]\n }\n when apply is principal_profit { calculate: [{ target: outstandingTotal, op: sum, values: [{ field: self.outstandingPrincipal }, { field: self.outstandingProfit }] }] }\n when apply is pro_rata { calculate: [{ target: outstandingTotal, op: sum, values: [{ field: self.outstandingPrincipal }, { field: self.outstandingProfit }] }] }\n requires self.plan in [active]; requires self.amount <= self.outstandingTotal\n moves self.amount from self.plan.borrower to self.held\n when apply is principal_profit {\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, action: collect_principal, input: { payment: { field: self.id } } }, { selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, action: collect_profit, input: { payment: { field: self.id } } }]\n }\n when apply is fines_profit_principal {\n invoke: [{ selection: { instrument: all(financing.late_charge), reference: \"plan\", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: collect, input: { payment: { field: self.id } } }, { selection: { instrument: all(financing.late_charge.cost), reference: \"plan\", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: collect, input: { payment: { field: self.id } } }]\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, action: collect_profit, input: { payment: { field: self.id } } }, { selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, action: collect_principal, input: { payment: { field: self.id } } }]\n }\n when apply is pro_rata {\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, action: collect_pro_rata, input: { payment: { field: self.id } } }]\n }\n invoke: [{ reference: self.id, action: record, input: {} }]\n }\n action record {\n from: paid, to: paid, actor: { parent: current() }\n requires self.held.balance == \"0\"\n calculate: [{ target: principalReturned, op: aggregate, selection: { instrument: principal_piece, reference: \"payment\", anchor: self.id, states: [paid], limit: 366 }, measure: { sum: \"amount\" } }, { target: profitReturned, op: aggregate, selection: { instrument: profit_piece, reference: \"payment\", anchor: self.id, states: [paid], limit: 366 }, measure: { sum: \"amount\" } }]\n invoke: [{ reference: self.plan, action: record_payment, input: { identity: { field: self.paymentIdentity }, principal: { field: self.principalReturned }, profit: { field: self.profitReturned } } }]\n }\n action refund {\n from: paid, to: refunded\n requires unique \"settlement_consumption\" on [self.plan, self.paymentIdentity]; requires self.plan in [active, paid]\n when apply is fines_profit_principal {\n invoke: [{ selection: { instrument: all(financing.late_charge), reference: \"plan\", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: refund, input: { payment: { field: self.id } } }, { selection: { instrument: all(financing.late_charge.cost), reference: \"plan\", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: refund, input: { payment: { field: self.id } } }]\n }\n invoke: [{ reference: self.plan, action: restore, input: {} }, { selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366 }, action: restore, input: {} }, { selection: { instrument: principal_piece, reference: \"payment\", anchor: self.id, states: [paid], limit: 366 }, action: refund, input: {} }, { selection: { instrument: profit_piece, reference: \"payment\", anchor: self.id, states: [paid], limit: 366 }, action: refund, input: {} }]\n }\n }\n principal_piece: {\n fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money = minimum(self.payment.held.balance, self.slice.principalReceivable.balance, self.limit) }\n lifecycle { states: [paid, refunded], initial: paid }\n action create {\n actor: { parent: slice }\n input { slice: ref<slice>, payment: ref<payment>, limit: money }\n requires unique \"principal_receipt\" on [self.slice, self.payment]\n moves self.amount from self.payment.held to self.slice.plan.capital; moves self.amount from self.slice.principalReceivable to self.slice.debt\n }\n action refund {\n from: paid, to: refunded, actor: { parent: payment }\n moves self.amount from self.slice.plan.capital to self.slice.plan.funds.payer; moves self.amount from self.slice.debt to self.slice.principalReceivable\n }\n }\n profit_piece: {\n fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money = minimum(self.payment.held.balance, self.limit) }\n lifecycle { states: [paid, refunded], initial: paid }\n action create {\n actor: { parent: slice }\n input { slice: ref<slice>, payment: ref<payment>, limit: money }\n requires unique \"profit_receipt\" on [self.slice, self.payment]\n moves self.amount from self.payment.held to self.slice.plan.profitIncome\n when profit_earned is on_payment { moves self.amount from self.slice.profitReceivable to self.slice.debt }\n when profit_earned is by_schedule { moves self.amount from self.slice.profitEarned to self.slice.debt }\n when profit_earned is at_disbursement { moves self.amount from self.slice.profitEarned to self.slice.debt }\n }\n action refund {\n from: paid, to: refunded, actor: { parent: payment }\n moves self.amount from self.slice.plan.profitIncome to self.slice.plan.borrower\n when profit_earned is on_payment { moves self.amount from self.slice.debt to self.slice.profitReceivable }\n when profit_earned is by_schedule { moves self.amount from self.slice.debt to self.slice.profitEarned }\n when profit_earned is at_disbursement { moves self.amount from self.slice.debt to self.slice.profitEarned }\n }\n }\n }\n}\n\ninstrument late_charge(on: ref<financing.installments>[], grace: duration = 3d, fine: money = 50 SAR, cap: money = 25 SAR, fines_to: party = programOperator, costs_to: party = programOperator, approval: approval = programOperator, borrower: party = party(person)) {\n summary: \"Separate approved fine and evidenced recovery claims on an overdue installment.\"\n fields {\n plan: ref<on>, slice: ref<on.slice>, borrower: account of borrower, fine: money = fine, overdueAt: date = after(self.slice.dueAt, grace)\n receivable: account(self, claim), debt: account(borrower, claim, contra, \"debt\")\n }\n lifecycle { states: [proposed, assessed, collected, waived, refunded], initial: proposed }\n action create { requires unique \"late_assessment\" on [self.slice]; requires self.borrower == self.slice.plan.borrower; requires self.plan == self.slice.plan }\n action assess {\n from: proposed, to: assessed, due: { at: self.overdueAt }\n requires self.slice.principalReceivable.balance > \"0\"; requires approval by approval\n moves self.fine from self.debt to self.receivable\n }\n action collect {\n from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }\n requires input.payment in [paid]\n requires input.payment.plan == self.plan\n invoke: [{ instrument: receipt, action: create, input: { assessment: { field: self.id }, payment: { field: input.payment } } }, { selection: { instrument: current(), reference: \"plan\", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: \"0\" } } }, action: finish, input: {} }]\n }\n action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == \"0\" }\n action cancel { from: proposed, to: waived }\n action waive { from: assessed, to: waived, moves self.receivable.balance from self.receivable to self.debt }\n action refund {\n from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }\n requires input.payment in [paid, refunded]\n requires input.payment.plan == self.plan\n invoke: [{ selection: { instrument: receipt, reference: \"payment\", anchor: input.payment, states: [paid], limit: 1, where: { assessment: { field: self.id } } }, action: refund, input: {} }, { selection: { instrument: current(), reference: \"plan\", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: \"0\" } } }, action: finish, input: {} }]\n }\n records {\n receipt: {\n fields { assessment: ref<parent>, payment: ref<on.payment>, amount: money = minimum(self.payment.held.balance, self.assessment.receivable.balance) }\n lifecycle { states: [paid, refunded], initial: paid }\n action create { actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique \"fine_receipt\" on [self.assessment, self.payment]; moves self.amount from self.payment.held to fines_to; moves self.amount from self.assessment.receivable to self.assessment.debt }\n action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from fines_to to self.payment.plan.borrower; moves self.amount from self.assessment.debt to self.assessment.receivable }\n }\n\n cost: {\n fields {\n plan: ref<on>, assessment: ref<parent>, overdueAt: date = after(self.assessment.slice.dueAt, grace), evidence: text, evidencedCost: money, amount: money = minimum(self.evidencedCost, cap)\n receivable: account(self, claim), debt: account(borrower, claim, contra, \"debt\")\n }\n lifecycle { states: [proposed, assessed, collected, waived, refunded], initial: proposed }\n action create { requires unique \"recovery_assessment\" on [self.assessment]; requires self.plan == self.assessment.plan }\n action assess {\n from: proposed, to: assessed, due: { at: self.assessment.overdueAt }\n requires self.assessment.slice.principalReceivable.balance > \"0\"; requires approval by approval\n moves self.amount from self.debt to self.receivable\n }\n action collect {\n from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }\n requires input.payment in [paid]\n requires input.payment.plan == self.plan\n invoke: [{ instrument: receipt, action: create, input: { assessment: { field: self.id }, payment: { field: input.payment } } }, { selection: { instrument: current(), reference: \"plan\", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: \"0\" } } }, action: finish, input: {} }]\n }\n action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == \"0\" }\n action cancel { from: proposed, to: waived }\n action waive { from: assessed, to: waived, moves self.receivable.balance from self.receivable to self.debt }\n action refund {\n from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }\n requires input.payment in [paid, refunded]\n requires input.payment.plan == self.plan\n invoke: [{ selection: { instrument: receipt, reference: \"payment\", anchor: input.payment, states: [paid], limit: 1, where: { assessment: { field: self.id } } }, action: refund, input: {} }, { selection: { instrument: current(), reference: \"plan\", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: \"0\" } } }, action: finish, input: {} }]\n }\n records {\n receipt: {\n fields { assessment: ref<parent>, payment: ref<on.payment>, amount: money = minimum(self.payment.held.balance, self.assessment.receivable.balance) }\n lifecycle { states: [paid, refunded], initial: paid }\n action create { actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique \"cost_receipt\" on [self.assessment, self.payment]; moves self.amount from self.payment.held to costs_to; moves self.amount from self.assessment.receivable to self.assessment.debt }\n action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from costs_to to self.payment.plan.borrower; moves self.amount from self.assessment.debt to self.assessment.receivable }\n }\n }\n }\n }\n}\n\ninstrument limits(per_borrower: money, portfolio: money, borrower: party = party(person), active_plans: integer = 1) {\n summary: \"One borrower ceiling and one shared portfolio ceiling.\"\n fields { borrower: account of borrower, limit: money = per_borrower, activePlans: integer = active_plans }\n lifecycle { states: [pending, approved], initial: pending }\n action create { requires unique \"borrower_limit\" on [self.borrower] }\n action approve { from: pending, to: approved }\n records {\n portfolio: {\n fields { key: text = \"portfolio\", limit: money = portfolio }\n lifecycle { states: [pending, approved], initial: pending }\n action create { requires unique \"portfolio_limit\" on [self.key] }\n action approve { from: pending, to: approved }\n }\n }\n}\n\ninstrument credit_line(borrower: party, lender: party, limit: money, expires: date) {\n summary: \"A revolving facility; each draw is a separate immutable advance.\"\n fields { borrower: account of borrower, lender: account of lender, debt: account(borrower, claim, contra, \"debt\"), limit: money = limit, expiresAt: date = expires }\n lifecycle { states: [pending, active, suspended, closed], initial: pending }\n action create {}\n action activate { from: pending, to: active, actor: { party: lender } }\n action suspend { from: active, to: suspended, actor: { party: lender } }\n action resume { from: suspended, to: active, actor: { party: lender } }\n action close { from: [active, suspended], to: closed, due: { at: self.expiresAt }, requires count of { instrument: all(financing.advance), reference: \"line\", anchor: self.id, states: [drawn], limit: 366 } == 0 }\n}\n\ninstrument advance(line: ref<financing.credit_line>, amount: money = runtime) {\n summary: \"A single facility draw with an immutable principal and full repayment.\"\n fields { line: ref<line>, amount: money = amount, principalReceivable: account(self, claim) }\n lifecycle { states: [pending, drawn, repaid, cancelled], initial: pending }\n action create { requires self.amount <= self.line.limit }\n invariants sum \"principalReceivable.balance\" of { instrument: all(financing.advance), reference: \"line\", anchor: self.line, states: [drawn], limit: 366 } <= self.line.limit\n action draw {\n from: pending, to: drawn\n requires self.line in [active]\n deadline: { at: self.line.expiresAt }\n moves self.amount from self.line.lender to self.line.borrower; moves self.amount from self.line.debt to self.principalReceivable\n }\n action repay { from: drawn, to: repaid, moves self.amount from self.line.borrower to self.line.lender; moves self.amount from self.principalReceivable to self.line.debt }\n action cancel { from: pending, to: cancelled }\n}\n"],
|
|
9
|
+
["insurance.hsx", "header insurance\n\ninstrument cover(holder: party, insurer: party, commission: percent = 0%, covers: ref, premium: money = runtime) {\n summary: \"Recurring cover with separate premium slices and commission clawback.\"\n fields { covers: ref<covers>, holder: account of holder, insurer: account of insurer, expiresAt: date }\n lifecycle { states: [pending, active, cancelled, expired], initial: pending }\n action create {}\n action activate { from: pending, to: active, actor: { party: insurer } }\n action cancel {\n from: active, to: cancelled\n invoke: [{ selection: { instrument: slice, reference: \"cover\", anchor: self.id, states: [paid], limit: 366 }, action: refund, input: {} }]\n }\n action expire { from: active, to: expired, actor: clock, due: { at: self.expiresAt } }\n records {\n slice: {\n summary: \"A whole premium period, collected once and refundable only before it starts.\"\n fields {\n cover: ref<parent>, covers: ref<covers>, premium: money = premium, startsAt: date, endsAt: date\n commission: money = rate(self.premium, commission)\n carrierNet: money = subtract(self.premium, self.commission)\n }\n lifecycle { states: [pending, paid, active, expired, refunded], initial: pending }\n action create {\n requires self.covers == self.cover.covers\n requires self.startsAt < self.endsAt\n requires unique \"premium_period\" on [self.cover, self.startsAt, self.endsAt]\n }\n action collect {\n from: pending, to: paid\n requires self.cover in [active]\n moves self.premium from holder to insurer\n moves self.commission from insurer to programOperator\n }\n action start { from: paid, to: active, actor: clock, due: { at: self.startsAt } }\n action expire { from: active, to: expired, actor: clock, due: { at: self.endsAt } }\n action refund {\n from: paid, to: refunded, deadline: { at: self.startsAt }\n moves self.commission from programOperator to insurer\n moves self.premium from insurer to holder\n }\n }\n }\n}\n\ninstrument claim(cover: ref<insurance.cover>, approved_by: approval) {\n summary: \"A carrier decision reserves one claim payment before settlement.\"\n fields { cover: ref<cover>, amount: money, expiresAt: date, evidence: text, reservation: text? }\n lifecycle { states: [submitted, approved, paid, denied, expired], initial: submitted }\n action create { requires self.cover in [active] }\n action approve {\n from: submitted, to: approved, actor: { party: approved_by }\n requires approval by approved_by\n deadline: { at: self.expiresAt }\n moves reserve self.amount from self.cover.insurer to self.cover.holder capture \"reservation\"\n }\n action pay { from: approved, to: paid, deadline: { at: self.expiresAt }, moves post self.reservation }\n action deny { from: submitted, to: denied, requires approval by approved_by for approve is \"declined\" }\n action expire { from: approved, to: expired, actor: clock, due: { at: self.expiresAt }, moves void self.reservation }\n}\n"],
|
|
10
|
+
["lending.hsx", "header lending\n\ninstrument round(borrower: party, capital: party, plan: ref<financing.installments> = object(financing.installments), months: integer(1, 366) = 3, profit: percent = 3%, minimum_ticket: money = 100 SAR, investor_cap: percent = 20%, commitments: ref<lending.commitment> = object(lending.commitment)) {\n summary: \"A threshold funding round; failed rounds return each original wallet contribution.\"\n fields {\n borrower: account of borrower, capital: account(capital, cash, \"capital\"), plan: ref<plan>, price: money, closesAt: date\n months: integer = months, profit: money = rate(self.price, profit)\n minimumTicket: money = minimum_ticket, maximumTicket: money = rate(self.price, investor_cap)\n held: account of self\n }\n lifecycle { states: [open, funded, failed], initial: open }\n action create { requires unique \"funding_plan\" on [self.plan]; requires self.months == self.plan.months; requires self.profit == self.plan.profit; requires self.price == self.plan.principal; requires self.borrower == self.plan.funds.payer }\n action close {\n from: open, to: funded\n requires sum \"amount\" of { instrument: commitments, reference: \"round\", anchor: self.id, states: [committed], limit: 366 } == self.price\n requires self.held.balance == self.price\n moves self.price from self.held to self.plan.capital\n invoke: [{ selection: { instrument: commitments, reference: \"round\", anchor: self.id, states: [committed], limit: 366 }, action: fund, input: {} }]\n }\n action fail {\n from: open, to: failed, actor: clock, due: { at: self.closesAt }\n requires self.held.balance < self.price\n invoke: [{ selection: { instrument: commitments, reference: \"round\", anchor: self.id, states: [committed], limit: 366 }, action: refund, input: {} }]\n }\n}\n\ninstrument commitment(round: ref<lending.round>, wallet: ref<wallet.balance>, investor: party = party(person)) {\n summary: \"One investor's original-wallet contribution, capped cumulatively within the round.\"\n fields { round: ref<round>, wallet: ref<wallet>, amount: money, loss: account(investor, claim, \"loss\") }\n lifecycle { states: [committed, funded, withdrawn, refunded], initial: committed }\n invariants sum \"amount\" of { instrument: all(lending.commitment), reference: \"round\", anchor: self.round, states: [committed, funded], limit: 366, where: { wallet: { field: self.wallet } } } <= self.round.maximumTicket\n invariants sum \"amount\" of { instrument: all(lending.commitment), reference: \"round\", anchor: self.round, states: [committed, funded], limit: 366 } <= self.round.price\n action create {\n requires self.round in [open]\n requires self.wallet in [active]\n requires self.amount >= self.round.minimumTicket\n deadline: { at: self.round.closesAt }\n moves self.amount from self.wallet.held to self.round.held\n }\n action fund { from: committed, to: funded, actor: { parent: round } }\n action withdraw {\n from: committed, to: withdrawn\n requires self.round in [open]\n deadline: { at: self.round.closesAt }\n moves self.amount from self.round.held to self.wallet.held\n }\n action refund {\n from: committed, to: refunded, actor: { parent: round }\n moves self.amount from self.round.held to self.wallet.held\n }\n}\n\ninstrument distribution(round: ref<lending.round>, receipt: ref, commitments: ref<lending.commitment> = object(lending.commitment), mode: enum(cash, loss) = cash, residual_to: party = programOperator, fee: percent = 1%, tax: percent = 15%) {\n summary: \"Frozen weighted shares, floored once, with residual assigned to the chosen party.\"\n fields {\n round: ref<round>, source: ref<receipt>, mode: enum(cash, loss) = mode\n principal: money?, profit: money?, grossTotal: money?, charge: money?, vat: money?, net: money?\n held: account of self, losses: account(self, claim), residualLoss: account(residual_to, claim, \"loss\")\n }\n lifecycle { states: [pending, cash_ready, loss_ready, distributed], initial: pending }\n action create { requires self.round in [funded]; requires self.source.plan == self.round.plan; requires unique \"settlement_consumption\" on [self.source.plan, self.source.identity] }\n action prepare_cash {\n from: pending, to: cash_ready\n requires self.mode == \"cash\"; requires self.source.kind == \"cash\"\n calculate: [\n { target: \"principal\", op: \"sum\", values: [{ field: self.source.principal }] }\n { target: \"profit\", op: \"sum\", values: [{ field: self.source.profit }] }\n { target: \"charge\", op: \"rate\", base: { field: self.profit }, bps: { literal: fee }, rounding: \"floor\" }\n { target: \"vat\", op: \"rate\", base: { field: self.charge }, bps: { literal: tax }, rounding: \"floor\" }\n { target: \"grossTotal\", op: \"sum\", values: [{ field: self.principal }, { field: self.profit }] }\n { target: \"net\", op: \"subtract\", base: { field: self.grossTotal }, subtract: [{ field: self.charge }, { field: self.vat }] }\n ]\n }\n action prepare_loss {\n from: pending, to: loss_ready\n requires self.mode == \"loss\"; requires self.source.kind == \"loss\"\n calculate: [{ target: \"net\", op: \"sum\", values: [{ field: self.source.loss }] }]\n }\n action distribute_cash {\n from: cash_ready, to: distributed\n requires sum \"weight\" of { instrument: share, reference: \"distribution\", anchor: self.id, states: [pending], limit: 366 } == self.round.price\n moves self.principal from self.source.capital to self.held; moves self.profit from self.source.profitIncome to self.held; moves self.charge from self.held to programOperator; moves self.vat from self.held to programTax\n invoke: [{ selection: { instrument: share, reference: \"distribution\", anchor: self.id, states: [pending], limit: 366 }, action: cash, input: {} }, { reference: self.id, action: residual_cash, input: {} }]\n }\n action residual_cash {\n from: distributed, to: distributed, actor: { parent: current() }\n moves self.held.balance from self.held to residual_to\n }\n action distribute_loss {\n from: loss_ready, to: distributed\n requires sum \"weight\" of { instrument: share, reference: \"distribution\", anchor: self.id, states: [pending], limit: 366 } == self.round.price\n moves self.net from self.source.lossAccount to self.losses\n invoke: [{ selection: { instrument: share, reference: \"distribution\", anchor: self.id, states: [pending], limit: 366 }, action: loss, input: {} }, { reference: self.id, action: residual_loss, input: {} }]\n }\n action residual_loss {\n from: distributed, to: distributed, actor: { parent: current() }\n moves self.losses.balance from self.losses to self.residualLoss\n }\n records {\n share: {\n fields {\n distribution: ref<parent>, commitment: ref<commitments>\n weight: money = self.commitment.amount\n gross: money = ratio(self.distribution.net, self.weight, self.distribution.round.price)\n }\n lifecycle { states: [pending, paid], initial: pending }\n action create { requires self.distribution in [cash_ready, loss_ready]; requires self.commitment in [funded]; requires unique \"distribution_weight\" on [self.distribution, self.commitment]; requires self.commitment.round == self.distribution.round }\n action cash {\n from: pending, to: paid, actor: { parent: parent }\n moves self.gross from self.distribution.held to self.commitment.wallet.held\n }\n action loss { from: pending, to: paid, actor: { parent: parent }, moves self.gross from self.distribution.losses to self.commitment.loss }\n }\n }\n}\n"],
|
|
11
|
+
["marketplace.hsx", "header marketplace\n\ninstrument listing(seller: party) {\n summary: \"A seller's offer and its asking price.\"\n fields { seller: account of seller, title: text, price: money }\n lifecycle { states: [draft, active, withdrawn, sold], initial: draft }\n action create { actor: { party: seller } }\n action publish { from: draft, to: active, actor: { party: seller } }\n action withdraw { from: active, to: withdrawn, actor: { party: seller } }\n action sell { from: active, to: sold, actor: { party: seller } }\n}\n\ninstrument order(listing: ref<marketplace.listing>, buyer: party) {\n summary: \"A buyer's order for one listing at its declared price.\"\n fields { listing: ref<listing>, buyer: account of buyer, price: money }\n lifecycle { states: [placed, fulfilled, cancelled], initial: placed }\n action create {\n actor: { party: buyer }\n requires self.listing in [active]\n requires self.price == self.listing.price\n }\n action fulfill { from: placed, to: fulfilled }\n action cancel { from: placed, to: cancelled, actor: { party: buyer } }\n}\n"],
|
|
12
|
+
["money.hsx", "header money\n\ninstrument transfer(payer: party, payee: party, amount: money = runtime) {\n summary: \"One transfer between the bound parties.\"\n fields { amount: money = amount }\n lifecycle { states: [pending, paid, cancelled], initial: pending }\n action create { actor: { party: payer } }\n action pay {\n from: pending, to: paid, actor: { party: payer }\n moves self.amount from payer to payee\n }\n action cancel { from: pending, to: cancelled, actor: { party: payer } }\n}\n\ninstrument hold(payer: party, payee: party, amount: money = runtime) {\n summary: \"Hold the declared amount until release or refund.\"\n fields { payer: account of payer, payee: account of payee, amount: money = amount, price: money = self.amount, held: account of self }\n lifecycle { states: [pending, funded, released, refunded, cancelled], initial: pending }\n action create { }\n action fund {\n from: pending, to: funded, actor: { party: payer }\n moves self.amount from payer to self.held\n }\n action confirm {\n from: pending, to: funded\n requires self.held.balance == self.amount\n }\n action release {\n from: funded, to: released\n moves self.amount from self.held to payee\n }\n action refund {\n from: funded, to: refunded\n moves self.amount from self.held to payer\n }\n action cancel { from: pending, to: cancelled }\n}\n\ninstrument split(payer: party, amount: money = runtime, shares: split = { programOperator: 100% }) {\n summary: \"Split one amount; the last declared recipient receives the rounding remainder.\"\n fields { amount: money = amount }\n lifecycle { states: [pending, paid, cancelled], initial: pending }\n action create {}\n action pay { from: pending, to: paid, moves self.amount from payer shares shares }\n action cancel { from: pending, to: cancelled }\n}\n\ninstrument schedule(payer: party, payee: party, amount: money = runtime, count: integer(1, 366) = 12) {\n summary: \"A finite schedule whose dated pieces sum exactly to the declared total.\"\n fields { amount: money = amount, count: integer = count, dates: list(date, count) }\n lifecycle { states: [draft, active, complete, cancelled], initial: draft }\n action create {}\n action activate {\n from: draft, to: active\n requires count of { instrument: occurrence, reference: \"schedule\", anchor: self.id, states: [pending], limit: 366 } == self.count\n }\n action complete {\n from: active, to: complete\n requires count of { instrument: occurrence, reference: \"schedule\", anchor: self.id, states: [pending], limit: 366 } == 0\n }\n action cancel { from: [draft, active], to: cancelled }\n records {\n occurrence: {\n fields {\n schedule: ref<parent>, position: integer(1, count), dueAt: date = at(self.schedule.dates, self.position)\n base: money = divide(self.schedule.amount, count)\n allocated: money = multiply(self.base, count)\n remainder: money = subtract(self.schedule.amount, self.allocated)\n first: integer = divide(1, self.position)\n adjustment: money = multiply(self.remainder, self.first)\n amount: money = sum(self.base, self.adjustment)\n }\n lifecycle { states: [pending, paid, cancelled], initial: pending }\n action create { requires self.schedule in [draft]; requires unique \"scheduled_position\" on [self.schedule, self.position] }\n action pay { from: pending, to: paid, actor: clock, due: { at: self.dueAt }, requires self.schedule in [active], moves self.amount from payer to payee }\n action cancel { from: pending, to: cancelled, requires self.schedule in [cancelled] }\n }\n }\n}\n\ninstrument pool(payer: party, payee: party, target: money, closes: date) {\n summary: \"Contributions fund one threshold or return to their original contributor.\"\n fields { target: money = target, closesAt: date = closes, held: account of self }\n lifecycle { states: [open, paid, failed], initial: open }\n action create { }\n action pay {\n from: open, to: paid\n requires self.held.balance == self.target\n moves self.target from self.held to payee\n }\n action fail {\n from: open, to: failed, actor: clock, due: { at: self.closesAt }\n requires self.held.balance < self.target\n invoke: [{ selection: { instrument: contribution, reference: \"pool\", anchor: self.id, states: [paid], limit: 366 }, action: refund, input: {} }]\n }\n records {\n contribution: {\n fields { pool: ref<parent>, payer: account of payer, amount: money }\n lifecycle { states: [pending, paid, refunded], initial: pending }\n action create { requires self.pool in [open] }\n action pay { from: pending, to: paid, deadline: { at: self.pool.closesAt }, requires self.pool in [open], moves self.amount from self.payer to self.pool.held }\n action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from self.pool.held to self.payer }\n }\n }\n}\n\ninstrument swap(first: party, second: party, first_amount: money, second_amount: money, expires: date) {\n summary: \"Two reservations settle together or both return.\"\n fields { firstAmount: money = first_amount, secondAmount: money = second_amount, expiresAt: date = expires, firstReceipt: text?, secondReceipt: text? }\n lifecycle { states: [pending, first_ready, second_ready, both_ready, exchanged, cancelled], initial: pending }\n action create {}\n action fund_first { from: pending, to: first_ready, actor: { party: first }, deadline: { at: self.expiresAt }, moves reserve self.firstAmount from first to second capture \"firstReceipt\" }\n action fund_second { from: first_ready, to: both_ready, actor: { party: second }, deadline: { at: self.expiresAt }, moves reserve self.secondAmount from second to first capture \"secondReceipt\" }\n action start_second { from: pending, to: second_ready, actor: { party: second }, deadline: { at: self.expiresAt }, moves reserve self.secondAmount from second to first capture \"secondReceipt\" }\n action finish_first { from: second_ready, to: both_ready, actor: { party: first }, deadline: { at: self.expiresAt }, moves reserve self.firstAmount from first to second capture \"firstReceipt\" }\n action expire_second { from: second_ready, to: cancelled, actor: clock, due: { at: self.expiresAt }, moves void self.secondReceipt }\n action exchange { from: both_ready, to: exchanged, moves post self.firstReceipt; moves post self.secondReceipt }\n action cancel { from: pending, to: cancelled }\n action expire_first { from: first_ready, to: cancelled, actor: clock, due: { at: self.expiresAt }, moves void self.firstReceipt }\n action expire_both { from: both_ready, to: cancelled, actor: clock, due: { at: self.expiresAt }, moves void self.firstReceipt; moves void self.secondReceipt }\n}\n\ninstrument deposit(payer: party, payee: party, amount: money, expires: date, approval: approval) {\n fields { amount: money = amount, expiresAt: date = expires, reservation: text? }\n lifecycle { states: [pending, held, claimed, returned, cancelled], initial: pending }\n action create {}\n action fund { from: pending, to: held, actor: { party: payer }, moves reserve self.amount from payer to payee capture \"reservation\" }\n action claim { from: held, to: claimed, deadline: { at: self.expiresAt }, requires approval by approval, moves post self.reservation }\n action return { from: held, to: returned, actor: clock, due: { at: self.expiresAt }, moves void self.reservation }\n action cancel { from: pending, to: cancelled }\n}\n\ninstrument payout(payer: party, payee: party, amount: money = runtime, reconcile_within: duration = 3d) {\n summary: \"A bank-linked transfer with confirmation bound to its captured identity.\"\n fields { amount: money = amount, bank: account(payee, cash, external, \"bank\"), receipt: text?, expectedAt: date = after(self.createdAt, reconcile_within) }\n lifecycle { states: [pending, instructed, settled, exception, reversed], initial: pending }\n action create {}\n action instruct { from: pending, to: instructed, actor: { party: payer }, moves self.amount from payer to self.bank capture \"receipt\" }\n action confirm {\n from: [instructed, exception], to: settled\n requires self.receipt.status == \"settled\"\n }\n action expire {\n from: instructed, to: exception, actor: clock, due: { at: self.expectedAt }\n requires self.receipt.status != \"settled\"\n }\n action reverse {\n from: [instructed, exception], to: reversed\n requires self.receipt.status == \"reversed\"\n }\n}\n\ninstrument metered(payer: party, payee: party, unit_price: money, units: integer = 1, fee: fee = { seller: 0% }) {\n summary: \"A unique usage event priced once at the fixed unit price.\"\n fields { usageIdentity: text, unitPrice: money = unit_price, units: integer = units, amount: money = multiply(self.unitPrice, self.units) }\n lifecycle { states: [recorded, billed, cancelled], initial: recorded }\n action create { requires unique \"usage_event\" on [self.usageIdentity] }\n action bill { from: recorded, to: billed, moves self.amount from payer to payee fee fee }\n action cancel { from: recorded, to: cancelled }\n}\n\ninstrument batch(payer: party, payee: party, closes: date, approval: approval, reconcile_within: duration = 3d) {\n summary: \"A frozen positive net of credit and debit entries, paid once.\"\n fields { closesAt: date = closes, credits: money, debits: money, amount: money = subtract(self.credits, self.debits), bank: account(payee, cash, external, \"bank\"), receipt: text?, instructedAt: date?, expectedAt: date? }\n lifecycle { states: [open, closed, approved, instructed, exception, paid, reversed], initial: open }\n action create {}\n action close {\n from: open, to: closed, actor: clock, due: { at: self.closesAt }\n requires sum \"amount\" of { instrument: credit, reference: \"batch\", anchor: self.id, states: [recorded], limit: 366 } == self.credits\n requires sum \"amount\" of { instrument: debit, reference: \"batch\", anchor: self.id, states: [recorded], limit: 366 } == self.debits\n requires self.amount > \"0\"\n }\n action approve { from: closed, to: approved, requires approval by approval }\n action pay {\n from: approved, to: instructed\n set: { instructedAt: { field: self.now } }\n calculate: [{ target: \"expectedAt\", op: \"shift\", date: { field: self.now }, milliseconds: { literal: reconcile_within }, direction: \"after\" }]\n moves self.amount from payer to self.bank capture \"receipt\"\n }\n action confirm { from: [instructed, exception], to: paid, requires self.receipt.status == \"settled\" }\n action expire { from: instructed, to: exception, actor: clock, due: { at: self.expectedAt }, requires self.receipt.status != \"settled\" }\n action reverse { from: [instructed, exception], to: reversed, requires self.receipt.status == \"reversed\" }\n records {\n credit: {\n fields { batch: ref<parent>, identity: text, amount: money }\n lifecycle { states: [recorded], initial: recorded }\n action create { requires self.batch in [open]; requires unique \"batch_credit\" on [self.identity] }\n }\n debit: {\n fields { batch: ref<parent>, identity: text, amount: money }\n lifecycle { states: [recorded], initial: recorded }\n action create { requires self.batch in [open]; requires unique \"batch_debit\" on [self.identity] }\n }\n }\n}\n"],
|
|
13
|
+
["savings.hsx", "header savings\n\ninstrument circle(contribution: money, members: integer(1, 366), starts: date, memberships: ref<savings.membership> = object(savings.membership)) {\n summary: \"A fixed group contributes on its explicit calendar and takes the pot in order.\"\n fields { contribution: money = contribution, members: integer = members, startsAt: date = starts, dates: list(date, members), pot: money = multiply(self.contribution, self.members), held: account of self }\n lifecycle { states: [forming, active, closed, disbanded], initial: forming }\n action create { }\n action activate {\n from: forming, to: active, actor: clock, due: { at: self.startsAt }\n requires count of { instrument: memberships, reference: \"circle\", anchor: self.id, states: [joined], limit: 60 } == self.members\n }\n action close {\n from: active, to: closed\n requires count of { instrument: memberships, reference: \"circle\", anchor: self.id, states: [paid], limit: 60 } == self.members\n requires self.held.balance == \"0\"\n }\n action disband { from: forming, to: disbanded }\n}\n\ninstrument membership(circle: ref<savings.circle>, member: party) {\n fields { circle: ref<circle>, member: account of member, position: integer(1, 60), preceding: integer = subtract(self.position, 1), receivesAt: date = at(self.circle.dates, self.position), contribution: money = self.circle.contribution, total: money = self.circle.pot, dates: list(date, 60) }\n lifecycle { states: [joined, active, paid, withdrawn], initial: joined }\n action create { requires self.position <= self.circle.members; requires self.circle in [forming]; requires unique \"circle_seat\" on [self.circle, self.position]; requires unique \"circle_member\" on [self.circle, self.member] }\n action activate {\n from: joined, to: active\n requires self.circle in [active]\n requires count of { instrument: contribution, reference: \"membership\", anchor: self.id, states: [pending], limit: 60 } == self.circle.members\n }\n action receive {\n from: active, to: paid, actor: clock, due: { at: self.receivesAt }\n requires self.circle in [active]\n requires count of { instrument: all(savings.membership), reference: \"circle\", anchor: self.circle, states: [paid], limit: 60 } == self.preceding\n moves self.total from self.circle.held to self.member\n }\n action withdraw { from: joined, to: withdrawn, requires self.circle in [disbanded] }\n records {\n contribution: {\n fields { membership: ref<parent>, position: integer(1, 60), dueAt: date = at(self.membership.circle.dates, self.position), amount: money = self.membership.contribution }\n lifecycle { states: [pending, paid], initial: pending }\n action create { requires self.position <= self.membership.circle.members; requires self.membership in [joined]; requires unique \"seat_period\" on [self.membership, self.position] }\n action pay { from: pending, to: paid, actor: clock, due: { at: self.dueAt }, requires self.membership in [active, paid]; requires self.membership.circle in [active], moves self.amount from self.membership.member to self.membership.circle.held }\n }\n }\n}\n"],
|
|
14
|
+
["travel.hsx", "header travel\n\ninstrument package(price: money, supplier_cost: money, departure: date) {\n constraints { supplier_cost: at_most(price) }\n summary: \"Immutable package price, supplier cost and departure.\"\n fields { price: money = price, supplierCost: money = supplier_cost, departure: date = departure }\n lifecycle { states: [draft, published, closed], initial: draft }\n action create { requires self.supplierCost <= self.price }\n action publish { from: draft, to: published }\n action close { from: published, to: closed, actor: clock, due: { at: self.departure } }\n}\n\ninstrument booking(package: ref<travel.package>, buyer: party, supplier: approval, cover: ref<insurance.cover>, deposit: percent = 30%, balance_before: duration = 14d, confirm_within: duration = 24h, tax: percent = 15%, early_before: duration = 30d, middle_before: duration = 15d, middle_penalty: percent = 10%, late_penalty: percent = 30%) {\n constraints { early_before: greater_than(middle_before) }\n summary: \"A deposited booking with three cancellation bands and supplier confirmation.\"\n fields {\n package: ref<package>, bookingReference: text, buyer: account of buyer, supplier: account of supplier\n price: money = self.package.price, supplierCost: money = self.package.supplierCost\n deposit: money = rate(self.price, deposit), balance: money = subtract(self.price, self.deposit)\n margin: money = subtract(self.price, self.supplierCost)\n tax: money = rate(self.margin, tax), operatorNet: money = subtract(self.margin, self.tax)\n middlePenalty: money = rate(self.supplierCost, middle_penalty)\n latePenalty: money = rate(self.supplierCost, late_penalty)\n balanceAt: date = before(self.package.departure, balance_before)\n earlyAt: date = before(self.package.departure, early_before)\n middleAt: date = before(self.package.departure, middle_before)\n lateAt: date = after(self.middleAt, 1ms)\n confirmBy: date = after(self.package.departure, confirm_within)\n held: account of self\n refund: money?, retainedMargin: money?, retainedTax: money?, retainedNet: money?\n }\n lifecycle { states: [created, deposit_paid, paid, completed, cancelled, refunded], initial: created }\n action create {\n requires self.package in [published]\n requires unique \"booking_reference\" on [self.bookingReference]\n }\n action deposit {\n from: created, to: deposit_paid, actor: { party: buyer }, deadline: { at: self.balanceAt }\n requires count of { instrument: child(cover, slice), reference: \"covers\", anchor: self.id, states: [pending], limit: 366 } >= 1\n moves self.deposit from buyer to self.held\n invoke: [{ selection: { instrument: child(cover, slice), reference: \"covers\", anchor: self.id, states: [pending], limit: 366 }, action: collect, input: {} }]\n }\n action balance {\n from: deposit_paid, to: paid, actor: { party: buyer }, deadline: { at: self.balanceAt }\n moves self.balance from buyer to self.held\n }\n action confirm {\n from: paid, to: completed, actor: { party: supplier }\n due: { at: self.package.departure }, deadline: { at: self.confirmBy }\n requires approval by supplier\n moves self.supplierCost from self.held to supplier\n moves self.tax from self.held to programTax\n moves self.operatorNet from self.held to programOperator\n }\n action timeout {\n from: paid, to: refunded, actor: clock, due: { at: self.confirmBy }\n moves self.price from self.held to buyer\n }\n action cancel_early {\n from: [deposit_paid, paid], to: cancelled, deadline: { at: self.earlyAt }\n moves self.held.balance from self.held to buyer\n }\n action cancel_middle {\n from: [deposit_paid, paid], to: cancelled, due: { at: self.earlyAt }, deadline: { at: self.lateAt }\n calculate: [\n { target: refund, op: \"subtract\", base: { field: self.held.balance }, subtract: [{ field: self.deposit }] }\n { target: retainedMargin, op: \"subtract\", base: { field: self.deposit }, subtract: [{ field: self.middlePenalty }] }\n { target: retainedTax, op: \"rate\", base: { field: self.retainedMargin }, bps: { literal: tax }, rounding: \"floor\" }\n { target: retainedNet, op: \"subtract\", base: { field: self.retainedMargin }, subtract: [{ field: self.retainedTax }] }\n ]\n moves self.refund from self.held to buyer\n moves self.middlePenalty from self.held to supplier\n moves self.retainedTax from self.held to programTax\n moves self.retainedNet from self.held to programOperator\n }\n action cancel_late {\n from: [deposit_paid, paid], to: cancelled, actor: caller, due: { at: self.lateAt }\n calculate: [\n { target: retainedMargin, op: \"subtract\", base: { field: self.held.balance }, subtract: [{ field: self.latePenalty }] }\n { target: retainedTax, op: \"rate\", base: { field: self.retainedMargin }, bps: { literal: tax }, rounding: \"floor\" }\n { target: retainedNet, op: \"subtract\", base: { field: self.retainedMargin }, subtract: [{ field: self.retainedTax }] }\n ]\n moves self.latePenalty from self.held to supplier\n moves self.retainedTax from self.held to programTax\n moves self.retainedNet from self.held to programOperator\n }\n action default_balance {\n from: deposit_paid, to: cancelled, actor: clock, due: { at: self.balanceAt }\n calculate: [\n { target: retainedMargin, op: \"subtract\", base: { field: self.held.balance }, subtract: [{ field: self.latePenalty }] }\n { target: retainedTax, op: \"rate\", base: { field: self.retainedMargin }, bps: { literal: tax }, rounding: \"floor\" }\n { target: retainedNet, op: \"subtract\", base: { field: self.retainedMargin }, subtract: [{ field: self.retainedTax }] }\n ]\n moves self.latePenalty from self.held to supplier\n moves self.retainedTax from self.held to programTax\n moves self.retainedNet from self.held to programOperator\n }\n}\n"],
|
|
15
|
+
["wallet.hsx", "header wallet\n\ninstrument balance(holder: party, spend_limit: money = 100000 SAR) {\n summary: \"A holder's reusable balance with an immutable per-spend ceiling.\"\n fields { holder: account of holder, held: account of self, spendLimit: money = spend_limit }\n lifecycle { states: [pending, active, frozen, closed], initial: pending }\n action create { }\n action activate { from: pending, to: active, actor: { party: holder } }\n action topup {\n from: active, to: active, actor: { party: holder }\n input { amount: money }\n moves input.amount from holder to self.held\n }\n action withdraw {\n from: active, to: active, actor: { party: holder }\n input { amount: money }\n moves input.amount from self.held to holder\n }\n action freeze { from: active, to: frozen, }\n action unfreeze { from: frozen, to: active, }\n action close { from: [pending, active], to: closed, requires self.held.balance == \"0\" }\n}\n\ninstrument spend(wallet: ref<wallet.balance>, payee: party) {\n summary: \"Reserve one wallet spend, then post or void the same reservation.\"\n fields { wallet: ref<wallet>, amount: money, expiresAt: date, reservation: text? }\n lifecycle { states: [pending, reserved, paid, cancelled, expired], initial: pending }\n action create {\n requires self.wallet in [active]\n requires self.amount <= self.wallet.spendLimit\n }\n action reserve {\n from: pending, to: reserved\n requires self.wallet in [active]\n deadline: { at: self.expiresAt }\n moves reserve self.amount from self.wallet.held to payee capture \"reservation\"\n }\n action pay { from: reserved, to: paid, moves post self.reservation }\n action cancel { from: reserved, to: cancelled, moves void self.reservation }\n action expire { from: reserved, to: expired, actor: clock, due: { at: self.expiresAt }, moves void self.reservation }\n}\n"],
|
|
24
16
|
]);
|