@claritydao/midnight-agora-sdk 0.1.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 (60) hide show
  1. package/.prettierrc +1 -0
  2. package/LICENSE +0 -0
  3. package/README.md +1134 -0
  4. package/dist/browser-providers.d.ts +21 -0
  5. package/dist/browser-providers.d.ts.map +1 -0
  6. package/dist/browser-providers.js +147 -0
  7. package/dist/browser-providers.js.map +1 -0
  8. package/dist/common-types.d.ts +29 -0
  9. package/dist/common-types.d.ts.map +1 -0
  10. package/dist/common-types.js +2 -0
  11. package/dist/common-types.js.map +1 -0
  12. package/dist/errors.d.ts +67 -0
  13. package/dist/errors.d.ts.map +1 -0
  14. package/dist/errors.js +108 -0
  15. package/dist/errors.js.map +1 -0
  16. package/dist/governor-api.d.ts +348 -0
  17. package/dist/governor-api.d.ts.map +1 -0
  18. package/dist/governor-api.js +716 -0
  19. package/dist/governor-api.js.map +1 -0
  20. package/dist/index.d.ts +11 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +14 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/manager.d.ts +87 -0
  25. package/dist/manager.d.ts.map +1 -0
  26. package/dist/manager.js +93 -0
  27. package/dist/manager.js.map +1 -0
  28. package/dist/types.d.ts +307 -0
  29. package/dist/types.d.ts.map +1 -0
  30. package/dist/types.js +77 -0
  31. package/dist/types.js.map +1 -0
  32. package/dist/utils/index.d.ts +34 -0
  33. package/dist/utils/index.d.ts.map +1 -0
  34. package/dist/utils/index.js +57 -0
  35. package/dist/utils/index.js.map +1 -0
  36. package/dist/validators.d.ts +52 -0
  37. package/dist/validators.d.ts.map +1 -0
  38. package/dist/validators.js +160 -0
  39. package/dist/validators.js.map +1 -0
  40. package/eslint.config.mjs +48 -0
  41. package/package.json +39 -0
  42. package/src/browser-providers.ts +274 -0
  43. package/src/common-types.ts +51 -0
  44. package/src/errors.ts +124 -0
  45. package/src/governor-api.ts +948 -0
  46. package/src/index.ts +51 -0
  47. package/src/manager.ts +203 -0
  48. package/src/types.ts +403 -0
  49. package/src/utils/index.ts +60 -0
  50. package/src/validators.ts +245 -0
  51. package/test/README.md +409 -0
  52. package/test/errors.test.ts +179 -0
  53. package/test/integration/governor-api.test.ts +370 -0
  54. package/test/mocks/providers.ts +130 -0
  55. package/test/types.test.ts +112 -0
  56. package/test/utils.test.ts +111 -0
  57. package/test/validators.test.ts +368 -0
  58. package/test/wasm-init.test.ts +132 -0
  59. package/tsconfig.json +18 -0
  60. package/vitest.config.ts +9 -0
package/README.md ADDED
@@ -0,0 +1,1134 @@
1
+ # Agora DAO Midnight SDK
2
+
3
+ Production-ready TypeScript SDK for governance operations on the Midnight blockchain.
4
+
5
+ ## Overview
6
+
7
+ The Agora DAO Midnight SDK provides a type-safe, professional-grade interface for interacting with governance contracts on Midnight Network. It integrates with the Lace wallet extension and provides comprehensive APIs for DAO governance operations.
8
+
9
+ ## Features
10
+
11
+ - **Type-Safe**: Full TypeScript support with comprehensive type definitions
12
+ - **Production-Ready**: No hardcoded values, proper error handling, validation
13
+ - **Browser-Native**: Works directly in modern browsers with Lace wallet
14
+ - **Observable State**: Reactive patterns with RxJS for real-time updates
15
+ - **Zero-Knowledge**: Privacy-preserving governance with ZK proofs
16
+ - **Well-Documented**: Complete API reference and examples
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pnpm add @agora-dao-midnight/sdk
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Deploy a New Governor Contract
27
+
28
+ ```typescript
29
+ import { GovernorAPI, initializeProviders, ConfigPresets } from '@agora-dao-midnight/sdk';
30
+ import { randomBytes } from '@midnight-ntwrk/midnight-js-utils';
31
+ import pino from 'pino';
32
+
33
+ const logger = pino({ level: 'info' });
34
+
35
+ // Initialize providers (connects to Lace wallet)
36
+ const providers = await initializeProviders(logger);
37
+
38
+ // Create deployment configuration
39
+ const config = {
40
+ governor: {
41
+ governance: ConfigPresets.testing(), // or ConfigPresets.production()
42
+ executionDelay: BigInt(60),
43
+ gracePeriod: BigInt(300),
44
+ maxActiveProposals: BigInt(100),
45
+ proposalLifetime: BigInt(86400),
46
+ emergencyVetoEnabled: true,
47
+ emergencyVetoThreshold: BigInt(75000),
48
+ },
49
+ token: {
50
+ name: 'My DAO Token',
51
+ symbol: 'MDAO',
52
+ decimals: BigInt(18),
53
+ },
54
+ adminKey: randomBytes(32), // IMPORTANT: Use secure random bytes!
55
+ };
56
+
57
+ // Deploy contract
58
+ const api = await GovernorAPI.deploy(providers, config, logger);
59
+ console.log('Contract deployed at:', api.deployedContractAddress);
60
+ ```
61
+
62
+ ### 2. Join an Existing Contract
63
+
64
+ ```typescript
65
+ import { GovernorAPI, initializeProviders } from '@agora-dao-midnight/sdk';
66
+ import pino from 'pino';
67
+
68
+ const logger = pino({ level: 'info' });
69
+ const providers = await initializeProviders(logger);
70
+
71
+ // Join existing contract
72
+ const contractAddress = 'existing-contract-address';
73
+ const api = await GovernorAPI.join(providers, contractAddress, logger);
74
+ ```
75
+
76
+ ### 3. Create a Proposal
77
+
78
+ ```typescript
79
+ import { VoteChoice } from '@agora-dao-midnight/sdk';
80
+ import type { ProposalAction } from '@agora-dao-midnight/sdk';
81
+
82
+ // Define proposal actions (what happens if proposal passes)
83
+ const actions: ProposalAction[] = [
84
+ {
85
+ typ: 0, // Transfer type
86
+ token: tokenAddress,
87
+ amount: BigInt(10000),
88
+ to: recipientAddress,
89
+ configKey: new Uint8Array(32),
90
+ configValue: new Uint8Array(32),
91
+ },
92
+ ];
93
+
94
+ // Create proposal (requires sufficient stake)
95
+ const { txData, proposalId } = await api.createProposal(
96
+ 'Fund Community Initiative',
97
+ 'Transfer 10,000 tokens to community wallet for Q1 initiatives',
98
+ creatorAddress,
99
+ actions
100
+ );
101
+
102
+ console.log('Proposal created with ID:', proposalId);
103
+ ```
104
+
105
+ ### 4. Cast a Vote
106
+
107
+ ```typescript
108
+ import { VoteChoice } from '@agora-dao-midnight/sdk';
109
+
110
+ // Vote on proposal (type-safe!)
111
+ await api.castVote(
112
+ proposalId,
113
+ voterAddress,
114
+ VoteChoice.For // VoteChoice.Against or VoteChoice.Abstain
115
+ );
116
+
117
+ console.log('Vote cast successfully');
118
+ ```
119
+
120
+ ## Configuration Presets
121
+
122
+ The SDK provides two configuration presets:
123
+
124
+ ### Testing Configuration
125
+
126
+ For development and testnet:
127
+
128
+ ```typescript
129
+ import { ConfigPresets } from '@agora-dao-midnight/sdk';
130
+
131
+ const governanceConfig = ConfigPresets.testing();
132
+ // Returns:
133
+ // {
134
+ // votingPeriod: BigInt(300), // 5 minutes
135
+ // quorumThreshold: BigInt(100), // 100 tokens
136
+ // proposalThreshold: BigInt(50), // 50 tokens to create proposals
137
+ // proposalLifetime: BigInt(3600), // 1 hour
138
+ // executionDelay: BigInt(60), // 1 minute
139
+ // gracePeriod: BigInt(300), // 5 minutes
140
+ // minStakeAmount: BigInt(10), // 10 tokens minimum
141
+ // stakingPeriod: BigInt(60), // 1 minute lock
142
+ // }
143
+ ```
144
+
145
+ ### Production Configuration
146
+
147
+ For mainnet:
148
+
149
+ ```typescript
150
+ import { ConfigPresets } from '@agora-dao-midnight/sdk';
151
+
152
+ const governanceConfig = ConfigPresets.production();
153
+ // Returns:
154
+ // {
155
+ // votingPeriod: BigInt(604800), // 7 days
156
+ // quorumThreshold: BigInt(100000), // 100K tokens
157
+ // proposalThreshold: BigInt(50000), // 50K tokens to create proposals
158
+ // proposalLifetime: BigInt(1209600), // 14 days
159
+ // executionDelay: BigInt(86400), // 24 hours
160
+ // gracePeriod: BigInt(259200), // 3 days
161
+ // minStakeAmount: BigInt(1000), // 1K tokens minimum
162
+ // stakingPeriod: BigInt(604800), // 7 days lock
163
+ // }
164
+ ```
165
+
166
+ ## API Reference
167
+
168
+ ### Types
169
+
170
+ #### DeploymentConfig
171
+
172
+ Complete deployment configuration:
173
+
174
+ ```typescript
175
+ interface DeploymentConfig {
176
+ governor: GovernorConfig;
177
+ token: TokenConfig;
178
+ adminKey: Uint8Array; // 32 bytes - use randomBytes(32)
179
+ }
180
+ ```
181
+
182
+ #### GovernorConfig
183
+
184
+ Governor contract parameters:
185
+
186
+ ```typescript
187
+ interface GovernorConfig {
188
+ governance: GovernanceConfig;
189
+ executionDelay: bigint;
190
+ gracePeriod: bigint;
191
+ maxActiveProposals: bigint;
192
+ proposalLifetime: bigint;
193
+ emergencyVetoEnabled: boolean;
194
+ emergencyVetoThreshold: bigint;
195
+ }
196
+ ```
197
+
198
+ #### GovernanceConfig
199
+
200
+ Core governance parameters:
201
+
202
+ ```typescript
203
+ interface GovernanceConfig {
204
+ votingPeriod: bigint; // How long voting lasts
205
+ quorumThreshold: bigint; // Minimum votes to pass
206
+ proposalThreshold: bigint; // Stake required to create proposals
207
+ proposalLifetime: bigint; // How long proposals remain valid
208
+ executionDelay: bigint; // Time-lock before execution
209
+ gracePeriod: bigint; // Window to execute after delay
210
+ minStakeAmount: bigint; // Minimum stake to vote
211
+ stakingPeriod: bigint; // Stake lock duration
212
+ }
213
+ ```
214
+
215
+ #### TokenConfig
216
+
217
+ Token parameters:
218
+
219
+ ```typescript
220
+ interface TokenConfig {
221
+ name: string; // Max 64 characters
222
+ symbol: string; // Max 16 characters
223
+ decimals: bigint; // Max 18
224
+ }
225
+ ```
226
+
227
+ #### ProposalAction
228
+
229
+ Actions to execute when proposal passes:
230
+
231
+ ```typescript
232
+ interface ProposalAction {
233
+ typ: number; // Action type (0 = transfer)
234
+ token: Uint8Array; // Token address (32 bytes)
235
+ amount: bigint; // Amount to transfer
236
+ to: Uint8Array; // Recipient address (32 bytes)
237
+ configKey: Uint8Array; // Config key (32 bytes)
238
+ configValue: Uint8Array; // Config value (32 bytes)
239
+ }
240
+ ```
241
+
242
+ #### VoteChoice
243
+
244
+ Type-safe vote choices:
245
+
246
+ ```typescript
247
+ enum VoteChoice {
248
+ For = 0, // Vote in favor
249
+ Against = 1, // Vote against
250
+ Abstain = 2, // Abstain from voting
251
+ }
252
+ ```
253
+
254
+ ### GovernorAPI
255
+
256
+ #### Static Methods
257
+
258
+ ##### deploy()
259
+
260
+ ```typescript
261
+ static async deploy(
262
+ providers: GovernorProviders,
263
+ config: DeploymentConfig,
264
+ logger?: Logger
265
+ ): Promise<GovernorAPI>
266
+ ```
267
+
268
+ Deploys a new governor contract with the specified configuration.
269
+
270
+ **Parameters:**
271
+ - `providers`: Initialized providers (from `initializeProviders`)
272
+ - `config`: Deployment configuration
273
+ - `logger` (optional): Pino logger instance
274
+
275
+ **Returns:** GovernorAPI instance
276
+
277
+ **Throws:**
278
+ - `InvalidConfigurationError`: Invalid configuration parameters
279
+ - `DeploymentError`: Contract deployment failed
280
+ - `WalletNotConnectedError`: Wallet not connected
281
+
282
+ **Example:**
283
+ ```typescript
284
+ const api = await GovernorAPI.deploy(providers, config, logger);
285
+ ```
286
+
287
+ ##### join()
288
+
289
+ ```typescript
290
+ static async join(
291
+ providers: GovernorProviders,
292
+ contractAddress: ContractAddress,
293
+ logger?: Logger
294
+ ): Promise<GovernorAPI>
295
+ ```
296
+
297
+ Joins an existing governor contract.
298
+
299
+ **Parameters:**
300
+ - `providers`: Initialized providers
301
+ - `contractAddress`: Address of deployed contract
302
+ - `logger` (optional): Pino logger instance
303
+
304
+ **Returns:** GovernorAPI instance
305
+
306
+ **Throws:**
307
+ - `ContractNotFoundError`: Contract not found at address
308
+ - `WalletNotConnectedError`: Wallet not connected
309
+
310
+ #### Instance Methods
311
+
312
+ ##### createProposal()
313
+
314
+ ```typescript
315
+ async createProposal(
316
+ title: string,
317
+ description: string,
318
+ creator: Uint8Array,
319
+ actions: ProposalAction[]
320
+ ): Promise<{ txData: FinalizedTxData; proposalId: bigint }>
321
+ ```
322
+
323
+ Creates a new governance proposal.
324
+
325
+ **Parameters:**
326
+ - `title`: Proposal title (max 256 chars)
327
+ - `description`: Proposal description (max 10,000 chars)
328
+ - `creator`: Creator address (32 bytes, must have sufficient stake)
329
+ - `actions`: Array of actions to execute if proposal passes
330
+
331
+ **Returns:** Object with transaction data and proposal ID
332
+
333
+ **Throws:**
334
+ - `InvalidParameterError`: Invalid parameters
335
+ - `InsufficientStakeError`: Creator has insufficient stake
336
+ - `TransactionError`: Transaction failed
337
+
338
+ **Example:**
339
+ ```typescript
340
+ const { proposalId } = await api.createProposal(
341
+ 'Increase Treasury Allocation',
342
+ 'Detailed description of the proposal...',
343
+ creatorAddress,
344
+ [
345
+ {
346
+ typ: 0,
347
+ token: tokenAddress,
348
+ amount: BigInt(10000),
349
+ to: recipientAddress,
350
+ configKey: new Uint8Array(32),
351
+ configValue: new Uint8Array(32),
352
+ },
353
+ ]
354
+ );
355
+ ```
356
+
357
+ ##### castVote()
358
+
359
+ ```typescript
360
+ async castVote(
361
+ proposalId: bigint,
362
+ voter: Uint8Array,
363
+ choice: VoteChoice
364
+ ): Promise<FinalizedTxData>
365
+ ```
366
+
367
+ Casts a vote on an active proposal.
368
+
369
+ **Parameters:**
370
+ - `proposalId`: Proposal ID to vote on
371
+ - `voter`: Voter address (32 bytes, must have voting power)
372
+ - `choice`: Vote choice (VoteChoice.For, VoteChoice.Against, or VoteChoice.Abstain)
373
+
374
+ **Returns:** Transaction data
375
+
376
+ **Throws:**
377
+ - `InvalidParameterError`: Invalid parameters
378
+ - `InsufficientStakeError`: Voter has insufficient voting power
379
+ - `ProposalNotFoundError`: Proposal not found or not active
380
+ - `TransactionError`: Transaction failed
381
+
382
+ **Example:**
383
+ ```typescript
384
+ await api.castVote(proposalId, voterAddress, VoteChoice.For);
385
+ ```
386
+
387
+ ##### registerStake()
388
+
389
+ ```typescript
390
+ async registerStake(
391
+ userId: Uint8Array,
392
+ amount: bigint
393
+ ): Promise<FinalizedTxData>
394
+ ```
395
+
396
+ Registers a stake for governance participation.
397
+
398
+ **Parameters:**
399
+ - `userId`: User address (32 bytes)
400
+ - `amount`: Amount to stake
401
+
402
+ **Returns:** Transaction data
403
+
404
+ ##### finalizeProposal()
405
+
406
+ ```typescript
407
+ async finalizeProposal(proposalId: bigint): Promise<FinalizedTxData>
408
+ ```
409
+
410
+ Finalizes a proposal after voting period ends.
411
+
412
+ ##### queueProposal()
413
+
414
+ ```typescript
415
+ async queueProposal(proposalId: bigint): Promise<FinalizedTxData>
416
+ ```
417
+
418
+ Queues a finalized proposal for execution.
419
+
420
+ ##### executeProposal()
421
+
422
+ ```typescript
423
+ async executeProposal(
424
+ proposalId: bigint,
425
+ effectId: bigint
426
+ ): Promise<FinalizedTxData>
427
+ ```
428
+
429
+ Executes a queued proposal after execution delay.
430
+
431
+ ##### getProposalDetails()
432
+
433
+ ```typescript
434
+ async getProposalDetails(proposalId: bigint): Promise<ProposalDetails>
435
+ ```
436
+
437
+ Gets detailed information about a proposal.
438
+
439
+ ##### getProposalVoteCount()
440
+
441
+ ```typescript
442
+ async getProposalVoteCount(proposalId: bigint): Promise<VoteCount>
443
+ ```
444
+
445
+ Gets vote counts for a proposal.
446
+
447
+ ##### getStakeInfo()
448
+
449
+ ```typescript
450
+ async getStakeInfo(userId: Uint8Array): Promise<StakeInfo>
451
+ ```
452
+
453
+ Gets staking information for a user.
454
+
455
+ ### initializeProviders()
456
+
457
+ ```typescript
458
+ async function initializeProviders(logger: Logger): Promise<GovernorProviders>
459
+ ```
460
+
461
+ Initializes all required providers for governance operations.
462
+
463
+ **What it does:**
464
+ 1. Connects to Lace wallet extension
465
+ 2. Requests user authorization
466
+ 3. Retrieves wallet state and service URIs
467
+ 4. Configures all providers (IndexedDB, HTTP, Indexer, Wallet, Proof server)
468
+
469
+ **Parameters:**
470
+ - `logger`: Pino logger instance
471
+
472
+ **Returns:** Configured providers object
473
+
474
+ **Throws:**
475
+ - Error if Lace wallet not found
476
+ - Error if wallet connection rejected
477
+ - Error if incompatible wallet version
478
+
479
+ **Example:**
480
+ ```typescript
481
+ const providers = await initializeProviders(logger);
482
+ ```
483
+
484
+ ### GovernorManager
485
+
486
+ High-level API with observable state management.
487
+
488
+ #### Constructor
489
+
490
+ ```typescript
491
+ constructor(
492
+ deploymentConfig: DeploymentConfig,
493
+ logger: Logger
494
+ )
495
+ ```
496
+
497
+ **Parameters:**
498
+ - `deploymentConfig`: Deployment configuration
499
+ - `logger`: Pino logger instance
500
+
501
+ **Example:**
502
+ ```typescript
503
+ const manager = new GovernorManager(config, logger);
504
+ ```
505
+
506
+ #### Methods
507
+
508
+ ##### resolve()
509
+
510
+ ```typescript
511
+ resolve(contractAddress?: ContractAddress): Observable<GovernorDeployment>
512
+ ```
513
+
514
+ Deploys a new contract or joins an existing one, with observable state.
515
+
516
+ **Returns:** Observable that emits deployment state changes
517
+
518
+ **Deployment States:**
519
+ ```typescript
520
+ { status: 'in-progress' }
521
+ { status: 'deployed', api: DeployedGovernorAPI }
522
+ { status: 'failed', error: Error }
523
+ ```
524
+
525
+ ## Error Handling
526
+
527
+ ### Custom Error Classes
528
+
529
+ The SDK provides specific error classes for different failure scenarios:
530
+
531
+ ```typescript
532
+ import {
533
+ GovernorSDKError,
534
+ WalletNotConnectedError,
535
+ InsufficientStakeError,
536
+ InvalidParameterError,
537
+ ContractNotFoundError,
538
+ ProposalNotFoundError,
539
+ TransactionError,
540
+ DeploymentError,
541
+ InvalidConfigurationError,
542
+ NetworkNotConfiguredError,
543
+ } from '@agora-dao-midnight/sdk';
544
+ ```
545
+
546
+ ### Error Handling Pattern
547
+
548
+ ```typescript
549
+ try {
550
+ await api.createProposal(title, description, creator, actions);
551
+ } catch (error) {
552
+ if (error instanceof InsufficientStakeError) {
553
+ console.error('Need more stake:', error.message);
554
+ } else if (error instanceof InvalidParameterError) {
555
+ console.error('Invalid input:', error.message);
556
+ } else if (error instanceof TransactionError) {
557
+ console.error('Transaction failed:', error.message);
558
+ } else {
559
+ console.error('Unexpected error:', error);
560
+ }
561
+ }
562
+ ```
563
+
564
+ ## Validation
565
+
566
+ The SDK includes comprehensive input validation:
567
+
568
+ ```typescript
569
+ import { validators } from '@agora-dao-midnight/sdk';
570
+
571
+ // Validate proposal parameters
572
+ validators.validateProposal(title, description, creator);
573
+
574
+ // Validate vote choice
575
+ validators.validateVoteChoice(choice);
576
+
577
+ // Validate deployment config
578
+ validators.validateDeploymentConfig(config);
579
+
580
+ // Validate governance config
581
+ validators.validateGovernanceConfig(governanceConfig);
582
+ ```
583
+
584
+ ## Browser Integration
585
+
586
+ ### Critical: Frontend Setup Requirements
587
+
588
+ Before integrating this SDK into a browser application, you MUST configure your build tool correctly to handle WASM modules. **Failure to do so will result in the error `ocrt.maxField is not a function`.**
589
+
590
+ ### Required: Vite Configuration
591
+
592
+ **IMPORTANT:** Use Vite, not Next.js. Next.js 15's Turbopack does not support the webpack configurations required for this WASM pattern.
593
+
594
+ #### 1. Install Required Vite Plugins
595
+
596
+ ```bash
597
+ pnpm add -D vite vite-plugin-wasm vite-plugin-top-level-await @vitejs/plugin-react
598
+ ```
599
+
600
+ #### 2. Create `vite.config.ts`
601
+
602
+ This exact configuration is required. It's based on Midnight Network's official `example-bboard` implementation:
603
+
604
+ ```typescript
605
+ import { defineConfig } from 'vite';
606
+ import react from '@vitejs/plugin-react';
607
+ import wasm from 'vite-plugin-wasm';
608
+ import topLevelAwait from 'vite-plugin-top-level-await';
609
+
610
+ export default defineConfig({
611
+ cacheDir: './.vite',
612
+ build: {
613
+ target: 'esnext',
614
+ minify: false,
615
+ rollupOptions: {
616
+ output: {
617
+ manualChunks: {
618
+ // CRITICAL: Separate chunk for WASM modules
619
+ wasm: ['@midnight-ntwrk/onchain-runtime'],
620
+ },
621
+ },
622
+ },
623
+ commonjsOptions: {
624
+ // CRITICAL: Transform CommonJS to ESM
625
+ transformMixedEsModules: true,
626
+ extensions: ['.js', '.cjs'],
627
+ ignoreDynamicRequires: true,
628
+ },
629
+ },
630
+ plugins: [
631
+ react(),
632
+ wasm(),
633
+ topLevelAwait({
634
+ promiseExportName: '__tla',
635
+ promiseImportName: (i) => `__tla_${i}`,
636
+ }),
637
+ // CRITICAL: Custom resolver for WASM module handling
638
+ {
639
+ name: 'wasm-module-resolver',
640
+ resolveId(source, importer) {
641
+ if (
642
+ source === '@midnight-ntwrk/onchain-runtime' &&
643
+ importer &&
644
+ importer.includes('@midnight-ntwrk/compact-runtime')
645
+ ) {
646
+ return {
647
+ id: source,
648
+ external: false,
649
+ moduleSideEffects: true,
650
+ };
651
+ }
652
+ return null;
653
+ },
654
+ },
655
+ ],
656
+ optimizeDeps: {
657
+ esbuildOptions: {
658
+ target: 'esnext',
659
+ supported: { 'top-level-await': true },
660
+ platform: 'browser',
661
+ format: 'esm',
662
+ loader: { '.wasm': 'binary' },
663
+ },
664
+ // CRITICAL: Pre-bundle for CJS→ESM conversion
665
+ include: ['@midnight-ntwrk/compact-runtime'],
666
+ // CRITICAL: Exclude WASM from optimization
667
+ exclude: [
668
+ '@midnight-ntwrk/onchain-runtime',
669
+ '@midnight-ntwrk/onchain-runtime/midnight_onchain_runtime_wasm_bg.wasm',
670
+ '@midnight-ntwrk/onchain-runtime/midnight_onchain_runtime_wasm.js',
671
+ ],
672
+ },
673
+ resolve: {
674
+ extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.wasm'],
675
+ mainFields: ['browser', 'module', 'main'],
676
+ },
677
+ });
678
+ ```
679
+
680
+ #### Why This Configuration is Required
681
+
682
+ The SDK depends on `@midnight-ntwrk/compact-runtime`, which:
683
+ 1. Uses CommonJS (`require()`)
684
+ 2. Imports `@midnight-ntwrk/onchain-runtime` (WASM module with top-level await)
685
+ 3. Executes `ocrt.maxField()` at the top level
686
+
687
+ This creates a CJS/ESM/WASM incompatibility that requires the custom resolver plugin and specific optimization settings.
688
+
689
+ ### Why Next.js Doesn't Work
690
+
691
+ Next.js 15 uses **Turbopack** by default, which doesn't support the webpack configurations needed:
692
+
693
+ ```typescript
694
+ // These webpack configs don't work with Turbopack:
695
+ config.experiments = {
696
+ asyncWebAssembly: true,
697
+ topLevelAwait: true,
698
+ };
699
+ ```
700
+
701
+ Even with the `--webpack` flag, the CJS/ESM/WASM interaction is too complex for Next.js to handle correctly.
702
+
703
+ **Recommendation:** Use Vite for browser dApps (officially supported by Midnight Network).
704
+
705
+ ### React Example
706
+
707
+ ```tsx
708
+ import { GovernorManager, VoteChoice, ConfigPresets } from '@agora-dao-midnight/sdk';
709
+ import { randomBytes } from '@midnight-ntwrk/midnight-js-utils';
710
+ import { useEffect, useState } from 'react';
711
+ import pino from 'pino';
712
+
713
+ const logger = pino({ level: 'info' });
714
+
715
+ const config = {
716
+ governor: {
717
+ governance: ConfigPresets.testing(),
718
+ executionDelay: BigInt(60),
719
+ gracePeriod: BigInt(300),
720
+ maxActiveProposals: BigInt(100),
721
+ proposalLifetime: BigInt(3600),
722
+ emergencyVetoEnabled: true,
723
+ emergencyVetoThreshold: BigInt(75000),
724
+ },
725
+ token: {
726
+ name: 'Test DAO',
727
+ symbol: 'TDAO',
728
+ decimals: BigInt(18),
729
+ },
730
+ adminKey: randomBytes(32), // CRITICAL: Use secure random bytes!
731
+ };
732
+
733
+ const manager = new GovernorManager(config, logger);
734
+
735
+ function GovernanceApp() {
736
+ const [deployment, setDeployment] = useState(null);
737
+
738
+ useEffect(() => {
739
+ const subscription = manager.resolve().subscribe(setDeployment);
740
+ return () => subscription.unsubscribe();
741
+ }, []);
742
+
743
+ if (!deployment || deployment.status === 'in-progress') {
744
+ return <div>Connecting to wallet and deploying contract...</div>;
745
+ }
746
+
747
+ if (deployment.status === 'failed') {
748
+ return <div>Error: {deployment.error.message}</div>;
749
+ }
750
+
751
+ const api = deployment.api;
752
+
753
+ const handleCreateProposal = async () => {
754
+ try {
755
+ const actions = [
756
+ {
757
+ typ: 0,
758
+ token: tokenAddress,
759
+ amount: BigInt(1000),
760
+ to: recipientAddress,
761
+ configKey: new Uint8Array(32),
762
+ configValue: new Uint8Array(32),
763
+ },
764
+ ];
765
+
766
+ const { proposalId } = await api.createProposal(
767
+ 'My Proposal',
768
+ 'Description here',
769
+ creatorAddress,
770
+ actions
771
+ );
772
+
773
+ alert(`Proposal created: ${proposalId}`);
774
+ } catch (error) {
775
+ alert(`Error: ${error.message}`);
776
+ }
777
+ };
778
+
779
+ const handleVote = async (proposalId) => {
780
+ try {
781
+ await api.castVote(proposalId, voterAddress, VoteChoice.For);
782
+ alert('Vote cast successfully');
783
+ } catch (error) {
784
+ alert(`Error: ${error.message}`);
785
+ }
786
+ };
787
+
788
+ return (
789
+ <div>
790
+ <h1>Governor Contract</h1>
791
+ <p>Address: {api.deployedContractAddress}</p>
792
+
793
+ <button onClick={handleCreateProposal}>Create Proposal</button>
794
+ <button onClick={() => handleVote(1n)}>Vote For Proposal #1</button>
795
+ </div>
796
+ );
797
+ }
798
+ ```
799
+
800
+ ## Security Best Practices
801
+
802
+ ### 1. Always Use Secure Random for Admin Keys
803
+
804
+ ```typescript
805
+ import { randomBytes } from '@midnight-ntwrk/midnight-js-utils';
806
+
807
+ // ✅ Good: Cryptographically secure random
808
+ const adminKey = randomBytes(32);
809
+
810
+ // ❌ Bad: Predictable key
811
+ const adminKey = new Uint8Array(32); // All zeros - INSECURE!
812
+ ```
813
+
814
+ ### 2. Use Configuration Presets
815
+
816
+ ```typescript
817
+ // ✅ Good: Use tested presets
818
+ const config = ConfigPresets.production();
819
+
820
+ // ❌ Bad: Hardcoded magic numbers
821
+ const config = {
822
+ votingPeriod: BigInt(300), // What does 300 mean?
823
+ // ...
824
+ };
825
+ ```
826
+
827
+ ### 3. Validate User Inputs
828
+
829
+ ```typescript
830
+ import { validators } from '@agora-dao-midnight/sdk';
831
+
832
+ // ✅ Good: Validate before submission
833
+ validators.validateProposal(title, description, creator);
834
+ await api.createProposal(title, description, creator, actions);
835
+
836
+ // ❌ Bad: No validation
837
+ await api.createProposal(title, description, creator, actions);
838
+ ```
839
+
840
+ ### 4. Handle Errors Gracefully
841
+
842
+ ```typescript
843
+ // ✅ Good: Specific error handling
844
+ try {
845
+ await api.castVote(proposalId, voter, choice);
846
+ } catch (error) {
847
+ if (error instanceof InsufficientStakeError) {
848
+ showMessage('You need more stake to vote');
849
+ } else {
850
+ showMessage('An error occurred');
851
+ }
852
+ }
853
+
854
+ // ❌ Bad: Silent failures
855
+ try {
856
+ await api.castVote(proposalId, voter, choice);
857
+ } catch (error) {
858
+ // Silent failure
859
+ }
860
+ ```
861
+
862
+ ## Troubleshooting
863
+
864
+ ### Critical: WASM Initialization Error
865
+
866
+ **Problem:** `TypeError: ocrt.maxField is not a function`
867
+
868
+ **Root Cause:** CJS/ESM/WASM module incompatibility. The SDK depends on `@midnight-ntwrk/compact-runtime` (CommonJS) which requires `@midnight-ntwrk/onchain-runtime` (WASM with top-level await).
869
+
870
+ **Solutions:**
871
+
872
+ 1. **Use Vite, not Next.js**
873
+ ```bash
874
+ # Install Vite and required plugins
875
+ pnpm add -D vite vite-plugin-wasm vite-plugin-top-level-await @vitejs/plugin-react
876
+ ```
877
+
878
+ 2. **Configure vite.config.ts correctly** - See "Browser Integration" section above for complete configuration
879
+
880
+ 3. **Key configuration requirements:**
881
+ - Custom `wasm-module-resolver` plugin
882
+ - `transformMixedEsModules: true` in commonjsOptions
883
+ - Manual chunks for WASM modules
884
+ - Proper include/exclude in optimizeDeps
885
+
886
+ **Error Location:**
887
+ ```
888
+ File: node_modules/@midnight-ntwrk/compact-runtime/dist/runtime.js:90
889
+ Code: exports.MAX_FIELD = ocrt.maxField();
890
+ ```
891
+
892
+ **Why Next.js Doesn't Work:**
893
+ - Next.js 15 uses Turbopack which doesn't support required webpack configurations
894
+ - Even with `--webpack` flag, the CJS/ESM/WASM interaction is too complex
895
+
896
+ **Reference Implementations:**
897
+ - Working example: Midnight Network's `example-bboard` application
898
+ - Configuration tested and verified in production
899
+
900
+ ### Admin Key Security Error
901
+
902
+ **Problem:** "Insecure admin key detected" or predictable deployment behavior
903
+
904
+ **Root Cause:** Using an all-zeros Uint8Array for admin key
905
+
906
+ **Bad Example:**
907
+ ```typescript
908
+ const adminKey = new Uint8Array(32); // All zeros - INSECURE!
909
+ ```
910
+
911
+ **Correct Solution:**
912
+ ```typescript
913
+ import { randomBytes } from '@midnight-ntwrk/midnight-js-utils';
914
+
915
+ const adminKey = randomBytes(32); // Cryptographically secure
916
+ ```
917
+
918
+ **Why It Matters:**
919
+ - Admin keys control contract upgrades and governance
920
+ - Predictable keys can be exploited
921
+ - All production deployments MUST use secure random keys
922
+
923
+ ### Build Configuration Issues
924
+
925
+ **Problem:** "require is not defined" in browser
926
+
927
+ **Root Cause:** Contract module uses CommonJS but wasn't transformed to ESM
928
+
929
+ **Solution:**
930
+ ```typescript
931
+ // In vite.config.ts
932
+ commonjsOptions: {
933
+ transformMixedEsModules: true,
934
+ extensions: ['.js', '.cjs'],
935
+ }
936
+ ```
937
+
938
+ **Problem:** "Top-level await is not available"
939
+
940
+ **Root Cause:** WASM module with top-level await in wrong bundle chunk
941
+
942
+ **Solution:**
943
+ ```typescript
944
+ // In vite.config.ts
945
+ rollupOptions: {
946
+ output: {
947
+ manualChunks: {
948
+ wasm: ['@midnight-ntwrk/onchain-runtime'],
949
+ },
950
+ },
951
+ }
952
+ ```
953
+
954
+ **Problem:** Module optimization errors during build
955
+
956
+ **Root Cause:** Vite trying to optimize WASM and CJS modules together
957
+
958
+ **Solution:**
959
+ ```typescript
960
+ // In vite.config.ts
961
+ optimizeDeps: {
962
+ include: ['@midnight-ntwrk/compact-runtime'], // Pre-bundle for CJS→ESM
963
+ exclude: [
964
+ '@midnight-ntwrk/onchain-runtime', // Don't optimize WASM
965
+ '@agora-dao-midnight/contract', // Don't optimize contract
966
+ ],
967
+ }
968
+ ```
969
+
970
+ ### Wallet Connection Issues
971
+
972
+ **Problem:** "Could not find Midnight Lace wallet"
973
+
974
+ **Solutions:**
975
+ - Install Lace wallet extension from [lace.io](https://www.lace.io/)
976
+ - Refresh the browser page
977
+ - Check browser console for errors
978
+ - Ensure extension is enabled
979
+
980
+ **Problem:** "Incompatible version of Midnight Lace wallet"
981
+
982
+ **Solutions:**
983
+ - Update Lace extension to latest version
984
+ - SDK requires Lace API v1.x
985
+ - Check compatibility in browser console
986
+
987
+ ### Transaction Failures
988
+
989
+ **Problem:** InsufficientStakeError
990
+
991
+ **Solutions:**
992
+ - Register stake before creating proposals
993
+ - Ensure stake amount ≥ proposalThreshold
994
+ - Check staking period hasn't expired
995
+
996
+ **Problem:** TransactionError: "User rejected transaction"
997
+
998
+ **Solutions:**
999
+ - User must approve transaction in Lace wallet
1000
+ - Check wallet has sufficient tDUST balance
1001
+ - Verify transaction parameters are correct
1002
+
1003
+ ### Build Errors
1004
+
1005
+ **Problem:** TypeScript compilation errors
1006
+
1007
+ **Solutions:**
1008
+ ```bash
1009
+ # Update dependencies
1010
+ pnpm install
1011
+
1012
+ # Rebuild SDK
1013
+ pnpm run build
1014
+ ```
1015
+
1016
+ **Problem:** Module not found errors
1017
+
1018
+ **Solutions:**
1019
+ - Ensure all dependencies are installed
1020
+ - Check `node_modules` exists
1021
+ - Verify import paths are correct
1022
+
1023
+ ### Runtime Errors
1024
+
1025
+ **Problem:** "Contract not found at address"
1026
+
1027
+ **Solutions:**
1028
+ - Verify contract address is correct
1029
+ - Check network connection
1030
+ - Ensure contract is deployed on current network
1031
+
1032
+ **Problem:** "InvalidParameterError"
1033
+
1034
+ **Solutions:**
1035
+ - Check parameter types and formats
1036
+ - Validate inputs before submission
1037
+ - Review API documentation for correct usage
1038
+
1039
+ ### Cache Issues
1040
+
1041
+ **Problem:** Changes not reflected after rebuild
1042
+
1043
+ **Solutions:**
1044
+ ```bash
1045
+ # Clear Vite cache
1046
+ rm -rf node_modules/.vite
1047
+
1048
+ # Clear browser cache or hard refresh
1049
+ # Chrome/Edge: Ctrl+Shift+R (Windows) or Cmd+Shift+R (Mac)
1050
+ # Firefox: Ctrl+F5 (Windows) or Cmd+Shift+R (Mac)
1051
+ ```
1052
+
1053
+ ## Development
1054
+
1055
+ ### Building from Source
1056
+
1057
+ ```bash
1058
+ # Clone repository
1059
+ git clone https://github.com/your-org/agora-dao-midnight-sdk
1060
+ cd internalAgoraMidnightSDK
1061
+
1062
+ # Install dependencies
1063
+ pnpm install
1064
+
1065
+ # Build contract first
1066
+ cd contract
1067
+ pnpm run build
1068
+
1069
+ # Build SDK
1070
+ cd ../sdk
1071
+ pnpm run build
1072
+ ```
1073
+
1074
+ ### Project Structure
1075
+
1076
+ ```
1077
+ sdk/
1078
+ ├── src/
1079
+ │ ├── browser-providers.ts # Lace wallet integration
1080
+ │ ├── governor-api.ts # Main API implementation
1081
+ │ ├── manager.ts # Observable state manager
1082
+ │ ├── types.ts # Type definitions
1083
+ │ ├── errors.ts # Error classes
1084
+ │ ├── validators.ts # Input validation
1085
+ │ ├── common-types.ts # Shared types
1086
+ │ ├── index.ts # Main exports
1087
+ │ └── utils/ # Utility functions
1088
+ ├── package.json
1089
+ ├── tsconfig.json
1090
+ └── README.md
1091
+ ```
1092
+
1093
+ ## Prerequisites
1094
+
1095
+ ### Required Software
1096
+
1097
+ 1. **Lace Wallet Extension**
1098
+ - Install from [lace.io](https://www.lace.io/)
1099
+ - Create or import wallet
1100
+ - Switch to Midnight TestNet
1101
+
1102
+ 2. **TestNet Tokens (tDUST)**
1103
+ - Request from [Midnight Faucet](https://faucet.testnet.midnight.network/)
1104
+ - Needed to pay for transactions
1105
+
1106
+ 3. **Modern Browser**
1107
+ - Chrome/Edge 89+
1108
+ - Firefox 89+
1109
+ - Safari 15+
1110
+
1111
+ ### Browser APIs Required
1112
+
1113
+ - IndexedDB (private state storage)
1114
+ - Web Crypto API (cryptographic operations)
1115
+ - Fetch API (HTTP requests)
1116
+ - WebSocket (real-time updates)
1117
+
1118
+ ## Resources
1119
+
1120
+ - [Midnight Documentation](https://docs.midnight.network/)
1121
+ - [Lace Wallet](https://www.lace.io/)
1122
+ - [TestNet Faucet](https://faucet.testnet.midnight.network/)
1123
+ - [Compact Language Guide](https://docs.midnight.network/develop/compact/)
1124
+ - [Production Readiness Guide](./PRODUCTION_READY.md)
1125
+
1126
+ ## License
1127
+
1128
+ Apache-2.0
1129
+
1130
+ ## Support
1131
+
1132
+ For issues and questions:
1133
+ - GitHub Issues: [Create an issue](https://github.com/your-org/agora-dao-midnight-sdk/issues)
1134
+ - Documentation: [Read the docs](https://docs.midnight.network/)