@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
@@ -0,0 +1,274 @@
1
+ import { type GovernorProviders, type GovernorCircuits } from "./common-types";
2
+ import {
3
+ concatMap,
4
+ filter,
5
+ firstValueFrom,
6
+ interval,
7
+ map,
8
+ of,
9
+ take,
10
+ tap,
11
+ throwError,
12
+ timeout,
13
+ catchError
14
+ } from "rxjs";
15
+ import { pipe as fnPipe } from "fp-ts/function";
16
+ import { type Logger } from "pino";
17
+ import {
18
+ type InitialAPI,
19
+ type ConnectedAPI,
20
+ type Configuration
21
+ } from "@midnight-ntwrk/dapp-connector-api";
22
+ import { levelPrivateStateProvider } from "@midnight-ntwrk/midnight-js-level-private-state-provider";
23
+ import { FetchZkConfigProvider } from "@midnight-ntwrk/midnight-js-fetch-zk-config-provider";
24
+ import { httpClientProofProvider } from "@midnight-ntwrk/midnight-js-http-client-proof-provider";
25
+ import { indexerPublicDataProvider } from "@midnight-ntwrk/midnight-js-indexer-public-data-provider";
26
+ import {
27
+ type BalancedProvingRecipe,
28
+ type WalletProvider
29
+ } from "@midnight-ntwrk/midnight-js-types";
30
+ import type {
31
+ ShieldedCoinInfo,
32
+ UnprovenTransaction,
33
+ TransactionId,
34
+ FinalizedTransaction,
35
+ CoinPublicKey,
36
+ EncPublicKey
37
+ } from "@midnight-ntwrk/ledger-v6";
38
+ import semver from "semver";
39
+ import {
40
+ getNetworkId,
41
+ setNetworkId
42
+ } from "@midnight-ntwrk/midnight-js-network-id";
43
+
44
+ // Helper functions for hex encoding/decoding
45
+ const toHexString = (bytes: Uint8Array): string =>
46
+ Array.from(bytes)
47
+ .map((b) => b.toString(16).padStart(2, "0"))
48
+ .join("");
49
+
50
+ const _fromHexString = (hex: string): Uint8Array =>
51
+ new Uint8Array(hex.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)));
52
+
53
+ /**
54
+ * Initializes and returns the providers required for Governor contract interactions in a browser environment.
55
+ *
56
+ * @param logger The logger instance for logging.
57
+ * @param networkId The network ID to connect to (e.g., 'testnet', 'mainnet').
58
+ * @returns A promise that resolves to configured GovernorProviders.
59
+ *
60
+ * @remarks
61
+ * This function connects to the Midnight Lace wallet extension and retrieves network configuration
62
+ * from the wallet. The network ID is specified by the caller.
63
+ */
64
+ export const initializeProviders = async (
65
+ logger: Logger,
66
+ networkId: string = "testnet"
67
+ ): Promise<GovernorProviders> => {
68
+ const { wallet, config } = await connectToWallet(logger, networkId);
69
+ const zkConfigPath = window.location.origin;
70
+
71
+ logger.info(`Connecting to wallet with network ID: ${getNetworkId()}`);
72
+
73
+ // Get wallet addresses
74
+ const addresses = await wallet.getShieldedAddresses();
75
+
76
+ return {
77
+ privateStateProvider: levelPrivateStateProvider({
78
+ privateStateStoreName: "governor-private-state"
79
+ }),
80
+ zkConfigProvider: new FetchZkConfigProvider<GovernorCircuits>(
81
+ zkConfigPath,
82
+ fetch.bind(window)
83
+ ),
84
+ proofProvider: config.proverServerUri
85
+ ? httpClientProofProvider(config.proverServerUri)
86
+ : httpClientProofProvider(""), // Fallback - proving may be done by wallet
87
+ publicDataProvider: indexerPublicDataProvider(
88
+ config.indexerUri,
89
+ config.indexerWsUri
90
+ ),
91
+ walletProvider: createWalletProvider(wallet, addresses),
92
+ midnightProvider: {
93
+ async submitTx(tx: FinalizedTransaction): Promise<TransactionId> {
94
+ // Convert transaction to hex string for the connector API
95
+ const serialized = tx.serialize();
96
+ const hexString = toHexString(serialized);
97
+ await wallet.submitTransaction(hexString);
98
+ // Return the transaction hash as the ID
99
+ return tx.transactionHash() as TransactionId;
100
+ }
101
+ }
102
+ };
103
+ };
104
+
105
+ /**
106
+ * Creates a WalletProvider from the connected wallet API.
107
+ */
108
+ const createWalletProvider = (
109
+ wallet: ConnectedAPI,
110
+ addresses: {
111
+ shieldedCoinPublicKey: string;
112
+ shieldedEncryptionPublicKey: string;
113
+ }
114
+ ): WalletProvider => {
115
+ // Note: The bech32m addresses need to be converted to the expected key types
116
+ // This conversion may need adjustment based on the actual key format expected
117
+ const coinPublicKey =
118
+ addresses.shieldedCoinPublicKey as unknown as CoinPublicKey;
119
+ const encryptionPublicKey =
120
+ addresses.shieldedEncryptionPublicKey as unknown as EncPublicKey;
121
+
122
+ return {
123
+ getCoinPublicKey(): CoinPublicKey {
124
+ return coinPublicKey;
125
+ },
126
+ getEncryptionPublicKey(): EncPublicKey {
127
+ return encryptionPublicKey;
128
+ },
129
+ async balanceTx(
130
+ tx: UnprovenTransaction,
131
+ _newCoins?: ShieldedCoinInfo[],
132
+ _ttl?: Date
133
+ ): Promise<BalancedProvingRecipe> {
134
+ // Use the wallet's balancing capability
135
+ // Convert Uint8Array to hex string for the connector API
136
+ const serialized = tx.serialize();
137
+ const hexString = toHexString(serialized);
138
+ const _result = await wallet.balanceUnsealedTransaction(hexString);
139
+
140
+ // The wallet returns a balanced transaction as a hex string
141
+ // We need to convert it back and return as NothingToProve
142
+ // Note: The actual deserialization may need the Transaction.deserialize with proper markers
143
+ // For now, we return the original transaction as the wallet handles proving internally
144
+ return {
145
+ type: "NothingToProve" as const,
146
+ transaction: tx // The wallet should have balanced this
147
+ };
148
+ }
149
+ };
150
+ };
151
+
152
+ /**
153
+ * Connects to the Midnight Lace wallet extension.
154
+ *
155
+ * @param logger The logger instance for logging.
156
+ * @param networkId The network ID to connect to.
157
+ * @returns A promise that resolves to the wallet API and configuration.
158
+ */
159
+ const connectToWallet = (
160
+ logger: Logger,
161
+ networkId: string
162
+ ): Promise<{ wallet: ConnectedAPI; config: Configuration }> => {
163
+ const COMPATIBLE_CONNECTOR_API_VERSION = "4.x";
164
+
165
+ return firstValueFrom(
166
+ fnPipe(
167
+ interval(100),
168
+ map(() => {
169
+ // Find wallet in window.midnight
170
+ const midnight = window.midnight;
171
+ if (!midnight) return undefined;
172
+
173
+ // The new API uses rdns keys instead of named keys like mnLace
174
+ // Look for any available wallet
175
+ const walletKeys = Object.keys(midnight).filter(
176
+ (key) => key !== "addEventListener"
177
+ );
178
+ if (walletKeys.length === 0) return undefined;
179
+
180
+ return midnight[walletKeys[0]] as InitialAPI | undefined;
181
+ }),
182
+ tap((connectorAPI) => {
183
+ logger.info(connectorAPI, "Check for wallet connector API");
184
+ }),
185
+ filter((connectorAPI): connectorAPI is InitialAPI => !!connectorAPI),
186
+ concatMap((connectorAPI) =>
187
+ semver.satisfies(
188
+ connectorAPI.apiVersion,
189
+ COMPATIBLE_CONNECTOR_API_VERSION
190
+ )
191
+ ? of(connectorAPI)
192
+ : throwError(() => {
193
+ logger.error(
194
+ {
195
+ expected: COMPATIBLE_CONNECTOR_API_VERSION,
196
+ actual: connectorAPI.apiVersion
197
+ },
198
+ "Incompatible version of wallet connector API"
199
+ );
200
+
201
+ return new Error(
202
+ `Incompatible version of Midnight Lace wallet found. Require '${COMPATIBLE_CONNECTOR_API_VERSION}', got '${connectorAPI.apiVersion}'.`
203
+ );
204
+ })
205
+ ),
206
+ tap((connectorAPI) => {
207
+ logger.info(
208
+ connectorAPI,
209
+ "Compatible wallet connector API found. Connecting."
210
+ );
211
+ }),
212
+ take(1),
213
+ timeout({
214
+ first: 1_000,
215
+ with: () =>
216
+ throwError(() => {
217
+ logger.error("Could not find wallet connector API");
218
+
219
+ return new Error(
220
+ "Could not find Midnight Lace wallet. Extension installed?"
221
+ );
222
+ })
223
+ }),
224
+ concatMap(async (connectorAPI) => {
225
+ // Connect to the wallet with the specified network ID
226
+ const connectedAPI = await connectorAPI.connect(networkId);
227
+
228
+ // Set the network ID globally
229
+ setNetworkId(networkId);
230
+
231
+ logger.info("Connected to wallet");
232
+
233
+ return { walletConnectorAPI: connectedAPI, connectorAPI };
234
+ }),
235
+ timeout({
236
+ first: 30_000, // Increased timeout for user approval
237
+ with: () =>
238
+ throwError(() => {
239
+ logger.error("Wallet connector API has failed to respond");
240
+
241
+ return new Error(
242
+ "Midnight Lace wallet has failed to respond. Extension enabled?"
243
+ );
244
+ })
245
+ }),
246
+ catchError((error, apis) =>
247
+ error
248
+ ? throwError(() => {
249
+ logger.error("Unable to connect to wallet");
250
+ return new Error(
251
+ "Application is not authorized or connection failed"
252
+ );
253
+ })
254
+ : apis
255
+ ),
256
+ concatMap(async ({ walletConnectorAPI }) => {
257
+ const config = await walletConnectorAPI.getConfiguration();
258
+
259
+ logger.info(
260
+ "Connected to wallet connector API and retrieved service configuration"
261
+ );
262
+
263
+ return { wallet: walletConnectorAPI, config };
264
+ })
265
+ )
266
+ );
267
+ };
268
+
269
+ // Extend Window interface for TypeScript
270
+ declare global {
271
+ interface Window {
272
+ midnight?: Record<string, InitialAPI>;
273
+ }
274
+ }
@@ -0,0 +1,51 @@
1
+ import type {
2
+ ImpureCircuitId,
3
+ MidnightProviders,
4
+ Contract
5
+ } from "@midnight-ntwrk/midnight-js-types";
6
+ import type {
7
+ DeployedContract,
8
+ FoundContract
9
+ } from "@midnight-ntwrk/midnight-js-contracts";
10
+
11
+ // Use Contract type from midnight-js-types to avoid importing the actual Contract class
12
+ // This prevents eager loading of the contract CJS module which requires WASM
13
+ export type GovernorContract = Contract;
14
+
15
+ export type GovernorCircuits = ImpureCircuitId<GovernorContract>;
16
+
17
+ export const GovernorPrivateStateId = "governorPrivateState";
18
+
19
+ // Define GovernorPrivateState type locally to avoid importing from contract module
20
+ // which would trigger WASM initialization
21
+ export type GovernorPrivateState = {
22
+ secretKey: Uint8Array | null;
23
+ currentTime: bigint;
24
+ userStake: {
25
+ amount: bigint;
26
+ delegatedTo: Uint8Array;
27
+ stakedAt: bigint;
28
+ };
29
+ votingHistory: any[];
30
+ proposalHistory: any[];
31
+ governanceConfig: {
32
+ votingPeriod: bigint;
33
+ quorumThreshold: bigint;
34
+ proposalThreshold: bigint;
35
+ proposalLifetime: bigint;
36
+ executionDelay: bigint;
37
+ gracePeriod: bigint;
38
+ minStakeAmount: bigint;
39
+ stakingPeriod: bigint;
40
+ };
41
+ };
42
+
43
+ export type GovernorProviders = MidnightProviders<
44
+ GovernorCircuits,
45
+ typeof GovernorPrivateStateId,
46
+ GovernorPrivateState
47
+ >;
48
+
49
+ export type DeployedGovernorContract =
50
+ | DeployedContract<GovernorContract>
51
+ | FoundContract<GovernorContract>;
package/src/errors.ts ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Custom error classes for the Agora DAO Governor SDK.
3
+ *
4
+ * @module errors
5
+ */
6
+
7
+ /**
8
+ * Base error class for all SDK errors.
9
+ */
10
+ export class GovernorSDKError extends Error {
11
+ constructor(message: string) {
12
+ super(message);
13
+ this.name = "GovernorSDKError";
14
+ Object.setPrototypeOf(this, GovernorSDKError.prototype);
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Thrown when wallet is not connected.
20
+ */
21
+ export class WalletNotConnectedError extends GovernorSDKError {
22
+ constructor() {
23
+ super(
24
+ "Wallet not connected. Please ensure Lace wallet is installed and connected."
25
+ );
26
+ this.name = "WalletNotConnectedError";
27
+ Object.setPrototypeOf(this, WalletNotConnectedError.prototype);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Thrown when user has insufficient stake for an operation.
33
+ */
34
+ export class InsufficientStakeError extends GovernorSDKError {
35
+ constructor(required: bigint, actual: bigint) {
36
+ super(`Insufficient stake. Required: ${required}, Actual: ${actual}`);
37
+ this.name = "InsufficientStakeError";
38
+ Object.setPrototypeOf(this, InsufficientStakeError.prototype);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Thrown when a parameter is invalid.
44
+ */
45
+ export class InvalidParameterError extends GovernorSDKError {
46
+ constructor(parameterName: string, reason: string) {
47
+ super(`Invalid parameter '${parameterName}': ${reason}`);
48
+ this.name = "InvalidParameterError";
49
+ Object.setPrototypeOf(this, InvalidParameterError.prototype);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Thrown when a contract is not found at the specified address.
55
+ */
56
+ export class ContractNotFoundError extends GovernorSDKError {
57
+ constructor(address: string) {
58
+ super(`Contract not found at address: ${address}`);
59
+ this.name = "ContractNotFoundError";
60
+ Object.setPrototypeOf(this, ContractNotFoundError.prototype);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Thrown when a proposal is not found.
66
+ */
67
+ export class ProposalNotFoundError extends GovernorSDKError {
68
+ constructor(proposalId: bigint) {
69
+ super(`Proposal not found with ID: ${proposalId}`);
70
+ this.name = "ProposalNotFoundError";
71
+ Object.setPrototypeOf(this, ProposalNotFoundError.prototype);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Thrown when a transaction fails.
77
+ */
78
+ export class TransactionError extends GovernorSDKError {
79
+ constructor(
80
+ message: string,
81
+ public readonly txHash?: string
82
+ ) {
83
+ super(
84
+ `Transaction failed: ${message}${txHash ? ` (txHash: ${txHash})` : ""}`
85
+ );
86
+ this.name = "TransactionError";
87
+ Object.setPrototypeOf(this, TransactionError.prototype);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Thrown when contract deployment fails.
93
+ */
94
+ export class DeploymentError extends GovernorSDKError {
95
+ constructor(message: string) {
96
+ super(`Contract deployment failed: ${message}`);
97
+ this.name = "DeploymentError";
98
+ Object.setPrototypeOf(this, DeploymentError.prototype);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Thrown when configuration is invalid.
104
+ */
105
+ export class InvalidConfigurationError extends GovernorSDKError {
106
+ constructor(message: string) {
107
+ super(`Invalid configuration: ${message}`);
108
+ this.name = "InvalidConfigurationError";
109
+ Object.setPrototypeOf(this, InvalidConfigurationError.prototype);
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Thrown when network ID is not set.
115
+ */
116
+ export class NetworkNotConfiguredError extends GovernorSDKError {
117
+ constructor() {
118
+ super(
119
+ "Network ID not configured. Set window.midnight.mnLace.networkId before initializing providers."
120
+ );
121
+ this.name = "NetworkNotConfiguredError";
122
+ Object.setPrototypeOf(this, NetworkNotConfiguredError.prototype);
123
+ }
124
+ }