@ottochain/sdk 2.4.0 → 2.5.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.
Files changed (225) hide show
  1. package/dist/cjs/apps/compute/index.js +33 -0
  2. package/dist/cjs/apps/compute/state-machines/compute-rule110.js +164 -0
  3. package/dist/cjs/apps/compute/state-machines/index.js +9 -0
  4. package/dist/cjs/apps/contracts/index.js +1 -1
  5. package/dist/cjs/apps/contracts/state-machines/contract-agreement.js +126 -148
  6. package/dist/cjs/apps/contracts/state-machines/contract-escrow.js +153 -174
  7. package/dist/cjs/apps/contracts/state-machines/contract-universal.js +46 -58
  8. package/dist/cjs/apps/corporate/state-machines/corp-board.js +415 -481
  9. package/dist/cjs/apps/corporate/state-machines/corp-entity.js +368 -440
  10. package/dist/cjs/apps/corporate/state-machines/corp-securities.js +532 -647
  11. package/dist/cjs/apps/corporate/state-machines/corp-shareholders.js +499 -603
  12. package/dist/cjs/apps/governance/index.js +7 -7
  13. package/dist/cjs/apps/governance/state-machines/dao-multisig.js +170 -209
  14. package/dist/cjs/apps/governance/state-machines/dao-reputation.js +161 -214
  15. package/dist/cjs/apps/governance/state-machines/dao-single.js +92 -92
  16. package/dist/cjs/apps/governance/state-machines/dao-token.js +169 -223
  17. package/dist/cjs/apps/governance/state-machines/governance-simple.js +185 -217
  18. package/dist/cjs/apps/governance/state-machines/governance-universal.js +64 -64
  19. package/dist/cjs/apps/identity/constants.js +16 -24
  20. package/dist/cjs/apps/identity/index.js +1 -1
  21. package/dist/cjs/apps/identity/state-machines/identity-agent.js +124 -158
  22. package/dist/cjs/apps/identity/state-machines/identity-oracle.js +151 -169
  23. package/dist/cjs/apps/identity/state-machines/identity-registry.js +89 -115
  24. package/dist/cjs/apps/identity/state-machines/identity-universal.js +51 -57
  25. package/dist/cjs/apps/index.js +4 -1
  26. package/dist/cjs/apps/lending/assets.js +41 -17
  27. package/dist/cjs/apps/lending/credit-scoring.js +4 -4
  28. package/dist/cjs/apps/lending/eligibility.js +9 -25
  29. package/dist/cjs/apps/lending/index.js +1 -1
  30. package/dist/cjs/apps/lending/state-machines/lending-zk-loan.js +177 -156
  31. package/dist/cjs/apps/markets/index.js +1 -1
  32. package/dist/cjs/apps/markets/state-machines/market-auction.js +115 -134
  33. package/dist/cjs/apps/markets/state-machines/market-crowdfund.js +122 -137
  34. package/dist/cjs/apps/markets/state-machines/market-group-buy.js +158 -177
  35. package/dist/cjs/apps/markets/state-machines/market-prediction.js +193 -227
  36. package/dist/cjs/apps/markets/state-machines/market-universal.js +65 -80
  37. package/dist/cjs/apps/oracles/index.js +1 -1
  38. package/dist/cjs/apps/privacy/index.js +67 -0
  39. package/dist/cjs/apps/privacy/state-machines/index.js +17 -0
  40. package/dist/cjs/apps/privacy/state-machines/mixer-ddhRing.js +286 -0
  41. package/dist/cjs/apps/privacy/state-machines/note-pool.js +476 -0
  42. package/dist/cjs/apps/staked-pool/assets.js +103 -0
  43. package/dist/cjs/apps/staked-pool/base.js +498 -0
  44. package/dist/cjs/apps/staked-pool/consumer.js +55 -0
  45. package/dist/cjs/apps/staked-pool/index.js +83 -0
  46. package/dist/cjs/apps/staked-pool/state-machines/index.js +12 -0
  47. package/dist/cjs/apps/staked-pool/state-machines/staked-pool-base.js +49 -0
  48. package/dist/cjs/apps/staked-pool/state-machines/staked-pool-oracle.js +131 -0
  49. package/dist/cjs/generated/google/protobuf/struct.js +1 -1
  50. package/dist/cjs/generated/google/protobuf/timestamp.js +1 -1
  51. package/dist/cjs/generated/ottochain/apps/contracts/v1/contract.js +1 -1
  52. package/dist/cjs/generated/ottochain/apps/corporate/v1/corporate.js +1 -1
  53. package/dist/cjs/generated/ottochain/apps/governance/v1/governance.js +1 -1
  54. package/dist/cjs/generated/ottochain/apps/identity/v1/attestation.js +1 -1
  55. package/dist/cjs/generated/ottochain/apps/identity/v1/identity.js +1 -1
  56. package/dist/cjs/generated/ottochain/apps/markets/v1/market.js +1 -1
  57. package/dist/cjs/generated/ottochain/v1/common.js +1 -1
  58. package/dist/cjs/generated/ottochain/v1/fiber.js +40 -2
  59. package/dist/cjs/generated/ottochain/v1/messages.js +1 -1
  60. package/dist/cjs/generated/ottochain/v1/records.js +1 -1
  61. package/dist/cjs/index.js +20 -3
  62. package/dist/cjs/ottochain/genesis-manifest.js +6 -0
  63. package/dist/cjs/ottochain/metagraph-client.js +16 -8
  64. package/dist/cjs/ottochain/snapshot.js +2 -4
  65. package/dist/cjs/ottochain/transaction.js +10 -0
  66. package/dist/cjs/privacy/sealed-bid.js +85 -82
  67. package/dist/cjs/privacy/shield-app.js +32 -32
  68. package/dist/cjs/schema/effects.js +117 -1
  69. package/dist/cjs/schema/fiber-app.js +132 -3
  70. package/dist/cjs/schema/guard-lint.js +3 -12
  71. package/dist/cjs/schema/guards.js +38 -38
  72. package/dist/cjs/templates/asset-policy.js +190 -0
  73. package/dist/cjs/templates/index.js +86 -0
  74. package/dist/cjs/templates/machine.js +130 -0
  75. package/dist/cjs/templates/migration.js +39 -0
  76. package/dist/cjs/templates/state-shape.js +38 -0
  77. package/dist/cjs/validation.js +1 -3
  78. package/dist/esm/apps/compute/index.js +29 -0
  79. package/dist/esm/apps/compute/state-machines/compute-rule110.js +161 -0
  80. package/dist/esm/apps/compute/state-machines/index.js +5 -0
  81. package/dist/esm/apps/contracts/index.js +4 -4
  82. package/dist/esm/apps/contracts/state-machines/contract-agreement.js +128 -150
  83. package/dist/esm/apps/contracts/state-machines/contract-escrow.js +155 -176
  84. package/dist/esm/apps/contracts/state-machines/contract-universal.js +47 -59
  85. package/dist/esm/apps/corporate/index.js +2 -2
  86. package/dist/esm/apps/corporate/state-machines/corp-board.js +418 -484
  87. package/dist/esm/apps/corporate/state-machines/corp-entity.js +371 -443
  88. package/dist/esm/apps/corporate/state-machines/corp-securities.js +535 -650
  89. package/dist/esm/apps/corporate/state-machines/corp-shareholders.js +502 -606
  90. package/dist/esm/apps/governance/index.js +10 -10
  91. package/dist/esm/apps/governance/state-machines/dao-multisig.js +172 -211
  92. package/dist/esm/apps/governance/state-machines/dao-reputation.js +163 -216
  93. package/dist/esm/apps/governance/state-machines/dao-single.js +94 -94
  94. package/dist/esm/apps/governance/state-machines/dao-token.js +171 -225
  95. package/dist/esm/apps/governance/state-machines/governance-simple.js +187 -219
  96. package/dist/esm/apps/governance/state-machines/governance-universal.js +65 -65
  97. package/dist/esm/apps/identity/constants.js +18 -26
  98. package/dist/esm/apps/identity/index.js +8 -8
  99. package/dist/esm/apps/identity/state-machines/identity-agent.js +125 -159
  100. package/dist/esm/apps/identity/state-machines/identity-oracle.js +153 -171
  101. package/dist/esm/apps/identity/state-machines/identity-registry.js +91 -117
  102. package/dist/esm/apps/identity/state-machines/identity-universal.js +52 -58
  103. package/dist/esm/apps/index.js +9 -6
  104. package/dist/esm/apps/lending/assets.js +41 -17
  105. package/dist/esm/apps/lending/credit-scoring.js +6 -6
  106. package/dist/esm/apps/lending/eligibility.js +10 -26
  107. package/dist/esm/apps/lending/index.js +5 -5
  108. package/dist/esm/apps/lending/state-machines/lending-zk-loan.js +178 -157
  109. package/dist/esm/apps/markets/index.js +5 -5
  110. package/dist/esm/apps/markets/state-machines/market-auction.js +117 -136
  111. package/dist/esm/apps/markets/state-machines/market-crowdfund.js +124 -139
  112. package/dist/esm/apps/markets/state-machines/market-group-buy.js +160 -179
  113. package/dist/esm/apps/markets/state-machines/market-prediction.js +195 -229
  114. package/dist/esm/apps/markets/state-machines/market-universal.js +66 -81
  115. package/dist/esm/apps/oracles/index.js +3 -3
  116. package/dist/esm/apps/privacy/index.js +56 -0
  117. package/dist/esm/apps/privacy/state-machines/index.js +6 -0
  118. package/dist/esm/apps/privacy/state-machines/mixer-ddhRing.js +282 -0
  119. package/dist/esm/apps/privacy/state-machines/note-pool.js +470 -0
  120. package/dist/esm/apps/staked-pool/assets.js +91 -0
  121. package/dist/esm/apps/staked-pool/base.js +493 -0
  122. package/dist/esm/apps/staked-pool/consumer.js +49 -0
  123. package/dist/esm/apps/staked-pool/index.js +60 -0
  124. package/dist/esm/apps/staked-pool/state-machines/index.js +6 -0
  125. package/dist/esm/apps/staked-pool/state-machines/staked-pool-base.js +46 -0
  126. package/dist/esm/apps/staked-pool/state-machines/staked-pool-oracle.js +127 -0
  127. package/dist/esm/generated/google/protobuf/struct.js +1 -1
  128. package/dist/esm/generated/google/protobuf/timestamp.js +1 -1
  129. package/dist/esm/generated/ottochain/apps/contracts/v1/contract.js +1 -1
  130. package/dist/esm/generated/ottochain/apps/corporate/v1/corporate.js +1 -1
  131. package/dist/esm/generated/ottochain/apps/governance/v1/governance.js +1 -1
  132. package/dist/esm/generated/ottochain/apps/identity/v1/attestation.js +1 -1
  133. package/dist/esm/generated/ottochain/apps/identity/v1/identity.js +1 -1
  134. package/dist/esm/generated/ottochain/apps/markets/v1/market.js +1 -1
  135. package/dist/esm/generated/ottochain/v1/common.js +1 -1
  136. package/dist/esm/generated/ottochain/v1/fiber.js +40 -2
  137. package/dist/esm/generated/ottochain/v1/messages.js +1 -1
  138. package/dist/esm/generated/ottochain/v1/records.js +1 -1
  139. package/dist/esm/index.js +10 -4
  140. package/dist/esm/ottochain/genesis-manifest.js +6 -0
  141. package/dist/esm/ottochain/metagraph-client.js +16 -8
  142. package/dist/esm/ottochain/snapshot.js +2 -4
  143. package/dist/esm/ottochain/transaction.js +8 -0
  144. package/dist/esm/privacy/index.js +2 -2
  145. package/dist/esm/privacy/sealed-bid.js +87 -84
  146. package/dist/esm/privacy/shield-app.js +33 -33
  147. package/dist/esm/schema/effects.js +110 -0
  148. package/dist/esm/schema/fiber-app.js +127 -3
  149. package/dist/esm/schema/guard-lint.js +3 -12
  150. package/dist/esm/schema/guards.js +36 -37
  151. package/dist/esm/templates/asset-policy.js +182 -0
  152. package/dist/esm/templates/index.js +41 -0
  153. package/dist/esm/templates/machine.js +125 -0
  154. package/dist/esm/templates/migration.js +34 -0
  155. package/dist/esm/templates/state-shape.js +34 -0
  156. package/dist/esm/validation.js +1 -3
  157. package/dist/esm/zk/index.js +4 -4
  158. package/dist/types/apps/compute/index.d.ts +176 -0
  159. package/dist/types/apps/compute/state-machines/compute-rule110.d.ts +157 -0
  160. package/dist/types/apps/compute/state-machines/index.d.ts +5 -0
  161. package/dist/types/apps/contracts/index.d.ts +14 -14
  162. package/dist/types/apps/contracts/state-machines/contract-agreement.d.ts +3 -3
  163. package/dist/types/apps/contracts/state-machines/contract-escrow.d.ts +3 -3
  164. package/dist/types/apps/contracts/state-machines/contract-universal.d.ts +4 -4
  165. package/dist/types/apps/corporate/index.d.ts +162 -162
  166. package/dist/types/apps/corporate/state-machines/corp-board.d.ts +88 -88
  167. package/dist/types/apps/corporate/state-machines/corp-entity.d.ts +14 -14
  168. package/dist/types/apps/corporate/state-machines/corp-securities.d.ts +18 -18
  169. package/dist/types/apps/corporate/state-machines/corp-shareholders.d.ts +43 -43
  170. package/dist/types/apps/governance/index.d.ts +81 -81
  171. package/dist/types/apps/governance/state-machines/dao-multisig.d.ts +16 -16
  172. package/dist/types/apps/governance/state-machines/dao-reputation.d.ts +22 -22
  173. package/dist/types/apps/governance/state-machines/dao-token.d.ts +21 -21
  174. package/dist/types/apps/governance/state-machines/governance-simple.d.ts +11 -11
  175. package/dist/types/apps/governance/state-machines/governance-universal.d.ts +4 -4
  176. package/dist/types/apps/identity/constants.d.ts +2 -2
  177. package/dist/types/apps/identity/index.d.ts +70 -70
  178. package/dist/types/apps/identity/state-machines/identity-agent.d.ts +23 -23
  179. package/dist/types/apps/identity/state-machines/identity-oracle.d.ts +11 -11
  180. package/dist/types/apps/identity/state-machines/identity-registry.d.ts +27 -27
  181. package/dist/types/apps/identity/state-machines/identity-universal.d.ts +4 -4
  182. package/dist/types/apps/index.d.ts +9 -6
  183. package/dist/types/apps/lending/assets.d.ts +2 -2
  184. package/dist/types/apps/lending/credit-scoring.d.ts +5 -5
  185. package/dist/types/apps/lending/eligibility.d.ts +2 -2
  186. package/dist/types/apps/lending/index.d.ts +20 -12
  187. package/dist/types/apps/lending/state-machines/lending-zk-loan.d.ts +15 -7
  188. package/dist/types/apps/markets/index.d.ts +76 -76
  189. package/dist/types/apps/markets/state-machines/market-auction.d.ts +12 -12
  190. package/dist/types/apps/markets/state-machines/market-crowdfund.d.ts +19 -19
  191. package/dist/types/apps/markets/state-machines/market-group-buy.d.ts +18 -18
  192. package/dist/types/apps/markets/state-machines/market-prediction.d.ts +15 -15
  193. package/dist/types/apps/markets/state-machines/market-universal.d.ts +7 -7
  194. package/dist/types/apps/oracles/index.d.ts +1 -1
  195. package/dist/types/apps/privacy/index.d.ts +328 -0
  196. package/dist/types/apps/privacy/state-machines/index.d.ts +6 -0
  197. package/dist/types/apps/privacy/state-machines/mixer-ddhRing.d.ts +337 -0
  198. package/dist/types/apps/privacy/state-machines/note-pool.d.ts +196 -0
  199. package/dist/types/apps/staked-pool/assets.d.ts +53 -0
  200. package/dist/types/apps/staked-pool/base.d.ts +80 -0
  201. package/dist/types/apps/staked-pool/consumer.d.ts +29 -0
  202. package/dist/types/apps/staked-pool/index.d.ts +51 -0
  203. package/dist/types/apps/staked-pool/state-machines/index.d.ts +6 -0
  204. package/dist/types/apps/staked-pool/state-machines/staked-pool-base.d.ts +8 -0
  205. package/dist/types/apps/staked-pool/state-machines/staked-pool-oracle.d.ts +34 -0
  206. package/dist/types/generated/openapi.d.ts +13 -0
  207. package/dist/types/generated/ottochain/v1/fiber.d.ts +7 -0
  208. package/dist/types/index.d.ts +3 -3
  209. package/dist/types/openapi.d.ts +4 -2
  210. package/dist/types/ottochain/genesis-manifest.d.ts +6 -0
  211. package/dist/types/ottochain/transaction.d.ts +9 -1
  212. package/dist/types/ottochain/types.d.ts +1 -1
  213. package/dist/types/privacy/index.d.ts +2 -2
  214. package/dist/types/privacy/sealed-bid.d.ts +9 -9
  215. package/dist/types/privacy/shield-app.d.ts +1 -1
  216. package/dist/types/schema/effects.d.ts +107 -0
  217. package/dist/types/schema/fiber-app.d.ts +281 -0
  218. package/dist/types/schema/guards.d.ts +12 -0
  219. package/dist/types/templates/asset-policy.d.ts +119 -0
  220. package/dist/types/templates/index.d.ts +26 -0
  221. package/dist/types/templates/machine.d.ts +139 -0
  222. package/dist/types/templates/migration.d.ts +34 -0
  223. package/dist/types/templates/state-shape.d.ts +30 -0
  224. package/dist/types/zk/index.d.ts +4 -4
  225. package/package.json +31 -13
@@ -0,0 +1,470 @@
1
+ /**
2
+ * shielded-note-pool — a Tornado-style fixed-denomination UTXO-private asset pool.
3
+ *
4
+ * One fiber instance is one shielded pool. The pool's PUBLIC state is the privacy
5
+ * scaffolding only — a Poseidon-Merkle commitment ROOT window, a spent-NULLIFIER set,
6
+ * an append-only output-COMMITMENT log, and the asset-record UUIDs the pool custodies.
7
+ * The real value model is a UTXO note layer that lives OFF-chain in notes committed to
8
+ * the tree; each spend is attested by a single Groth16 proof of the `zk-shielded` circuit.
9
+ *
10
+ * Division of labour (the Kachina/Midnight split — a general verifier in the VM, the
11
+ * shielded pool as ordinary state + transitions on top of it; see
12
+ * docs/design/ergo-patterns-as-fiber-primitives.md §"shielded-note-pool" and
13
+ * docs/design/shielded-transfer-app-sketch.md):
14
+ * - the CIRCUIT proves membership (Poseidon-Merkle inclusion) ∧ authorization
15
+ * (`owner == Poseidon([nsk])`) ∧ nullifier derivation (`nf == Poseidon([rho, nsk])`)
16
+ * ∧ intra-transfer nullifier uniqueness ∧ per-asset conservation ∧ u64 range;
17
+ * - this GUARD (combine) verifies the proof against the pinned `vkey`, binds the
18
+ * public inputs by slicing them from the VERIFIED `publicValues` bytes, rejects an
19
+ * already-spent nullifier (`none`), checks the spend's anchor is still honored
20
+ * (`in [anchor, knownRoots]`), and pins the fee word to a configured value;
21
+ * - the EFFECT (combine) spends the nullifier, logs the output commitment, advances
22
+ * the rolling anchor window, and (on `unshield`) releases exactly one whole
23
+ * `denom`-valued asset record to a clear recipient.
24
+ *
25
+ * It uses NO new metakit opcode. `groth16_verify` verifies the proof; the public-input
26
+ * words are extracted from the verified `publicValues` string with fixed-offset `substr`
27
+ * and re-prefixed `0x` (`{cat:['0x', {substr:[pv,off,64]}]}`) — slicing the verified
28
+ * bytes IS the binding, so no field can be mauled independently of the proof.
29
+ *
30
+ * ──────────────────────────────────────────────────────────────────────────────────────
31
+ * ⚠ UNAUDITED — TEST ASSETS ONLY. The `zk-shielded` circuit and its Groth16 verifier are
32
+ * UNAUDITED; BN254 is ~100-bit security. This MVP is test-assets-only and pins
33
+ * N=1 input / M=1 output / zero fee, with a SINGLE TRUSTED RELAYER for root advancement.
34
+ * Do NOT deploy with value-bearing assets until the circuit + verifier are audited and the
35
+ * in-circuit `newRoot` (which removes the trusted relayer) has landed. See §"Scope" / the
36
+ * Risks list in the design doc.
37
+ * ──────────────────────────────────────────────────────────────────────────────────────
38
+ *
39
+ * MVP constraints (CONFIRMED DEFAULTS):
40
+ * - test assets only;
41
+ * - single trusted relayer gates root advancement (`event.newRoot` is NOT in the proven
42
+ * public values, so a non-relayer could otherwise flood `knownRoots` and evict honest
43
+ * in-flight anchors — a real DoS). LATER: prove `newRoot` in-circuit and drop the relayer.
44
+ * - fixed denomination; the deposit mint is transparent (Tornado-style), the amount is
45
+ * hidden among same-`denom` notes (anonymity set = all live `denom` notes).
46
+ *
47
+ * On-chain primitive facts this builder relies on (re-verified against
48
+ * `@constellation-network/metagraph-sdk-jlvm@1.8.0-rc.5`, see tests/note-pool.test.ts):
49
+ * - array APPEND is `merge([arr,[item]])` (flatten one level); `cat` on arrays ERRORS
50
+ * ("Unexpected input for `cat`") — never use `cat` for array append on-chain;
51
+ * - every crypto/compare value is `0x`-prefixed; an extracted word MUST be re-prefixed
52
+ * `{cat:['0x', {substr:[pv,off,64]}]}` before comparing with a stored `0x` hash;
53
+ * - `none [arr, {===:[{var:""}, x]}]` fires correctly (fresh→true, spent→false);
54
+ * - `groth16_verify` on a garbage bundle returns `false` (graceful deny, never throws);
55
+ * - `pmt_verify([root, leaf, index, [siblings]])` is Poseidon-Merkle inclusion with
56
+ * ROOT-FIRST siblings and a leaf-position `index` (depth range-checked) — exported as
57
+ * {@link pmtMembership} for off-chain witness building / a membership-gated variant.
58
+ */
59
+ import { defineFiberApp, } from '../../../schema/fiber-app.js';
60
+ import { signerIsParty } from '../../../schema/guards.js';
61
+ import { transferAsset, toWallet } from '../../../schema/effects.js';
62
+ // =============================================================================
63
+ // Public-input layout the guard expects (for fixture / circuit generation)
64
+ // =============================================================================
65
+ /**
66
+ * The EXACT `publicValues` layout the guard slices, for the N=1 input / M=1 output MVP.
67
+ *
68
+ * `publicValues` is the abi-encoded `ShieldedTransferPublicValues` carried as a single
69
+ * `0x`-prefixed lowercase-hex string. Each 32-byte ABI word is 64 hex chars; word `w`
70
+ * begins at hex offset `2 + 64*w` (the leading `2` skips `0x`).
71
+ *
72
+ * `ShieldedTransferPublicValues { bytes32 anchor; bytes32[] nullifiers; bytes32[] outputCms;
73
+ * uint64 fee; bytes32 feeAsset; }`. The circuit commits this via `alloy_sol_types::abi_encode`,
74
+ * which — the struct being a DYNAMIC tuple (it holds the two dynamic arrays) — emits a single leading
75
+ * `0x20` tuple-offset HEAD WORD before the struct body, then the body's own head of array offsets +
76
+ * a tail of (len, elems). For FIXED N=1/M=1 the tail is static and `substr`-addressable.
77
+ *
78
+ * ┌── field ─────────────┬── hex offset ─┬── width ─┬── notes ─────────────────────────────────┐
79
+ * │ tuple head (0x20) │ 2 │ 64 │ abi_encode dynamic-tuple offset; NOT a field│
80
+ * │ anchor │ 66 │ 64 │ Poseidon-Merkle root the inputs prove under │
81
+ * │ nullifiers off (0xa0)│ 130 │ 64 │ body-relative offset of the nullifiers array│
82
+ * │ outputCms off (0xe0)│ 194 │ 64 │ body-relative offset of the outputCms array │
83
+ * │ fee (uint64 word) │ 258 │ 64 │ transparent fee, right-aligned in the word │
84
+ * │ feeAsset │ 322 │ 64 │ asset-as-Fr the fee is denominated in │
85
+ * │ nullifiers.len │ 386 │ 64 │ == 1 for the N=1 MVP │
86
+ * │ nullifiers[0] │ 450 │ 64 │ the revealed input nullifier │
87
+ * │ outputCms.len │ 514 │ 64 │ == 1 for the M=1 MVP │
88
+ * │ outputCms[0] │ 578 │ 64 │ the revealed output commitment │
89
+ * └──────────────────────┴───────────────┴──────────┴─────────────────────────────────────────────┘
90
+ *
91
+ * The word ORDER and ENCODING a matching SP1 fixture MUST produce (so the placeholder can be
92
+ * swapped for a real proof): the `0x`-string is
93
+ * 0x ‖ 0x20 ‖ anchor ‖ nullifiers.off ‖ outputCms.off ‖ fee ‖ feeAsset ‖
94
+ * nullifiers.len ‖ nullifiers[0] ‖ outputCms.len ‖ outputCms[0]
95
+ * laid out by the circuit's `commit_slice(abi_encode(ShieldedTransferPublicValues{..}))`, such that
96
+ * the five field offsets below land on the words above. Every word is a lowercase 32-byte hex with
97
+ * NO `0x` per-word prefix (the single `0x` is on the whole string).
98
+ *
99
+ * RECONCILED 2026-06 against the REAL `zk-shielded` circuit (~/repos/metakit-sdk/rust/zk-shielded):
100
+ * an earlier version of this layout omitted the leading `0x20` dynamic-tuple head word, so every
101
+ * offset was one 32-byte word (64 hex) too low and the guard sliced the WRONG words. The circuit is
102
+ * the source of truth; the offsets above were corrected (+64 each) to match a real SP1 Groth16 proof
103
+ * whose `publicValues` is `0x ‖ 0x20 ‖ anchor ‖ 0xa0 ‖ 0xe0 ‖ fee ‖ feeAsset ‖ 1 ‖ nf ‖ 1 ‖ cm`.
104
+ *
105
+ * cm / nf preimages (the circuit's, fixed and byte-for-byte across Scala/Rust/TS):
106
+ * - `cm = Poseidon([value_as_fr, owner, asset, rho])` (4-input, MAX poseidon arity);
107
+ * - `owner = Poseidon([nsk])`;
108
+ * - `nf = Poseidon([rho, nsk])` (FIELD ORDER rho, nsk).
109
+ */
110
+ export const PV_LAYOUT = {
111
+ /** anchor — word 1 (word 0 is the dynamic-tuple `0x20` head word; see the table). */
112
+ anchor: 66,
113
+ /** fee — word 4 (uint64 right-aligned). MVP pins this whole word to {@link NotePoolOptions.feeWord}. */
114
+ fee: 258,
115
+ /** feeAsset — word 5. */
116
+ feeAsset: 322,
117
+ /** nullifiers[0] — after the nullifiers length word @386. */
118
+ nullifier: 450,
119
+ /** outputCms[0] — after the outputCms length word @514. */
120
+ outputCm: 578,
121
+ };
122
+ // =============================================================================
123
+ // Field extraction — slice the VERIFIED bytes, re-prefix 0x (the binding)
124
+ // =============================================================================
125
+ const WORD_HEX = 64; // a 32-byte ABI word as hex chars
126
+ /**
127
+ * Lift `publicValues` word at hex `offset` back to a `0x`-hex value comparable with a stored
128
+ * `bytes32`/`hash`. `cat` of two STRINGS is valid on-chain; `cat` of arrays ERRORS — this is
129
+ * strings only. The slice is taken from the SAME bytes `groth16_verify` consumed, so a changed
130
+ * field invalidates the proof: re-prefixing the verified slice IS the public-input binding.
131
+ */
132
+ export const pvField = (offset) => ({
133
+ cat: ['0x', { substr: [{ var: 'event.publicValues' }, offset, WORD_HEX] }],
134
+ });
135
+ /**
136
+ * `pmt_verify([root, leaf, index, [siblings]])` Poseidon-Merkle inclusion (root-first siblings,
137
+ * leaf-position `index`, depth = `siblings.length`, range-checked). Exported for off-chain witness
138
+ * building and for a membership-gated pool variant. NOTE: the MVP `transfer`/`unshield` guards do
139
+ * NOT call `pmt_verify` — membership is proven INSIDE the circuit (`verify_inclusion`) and bound to
140
+ * the verified `anchor`; the chain only checks `anchor ∈ knownRoots`. This helper exists so a
141
+ * downstream pool that wants an additional ON-CHAIN inclusion check (e.g. for a transparent leaf)
142
+ * can express it with the verified operand format.
143
+ */
144
+ export const pmtMembership = (rootVar, leafVar, indexVar, siblingsVar) => ({ pmt_verify: [rootVar, leafVar, indexVar, siblingsVar] });
145
+ /** The all-zero 32-byte word — the MVP `feeWord` (pins fee=0). */
146
+ export const ZERO_WORD = '0x' + '0'.repeat(64);
147
+ /** Public state every shielded note-pool carries (the note value model itself is private/off-chain). */
148
+ export const NOTE_POOL_STATE = {
149
+ vkey: {
150
+ type: 'hash',
151
+ immutable: true,
152
+ description: 'zk-shielded program vkey (32B, 0x)',
153
+ },
154
+ depth: {
155
+ type: 'integer',
156
+ immutable: true,
157
+ description: 'merkle depth pinned at creation',
158
+ },
159
+ denom: {
160
+ type: 'integer',
161
+ immutable: true,
162
+ description: 'fixed note denomination (asset units)',
163
+ },
164
+ poolPolicyRef: {
165
+ type: 'string',
166
+ immutable: true,
167
+ description: 'asset policy the pool mints/burns note-records under',
168
+ },
169
+ feeAsset: {
170
+ type: 'hash',
171
+ immutable: true,
172
+ description: '0x Fr label fees are charged in (== asset-as-Fr)',
173
+ },
174
+ feeWord: {
175
+ type: 'hash',
176
+ immutable: true,
177
+ description: 'the EXACT 0x 32-byte word `fee` must equal (MVP: zero word)',
178
+ },
179
+ relayer: {
180
+ type: 'string',
181
+ immutable: true,
182
+ description: 'DAG address authorized to advance the root (MVP single trusted relayer)',
183
+ },
184
+ rootWindow: { type: 'integer', immutable: true, default: 64 },
185
+ currentRoot: { type: 'hash', computed: true },
186
+ knownRoots: {
187
+ type: 'array',
188
+ computed: true,
189
+ description: 'rolling window of valid anchors (0x)',
190
+ },
191
+ nullifiers: {
192
+ type: 'array',
193
+ computed: true,
194
+ description: 'spent set (0x); monotonic — O(n) `none` scan, see ceiling note',
195
+ },
196
+ commitments: {
197
+ type: 'array',
198
+ computed: true,
199
+ description: 'append-only output-commitment log (0x)',
200
+ },
201
+ noteRecords: {
202
+ type: 'array',
203
+ computed: true,
204
+ description: 'assetId UUIDs of live note-records the pool holds',
205
+ },
206
+ leafCount: { type: 'integer', computed: true, default: 0 },
207
+ transfers: { type: 'integer', computed: true, default: 0 },
208
+ };
209
+ // =============================================================================
210
+ // Guard / effect builders
211
+ // =============================================================================
212
+ /** Extracted, 0x-re-prefixed public-input words (shared by `transfer` and `unshield`). */
213
+ const pvWords = () => ({
214
+ anchor: pvField(PV_LAYOUT.anchor),
215
+ feeWord: pvField(PV_LAYOUT.fee),
216
+ feeAsset: pvField(PV_LAYOUT.feeAsset),
217
+ nullifier: pvField(PV_LAYOUT.nullifier),
218
+ newCm: pvField(PV_LAYOUT.outputCm),
219
+ });
220
+ /**
221
+ * The proof + nullifier + anchor + fee binding shared by every spend (combiner-only, all
222
+ * stateful). Binds against the EXACT verified bytes: any field change invalidates the Groth16.
223
+ */
224
+ const spendBinding = (w) => [
225
+ // 1. the proof verifies against the pool's pinned vkey over EXACTLY these public bytes
226
+ {
227
+ groth16_verify: [{ var: 'state.vkey' }, { var: 'event.publicValues' }, { var: 'event.proof' }],
228
+ },
229
+ // 2. the spend's anchor is a root we produced and still honor
230
+ { in: [w.anchor, { var: 'state.knownRoots' }] },
231
+ // 3. INTER-transfer double-spend: the (0x-prefixed) nullifier is not already spent
232
+ {
233
+ none: [{ var: 'state.nullifiers' }, { '===': [{ var: '' }, w.nullifier] }],
234
+ },
235
+ // 4. no fee-asset spoofing
236
+ { '===': [w.feeAsset, { var: 'state.feeAsset' }] },
237
+ // 5. no value siphon via fee: the whole fee word is pinned (MVP: the zero word ⇒ fee==0)
238
+ { '===': [w.feeWord, { var: 'state.feeWord' }] },
239
+ ];
240
+ /**
241
+ * Build the shielded-note-pool fiber app.
242
+ *
243
+ * Single state `ACTIVE → ACTIVE` with three transitions:
244
+ * - `transfer` — note-to-note spend (no public asset move); advances the tree. Relayer-gated.
245
+ * - `noteMinted`— witness transition recording a deposit's commitment + record UUID (the deposit
246
+ * itself is an asset-model `MintAsset` into `Fiber(poolId)`, gated by the
247
+ * `poolPolicyRef` mintPolicy — NOT a fiber transition).
248
+ * - `unshield` — burn a note (nullify), release one whole `denom` note-record to a clear recipient.
249
+ * Self-sufficient because it is CASCADE-REACHABLE (asset transfers fire from cascades).
250
+ */
251
+ export function notePoolDef(opts) {
252
+ const feeWord = opts.feeWord ?? ZERO_WORD;
253
+ const w = pvWords();
254
+ // --- transfer: note-to-note spend, advance the rolling anchor window ---------------------
255
+ const transferGuard = {
256
+ and: [
257
+ ...spendBinding(w),
258
+ // Root-advance authz: only the pinned relayer's signed `transfer` advances the tree, so a
259
+ // non-relayer cannot flood `knownRoots` and evict honest in-flight anchors (DoS). Uses
260
+ // `proofs[].address` (verified signers) — NOT a forgeable `event.*` field.
261
+ signerIsParty('state.relayer'),
262
+ ],
263
+ };
264
+ const transferEffect = {
265
+ merge: [
266
+ { var: 'state' },
267
+ {
268
+ // append via merge (NOT cat — cat errors on arrays on-chain)
269
+ nullifiers: { merge: [{ var: 'state.nullifiers' }, [w.nullifier]] },
270
+ commitments: { merge: [{ var: 'state.commitments' }, [w.newCm]] },
271
+ currentRoot: { var: 'event.newRoot' },
272
+ // append newRoot, then trim to the last rootWindow anchors via negative-start slice
273
+ knownRoots: {
274
+ slice: [
275
+ {
276
+ merge: [{ var: 'state.knownRoots' }, [{ var: 'event.newRoot' }]],
277
+ },
278
+ { '*': [-1, { var: 'state.rootWindow' }] },
279
+ ],
280
+ },
281
+ leafCount: { '+': [{ var: 'state.leafCount' }, 1] },
282
+ transfers: { '+': [{ var: 'state.transfers' }, 1] },
283
+ },
284
+ ],
285
+ };
286
+ // --- noteMinted: witness a deposit (record its commitment + record UUID) ------------------
287
+ // The deposit is an asset-model MintAsset of a `denom`-valued record into Fiber(poolId), gated by
288
+ // the poolPolicyRef mintPolicy (pins amount==denom, holder==pool, depositor-signed). This witness
289
+ // transition only records bookkeeping; `unshield` independently re-checks record custody at
290
+ // withdrawal (the asset combiner's R1 holder defense), so a forged noteMinted cannot extract value.
291
+ const noteMintedGuard = {
292
+ // the depositor (who signed the mint) signs this witness; the commitment + recordId are theirs.
293
+ // No balance read (no guard can witness an escrow). The release-time custody check is the real gate.
294
+ and: [signerIsParty('event.depositor')],
295
+ };
296
+ const noteMintedEffect = {
297
+ merge: [
298
+ { var: 'state' },
299
+ {
300
+ commitments: {
301
+ merge: [{ var: 'state.commitments' }, [{ var: 'event.commitment' }]],
302
+ },
303
+ noteRecords: {
304
+ merge: [{ var: 'state.noteRecords' }, [{ var: 'event.recordId' }]],
305
+ },
306
+ leafCount: { '+': [{ var: 'state.leafCount' }, 1] },
307
+ },
308
+ ],
309
+ };
310
+ // --- unshield: burn a note, release exactly one whole note-record ------------------------
311
+ const unshieldGuard = {
312
+ and: [
313
+ ...spendBinding(w),
314
+ // can only release a record the pool actually holds (combiner R1 holder defense re-checks too)
315
+ { in: [{ var: 'event.recordId' }, { var: 'state.noteRecords' }] },
316
+ ],
317
+ };
318
+ const unshieldEffect = {
319
+ merge: [
320
+ { var: 'state' },
321
+ {
322
+ nullifiers: { merge: [{ var: 'state.nullifiers' }, [w.nullifier]] },
323
+ // remove the released record id from the live set
324
+ noteRecords: {
325
+ filter: [{ var: 'state.noteRecords' }, { '!==': [{ var: '' }, { var: 'event.recordId' }] }],
326
+ },
327
+ transfers: { '+': [{ var: 'state.transfers' }, 1] },
328
+ // release exactly one whole denom-valued record to a clear recipient wallet
329
+ ...transferAsset([
330
+ {
331
+ assetId: { var: 'event.recordId' },
332
+ recipient: toWallet({ var: 'event.recipient' }),
333
+ },
334
+ ]),
335
+ },
336
+ ],
337
+ };
338
+ return defineFiberApp({
339
+ metadata: {
340
+ name: 'ShieldedNotePool',
341
+ app: 'privacy',
342
+ type: 'shielded-note-pool',
343
+ version: '0.1.0',
344
+ description: 'UNAUDITED, TEST-ASSETS-ONLY fixed-denomination UTXO-private asset pool over the zk-shielded ' +
345
+ 'Groth16 circuit (BN254 ~100-bit). Notes live off-chain in a Poseidon-Merkle tree; spends are ' +
346
+ 'attested by a Groth16 proof. Public state = nullifier set + anchor window + commitment log. ' +
347
+ 'Single trusted relayer advances the root (MVP). Do NOT deploy with value-bearing assets.',
348
+ },
349
+ createSchema: {
350
+ required: ['vkey', 'depth', 'denom', 'poolPolicyRef', 'feeAsset', 'relayer'],
351
+ properties: {
352
+ vkey: { type: 'hash', immutable: true, default: opts.vkey },
353
+ depth: { type: 'integer', immutable: true, default: opts.depth },
354
+ denom: { type: 'integer', immutable: true, default: opts.denom },
355
+ poolPolicyRef: {
356
+ type: 'string',
357
+ immutable: true,
358
+ default: opts.poolPolicyRef,
359
+ },
360
+ feeAsset: { type: 'hash', immutable: true, default: opts.feeAsset },
361
+ feeWord: { type: 'hash', immutable: true, default: feeWord },
362
+ relayer: { type: 'string', immutable: true, default: opts.relayer },
363
+ rootWindow: {
364
+ type: 'integer',
365
+ immutable: true,
366
+ default: opts.rootWindow ?? 64,
367
+ },
368
+ },
369
+ },
370
+ stateSchema: { properties: { ...NOTE_POOL_STATE } },
371
+ eventSchemas: {
372
+ transfer: {
373
+ description: 'Note-to-note shielded spend (N=1/M=1), attested by a zk-shielded Groth16 proof; advances the tree.',
374
+ required: ['proof', 'publicValues', 'newRoot'],
375
+ properties: {
376
+ proof: {
377
+ type: 'string',
378
+ description: 'Groth16 proof bytes (0x hex)',
379
+ },
380
+ publicValues: {
381
+ type: 'string',
382
+ description: 'abi-encoded ShieldedTransferPublicValues (0x hex); see PV_LAYOUT',
383
+ },
384
+ newRoot: {
385
+ type: 'hash',
386
+ description: 'wallet/relayer-computed root after appending outputCms[0]',
387
+ },
388
+ },
389
+ },
390
+ noteMinted: {
391
+ description: "Witness a deposit: record its commitment + the minted note-record's UUID.",
392
+ required: ['commitment', 'recordId', 'depositor'],
393
+ properties: {
394
+ commitment: {
395
+ type: 'hash',
396
+ description: 'client-computed cm = Poseidon([denom, owner, feeAsset, rho]) (0x)',
397
+ },
398
+ recordId: {
399
+ type: 'uuid',
400
+ description: 'assetId of the denom-valued record minted into Fiber(poolId)',
401
+ },
402
+ depositor: {
403
+ type: 'address',
404
+ description: 'DAG address of the depositor who signed the mint',
405
+ },
406
+ },
407
+ },
408
+ unshield: {
409
+ description: 'Burn a note, release one whole denom-valued note-record to a clear recipient.',
410
+ required: ['proof', 'publicValues', 'recordId', 'recipient'],
411
+ properties: {
412
+ proof: {
413
+ type: 'string',
414
+ description: 'Groth16 proof bytes (0x hex)',
415
+ },
416
+ publicValues: {
417
+ type: 'string',
418
+ description: 'abi-encoded ShieldedTransferPublicValues (0x hex); see PV_LAYOUT',
419
+ },
420
+ recordId: {
421
+ type: 'uuid',
422
+ description: 'note-record to release; MUST be in state.noteRecords',
423
+ },
424
+ recipient: {
425
+ type: 'address',
426
+ description: 'clear DAG address that receives the released record',
427
+ },
428
+ },
429
+ },
430
+ },
431
+ states: {
432
+ ACTIVE: {
433
+ id: 'ACTIVE',
434
+ isFinal: false,
435
+ metadata: {
436
+ label: 'Active',
437
+ description: 'Pool accepts shielded transfers, deposits, and withdrawals',
438
+ category: 'active',
439
+ },
440
+ },
441
+ },
442
+ initialState: 'ACTIVE',
443
+ transitions: [
444
+ {
445
+ from: 'ACTIVE',
446
+ to: 'ACTIVE',
447
+ eventName: 'transfer',
448
+ guard: transferGuard,
449
+ effect: transferEffect,
450
+ dependencies: [],
451
+ },
452
+ {
453
+ from: 'ACTIVE',
454
+ to: 'ACTIVE',
455
+ eventName: 'noteMinted',
456
+ guard: noteMintedGuard,
457
+ effect: noteMintedEffect,
458
+ dependencies: [],
459
+ },
460
+ {
461
+ from: 'ACTIVE',
462
+ to: 'ACTIVE',
463
+ eventName: 'unshield',
464
+ guard: unshieldGuard,
465
+ effect: unshieldEffect,
466
+ dependencies: [],
467
+ },
468
+ ],
469
+ });
470
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Asset-subsystem integration for the staked-pool family.
3
+ *
4
+ * Two asset roles:
5
+ * - STAKE token: a governed fungible. Joining is a custody `Transfer` of the whole stake instance into
6
+ * `AssetHolder.Fiber(poolId)` (the loan family's "lock = Transfer into Fiber holder" pattern); the
7
+ * pool's `stake_and_join` guard verifies the custody via `heldAssets` (H5). Withdraw/slash move it back
8
+ * out via the fiber's `_transferAsset` directive.
9
+ * - REWARD token: a shared fungible the pool pre-holds. `claim_reward` moves ONE whole reward instance to
10
+ * a verified in-consensus claimant (per-claim model — the whole-asset transfer has no amount field).
11
+ *
12
+ * Reuses the lending family's asset payload builders (`createAssetPolicyPayload`, `createMintAssetPayload`,
13
+ * `createApplyMorphismPayload`, `fiberHolder`, `walletHolder`) — re-exported here so a staked-pool caller
14
+ * has a single import surface.
15
+ */
16
+ import { createAssetPolicyPayload, createMintAssetPayload, createApplyMorphismPayload, fiberHolder, walletHolder, TokenBehaviors, } from '../lending/assets.js';
17
+ export { createAssetPolicyPayload, createMintAssetPayload, createApplyMorphismPayload, fiberHolder, walletHolder, TokenBehaviors, };
18
+ /**
19
+ * Stake-token policy: a governed fungible. `Transfer` is the custody move into/out of the pool fiber.
20
+ * Transferable so withdraw/slash can return it (a soulbound stake would fail the R1 transferable check
21
+ * on withdraw — see design §11; not the default here).
22
+ */
23
+ export function stakePolicy(name = 'staked-pool-stake-v1.asset', version = '1.0.0') {
24
+ return {
25
+ name,
26
+ version,
27
+ behavior: TokenBehaviors.GovernedFungible,
28
+ supply: {
29
+ maxSupply: null,
30
+ mintPolicy: null,
31
+ // Only the holder may burn their own stake (defensive; the pool moves it via Transfer, not Burn).
32
+ burnPolicy: { in: [{ var: 'holder.Wallet.address' }, { var: 'signers' }] },
33
+ decimals: 0,
34
+ },
35
+ morphisms: {
36
+ // Custody Transfer is the join/withdraw mechanism; gated so the pool fiber authorizes movement.
37
+ Transfer: { visibility: 'Public' },
38
+ Burn: { visibility: 'Public' },
39
+ },
40
+ stateShape: {},
41
+ metadata: { family: 'staked-pool', role: 'stake' },
42
+ };
43
+ }
44
+ /**
45
+ * Reward-pot policy: a shared fungible the pool pre-holds and pays out one whole instance per claim.
46
+ * `mintGuard` (optional) gates who may mint reward instances into the pool (e.g. the pool authority);
47
+ * omit for an open mint pinned operationally.
48
+ */
49
+ export function rewardPotPolicy(mintGuard = null, name = 'staked-pool-reward-v1.asset', version = '1.0.0') {
50
+ return {
51
+ name,
52
+ version,
53
+ behavior: TokenBehaviors.Fungible,
54
+ supply: {
55
+ maxSupply: null,
56
+ mintPolicy: mintGuard,
57
+ burnPolicy: null,
58
+ decimals: 0,
59
+ },
60
+ morphisms: {
61
+ Transfer: { visibility: 'Public' },
62
+ },
63
+ stateShape: {},
64
+ metadata: { family: 'staked-pool', role: 'reward' },
65
+ };
66
+ }
67
+ /**
68
+ * stake_and_join driver: the participant Transfers their whole stake instance into the pool fiber BEFORE
69
+ * the `stake_and_join` event (custody = Transfer to `AssetHolder.Fiber`). Mirrors `lockCollateralOp`.
70
+ */
71
+ export function stakeJoinOp(args) {
72
+ return createApplyMorphismPayload({
73
+ assetId: args.stakeAssetId,
74
+ kind: 'Transfer',
75
+ recipient: fiberHolder(args.poolFiberId),
76
+ targetSequenceNumber: args.targetSequenceNumber,
77
+ });
78
+ }
79
+ /**
80
+ * Pre-mint reward instances into the pool fiber (sized to expected claimants for the epoch). Mirrors
81
+ * `mintPrincipalOp` but mints to `Fiber(poolId)` so `claim_reward` can later transfer one whole instance.
82
+ */
83
+ export function mintRewardInstancesOp(args) {
84
+ return createMintAssetPayload({
85
+ assetId: args.rewardAssetId,
86
+ policyRef: args.rewardPolicyRef,
87
+ holder: fiberHolder(args.poolFiberId),
88
+ amount: args.amount,
89
+ ...(args.witness ? { witness: args.witness } : {}),
90
+ });
91
+ }