@hyperscale0/hsx 1.0.265 → 1.0.293

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +36 -5
  2. package/README.md +3 -3
  3. package/SECURITY.md +10 -21
  4. package/bases/lending.hsx +0 -1
  5. package/bases/wallet.hsx +0 -1
  6. package/dist/src/bases-bundle.js +2 -2
  7. package/dist/src/bases-bundle.js.map +1 -1
  8. package/dist/src/compile.d.ts.map +1 -1
  9. package/dist/src/compile.js +130 -15
  10. package/dist/src/compile.js.map +1 -1
  11. package/dist/src/examples-bundle.d.ts.map +1 -1
  12. package/dist/src/examples-bundle.js +16 -15
  13. package/dist/src/examples-bundle.js.map +1 -1
  14. package/dist/src/std-bundle.d.ts.map +1 -1
  15. package/dist/src/std-bundle.js +10 -8
  16. package/dist/src/std-bundle.js.map +1 -1
  17. package/docs/README.md +46 -31
  18. package/examples/cards.hsx +2 -2
  19. package/examples/collections.hsx +2 -2
  20. package/examples/escrow.hsx +2 -2
  21. package/examples/financing.hsx +2 -2
  22. package/examples/insurance.hsx +2 -2
  23. package/examples/lending.hsx +2 -3
  24. package/examples/library.hsx +9 -3
  25. package/examples/marketplace.hsx +2 -2
  26. package/examples/money.hsx +2 -2
  27. package/examples/rental-deposit.hsx +9 -27
  28. package/examples/rental-hire.hsx +23 -0
  29. package/examples/repair-approval.hsx +2 -2
  30. package/examples/reporting.hsx +2 -2
  31. package/examples/savings.hsx +2 -2
  32. package/examples/serviced.hsx +9 -3
  33. package/examples/wallet.hsx +2 -4
  34. package/package.json +4 -4
  35. package/src/bases-bundle.ts +2 -2
  36. package/src/compile.ts +170 -19
  37. package/src/examples-bundle.ts +16 -15
  38. package/src/std-bundle.ts +10 -8
  39. package/std/booking.hsx +8 -3
  40. package/std/collections.hsx +1 -1
  41. package/std/escrow.hsx +16 -29
  42. package/std/financing.hsx +21 -48
  43. package/std/marketplace.hsx +2 -7
  44. package/std/money.hsx +6 -8
  45. package/std/purchase.hsx +89 -0
  46. package/std/rental.hsx +68 -0
  47. package/std/reporting.hsx +1 -11
  48. package/std/wallet.hsx +2 -5
package/CHANGELOG.md CHANGED
@@ -1,14 +1,45 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## Since 1.0.123
4
4
 
5
- - Add eleven neutral archetype programs in `bases/` and the browser export
5
+ Releases after 1.0.123 shipped without per-release entries. This entry
6
+ summarises what changed in the language, the compiler and the bundled programs
7
+ since then.
8
+
9
+ Added:
10
+
11
+ - Eleven neutral archetype programs in `bases/` and the browser export
6
12
  `@hyperscale0/hsx/bases`. Generation and preparation bundle their source and
7
13
  metadata. Package checks enforce compilation, coverage and neutral content.
14
+ - The `booking` std header, with deposit, balance and cancellation windows.
15
+ - `financing.installments` prices a plan as `amortizing` or `flat_total`, and
16
+ `refund_within` (default 30d) bounds refunds after a plan is paid.
17
+ - `wallet.balance` takes a `topup_fee`; `escrow.hold` takes `deliver_within`.
18
+ - Selections accept `overlaps: { start, end, from, until }` to match rows whose
19
+ date period meets a range.
20
+ - Program instruments take party parameters, and a `fee` on a move of an action
21
+ input is calculated when that action runs.
22
+ - Every agreement reference is an optional creation input on an exposed create.
23
+ - Customers can start the std agreements whose first party can be a customer on
24
+ a record they do not own, through the object's `entryActions`.
25
+ - `economics` can name a party reached through a reference.
26
+
27
+ Changed:
8
28
 
9
- - Rewrite all 15 ready-made products as Saudi business examples with SAR prices,
10
- explicit fees and VAT treatment, named business counterparties, useful list
11
- columns, and supported payment, refund, dispute and overdue paths.
29
+ - All 15 examples are Saudi businesses with SAR prices, explicit fees and VAT
30
+ treatment, named business counterparties, list columns, and payment, refund,
31
+ dispute and overdue paths.
32
+ - A required instrument value with no fixed value becomes create `input` on the
33
+ agreement. Only an explicit `rename` shares it as record metadata.
34
+ - Record actions stay private until the program exposes them.
35
+ - Std actions name an accountable actor, and std money pulled from a customer
36
+ needs that customer's authority.
37
+ - A split binds declared business and subject-role recipients itself, and a
38
+ share the payer keeps stays in the source account with no self-transfer.
39
+ - Late charges and recovery costs are bounded across the whole plan, and an
40
+ insurance cover's claim limit is shared across every claim attachment.
41
+ - Financing and escrow plans have exits for returned, undelivered and paid-off
42
+ goods, and a paid-off plan closes once its refund window passes.
12
43
 
13
44
  ## 1.0.123
14
45
 
package/README.md CHANGED
@@ -20,7 +20,8 @@ npx hsx cost rental.hsx
20
20
 
21
21
  Start with the [first program and rental guide](docs/README.md). Save the
22
22
  [rental source](examples/rental-deposit.hsx) as `rental.hsx` for these commands.
23
- It holds 1,000 SAR, charges a one-time 50 SAR late fee, and returns the remainder.
23
+ It holds 1,000 SAR, charges one 50 SAR late fee plus 7.50 SAR VAT, and returns
24
+ the remainder.
24
25
  The guide explains the fixed business binding and the host needed to execute it.
25
26
 
26
27
  `npx hsx headers --json` prints every std instrument and its tunables from the
@@ -75,8 +76,7 @@ currency units, basis points and milliseconds.
75
76
 
76
77
  Examples have `id`, `title`, `summary`, `headers` and `source`. The ID is the
77
78
  filename without `.hsx`; leading comments supply the summary. The array is sorted
78
- by filename and contains no filesystem imports. Workspace imports use
79
- `@hyperscale0/hsx` instead of the published `@hyperscale0/hsx` name.
79
+ by filename and contains no filesystem imports.
80
80
 
81
81
  ## Working from source
82
82
 
package/SECURITY.md CHANGED
@@ -9,9 +9,8 @@ security-sensitive belongs in an issue, a pull request, a discussion, or a
9
9
  commit message.
10
10
 
11
11
  A report we can act on names the affected version and gives us something to
12
- run: the `.hsx` source, and what you expected the compiler to do with it. If
13
- you can shape it as a fixture under `test/fixtures/`, do that; it goes straight
14
- into the fix.
12
+ run: the `.hsx` source, and what you expected the compiler to do with it. A
13
+ failing spec is the most useful shape; it goes straight into the fix.
15
14
 
16
15
  ## What counts
17
16
 
@@ -23,22 +22,12 @@ executes, so the interesting failures are the ones a source file can cause:
23
22
  whose fees move money the source did not authorise. A `valid` verdict on an
24
23
  unsound program is the worst bug this package can have.
25
24
  - **A program that makes the lexer, parser, checker, or lowering burn
26
- unbounded time or memory.** The schedule, split, and money-event limits bound
27
- the output; `src/limits.ts` bounds the input, and a source over either of its
28
- ceilings is refused with a diagnostic rather than read.
29
-
30
- The stages behind that ceiling are linear, measured rather than assumed. The
31
- lexer was timed on 21 adversarial inputs, one per branch of the scanner:
32
- unterminated strings, unclosed comments, single-character invalid runs, one
33
- quarter-megabyte invalid run, digit and percent soup, and interleaved open
34
- delimiters. Across a 64x range of input size its cost per byte grew at most
35
- 1.44x, where a quadratic scanner would grow 64x. The worst admitted file
36
- costs 9.8 ms to lex and 21.1 ms to compile. A payload that makes any stage
37
- cost meaningfully more than that per byte is a finding worth reporting.
38
-
39
- - **A crash.** Parsing and checking are total by design: they return
40
- diagnostics, they never throw. A source file that throws out of `compile()`
41
- is a bug even when the program is nonsense.
25
+ unbounded time or memory.** The parser refuses source over 256 KiB with
26
+ `HSX1004`, and block or expression nesting deeper than 32. A way around
27
+ either, or a stage whose cost grows faster than its input, is a finding
28
+ worth reporting.
29
+ - **A crash.** `compile()` reports invalid source as diagnostics. A source
30
+ file that makes it throw is a bug even when the program is nonsense.
42
31
  - **A diagnostic that reports the wrong source coordinates**, which sends an
43
32
  author to fix the wrong line.
44
33
 
@@ -47,8 +36,8 @@ anything that requires already controlling the machine running it.
47
36
 
48
37
  ## Supported versions
49
38
 
50
- Alpha releases are supported at the newest published `alpha` version only.
51
- Fixes land there; there is no backport branch before 1.0.0.
39
+ Fixes land in the newest published `1.0.N` release. Older releases get no
40
+ backports.
52
41
 
53
42
  ## Disclosure
54
43
 
package/bases/lending.hsx CHANGED
@@ -35,7 +35,6 @@ object plan "Plan" {
35
35
  expose activate as activate_balance
36
36
  expose topup as add_funds
37
37
  expose withdraw as withdraw_funds
38
- expose close as close_balance
39
38
  }
40
39
  attach round = lending.round {
41
40
  borrower: owner, plan: plan
package/bases/wallet.hsx CHANGED
@@ -11,7 +11,6 @@ object balance "Balance" {
11
11
  expose withdraw as withdraw_funds
12
12
  expose freeze as freeze_balance
13
13
  expose unfreeze as unfreeze_balance
14
- expose close as close_balance
15
14
  }
16
15
  attach spend = wallet.spend {
17
16
  wallet: balance, payee: operator, holder: owner
@@ -4,11 +4,11 @@ export const bases = [
4
4
  { "id": "escrow", "title": "Hold payment until delivery", "summary": "Hold a buyer’s payment until delivery is accepted.", "headers": ["escrow"], "source": "// Hold a buyer’s payment until delivery is accepted.\nprogram escrow_base \"Hold payment until delivery\"\nuse escrow\nparty seller: business\n\nobject order \"Order\" {\n fields { price: money }\n attach payment = escrow.hold {\n payer: owner, payee: seller\n expose create as create\n expose fund as fund\n expose deliver as deliver\n expose accept as accept\n expose dispute as dispute\n expose verify_return as verify_return\n expose refund as refund\n expose refund_undelivered as refund_undelivered\n expose settle_release as settle_release\n expose settle_return as settle_return\n expose settle_refund as settle_refund\n expose cancel as cancel\n }\n}\n" },
5
5
  { "id": "financing", "title": "Split a purchase into instalments", "summary": "Fund a purchase and collect its scheduled repayments.", "headers": ["financing"], "source": "// Fund a purchase and collect its scheduled repayments.\nprogram financing_base \"Split a purchase into instalments\"\nuse financing\nparty seller: business\n\nobject purchase \"Purchase\" {\n fields { price: money }\n attach allowance = financing.limits {\n borrower: owner, per_borrower: runtime\n expose create as request_limit\n expose approve as approve_limit\n }\n attach budget = financing.portfolio_limit {\n limit: runtime\n expose create as create_budget\n expose approve as approve_budget\n }\n // Architect sets the placeholder profit rate during tailoring.\n attach plan = financing.installments {\n borrower: owner, capital: operator\n months: 2, pricing: flat_total, profit_rate: 0%\n disburse_to: seller, seller: seller, limits: allowance, portfolio: budget\n expose create as quote\n expose sign as sign\n expose disburse as fund\n expose cancel as cancel_quote\n expose void as cancel_plan\n expose payoff as repay_early\n expose complete as complete\n }\n}\n\nexpose purchase.plan.payment.create as record_payment\nexpose purchase.plan.payment.pay as pay_instalment\nexpose purchase.plan.payment.refund as refund_payment\nexpose purchase.plan.payoff_quote.create as quote_payoff\n" },
6
6
  { "id": "insurance", "title": "Protect a purchase", "summary": "Collect cover premiums and reserve approved claims for payment.", "headers": ["insurance"], "source": "// Collect cover premiums and reserve approved claims for payment.\nprogram insurance_base \"Protect a purchase\"\nuse insurance\n\nobject purchase \"Purchase\" {\n attach cover = insurance.cover {\n holder: owner, adapter: \"conformance_boundary\"\n expose create as create_cover\n expose activate as activate_cover\n expose cancel as cancel_cover\n }\n attach claim = insurance.claim {\n cover: cover, inspector: operator\n expose create as create_claim\n expose approve as approve_claim\n expose deny as deny_claim\n expose pay as pay_claim\n }\n}\n\nexpose purchase.cover.slice.create as create_period\nexpose purchase.cover.slice.collect as collect_premium\n" },
7
- { "id": "lending", "title": "Pool funds for lending", "summary": "Pool member commitments to fund a loan and distribute repayments or losses.", "headers": ["lending", "financing", "wallet"], "source": "// Pool member commitments to fund a loan and distribute repayments or losses.\nprogram lending_base \"Pool funds for lending\"\nuse lending\nuse financing\nuse wallet\n\nobject plan \"Plan\" {\n fields { price: money }\n attach allowance = financing.limits {\n borrower: owner, per_borrower: runtime\n expose create as request_limit\n expose approve as approve_limit\n }\n attach budget = financing.portfolio_limit {\n limit: runtime\n expose create as create_budget\n expose approve as approve_budget\n }\n // Architect sets the placeholder profit rate during tailoring.\n attach plan = financing.installments {\n borrower: owner, capital: operator\n months: 2, pricing: flat_total, profit_rate: 0%\n disburse_to: borrower, limits: allowance, portfolio: budget\n expose create as quote\n expose sign as sign\n expose disburse as fund\n expose cancel as cancel_quote\n expose void as cancel_plan\n expose payoff as repay_early\n expose complete as complete\n }\n attach balance = wallet.balance {\n holder: actor\n expose create as create_balance\n expose activate as activate_balance\n expose topup as add_funds\n expose withdraw as withdraw_funds\n expose close as close_balance\n }\n attach round = lending.round {\n borrower: owner, plan: plan\n expose create as open_round\n expose close as close_round\n }\n attach commitment = lending.commitment {\n round: round, wallet: balance, investor: actor\n expose create as commit\n expose withdraw as withdraw_commitment\n }\n attach returns = lending.distribution {\n round: round, receipt: plan.settlement\n expose create as record_settlement\n expose prepare_cash as prepare_returns\n expose distribute_cash as distribute_returns\n }\n attach losses = lending.distribution {\n round: round, receipt: plan.settlement, mode: loss\n expose create as record_loss\n expose prepare_loss as prepare_losses\n expose distribute_loss as distribute_losses\n }\n}\n\nexpose plan.plan.payment.create as record_payment\nexpose plan.plan.payment.pay as pay_instalment\nexpose plan.plan.payment.refund as refund_payment\nexpose plan.plan.payoff_quote.create as quote_payoff\n\nexpose plan.returns.share.create as create_return_share\nexpose plan.losses.share.create as create_loss_share\nexpose plan.plan.write_off_request.create as request_write_off\nexpose plan.plan.write_off_request.apply as apply_write_off\nexpose plan.plan.write_off_request.cancel as cancel_write_off\n" },
7
+ { "id": "lending", "title": "Pool funds for lending", "summary": "Pool member commitments to fund a loan and distribute repayments or losses.", "headers": ["lending", "financing", "wallet"], "source": "// Pool member commitments to fund a loan and distribute repayments or losses.\nprogram lending_base \"Pool funds for lending\"\nuse lending\nuse financing\nuse wallet\n\nobject plan \"Plan\" {\n fields { price: money }\n attach allowance = financing.limits {\n borrower: owner, per_borrower: runtime\n expose create as request_limit\n expose approve as approve_limit\n }\n attach budget = financing.portfolio_limit {\n limit: runtime\n expose create as create_budget\n expose approve as approve_budget\n }\n // Architect sets the placeholder profit rate during tailoring.\n attach plan = financing.installments {\n borrower: owner, capital: operator\n months: 2, pricing: flat_total, profit_rate: 0%\n disburse_to: borrower, limits: allowance, portfolio: budget\n expose create as quote\n expose sign as sign\n expose disburse as fund\n expose cancel as cancel_quote\n expose void as cancel_plan\n expose payoff as repay_early\n expose complete as complete\n }\n attach balance = wallet.balance {\n holder: actor\n expose create as create_balance\n expose activate as activate_balance\n expose topup as add_funds\n expose withdraw as withdraw_funds\n }\n attach round = lending.round {\n borrower: owner, plan: plan\n expose create as open_round\n expose close as close_round\n }\n attach commitment = lending.commitment {\n round: round, wallet: balance, investor: actor\n expose create as commit\n expose withdraw as withdraw_commitment\n }\n attach returns = lending.distribution {\n round: round, receipt: plan.settlement\n expose create as record_settlement\n expose prepare_cash as prepare_returns\n expose distribute_cash as distribute_returns\n }\n attach losses = lending.distribution {\n round: round, receipt: plan.settlement, mode: loss\n expose create as record_loss\n expose prepare_loss as prepare_losses\n expose distribute_loss as distribute_losses\n }\n}\n\nexpose plan.plan.payment.create as record_payment\nexpose plan.plan.payment.pay as pay_instalment\nexpose plan.plan.payment.refund as refund_payment\nexpose plan.plan.payoff_quote.create as quote_payoff\n\nexpose plan.returns.share.create as create_return_share\nexpose plan.losses.share.create as create_loss_share\nexpose plan.plan.write_off_request.create as request_write_off\nexpose plan.plan.write_off_request.apply as apply_write_off\nexpose plan.plan.write_off_request.cancel as cancel_write_off\n" },
8
8
  { "id": "marketplace", "title": "Connect buyers and sellers", "summary": "Turn a seller’s offer into an order with payment held until acceptance.", "headers": ["marketplace", "escrow"], "source": "// Turn a seller’s offer into an order with payment held until acceptance.\nprogram marketplace_base \"Connect buyers and sellers\"\nuse marketplace\nuse escrow\nparty seller: business\n\nobject order \"Order\" {\n fields { price: money }\n attach offer = marketplace.listing {\n seller: seller\n expose create as create_offer\n expose publish as publish_offer\n expose withdraw as withdraw_offer\n expose sell as sell_offer\n }\n attach order = marketplace.order {\n listing: offer, buyer: owner, fulfiller: seller\n expose create as create_order\n expose commit as commit_order\n expose fulfill as fulfill_order\n expose cancel as cancel_order\n }\n attach payment = escrow.hold {\n payer: owner, payee: seller\n expose create as create\n expose fund as fund\n expose deliver as deliver\n expose accept as accept\n expose dispute as dispute\n expose verify_return as verify_return\n expose refund as refund\n expose refund_undelivered as refund_undelivered\n expose settle_release as settle_release\n expose settle_return as settle_return\n expose settle_refund as settle_refund\n expose cancel as cancel\n }\n}\n" },
9
9
  { "id": "money", "title": "Collect a payment", "summary": "Collect a payment from a customer.", "headers": ["money"], "source": "// Collect a payment from a customer.\nprogram money_base \"Collect a payment\"\nuse money\n\nobject purchase \"Purchase\" {\n attach payment = money.transfer {\n payer: owner, payee: operator\n expose create as create\n expose pay as pay\n expose cancel as cancel\n }\n}\n" },
10
10
  { "id": "reporting", "title": "Track repayments", "summary": "Report outstanding balances and overdue repayments on financed purchases.", "headers": ["reporting", "financing"], "source": "// Report outstanding balances and overdue repayments on financed purchases.\nprogram reporting_base \"Track repayments\"\nuse reporting\nuse financing\n\nobject purchase \"Purchase\" {\n fields { price: money }\n attach allowance = financing.limits {\n borrower: owner, per_borrower: runtime\n expose create as request_limit\n expose approve as approve_limit\n }\n attach budget = financing.portfolio_limit {\n limit: runtime\n expose create as create_budget\n expose approve as approve_budget\n }\n // Architect sets the placeholder profit rate during tailoring.\n attach plan = financing.installments {\n borrower: owner, capital: operator\n months: 2, pricing: flat_total, profit_rate: 0%\n disburse_to: borrower, limits: allowance, portfolio: budget\n expose create as quote\n expose sign as sign\n expose disburse as fund\n expose cancel as cancel_quote\n expose void as cancel_plan\n expose payoff as repay_early\n expose complete as complete\n }\n attach reports = reporting.portfolio {\n on: plan\n expose create as create_reports\n }\n}\n\nexpose purchase.plan.payment.create as record_payment\nexpose purchase.plan.payment.pay as pay_instalment\nexpose purchase.plan.payment.refund as refund_payment\nexpose purchase.plan.payoff_quote.create as quote_payoff\n" },
11
11
  { "id": "savings", "title": "Save as a group", "summary": "Collect contributions into a shared pot and pay each member in turn.", "headers": ["savings"], "source": "// Collect contributions into a shared pot and pay each member in turn.\nprogram savings_base \"Save as a group\"\nuse savings\n\nobject pot \"Pot\" {\n attach circle = savings.circle {\n contribution: runtime, members: 2, starts: runtime\n expose create as create_circle\n expose disband as disband_circle\n expose close as close_circle\n }\n attach membership = savings.membership {\n circle: circle, member: actor\n expose create as join\n expose activate as activate_membership\n expose withdraw as withdraw\n }\n}\n\nexpose pot.membership.contribution.create as schedule_contribution\n" },
12
- { "id": "wallet", "title": "Offer prepaid credit", "summary": "Let a customer add funds and reserve each spend before payment.", "headers": ["wallet"], "source": "// Let a customer add funds and reserve each spend before payment.\nprogram wallet_base \"Offer prepaid credit\"\nuse wallet\n\nobject balance \"Balance\" {\n attach balance = wallet.balance {\n holder: owner\n expose create as create_balance\n expose activate as activate_balance\n expose topup as add_funds\n expose withdraw as withdraw_funds\n expose freeze as freeze_balance\n expose unfreeze as unfreeze_balance\n expose close as close_balance\n }\n attach spend = wallet.spend {\n wallet: balance, payee: operator, holder: owner\n expose create as create\n expose reserve as reserve\n expose pay as pay\n expose cancel as cancel\n }\n}\n" },
12
+ { "id": "wallet", "title": "Offer prepaid credit", "summary": "Let a customer add funds and reserve each spend before payment.", "headers": ["wallet"], "source": "// Let a customer add funds and reserve each spend before payment.\nprogram wallet_base \"Offer prepaid credit\"\nuse wallet\n\nobject balance \"Balance\" {\n attach balance = wallet.balance {\n holder: owner\n expose create as create_balance\n expose activate as activate_balance\n expose topup as add_funds\n expose withdraw as withdraw_funds\n expose freeze as freeze_balance\n expose unfreeze as unfreeze_balance\n }\n attach spend = wallet.spend {\n wallet: balance, payee: operator, holder: owner\n expose create as create\n expose reserve as reserve\n expose pay as pay\n expose cancel as cancel\n }\n}\n" },
13
13
  ];
14
14
  //# sourceMappingURL=bases-bundle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bases-bundle.js","sourceRoot":"","sources":["../../src/bases-bundle.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,KAAK,GAAc;IAC9B,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAC,uBAAuB,EAAC,SAAS,EAAC,kEAAkE,EAAC,SAAS,EAAC,CAAC,OAAO,CAAC,EAAC,QAAQ,EAAC,srCAAsrC,EAAC;IAC/0C,EAAC,IAAI,EAAC,aAAa,EAAC,OAAO,EAAC,4BAA4B,EAAC,SAAS,EAAC,sEAAsE,EAAC,SAAS,EAAC,CAAC,aAAa,EAAC,WAAW,CAAC,EAAC,QAAQ,EAAC,wqDAAwqD,EAAC;IACl2D,EAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,6BAA6B,EAAC,SAAS,EAAC,oDAAoD,EAAC,SAAS,EAAC,CAAC,QAAQ,CAAC,EAAC,QAAQ,EAAC,ksBAAksB,EAAC;IACr1B,EAAC,IAAI,EAAC,WAAW,EAAC,OAAO,EAAC,mCAAmC,EAAC,SAAS,EAAC,uDAAuD,EAAC,SAAS,EAAC,CAAC,WAAW,CAAC,EAAC,QAAQ,EAAC,0uCAA0uC,EAAC;IAC54C,EAAC,IAAI,EAAC,WAAW,EAAC,OAAO,EAAC,oBAAoB,EAAC,SAAS,EAAC,iEAAiE,EAAC,SAAS,EAAC,CAAC,WAAW,CAAC,EAAC,QAAQ,EAAC,wrBAAwrB,EAAC;IACr1B,EAAC,IAAI,EAAC,SAAS,EAAC,OAAO,EAAC,wBAAwB,EAAC,SAAS,EAAC,6EAA6E,EAAC,SAAS,EAAC,CAAC,SAAS,EAAC,WAAW,EAAC,QAAQ,CAAC,EAAC,QAAQ,EAAC,8+EAA8+E,EAAC;IAC5qF,EAAC,IAAI,EAAC,aAAa,EAAC,OAAO,EAAC,4BAA4B,EAAC,SAAS,EAAC,yEAAyE,EAAC,SAAS,EAAC,CAAC,aAAa,EAAC,QAAQ,CAAC,EAAC,QAAQ,EAAC,yqCAAyqC,EAAC;IACn2C,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAC,mBAAmB,EAAC,SAAS,EAAC,oCAAoC,EAAC,SAAS,EAAC,CAAC,OAAO,CAAC,EAAC,QAAQ,EAAC,iSAAiS,EAAC;IACxZ,EAAC,IAAI,EAAC,WAAW,EAAC,OAAO,EAAC,kBAAkB,EAAC,SAAS,EAAC,2EAA2E,EAAC,SAAS,EAAC,CAAC,WAAW,EAAC,WAAW,CAAC,EAAC,QAAQ,EAAC,wzCAAwzC,EAAC;IACz+C,EAAC,IAAI,EAAC,SAAS,EAAC,OAAO,EAAC,iBAAiB,EAAC,SAAS,EAAC,sEAAsE,EAAC,SAAS,EAAC,CAAC,SAAS,CAAC,EAAC,QAAQ,EAAC,4mBAA4mB,EAAC;IACvwB,EAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,sBAAsB,EAAC,SAAS,EAAC,iEAAiE,EAAC,SAAS,EAAC,CAAC,QAAQ,CAAC,EAAC,QAAQ,EAAC,grBAAgrB,EAAC;CAC10B,CAAC"}
1
+ {"version":3,"file":"bases-bundle.js","sourceRoot":"","sources":["../../src/bases-bundle.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,KAAK,GAAc;IAC9B,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAC,uBAAuB,EAAC,SAAS,EAAC,kEAAkE,EAAC,SAAS,EAAC,CAAC,OAAO,CAAC,EAAC,QAAQ,EAAC,srCAAsrC,EAAC;IAC/0C,EAAC,IAAI,EAAC,aAAa,EAAC,OAAO,EAAC,4BAA4B,EAAC,SAAS,EAAC,sEAAsE,EAAC,SAAS,EAAC,CAAC,aAAa,EAAC,WAAW,CAAC,EAAC,QAAQ,EAAC,wqDAAwqD,EAAC;IACl2D,EAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,6BAA6B,EAAC,SAAS,EAAC,oDAAoD,EAAC,SAAS,EAAC,CAAC,QAAQ,CAAC,EAAC,QAAQ,EAAC,ksBAAksB,EAAC;IACr1B,EAAC,IAAI,EAAC,WAAW,EAAC,OAAO,EAAC,mCAAmC,EAAC,SAAS,EAAC,uDAAuD,EAAC,SAAS,EAAC,CAAC,WAAW,CAAC,EAAC,QAAQ,EAAC,0uCAA0uC,EAAC;IAC54C,EAAC,IAAI,EAAC,WAAW,EAAC,OAAO,EAAC,oBAAoB,EAAC,SAAS,EAAC,iEAAiE,EAAC,SAAS,EAAC,CAAC,WAAW,CAAC,EAAC,QAAQ,EAAC,wrBAAwrB,EAAC;IACr1B,EAAC,IAAI,EAAC,SAAS,EAAC,OAAO,EAAC,wBAAwB,EAAC,SAAS,EAAC,6EAA6E,EAAC,SAAS,EAAC,CAAC,SAAS,EAAC,WAAW,EAAC,QAAQ,CAAC,EAAC,QAAQ,EAAC,28EAA28E,EAAC;IACzoF,EAAC,IAAI,EAAC,aAAa,EAAC,OAAO,EAAC,4BAA4B,EAAC,SAAS,EAAC,yEAAyE,EAAC,SAAS,EAAC,CAAC,aAAa,EAAC,QAAQ,CAAC,EAAC,QAAQ,EAAC,yqCAAyqC,EAAC;IACn2C,EAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAC,mBAAmB,EAAC,SAAS,EAAC,oCAAoC,EAAC,SAAS,EAAC,CAAC,OAAO,CAAC,EAAC,QAAQ,EAAC,iSAAiS,EAAC;IACxZ,EAAC,IAAI,EAAC,WAAW,EAAC,OAAO,EAAC,kBAAkB,EAAC,SAAS,EAAC,2EAA2E,EAAC,SAAS,EAAC,CAAC,WAAW,EAAC,WAAW,CAAC,EAAC,QAAQ,EAAC,wzCAAwzC,EAAC;IACz+C,EAAC,IAAI,EAAC,SAAS,EAAC,OAAO,EAAC,iBAAiB,EAAC,SAAS,EAAC,sEAAsE,EAAC,SAAS,EAAC,CAAC,SAAS,CAAC,EAAC,QAAQ,EAAC,4mBAA4mB,EAAC;IACvwB,EAAC,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,sBAAsB,EAAC,SAAS,EAAC,iEAAiE,EAAC,SAAS,EAAC,CAAC,QAAQ,CAAC,EAAC,QAAQ,EAAC,6oBAA6oB,EAAC;CACvyB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.ts"],"names":[],"mappings":"AAOA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAiBL,KAAK,WAAW,EAQjB,MAAM,kBAAkB,CAAC;AAU1B,OAAO,EAIL,KAAK,UAAU,EAKf,KAAK,IAAI,EACV,MAAM,UAAU,CAAC;AAClB,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACpC;AACD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AACD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAClE;AACD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE;QACV,QAAQ,EAAE,WAAW,CAAC;QACtB,YAAY,EAAE,eAAe,CAAC;QAC9B,SAAS,EAAE,qBAAqB,EAAE,CAAC;KACpC,CAAC;CACH;AAqTD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,aAAa,CAq8Hf"}
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.ts"],"names":[],"mappings":"AAOA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAkBL,KAAK,WAAW,EAQjB,MAAM,kBAAkB,CAAC;AAU1B,OAAO,EAIL,KAAK,UAAU,EAKf,KAAK,IAAI,EACV,MAAM,UAAU,CAAC;AAClB,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACpC;AACD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AACD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAClE;AACD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE;QACV,QAAQ,EAAE,WAAW,CAAC;QACtB,YAAY,EAAE,eAAe,CAAC;QAC9B,SAAS,EAAE,qBAAqB,EAAE,CAAC;KACpC,CAAC;CACH;AAmVD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,aAAa,CA6jIf"}
@@ -1,7 +1,7 @@
1
1
  import { CompileFailure, didYouMean, fail, failWithCode, } from "./diagnostics.js";
2
2
  import { hash as sha256 } from "fast-sha256";
3
3
  import { buildUdlCostManifest } from "./cost.js";
4
- import { validateUdl, resolveField, sameAccount, sameObjectField, subjectPartyRoles, RESERVED_OBJECT_NAMES, UDL_FORMAT_VERSION, udlObjectFieldSchema, udlInstrumentSchema, } from "@hyperscale0/udl";
4
+ import { validateUdl, resolveField, sameAccount, sameObjectField, subjectPartyRoles, RESERVED_OBJECT_NAMES, UDL_FORMAT_VERSION, udlObjectFieldSchema, udlFieldValueSchema, udlInstrumentSchema, } from "@hyperscale0/udl";
5
5
  import { bindingDependencies, parameterDiagnostics, BindingContractError, } from "./binding-contract.js";
6
6
  import { tunableBounds } from "./tunables.js";
7
7
  import { parseProgram } from "./parse.js";
@@ -135,6 +135,24 @@ const camel = (name) => name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
135
135
  const automatic = (actor) => actor === "clock" ||
136
136
  (actor !== null && typeof actor === "object" && "parent" in actor);
137
137
  const title = (name) => name[0].toUpperCase() + name.slice(1).replaceAll("_", " ");
138
+ /** Parties an instrument names itself: account owners, actors, move endpoints. */
139
+ function namedParties(instrument) {
140
+ const names = instrument.fields.flatMap((field) => field.type === "account" && typeof field.owner === "string"
141
+ ? [field.owner]
142
+ : []);
143
+ for (const action of Object.values(instrument.actions)) {
144
+ if (typeof action.actor === "object" && "party" in action.actor)
145
+ names.push(action.actor.party);
146
+ for (const move of action.moves) {
147
+ if (move.economics)
148
+ names.push(move.economics.sourceParty);
149
+ for (const endpoint of "from" in move ? [move.from, move.to] : [])
150
+ if (endpoint.startsWith("party."))
151
+ names.push(endpoint.slice(6));
152
+ }
153
+ }
154
+ return names;
155
+ }
138
156
  function entries(block) {
139
157
  const map = new Map();
140
158
  for (const row of block.entries) {
@@ -174,6 +192,8 @@ function text(expr) {
174
192
  return fail(expr, "expected a name or literal", "write a single name or literal");
175
193
  }
176
194
  function decimal(raw, scale, expr) {
195
+ if (raw.startsWith("-"))
196
+ fail(expr, "literal cannot be negative", "write a positive value; a move's from and to set its direction");
177
197
  const [whole = "", fraction = ""] = raw.split(".");
178
198
  if (!/^\d+$/.test(whole) || fraction.length > scale)
179
199
  fail(expr, `literal has more than ${scale} decimal places`, `write at most ${scale} decimal places`);
@@ -243,6 +263,8 @@ function literal(expr, expectedType) {
243
263
  return expr.value;
244
264
  if (expr.kind === "name" && ["true", "false"].includes(expr.value))
245
265
  return expr.value === "true";
266
+ if (expr.kind === "name" && expr.value === "runtime")
267
+ fail(expr, "this value is fixed when the program compiles, so it cannot be runtime", "write a literal value");
246
268
  return fail(expr, "expected a literal", "write a typed constant");
247
269
  }
248
270
  export function compile(source, options = {}) {
@@ -990,7 +1012,7 @@ export function compile(source, options = {}) {
990
1012
  if (bounds &&
991
1013
  (BigInt(String(value)) < BigInt(bounds.minimum) ||
992
1014
  BigInt(String(value)) > BigInt(bounds.maximum)))
993
- fail(v, `${param.key} is outside ${bounds.minimum}..${bounds.maximum}`, "choose a value inside the tunable's bounds");
1015
+ fail(v, `value is outside ${bounds.minimum}..${bounds.maximum}`, "choose a value inside the tunable's bounds");
994
1016
  }
995
1017
  catch (error) {
996
1018
  if (error instanceof CompileFailure)
@@ -1039,7 +1061,7 @@ export function compile(source, options = {}) {
1039
1061
  const left = environment.get(constraint.key), right = environment.get(text(rule.args[0]));
1040
1062
  if (!left || !right)
1041
1063
  fail(rule, "constraint needs two declared tunables", "name the compared tunables");
1042
- const numericKind = (expr) => expr.kind === "name" && /^P/.test(expr.value)
1064
+ const numericKind = (expr) => expr.kind === "name" && /^P(?:\d|T)/.test(expr.value)
1043
1065
  ? "duration"
1044
1066
  : expr.kind;
1045
1067
  if (!["money", "number", "percent", "duration"].includes(numericKind(left)) ||
@@ -1106,6 +1128,7 @@ export function compile(source, options = {}) {
1106
1128
  return lowered;
1107
1129
  };
1108
1130
  const fields = [];
1131
+ const boundaryReceipts = new Map();
1109
1132
  const calculations = [];
1110
1133
  let currentAction;
1111
1134
  let currentActionName;
@@ -1486,6 +1509,38 @@ export function compile(source, options = {}) {
1486
1509
  actions: Object.create(null),
1487
1510
  actionOrder: [],
1488
1511
  };
1512
+ const actionTargets = new Set();
1513
+ const collectTargets = (block) => {
1514
+ for (const entry of block.entries)
1515
+ if (entry.key.startsWith("when "))
1516
+ collectTargets(asBlock(entry.value));
1517
+ else if (entry.key === "calculate")
1518
+ for (const item of entry.value.kind === "list"
1519
+ ? entry.value.items
1520
+ : [entry.value]) {
1521
+ const target = item.kind === "block" ? entries(item).get("target") : undefined;
1522
+ if (target)
1523
+ actionTargets.add(String(data(target)));
1524
+ }
1525
+ };
1526
+ for (const row of decl.body.entries)
1527
+ if (row.key.startsWith("action "))
1528
+ collectTargets(asBlock(row.value));
1529
+ // Instrument calculations run before every action, create included, so
1530
+ // a fee or split calculates there only on an amount held from create.
1531
+ const heldFromCreate = (amount) => {
1532
+ if (!("field" in amount))
1533
+ return true;
1534
+ const [root, name, ...rest] = amount.field.split(".");
1535
+ const field = fields.find((item) => item.name === name);
1536
+ return (root === "self" &&
1537
+ !rest.length &&
1538
+ !!field &&
1539
+ !actionTargets.has(name) &&
1540
+ (!field.optional ||
1541
+ "value" in field ||
1542
+ calculations.some((node) => node.target === name)));
1543
+ };
1489
1544
  for (const row of decl.body.entries.filter((e) => e.key.startsWith("action "))) {
1490
1545
  const name = row.key.slice(7);
1491
1546
  const selected = (block) => ({
@@ -1744,7 +1799,7 @@ export function compile(source, options = {}) {
1744
1799
  ...(actionSubject ? { subject: actionSubject } : {}),
1745
1800
  };
1746
1801
  authored.set(a, row.value);
1747
- const actionFeeCalculations = [];
1802
+ const moveCalculations = [];
1748
1803
  for (const binding of boundaryBindings)
1749
1804
  if (!a.subject?.adapters.find((entry) => entry.binding === binding)
1750
1805
  ?.snapshot)
@@ -1775,8 +1830,20 @@ export function compile(source, options = {}) {
1775
1830
  const op = parts.has("operation")
1776
1831
  ? String(data(parts.get("operation")))
1777
1832
  : "internal_transfer.create";
1778
- if (op === "internal_transfer.reserve" && !parts.has("capture"))
1779
- fail(move, "reserve needs a receipt field", "add capture: receipt_name");
1833
+ if (op === "internal_transfer.reserve" && !parts.has("capture")) {
1834
+ if (!parts.has("boundary"))
1835
+ fail(move, "reserve needs a receipt field", "add capture: receipt_name");
1836
+ const capture = `${name}Receipt`;
1837
+ if (fields.some((field) => field.name === capture))
1838
+ fail(move, "boundary receipt name is already used", "name each reservation with an explicit capture");
1839
+ fields.push({ name: capture, type: "text", optional: true });
1840
+ boundaryReceipts.set(name, capture);
1841
+ parts.set("capture", {
1842
+ kind: "text",
1843
+ value: capture,
1844
+ span: move.span,
1845
+ });
1846
+ }
1780
1847
  if ((op === "internal_transfer.post" ||
1781
1848
  op === "internal_transfer.void") &&
1782
1849
  !parts.has("transfer"))
@@ -1890,6 +1957,7 @@ export function compile(source, options = {}) {
1890
1957
  fail(move, "split needs 1 to 64 recipients", "name the receiving parties and percentages");
1891
1958
  let total = 0;
1892
1959
  const pieces = [];
1960
+ const perAction = !heldFromCreate(val(amount));
1893
1961
  for (const [index, [party, rate]] of shares.entries()) {
1894
1962
  const recipient = resolve({
1895
1963
  kind: "name",
@@ -1920,8 +1988,12 @@ export function compile(source, options = {}) {
1920
1988
  const target = `${name}_${key}_share${index + 1}`;
1921
1989
  if (fields.some((f) => f.name === target))
1922
1990
  fail(move, "split field name conflicts", "rename the move key");
1923
- fields.push({ name: target, type: "money" });
1924
- calculations.push(index === shares.length - 1 && pieces.length
1991
+ fields.push({
1992
+ name: target,
1993
+ type: "money",
1994
+ ...(perAction ? { optional: true } : {}),
1995
+ });
1996
+ (perAction ? moveCalculations : calculations).push(index === shares.length - 1 && pieces.length
1925
1997
  ? {
1926
1998
  target,
1927
1999
  op: "subtract",
@@ -1993,14 +2065,18 @@ export function compile(source, options = {}) {
1993
2065
  if (terms.has("seller") === terms.has("buyer"))
1994
2066
  fail(fee, "fee needs exactly one paying party", "write seller: 1% or buyer: 1%");
1995
2067
  const quoted = terms.get(paidBy);
2068
+ // An account the agreement owns holds only what was moved
2069
+ // into it, so a charge on top has nothing to come from.
2070
+ if (paidBy === "buyer" &&
2071
+ fields.some((field) => `self.${field.name}` === transfer.from &&
2072
+ field.type === "account" &&
2073
+ field.owner === "self"))
2074
+ fail(quoted, `a buyer fee cannot come from \`${transfer.from.slice("self.".length)}\`, which holds only the moved amount`, "use a seller fee, which comes out of the held amount");
1996
2075
  const rate = quoted.kind === "capped" ? quoted.rate : quoted;
1997
2076
  if (rate.kind !== "percent")
1998
2077
  fail(rate, "fee rate needs a percentage", "write a percentage such as 1%");
1999
2078
  const prefix = `${name}_${key}`;
2000
- // Instrument calculations run at create, before any action
2001
- // input exists, so a fee on an input amount runs with its action.
2002
- const perAction = "field" in transfer.amount &&
2003
- transfer.amount.field.startsWith("input.");
2079
+ const perAction = !heldFromCreate(transfer.amount);
2004
2080
  const derived = (suffix, label, calculation) => {
2005
2081
  const target = prefix + "_" + suffix;
2006
2082
  if (fields.some((f) => f.name === target))
@@ -2011,7 +2087,7 @@ export function compile(source, options = {}) {
2011
2087
  label,
2012
2088
  ...(perAction ? { optional: true } : {}),
2013
2089
  });
2014
- (perAction ? actionFeeCalculations : calculations).push({
2090
+ (perAction ? moveCalculations : calculations).push({
2015
2091
  ...calculation,
2016
2092
  target,
2017
2093
  });
@@ -2039,6 +2115,27 @@ export function compile(source, options = {}) {
2039
2115
  const tax = terms.get("tax");
2040
2116
  if (tax && tax.kind !== "percent")
2041
2117
  fail(tax, "tax needs a percentage of the fee", "write tax: 15%");
2118
+ if (paidBy === "seller" &&
2119
+ (!("literal" in transfer.amount) ||
2120
+ udlFieldValueSchema({
2121
+ name: "amount",
2122
+ type: "money",
2123
+ }).safeParse(transfer.amount.literal).success)) {
2124
+ const bps = BigInt(Number(literal(rate)));
2125
+ const taxBps = BigInt(tax ? Number(literal(tax)) : 0);
2126
+ const cap = quoted.kind === "capped"
2127
+ ? BigInt(String(literal(quoted.cap)))
2128
+ : undefined;
2129
+ let exceeds = cap === undefined && bps * (10000n + taxBps) > 100000000n;
2130
+ if ("literal" in transfer.amount) {
2131
+ const amount = BigInt(String(transfer.amount.literal));
2132
+ const grossFee = (amount * bps) / 10000n;
2133
+ const fee = cap !== undefined && cap < grossFee ? cap : grossFee;
2134
+ exceeds = fee + (fee * taxBps) / 10000n > amount;
2135
+ }
2136
+ if (exceeds)
2137
+ failWithCode(quoted, "seller_fee_exceeds_amount", "seller fee plus tax exceeds the base amount", "lower the seller fee, cap or tax so the combined charge fits within the amount");
2138
+ }
2042
2139
  const vat = tax
2043
2140
  ? derived("tax", "Fee tax", {
2044
2141
  op: "rate",
@@ -2120,8 +2217,8 @@ export function compile(source, options = {}) {
2120
2217
  else
2121
2218
  a[key] = data(expr);
2122
2219
  }
2123
- if (actionFeeCalculations.length)
2124
- a.calculate = [...(a.calculate ?? []), ...actionFeeCalculations];
2220
+ if (moveCalculations.length)
2221
+ a.calculate = [...(a.calculate ?? []), ...moveCalculations];
2125
2222
  if (attachmentInfo) {
2126
2223
  if (attachmentInfo.exposed.has(name)) {
2127
2224
  if (automatic(a.actor))
@@ -2199,6 +2296,13 @@ export function compile(source, options = {}) {
2199
2296
  inst.actions[name] = a;
2200
2297
  inst.actionOrder.push(name);
2201
2298
  }
2299
+ for (const action of Object.values(inst.actions))
2300
+ for (const move of action.moves)
2301
+ if ("transfer" in move) {
2302
+ const receipt = boundaryReceipts.get(move.transfer.replace(/^self\./, ""));
2303
+ if (receipt)
2304
+ move.transfer = `self.${receipt}`;
2305
+ }
2202
2306
  if (attachmentInfo && !attachmentInfo.child)
2203
2307
  applyAttachmentEconomics(document, inst, attachmentInfo.parties, attachmentInfo.economics, data);
2204
2308
  for (const key of [
@@ -2403,6 +2507,17 @@ export function compile(source, options = {}) {
2403
2507
  bindingDiagnostics.push(error.diagnostic);
2404
2508
  continue;
2405
2509
  }
2510
+ // A business the instrument names itself binds like a parameter, so
2511
+ // Build asks for it and the engine resolves its account and principal.
2512
+ // programOperator and programTax are the company's own accounts.
2513
+ for (const party of document.instruments
2514
+ .slice(firstAttachedInstrument)
2515
+ .flatMap(namedParties))
2516
+ if (document.parties[party]?.kind === "business" &&
2517
+ party !== "programOperator" &&
2518
+ party !== "programTax" &&
2519
+ !Object.values(parties).some((binding) => "party" in binding && binding.party === party))
2520
+ parties[party] ??= { party };
2406
2521
  const attachedInst = document.instruments.find((i) => i.id === instId);
2407
2522
  for (const createdInst of document.instruments
2408
2523
  .slice(firstAttachedInstrument)