@no-witness-labs/midday-sdk 0.2.4 → 0.2.5

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 (66) hide show
  1. package/README.md +60 -82
  2. package/dist/Client.d.ts +248 -332
  3. package/dist/Client.d.ts.map +1 -1
  4. package/dist/Client.js +318 -359
  5. package/dist/Client.js.map +1 -1
  6. package/dist/Config.d.ts +2 -57
  7. package/dist/Config.d.ts.map +1 -1
  8. package/dist/Config.js +1 -47
  9. package/dist/Config.js.map +1 -1
  10. package/dist/Hash.d.ts +126 -0
  11. package/dist/Hash.d.ts.map +1 -0
  12. package/dist/Hash.js +146 -0
  13. package/dist/Hash.js.map +1 -0
  14. package/dist/PrivateState.d.ts +9 -0
  15. package/dist/PrivateState.d.ts.map +1 -0
  16. package/dist/PrivateState.js +9 -0
  17. package/dist/PrivateState.js.map +1 -0
  18. package/dist/Providers.d.ts +42 -63
  19. package/dist/Providers.d.ts.map +1 -1
  20. package/dist/Providers.js +34 -62
  21. package/dist/Providers.js.map +1 -1
  22. package/dist/Runtime.d.ts +8 -0
  23. package/dist/Runtime.d.ts.map +1 -0
  24. package/dist/Runtime.js +8 -0
  25. package/dist/Runtime.js.map +1 -0
  26. package/dist/Wallet.d.ts +1 -1
  27. package/dist/Wallet.d.ts.map +1 -1
  28. package/dist/Wallet.js +2 -0
  29. package/dist/Wallet.js.map +1 -1
  30. package/dist/ZkConfig.d.ts +80 -0
  31. package/dist/ZkConfig.d.ts.map +1 -0
  32. package/dist/ZkConfig.js +85 -0
  33. package/dist/ZkConfig.js.map +1 -0
  34. package/dist/devnet/Cluster.d.ts +0 -9
  35. package/dist/devnet/Cluster.d.ts.map +1 -1
  36. package/dist/devnet/Cluster.js +0 -13
  37. package/dist/devnet/Cluster.js.map +1 -1
  38. package/dist/devnet/index.d.ts +9 -8
  39. package/dist/devnet/index.d.ts.map +1 -1
  40. package/dist/devnet/index.js +9 -8
  41. package/dist/devnet/index.js.map +1 -1
  42. package/dist/index.d.ts +30 -47
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +33 -52
  45. package/dist/index.js.map +1 -1
  46. package/dist/providers/HttpZkConfigProvider.d.ts +8 -6
  47. package/dist/providers/HttpZkConfigProvider.d.ts.map +1 -1
  48. package/dist/providers/HttpZkConfigProvider.js +8 -6
  49. package/dist/providers/HttpZkConfigProvider.js.map +1 -1
  50. package/dist/providers/IndexedDBPrivateStateProvider.d.ts +8 -8
  51. package/dist/providers/IndexedDBPrivateStateProvider.js +8 -8
  52. package/dist/utils/index.d.ts +9 -0
  53. package/dist/utils/index.d.ts.map +1 -0
  54. package/dist/utils/index.js +9 -0
  55. package/dist/utils/index.js.map +1 -0
  56. package/dist/wallet/connector.d.ts +1 -1
  57. package/dist/wallet/connector.js +1 -1
  58. package/dist/wallet/index.d.ts +15 -0
  59. package/dist/wallet/index.d.ts.map +1 -0
  60. package/dist/wallet/index.js +18 -0
  61. package/dist/wallet/index.js.map +1 -0
  62. package/package.json +1 -1
  63. package/dist/sdk/Type.d.ts +0 -91
  64. package/dist/sdk/Type.d.ts.map +0 -1
  65. package/dist/sdk/Type.js +0 -8
  66. package/dist/sdk/Type.js.map +0 -1
package/dist/Client.js CHANGED
@@ -3,26 +3,31 @@
3
3
  *
4
4
  * ## API Design
5
5
  *
6
- * This module uses a **module-function pattern**:
6
+ * This module uses a **Client-centric hub pattern** following the Effect hybrid pattern:
7
7
  *
8
- * - **Stateless**: Functions operate on Client/Contract data
9
- * - **Module functions**: `Client.contractFrom(client, options)`, `Contract.deploy(builder)`
10
- * - **Data-oriented**: Client/Contract are plain data, not instances with methods
8
+ * - **Effect is source of truth**: All logic in Effect functions
9
+ * - **Client is the hub**: All operations flow from the client
10
+ * - **Two interfaces**: `.effect.method()` for Effect users, `.method()` for Promise users
11
+ * - **Effects call Effects, Promises call Promises**: Never mix execution models
11
12
  *
12
13
  * ### Usage Patterns
13
14
  *
14
15
  * ```typescript
15
- * // Promise user
16
- * const client = await Client.create(config);
17
- * const builder = await Client.contractFrom(client, { module });
18
- * const contract = await ContractBuilder.deploy(builder);
19
- * const result = await Contract.call(contract, 'increment');
20
- *
21
- * // Effect user
22
- * const client = yield* Client.effect.create(config);
23
- * const builder = yield* Client.effect.contractFrom(client, { module });
24
- * const contract = yield* ContractBuilder.effect.deploy(builder);
25
- * const result = yield* Contract.effect.call(contract, 'increment');
16
+ * // Promise user - simple flow
17
+ * const client = await Midday.Client.create(config);
18
+ * const contract = await client.loadContract({ path: './contracts/counter' });
19
+ * await contract.deploy();
20
+ * await contract.call('increment');
21
+ * const state = await contract.ledgerState();
22
+ *
23
+ * // Effect user - compositional
24
+ * const program = Effect.gen(function* () {
25
+ * const client = yield* Midday.Client.effect.create(config);
26
+ * const contract = yield* client.effect.loadContract({ path: './contracts/counter' });
27
+ * yield* contract.effect.deploy();
28
+ * yield* contract.effect.call('increment');
29
+ * const state = yield* contract.effect.ledgerState();
30
+ * });
26
31
  * ```
27
32
  *
28
33
  * @since 0.1.0
@@ -55,11 +60,11 @@ export class ClientError extends Data.TaggedError('ClientError') {
55
60
  export class ContractError extends Data.TaggedError('ContractError') {
56
61
  }
57
62
  // =============================================================================
58
- // Client - Internal Effect Implementations
63
+ // Effect Implementations (Source of Truth)
59
64
  // =============================================================================
60
- function createEffect(config) {
65
+ function createClientDataEffect(config) {
61
66
  return Effect.gen(function* () {
62
- const { network = 'local', networkConfig: customNetworkConfig, seed, zkConfigProvider, privateStateProvider, storage, logging = true, } = config;
67
+ const { network = 'local', networkConfig: customNetworkConfig, seed, privateStateProvider, storage, logging = true, } = config;
63
68
  // Resolve network configuration
64
69
  const networkConfig = customNetworkConfig ?? Config.getNetworkConfig(network);
65
70
  // Resolve seed (use dev wallet only for local network)
@@ -81,13 +86,13 @@ function createEffect(config) {
81
86
  message: `Failed to sync wallet: ${e.message}`,
82
87
  })));
83
88
  yield* Effect.logDebug('Wallet synced');
89
+ // Create base providers (no zkConfig - that's per-contract)
84
90
  const providerOptions = {
85
91
  networkConfig,
86
- zkConfigProvider,
87
92
  privateStateProvider,
88
93
  storageConfig: storage,
89
94
  };
90
- const providers = Providers.create(walletContext, providerOptions);
95
+ const providers = Providers.createBase(walletContext, providerOptions);
91
96
  return {
92
97
  wallet: walletContext,
93
98
  networkConfig,
@@ -96,9 +101,9 @@ function createEffect(config) {
96
101
  };
97
102
  });
98
103
  }
99
- function fromWalletEffect(connection, config) {
104
+ function fromWalletDataEffect(connection, config) {
100
105
  return Effect.gen(function* () {
101
- const { zkConfigProvider, privateStateProvider, logging = true } = config;
106
+ const { privateStateProvider, logging = true } = config;
102
107
  // Create network config from wallet configuration
103
108
  const networkConfig = {
104
109
  networkId: connection.config.networkId,
@@ -109,13 +114,13 @@ function fromWalletEffect(connection, config) {
109
114
  };
110
115
  // Create wallet providers from connection
111
116
  const { walletProvider, midnightProvider } = createWalletProviders(connection.wallet, connection.addresses);
117
+ // Create base providers (no zkConfig - that's per-contract)
112
118
  const providerOptions = {
113
119
  networkConfig,
114
- zkConfigProvider,
115
120
  privateStateProvider,
116
121
  };
117
122
  // Create providers using the wallet providers
118
- const providers = Providers.createFromWalletProviders(walletProvider, midnightProvider, providerOptions);
123
+ const providers = Providers.createBaseFromWalletProviders(walletProvider, midnightProvider, providerOptions);
119
124
  yield* Effect.logDebug('Connected to wallet');
120
125
  return {
121
126
  wallet: null,
@@ -128,22 +133,57 @@ function fromWalletEffect(connection, config) {
128
133
  message: `Failed to create client from wallet: ${defect instanceof Error ? defect.message : String(defect)}`,
129
134
  }))));
130
135
  }
131
- function contractFromEffect(client, options) {
132
- return Effect.try({
133
- try: () => {
134
- if (!options.module) {
135
- throw new Error('Contract module is required. Import and pass the contract module.');
136
+ function loadContractEffect(clientData, options) {
137
+ return Effect.tryPromise({
138
+ try: async () => {
139
+ let module;
140
+ let zkConfig;
141
+ // Determine loading method
142
+ if (options.path) {
143
+ // Path-based loading (Node.js)
144
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
145
+ const { join } = require('path');
146
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
147
+ const { NodeZkConfigProvider } = require('@midnight-ntwrk/midnight-js-node-zk-config-provider');
148
+ const modulePath = join(options.path, 'contract', 'index.js');
149
+ module = await import(modulePath);
150
+ zkConfig = new NodeZkConfigProvider(options.path);
151
+ }
152
+ else if (options.moduleUrl && options.zkConfigBaseUrl) {
153
+ // URL-based loading (browser)
154
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
155
+ const { HttpZkConfigProvider } = require('./providers/HttpZkConfigProvider.js');
156
+ module = await import(/* webpackIgnore: true */ options.moduleUrl);
157
+ zkConfig = new HttpZkConfigProvider(options.zkConfigBaseUrl);
158
+ }
159
+ else if (options.module && options.zkConfig) {
160
+ // Direct loading
161
+ module = options.module;
162
+ zkConfig = options.zkConfig;
136
163
  }
137
- const module = {
138
- Contract: options.module.Contract,
139
- ledger: options.module.ledger,
164
+ else {
165
+ throw new Error('Contract loading requires one of: ' +
166
+ '(1) module + zkConfig, ' +
167
+ '(2) path (Node.js), or ' +
168
+ '(3) moduleUrl + zkConfigBaseUrl (browser)');
169
+ }
170
+ const loadedModule = {
171
+ Contract: module.Contract,
172
+ ledger: module.ledger,
140
173
  privateStateId: options.privateStateId ?? 'contract',
141
174
  witnesses: options.witnesses ?? {},
142
175
  };
176
+ // Create full providers with zkConfig for this contract
177
+ const providers = {
178
+ ...clientData.providers,
179
+ zkConfigProvider: zkConfig,
180
+ };
143
181
  return {
144
- module,
145
- providers: client.providers,
146
- logging: client.logging,
182
+ module: loadedModule,
183
+ providers,
184
+ logging: clientData.logging,
185
+ address: undefined,
186
+ instance: undefined,
147
187
  };
148
188
  },
149
189
  catch: (cause) => new ClientError({
@@ -152,10 +192,10 @@ function contractFromEffect(client, options) {
152
192
  }),
153
193
  });
154
194
  }
155
- function waitForTxEffect(client, txHash) {
195
+ function waitForTxEffect(clientData, txHash) {
156
196
  return Effect.tryPromise({
157
197
  try: async () => {
158
- const data = await client.providers.publicDataProvider.watchForTxData(txHash);
198
+ const data = await clientData.providers.publicDataProvider.watchForTxData(txHash);
159
199
  return {
160
200
  txHash: data.txHash,
161
201
  blockHeight: data.blockHeight,
@@ -168,86 +208,17 @@ function waitForTxEffect(client, txHash) {
168
208
  }),
169
209
  });
170
210
  }
171
- // =============================================================================
172
- // Client - Promise API
173
- // =============================================================================
174
- /**
175
- * Create a Midnight client for interacting with contracts using a seed.
176
- *
177
- * @example
178
- * ```typescript
179
- * const client = await Client.create({
180
- * seed: 'your-64-char-hex-seed',
181
- * networkConfig: Config.NETWORKS.local,
182
- * zkConfigProvider,
183
- * privateStateProvider,
184
- * });
185
- * ```
186
- *
187
- * @since 0.2.0
188
- * @category constructors
189
- */
190
- export async function create(config) {
191
- const logging = config.logging ?? true;
192
- return runEffectWithLogging(createEffect(config), logging);
193
- }
194
- /**
195
- * Create a Midnight client from a connected wallet (browser).
196
- *
197
- * @since 0.2.0
198
- * @category constructors
199
- */
200
- export async function fromWallet(connection, config) {
201
- const logging = config.logging ?? true;
202
- return runEffectWithLogging(fromWalletEffect(connection, config), logging);
203
- }
204
- /**
205
- * Load a contract module for a client.
206
- *
207
- * @example
208
- * ```typescript
209
- * const builder = await Client.contractFrom(client, {
210
- * module: await import('./contracts/counter/index.js'),
211
- * });
212
- * ```
213
- *
214
- * @since 0.2.0
215
- * @category operations
216
- */
217
- export async function contractFrom(client, options) {
218
- return runEffectWithLogging(contractFromEffect(client, options), client.logging);
219
- }
220
- /**
221
- * Wait for a transaction to be finalized.
222
- *
223
- * @since 0.2.0
224
- * @category operations
225
- */
226
- export async function waitForTx(client, txHash) {
227
- return runEffectWithLogging(waitForTxEffect(client, txHash), client.logging);
228
- }
229
- /**
230
- * Raw Effect APIs for advanced users.
231
- *
232
- * @example
233
- * ```typescript
234
- * const client = yield* Client.effect.create(config);
235
- * const builder = yield* Client.effect.contractFrom(client, { module });
236
- * ```
237
- *
238
- * @since 0.2.0
239
- * @category effect
240
- */
241
- export const effect = {
242
- create: createEffect,
243
- fromWallet: fromWalletEffect,
244
- contractFrom: contractFromEffect,
245
- waitForTx: waitForTxEffect,
246
- };
247
- function deployEffect(builder, options) {
211
+ function deployContractEffect(contractData, options) {
248
212
  return Effect.gen(function* () {
213
+ // State machine check
214
+ if (contractData.address !== undefined) {
215
+ return yield* Effect.fail(new ContractError({
216
+ cause: new Error('Already deployed'),
217
+ message: `Contract already deployed at ${contractData.address}. Cannot deploy again.`,
218
+ }));
219
+ }
249
220
  const { initialPrivateState = {} } = options ?? {};
250
- const { module, providers, logging } = builder;
221
+ const { module, providers, logging } = contractData;
251
222
  yield* Effect.logDebug('Deploying contract...');
252
223
  const ContractClass = module.Contract;
253
224
  const contract = new ContractClass(module.witnesses);
@@ -277,10 +248,17 @@ function deployEffect(builder, options) {
277
248
  };
278
249
  });
279
250
  }
280
- function joinEffect(builder, address, options) {
251
+ function joinContractEffect(contractData, address, options) {
281
252
  return Effect.gen(function* () {
253
+ // State machine check
254
+ if (contractData.address !== undefined) {
255
+ return yield* Effect.fail(new ContractError({
256
+ cause: new Error('Already deployed'),
257
+ message: `Contract already connected at ${contractData.address}. Cannot join another.`,
258
+ }));
259
+ }
282
260
  const { initialPrivateState = {} } = options ?? {};
283
- const { module, providers, logging } = builder;
261
+ const { module, providers, logging } = contractData;
284
262
  yield* Effect.logDebug(`Joining contract at ${address}...`);
285
263
  const ContractClass = module.Contract;
286
264
  const contract = new ContractClass(module.witnesses);
@@ -309,61 +287,16 @@ function joinEffect(builder, address, options) {
309
287
  };
310
288
  });
311
289
  }
312
- // =============================================================================
313
- // ContractBuilder - Promise API & Effect Namespace
314
- // =============================================================================
315
- /**
316
- * ContractBuilder module functions.
317
- *
318
- * @since 0.2.0
319
- * @category ContractBuilder
320
- */
321
- export const ContractBuilder = {
322
- /**
323
- * Deploy a new contract instance.
324
- *
325
- * @example
326
- * ```typescript
327
- * const contract = await ContractBuilder.deploy(builder);
328
- * ```
329
- *
330
- * @since 0.2.0
331
- * @category lifecycle
332
- */
333
- deploy: async (builder, options) => {
334
- return runEffectWithLogging(deployEffect(builder, options), builder.logging);
335
- },
336
- /**
337
- * Join an existing contract.
338
- *
339
- * @example
340
- * ```typescript
341
- * const contract = await ContractBuilder.join(builder, '0x...');
342
- * ```
343
- *
344
- * @since 0.2.0
345
- * @category lifecycle
346
- */
347
- join: async (builder, address, options) => {
348
- return runEffectWithLogging(joinEffect(builder, address, options), builder.logging);
349
- },
350
- /**
351
- * Raw Effect APIs for ContractBuilder.
352
- *
353
- * @since 0.2.0
354
- * @category effect
355
- */
356
- effect: {
357
- deploy: deployEffect,
358
- join: joinEffect,
359
- },
360
- };
361
- // =============================================================================
362
- // Contract - Internal Effect Implementations
363
- // =============================================================================
364
- function callEffect(contract, action, ...args) {
290
+ function callContractEffect(contractData, action, ...args) {
365
291
  return Effect.gen(function* () {
366
- const { instance } = contract;
292
+ // State machine check
293
+ if (contractData.instance === undefined) {
294
+ return yield* Effect.fail(new ContractError({
295
+ cause: new Error('Not deployed'),
296
+ message: `Contract not deployed. Call deploy() or join() first.`,
297
+ }));
298
+ }
299
+ const { instance } = contractData;
367
300
  yield* Effect.logDebug(`Calling ${action}()...`);
368
301
  const deployed = instance;
369
302
  const callTx = deployed.callTx;
@@ -390,10 +323,17 @@ function callEffect(contract, action, ...args) {
390
323
  };
391
324
  });
392
325
  }
393
- function stateEffect(contract) {
326
+ function contractStateEffect(contractData) {
327
+ if (contractData.address === undefined) {
328
+ return Effect.fail(new ContractError({
329
+ cause: new Error('Not deployed'),
330
+ message: `Contract not deployed. Call deploy() or join() first.`,
331
+ }));
332
+ }
333
+ const address = contractData.address;
394
334
  return Effect.tryPromise({
395
335
  try: async () => {
396
- const { address, providers } = contract;
336
+ const { providers } = contractData;
397
337
  const contractState = await providers.publicDataProvider.queryContractState(address);
398
338
  if (!contractState) {
399
339
  throw new Error(`Contract state not found at ${address}`);
@@ -406,10 +346,17 @@ function stateEffect(contract) {
406
346
  }),
407
347
  });
408
348
  }
409
- function stateAtEffect(contract, blockHeight) {
349
+ function contractStateAtEffect(contractData, blockHeight) {
350
+ if (contractData.address === undefined) {
351
+ return Effect.fail(new ContractError({
352
+ cause: new Error('Not deployed'),
353
+ message: `Contract not deployed. Call deploy() or join() first.`,
354
+ }));
355
+ }
356
+ const address = contractData.address;
410
357
  return Effect.tryPromise({
411
358
  try: async () => {
412
- const { address, providers } = contract;
359
+ const { providers } = contractData;
413
360
  const contractState = await providers.publicDataProvider.queryContractState(address, {
414
361
  type: 'blockHeight',
415
362
  blockHeight,
@@ -425,223 +372,249 @@ function stateAtEffect(contract, blockHeight) {
425
372
  }),
426
373
  });
427
374
  }
428
- function ledgerStateEffect(contract) {
429
- return stateEffect(contract).pipe(Effect.map((data) => contract.module.ledger(data)));
375
+ function ledgerStateEffect(contractData) {
376
+ return contractStateEffect(contractData).pipe(Effect.map((data) => contractData.module.ledger(data)));
430
377
  }
431
- function ledgerStateAtEffect(contract, blockHeight) {
432
- return stateAtEffect(contract, blockHeight).pipe(Effect.map((data) => contract.module.ledger(data)));
378
+ function ledgerStateAtEffect(contractData, blockHeight) {
379
+ return contractStateAtEffect(contractData, blockHeight).pipe(Effect.map((data) => contractData.module.ledger(data)));
433
380
  }
434
381
  // =============================================================================
435
- // Contract - Promise API & Effect Namespace
382
+ // Handle Factories (Create handles with methods from data)
436
383
  // =============================================================================
437
384
  /**
438
- * Contract module functions.
439
- *
440
- * @since 0.2.0
441
- * @category Contract
385
+ * Create a stateful Contract handle from internal data.
386
+ * The contract maintains mutable state internally for simplicity.
387
+ * @internal
442
388
  */
443
- export const Contract = {
444
- /**
445
- * Call a contract action.
446
- *
447
- * @example
448
- * ```typescript
449
- * const result = await Contract.call(contract, 'increment');
450
- * ```
451
- *
452
- * @since 0.2.0
453
- * @category operations
454
- */
455
- call: async (contract, action, ...args) => {
456
- return runEffectWithLogging(callEffect(contract, action, ...args), contract.logging);
457
- },
458
- /**
459
- * Get contract state.
460
- *
461
- * @since 0.2.0
462
- * @category inspection
463
- */
464
- state: async (contract) => {
465
- return runEffectWithLogging(stateEffect(contract), contract.logging);
466
- },
467
- /**
468
- * Get contract state at a specific block height.
469
- *
470
- * @since 0.2.0
471
- * @category inspection
472
- */
473
- stateAt: async (contract, blockHeight) => {
474
- return runEffectWithLogging(stateAtEffect(contract, blockHeight), contract.logging);
475
- },
476
- /**
477
- * Get ledger state (parsed through ledger function).
478
- *
479
- * @since 0.2.0
480
- * @category inspection
481
- */
482
- ledgerState: async (contract) => {
483
- return runEffectWithLogging(ledgerStateEffect(contract), contract.logging);
484
- },
485
- /**
486
- * Get ledger state at a specific block height.
487
- *
488
- * @since 0.2.0
489
- * @category inspection
490
- */
491
- ledgerStateAt: async (contract, blockHeight) => {
492
- return runEffectWithLogging(ledgerStateAtEffect(contract, blockHeight), contract.logging);
493
- },
494
- /**
495
- * Raw Effect APIs for Contract.
496
- *
497
- * @since 0.2.0
498
- * @category effect
499
- */
500
- effect: {
501
- call: callEffect,
502
- state: stateEffect,
503
- stateAt: stateAtEffect,
504
- ledgerState: ledgerStateEffect,
505
- ledgerStateAt: ledgerStateAtEffect,
506
- },
507
- };
389
+ function createContractHandle(initialData) {
390
+ // Mutable state - the contract data can change via deploy/join
391
+ let data = initialData;
392
+ const handle = {
393
+ // Computed state property
394
+ get state() {
395
+ return data.address !== undefined ? 'deployed' : 'loaded';
396
+ },
397
+ // Data accessors
398
+ get address() {
399
+ return data.address;
400
+ },
401
+ get module() {
402
+ return data.module;
403
+ },
404
+ get providers() {
405
+ return data.providers;
406
+ },
407
+ // Lifecycle methods
408
+ deploy: async (options) => {
409
+ const newData = await runEffectWithLogging(deployContractEffect(data, options), data.logging);
410
+ data = newData; // Update internal state
411
+ },
412
+ join: async (address, options) => {
413
+ const newData = await runEffectWithLogging(joinContractEffect(data, address, options), data.logging);
414
+ data = newData; // Update internal state
415
+ },
416
+ // Contract methods
417
+ call: (action, ...args) => runEffectWithLogging(callContractEffect(data, action, ...args), data.logging),
418
+ getState: () => runEffectWithLogging(contractStateEffect(data), data.logging),
419
+ getStateAt: (blockHeight) => runEffectWithLogging(contractStateAtEffect(data, blockHeight), data.logging),
420
+ ledgerState: () => runEffectWithLogging(ledgerStateEffect(data), data.logging),
421
+ ledgerStateAt: (blockHeight) => runEffectWithLogging(ledgerStateAtEffect(data, blockHeight), data.logging),
422
+ // Effect API
423
+ effect: {
424
+ deploy: (options) => deployContractEffect(data, options).pipe(Effect.tap((newData) => Effect.sync(() => { data = newData; }))),
425
+ join: (address, options) => joinContractEffect(data, address, options).pipe(Effect.tap((newData) => Effect.sync(() => { data = newData; }))),
426
+ call: (action, ...args) => callContractEffect(data, action, ...args),
427
+ getState: () => contractStateEffect(data),
428
+ getStateAt: (blockHeight) => contractStateAtEffect(data, blockHeight),
429
+ ledgerState: () => ledgerStateEffect(data),
430
+ ledgerStateAt: (blockHeight) => ledgerStateAtEffect(data, blockHeight),
431
+ },
432
+ };
433
+ return handle;
434
+ }
508
435
  /**
509
- * Context.Tag for ClientService dependency injection.
436
+ * Create a MiddayClient handle from internal data.
437
+ * @internal
438
+ */
439
+ function createClientHandle(data) {
440
+ return {
441
+ // Data accessors
442
+ networkConfig: data.networkConfig,
443
+ providers: data.providers,
444
+ wallet: data.wallet,
445
+ // Promise API - returns Contract directly (new API)
446
+ loadContract: async (options) => {
447
+ const contractData = await runEffectWithLogging(loadContractEffect(data, options), data.logging);
448
+ return createContractHandle(contractData);
449
+ },
450
+ waitForTx: (txHash) => runEffectWithLogging(waitForTxEffect(data, txHash), data.logging),
451
+ // Effect API
452
+ effect: {
453
+ loadContract: (options) => loadContractEffect(data, options).pipe(Effect.map(createContractHandle)),
454
+ waitForTx: (txHash) => waitForTxEffect(data, txHash),
455
+ },
456
+ };
457
+ }
458
+ // =============================================================================
459
+ // Module - Public API
460
+ // =============================================================================
461
+ /**
462
+ * Create a Midnight client for interacting with contracts.
510
463
  *
511
464
  * @example
512
465
  * ```typescript
513
- * const program = Effect.gen(function* () {
514
- * const clientService = yield* ClientService;
515
- * const client = yield* clientService.create(config);
516
- * return client;
466
+ * const client = await Midday.Client.create({
467
+ * seed: 'your-64-char-hex-seed',
468
+ * networkConfig: Midday.Config.NETWORKS.local,
469
+ * privateStateProvider,
517
470
  * });
518
471
  *
519
- * Effect.runPromise(program.pipe(Effect.provide(ClientLive)));
472
+ * const contract = await client.loadContract({ path: './contracts/counter' });
473
+ * await contract.deploy();
474
+ * await contract.call('increment');
520
475
  * ```
521
476
  *
522
477
  * @since 0.2.0
523
- * @category service
478
+ * @category constructors
524
479
  */
525
- export class ClientService extends Context.Tag('ClientService')() {
480
+ export async function create(config) {
481
+ const logging = config.logging ?? true;
482
+ const data = await runEffectWithLogging(createClientDataEffect(config), logging);
483
+ return createClientHandle(data);
526
484
  }
527
485
  /**
528
- * Context.Tag for ContractBuilderService dependency injection.
486
+ * Create a Midnight client from a connected wallet (browser).
529
487
  *
530
488
  * @since 0.2.0
531
- * @category service
489
+ * @category constructors
532
490
  */
533
- export class ContractBuilderService extends Context.Tag('ContractBuilderService')() {
491
+ export async function fromWallet(connection, config) {
492
+ const logging = config.logging ?? true;
493
+ const data = await runEffectWithLogging(fromWalletDataEffect(connection, config), logging);
494
+ return createClientHandle(data);
534
495
  }
535
496
  /**
536
- * Context.Tag for ContractService dependency injection.
497
+ * Raw Effect APIs for advanced users who want to compose Effects.
498
+ *
499
+ * @example
500
+ * ```typescript
501
+ * const program = Effect.gen(function* () {
502
+ * const client = yield* Midday.Client.effect.create(config);
503
+ * const contract = yield* client.effect.loadContract({ module });
504
+ * yield* contract.effect.deploy();
505
+ * yield* contract.effect.call('increment');
506
+ * });
507
+ * ```
537
508
  *
538
509
  * @since 0.2.0
539
- * @category service
510
+ * @category effect
540
511
  */
541
- export class ContractService extends Context.Tag('ContractService')() {
542
- }
543
- // =============================================================================
544
- // Effect DI - Live Layers
545
- // =============================================================================
512
+ export const effect = {
513
+ /**
514
+ * Create a client (Effect version).
515
+ */
516
+ create: (config) => createClientDataEffect(config).pipe(Effect.map(createClientHandle)),
517
+ /**
518
+ * Create a client from wallet (Effect version).
519
+ */
520
+ fromWallet: (connection, config) => fromWalletDataEffect(connection, config).pipe(Effect.map(createClientHandle)),
521
+ };
546
522
  /**
547
- * Live Layer for ClientService.
523
+ * Load a Compact contract from a directory path.
548
524
  *
549
- * @since 0.2.0
550
- * @category layer
525
+ * Note: Prefer using `client.loadContract({ path })` which handles this automatically.
526
+ * This function is useful when you need to load the module before creating a client.
527
+ *
528
+ * @param contractPath - Absolute path to the contract directory
529
+ * @param options - Optional loading configuration
530
+ * @returns Promise resolving to the contract module and ZK config provider
531
+ *
532
+ * @example
533
+ * ```typescript
534
+ * // Preferred: let loadContract handle it
535
+ * const contract = await client.loadContract({ path: contractPath });
536
+ *
537
+ * // Alternative: load separately (useful for pre-loading)
538
+ * const { module, zkConfig } = await Midday.Client.loadContractModule(contractPath);
539
+ * const contract = await client.loadContract({ module, zkConfig });
540
+ * ```
541
+ *
542
+ * @since 0.4.0
543
+ * @category loading
551
544
  */
552
- export const ClientLive = Layer.succeed(ClientService, {
553
- create: createEffect,
554
- fromWallet: fromWalletEffect,
555
- contractFrom: contractFromEffect,
556
- waitForTx: waitForTxEffect,
557
- });
545
+ export async function loadContractModule(contractPath, options = {}) {
546
+ const { moduleSubdir = 'contract', moduleEntry = 'index.js' } = options;
547
+ // Dynamic imports to avoid bundling Node.js code in browser
548
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
549
+ const { join } = require('path');
550
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
551
+ const { NodeZkConfigProvider } = require('@midnight-ntwrk/midnight-js-node-zk-config-provider');
552
+ const modulePath = join(contractPath, moduleSubdir, moduleEntry);
553
+ const module = (await import(modulePath));
554
+ const zkConfig = new NodeZkConfigProvider(contractPath);
555
+ return { module, zkConfig };
556
+ }
558
557
  /**
559
- * Live Layer for ContractBuilderService.
558
+ * Load a contract from URLs (browser environments).
560
559
  *
561
- * @since 0.2.0
562
- * @category layer
560
+ * @param moduleUrl - URL to the contract module
561
+ * @param zkConfigBaseUrl - Base URL for ZK artifacts
562
+ * @returns Promise resolving to the contract module and ZK config provider
563
+ *
564
+ * @since 0.4.0
565
+ * @category loading
563
566
  */
564
- export const ContractBuilderLive = Layer.succeed(ContractBuilderService, {
565
- deploy: deployEffect,
566
- join: joinEffect,
567
- });
567
+ export async function loadContractModuleFromUrl(moduleUrl, zkConfigBaseUrl) {
568
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
569
+ const { HttpZkConfigProvider } = require('./providers/HttpZkConfigProvider.js');
570
+ const module = (await import(/* webpackIgnore: true */ moduleUrl));
571
+ const zkConfig = new HttpZkConfigProvider(zkConfigBaseUrl);
572
+ return { module, zkConfig };
573
+ }
568
574
  /**
569
- * Live Layer for ContractService.
575
+ * Context.Tag for ClientService dependency injection.
570
576
  *
571
577
  * @since 0.2.0
572
- * @category layer
578
+ * @category service
573
579
  */
574
- export const ContractLive = Layer.succeed(ContractService, {
575
- call: callEffect,
576
- state: stateEffect,
577
- stateAt: stateAtEffect,
578
- ledgerState: ledgerStateEffect,
579
- ledgerStateAt: ledgerStateAtEffect,
580
- });
581
- // =============================================================================
582
- // Layer Factories
583
- // =============================================================================
580
+ export class ClientService extends Context.Tag('ClientService')() {
581
+ }
584
582
  /**
585
- * Create a Layer providing all Client-related factory services.
586
- *
587
- * Use this when you want to create clients on-demand within your Effect programs.
588
- * For pre-initialized clients, use `Client.layer(config)` instead.
589
- *
590
- * @example
591
- * ```typescript
592
- * import { Effect } from 'effect';
593
- * import * as Midday from '@no-witness-labs/midday-sdk';
594
- *
595
- * const program = Effect.gen(function* () {
596
- * const clientService = yield* Midday.ClientService;
597
- * const client = yield* clientService.create(config);
598
- * return client;
599
- * });
600
- *
601
- * await Effect.runPromise(program.pipe(Effect.provide(Midday.Client.services())));
602
- * ```
583
+ * Live Layer for ClientService.
603
584
  *
604
- * @since 0.3.0
585
+ * @since 0.2.0
605
586
  * @category layer
606
587
  */
607
- export function services() {
608
- return Layer.mergeAll(ClientLive, ContractBuilderLive, ContractLive);
609
- }
588
+ export const ClientLive = Layer.succeed(ClientService, {
589
+ create: effect.create,
590
+ fromWallet: effect.fromWallet,
591
+ });
610
592
  // =============================================================================
611
593
  // Pre-configured Client Layer
612
594
  // =============================================================================
613
595
  /**
614
- * Context.Tag for a pre-initialized MidnightClient.
615
- *
616
- * Use with `Client.layer(config)` for dependency injection of a configured client.
596
+ * Context.Tag for a pre-initialized MiddayClient.
617
597
  *
618
598
  * @since 0.3.0
619
599
  * @category service
620
600
  */
621
- export class MidnightClientService extends Context.Tag('MidnightClientService')() {
601
+ export class MiddayClientService extends Context.Tag('MiddayClientService')() {
622
602
  }
623
603
  /**
624
- * Create a Layer that provides a pre-initialized MidnightClient.
625
- *
626
- * This is the recommended way to inject a client into Effect programs
627
- * when you have a known configuration at startup. Follows the same pattern
628
- * as `Cluster.layer(config)`.
604
+ * Create a Layer that provides a pre-initialized MiddayClient.
629
605
  *
630
606
  * @example
631
607
  * ```typescript
632
- * import { Effect } from 'effect';
633
- * import * as Midday from '@no-witness-labs/midday-sdk';
634
- *
635
608
  * const clientLayer = Midday.Client.layer({
636
609
  * seed: 'your-64-char-hex-seed',
637
610
  * networkConfig: Midday.Config.NETWORKS.local,
638
- * zkConfigProvider: new Midday.HttpZkConfigProvider('http://localhost:3000/zk'),
639
- * privateStateProvider: Midday.inMemoryPrivateStateProvider(),
611
+ * zkConfigProvider,
612
+ * privateStateProvider,
640
613
  * });
641
614
  *
642
615
  * const program = Effect.gen(function* () {
643
- * const client = yield* Midday.MidnightClientService;
644
- * const builder = yield* Midday.Client.effect.contractFrom(client, { module });
616
+ * const client = yield* Midday.MiddayClientService;
617
+ * const builder = yield* client.effect.loadContract({ module });
645
618
  * return builder;
646
619
  * });
647
620
  *
@@ -652,38 +625,24 @@ export class MidnightClientService extends Context.Tag('MidnightClientService')(
652
625
  * @category layer
653
626
  */
654
627
  export function layer(config) {
655
- return Layer.effect(MidnightClientService, createEffect(config));
628
+ return Layer.effect(MiddayClientService, effect.create(config));
656
629
  }
657
630
  /**
658
- * Create a Layer that provides a pre-initialized MidnightClient from a wallet connection.
659
- *
660
- * Use this for browser environments with Lace wallet integration.
661
- *
662
- * @example
663
- * ```typescript
664
- * import { Effect } from 'effect';
665
- * import * as Midday from '@no-witness-labs/midday-sdk';
666
- *
667
- * // After connecting wallet
668
- * const connection = await Midday.connectWallet('testnet');
669
- *
670
- * const clientLayer = Midday.Client.layerFromWallet(connection, {
671
- * zkConfigProvider: new Midday.HttpZkConfigProvider('https://cdn.example.com/zk'),
672
- * privateStateProvider: Midday.indexedDBPrivateStateProvider({ privateStateStoreName: 'my-app' }),
673
- * });
674
- *
675
- * const program = Effect.gen(function* () {
676
- * const client = yield* Midday.MidnightClientService;
677
- * // Use client...
678
- * });
679
- *
680
- * await Effect.runPromise(program.pipe(Effect.provide(clientLayer)));
681
- * ```
631
+ * Create a Layer that provides a pre-initialized MiddayClient from a wallet connection.
682
632
  *
683
633
  * @since 0.3.0
684
634
  * @category layer
685
635
  */
686
636
  export function layerFromWallet(connection, config) {
687
- return Layer.effect(MidnightClientService, fromWalletEffect(connection, config));
637
+ return Layer.effect(MiddayClientService, effect.fromWallet(connection, config));
638
+ }
639
+ /**
640
+ * Create a Layer providing all Client-related services.
641
+ *
642
+ * @since 0.3.0
643
+ * @category layer
644
+ */
645
+ export function services() {
646
+ return ClientLive;
688
647
  }
689
648
  //# sourceMappingURL=Client.js.map