@obscura-fhe/sdk 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  All notable changes to `@obscura-fhe/sdk` are documented in this file.
4
4
 
5
+ ## [1.0.2] - 2026-05-30
6
+
7
+ ### Added
8
+
9
+ - **Credit module:** `getMarketUtilization()` — public pool aggregates (`utilizationBps`, `totalSupplyAssets`, `totalBorrowAssets`)
10
+ - **Credit module:** `getPositionHandles()` — encrypted position handles as opaque hex; optional plaintext shadows with explicit warning
11
+ - Exported types: `CreditMarketUtilization`, `CreditPositionHandles`
12
+
13
+ ## [1.0.1] - 2026-05-29
14
+
15
+ ### Fixed
16
+
17
+ - **Vote module:** `getProposalCount()` now reads on-chain `nextProposalId()` instead of non-existent `proposalCount()` (verified against Arbitrum Sepolia ObscuraVote `0xe358…1730`)
18
+
19
+ ### Added
20
+
21
+ - `ActivityModule.isConfigured()` — check Supabase credentials before querying
22
+ - Clearer activity error messages with default Supabase URL and setup instructions
23
+ - Module examples: `reputation`, `activity`, `notifications`, `pay`, `credit`, `vote`
24
+ - README: requirements matrix, viem rpcUrl/transport examples, TypeScript `verbatimModuleSyntax` guidance
25
+
26
+ ### Docs
27
+
28
+ - Developer portal quick-start, SDK onboarding, and SDK reference updated with hidden requirements
29
+
5
30
  ## [1.0.0] - 2026-05-29
6
31
 
7
32
  ### Added
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @obscura-fhe/sdk
2
2
 
3
- Official TypeScript SDK for **Obscura Pay**, **Credit**, and Vote on Arbitrum Sepolia.
3
+ Official TypeScript SDK for **Obscura Pay**, **Credit**, and **Vote** on Arbitrum Sepolia.
4
4
 
5
- Privacy-first DeFi with FHE (Fully Homomorphic Encryption). Framework-agnostic — works in Node.js, browsers, and automation scripts.
5
+ Privacy-first DeFi with FHE (Fully Homomorphic Encryption). Framework-agnostic — works in Node.js, browsers, Vite, Next.js, and automation scripts.
6
6
 
7
7
  ## Install
8
8
 
@@ -10,7 +10,22 @@ Privacy-first DeFi with FHE (Fully Homomorphic Encryption). Framework-agnostic
10
10
  npm install @obscura-fhe/sdk viem
11
11
  ```
12
12
 
13
- `viem` is a peer dependency (v2+).
13
+ `viem` is a **peer dependency** (v2+). You must install it alongside the SDK.
14
+
15
+ ## Requirements at a glance
16
+
17
+ | Module | Wallet | RPC / chain | Supabase | FHE provider |
18
+ |--------|--------|-------------|----------|--------------|
19
+ | `reputation` | No | No | No | No |
20
+ | `notifications` | No | No | No | No |
21
+ | `activity` | No | No | **Yes** (URL + anon key) | No |
22
+ | `pay` / `credit` / `vote` reads | No | **Yes** (default RPC) | No | No |
23
+ | Encrypted writes (`buildShield`, etc.) | Optional* | Yes | No | **Yes** (or pre-encrypted input) |
24
+ | `sendCall()` | **Yes** (`walletClient`) | Yes | No | If write needs FHE |
25
+
26
+ \*Use `encodeCall()` and sign externally if you do not inject `walletClient`.
27
+
28
+ **Network:** Arbitrum Sepolia (`chainId` **421614**) by default.
14
29
 
15
30
  ## Quick start
16
31
 
@@ -18,49 +33,153 @@ npm install @obscura-fhe/sdk viem
18
33
  import { ObscuraSDK } from "@obscura-fhe/sdk";
19
34
 
20
35
  const sdk = ObscuraSDK.create({
21
- supabaseAnonKey: process.env.OBSCURA_SUPABASE_ANON_KEY, // required for activity feed
36
+ // Required for activity feed only:
37
+ supabaseAnonKey: process.env.OBSCURA_SUPABASE_ANON_KEY,
38
+ // supabaseUrl defaults to Obscura production project
22
39
  });
23
40
 
24
- // Reputation (off-chain, obscura-api)
41
+ // Reputation — no wallet, no Supabase
25
42
  const summary = await sdk.reputation.getSummary("0xYourWallet...");
26
43
  console.log(summary.tier, summary.totalCappedWeight);
27
44
 
28
- // Activity feed (Supabase)
29
- const { items, hasMore } = await sdk.activity.listForWallet("0xYourWallet...", {
30
- filter: "credit",
45
+ // Activity requires supabaseAnonKey
46
+ if (sdk.activity.isConfigured()) {
47
+ const { items } = await sdk.activity.listForWallet("0xYourWallet...", { filter: "credit" });
48
+ console.log(items.length);
49
+ }
50
+
51
+ // On-chain read — uses default Arbitrum Sepolia RPC
52
+ const proposalCount = await sdk.vote.getProposalCount(); // reads nextProposalId()
53
+ const balanceCt = await sdk.pay.getShieldedBalance("0xYourWallet...");
54
+ ```
55
+
56
+ ## viem integration (recommended)
57
+
58
+ Pass an explicit RPC URL and viem clients when you need custom transport, account abstraction, or `sendCall()`:
59
+
60
+ ```typescript
61
+ import { ObscuraSDK } from "@obscura-fhe/sdk";
62
+ import { createPublicClient, createWalletClient, http, custom } from "viem";
63
+ import { arbitrumSepolia } from "viem/chains";
64
+ import { privateKeyToAccount } from "viem/accounts";
65
+
66
+ const rpcUrl = process.env.ARB_SEPOLIA_RPC_URL ?? "https://sepolia-rollup.arbitrum.io/rpc";
67
+
68
+ const publicClient = createPublicClient({
69
+ chain: arbitrumSepolia,
70
+ transport: http(rpcUrl),
71
+ });
72
+
73
+ // Browser wallet (MetaMask, etc.)
74
+ const walletClient = createWalletClient({
75
+ chain: arbitrumSepolia,
76
+ transport: custom(window.ethereum),
77
+ });
78
+
79
+ // Or Node with private key
80
+ // const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
81
+ // const walletClient = createWalletClient({ account, chain: arbitrumSepolia, transport: http(rpcUrl) });
82
+
83
+ const sdk = ObscuraSDK.create({
84
+ chainId: 421614,
85
+ rpcUrl,
86
+ publicClient,
87
+ walletClient,
88
+ supabaseUrl: process.env.OBSCURA_SUPABASE_URL,
89
+ supabaseAnonKey: process.env.OBSCURA_SUPABASE_ANON_KEY,
90
+ });
91
+
92
+ // Shorthand — SDK creates publicClient from rpcUrl when publicClient omitted:
93
+ const sdkMinimal = ObscuraSDK.create({ rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc" });
94
+ ```
95
+
96
+ ## Activity module — Supabase setup
97
+
98
+ The activity feed reads indexed on-chain events from Supabase (`obscura_activity`). **Both** credentials are required:
99
+
100
+ ```typescript
101
+ import { ObscuraSDK, DEFAULT_SUPABASE_URL } from "@obscura-fhe/sdk";
102
+
103
+ const sdk = ObscuraSDK.create({
104
+ supabaseUrl: process.env.OBSCURA_SUPABASE_URL ?? DEFAULT_SUPABASE_URL,
105
+ supabaseAnonKey: process.env.OBSCURA_SUPABASE_ANON_KEY!, // required
106
+ });
107
+
108
+ if (!sdk.activity.isConfigured()) {
109
+ throw new Error("Set OBSCURA_SUPABASE_ANON_KEY for activity queries");
110
+ }
111
+
112
+ const { items, hasMore } = await sdk.activity.listForWallet(wallet, {
113
+ filter: "vote", // all | pay | credit | vote | stream | ...
31
114
  page: 0,
115
+ pageSize: 20,
32
116
  });
117
+ ```
33
118
 
34
- // Notifications
35
- const vapidKey = await sdk.notifications.getVapidPublicKey();
36
- const prefs = await sdk.notifications.getPrefs("0xYourWallet...");
119
+ **Environment variables:**
37
120
 
38
- // On-chain reads
39
- const balanceCtHash = await sdk.pay.getShieldedBalance("0xYourWallet...");
40
- const proposalCount = await sdk.vote.getProposalCount();
121
+ ```bash
122
+ OBSCURA_SUPABASE_URL=https://quoovjkjwgtdqwdofubh.supabase.co # optional this is the default
123
+ OBSCURA_SUPABASE_ANON_KEY=eyJ... # required for activity
124
+ ```
125
+
126
+ Get the anon key from Supabase → Project Settings → API → `anon` `public` key.
127
+
128
+ ## TypeScript setup
129
+
130
+ The SDK ships ESM + CJS with `.d.ts` types. Recommended `tsconfig.json`:
41
131
 
42
- // Transaction builders (return ContractCall — sign with your wallet)
43
- const call = await sdk.pay.buildTransfer(to, amount, encryptedAmount);
44
- const calldata = sdk.encodeCall(call);
132
+ ```json
133
+ {
134
+ "compilerOptions": {
135
+ "target": "ES2022",
136
+ "module": "ESNext",
137
+ "moduleResolution": "bundler",
138
+ "strict": true,
139
+ "skipLibCheck": true,
140
+ "esModuleInterop": true,
141
+ "verbatimModuleSyntax": true
142
+ }
143
+ }
45
144
  ```
46
145
 
47
- ## Modules
146
+ ### `verbatimModuleSyntax`
48
147
 
49
- | Module | Methods | Backend |
50
- |--------|---------|---------|
51
- | `pay` | `getShieldedBalance`, `buildShield`, `buildUnshield`, `buildTransfer` | On-chain (ocUSDC_Pay) |
52
- | `credit` | `getMarketAddress`, `buildSupplyCollateral`, `buildBorrow`, `buildRepay` | On-chain (credit market) |
53
- | `vote` | `getProposalCount`, `getProposal`, `buildCastVote`, `buildDelegate` | On-chain (ObscuraVote) |
54
- | `reputation` | `getSummary` | obscura-api REST |
55
- | `activity` | `listForWallet`, `getEventFilters` | Supabase |
56
- | `notifications` | `getVapidPublicKey`, `getPrefs`, `savePrefs`, `subscribe`, `unsubscribe` | obscura-api REST |
148
+ With `verbatimModuleSyntax: true`, TypeScript requires **type-only imports** for types:
149
+
150
+ ```typescript
151
+ // Correct
152
+ import { ObscuraSDK } from "@obscura-fhe/sdk";
153
+ import type { FheProvider, ReputationSummary, ContractCall } from "@obscura-fhe/sdk";
154
+
155
+ // Wrong TS1363 error under verbatimModuleSyntax
156
+ import { FheProvider, ReputationSummary } from "@obscura-fhe/sdk";
157
+ ```
158
+
159
+ If you cannot use type-only imports, set `"verbatimModuleSyntax": false` or use `"isolatedModules": true` without verbatim (Vite/Next default).
160
+
161
+ ## Module examples
162
+
163
+ See [`examples/`](./examples/) for runnable scripts:
57
164
 
58
- ## Configuration
165
+ | File | Module |
166
+ |------|--------|
167
+ | `examples/reputation.ts` | Reputation summary |
168
+ | `examples/activity.ts` | Activity feed (Supabase) |
169
+ | `examples/notifications.ts` | VAPID + prefs |
170
+ | `examples/pay.ts` | Shielded balance + transfer builder |
171
+ | `examples/credit.ts` | Market address + borrow builder |
172
+ | `examples/vote.ts` | Proposal count + delegate |
173
+ | `examples/basic-usage.ts` | Combined smoke test |
174
+
175
+ Run: `npm run example:basic` or `npx tsx examples/reputation.ts`
176
+
177
+ ## Configuration reference
59
178
 
60
179
  ```typescript
61
180
  ObscuraSDK.create({
62
181
  chainId: 421614, // default: Arbitrum Sepolia
63
- rpcUrl: "https://...", // default: Arbitrum Sepolia RPC
182
+ rpcUrl: "https://...", // default: Arbitrum Sepolia public RPC
64
183
  apiUrl: "https://...", // default: obscura-api production
65
184
  supabaseUrl: "https://...", // default: Obscura Supabase project
66
185
  supabaseAnonKey: "...", // required for activity module
@@ -73,14 +192,14 @@ ObscuraSDK.create({
73
192
 
74
193
  ## FHE (encrypted writes)
75
194
 
76
- Encrypted contract inputs require a host-supplied `FheProvider` (typically wrapping `@cofhe/sdk`):
195
+ Encrypted contract inputs require a host-supplied `FheProvider` (typically wrapping `@fhenixprotocol/cofhe-sdk`):
77
196
 
78
197
  ```typescript
79
198
  import type { FheProvider } from "@obscura-fhe/sdk";
80
199
 
81
200
  const fhe: FheProvider = {
82
201
  async encryptUint64(value, { contractAddress }) {
83
- // wrap @cofhe/sdk encrypt here
202
+ // wrap CoFHE encrypt — user-triggered only
84
203
  return { ctHash, securityZone, utype, signature };
85
204
  },
86
205
  };
@@ -99,11 +218,24 @@ const call = sdk.vote.buildDelegate("0xDelegatee...");
99
218
  const hash = await sdk.sendCall(call, account); // requires walletClient
100
219
  ```
101
220
 
221
+ ## Modules
222
+
223
+ | Module | Methods | Backend |
224
+ |--------|---------|---------|
225
+ | `pay` | `getShieldedBalance`, `buildShield`, `buildUnshield`, `buildTransfer` | On-chain (ocUSDC_Pay) |
226
+ | `credit` | `getMarketAddress`, `buildSupplyCollateral`, `buildBorrow`, `buildRepay` | On-chain (credit market) |
227
+ | `vote` | `getProposalCount`, `getProposal`, `buildCastVote`, `buildDelegate` | On-chain (ObscuraVote) |
228
+ | `reputation` | `getSummary` | obscura-api REST |
229
+ | `activity` | `listForWallet`, `getEventFilters`, `isConfigured` | Supabase |
230
+ | `notifications` | `getVapidPublicKey`, `getPrefs`, `savePrefs`, `subscribe`, `unsubscribe` | obscura-api REST |
231
+
102
232
  ## Defaults (Arbitrum Sepolia)
103
233
 
104
234
  | Setting | Value |
105
235
  |---------|-------|
106
236
  | Chain ID | `421614` |
237
+ | RPC | `https://sepolia-rollup.arbitrum.io/rpc` |
238
+ | Supabase URL | `https://quoovjkjwgtdqwdofubh.supabase.co` |
107
239
  | ocUSDC_Pay | `0xEd46020Df8abe7BB1E096f27d089F4326D223a53` |
108
240
  | Vote | `0xe358776AfdbA95d7c9F040e6ef1f5A021aF91730` |
109
241
  | Credit market | `0x1Ec113297c7F9516A6604aa3b18C180559a6f551` |
@@ -115,7 +247,7 @@ Core: `ObscuraSDK`, `ObscuraSDKConfig`, `ContractCall`, `InEuint64`, `FheProvide
115
247
 
116
248
  Types: `ReputationSummary`, `ActivityItem`, `NotificationPrefs`, `ProposalState`, …
117
249
 
118
- Constants: `DEFAULT_ADDRESSES`, `ARBITRUM_SEPOLIA_CHAIN_ID`, `ACTIVITY_EVENT_FILTERS`
250
+ Constants: `DEFAULT_ADDRESSES`, `DEFAULT_SUPABASE_URL`, `ARBITRUM_SEPOLIA_CHAIN_ID`, `ACTIVITY_EVENT_FILTERS`
119
251
 
120
252
  Utilities: `encodeCall`, `normalizeWallet`, `toContractInEuint64`, `HttpError`
121
253
 
package/dist/index.cjs CHANGED
@@ -222,16 +222,30 @@ var ACTIVITY_EVENT_FILTERS = {
222
222
  var DEFAULT_PAGE_SIZE = 20;
223
223
  var ActivityModule = class {
224
224
  client;
225
+ supabaseUrl;
226
+ supabaseAnonKey;
225
227
  constructor(supabaseUrl, supabaseAnonKey) {
228
+ this.supabaseUrl = supabaseUrl;
229
+ this.supabaseAnonKey = supabaseAnonKey;
226
230
  this.client = supabaseUrl && supabaseAnonKey ? supabaseJs.createClient(supabaseUrl, supabaseAnonKey) : null;
227
231
  }
232
+ /** True when both Supabase URL and anon key were supplied at SDK init. */
233
+ isConfigured() {
234
+ return this.client !== null;
235
+ }
228
236
  getEventFilters() {
229
237
  return ACTIVITY_EVENT_FILTERS;
230
238
  }
231
239
  async listForWallet(wallet, options = {}) {
232
240
  if (!this.client) {
233
241
  throw new Error(
234
- "Supabase not configured. Pass supabaseUrl and supabaseAnonKey to ObscuraSDK.create()."
242
+ [
243
+ "Activity module requires Supabase credentials.",
244
+ `Pass supabaseAnonKey (and optionally supabaseUrl) to ObscuraSDK.create().`,
245
+ `Default URL: ${DEFAULT_SUPABASE_URL}`,
246
+ "Get the anon key from your Supabase project \u2192 Settings \u2192 API \u2192 anon public.",
247
+ "Reputation and notifications work without Supabase; activity does not."
248
+ ].join(" ")
235
249
  );
236
250
  }
237
251
  const normalized = normalizeWallet(wallet);
@@ -303,6 +317,53 @@ var OC_USDC_PAY_ABI = [
303
317
  }
304
318
  ];
305
319
  var CREDIT_MARKET_ABI = [
320
+ {
321
+ name: "utilizationBps",
322
+ type: "function",
323
+ stateMutability: "view",
324
+ inputs: [],
325
+ outputs: [{ name: "", type: "uint256" }]
326
+ },
327
+ {
328
+ name: "totalSupplyAssets",
329
+ type: "function",
330
+ stateMutability: "view",
331
+ inputs: [],
332
+ outputs: [{ name: "", type: "uint128" }]
333
+ },
334
+ {
335
+ name: "totalBorrowAssets",
336
+ type: "function",
337
+ stateMutability: "view",
338
+ inputs: [],
339
+ outputs: [{ name: "", type: "uint128" }]
340
+ },
341
+ {
342
+ name: "getPosition",
343
+ type: "function",
344
+ stateMutability: "view",
345
+ inputs: [{ name: "user", type: "address" }],
346
+ outputs: [
347
+ { name: "encSupplyShares", type: "uint256" },
348
+ { name: "borrowShares", type: "uint256" },
349
+ { name: "collateral", type: "uint256" },
350
+ { name: "disburseTo", type: "uint256" }
351
+ ]
352
+ },
353
+ {
354
+ name: "getPlainCollateral",
355
+ type: "function",
356
+ stateMutability: "view",
357
+ inputs: [{ name: "user", type: "address" }],
358
+ outputs: [{ name: "", type: "uint128" }]
359
+ },
360
+ {
361
+ name: "getPlainBorrow",
362
+ type: "function",
363
+ stateMutability: "view",
364
+ inputs: [{ name: "user", type: "address" }],
365
+ outputs: [{ name: "", type: "uint128" }]
366
+ },
306
367
  {
307
368
  name: "supplyCollateral",
308
369
  type: "function",
@@ -333,7 +394,7 @@ var CREDIT_MARKET_ABI = [
333
394
  ];
334
395
  var OBSCURA_VOTE_ABI = [
335
396
  {
336
- name: "proposalCount",
397
+ name: "nextProposalId",
337
398
  type: "function",
338
399
  stateMutability: "view",
339
400
  inputs: [],
@@ -375,6 +436,11 @@ var OBSCURA_VOTE_ABI = [
375
436
  ];
376
437
 
377
438
  // src/modules/credit.ts
439
+ var PLAINTEXT_SHADOW_WARNING = "Plaintext shadows are public testnet UI hints only \u2014 not encrypted balances. Do not treat as private position size. Omit on mainnet strict privacy mode.";
440
+ function bigintToHandleHex(value) {
441
+ const hex = value.toString(16).padStart(64, "0");
442
+ return `0x${hex}`;
443
+ }
378
444
  var CreditModule = class {
379
445
  constructor(deps) {
380
446
  this.deps = deps;
@@ -383,6 +449,75 @@ var CreditModule = class {
383
449
  getMarketAddress(override) {
384
450
  return override ?? this.deps.addresses.CreditCanonicalPayOcUSDCMarket;
385
451
  }
452
+ /** Public pool aggregates — no wallet required */
453
+ async getMarketUtilization(marketAddress) {
454
+ const market = this.getMarketAddress(marketAddress);
455
+ const [utilizationBps, totalSupplyAssets, totalBorrowAssets] = await Promise.all([
456
+ this.deps.publicClient.readContract({
457
+ address: market,
458
+ abi: CREDIT_MARKET_ABI,
459
+ functionName: "utilizationBps"
460
+ }),
461
+ this.deps.publicClient.readContract({
462
+ address: market,
463
+ abi: CREDIT_MARKET_ABI,
464
+ functionName: "totalSupplyAssets"
465
+ }),
466
+ this.deps.publicClient.readContract({
467
+ address: market,
468
+ abi: CREDIT_MARKET_ABI,
469
+ functionName: "totalBorrowAssets"
470
+ })
471
+ ]);
472
+ return {
473
+ marketAddress: market,
474
+ utilizationBps,
475
+ totalSupplyAssets,
476
+ totalBorrowAssets
477
+ };
478
+ }
479
+ /**
480
+ * Returns encrypted position handles as opaque hex strings.
481
+ * Plaintext shadows are optional and labeled with an explicit warning.
482
+ */
483
+ async getPositionHandles(wallet, options = {}) {
484
+ const normalized = normalizeWallet(wallet);
485
+ if (!normalized) throw new Error("Invalid wallet address");
486
+ const market = this.getMarketAddress(options.marketAddress);
487
+ const position = await this.deps.publicClient.readContract({
488
+ address: market,
489
+ abi: CREDIT_MARKET_ABI,
490
+ functionName: "getPosition",
491
+ args: [normalized]
492
+ });
493
+ const result = {
494
+ marketAddress: market,
495
+ wallet: normalized,
496
+ encryptedSupplySharesHandle: bigintToHandleHex(position[0]),
497
+ encryptedBorrowSharesHandle: bigintToHandleHex(position[1]),
498
+ encryptedCollateralHandle: bigintToHandleHex(position[2])
499
+ };
500
+ if (options.includePlaintextShadows) {
501
+ const [plainCollateral, plainBorrow] = await Promise.all([
502
+ this.deps.publicClient.readContract({
503
+ address: market,
504
+ abi: CREDIT_MARKET_ABI,
505
+ functionName: "getPlainCollateral",
506
+ args: [normalized]
507
+ }),
508
+ this.deps.publicClient.readContract({
509
+ address: market,
510
+ abi: CREDIT_MARKET_ABI,
511
+ functionName: "getPlainBorrow",
512
+ args: [normalized]
513
+ })
514
+ ]);
515
+ result.plaintextShadowWarning = PLAINTEXT_SHADOW_WARNING;
516
+ result.plainCollateral = plainCollateral;
517
+ result.plainBorrow = plainBorrow;
518
+ }
519
+ return result;
520
+ }
386
521
  async buildSupplyCollateral(amount, encryptedAmount, marketAddress) {
387
522
  const market = this.getMarketAddress(marketAddress);
388
523
  const enc = await resolveInEuint64(amount, market, this.deps.fhe, encryptedAmount);
@@ -536,11 +671,12 @@ var VoteModule = class {
536
671
  get voteAddress() {
537
672
  return this.deps.addresses.ObscuraVote;
538
673
  }
674
+ /** Returns total proposals created (= public `nextProposalId` on ObscuraVote). */
539
675
  async getProposalCount() {
540
676
  return this.deps.publicClient.readContract({
541
677
  address: this.deps.addresses.ObscuraVote,
542
678
  abi: OBSCURA_VOTE_ABI,
543
- functionName: "proposalCount"
679
+ functionName: "nextProposalId"
544
680
  });
545
681
  }
546
682
  async getProposal(id) {
@@ -620,6 +756,7 @@ var ObscuraSDK = class _ObscuraSDK {
620
756
  this.credit = new CreditModule({
621
757
  chainId: this.chainId,
622
758
  addresses: this.addresses,
759
+ publicClient: this.publicClient,
623
760
  fhe: this.fhe
624
761
  });
625
762
  this.vote = new VoteModule(moduleDeps);