@crediolabs/policy-synth 0.2.0 → 0.3.1

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 (249) hide show
  1. package/README.md +4 -4
  2. package/dist/adapters/interpreter/adapter.d.ts +19 -25
  3. package/dist/adapters/interpreter/adapter.js +54 -487
  4. package/dist/errors.d.ts +1 -1
  5. package/dist/index.d.ts +1 -4
  6. package/dist/index.js +1 -4
  7. package/dist/install/build-add-context-rule.d.ts +3 -19
  8. package/dist/install/build-add-context-rule.js +3 -98
  9. package/dist/install/build-install-policy.d.ts +4 -53
  10. package/dist/install/build-install-policy.js +44 -123
  11. package/dist/install/index.d.ts +0 -2
  12. package/dist/install/index.js +0 -7
  13. package/dist/predicate/decode.d.ts +1 -1
  14. package/dist/predicate/decode.js +2 -70
  15. package/dist/predicate/encode.js +45 -229
  16. package/dist/predicate/from-json.js +1 -42
  17. package/dist/record/decode.js +3 -8
  18. package/dist/record/freshness.d.ts +17 -11
  19. package/dist/record/freshness.js +28 -18
  20. package/dist/review-card/builder.d.ts +4 -3
  21. package/dist/review-card/builder.js +18 -162
  22. package/dist/review-card/cross-check.js +2 -105
  23. package/dist/review-card/render-leaf.d.ts +4 -0
  24. package/dist/review-card/render-leaf.js +47 -0
  25. package/dist/run/index.d.ts +36 -139
  26. package/dist/run/index.js +125 -437
  27. package/dist/run/schemas.d.ts +168 -1929
  28. package/dist/run/schemas.js +40 -269
  29. package/dist/{synth → simulate}/deny-cases.d.ts +4 -8
  30. package/dist/simulate/deny-cases.js +282 -0
  31. package/dist/simulate/evaluate.d.ts +18 -0
  32. package/dist/simulate/evaluate.js +271 -0
  33. package/dist/simulate/index.d.ts +4 -0
  34. package/dist/simulate/index.js +8 -0
  35. package/dist/synth/compose-from-recording.d.ts +24 -51
  36. package/dist/synth/compose-from-recording.js +120 -282
  37. package/dist/synth/index.d.ts +0 -5
  38. package/dist/synth/index.js +0 -5
  39. package/dist/synth/synthesize-from-recording.d.ts +1 -29
  40. package/dist/synth/synthesize-from-recording.js +41 -356
  41. package/dist/types.d.ts +8 -60
  42. package/dist/types.js +5 -2
  43. package/dist-cjs/adapters/interpreter/adapter.d.ts +19 -25
  44. package/dist-cjs/adapters/interpreter/adapter.js +56 -489
  45. package/dist-cjs/errors.d.ts +1 -1
  46. package/dist-cjs/index.d.ts +1 -4
  47. package/dist-cjs/index.js +1 -4
  48. package/dist-cjs/install/build-add-context-rule.d.ts +3 -19
  49. package/dist-cjs/install/build-add-context-rule.js +1 -97
  50. package/dist-cjs/install/build-install-policy.d.ts +4 -53
  51. package/dist-cjs/install/build-install-policy.js +44 -122
  52. package/dist-cjs/install/index.d.ts +0 -2
  53. package/dist-cjs/install/index.js +2 -23
  54. package/dist-cjs/predicate/decode.d.ts +1 -1
  55. package/dist-cjs/predicate/decode.js +2 -70
  56. package/dist-cjs/predicate/encode.js +45 -229
  57. package/dist-cjs/predicate/from-json.js +1 -42
  58. package/dist-cjs/record/decode.js +3 -8
  59. package/dist-cjs/record/freshness.d.ts +17 -11
  60. package/dist-cjs/record/freshness.js +28 -18
  61. package/dist-cjs/review-card/builder.d.ts +4 -3
  62. package/dist-cjs/review-card/builder.js +23 -167
  63. package/dist-cjs/review-card/cross-check.js +7 -110
  64. package/dist-cjs/review-card/render-leaf.d.ts +4 -0
  65. package/dist-cjs/review-card/render-leaf.js +52 -0
  66. package/dist-cjs/run/index.d.ts +36 -139
  67. package/dist-cjs/run/index.js +125 -444
  68. package/dist-cjs/run/schemas.d.ts +168 -1929
  69. package/dist-cjs/run/schemas.js +41 -270
  70. package/dist-cjs/{synth → simulate}/deny-cases.d.ts +4 -8
  71. package/dist-cjs/simulate/deny-cases.js +286 -0
  72. package/dist-cjs/simulate/evaluate.d.ts +18 -0
  73. package/dist-cjs/simulate/evaluate.js +274 -0
  74. package/dist-cjs/simulate/index.d.ts +4 -0
  75. package/dist-cjs/simulate/index.js +13 -0
  76. package/dist-cjs/synth/compose-from-recording.d.ts +24 -51
  77. package/dist-cjs/synth/compose-from-recording.js +120 -282
  78. package/dist-cjs/synth/index.d.ts +0 -5
  79. package/dist-cjs/synth/index.js +1 -11
  80. package/dist-cjs/synth/synthesize-from-recording.d.ts +1 -29
  81. package/dist-cjs/synth/synthesize-from-recording.js +39 -354
  82. package/dist-cjs/types.d.ts +8 -60
  83. package/dist-cjs/types.js +6 -3
  84. package/package.json +5 -5
  85. package/src/adapters/interpreter/adapter.ts +76 -572
  86. package/src/errors.ts +0 -19
  87. package/src/index.ts +1 -4
  88. package/src/install/build-add-context-rule.ts +5 -139
  89. package/src/install/build-install-policy.ts +93 -214
  90. package/src/install/index.ts +0 -34
  91. package/src/predicate/decode.ts +2 -70
  92. package/src/predicate/encode.ts +49 -261
  93. package/src/predicate/from-json.ts +1 -43
  94. package/src/record/decode.ts +3 -8
  95. package/src/record/freshness.ts +29 -18
  96. package/src/review-card/builder.ts +19 -168
  97. package/src/review-card/cross-check.ts +3 -105
  98. package/src/review-card/render-leaf.ts +48 -0
  99. package/src/run/index.ts +144 -572
  100. package/src/run/schemas.ts +42 -291
  101. package/src/simulate/deny-cases.ts +334 -0
  102. package/src/simulate/evaluate.ts +284 -0
  103. package/src/simulate/index.ts +11 -0
  104. package/src/synth/compose-from-recording.ts +148 -347
  105. package/src/synth/index.ts +0 -13
  106. package/src/synth/synthesize-from-recording.ts +43 -456
  107. package/src/types.ts +13 -49
  108. package/dist/adapters/interpreter/index.d.ts +0 -1
  109. package/dist/adapters/interpreter/index.js +0 -2
  110. package/dist/adapters/oz/adapter.d.ts +0 -20
  111. package/dist/adapters/oz/adapter.js +0 -295
  112. package/dist/adapters/oz/index.d.ts +0 -1
  113. package/dist/adapters/oz/index.js +0 -2
  114. package/dist/codegen/compile-gate.d.ts +0 -33
  115. package/dist/codegen/compile-gate.js +0 -124
  116. package/dist/codegen/index.d.ts +0 -2
  117. package/dist/codegen/index.js +0 -8
  118. package/dist/codegen/template.d.ts +0 -18
  119. package/dist/codegen/template.js +0 -148
  120. package/dist/install/authority-overlap.d.ts +0 -134
  121. package/dist/install/authority-overlap.js +0 -0
  122. package/dist/install/build-install-predicate.d.ts +0 -96
  123. package/dist/install/build-install-predicate.js +0 -444
  124. package/dist/install/build-merge-policy.d.ts +0 -70
  125. package/dist/install/build-merge-policy.js +0 -130
  126. package/dist/install/plan-merge-policy.d.ts +0 -49
  127. package/dist/install/plan-merge-policy.js +0 -86
  128. package/dist/install/read-account-rules.d.ts +0 -100
  129. package/dist/install/read-account-rules.js +0 -283
  130. package/dist/ir/index.d.ts +0 -1
  131. package/dist/ir/index.js +0 -2
  132. package/dist/ir/types.d.ts +0 -140
  133. package/dist/ir/types.js +0 -11
  134. package/dist/mandate/index.d.ts +0 -2
  135. package/dist/mandate/index.js +0 -2
  136. package/dist/mandate/to-ir.d.ts +0 -3
  137. package/dist/mandate/to-ir.js +0 -60
  138. package/dist/mandate/types.d.ts +0 -20
  139. package/dist/mandate/types.js +0 -8
  140. package/dist/seams/index.d.ts +0 -1
  141. package/dist/seams/index.js +0 -2
  142. package/dist/seams/types.d.ts +0 -66
  143. package/dist/seams/types.js +0 -11
  144. package/dist/synth/deny-cases.js +0 -562
  145. package/dist/synth/evaluate.d.ts +0 -39
  146. package/dist/synth/evaluate.js +0 -551
  147. package/dist/synth/harness.d.ts +0 -28
  148. package/dist/synth/harness.js +0 -47
  149. package/dist/synth/minimize.d.ts +0 -4
  150. package/dist/synth/minimize.js +0 -38
  151. package/dist/synth/permit-context.d.ts +0 -15
  152. package/dist/synth/permit-context.js +0 -116
  153. package/dist/synth/predicate-literals.d.ts +0 -5
  154. package/dist/synth/predicate-literals.js +0 -25
  155. package/dist/synth/synthesize-from-mandate.d.ts +0 -20
  156. package/dist/synth/synthesize-from-mandate.js +0 -59
  157. package/dist/verify/envelope.d.ts +0 -15
  158. package/dist/verify/envelope.js +0 -22
  159. package/dist/verify/index.d.ts +0 -3
  160. package/dist/verify/index.js +0 -3
  161. package/dist/verify/simulate.d.ts +0 -31
  162. package/dist/verify/simulate.js +0 -258
  163. package/dist/verify/verify.d.ts +0 -21
  164. package/dist/verify/verify.js +0 -189
  165. package/dist-cjs/adapters/interpreter/index.d.ts +0 -1
  166. package/dist-cjs/adapters/interpreter/index.js +0 -8
  167. package/dist-cjs/adapters/oz/adapter.d.ts +0 -20
  168. package/dist-cjs/adapters/oz/adapter.js +0 -300
  169. package/dist-cjs/adapters/oz/index.d.ts +0 -1
  170. package/dist-cjs/adapters/oz/index.js +0 -8
  171. package/dist-cjs/codegen/compile-gate.d.ts +0 -33
  172. package/dist-cjs/codegen/compile-gate.js +0 -128
  173. package/dist-cjs/codegen/index.d.ts +0 -2
  174. package/dist-cjs/codegen/index.js +0 -14
  175. package/dist-cjs/codegen/template.d.ts +0 -18
  176. package/dist-cjs/codegen/template.js +0 -151
  177. package/dist-cjs/install/authority-overlap.d.ts +0 -134
  178. package/dist-cjs/install/authority-overlap.js +0 -0
  179. package/dist-cjs/install/build-install-predicate.d.ts +0 -96
  180. package/dist-cjs/install/build-install-predicate.js +0 -479
  181. package/dist-cjs/install/build-merge-policy.d.ts +0 -70
  182. package/dist-cjs/install/build-merge-policy.js +0 -134
  183. package/dist-cjs/install/plan-merge-policy.d.ts +0 -49
  184. package/dist-cjs/install/plan-merge-policy.js +0 -90
  185. package/dist-cjs/install/read-account-rules.d.ts +0 -100
  186. package/dist-cjs/install/read-account-rules.js +0 -296
  187. package/dist-cjs/ir/index.d.ts +0 -1
  188. package/dist-cjs/ir/index.js +0 -3
  189. package/dist-cjs/ir/types.d.ts +0 -140
  190. package/dist-cjs/ir/types.js +0 -12
  191. package/dist-cjs/mandate/index.d.ts +0 -2
  192. package/dist-cjs/mandate/index.js +0 -6
  193. package/dist-cjs/mandate/to-ir.d.ts +0 -3
  194. package/dist-cjs/mandate/to-ir.js +0 -63
  195. package/dist-cjs/mandate/types.d.ts +0 -20
  196. package/dist-cjs/mandate/types.js +0 -9
  197. package/dist-cjs/seams/index.d.ts +0 -1
  198. package/dist-cjs/seams/index.js +0 -3
  199. package/dist-cjs/seams/types.d.ts +0 -66
  200. package/dist-cjs/seams/types.js +0 -12
  201. package/dist-cjs/synth/deny-cases.js +0 -568
  202. package/dist-cjs/synth/evaluate.d.ts +0 -39
  203. package/dist-cjs/synth/evaluate.js +0 -554
  204. package/dist-cjs/synth/harness.d.ts +0 -28
  205. package/dist-cjs/synth/harness.js +0 -50
  206. package/dist-cjs/synth/minimize.d.ts +0 -4
  207. package/dist-cjs/synth/minimize.js +0 -41
  208. package/dist-cjs/synth/permit-context.d.ts +0 -15
  209. package/dist-cjs/synth/permit-context.js +0 -119
  210. package/dist-cjs/synth/predicate-literals.d.ts +0 -5
  211. package/dist-cjs/synth/predicate-literals.js +0 -28
  212. package/dist-cjs/synth/synthesize-from-mandate.d.ts +0 -20
  213. package/dist-cjs/synth/synthesize-from-mandate.js +0 -62
  214. package/dist-cjs/verify/envelope.d.ts +0 -15
  215. package/dist-cjs/verify/envelope.js +0 -23
  216. package/dist-cjs/verify/index.d.ts +0 -3
  217. package/dist-cjs/verify/index.js +0 -8
  218. package/dist-cjs/verify/simulate.d.ts +0 -31
  219. package/dist-cjs/verify/simulate.js +0 -261
  220. package/dist-cjs/verify/verify.d.ts +0 -21
  221. package/dist-cjs/verify/verify.js +0 -192
  222. package/src/adapters/interpreter/index.ts +0 -8
  223. package/src/adapters/oz/adapter.ts +0 -376
  224. package/src/adapters/oz/index.ts +0 -9
  225. package/src/codegen/compile-gate.ts +0 -167
  226. package/src/codegen/index.ts +0 -17
  227. package/src/codegen/template.ts +0 -165
  228. package/src/install/authority-overlap.ts +0 -0
  229. package/src/install/build-merge-policy.ts +0 -219
  230. package/src/install/plan-merge-policy.ts +0 -133
  231. package/src/install/read-account-rules.ts +0 -376
  232. package/src/ir/index.ts +0 -13
  233. package/src/ir/types.ts +0 -132
  234. package/src/mandate/index.ts +0 -4
  235. package/src/mandate/to-ir.ts +0 -71
  236. package/src/mandate/types.ts +0 -21
  237. package/src/seams/index.ts +0 -11
  238. package/src/seams/types.ts +0 -81
  239. package/src/synth/deny-cases.ts +0 -663
  240. package/src/synth/evaluate.ts +0 -613
  241. package/src/synth/harness.ts +0 -68
  242. package/src/synth/minimize.ts +0 -48
  243. package/src/synth/permit-context.ts +0 -136
  244. package/src/synth/predicate-literals.ts +0 -27
  245. package/src/synth/synthesize-from-mandate.ts +0 -82
  246. package/src/verify/envelope.ts +0 -28
  247. package/src/verify/index.ts +0 -5
  248. package/src/verify/simulate.ts +0 -311
  249. package/src/verify/verify.ts +0 -243
@@ -19,6 +19,7 @@
19
19
 
20
20
  import { createHash } from 'node:crypto'
21
21
  import {
22
+ Account,
22
23
  Address,
23
24
  BASE_FEE,
24
25
  Contract,
@@ -30,7 +31,8 @@ import {
30
31
  TransactionBuilder,
31
32
  xdr,
32
33
  } from '@stellar/stellar-sdk'
33
- import { buildAddContextRuleArgs, DEFAULT_GRAMMAR_VERSION } from './build-add-context-rule.ts'
34
+ import type { ContextRuleDraft } from '../types.ts'
35
+ import { buildAddContextRuleArgs } from './build-add-context-rule.ts'
34
36
  import {
35
37
  accountEntry,
36
38
  authDigest,
@@ -106,20 +108,14 @@ export interface BuildInstallPolicyArgs {
106
108
  sourceAccount: string
107
109
  /** Network passphrase - pins the hash the auth digest binds. */
108
110
  networkPassphrase: string
109
- /** The new rule to install. Mirrors the core `ContextRuleDraft`. */
110
- rule: BuildInstallPolicyRuleDraft
111
+ /** The new rule to install. */
112
+ rule: ContextRuleDraft
111
113
  /** Per-rule install nonce; 1 for a fresh install. */
112
114
  installNonce: number
113
115
  /** Already-encoded (base64) canonical ScVal of the predicate. */
114
116
  encodedPredicate: string
115
117
  /** Hex sha256 of the canonical predicate XDR bytes. */
116
118
  predicateHash: string
117
- /** Per-policy oracle overrides. */
118
- oracleParams?: {
119
- maxStalenessSeconds?: number
120
- maxDeviationBps?: number
121
- maxCrossFeedDeviationBps?: number
122
- }
123
119
  /** Grammar version override; defaults to 1. */
124
120
  grammarVersion?: number
125
121
  /** Base fee in stroops; defaults to BASE_FEE (100). */
@@ -130,36 +126,6 @@ export interface BuildInstallPolicyArgs {
130
126
  authValidUntilLedgers?: number
131
127
  }
132
128
 
133
- export type BuildInstallPolicyRuleDraft = {
134
- contextRuleType:
135
- | { kind: 'default' }
136
- | { kind: 'call_contract'; contract: string }
137
- | { kind: 'create_contract'; wasmHash: string }
138
- name: string
139
- validUntilLedger: number | null
140
- signers: BuildInstallPolicySignerDraft[]
141
- policies: BuildInstallPolicyPolicyRef[]
142
- }
143
-
144
- export type BuildInstallPolicySignerDraft =
145
- | { kind: 'delegated'; address: string }
146
- | { kind: 'external'; verifier: string; keyBytes: string }
147
-
148
- export type BuildInstallPolicyPolicyRef =
149
- | {
150
- kind: 'interpreter'
151
- interpreterAddress: string
152
- predicateBlobBase64: string
153
- }
154
- | {
155
- kind: 'oz_builtin'
156
- instanceAddress: string
157
- primitive: {
158
- primitive: 'spending_limit' | 'simple_threshold' | 'weighted_threshold'
159
- params: Record<string, unknown>
160
- }
161
- }
162
-
163
129
  /** Human-readable description of the install call, decoded FROM the built
164
130
  * XDR (not from the input args). The XDR is the source of truth: a
165
131
  * human approving a review card needs the description to mirror the bytes
@@ -193,20 +159,13 @@ export interface InstallCallDescribes {
193
159
  * sha256 of the predicate blob actually embedded in the XDR - so a
194
160
  * mismatch between the wire bytes and the review card is detectable
195
161
  * by reading `describes`. */
196
- policies: Array<
197
- | {
198
- kind: 'interpreter'
199
- address: string
200
- installNonce: number
201
- predicateHash: string
202
- predicateSha256OfEmbeddedBytes: string
203
- }
204
- | {
205
- kind: 'oz_builtin'
206
- address: string
207
- primitive: 'spending_limit' | 'simple_threshold' | 'weighted_threshold'
208
- }
209
- >
162
+ policies: Array<{
163
+ kind: 'interpreter'
164
+ address: string
165
+ installNonce: number
166
+ predicateHash: string
167
+ predicateSha256OfEmbeddedBytes: string
168
+ }>
210
169
  /** The install nonce, decoded from the interpreter policy's
211
170
  * `install_nonce` field. Echoed at the top level for reviewer convenience;
212
171
  * the per-policy entry is the source of truth. */
@@ -261,102 +220,30 @@ export async function buildInstallPolicyXdr(
261
220
  name: args.rule.name,
262
221
  validUntilLedger: args.rule.validUntilLedger,
263
222
  signers: args.rule.signers,
264
- policies: args.rule.policies.map(adaptPolicyRef),
223
+ policies: args.rule.policies,
265
224
  },
266
225
  {
267
226
  signers: args.rule.signers,
268
- policies: args.rule.policies.map(adaptPolicyRef),
227
+ policies: args.rule.policies,
269
228
  installNonce: args.installNonce,
270
229
  encodedPredicate: args.encodedPredicate,
271
230
  predicateHash: args.predicateHash,
272
- ...(args.oracleParams ? { oracleParams: args.oracleParams } : {}),
273
231
  ...(args.grammarVersion !== undefined ? { grammarVersion: args.grammarVersion } : {}),
274
232
  }
275
233
  )
276
234
 
277
- // 2. Fetch the source sequence, then build the recording transaction with a
278
- // bare host call. The recording pass assigns the smart-account auth nonce
279
- // and root invocation needed to construct OZ's AuthPayload.
280
- const source = await args.rpc.getAccount(args.sourceAccount)
281
- const hostFunction = xdr.HostFunction.hostFunctionTypeInvokeContract(
282
- new xdr.InvokeContractArgs({
283
- contractAddress: new Address(args.smartAccount).toScAddress(),
284
- functionName: 'add_context_rule',
285
- args: [...callArgs],
286
- })
287
- )
288
- const makeOperation = (auth: xdr.SorobanAuthorizationEntry[] = []) =>
289
- Operation.invokeHostFunction({ func: hostFunction, auth })
290
- const baseFee = args.baseFee !== undefined ? String(args.baseFee) : BASE_FEE
291
- const buildTx = (op: xdr.Operation) =>
292
- buildUnsignedTx({
293
- sourceAccount: args.sourceAccount,
294
- sequence: source.sequenceNumber(),
295
- fee: baseFee,
296
- networkPassphrase: args.networkPassphrase,
297
- op,
298
- })
299
- const recordingTx = buildTx(makeOperation())
300
-
301
- // 3. Recording simulation: capture the smart account's nonce and invocation
302
- // tree. Nothing is signed here.
303
- const recorded = await args.rpc.simulateTransaction(recordingTx)
304
- if (rpc.Api.isSimulationError(recorded)) {
305
- // Short, stable reason. The full `simulateTransaction` error (which
306
- // carries host + URL detail) stays in the SDK's own logs - never
307
- // reflected back into a user-facing message where it would
308
- // reconnoitre the RPC.
309
- throw new Error('install_policy: simulateTransaction failed')
310
- }
311
- const original = (recorded.result?.auth ?? []).find(
312
- (entry) =>
313
- entry.credentials().switch().name === 'sorobanCredentialsAddress' &&
314
- Address.fromScAddress(entry.credentials().address().address()).toString() ===
315
- args.smartAccount
316
- )
317
- if (!original) {
318
- throw new Error(
319
- `install_policy: no Soroban auth entry for smart account ${args.smartAccount}; this call does not route through the smart account`
320
- )
321
- }
322
-
323
- // 4. `add_context_rule` is authorised by the deploy-time admin rule (rule 0).
235
+ // 2. `add_context_rule` is authorised by the deploy-time admin rule (rule 0).
324
236
  // The delegated signer uses source-account credentials, so its signature
325
237
  // bytes stay empty and the ordinary transaction-envelope signature covers
326
238
  // the call when the consumer signs it.
327
- const validUntilLedger =
328
- (await args.rpc.getLatestLedger()).sequence +
329
- (args.authValidUntilLedgers ?? DEFAULT_AUTH_VALID_UNTIL_LEDGERS)
330
- const contextRuleIds = [0]
331
- const digest = authDigest(
332
- signaturePayload(
333
- args.networkPassphrase,
334
- original.credentials().address().nonce(),
335
- validUntilLedger,
336
- original.rootInvocation()
337
- ),
338
- contextRuleIds
239
+ const { finalTx, original, validUntilLedger } = await buildAuthorisedSmartAccountTx(
240
+ args,
241
+ 'add_context_rule',
242
+ [...callArgs],
243
+ 'install_policy'
339
244
  )
340
- const authEntries = [
341
- accountEntry(
342
- original,
343
- validUntilLedger,
344
- authPayload([args.sourceAccount], contextRuleIds, () => Buffer.alloc(0))
345
- ),
346
- ...contextRuleIds.map(() => delegatedSignerEntry(args.smartAccount, digest)),
347
- ]
348
245
 
349
- // 5. Simulate again with the OZ entries already attached. The SDK preserves
350
- // existing auth during assembly and adds the simulated Soroban footprint +
351
- // resource fee, yielding a complete unsigned envelope.
352
- const txWithAuth = buildTx(makeOperation(authEntries))
353
- const enforcing = await args.rpc.simulateTransaction(txWithAuth)
354
- if (rpc.Api.isSimulationError(enforcing)) {
355
- throw new Error('install_policy: auth simulateTransaction failed')
356
- }
357
- const finalTx = rpc.assembleTransaction(txWithAuth, enforcing).build()
358
-
359
- // 6. Decode the structured description FROM the final assembled transaction.
246
+ // 3. Decode the structured description FROM the final assembled transaction.
360
247
  // The human approval binds to the exact bytes the wallet will sign.
361
248
  const describes = decodeInstallCallDescribes(finalTx, args.installNonce)
362
249
 
@@ -392,12 +279,75 @@ export async function buildRevokePolicyXdr(args: {
392
279
  baseFee?: number
393
280
  authValidUntilLedgers?: number
394
281
  }): Promise<BuildRevokePolicyResult> {
282
+ // Removal is master-only, so the deploy-time admin rule authorises it. The
283
+ // recorded rootInvocation still includes remove_context_rule(ruleId), binding
284
+ // the auth payload to the exact rule being removed. As with install,
285
+ // source-account credentials carry the delegated signer entry and the
286
+ // consumer supplies only the ordinary envelope signature.
287
+ const { finalTx, original, validUntilLedger } = await buildAuthorisedSmartAccountTx(
288
+ args,
289
+ 'remove_context_rule',
290
+ [xdr.ScVal.scvU32(args.ruleId)],
291
+ 'revoke_policy'
292
+ )
293
+
294
+ return {
295
+ unsignedXdr: finalTx.toEnvelope().toXDR().toString('base64'),
296
+ smartAccount: args.smartAccount,
297
+ sourceAccount: args.sourceAccount,
298
+ call: { contract: args.smartAccount, fn: 'remove_context_rule', ruleId: args.ruleId },
299
+ authNonce: original.credentials().address().nonce().toString(),
300
+ authValidUntilLedger: validUntilLedger,
301
+ rootInvocationXdr: original.rootInvocation().toXDR().toString('base64'),
302
+ }
303
+ }
304
+
305
+ export interface BuildRevokePolicyResult {
306
+ unsignedXdr: string
307
+ smartAccount: string
308
+ sourceAccount: string
309
+ call: { contract: string; fn: 'remove_context_rule'; ruleId: number }
310
+ authNonce: string
311
+ authValidUntilLedger: number
312
+ rootInvocationXdr: string
313
+ }
314
+
315
+ /** ~25 minutes at 5s/ledger. */
316
+ const DEFAULT_AUTH_VALID_UNTIL_LEDGERS = 300
317
+
318
+ // ---- internals ----
319
+
320
+ /** Record a bare call to the smart account, attach the deploy-time admin rule's
321
+ * auth entries, and re-simulate to assemble the footprint.
322
+ *
323
+ * Install and revoke differ only in the call they wrap, so they share this
324
+ * spine: the recording pass supplies the auth nonce and root invocation OZ's
325
+ * AuthPayload binds to, and the second simulation adds the Soroban footprint +
326
+ * resource fee. Nothing here is signed. `errorPrefix` names the calling tool in
327
+ * the fail-closed messages so a caller can still tell which call failed. */
328
+ async function buildAuthorisedSmartAccountTx(
329
+ args: {
330
+ smartAccount: string
331
+ sourceAccount: string
332
+ networkPassphrase: string
333
+ rpc: InstallRpcClient
334
+ baseFee?: number
335
+ authValidUntilLedgers?: number
336
+ },
337
+ functionName: string,
338
+ callArgs: xdr.ScVal[],
339
+ errorPrefix: string
340
+ ): Promise<{
341
+ finalTx: Transaction
342
+ original: xdr.SorobanAuthorizationEntry
343
+ validUntilLedger: number
344
+ }> {
395
345
  const source = await args.rpc.getAccount(args.sourceAccount)
396
346
  const hostFunction = xdr.HostFunction.hostFunctionTypeInvokeContract(
397
347
  new xdr.InvokeContractArgs({
398
348
  contractAddress: new Address(args.smartAccount).toScAddress(),
399
- functionName: 'remove_context_rule',
400
- args: [xdr.ScVal.scvU32(args.ruleId)],
349
+ functionName,
350
+ args: callArgs,
401
351
  })
402
352
  )
403
353
  const makeOperation = (auth: xdr.SorobanAuthorizationEntry[] = []) =>
@@ -418,7 +368,7 @@ export async function buildRevokePolicyXdr(args: {
418
368
  // carries host + URL detail) stays in the SDK's own logs - never
419
369
  // reflected back into a user-facing message where it would
420
370
  // reconnoitre the RPC.
421
- throw new Error('revoke_policy: simulateTransaction failed')
371
+ throw new Error(`${errorPrefix}: simulateTransaction failed`)
422
372
  }
423
373
  const original = (recorded.result?.auth ?? []).find(
424
374
  (entry) =>
@@ -428,15 +378,10 @@ export async function buildRevokePolicyXdr(args: {
428
378
  )
429
379
  if (!original) {
430
380
  throw new Error(
431
- `revoke_policy: no Soroban auth entry for smart account ${args.smartAccount}; this call does not route through the smart account`
381
+ `${errorPrefix}: no Soroban auth entry for smart account ${args.smartAccount}; this call does not route through the smart account`
432
382
  )
433
383
  }
434
384
 
435
- // Removal is master-only, so the deploy-time admin rule authorises it. The
436
- // recorded rootInvocation still includes remove_context_rule(ruleId), binding
437
- // the auth payload to the exact rule being removed. As with install,
438
- // source-account credentials carry the delegated signer entry and the
439
- // consumer supplies only the ordinary envelope signature.
440
385
  const validUntilLedger =
441
386
  (await args.rpc.getLatestLedger()).sequence +
442
387
  (args.authValidUntilLedgers ?? DEFAULT_AUTH_VALID_UNTIL_LEDGERS)
@@ -461,52 +406,12 @@ export async function buildRevokePolicyXdr(args: {
461
406
  const txWithAuth = buildTx(makeOperation(authEntries))
462
407
  const enforcing = await args.rpc.simulateTransaction(txWithAuth)
463
408
  if (rpc.Api.isSimulationError(enforcing)) {
464
- throw new Error('revoke_policy: auth simulateTransaction failed')
465
- }
466
- const finalTx = rpc.assembleTransaction(txWithAuth, enforcing).build()
467
-
468
- return {
469
- unsignedXdr: finalTx.toEnvelope().toXDR().toString('base64'),
470
- smartAccount: args.smartAccount,
471
- sourceAccount: args.sourceAccount,
472
- call: { contract: args.smartAccount, fn: 'remove_context_rule', ruleId: args.ruleId },
473
- authNonce: original.credentials().address().nonce().toString(),
474
- authValidUntilLedger: validUntilLedger,
475
- rootInvocationXdr: original.rootInvocation().toXDR().toString('base64'),
476
- }
477
- }
478
-
479
- export interface BuildRevokePolicyResult {
480
- unsignedXdr: string
481
- smartAccount: string
482
- sourceAccount: string
483
- call: { contract: string; fn: 'remove_context_rule'; ruleId: number }
484
- authNonce: string
485
- authValidUntilLedger: number
486
- rootInvocationXdr: string
487
- }
488
-
489
- /** ~25 minutes at 5s/ledger. */
490
- const DEFAULT_AUTH_VALID_UNTIL_LEDGERS = 300
491
-
492
- // ---- internals ----
493
-
494
- /** Adapter: turn the install-policy wire `PolicyRef` shape into the core
495
- * `PolicyRef` shape `buildAddContextRuleArgs` expects. Keeps the wire
496
- * schema hand-rolled + flat (the strict union would need a recursive
497
- * schema) while delegating to the proven encoder for the actual bytes. */
498
- function adaptPolicyRef(p: BuildInstallPolicyPolicyRef) {
499
- if (p.kind === 'interpreter') {
500
- return {
501
- kind: 'interpreter' as const,
502
- interpreterAddress: p.interpreterAddress,
503
- predicateBlobBase64: p.predicateBlobBase64,
504
- }
409
+ throw new Error(`${errorPrefix}: auth simulateTransaction failed`)
505
410
  }
506
411
  return {
507
- kind: 'oz_builtin' as const,
508
- instanceAddress: p.instanceAddress,
509
- primitive: p.primitive,
412
+ finalTx: rpc.assembleTransaction(txWithAuth, enforcing).build(),
413
+ original,
414
+ validUntilLedger,
510
415
  }
511
416
  }
512
417
 
@@ -580,14 +485,13 @@ function decodeInstallCallDescribes(
580
485
  `install_policy: built op has ${scvArgs.length} args, expected 5 (context_type, name, valid_until, signers, policies)`
581
486
  )
582
487
  }
583
- const contextType = scvArgs[0]
584
488
  const nameScv = scvArgs[1]
585
489
  const validUntilScv = scvArgs[2]
586
490
  const signersScv = scvArgs[3]
587
491
  const policiesScv = scvArgs[4]
588
492
  // The length check above pins these as defined; the local references
589
493
  // satisfy noUncheckedIndexedAccess on the accessors below.
590
- if (!nameScv || !validUntilScv || !signersScv || !policiesScv || !contextType) {
494
+ if (!nameScv || !validUntilScv || !signersScv || !policiesScv) {
591
495
  throw new Error('install_policy: built op args include an undefined slot despite length check')
592
496
  }
593
497
 
@@ -672,7 +576,6 @@ function decodeInstallCallDescribes(
672
576
  fields.set(fk.sym().toString(), inner.val())
673
577
  }
674
578
  // Interpreter policy fields carry grammar_version/install_nonce/predicate.
675
- // OZ primitives carry spending_limit/period_ledgers/threshold/signers.
676
579
  if (fields.has('predicate') || fields.has('grammar_version') || fields.has('install_nonce')) {
677
580
  const installNonceScv = fields.get('install_nonce')
678
581
  if (installNonceScv?.switch().name !== 'scvU32') {
@@ -706,21 +609,6 @@ function decodeInstallCallDescribes(
706
609
  observedInstallNonce = installNonce
707
610
  continue
708
611
  }
709
- // OZ built-in primitive. Distinguish by the parameter shape we
710
- // emitted; matching one of the three primitives exactly pins the
711
- // kind we built.
712
- if (fields.has('spending_limit') && fields.has('period_ledgers')) {
713
- policies.push({ kind: 'oz_builtin', address, primitive: 'spending_limit' })
714
- continue
715
- }
716
- if (fields.has('threshold') && fields.has('signer_weights')) {
717
- policies.push({ kind: 'oz_builtin', address, primitive: 'weighted_threshold' })
718
- continue
719
- }
720
- if (fields.has('threshold')) {
721
- policies.push({ kind: 'oz_builtin', address, primitive: 'simple_threshold' })
722
- continue
723
- }
724
612
  throw new Error(
725
613
  `install_policy: policies[${address}] value has an unknown field set; the encoder may have drifted`
726
614
  )
@@ -752,12 +640,3 @@ function decodeInstallCallDescribes(
752
640
  installNonce,
753
641
  }
754
642
  }
755
-
756
- /** Re-export so the run-layer does not need to import from
757
- * build-add-context-rule.ts (keeps the install/ -> install/ dependency
758
- * direction intact). */
759
- export { Contract, DEFAULT_GRAMMAR_VERSION }
760
-
761
- // Local re-import to avoid pulling the class from the SDK module path
762
- // at the top of the file (avoids the unused-import lint).
763
- import { Account } from '@stellar/stellar-sdk'
@@ -12,29 +12,6 @@
12
12
  // Exported here rather than from the package root to keep the root surface
13
13
  // about synthesis, and because these are transaction-building primitives whose
14
14
  // callers should know they are reaching for them.
15
-
16
- // Cross-rule authority analysis. A caller installing a policy needs to know
17
- // whether its signers can already reach the same calls through another context
18
- // rule, because OZ lets the signer name the rule and enforces only that one's
19
- // policies. Without this, a second, tighter rule reads as a restriction while
20
- // restricting nothing.
21
- export {
22
- ANY,
23
- type AuthorityOverlap,
24
- type ContextType,
25
- effectiveSelectors,
26
- findAuthorityOverlaps,
27
- type IntendedInstall,
28
- intersectSelectors,
29
- type MergeResult,
30
- mergeIntoAnd,
31
- type ObservedRule,
32
- type OverlapSeverity,
33
- permittedSelectors,
34
- type RuleClass,
35
- type Selector,
36
- signerKey,
37
- } from './authority-overlap.ts'
38
15
  export {
39
16
  ADD_CONTEXT_RULE_SYMBOL,
40
17
  type AddContextRuleArgs,
@@ -42,14 +19,3 @@ export {
42
19
  buildAddContextRuleArgs,
43
20
  DEFAULT_GRAMMAR_VERSION,
44
21
  } from './build-add-context-rule.ts'
45
- export {
46
- type AccountRuleReader,
47
- accountRuleReaderFromServer,
48
- type CollectedRules,
49
- collectObservedRules,
50
- decodeContextRule,
51
- decodeContextType,
52
- decodeSigner,
53
- docLedgerKey,
54
- MAX_RULE_ID_SCAN,
55
- } from './read-account-rules.ts'
@@ -64,21 +64,6 @@ function expectSymbol(v: xdr.ScVal | undefined, what: string): string {
64
64
  return v.sym().toString()
65
65
  }
66
66
 
67
- function expectAddress(v: xdr.ScVal | undefined, what: string): string {
68
- if (!v || v.switch() !== xdr.ScValType.scvAddress()) throw malformed(`${what} is not an address`)
69
- return Address.fromScAddress(v.address()).toString()
70
- }
71
-
72
- function expectU64(v: xdr.ScVal | undefined, what: string): string {
73
- if (!v || v.switch() !== xdr.ScValType.scvU64()) throw malformed(`${what} is not a u64`)
74
- return v.u64().toString()
75
- }
76
-
77
- function expectI128(v: xdr.ScVal | undefined, what: string): string {
78
- if (!v || v.switch() !== xdr.ScValType.scvI128()) throw malformed(`${what} is not an i128`)
79
- return scValToBigInt(v).toString()
80
- }
81
-
82
67
  /** Arity check with the same intent as the Rust `check_arity`: a selector with
83
68
  * the wrong element count is malformed, not silently truncated. */
84
69
  function arity(items: xdr.ScVal[], n: number, selector: string): void {
@@ -109,48 +94,6 @@ function decodeSelectorLeaf(items: xdr.ScVal[], sym: string): PredicateLeaf {
109
94
  element: expectU32(items[2], 'call_arg_field element'),
110
95
  field: expectSymbol(items[3], 'call_arg_field field'),
111
96
  }
112
- case 'call_arg_scaled':
113
- arity(items, 4, sym)
114
- return {
115
- kind: 'call_arg_scaled',
116
- index: expectU32(items[1], 'call_arg_scaled index'),
117
- num: expectI128(items[2], 'call_arg_scaled num'),
118
- den: expectI128(items[3], 'call_arg_scaled den'),
119
- }
120
- case 'amount':
121
- arity(items, 2, sym)
122
- return { kind: 'amount', token: expectAddress(items[1], 'amount token') }
123
- case 'window_spent':
124
- arity(items, 3, sym)
125
- return {
126
- kind: 'window_spent',
127
- token: expectAddress(items[1], 'window_spent token'),
128
- windowSeconds: Number(expectU64(items[2], 'window_spent windowSeconds')),
129
- }
130
- case 'now':
131
- arity(items, 1, sym)
132
- return { kind: 'now' }
133
- case 'valid_until':
134
- // The encoder refuses to emit this and the interpreter refuses it at
135
- // install, so it cannot appear in a document that installed. Decoding it
136
- // anyway would present a rule the chain would never have accepted.
137
- throw malformed('valid_until is not a usable predicate leaf')
138
- case 'invocation_count':
139
- arity(items, 2, sym)
140
- return {
141
- kind: 'invocation_count_in_window',
142
- windowSecs: Number(expectU64(items[1], 'invocation_count windowSecs')),
143
- }
144
- case 'oracle_price':
145
- arity(items, 2, sym)
146
- return { kind: 'oracle_price', asset: expectAddress(items[1], 'oracle_price asset') }
147
- case 'oracle_threshold':
148
- arity(items, 3, sym)
149
- return {
150
- kind: 'oracle_threshold',
151
- value: expectI128(items[1], 'oracle_threshold value'),
152
- decimals: expectU32(items[2], 'oracle_threshold decimals'),
153
- }
154
97
  default:
155
98
  // Deliberately NOT a literal_vec fallback - see the header note.
156
99
  throw malformed(`unknown selector symbol '${sym}'`)
@@ -173,38 +116,27 @@ export function decodeLeaf(v: xdr.ScVal): PredicateLeaf {
173
116
  return { kind: 'literal_symbol', value: v.sym().toString() }
174
117
  case xdr.ScValType.scvU32():
175
118
  return { kind: 'literal_u32', value: v.u32() }
176
- case xdr.ScValType.scvU64():
177
- return { kind: 'literal_u64', value: v.u64().toString() }
178
119
  case xdr.ScValType.scvI128():
179
120
  return { kind: 'literal_i128', value: scValToBigInt(v).toString() }
180
- case xdr.ScValType.scvBytes():
181
- return { kind: 'literal_bytes', value: Buffer.from(v.bytes()).toString('hex') }
182
121
  default:
183
122
  throw malformed(`unsupported leaf value type ${v.switch().name}`)
184
123
  }
185
124
  }
186
125
 
187
- /** One node. `not` wraps a NODE; the comparison ops wrap two LEAVES. */
126
+ /** One node. The comparison ops wrap two LEAVES. */
188
127
  export function decodeNode(v: xdr.ScVal): PredicateNode {
189
128
  const items = expectVec(v, 'node')
190
129
  const op = selectorSymbol(items)
191
130
  if (op === null) throw malformed('node does not start with an operator symbol')
192
131
  switch (op) {
193
- case 'and':
194
- case 'or': {
132
+ case 'and': {
195
133
  arity(items, 2, op)
196
134
  const children = expectVec(items[1] as xdr.ScVal, `${op} children`).map(decodeNode)
197
135
  if (children.length === 0) throw malformed(`${op} has no children`)
198
136
  return { op, children }
199
137
  }
200
- case 'not':
201
- arity(items, 2, op)
202
- return { op: 'not', child: decodeNode(items[1] as xdr.ScVal) }
203
138
  case 'eq':
204
- case 'lt':
205
139
  case 'lte':
206
- case 'gt':
207
- case 'gte':
208
140
  arity(items, 3, op)
209
141
  return {
210
142
  op,