@frontiercompute/zcash-ika 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,46 +1,31 @@
1
1
  # zcash-ika
2
2
 
3
- Split-key custody for Zcash and Bitcoin. The private key never exists whole.
3
+ Split-key custody for Zcash, Bitcoin, and EVM chains. The private key never exists whole. Spend policy enforced on-chain. Every action attested to Zcash.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@frontiercompute/zcash-ika)](https://www.npmjs.com/package/@frontiercompute/zcash-ika)
6
6
 
7
- ## What this is
7
+ ## What this does
8
8
 
9
- Your agent/DAO/treasury holds ZEC and BTC through [Ika's 2PC-MPC network](https://ika.xyz). One key share on your device, one distributed across Ika's nodes on Sui. Both must cooperate to sign. Spending policy enforced by smart contract. Every action attested on-chain via [ZAP1](https://pay.frontiercompute.io).
9
+ One secp256k1 dWallet on [Ika's 2PC-MPC network](https://ika.xyz) signs for three chain families. Your device holds half the key. Ika's nodes hold the other half. Neither can spend alone.
10
10
 
11
- **Ed25519 dWallet live on Ika testnet.** First Zcash-capable dWallet ever created on the network.
11
+ - **Spend policy** enforced by Sui Move contract (per-tx limits, daily caps, recipient whitelist, emergency freeze)
12
+ - **Transparent TX builder** for Zcash v5 transactions (ZIP 244 sighash, P2PKH, UTXO selection)
13
+ - **Attestation** of every operation to Zcash mainnet via [ZAP1](https://pay.frontiercompute.io)
14
+ - **5-chain verification** of attestation proofs (Arbitrum, Base, Hyperliquid, Solana, NEAR)
12
15
 
13
- ## Use cases
14
-
15
- ### AI Agent Custody
16
- Your agent needs to spend money. Give it a split-key wallet instead of full access. The agent requests transactions, but can't override spending limits, daily caps, or approved recipient lists. If the agent gets compromised, the attacker gets half a key. Worthless.
17
-
18
- ```
19
- npx @frontiercompute/zcash-mcp # 17 tools, any MCP client
20
- ```
21
-
22
- ### DAO Treasury
23
- Multi-sig is 2003 technology. Split-key custody means the treasury key literally doesn't exist in one place. Policy lives in a Sui Move contract that no single party controls. Every spend attested to Zcash for the full audit trail, but balances stay shielded.
24
-
25
- ### Privacy Payroll
26
- Pay contributors without publishing amounts on a block explorer. Shielded ZEC from an Orchard address, every payment attested via ZAP1 for compliance. The auditor sees proof of payment. Nobody else sees anything.
27
-
28
- ### Cross-Border Commerce
29
- Hold shielded ZEC + stablecoins (USDC/USDT via secp256k1 on EVM chains) in one wallet. Same operator, same policy. Swap between them via NEAR Intents. Settlement is private. Compliance is provable.
30
-
31
- ### Compliance Without Exposure
32
- ZAP1 attestations prove what happened without revealing what you hold. Bond deposits prove skin in the game. Policy verification proves you followed the rules. All on Zcash mainnet, all verifiable, nothing visible beyond what you choose to share.
33
-
34
- ## Signing parameters
16
+ ## Chain support
35
17
 
36
18
  | Chain | Curve | Algorithm | Hash | Status |
37
19
  |-------|-------|-----------|------|--------|
38
- | Zcash Orchard | ED25519 | EdDSA | SHA512 | dWallet live on testnet |
39
- | Bitcoin | SECP256K1 | ECDSASecp256k1 | DoubleSHA256 | SDK ready |
40
- | Zcash transparent | SECP256K1 | ECDSASecp256k1 | DoubleSHA256 | SDK ready |
41
- | Ethereum/Base | SECP256K1 | ECDSASecp256k1 | KECCAK256 | SDK ready |
20
+ | Zcash transparent | secp256k1 | ECDSA | DoubleSHA256 | TX builder + signing live |
21
+ | Bitcoin | secp256k1 | ECDSA | DoubleSHA256 | Same dWallet, signing live |
22
+ | Ethereum/EVM | secp256k1 | ECDSA | KECCAK256 | Same dWallet, signing live |
23
+
24
+ One dWallet. Three chain families. Split custody on all of them.
42
25
 
43
- One secp256k1 dWallet signs for Bitcoin, Zcash transparent, and every EVM chain.
26
+ ## What does NOT work
27
+
28
+ **Zcash shielded (Orchard)** requires RedPallas on the Pallas curve. Ika supports secp256k1 and Ed25519, not Pallas. No path from Ika to Orchard signing today. For shielded, use the [embedded wallet](https://github.com/Frontier-Compute/zap1) which holds Orchard keys directly.
44
29
 
45
30
  ## Install
46
31
 
@@ -52,102 +37,151 @@ npm install @frontiercompute/zcash-ika
52
37
 
53
38
  ```typescript
54
39
  import {
55
- createDualCustody,
56
- spendShielded,
57
- spendBitcoin,
40
+ createWallet,
41
+ sign,
58
42
  setPolicy,
59
- getHistory,
60
- checkCompliance,
61
- CHAIN_PARAMS,
43
+ spendTransparent,
44
+ checkPolicy,
45
+ deriveZcashAddress,
62
46
  } from "@frontiercompute/zcash-ika";
63
47
 
64
48
  const config = {
65
- network: "mainnet",
66
- zebraRpcUrl: "http://127.0.0.1:8232",
49
+ network: "testnet",
50
+ suiPrivateKey: "suiprivkey1...",
67
51
  zap1ApiUrl: "https://pay.frontiercompute.io",
68
- zap1ApiKey: "your-key",
69
52
  };
70
53
 
71
- // Create dual custody - shielded ZEC + BTC/stablecoins
72
- const custody = await createDualCustody(config, operatorSeed);
73
-
74
- // Set spending policy (enforced by Sui contract, not by trust)
75
- await setPolicy(config, custody.shielded.id, {
76
- maxPerTx: 100_000, // 0.001 ZEC per tx
77
- maxDaily: 1_000_000, // 0.01 ZEC daily cap
78
- allowedRecipients: [], // any recipient (or lock to specific addresses)
79
- approvalThreshold: 500_000, // operator approval above 0.005 ZEC
54
+ // 1. Create split-key wallet
55
+ const wallet = await createWallet(config);
56
+ console.log("dWallet:", wallet.id);
57
+ console.log("t-addr:", wallet.address);
58
+ console.log("Save this seed:", wallet.encryptionSeed);
59
+
60
+ // 2. Set spend policy (Sui Move contract)
61
+ const policy = await setPolicy(config, wallet.id, {
62
+ maxPerTx: 100_000_000, // 1 ZEC max per transaction
63
+ maxDaily: 500_000_000, // 5 ZEC daily cap
64
+ allowedRecipients: [], // empty = any recipient
65
+ approvalThreshold: 50_000_000, // flag above 0.5 ZEC
80
66
  });
67
+ console.log("Policy:", policy.policyId);
81
68
 
82
- // Shielded spend - agent requests, both key shares cooperate
83
- const result = await spendShielded(config, custody.shielded.id, operatorSeed, {
84
- to: "u1abc...",
85
- amount: 50_000,
86
- memo: "payment for API access",
69
+ // 3. Check if a spend is allowed
70
+ const check = await checkPolicy(config, policy.policyId, {
71
+ amount: 10_000_000,
72
+ recipient: "t1SomeAddress...",
87
73
  });
88
-
89
- // Compliance check (works now against live ZAP1 API)
90
- const compliance = await checkCompliance(config, custody.shielded.id);
91
- // { compliant: true, violations: 0, bondDeposits: 1 }
74
+ console.log("Allowed:", check.allowed);
75
+
76
+ // 4. Spend from the transparent address
77
+ const spend = await spendTransparent(config, {
78
+ walletId: wallet.id,
79
+ encryptionSeed: wallet.encryptionSeed,
80
+ recipient: "t1RecipientAddr...",
81
+ amount: 10_000_000, // 0.1 ZEC
82
+ zebraRpcUrl: "http://localhost:8232",
83
+ });
84
+ console.log("Txid:", spend.txid);
92
85
  ```
93
86
 
94
- ## How it works
87
+ ## Architecture
95
88
 
96
89
  ```
97
- Operator (phone / hardware wallet / DAO multisig)
90
+ Operator (device / HSM)
98
91
  |
99
- | user key share
92
+ | user key share (encryption seed from DKG)
100
93
  |
101
- Ika MPC Network (2PC-MPC on Sui, mainnet live)
94
+ Ika MPC Network (2PC-MPC on Sui)
102
95
  |
103
96
  | network key share (distributed across nodes)
104
97
  |
105
- +-- Spending Policy (Sui Move contract)
106
- | max per tx, daily cap, approved recipients
107
- | the agent CANNOT modify its own limits
98
+ +-- Spend Policy (Sui Move contract)
99
+ | max per tx, daily cap, recipient whitelist, freeze
108
100
  |
109
- +-- Sign Zcash tx (Ed25519/EdDSA) -> shielded spend
110
- +-- Sign Bitcoin tx (secp256k1/ECDSA) -> BTC spend
111
- +-- Sign EVM tx (secp256k1/ECDSA) -> USDC/USDT spend
101
+ +-- Sign ZEC transparent (secp256k1 / ECDSA / DoubleSHA256)
102
+ +-- Sign BTC (secp256k1 / ECDSA / DoubleSHA256)
103
+ +-- Sign ETH/EVM (secp256k1 / ECDSA / KECCAK256)
104
+ |
105
+ TX Builder (Zcash v5, ZIP 244)
106
+ +-- UTXO fetch from Zebra
107
+ +-- Sighash computation
108
+ +-- Signature attachment
109
+ +-- Broadcast + attestation
112
110
  |
113
111
  ZAP1 Attestation (Zcash mainnet)
114
- +-- every spend recorded as AGENT_ACTION
115
- +-- policy violations on-chain as POLICY_VIOLATION
116
- +-- bond deposits prove skin in the game
117
- +-- full audit trail, verifiable by anyone
112
+ +-- every spend recorded in Merkle tree
113
+ +-- anchored to Zcash blockchain
114
+ +-- verified on 5 EVM/L1 chains
118
115
  ```
119
116
 
120
- ## What's live
117
+ ## Sign flow
118
+
119
+ 1. **Presign** - pre-compute MPC ephemeral key share (Sui TX 1, poll for completion)
120
+ 2. **Sign** - approve message + request signature (Sui TX 2, poll for completion)
121
121
 
122
- - Ed25519 dWallet on Ika testnet (TX: `FYcuaxBCAfuZqfBW7JEtEJME3KLBSBKLvhjLpZGSyaXb`)
123
- - `getHistory()` and `checkCompliance()` against live ZAP1 API
124
- - All Ika SDK primitives re-exported and typed
125
- - Chain parameter configs for all signing modes
126
- - DKG test script proving the full round-trip
122
+ Both transactions on Sui. The user partial is computed locally via WASM. Neither party sees the full private key.
127
123
 
128
- ## What's next
124
+ ## Spend flow (transparent)
129
125
 
130
- - secp256k1 dWallet for BTC/stablecoins (same flow, different curve)
131
- - Sign a real Zcash sighash through the MPC
132
- - Ed25519 -> Orchard spending key derivation bridge
133
- - Move policy template for spending limits
134
- - Mainnet deployment
126
+ 1. Fetch UTXOs from Zebra RPC (`getaddressutxos`)
127
+ 2. Build unsigned v5 TX with ZIP 244 sighash per input
128
+ 3. Sign each sighash through Ika MPC (presign + sign)
129
+ 4. Attach DER signatures to scriptSig fields
130
+ 5. Broadcast via `sendrawtransaction`
131
+ 6. Attest spend to ZAP1
135
132
 
136
- ## The competition
133
+ ## Policy enforcement
137
134
 
138
- | Project | Custody | Privacy | Attestation |
139
- |---------|---------|---------|-------------|
140
- | Coinbase AgentKit | Full key in agent | None | None |
141
- | GOAT SDK | Full key in agent | None | None |
142
- | Solana Agent Kit | Full key in agent | None | None |
143
- | **zcash-ika** | **Split-key MPC** | **Zcash Orchard** | **ZAP1 on-chain** |
135
+ The Sui Move contract (`zap1_policy::policy`) stores:
136
+ - Per-transaction limit (zatoshis)
137
+ - 24-hour rolling daily cap
138
+ - Allowed recipient whitelist (empty = any)
139
+ - Emergency freeze toggle
140
+
141
+ Policy is checked before every MPC sign request. The contract owns the approval gate - you can't bypass it from the client.
142
+
143
+ Deploy: `sui client publish --path move/` then set `POLICY_PACKAGE_ID`.
144
+
145
+ ## On-chain proof
146
+
147
+ secp256k1 dWallet on Ika testnet:
148
+
149
+ - dWalletId: `0xd9055400c88aeae675413b78143aa54e25eca7061ab659f54a42167cbfdd7aec`
150
+ - TX: [`CYrS5X1S3itHUtux4qS35AJz5AAyUaJYeWZuqm1CcX2L`](https://testnet.suivision.xyz/txblock/CYrS5X1S3itHUtux4qS35AJz5AAyUaJYeWZuqm1CcX2L)
151
+ - Compressed pubkey: `03ba9e85a85674df494520c2e80b804656fac54fe68668266f33fee9b03ad4b069`
152
+ - Derived ZEC t-addr: `t1Rqh1TKqXsSiaV4wrSDandEPccucpHEudn`
153
+
154
+ Attestation anchors verified on Arbitrum, Base, Hyperliquid, Solana, NEAR testnet.
155
+
156
+ ## Environment variables
157
+
158
+ ```
159
+ SUI_PRIVATE_KEY - Sui keypair for signing transactions
160
+ POLICY_PACKAGE_ID - Published Move package address (after sui client publish)
161
+ ZAP1_API_URL - ZAP1 attestation API (default: https://pay.frontiercompute.io)
162
+ ZAP1_API_KEY - API key for attestation
163
+ ZEBRA_RPC_URL - Zebra JSON-RPC endpoint for UTXO queries and broadcast
164
+ ```
165
+
166
+ ## Test scripts
167
+
168
+ ```bash
169
+ # Create a new dWallet
170
+ SUI_PRIVATE_KEY=suiprivkey1... node dist/test-dkg.js
171
+
172
+ # Sign a test message through MPC
173
+ SUI_PRIVATE_KEY=... DWALLET_ID=0x... ENC_SEED=... node dist/test-sign.js
174
+
175
+ # End-to-end: DKG + presign + sign
176
+ SUI_PRIVATE_KEY=... node dist/test-e2e.js
177
+ ```
144
178
 
145
179
  ## Stack
146
180
 
147
181
  - [Ika](https://ika.xyz) - 2PC-MPC threshold signing on Sui
148
182
  - [ZAP1](https://pay.frontiercompute.io) - on-chain attestation protocol
149
183
  - [Zebra](https://github.com/ZcashFoundation/zebra) - Zcash node
150
- - [zcash-mcp](https://www.npmjs.com/package/@frontiercompute/zcash-mcp) - 17-tool MCP server
184
+ - [Sui Move](https://docs.sui.io/concepts/sui-move-concepts) - policy enforcement
151
185
 
152
186
  ## License
153
187
 
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Hybrid custody model for Zcash agents.
3
+ *
4
+ * Two custody paths, one interface:
5
+ *
6
+ * TRANSPARENT (secp256k1 via Ika MPC):
7
+ * - t-addr transactions signed through 2PC-MPC
8
+ * - Neither party holds the full key
9
+ * - Policy enforced by Sui Move contract
10
+ * - Same key signs BTC and ETH
11
+ *
12
+ * SHIELDED (RedPallas via local Orchard wallet):
13
+ * - Orchard transactions signed locally (direct key)
14
+ * - Ika cannot sign RedPallas (Pallas curve not supported)
15
+ * - Privacy from Zcash protocol, not from MPC
16
+ * - Every shielded spend attested to ZAP1
17
+ *
18
+ * The gap:
19
+ * Orchard uses RedPallas on the Pallas curve. Ika supports secp256k1,
20
+ * Ed25519, secp256r1, and Ristretto. None of these are Pallas.
21
+ * Until an MPC network adds Pallas curve support, shielded custody
22
+ * requires holding keys directly.
23
+ *
24
+ * The bridge:
25
+ * shield() - move ZEC from t-addr (MPC) to shielded pool (local wallet)
26
+ * unshield() - move ZEC from shielded back to t-addr (for MPC custody)
27
+ * Both operations attested on-chain via ZAP1.
28
+ */
29
+ import type { ZcashIkaConfig, DWalletHandle, SignResult } from "./index.js";
30
+ export type CustodyMode = "mpc" | "local";
31
+ export interface HybridWallet {
32
+ /** MPC-custody transparent wallet (secp256k1 dWallet on Ika) */
33
+ transparent: {
34
+ mode: "mpc";
35
+ walletId: string;
36
+ publicKey: Uint8Array;
37
+ tAddress: string;
38
+ encryptionSeed: string;
39
+ };
40
+ /** Local-custody shielded wallet (Orchard keys held directly) */
41
+ shielded: {
42
+ mode: "local";
43
+ /** Unified address (starts with u1) */
44
+ uAddress: string;
45
+ /** Whether the local wallet is initialized */
46
+ initialized: boolean;
47
+ };
48
+ /** ZAP1 attestation endpoint */
49
+ zap1ApiUrl: string;
50
+ }
51
+ export interface ShieldRequest {
52
+ /** Amount in zatoshis to move from t-addr to shielded pool */
53
+ amount: number;
54
+ /** Optional memo for the shielding transaction */
55
+ memo?: string;
56
+ }
57
+ export interface UnshieldRequest {
58
+ /** Amount in zatoshis to move from shielded back to t-addr */
59
+ amount: number;
60
+ /** Optional memo */
61
+ memo?: string;
62
+ }
63
+ export interface ShieldResult {
64
+ /** Zcash transaction ID */
65
+ txid: string;
66
+ /** Direction */
67
+ direction: "shield" | "unshield";
68
+ /** Amount moved */
69
+ amount: number;
70
+ /** ZAP1 attestation leaf hash */
71
+ leafHash?: string;
72
+ }
73
+ /**
74
+ * Create a hybrid wallet - MPC for transparent, local for shielded.
75
+ *
76
+ * The transparent side is an Ika dWallet (already created via createWallet).
77
+ * The shielded side connects to the local Zebra/Zashi wallet.
78
+ */
79
+ export declare function createHybridWallet(transparentWallet: DWalletHandle, shieldedAddress: string, zap1ApiUrl: string): HybridWallet;
80
+ /**
81
+ * Sign a transparent transaction through MPC.
82
+ * Delegates to the Ika sign() function.
83
+ */
84
+ export declare function signTransparent(config: ZcashIkaConfig, wallet: HybridWallet, sighash: Uint8Array): Promise<SignResult>;
85
+ /**
86
+ * Shield ZEC - move from MPC-custody t-addr to local shielded pool.
87
+ *
88
+ * Flow:
89
+ * 1. Build transparent tx: t-addr -> shielded address
90
+ * 2. Compute sighash (DoubleSHA256)
91
+ * 3. Sign via Ika MPC (secp256k1)
92
+ * 4. Broadcast via Zebra
93
+ * 5. Attest via ZAP1 as AGENT_ACTION
94
+ *
95
+ * After shielding, the ZEC is in the local Orchard wallet.
96
+ * Private from that point forward.
97
+ */
98
+ export declare function shield(config: ZcashIkaConfig, wallet: HybridWallet, request: ShieldRequest): Promise<ShieldResult>;
99
+ /**
100
+ * Unshield ZEC - move from local shielded pool back to MPC-custody t-addr.
101
+ *
102
+ * Flow:
103
+ * 1. Build Orchard tx: shielded -> t-addr (signed locally, RedPallas)
104
+ * 2. Broadcast via Zebra
105
+ * 3. Attest via ZAP1 as AGENT_ACTION
106
+ *
107
+ * After unshielding, the ZEC is back under MPC custody.
108
+ * The MPC can then send it to BTC, ETH, or another t-addr.
109
+ */
110
+ export declare function unshield(config: ZcashIkaConfig, wallet: HybridWallet, request: UnshieldRequest): Promise<ShieldResult>;
111
+ /**
112
+ * Attest a custody operation to ZAP1.
113
+ */
114
+ export declare function attestCustodyOp(zap1ApiUrl: string, zap1ApiKey: string, op: {
115
+ direction: "shield" | "unshield";
116
+ txid: string;
117
+ amount: number;
118
+ walletId: string;
119
+ }): Promise<string | null>;
package/dist/hybrid.js ADDED
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Hybrid custody model for Zcash agents.
3
+ *
4
+ * Two custody paths, one interface:
5
+ *
6
+ * TRANSPARENT (secp256k1 via Ika MPC):
7
+ * - t-addr transactions signed through 2PC-MPC
8
+ * - Neither party holds the full key
9
+ * - Policy enforced by Sui Move contract
10
+ * - Same key signs BTC and ETH
11
+ *
12
+ * SHIELDED (RedPallas via local Orchard wallet):
13
+ * - Orchard transactions signed locally (direct key)
14
+ * - Ika cannot sign RedPallas (Pallas curve not supported)
15
+ * - Privacy from Zcash protocol, not from MPC
16
+ * - Every shielded spend attested to ZAP1
17
+ *
18
+ * The gap:
19
+ * Orchard uses RedPallas on the Pallas curve. Ika supports secp256k1,
20
+ * Ed25519, secp256r1, and Ristretto. None of these are Pallas.
21
+ * Until an MPC network adds Pallas curve support, shielded custody
22
+ * requires holding keys directly.
23
+ *
24
+ * The bridge:
25
+ * shield() - move ZEC from t-addr (MPC) to shielded pool (local wallet)
26
+ * unshield() - move ZEC from shielded back to t-addr (for MPC custody)
27
+ * Both operations attested on-chain via ZAP1.
28
+ */
29
+ import { sign } from "./index.js";
30
+ /**
31
+ * Create a hybrid wallet - MPC for transparent, local for shielded.
32
+ *
33
+ * The transparent side is an Ika dWallet (already created via createWallet).
34
+ * The shielded side connects to the local Zebra/Zashi wallet.
35
+ */
36
+ export function createHybridWallet(transparentWallet, shieldedAddress, zap1ApiUrl) {
37
+ return {
38
+ transparent: {
39
+ mode: "mpc",
40
+ walletId: transparentWallet.id,
41
+ publicKey: transparentWallet.publicKey,
42
+ tAddress: transparentWallet.address,
43
+ encryptionSeed: transparentWallet.encryptionSeed,
44
+ },
45
+ shielded: {
46
+ mode: "local",
47
+ uAddress: shieldedAddress,
48
+ initialized: shieldedAddress.startsWith("u1"),
49
+ },
50
+ zap1ApiUrl,
51
+ };
52
+ }
53
+ /**
54
+ * Sign a transparent transaction through MPC.
55
+ * Delegates to the Ika sign() function.
56
+ */
57
+ export async function signTransparent(config, wallet, sighash) {
58
+ return sign(config, {
59
+ messageHash: sighash,
60
+ walletId: wallet.transparent.walletId,
61
+ chain: "zcash-transparent",
62
+ encryptionSeed: wallet.transparent.encryptionSeed,
63
+ });
64
+ }
65
+ /**
66
+ * Shield ZEC - move from MPC-custody t-addr to local shielded pool.
67
+ *
68
+ * Flow:
69
+ * 1. Build transparent tx: t-addr -> shielded address
70
+ * 2. Compute sighash (DoubleSHA256)
71
+ * 3. Sign via Ika MPC (secp256k1)
72
+ * 4. Broadcast via Zebra
73
+ * 5. Attest via ZAP1 as AGENT_ACTION
74
+ *
75
+ * After shielding, the ZEC is in the local Orchard wallet.
76
+ * Private from that point forward.
77
+ */
78
+ export async function shield(config, wallet, request) {
79
+ if (!wallet.shielded.initialized) {
80
+ throw new Error("Shielded wallet not initialized. Provide a valid u1 address.");
81
+ }
82
+ if (!config.zebraRpcUrl) {
83
+ throw new Error("shield() requires zebraRpcUrl for building and broadcasting the tx.");
84
+ }
85
+ // The full pipeline:
86
+ // 1. z_listunspent on the t-addr to find UTXOs
87
+ // 2. Build raw tx spending to the shielded address
88
+ // 3. Extract sighash
89
+ // 4. sign() via Ika MPC
90
+ // 5. Attach signature
91
+ // 6. sendrawtransaction
92
+ // 7. Attest to ZAP1
93
+ throw new Error("shield() requires Zcash transparent tx builder (zcash-primitives or librustzcash). " +
94
+ "Use sign() with a pre-built sighash for now. " +
95
+ "The shielding tx is a standard t-addr -> Orchard spend.");
96
+ }
97
+ /**
98
+ * Unshield ZEC - move from local shielded pool back to MPC-custody t-addr.
99
+ *
100
+ * Flow:
101
+ * 1. Build Orchard tx: shielded -> t-addr (signed locally, RedPallas)
102
+ * 2. Broadcast via Zebra
103
+ * 3. Attest via ZAP1 as AGENT_ACTION
104
+ *
105
+ * After unshielding, the ZEC is back under MPC custody.
106
+ * The MPC can then send it to BTC, ETH, or another t-addr.
107
+ */
108
+ export async function unshield(config, wallet, request) {
109
+ if (!config.zebraRpcUrl) {
110
+ throw new Error("unshield() requires zebraRpcUrl.");
111
+ }
112
+ // Unshielding is done entirely locally - no MPC involved.
113
+ // The Orchard wallet holds keys directly and signs with RedPallas.
114
+ // z_sendmany from the shielded address to the t-addr.
115
+ throw new Error("unshield() requires connection to local Zebra wallet with Orchard spending keys. " +
116
+ "Use z_sendmany via Zebra RPC directly for now.");
117
+ }
118
+ /**
119
+ * Attest a custody operation to ZAP1.
120
+ */
121
+ export async function attestCustodyOp(zap1ApiUrl, zap1ApiKey, op) {
122
+ try {
123
+ const resp = await fetch(`${zap1ApiUrl}/attest`, {
124
+ method: "POST",
125
+ headers: {
126
+ "Content-Type": "application/json",
127
+ Authorization: `Bearer ${zap1ApiKey}`,
128
+ },
129
+ body: JSON.stringify({
130
+ event_type: "AGENT_ACTION",
131
+ wallet_hash: op.walletId,
132
+ input_hash: op.txid,
133
+ memo: JSON.stringify({
134
+ action: op.direction,
135
+ amount: op.amount,
136
+ custody: op.direction === "shield" ? "mpc->local" : "local->mpc",
137
+ }),
138
+ }),
139
+ });
140
+ if (!resp.ok)
141
+ return null;
142
+ const data = (await resp.json());
143
+ return data.leaf_hash || null;
144
+ }
145
+ catch {
146
+ return null;
147
+ }
148
+ }