@utxopia/sdk 0.1.0-alpha.2 → 0.1.0-alpha.4

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 (142) hide show
  1. package/README.md +214 -108
  2. package/{packages/sdk/dist → dist}/client.d.ts +25 -1
  3. package/{packages/sdk/dist → dist}/client.js +36 -1
  4. package/{packages/sdk/dist → dist}/crypto-ed25519.d.ts +14 -0
  5. package/{packages/sdk/dist → dist}/crypto-ed25519.js +14 -0
  6. package/{packages/sdk/dist → dist}/index.d.ts +3 -3
  7. package/{packages/sdk/dist → dist}/index.js +3 -3
  8. package/{packages/sdk/dist → dist}/instructions.d.ts +28 -11
  9. package/{packages/sdk/dist → dist}/instructions.js +45 -15
  10. package/{packages/sdk/dist → dist}/psbt.d.ts +12 -2
  11. package/{packages/sdk/dist → dist}/psbt.js +17 -12
  12. package/{packages/sdk/dist → dist}/stealth.d.ts +104 -9
  13. package/{packages/sdk/dist → dist}/stealth.js +146 -14
  14. package/{packages/sdk/dist → dist}/taproot.d.ts +39 -2
  15. package/{packages/sdk/dist → dist}/taproot.js +54 -2
  16. package/package.json +86 -63
  17. package/src/announcement-client.ts +457 -0
  18. package/src/auditor-ciphertext.ts +181 -0
  19. package/src/auditor.ts +409 -0
  20. package/src/bitcoin/ika.ts +103 -0
  21. package/src/bitcoin/index.ts +5 -0
  22. package/src/bound-params.ts +322 -0
  23. package/src/chadbuffer.ts +603 -0
  24. package/src/circomlibjs.d.ts +51 -0
  25. package/src/claim-link.ts +53 -0
  26. package/src/client.ts +638 -0
  27. package/src/commitment-tree.ts +736 -0
  28. package/src/config.ts +772 -0
  29. package/src/core/esplora.ts +332 -0
  30. package/src/core/mempool.ts +159 -0
  31. package/src/crypto-babyjub.ts +385 -0
  32. package/src/crypto-ed25519.ts +297 -0
  33. package/src/crypto.ts +199 -0
  34. package/src/event-client.ts +231 -0
  35. package/src/events.ts +384 -0
  36. package/src/explorer.ts +300 -0
  37. package/src/index.ts +902 -0
  38. package/src/instructions.ts +2820 -0
  39. package/src/keys.ts +1228 -0
  40. package/src/logger.ts +41 -0
  41. package/src/magicblock.ts +278 -0
  42. package/src/merkle.ts +197 -0
  43. package/src/note.ts +754 -0
  44. package/src/pda.ts +516 -0
  45. package/src/pool-state.ts +176 -0
  46. package/src/poseidon.ts +175 -0
  47. package/src/prover/index.ts +19 -0
  48. package/src/prover/mobile.ts +303 -0
  49. package/src/prover/web.ts +771 -0
  50. package/src/psbt.ts +345 -0
  51. package/src/selective-disclosure.ts +284 -0
  52. package/src/sender-memo.ts +343 -0
  53. package/src/snarkjs.d.ts +19 -0
  54. package/src/sns-resolver.ts +333 -0
  55. package/src/solana/connection.ts +189 -0
  56. package/src/solana/priority-fee.ts +201 -0
  57. package/src/spend-doc.ts +163 -0
  58. package/src/stealth.ts +1477 -0
  59. package/src/taproot.ts +707 -0
  60. package/src/token-registry.ts +207 -0
  61. package/src/utils/encoding.ts +33 -0
  62. package/src/vk-registry.ts +295 -0
  63. package/LICENSE +0 -21
  64. package/packages/btc-client/src/esplora-client.ts +0 -153
  65. package/packages/btc-client/src/index.ts +0 -3
  66. package/packages/btc-client/src/op-return.ts +0 -93
  67. package/packages/btc-client/src/types.ts +0 -112
  68. package/packages/sdk/README.md +0 -277
  69. /package/{packages/sdk/dist → dist}/announcement-client.d.ts +0 -0
  70. /package/{packages/sdk/dist → dist}/announcement-client.js +0 -0
  71. /package/{packages/sdk/dist → dist}/auditor-ciphertext.d.ts +0 -0
  72. /package/{packages/sdk/dist → dist}/auditor-ciphertext.js +0 -0
  73. /package/{packages/sdk/dist → dist}/auditor.d.ts +0 -0
  74. /package/{packages/sdk/dist → dist}/auditor.js +0 -0
  75. /package/{packages/sdk/dist → dist}/bitcoin/ika.d.ts +0 -0
  76. /package/{packages/sdk/dist → dist}/bitcoin/ika.js +0 -0
  77. /package/{packages/sdk/dist → dist}/bitcoin/index.d.ts +0 -0
  78. /package/{packages/sdk/dist → dist}/bitcoin/index.js +0 -0
  79. /package/{packages/sdk/dist → dist}/bound-params.d.ts +0 -0
  80. /package/{packages/sdk/dist → dist}/bound-params.js +0 -0
  81. /package/{packages/sdk/dist → dist}/chadbuffer.d.ts +0 -0
  82. /package/{packages/sdk/dist → dist}/chadbuffer.js +0 -0
  83. /package/{packages/sdk/dist → dist}/claim-link.d.ts +0 -0
  84. /package/{packages/sdk/dist → dist}/claim-link.js +0 -0
  85. /package/{packages/sdk/dist → dist}/commitment-tree.d.ts +0 -0
  86. /package/{packages/sdk/dist → dist}/commitment-tree.js +0 -0
  87. /package/{packages/sdk/dist → dist}/config.d.ts +0 -0
  88. /package/{packages/sdk/dist → dist}/config.js +0 -0
  89. /package/{packages/sdk/dist → dist}/core/esplora.d.ts +0 -0
  90. /package/{packages/sdk/dist → dist}/core/esplora.js +0 -0
  91. /package/{packages/sdk/dist → dist}/core/mempool.d.ts +0 -0
  92. /package/{packages/sdk/dist → dist}/core/mempool.js +0 -0
  93. /package/{packages/sdk/dist → dist}/crypto-babyjub.d.ts +0 -0
  94. /package/{packages/sdk/dist → dist}/crypto-babyjub.js +0 -0
  95. /package/{packages/sdk/dist → dist}/crypto.d.ts +0 -0
  96. /package/{packages/sdk/dist → dist}/crypto.js +0 -0
  97. /package/{packages/sdk/dist → dist}/event-client.d.ts +0 -0
  98. /package/{packages/sdk/dist → dist}/event-client.js +0 -0
  99. /package/{packages/sdk/dist → dist}/events.d.ts +0 -0
  100. /package/{packages/sdk/dist → dist}/events.js +0 -0
  101. /package/{packages/sdk/dist → dist}/explorer.d.ts +0 -0
  102. /package/{packages/sdk/dist → dist}/explorer.js +0 -0
  103. /package/{packages/sdk/dist → dist}/keys.d.ts +0 -0
  104. /package/{packages/sdk/dist → dist}/keys.js +0 -0
  105. /package/{packages/sdk/dist → dist}/logger.d.ts +0 -0
  106. /package/{packages/sdk/dist → dist}/logger.js +0 -0
  107. /package/{packages/sdk/dist → dist}/magicblock.d.ts +0 -0
  108. /package/{packages/sdk/dist → dist}/magicblock.js +0 -0
  109. /package/{packages/sdk/dist → dist}/merkle.d.ts +0 -0
  110. /package/{packages/sdk/dist → dist}/merkle.js +0 -0
  111. /package/{packages/sdk/dist → dist}/note.d.ts +0 -0
  112. /package/{packages/sdk/dist → dist}/note.js +0 -0
  113. /package/{packages/sdk/dist → dist}/pda.d.ts +0 -0
  114. /package/{packages/sdk/dist → dist}/pda.js +0 -0
  115. /package/{packages/sdk/dist → dist}/pool-state.d.ts +0 -0
  116. /package/{packages/sdk/dist → dist}/pool-state.js +0 -0
  117. /package/{packages/sdk/dist → dist}/poseidon.d.ts +0 -0
  118. /package/{packages/sdk/dist → dist}/poseidon.js +0 -0
  119. /package/{packages/sdk/dist → dist}/prover/index.d.ts +0 -0
  120. /package/{packages/sdk/dist → dist}/prover/index.js +0 -0
  121. /package/{packages/sdk/dist → dist}/prover/mobile.d.ts +0 -0
  122. /package/{packages/sdk/dist → dist}/prover/mobile.js +0 -0
  123. /package/{packages/sdk/dist → dist}/prover/web.d.ts +0 -0
  124. /package/{packages/sdk/dist → dist}/prover/web.js +0 -0
  125. /package/{packages/sdk/dist → dist}/selective-disclosure.d.ts +0 -0
  126. /package/{packages/sdk/dist → dist}/selective-disclosure.js +0 -0
  127. /package/{packages/sdk/dist → dist}/sender-memo.d.ts +0 -0
  128. /package/{packages/sdk/dist → dist}/sender-memo.js +0 -0
  129. /package/{packages/sdk/dist → dist}/sns-resolver.d.ts +0 -0
  130. /package/{packages/sdk/dist → dist}/sns-resolver.js +0 -0
  131. /package/{packages/sdk/dist → dist}/solana/connection.d.ts +0 -0
  132. /package/{packages/sdk/dist → dist}/solana/connection.js +0 -0
  133. /package/{packages/sdk/dist → dist}/solana/priority-fee.d.ts +0 -0
  134. /package/{packages/sdk/dist → dist}/solana/priority-fee.js +0 -0
  135. /package/{packages/sdk/dist → dist}/spend-doc.d.ts +0 -0
  136. /package/{packages/sdk/dist → dist}/spend-doc.js +0 -0
  137. /package/{packages/sdk/dist → dist}/token-registry.d.ts +0 -0
  138. /package/{packages/sdk/dist → dist}/token-registry.js +0 -0
  139. /package/{packages/sdk/dist → dist}/utils/encoding.d.ts +0 -0
  140. /package/{packages/sdk/dist → dist}/utils/encoding.js +0 -0
  141. /package/{packages/sdk/dist → dist}/vk-registry.d.ts +0 -0
  142. /package/{packages/sdk/dist → dist}/vk-registry.js +0 -0
package/src/logger.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Conditional debug logger for UTXOPIA SDK.
3
+ *
4
+ * Set `UTXOPIA_DEBUG=1` (Node.js) or `localStorage.utxopiaDebug = "1"` (browser)
5
+ * to enable debug output. All logs are suppressed by default.
6
+ */
7
+
8
+ let _enabled: boolean | null = null;
9
+
10
+ function isEnabled(): boolean {
11
+ if (_enabled !== null) return _enabled;
12
+ try {
13
+ // Node.js
14
+ if (typeof process !== "undefined" && process.env?.UTXOPIA_DEBUG === "1") {
15
+ _enabled = true;
16
+ return true;
17
+ }
18
+ } catch { /* not Node */ }
19
+ try {
20
+ // Browser
21
+ if (typeof localStorage !== "undefined" && localStorage.getItem("utxopiaDebug") === "1") {
22
+ _enabled = true;
23
+ return true;
24
+ }
25
+ } catch { /* no localStorage */ }
26
+ _enabled = false;
27
+ return false;
28
+ }
29
+
30
+ export function debug(tag: string, ...args: unknown[]): void {
31
+ if (isEnabled()) console.log(`[utxo:${tag}]`, ...args);
32
+ }
33
+
34
+ export function warn(tag: string, ...args: unknown[]): void {
35
+ console.warn(`[utxo:${tag}]`, ...args);
36
+ }
37
+
38
+ /** Force-enable or disable debug logging at runtime */
39
+ export function setDebug(enabled: boolean): void {
40
+ _enabled = enabled;
41
+ }
@@ -0,0 +1,278 @@
1
+ import { getAddressEncoder, getProgramDerivedAddress, type Address } from "@solana/kit";
2
+ import { Connection as MagicBlockConnection } from "@magicblock-labs/ephemeral-rollups-kit";
3
+ import { address, type NetworkConfig } from "./config";
4
+
5
+ export type MagicBlockExecutionMode = "solana" | "er" | "per";
6
+ export type MagicBlockPolicyMode = "disabled" | "per";
7
+ export type MagicBlockValidatorRegion = "asia" | "eu" | "us" | "tee" | "local";
8
+
9
+ export type PrivacyDomainKind = "public" | "institution";
10
+
11
+ export interface MagicBlockEndpointConfig {
12
+ /** Magic Router HTTP endpoint used for delegated-account-aware routing. */
13
+ routerUrl?: string;
14
+ /** Magic Router WebSocket endpoint used for confirmations/subscriptions. */
15
+ routerWsUrl?: string;
16
+ /** MagicBlock ER endpoint used for low-latency public shielded transfers. */
17
+ erUrl?: string;
18
+ /** MagicBlock PER endpoint used for private institution policy execution. */
19
+ perUrl?: string;
20
+ /** Validator region selected for delegation. Use "tee" for PER. */
21
+ validatorRegion?: MagicBlockValidatorRegion;
22
+ }
23
+
24
+ export interface PrivacyDomainConfig {
25
+ /** Stable domain identifier. Bind this into future circuit bound params before multi-domain launch. */
26
+ domainId: string;
27
+ /** Human-readable label for UI and logs. */
28
+ label: string;
29
+ /** Product/compliance category for this privacy domain. */
30
+ kind: PrivacyDomainKind;
31
+ /** Execution lane selected for this domain. */
32
+ executionMode: MagicBlockExecutionMode;
33
+ /** Optional PER policy coprocessor. Asset execution remains Solana. */
34
+ policyMode: MagicBlockPolicyMode;
35
+ /** UTXOpia program instance for this domain. */
36
+ programId: Address;
37
+ /** Pool state PDA for this domain. */
38
+ poolStatePda: Address;
39
+ /** Active commitment tree PDA for this domain. */
40
+ commitmentTreePda: Address;
41
+ /** Optional auditor signer for permissioned domains. */
42
+ auditor?: Address;
43
+ /** Optional auditor viewing public key, encoded by the caller. */
44
+ auditorViewingPubkey?: string;
45
+ /** Optional MagicBlock endpoints for ER/PER routing. */
46
+ magicblock?: MagicBlockEndpointConfig;
47
+ }
48
+
49
+ export interface BuildPrivacyDomainOptions {
50
+ domainId?: string;
51
+ label?: string;
52
+ kind?: PrivacyDomainKind;
53
+ executionMode?: MagicBlockExecutionMode;
54
+ policyMode?: MagicBlockPolicyMode;
55
+ auditor?: Address;
56
+ auditorViewingPubkey?: string;
57
+ magicblock?: MagicBlockEndpointConfig;
58
+ }
59
+
60
+ export const MAGICBLOCK_DELEGATION_PROGRAM_ID = address(
61
+ "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh"
62
+ );
63
+
64
+ export const MAGICBLOCK_PERMISSION_PROGRAM_ID = address(
65
+ "ACLseoPoyC3cBqoUtkbjZ4aDrkurZW86v19pXz2XQnp1"
66
+ );
67
+
68
+ export const MAGICBLOCK_MAGIC_PROGRAM_ID = address("Magic11111111111111111111111111111111111111");
69
+ export const MAGICBLOCK_MAGIC_CONTEXT_ID = address(
70
+ "MagicContext1111111111111111111111111111111"
71
+ );
72
+ export const MAGICBLOCK_EPHEMERAL_VAULT_ID = address(
73
+ "MagicVau1t999999999999999999999999999999999"
74
+ );
75
+
76
+ export const MAGICBLOCK_DEVNET_ROUTER_URL = "https://devnet-router.magicblock.app";
77
+ export const MAGICBLOCK_DEVNET_ROUTER_WS_URL = "wss://devnet-router.magicblock.app";
78
+
79
+ export const MAGICBLOCK_VALIDATOR_IDENTITIES: Record<MagicBlockValidatorRegion, Address> = {
80
+ asia: address("MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57"),
81
+ eu: address("MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e"),
82
+ us: address("MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd"),
83
+ tee: address("MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo"),
84
+ local: address("mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev"),
85
+ };
86
+
87
+ export const MAGICBLOCK_PER_MEMBER_FLAGS = {
88
+ authority: 1 << 0,
89
+ txLogs: 1 << 1,
90
+ txBalances: 1 << 2,
91
+ txMessage: 1 << 3,
92
+ accountSignatures: 1 << 4,
93
+ } as const;
94
+
95
+ export const MAGICBLOCK_MAX_PER_MEMBERS = 8;
96
+
97
+ export type MagicBlockPerMemberFlagName = keyof typeof MAGICBLOCK_PER_MEMBER_FLAGS;
98
+
99
+ export function buildMagicBlockPerMemberFlags(flags: MagicBlockPerMemberFlagName[]): number {
100
+ return flags.reduce((acc, flag) => acc | MAGICBLOCK_PER_MEMBER_FLAGS[flag], 0);
101
+ }
102
+
103
+ export function getMagicBlockValidatorIdentity(region: MagicBlockValidatorRegion): Address {
104
+ return MAGICBLOCK_VALIDATOR_IDENTITIES[region];
105
+ }
106
+
107
+ function magicBlockAddressBytes(value: Address): Uint8Array {
108
+ return new Uint8Array(getAddressEncoder().encode(value));
109
+ }
110
+
111
+ export async function deriveMagicBlockDelegationRecordPDA(
112
+ delegatedAccount: Address
113
+ ): Promise<[Address, number]> {
114
+ const result = await getProgramDerivedAddress({
115
+ programAddress: MAGICBLOCK_DELEGATION_PROGRAM_ID,
116
+ seeds: [new TextEncoder().encode("delegation"), magicBlockAddressBytes(delegatedAccount)],
117
+ });
118
+ return [result[0], result[1]];
119
+ }
120
+
121
+ export async function deriveMagicBlockDelegationMetadataPDA(
122
+ delegatedAccount: Address
123
+ ): Promise<[Address, number]> {
124
+ const result = await getProgramDerivedAddress({
125
+ programAddress: MAGICBLOCK_DELEGATION_PROGRAM_ID,
126
+ seeds: [
127
+ new TextEncoder().encode("delegation-metadata"),
128
+ magicBlockAddressBytes(delegatedAccount),
129
+ ],
130
+ });
131
+ return [result[0], result[1]];
132
+ }
133
+
134
+ export async function deriveMagicBlockDelegateBufferPDA(
135
+ delegatedAccount: Address,
136
+ ownerProgram: Address
137
+ ): Promise<[Address, number]> {
138
+ const result = await getProgramDerivedAddress({
139
+ programAddress: ownerProgram,
140
+ seeds: [new TextEncoder().encode("buffer"), magicBlockAddressBytes(delegatedAccount)],
141
+ });
142
+ return [result[0], result[1]];
143
+ }
144
+
145
+ export async function deriveMagicBlockUndelegateBufferPDA(
146
+ delegatedAccount: Address
147
+ ): Promise<[Address, number]> {
148
+ const result = await getProgramDerivedAddress({
149
+ programAddress: MAGICBLOCK_DELEGATION_PROGRAM_ID,
150
+ seeds: [new TextEncoder().encode("undelegate-buffer"), magicBlockAddressBytes(delegatedAccount)],
151
+ });
152
+ return [result[0], result[1]];
153
+ }
154
+
155
+ export async function deriveMagicBlockCommitStatePDA(
156
+ delegatedAccount: Address
157
+ ): Promise<[Address, number]> {
158
+ const result = await getProgramDerivedAddress({
159
+ programAddress: MAGICBLOCK_DELEGATION_PROGRAM_ID,
160
+ seeds: [new TextEncoder().encode("state-diff"), magicBlockAddressBytes(delegatedAccount)],
161
+ });
162
+ return [result[0], result[1]];
163
+ }
164
+
165
+ export async function deriveMagicBlockCommitRecordPDA(
166
+ delegatedAccount: Address
167
+ ): Promise<[Address, number]> {
168
+ const result = await getProgramDerivedAddress({
169
+ programAddress: MAGICBLOCK_DELEGATION_PROGRAM_ID,
170
+ seeds: [
171
+ new TextEncoder().encode("commit-state-record"),
172
+ magicBlockAddressBytes(delegatedAccount),
173
+ ],
174
+ });
175
+ return [result[0], result[1]];
176
+ }
177
+
178
+ export async function deriveMagicBlockPermissionPDA(
179
+ permissionedAccount: Address
180
+ ): Promise<[Address, number]> {
181
+ const result = await getProgramDerivedAddress({
182
+ programAddress: MAGICBLOCK_PERMISSION_PROGRAM_ID,
183
+ seeds: [
184
+ new TextEncoder().encode("permission:"),
185
+ magicBlockAddressBytes(permissionedAccount),
186
+ ],
187
+ });
188
+ return [result[0], result[1]];
189
+ }
190
+
191
+ export function buildDefaultPrivacyDomain(
192
+ config: NetworkConfig,
193
+ options: BuildPrivacyDomainOptions = {}
194
+ ): PrivacyDomainConfig {
195
+ return {
196
+ domainId: options.domainId ?? "public",
197
+ label: options.label ?? "Public Pool",
198
+ kind: options.kind ?? "public",
199
+ executionMode: options.executionMode ?? "solana",
200
+ policyMode: options.policyMode ?? "disabled",
201
+ programId: config.utxopiaProgramId,
202
+ poolStatePda: config.poolStatePda,
203
+ commitmentTreePda: config.commitmentTreePda,
204
+ auditor: options.auditor,
205
+ auditorViewingPubkey: options.auditorViewingPubkey,
206
+ magicblock: {
207
+ routerUrl: MAGICBLOCK_DEVNET_ROUTER_URL,
208
+ routerWsUrl: MAGICBLOCK_DEVNET_ROUTER_WS_URL,
209
+ validatorRegion: options.policyMode === "per" ? "tee" : "asia",
210
+ ...options.magicblock,
211
+ },
212
+ };
213
+ }
214
+
215
+ export function requiresMagicBlockEndpoint(mode: MagicBlockExecutionMode): boolean {
216
+ return mode === "er" || mode === "per";
217
+ }
218
+
219
+ export function getMagicBlockEndpoint(
220
+ domain: PrivacyDomainConfig,
221
+ mode: MagicBlockExecutionMode = domain.executionMode
222
+ ): string | undefined {
223
+ if (mode === "er") {
224
+ return domain.magicblock?.erUrl;
225
+ }
226
+ if (mode === "per") {
227
+ return domain.magicblock?.perUrl;
228
+ }
229
+ return undefined;
230
+ }
231
+
232
+ export function assertMagicBlockRouteReady(domain: PrivacyDomainConfig): void {
233
+ const mode = domain.executionMode;
234
+ if (!requiresMagicBlockEndpoint(mode)) {
235
+ return;
236
+ }
237
+
238
+ const endpoint = getMagicBlockEndpoint(domain, mode);
239
+ if (!endpoint) {
240
+ throw new Error(
241
+ `Privacy domain "${domain.domainId}" uses ${mode.toUpperCase()} but no MagicBlock ${mode.toUpperCase()} endpoint is configured`
242
+ );
243
+ }
244
+ }
245
+
246
+ export function assertDomainExecutionPolicy(domain: PrivacyDomainConfig): void {
247
+ if (domain.executionMode !== "solana") {
248
+ throw new Error("UTXOpia asset execution must remain on Solana");
249
+ }
250
+ if (domain.kind === "public" && domain.policyMode === "per") {
251
+ throw new Error("Public permissionless domains must not require PER policy");
252
+ }
253
+ if (domain.policyMode === "per" && domain.magicblock?.validatorRegion !== "tee") {
254
+ throw new Error("PER policy domains must use the TEE validator region");
255
+ }
256
+ if (domain.policyMode === "per" && !domain.magicblock?.perUrl) {
257
+ throw new Error(`Privacy domain "${domain.domainId}" requires a PER policy endpoint`);
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Create the delegated-account-aware connection used for public ER traffic.
263
+ * PER uses the authenticated server relay because the kit connection does not
264
+ * expose request headers for the signed-challenge bearer token.
265
+ */
266
+ export async function createMagicBlockRouterConnection(
267
+ domain: PrivacyDomainConfig
268
+ ): Promise<MagicBlockConnection> {
269
+ assertDomainExecutionPolicy(domain);
270
+ if (domain.executionMode !== "er") {
271
+ throw new Error("MagicBlock kit router connections are supported only for public ER domains");
272
+ }
273
+ const routerUrl = domain.magicblock?.routerUrl;
274
+ if (!routerUrl) {
275
+ throw new Error("MagicBlock router URL is required for ER execution");
276
+ }
277
+ return MagicBlockConnection.create(routerUrl, domain.magicblock?.routerWsUrl);
278
+ }
package/src/merkle.ts ADDED
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Merkle tree utilities for UTXOpia
3
+ *
4
+ * Provides structures and helpers for Merkle proofs.
5
+ * Actual tree operations use Poseidon hashing which is computed
6
+ * on-chain or via circom circuits.
7
+ *
8
+ * Note: This module does NOT compute Poseidon hashes in JavaScript.
9
+ * The on-chain program maintains the Merkle tree. This SDK provides
10
+ * proof structures for interaction with the program.
11
+ */
12
+
13
+ import { bigintToBytes, bytesToBigint } from "./crypto";
14
+ import { toHex } from "./utils/encoding";
15
+
16
+ // Tree configuration - matches on-chain constants (depth 16 = 65,536 leaves)
17
+ export const TREE_DEPTH = 16;
18
+ export const ROOT_HISTORY_SIZE = 30;
19
+ export const MAX_LEAVES = 1 << TREE_DEPTH; // 65,536
20
+
21
+ // Zero value for empty nodes (matches on-chain)
22
+ export const ZERO_VALUE = bigintToBytes(
23
+ 0x2fe54c60d3ada40e0000000000000000000000000000000000000000n
24
+ );
25
+
26
+ /**
27
+ * Merkle proof structure
28
+ */
29
+ export interface MerkleProof {
30
+ // Sibling nodes along the path (20 elements for depth 20)
31
+ pathElements: Uint8Array[];
32
+ // Path indices (0 = left, 1 = right)
33
+ pathIndices: number[];
34
+ // Leaf index
35
+ leafIndex: number;
36
+ // Merkle root
37
+ root: Uint8Array;
38
+ }
39
+
40
+ /**
41
+ * Create a Merkle proof from on-chain data
42
+ *
43
+ * @param pathElements - Sibling hashes as 32-byte arrays
44
+ * @param pathIndices - Direction at each level (0=left, 1=right)
45
+ * @param leafIndex - Index of the leaf in the tree
46
+ * @param root - Current Merkle root
47
+ */
48
+ export function createMerkleProof(
49
+ pathElements: Uint8Array[],
50
+ pathIndices: number[],
51
+ leafIndex: number,
52
+ root: Uint8Array
53
+ ): MerkleProof {
54
+ if (pathElements.length !== TREE_DEPTH) {
55
+ throw new Error(`Expected ${TREE_DEPTH} path elements, got ${pathElements.length}`);
56
+ }
57
+ if (pathIndices.length !== TREE_DEPTH) {
58
+ throw new Error(`Expected ${TREE_DEPTH} path indices, got ${pathIndices.length}`);
59
+ }
60
+
61
+ return {
62
+ pathElements: pathElements.map((el) => new Uint8Array(el)),
63
+ pathIndices: [...pathIndices],
64
+ leafIndex,
65
+ root: new Uint8Array(root),
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Create a Merkle proof from bigint values
71
+ */
72
+ export function createMerkleProofFromBigints(
73
+ pathElements: bigint[],
74
+ pathIndices: number[],
75
+ leafIndex: number,
76
+ root: bigint
77
+ ): MerkleProof {
78
+ return createMerkleProof(
79
+ pathElements.map(bigintToBytes),
80
+ pathIndices,
81
+ leafIndex,
82
+ bigintToBytes(root)
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Convert Merkle proof to format expected by circom circuits
88
+ */
89
+ export function proofToCircomFormat(proof: MerkleProof): {
90
+ merkle_path: string[];
91
+ path_indices: string[];
92
+ merkle_root: string;
93
+ } {
94
+ return {
95
+ merkle_path: proof.pathElements.map(
96
+ (el) => "0x" + toHex(el)
97
+ ),
98
+ path_indices: proof.pathIndices.map((i) => i.toString()),
99
+ merkle_root: "0x" + toHex(proof.root),
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Convert Merkle proof to format expected by on-chain program
105
+ */
106
+ export function proofToOnChainFormat(proof: MerkleProof): {
107
+ siblings: number[][];
108
+ path: boolean[];
109
+ } {
110
+ return {
111
+ siblings: proof.pathElements.map((el) => Array.from(el)),
112
+ path: proof.pathIndices.map((i) => i === 1),
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Compute leaf index from path indices
118
+ */
119
+ export function pathIndicesToLeafIndex(pathIndices: number[]): number {
120
+ let index = 0;
121
+ for (let i = 0; i < pathIndices.length; i++) {
122
+ if (pathIndices[i] === 1) {
123
+ index |= 1 << i;
124
+ }
125
+ }
126
+ return index;
127
+ }
128
+
129
+ /**
130
+ * Compute path indices from leaf index
131
+ */
132
+ export function leafIndexToPathIndices(leafIndex: number, depth: number = TREE_DEPTH): number[] {
133
+ const indices: number[] = [];
134
+ let idx = leafIndex;
135
+ for (let i = 0; i < depth; i++) {
136
+ indices.push(idx & 1);
137
+ idx >>= 1;
138
+ }
139
+ return indices;
140
+ }
141
+
142
+ /**
143
+ * Create an empty/placeholder Merkle proof
144
+ * Used when constructing proofs before on-chain data is available
145
+ */
146
+ export function createEmptyMerkleProof(): MerkleProof {
147
+ const pathElements: Uint8Array[] = [];
148
+ const pathIndices: number[] = [];
149
+
150
+ for (let i = 0; i < TREE_DEPTH; i++) {
151
+ pathElements.push(new Uint8Array(ZERO_VALUE));
152
+ pathIndices.push(0);
153
+ }
154
+
155
+ return {
156
+ pathElements,
157
+ pathIndices,
158
+ leafIndex: 0,
159
+ root: new Uint8Array(ZERO_VALUE),
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Parse a merkle proof response (hex strings) into BigInt format for circuit input.
165
+ * Accepts the response from /api/merkle/proof or /api/tree/proof.
166
+ */
167
+ export function parseMerkleProofResponse(response: {
168
+ root: string;
169
+ siblings: string[];
170
+ indices: number[];
171
+ }): { root: bigint; pathElements: bigint[]; pathIndices: number[] } {
172
+ return {
173
+ root: BigInt("0x" + response.root),
174
+ pathElements: response.siblings.map((s) => BigInt("0x" + s)),
175
+ pathIndices: response.indices,
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Validate Merkle proof structure
181
+ */
182
+ export function validateMerkleProofStructure(proof: MerkleProof): boolean {
183
+ if (proof.pathElements.length !== TREE_DEPTH) return false;
184
+ if (proof.pathIndices.length !== TREE_DEPTH) return false;
185
+ if (proof.leafIndex < 0 || proof.leafIndex >= MAX_LEAVES) return false;
186
+ if (proof.root.length !== 32) return false;
187
+
188
+ for (const el of proof.pathElements) {
189
+ if (el.length !== 32) return false;
190
+ }
191
+
192
+ for (const idx of proof.pathIndices) {
193
+ if (idx !== 0 && idx !== 1) return false;
194
+ }
195
+
196
+ return true;
197
+ }