@usesigil/kit 0.17.0 → 0.19.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 (101) hide show
  1. package/dist/agent-errors.d.ts +1 -1
  2. package/dist/agent-errors.d.ts.map +1 -1
  3. package/dist/agent-errors.js +14 -2
  4. package/dist/agent-errors.js.map +1 -1
  5. package/dist/cosign-helper.d.ts +26 -6
  6. package/dist/cosign-helper.d.ts.map +1 -1
  7. package/dist/cosign-helper.js +29 -9
  8. package/dist/cosign-helper.js.map +1 -1
  9. package/dist/create-vault.d.ts.map +1 -1
  10. package/dist/create-vault.js +10 -0
  11. package/dist/create-vault.js.map +1 -1
  12. package/dist/dashboard/index.d.ts +21 -0
  13. package/dist/dashboard/index.d.ts.map +1 -1
  14. package/dist/dashboard/index.js +53 -0
  15. package/dist/dashboard/index.js.map +1 -1
  16. package/dist/dashboard/mutations.d.ts +82 -5
  17. package/dist/dashboard/mutations.d.ts.map +1 -1
  18. package/dist/dashboard/mutations.js +326 -82
  19. package/dist/dashboard/mutations.js.map +1 -1
  20. package/dist/dashboard/types.d.ts +24 -2
  21. package/dist/dashboard/types.d.ts.map +1 -1
  22. package/dist/errors/agent-errors.generated.d.ts +2 -2
  23. package/dist/errors/agent-errors.generated.d.ts.map +1 -1
  24. package/dist/errors/agent-errors.generated.js +5 -4
  25. package/dist/errors/agent-errors.generated.js.map +1 -1
  26. package/dist/events.d.ts.map +1 -1
  27. package/dist/events.js +2 -1
  28. package/dist/events.js.map +1 -1
  29. package/dist/generated/accounts/pendingPolicyUpdate.d.ts +52 -0
  30. package/dist/generated/accounts/pendingPolicyUpdate.d.ts.map +1 -1
  31. package/dist/generated/accounts/pendingPolicyUpdate.js +6 -0
  32. package/dist/generated/accounts/pendingPolicyUpdate.js.map +1 -1
  33. package/dist/generated/errors/sigil.d.ts +3 -1
  34. package/dist/generated/errors/sigil.d.ts.map +1 -1
  35. package/dist/generated/errors/sigil.js +3 -0
  36. package/dist/generated/errors/sigil.js.map +1 -1
  37. package/dist/generated/event-discriminators.d.ts.map +1 -1
  38. package/dist/generated/event-discriminators.js +2 -1
  39. package/dist/generated/event-discriminators.js.map +1 -1
  40. package/dist/generated/instructions/acceptOwnershipTransferMultisig.d.ts +12 -69
  41. package/dist/generated/instructions/acceptOwnershipTransferMultisig.d.ts.map +1 -1
  42. package/dist/generated/instructions/acceptOwnershipTransferMultisig.js.map +1 -1
  43. package/dist/generated/instructions/agentTransfer.d.ts +33 -7
  44. package/dist/generated/instructions/agentTransfer.d.ts.map +1 -1
  45. package/dist/generated/instructions/agentTransfer.js +37 -2
  46. package/dist/generated/instructions/agentTransfer.js.map +1 -1
  47. package/dist/generated/instructions/approvePendingPolicy.d.ts +58 -0
  48. package/dist/generated/instructions/approvePendingPolicy.d.ts.map +1 -0
  49. package/dist/generated/instructions/approvePendingPolicy.js +122 -0
  50. package/dist/generated/instructions/approvePendingPolicy.js.map +1 -0
  51. package/dist/generated/instructions/cancelAgentPermissionsUpdate.d.ts +38 -5
  52. package/dist/generated/instructions/cancelAgentPermissionsUpdate.d.ts.map +1 -1
  53. package/dist/generated/instructions/cancelAgentPermissionsUpdate.js +43 -4
  54. package/dist/generated/instructions/cancelAgentPermissionsUpdate.js.map +1 -1
  55. package/dist/generated/instructions/finalizeSession.d.ts +24 -3
  56. package/dist/generated/instructions/finalizeSession.d.ts.map +1 -1
  57. package/dist/generated/instructions/finalizeSession.js.map +1 -1
  58. package/dist/generated/instructions/index.d.ts +1 -0
  59. package/dist/generated/instructions/index.d.ts.map +1 -1
  60. package/dist/generated/instructions/index.js +1 -0
  61. package/dist/generated/instructions/index.js.map +1 -1
  62. package/dist/generated/programs/sigil.d.ts +31 -27
  63. package/dist/generated/programs/sigil.d.ts.map +1 -1
  64. package/dist/generated/programs/sigil.js +39 -27
  65. package/dist/generated/programs/sigil.js.map +1 -1
  66. package/dist/generated/types/auditEntry.d.ts +2 -0
  67. package/dist/generated/types/auditEntry.d.ts.map +1 -1
  68. package/dist/generated/types/auditEntry.js.map +1 -1
  69. package/dist/generated/types/index.d.ts +1 -0
  70. package/dist/generated/types/index.d.ts.map +1 -1
  71. package/dist/generated/types/index.js +1 -0
  72. package/dist/generated/types/index.js.map +1 -1
  73. package/dist/generated/types/policyCosignApproved.d.ts +27 -0
  74. package/dist/generated/types/policyCosignApproved.d.ts.map +1 -0
  75. package/dist/generated/types/policyCosignApproved.js +26 -0
  76. package/dist/generated/types/policyCosignApproved.js.map +1 -0
  77. package/dist/index.d.ts +3 -0
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +3 -0
  80. package/dist/index.js.map +1 -1
  81. package/dist/kit-adapter.d.ts +1 -1
  82. package/dist/kit-adapter.d.ts.map +1 -1
  83. package/dist/kit-adapter.js +1 -1
  84. package/dist/kit-adapter.js.map +1 -1
  85. package/dist/policy/compute-agent-perms-cosign-digest.d.ts +51 -0
  86. package/dist/policy/compute-agent-perms-cosign-digest.d.ts.map +1 -0
  87. package/dist/policy/compute-agent-perms-cosign-digest.js +55 -0
  88. package/dist/policy/compute-agent-perms-cosign-digest.js.map +1 -0
  89. package/dist/policy/compute-cosign-digest.d.ts +9 -7
  90. package/dist/policy/compute-cosign-digest.d.ts.map +1 -1
  91. package/dist/policy/compute-cosign-digest.js +9 -7
  92. package/dist/policy/compute-cosign-digest.js.map +1 -1
  93. package/dist/policy/compute-policy-preview-digest.d.ts +48 -27
  94. package/dist/policy/compute-policy-preview-digest.d.ts.map +1 -1
  95. package/dist/policy/compute-policy-preview-digest.js +49 -27
  96. package/dist/policy/compute-policy-preview-digest.js.map +1 -1
  97. package/dist/testing/errors/names.generated.d.ts +1 -0
  98. package/dist/testing/errors/names.generated.d.ts.map +1 -1
  99. package/dist/testing/errors/names.generated.js +2 -1
  100. package/dist/testing/errors/names.generated.js.map +1 -1
  101. package/package.json +1 -1
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { getProgramDerivedAddress, getAddressEncoder } from "../kit-adapter.js";
8
8
  import { getSigilModuleLogger } from "../logger.js";
9
- import { pipe, createTransactionMessage, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, addSignersToTransactionMessage, signTransactionMessageWithSigners, getBase64EncodedWireTransaction, } from "../kit-adapter.js";
9
+ import { pipe, createTransactionMessage, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, addSignersToTransactionMessage, signTransactionMessageWithSigners, partiallySignTransactionMessageWithSigners, getBase64EncodedWireTransaction, } from "../kit-adapter.js";
10
10
  import { getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction, } from "@solana-program/compute-budget";
11
11
  import { sendAndConfirmTransaction, getBlockhashCache, } from "../rpc-helpers.js";
12
12
  import { AccountRole } from "../kit-adapter.js";
@@ -16,7 +16,9 @@ import { redactCause } from "../network-errors.js";
16
16
  import { SIGIL_PROGRAM_ADDRESS, MAX_ALLOWED_PROTOCOLS } from "../types.js";
17
17
  import { fetchAgentVault } from "../generated/accounts/agentVault.js";
18
18
  import { fetchPolicyConfig } from "../generated/accounts/policyConfig.js";
19
- import { computePolicyPreviewDigest } from "../policy/compute-policy-preview-digest.js";
19
+ import { computePolicyPreviewDigest, computeAgentSetHash, } from "../policy/compute-policy-preview-digest.js";
20
+ import { computeAgentPermsCosignDigest } from "../policy/compute-agent-perms-cosign-digest.js";
21
+ import { computeCosignDigest } from "../policy/compute-cosign-digest.js";
20
22
  // Phase 3: Simple mutations
21
23
  import { getFreezeVaultInstructionAsync } from "../generated/instructions/freezeVault.js";
22
24
  import { getReactivateVaultInstructionAsync } from "../generated/instructions/reactivateVault.js";
@@ -38,7 +40,7 @@ import { getApplyPendingPolicyInstructionAsync } from "../generated/instructions
38
40
  import { getCancelPendingPolicyInstructionAsync } from "../generated/instructions/cancelPendingPolicy.js";
39
41
  import { getQueueAgentPermissionsUpdateInstructionAsync } from "../generated/instructions/queueAgentPermissionsUpdate.js";
40
42
  import { getApplyAgentPermissionsUpdateInstructionAsync } from "../generated/instructions/applyAgentPermissionsUpdate.js";
41
- import { getCancelAgentPermissionsUpdateInstruction } from "../generated/instructions/cancelAgentPermissionsUpdate.js";
43
+ import { getCancelAgentPermissionsUpdateInstructionAsync } from "../generated/instructions/cancelAgentPermissionsUpdate.js";
42
44
  import { getCreatePostAssertionsInstructionAsync } from "../generated/instructions/createPostAssertions.js";
43
45
  import { getClosePostAssertionsInstructionAsync } from "../generated/instructions/closePostAssertions.js";
44
46
  // M-2 (pre-redeploy audit 2026-05-21): Phase 8 ownership-transfer ix builders.
@@ -147,12 +149,38 @@ async function siblingHandlerExpectedDigest(rpc, vault, override) {
147
149
  // this via `queue_policy_update` only.
148
150
  cosignRequired: livePolicy.data.cosignRequired,
149
151
  // D-5 (Bucket 2 audit 2026-05-21, F-RP3-1): pass-through from live
150
- // policy. Position 22 of the canonical TA-19 digest. Sibling handlers
152
+ // policy. Position 21 of the canonical TA-19 digest. Sibling handlers
151
153
  // never mutate this — owner sets via queue_policy_update only.
152
154
  cosignSessionPubkey: livePolicy.data.cosignSessionPubkey,
155
+ // M-1 (audit 2026-06-11): per-protocol caps (positions 23-24). Sibling
156
+ // handlers never mutate the caps — pass-through from live policy so the
157
+ // re-bind digest matches the on-chain recompute (create_post_assertions
158
+ // .rs:138-139 / close_post_assertions.rs read policy.has_protocol_caps +
159
+ // policy.protocol_caps).
160
+ hasProtocolCaps: livePolicy.data.hasProtocolCaps,
161
+ protocolCaps: livePolicy.data.protocolCaps,
162
+ // HIGH (audit 2026-06-11 follow-up): create_post_assertions.rs:129 and
163
+ // close_post_assertions.rs recompute agent_set_hash from the LIVE vault
164
+ // agents, and :136 reads operator_grant_delay_seconds from live policy.
165
+ // Omitting them here defaulted the digest to EMPTY_AGENT_SET_HASH / 0n,
166
+ // mismatching the on-chain recompute (PolicyPreviewMismatch) for ANY vault
167
+ // with >=1 agent or a non-zero operator-grant delay — i.e. every real vault.
168
+ // vault.agents is the active-agent Vec (register pushes; owner-revoke
169
+ // removes the entry, auto-revoke zeroes its capability in place — either
170
+ // way membership matches the on-chain Vec), mapped 1:1 by
171
+ // computeAgentSetHash (mirrors compute_agent_set_hash).
172
+ agentSetHash: computeAgentSetHash(liveVault.data.agents),
173
+ operatorGrantDelaySeconds: livePolicy.data.operatorGrantDelaySeconds,
153
174
  });
154
175
  }
155
- async function run(rpc, owner, network, instructions, opts = {}) {
176
+ async function run(rpc, owner, network, instructions, opts = {},
177
+ // Elevated-cosign surface (audit 2026-06-12): additional signers beyond the
178
+ // owner (e.g. a cosign-session signer for an elevated queue mutation). The
179
+ // cosigner must ALSO be present in the instruction's account metas as a
180
+ // readonly-signer (the elevated wrappers append it); attaching it here makes
181
+ // its signature land in the wire tx. Default [] preserves the owner-only path
182
+ // for every existing non-elevated caller.
183
+ cosigners = []) {
156
184
  try {
157
185
  const cu = opts.computeUnits ?? CU_OWNER_ACTION;
158
186
  const allIx = [
@@ -171,7 +199,7 @@ async function run(rpc, owner, network, instructions, opts = {}) {
171
199
  const cache = getBlockhashCache(rpc);
172
200
  const blockhash = await cache.get(rpc);
173
201
  const txMessage = pipe(createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayer(owner.address, tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx), (tx) => appendTransactionMessageInstructions(allIx, tx));
174
- const txWithSigners = addSignersToTransactionMessage([owner], txMessage);
202
+ const txWithSigners = addSignersToTransactionMessage([owner, ...cosigners], txMessage);
175
203
  const signedTx = await signTransactionMessageWithSigners(txWithSigners);
176
204
  const wire = getBase64EncodedWireTransaction(signedTx);
177
205
  const signature = await sendAndConfirmTransaction(rpc, wire);
@@ -326,7 +354,7 @@ export async function cancelAgentGrant(rpc, vault, owner, network, opts) {
326
354
  * created/destroyed between the read and TX execution, the on-chain program
327
355
  * will reject the TX. This is a known race window — retry on failure.
328
356
  */
329
- export async function closeVault(rpc, vault, owner, network, opts) {
357
+ async function buildCloseVaultFinalIx(rpc, vault, owner, network) {
330
358
  const net = network === "mainnet" ? "mainnet-beta" : "devnet";
331
359
  // Resolve vault state to determine which remaining_accounts are needed
332
360
  const state = await resolveVaultStateForOwner(rpc, vault, undefined, net);
@@ -420,6 +448,16 @@ export async function closeVault(rpc, vault, owner, network, opts) {
420
448
  ],
421
449
  }
422
450
  : ix;
451
+ return finalIx;
452
+ }
453
+ /**
454
+ * Close a vault (owner-only path — for vaults WITHOUT cosign). On a
455
+ * cosign-required vault the on-chain `close_vault` rejects an owner-only close
456
+ * with `ErrCosignRequired`; use {@link buildCloseVaultElevated} (partial-sign,
457
+ * bound cosigner co-signs) instead.
458
+ */
459
+ export async function closeVault(rpc, vault, owner, network, opts) {
460
+ const finalIx = await buildCloseVaultFinalIx(rpc, vault, owner, network);
423
461
  return run(rpc, owner, network, [finalIx], {
424
462
  computeUnits: opts?.computeUnits ?? 400_000,
425
463
  priorityFeeMicroLamports: opts?.priorityFeeMicroLamports,
@@ -525,6 +563,37 @@ export async function withdraw(rpc, vault, owner, network, mint, amount, opts) {
525
563
  * - `sessionExpirySeconds` range (5..=90 when > 0; audit F5-H1)
526
564
  */
527
565
  export async function queuePolicyUpdate(rpc, vault, owner, network, changes, opts) {
566
+ // Non-elevated path: cosign_session = Pubkey::default(), no cosigner in
567
+ // remaining_accounts, owner-only signature. Shares buildPolicyUpdateIx (the
568
+ // merged-effective projection + TA-19 digest) with queuePolicyElevated — the
569
+ // single source of truth that prevents digest drift between the two surfaces.
570
+ // An elevated change submitted here (e.g. raising a cap on a cosign_required
571
+ // vault) fails closed on-chain with ErrCosignRequired; use queuePolicyElevated.
572
+ const ix = await buildPolicyUpdateIx(rpc, owner, vault, changes, DEFAULT_COSIGN_SESSION);
573
+ return run(rpc, owner, network, [ix], opts);
574
+ }
575
+ // ═══════════════════════════════════════════════════════════════════════════
576
+ // Elevated-cosign surface (audit 2026-06-12) — policy path.
577
+ //
578
+ // queuePolicyElevated / buildQueuePolicyElevated mirror the agent-perms pair for
579
+ // policy changes. They share buildPolicyUpdateIx with queuePolicyUpdate (DRY —
580
+ // the single source of truth for the merged-effective projection + TA-19 digest;
581
+ // duplicating it is the exact digest-drift failure mode the 2026-06-11 audit
582
+ // fixed). The only difference between non-elevated and elevated is the
583
+ // cosign_session arg (default vs a real cosigner pubkey) + the elevated-only
584
+ // change fields, all plumbed through `eff = changes.X ?? live`.
585
+ // ═══════════════════════════════════════════════════════════════════════════
586
+ /** Pubkey::default() (System Program) — the non-elevated cosign_session arg. */
587
+ const DEFAULT_COSIGN_SESSION = "11111111111111111111111111111111";
588
+ /**
589
+ * Shared queue_policy_update instruction builder. Validates `changes`, fetches
590
+ * live policy+vault, projects the merged-effective policy (`eff = changes.X ??
591
+ * live.X` for EVERY field, so omitted fields fall through to live — the
592
+ * non-elevated path is byte-identical to the prior inline impl), computes the
593
+ * TA-19 digest over it, and builds the ix with the supplied `cosignSession`
594
+ * (DEFAULT_COSIGN_SESSION = non-elevated; a real cosigner pubkey = elevated).
595
+ */
596
+ async function buildPolicyUpdateIx(rpc, owner, vault, changes, cosignSession) {
528
597
  if (Object.keys(changes).length === 0) {
529
598
  throw toDxError(new Error("At least one policy change is required"));
530
599
  }
@@ -542,10 +611,6 @@ export async function queuePolicyUpdate(rpc, vault, owner, network, changes, opt
542
611
  changes.approvedApps.length > MAX_ALLOWED_PROTOCOLS) {
543
612
  throw toDxError(new Error(`approvedApps length exceeds on-chain MAX_ALLOWED_PROTOCOLS (${MAX_ALLOWED_PROTOCOLS}). Got ${changes.approvedApps.length}. On-chain rejects TooManyAllowedProtocols.`));
544
613
  }
545
- // Phase 2 TA-19: fetch live policy + vault state to compute the digest of
546
- // the merged-effective policy that WILL result if this update is applied.
547
- // The on-chain handler re-asserts the same digest at queue time, so any
548
- // owner blind-sign that diverges from the SDK-projected update is rejected.
549
614
  const [policyPda] = await getPolicyPDA(vault);
550
615
  const livePolicy = await fetchPolicyConfig(rpc, policyPda);
551
616
  const liveVault = await fetchAgentVault(rpc, vault);
@@ -559,13 +624,21 @@ export async function queuePolicyUpdate(rpc, vault, owner, network, changes, opt
559
624
  const effDaily = changes.dailyCap ?? livePolicy.data.dailySpendingCapUsd;
560
625
  const effMaxTx = changes.maxPerTrade ?? livePolicy.data.maxTransactionSizeUsd;
561
626
  const effMaxSlip = changes.maxSlippageBps ?? livePolicy.data.maxSlippageBps;
562
- // PEN-CROSS-6: developer_fee_rate is now part of the digest. Project the
563
- // merged-effective value the same way as other Option<…> fields.
564
627
  const effDeveloperFeeRate = changes.developerFeeRate ?? livePolicy.data.developerFeeRate;
565
628
  const effTimelock = changes.timelock != null
566
629
  ? BigInt(changes.timelock)
567
630
  : livePolicy.data.timelockDuration;
568
631
  const effSessionExpiry = changes.sessionExpirySeconds ?? livePolicy.data.sessionExpirySeconds;
632
+ const effHasProtocolCaps = changes.hasProtocolCaps ?? livePolicy.data.hasProtocolCaps;
633
+ const effProtocolCaps = changes.protocolCaps ?? livePolicy.data.protocolCaps;
634
+ // Elevated-only fields (audit 2026-06-12): same merged-effective projection.
635
+ // Undefined ⇒ live pass-through, so queuePolicyUpdate's digest is unchanged.
636
+ const effStableFloor = changes.stableBalanceFloor ?? livePolicy.data.stableBalanceFloor;
637
+ const effPerRecip = changes.perRecipientDailyCapUsd ?? livePolicy.data.perRecipientDailyCapUsd;
638
+ const effCosignRequired = changes.cosignRequired ?? livePolicy.data.cosignRequired;
639
+ const effCosignSessionPubkey = changes.cosignSessionPubkey ?? livePolicy.data.cosignSessionPubkey;
640
+ const effOperatorDelay = changes.operatorGrantDelaySeconds ??
641
+ livePolicy.data.operatorGrantDelaySeconds;
569
642
  const newPolicyPreviewDigest = computePolicyPreviewDigest({
570
643
  dailySpendingCapUsd: effDaily,
571
644
  maxTransactionSizeUsd: effMaxTx,
@@ -579,31 +652,18 @@ export async function queuePolicyUpdate(rpc, vault, owner, network, changes, opt
579
652
  sessionExpirySeconds: effSessionExpiry,
580
653
  observeOnly: liveVault.data.observeOnly,
581
654
  hasPostAssertions: livePolicy.data.hasPostAssertions,
582
- // PEN-CROSS-2: created_at_slot is immutable post-init — read from live.
583
655
  createdAtSlot: livePolicy.data.createdAtSlot,
584
- // TA-05 (Phase 3): operating_hours is policy-owned and bound by TA-19.
585
- // queueAgentPermissions does not currently mutate it through the
586
- // dashboard mutation surface — read from live policy.
587
656
  operatingHours: livePolicy.data.operatingHours,
588
- // TA-07/17 (Phase 3): same — not mutated by this dashboard surface.
589
657
  autoPromoteGrays: livePolicy.data.autoPromoteGrays,
590
658
  autoRevokeThreshold: livePolicy.data.autoRevokeThreshold,
591
- // TA-12/14 (Phase 5): post-exec invariants. Not mutated by this surface;
592
- // pass-through from live policy. Mutating them is elevated per TA-09.
593
- stableBalanceFloor: livePolicy.data.stableBalanceFloor,
594
- perRecipientDailyCapUsd: livePolicy.data.perRecipientDailyCapUsd,
595
- // G6 (audit 2026-05-18 cosign opt-in): pass-through from live policy.
596
- // The non-elevated dashboard surface does NOT mutate cosign_required;
597
- // owners change cosign opt-in via a separate elevated workflow that
598
- // includes the cosign signer (or, for false→true direction, can also
599
- // be done non-elevated by passing the override directly through the
600
- // ix arg below — but this dashboard helper keeps the policy stable
601
- // for the default path).
602
- cosignRequired: livePolicy.data.cosignRequired,
603
- // F-Q6 (2026-06-02): operator_grant_delay not mutated by this dashboard
604
- // surface — pass-through from live policy so the digest matches the
605
- // on-chain merged (eff) value at canonical position 22.
606
- operatorGrantDelaySeconds: livePolicy.data.operatorGrantDelaySeconds,
659
+ stableBalanceFloor: effStableFloor,
660
+ perRecipientDailyCapUsd: effPerRecip,
661
+ cosignRequired: effCosignRequired,
662
+ operatorGrantDelaySeconds: effOperatorDelay,
663
+ hasProtocolCaps: effHasProtocolCaps,
664
+ protocolCaps: effProtocolCaps,
665
+ agentSetHash: computeAgentSetHash(liveVault.data.agents),
666
+ cosignSessionPubkey: effCosignSessionPubkey,
607
667
  });
608
668
  const ix = await getQueuePolicyUpdateInstructionAsync({
609
669
  owner,
@@ -620,55 +680,55 @@ export async function queuePolicyUpdate(rpc, vault, owner, network, changes, opt
620
680
  hasProtocolCaps: changes.hasProtocolCaps ?? null,
621
681
  protocolCaps: changes.protocolCaps ?? null,
622
682
  destinationMode: changes.destinationMode ?? null,
623
- // TA-05 (Phase 3): operating_hours is not mutated by this mutation
624
- // surface — pass null to fall through to live policy at on-chain merge.
625
683
  operatingHours: null,
626
- // TA-12/14 (Phase 5): not mutated by this non-elevated surface — pass
627
- // null to fall through to live policy. Elevated mutations (lowering
628
- // floor, raising per-recipient cap) require cosign and the
629
- // `queuePolicyElevated()` helper.
630
- stableBalanceFloor: null,
631
- perRecipientDailyCapUsd: null,
632
- // G6 (audit 2026-05-18 cosign opt-in): not mutated by this non-
633
- // elevated surface — pass null to fall through to live policy.
634
- // Toggling cosign on/off goes through a dedicated path that is
635
- // aware of the one-way-ratchet semantics (true→false requires
636
- // cosign; false→true does not).
637
- cosignRequired: null,
638
- // D-5 (Bucket 2 audit 2026-05-21, F-RP3-1): not mutated by this
639
- // non-elevated surface — pass null to keep live policy value. Owner
640
- // sets cosign_session_pubkey via a dedicated elevated helper that
641
- // verifies the new pubkey isn't a Sigil-protected PDA at queue time.
642
- cosignSessionPubkey: null,
643
- // F-Q6 (2026-06-02): not mutated by this dashboard surface — pass null
644
- // (falls through to live policy at on-chain merge). Configurability is
645
- // available via the raw codama builder + owner paths.
646
- operatorGrantDelaySeconds: null,
647
- // TA-09 (Phase 3): non-elevated path by default — pass the
648
- // System Program / zero-pubkey ("11111111111111111111111111111111").
649
- // Elevated mutations through this dashboard surface require a
650
- // follow-on `queuePolicyElevated()` helper (cosign-helper.ts, G4).
651
- //
652
- // CANONICAL `cosign_session` ARG CONTRACT (Round 2 §RP-2 B4 F-3,
653
- // 2026-05-19) — for non-Codama callers reading this file as a
654
- // reference impl:
655
- // - Non-elevated queue (this branch): pass `Pubkey::default()`
656
- // and OMIT any cosigner from `remaining_accounts`.
657
- // - Elevated queue (raising daily_cap, expanding destinations /
658
- // protocols, lowering stable_balance_floor, raising
659
- // per_recipient_daily_cap_usd, disabling protocol_caps, mutating
660
- // protocol_caps entries, or disabling cosign): pass a REAL session
661
- // pubkey + include it in `remaining_accounts` with
662
- // `is_signer == true`. Build the bundle via
663
- // `buildCosignBundle()` in `sdk/kit/src/cosign-helper.ts`.
664
- // - Reject path: a non-default `cosign_session` on a non-elevated
665
- // queue surfaces `InvalidPermissions` (6088). INTENTIONAL — the
666
- // on-chain handler refuses to silently downgrade a caller's
667
- // declared intent (Option A behaviour).
668
- cosignSession: "11111111111111111111111111111111",
684
+ stableBalanceFloor: changes.stableBalanceFloor ?? null,
685
+ perRecipientDailyCapUsd: changes.perRecipientDailyCapUsd ?? null,
686
+ cosignRequired: changes.cosignRequired ?? null,
687
+ cosignSessionPubkey: changes.cosignSessionPubkey ?? null,
688
+ operatorGrantDelaySeconds: changes.operatorGrantDelaySeconds ?? null,
689
+ cosignSession,
669
690
  newPolicyPreviewDigest,
670
691
  });
671
- return run(rpc, owner, network, [ix], opts);
692
+ return ix;
693
+ }
694
+ /**
695
+ * Elevated policy queue — single-builder dual-sign. Caller holds the cosigner
696
+ * key; signs [owner, cosigner] + sends. For true 2-party async use
697
+ * buildQueuePolicyElevated.
698
+ */
699
+ export async function queuePolicyElevated(rpc, vault, owner, network, changes, cosigner, opts) {
700
+ requireValidAddress(cosigner.address, "Cosigner address");
701
+ if (cosigner.address === owner.address) {
702
+ throw toDxError(new Error("Cosigner must be distinct from the owner (on-chain ErrCosignRequired)"));
703
+ }
704
+ const ix = await buildPolicyUpdateIx(rpc, owner, vault, changes, cosigner.address);
705
+ return run(rpc, owner, network, [withCosignerSigner(ix, cosigner.address)], opts, [cosigner]);
706
+ }
707
+ /**
708
+ * Elevated policy queue — partial-sign handoff. Owner-signs + returns the
709
+ * partial tx + the policy cosign digest for the cosigner to complete + send.
710
+ * The cosign digest binds the RAW queued args (mirrors compute_cosign_digest).
711
+ */
712
+ export async function buildQueuePolicyElevated(rpc, vault, owner, changes, cosignSession, opts) {
713
+ requireValidAddress(cosignSession, "Cosigner address");
714
+ if (cosignSession === owner.address) {
715
+ throw toDxError(new Error("Cosigner must be distinct from the owner (on-chain ErrCosignRequired)"));
716
+ }
717
+ const ix = await buildPolicyUpdateIx(rpc, owner, vault, changes, cosignSession);
718
+ const partialTransactionBase64 = await buildOwnerPartialSignedTx(rpc, owner, [withCosignerSigner(ix, cosignSession)], opts);
719
+ const cosignDigest = computeCosignDigest({
720
+ cosignSession,
721
+ dailySpendingCapUsd: changes.dailyCap ?? null,
722
+ maxTransactionAmountUsd: changes.maxPerTrade ?? null,
723
+ allowedDestinations: changes.allowedDestinations ?? null,
724
+ protocols: changes.approvedApps ?? null,
725
+ stableBalanceFloor: changes.stableBalanceFloor ?? null,
726
+ perRecipientDailyCapUsd: changes.perRecipientDailyCapUsd ?? null,
727
+ hasProtocolCaps: changes.hasProtocolCaps ?? null,
728
+ protocolCaps: changes.protocolCaps ?? null,
729
+ cosignRequired: changes.cosignRequired ?? null,
730
+ });
731
+ return { partialTransactionBase64, cosignSession, cosignDigest };
672
732
  }
673
733
  export async function applyPendingPolicy(rpc, vault, owner, network, opts) {
674
734
  const ix = await getApplyPendingPolicyInstructionAsync({ owner, vault });
@@ -710,13 +770,118 @@ cooldownSeconds = 0n) {
710
770
  // pass a REAL session pubkey + include it as a signer in
711
771
  // `remaining_accounts`.
712
772
  // - Reject path: passing a non-default `cosign_session` on a
713
- // non-elevated queue surfaces `InvalidPermissions` (6088).
773
+ // non-elevated queue surfaces `InvalidPermissions` (6036).
714
774
  // INTENTIONAL — the on-chain handler refuses to silently
715
775
  // downgrade a caller's declared intent (Option A behaviour).
716
776
  cosignSession: "11111111111111111111111111111111",
717
777
  });
718
778
  return run(rpc, owner, network, [ix], opts);
719
779
  }
780
+ // ═══════════════════════════════════════════════════════════════════════════
781
+ // Elevated-cosign surface (audit 2026-06-12).
782
+ //
783
+ // On a `cosign_required` vault, raising an agent's capability or spending limit,
784
+ // or setting a non-zero cooldown, is an ELEVATED mutation: the on-chain
785
+ // queue_agent_permissions_update handler requires a non-default `cosign_session`
786
+ // that is (a) distinct from the owner and (b) present as a signer in
787
+ // remaining_accounts. Two caller models:
788
+ // - queueAgentPermissionsElevated(...) single-builder dual-sign: caller
789
+ // supplies the cosigner as a TransactionSigner; we sign [owner, cosigner].
790
+ // - buildQueueAgentPermissionsElevated(...) partial-sign handoff: caller
791
+ // supplies only the cosigner PUBKEY; we owner-partial-sign and return the
792
+ // base64 partial tx + the cosign digest for the cosigner to complete + send.
793
+ // ═══════════════════════════════════════════════════════════════════════════
794
+ /** Append a cosign-session signer to a generated instruction's account metas. */
795
+ function withCosignerSigner(ix, cosignSession) {
796
+ return {
797
+ ...ix,
798
+ accounts: [
799
+ ...(ix.accounts ?? []),
800
+ { address: cosignSession, role: AccountRole.READONLY_SIGNER },
801
+ ],
802
+ };
803
+ }
804
+ /**
805
+ * Assemble the compute-budget-prefixed message and OWNER-partial-sign it (the
806
+ * cosigner — a required signer via the appended account meta — signs later).
807
+ * Returns the base64 wire transaction for handoff. Mirrors run()'s message
808
+ * assembly but does NOT send.
809
+ */
810
+ async function buildOwnerPartialSignedTx(rpc, owner, instructions, opts = {}) {
811
+ const cu = opts.computeUnits ?? CU_OWNER_ACTION;
812
+ const allIx = [
813
+ getSetComputeUnitLimitInstruction({
814
+ units: cu,
815
+ }),
816
+ ...(opts.priorityFeeMicroLamports
817
+ ? [
818
+ getSetComputeUnitPriceInstruction({
819
+ microLamports: BigInt(opts.priorityFeeMicroLamports),
820
+ }),
821
+ ]
822
+ : []),
823
+ ...instructions,
824
+ ];
825
+ const blockhash = await getBlockhashCache(rpc).get(rpc);
826
+ const txMessage = pipe(createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageFeePayer(owner.address, tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx), (tx) => appendTransactionMessageInstructions(allIx, tx));
827
+ const withOwner = addSignersToTransactionMessage([owner], txMessage);
828
+ const partial = await partiallySignTransactionMessageWithSigners(withOwner);
829
+ return getBase64EncodedWireTransaction(partial);
830
+ }
831
+ /**
832
+ * Elevated agent-permissions queue — single-builder dual-sign. The caller holds
833
+ * the cosigner key (server-side / single-operator). Signs [owner, cosigner] and
834
+ * sends. For a true 2-party async flow use `buildQueueAgentPermissionsElevated`.
835
+ */
836
+ export async function queueAgentPermissionsElevated(rpc, vault, owner, network, agent, permissions, spendingLimit, cooldownSeconds, cosigner, opts) {
837
+ requireValidAddress(agent, "Agent address");
838
+ requireValidPermissions(permissions);
839
+ requireValidAddress(cosigner.address, "Cosigner address");
840
+ if (cosigner.address === owner.address) {
841
+ throw toDxError(new Error("Cosigner must be distinct from the owner (on-chain ErrCosignRequired)"));
842
+ }
843
+ const ix = await getQueueAgentPermissionsUpdateInstructionAsync({
844
+ owner,
845
+ vault,
846
+ agent,
847
+ newCapability: Number(permissions),
848
+ spendingLimitUsd: spendingLimit,
849
+ cooldownSeconds,
850
+ cosignSession: cosigner.address,
851
+ });
852
+ return run(rpc, owner, network, [withCosignerSigner(ix, cosigner.address)], opts, [cosigner]);
853
+ }
854
+ /**
855
+ * Elevated agent-permissions queue — partial-sign handoff. Owner-signs and
856
+ * returns the partial transaction + cosign digest; the cosigner signs and sends
857
+ * out-of-band (true 2-of-2). Validation mirrors the dual-sign path.
858
+ */
859
+ export async function buildQueueAgentPermissionsElevated(rpc, vault, owner, agent, permissions, spendingLimit, cooldownSeconds, cosignSession, opts) {
860
+ requireValidAddress(agent, "Agent address");
861
+ requireValidPermissions(permissions);
862
+ requireValidAddress(cosignSession, "Cosigner address");
863
+ if (cosignSession === owner.address) {
864
+ throw toDxError(new Error("Cosigner must be distinct from the owner (on-chain ErrCosignRequired)"));
865
+ }
866
+ const ix = await getQueueAgentPermissionsUpdateInstructionAsync({
867
+ owner,
868
+ vault,
869
+ agent,
870
+ newCapability: Number(permissions),
871
+ spendingLimitUsd: spendingLimit,
872
+ cooldownSeconds,
873
+ cosignSession,
874
+ });
875
+ const partialTransactionBase64 = await buildOwnerPartialSignedTx(rpc, owner, [withCosignerSigner(ix, cosignSession)], opts);
876
+ const cosignDigest = computeAgentPermsCosignDigest({
877
+ cosignSession,
878
+ agent,
879
+ newCapability: Number(permissions),
880
+ spendingLimitUsd: spendingLimit,
881
+ cooldownSeconds,
882
+ });
883
+ return { partialTransactionBase64, cosignSession, cosignDigest };
884
+ }
720
885
  export async function applyAgentPermissions(rpc, vault, owner, network, agent, opts) {
721
886
  requireValidAddress(agent, "Agent address");
722
887
  const [overlayPda] = await getAgentOverlayPDA(vault, 0);
@@ -732,13 +897,92 @@ export async function applyAgentPermissions(rpc, vault, owner, network, agent, o
732
897
  export async function cancelAgentPermissions(rpc, vault, owner, network, agent, opts) {
733
898
  requireValidAddress(agent, "Agent address");
734
899
  const pendingPda = await derivePendingAgentPermsPDA(vault, agent);
735
- const ix = getCancelAgentPermissionsUpdateInstruction({
900
+ const ix = await getCancelAgentPermissionsUpdateInstructionAsync({
736
901
  owner,
737
902
  vault,
738
903
  pendingAgentPerms: pendingPda,
739
904
  });
740
905
  return run(rpc, owner, network, [ix], opts);
741
906
  }
907
+ function assertDistinctCosigner(owner, cosignSession) {
908
+ requireValidAddress(cosignSession, "Cosigner address");
909
+ if (cosignSession === owner.address) {
910
+ throw toDxError(new Error("Cosigner must be distinct from the owner (on-chain ErrCosignRequired)"));
911
+ }
912
+ }
913
+ /** Partial-sign: cancel a pending AGENT GRANT on a cosign-required vault. */
914
+ export async function buildCancelAgentGrantElevated(rpc, vault, owner, cosignSession, opts) {
915
+ assertDistinctCosigner(owner, cosignSession);
916
+ const ix = await getCancelAgentGrantInstructionAsync({ owner, vault });
917
+ const partialTransactionBase64 = await buildOwnerPartialSignedTx(rpc, owner, [withCosignerSigner(ix, cosignSession)], opts);
918
+ return { partialTransactionBase64, cosignSession };
919
+ }
920
+ /** Partial-sign: cancel a pending AGENT-PERMISSIONS update (M2b gate). */
921
+ export async function buildCancelAgentPermissionsElevated(rpc, vault, owner, agent, cosignSession, opts) {
922
+ assertDistinctCosigner(owner, cosignSession);
923
+ requireValidAddress(agent, "Agent address");
924
+ const pendingPda = await derivePendingAgentPermsPDA(vault, agent);
925
+ const ix = await getCancelAgentPermissionsUpdateInstructionAsync({
926
+ owner,
927
+ vault,
928
+ pendingAgentPerms: pendingPda,
929
+ });
930
+ const partialTransactionBase64 = await buildOwnerPartialSignedTx(rpc, owner, [withCosignerSigner(ix, cosignSession)], opts);
931
+ return { partialTransactionBase64, cosignSession };
932
+ }
933
+ /** Partial-sign: cancel a pending POLICY update (M2a gate). */
934
+ export async function buildCancelPendingPolicyElevated(rpc, vault, owner, cosignSession, opts) {
935
+ assertDistinctCosigner(owner, cosignSession);
936
+ const ix = await getCancelPendingPolicyInstructionAsync({ owner, vault });
937
+ const partialTransactionBase64 = await buildOwnerPartialSignedTx(rpc, owner, [withCosignerSigner(ix, cosignSession)], opts);
938
+ return { partialTransactionBase64, cosignSession };
939
+ }
940
+ /**
941
+ * Partial-sign: APPLY a queued agent-permissions update on a cosign-required
942
+ * vault. The on-chain H-1 gate requires the LIVE bound cosigner to sign at apply
943
+ * (defends the pre-signed-apply-survives-rotation class).
944
+ */
945
+ export async function buildApplyAgentPermissionsElevated(rpc, vault, owner, agent, cosignSession, opts) {
946
+ assertDistinctCosigner(owner, cosignSession);
947
+ requireValidAddress(agent, "Agent address");
948
+ const [overlayPda] = await getAgentOverlayPDA(vault, 0);
949
+ const pendingPda = await derivePendingAgentPermsPDA(vault, agent);
950
+ const ix = await getApplyAgentPermissionsUpdateInstructionAsync({
951
+ owner,
952
+ vault,
953
+ agentSpendOverlay: overlayPda,
954
+ pendingAgentPerms: pendingPda,
955
+ });
956
+ const partialTransactionBase64 = await buildOwnerPartialSignedTx(rpc, owner, [withCosignerSigner(ix, cosignSession)], opts);
957
+ return { partialTransactionBase64, cosignSession };
958
+ }
959
+ /**
960
+ * Partial-sign: APPLY a pending policy update that DISABLES cosign on a
961
+ * cosign-required vault (the on-chain disable gate requires the bound cosigner).
962
+ * Non-disable applies do NOT need the cosigner — use owner-only
963
+ * {@link applyPendingPolicy}.
964
+ */
965
+ export async function buildApplyPendingPolicyElevated(rpc, vault, owner, cosignSession, opts) {
966
+ assertDistinctCosigner(owner, cosignSession);
967
+ const ix = await getApplyPendingPolicyInstructionAsync({ owner, vault });
968
+ const partialTransactionBase64 = await buildOwnerPartialSignedTx(rpc, owner, [withCosignerSigner(ix, cosignSession)], opts);
969
+ return { partialTransactionBase64, cosignSession };
970
+ }
971
+ /**
972
+ * Partial-sign: CLOSE a cosign-required vault. `close_vault` is now cosign-gated
973
+ * (a leaked owner key alone cannot close → cannot start the close->reinit
974
+ * drain). Reuses the owner-only close's pending-PDA cleanup enumeration, then
975
+ * binds the cosigner.
976
+ */
977
+ export async function buildCloseVaultElevated(rpc, vault, owner, network, cosignSession, opts) {
978
+ assertDistinctCosigner(owner, cosignSession);
979
+ const finalIx = await buildCloseVaultFinalIx(rpc, vault, owner, network);
980
+ const partialTransactionBase64 = await buildOwnerPartialSignedTx(rpc, owner, [withCosignerSigner(finalIx, cosignSession)], {
981
+ computeUnits: opts?.computeUnits ?? 400_000,
982
+ priorityFeeMicroLamports: opts?.priorityFeeMicroLamports,
983
+ });
984
+ return { partialTransactionBase64, cosignSession };
985
+ }
742
986
  // ─── Post-execution assertions (Phase 2) ─────────────────────────────────────
743
987
  // Composes with pre-execution InstructionConstraints — NOT a replacement.
744
988
  //