pepay-streams-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 (62) hide show
  1. package/README.md +405 -0
  2. package/dist/api/index.d.mts +321 -0
  3. package/dist/api/index.d.ts +321 -0
  4. package/dist/api/index.js +312 -0
  5. package/dist/api/index.js.map +1 -0
  6. package/dist/api/index.mjs +306 -0
  7. package/dist/api/index.mjs.map +1 -0
  8. package/dist/automation/index.d.mts +140 -0
  9. package/dist/automation/index.d.ts +140 -0
  10. package/dist/automation/index.js +331 -0
  11. package/dist/automation/index.js.map +1 -0
  12. package/dist/automation/index.mjs +326 -0
  13. package/dist/automation/index.mjs.map +1 -0
  14. package/dist/campaigns/index.d.mts +286 -0
  15. package/dist/campaigns/index.d.ts +286 -0
  16. package/dist/campaigns/index.js +652 -0
  17. package/dist/campaigns/index.js.map +1 -0
  18. package/dist/campaigns/index.mjs +645 -0
  19. package/dist/campaigns/index.mjs.map +1 -0
  20. package/dist/claims/index.d.mts +190 -0
  21. package/dist/claims/index.d.ts +190 -0
  22. package/dist/claims/index.js +414 -0
  23. package/dist/claims/index.js.map +1 -0
  24. package/dist/claims/index.mjs +409 -0
  25. package/dist/claims/index.mjs.map +1 -0
  26. package/dist/index-BTG0TRJt.d.mts +555 -0
  27. package/dist/index-BTG0TRJt.d.ts +555 -0
  28. package/dist/index.d.mts +170 -0
  29. package/dist/index.d.ts +170 -0
  30. package/dist/index.js +2926 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/index.mjs +2888 -0
  33. package/dist/index.mjs.map +1 -0
  34. package/dist/marketplace/index.d.mts +225 -0
  35. package/dist/marketplace/index.d.ts +225 -0
  36. package/dist/marketplace/index.js +529 -0
  37. package/dist/marketplace/index.js.map +1 -0
  38. package/dist/marketplace/index.mjs +524 -0
  39. package/dist/marketplace/index.mjs.map +1 -0
  40. package/dist/react/index.d.mts +185 -0
  41. package/dist/react/index.d.ts +185 -0
  42. package/dist/react/index.js +340 -0
  43. package/dist/react/index.js.map +1 -0
  44. package/dist/react/index.mjs +333 -0
  45. package/dist/react/index.mjs.map +1 -0
  46. package/dist/staking/index.d.mts +158 -0
  47. package/dist/staking/index.d.ts +158 -0
  48. package/dist/staking/index.js +359 -0
  49. package/dist/staking/index.js.map +1 -0
  50. package/dist/staking/index.mjs +354 -0
  51. package/dist/staking/index.mjs.map +1 -0
  52. package/package.json +106 -0
  53. package/src/api/index.ts +577 -0
  54. package/src/automation/index.ts +436 -0
  55. package/src/campaigns/index.ts +835 -0
  56. package/src/claims/index.ts +530 -0
  57. package/src/client.ts +518 -0
  58. package/src/index.ts +101 -0
  59. package/src/marketplace/index.ts +730 -0
  60. package/src/react/index.ts +498 -0
  61. package/src/staking/index.ts +449 -0
  62. package/src/types/index.ts +631 -0
@@ -0,0 +1,835 @@
1
+ /**
2
+ * Campaign Creation Module
3
+ *
4
+ * Provides methods for creating all campaign types:
5
+ * - Instant Airdrops (Kind.INSTANT = 0)
6
+ * - Vested Airdrops (Kind.VESTED = 1)
7
+ * - Token Locks (Kind.LOCK = 2)
8
+ * - Vesting Streams (Kind.VESTING = 3)
9
+ */
10
+ import {
11
+ type PublicClient,
12
+ type WalletClient,
13
+ type Address,
14
+ type Hash,
15
+ type Hex,
16
+ zeroAddress,
17
+ } from 'viem';
18
+ import { DIAMOND_ABI } from '@pepay-streams/abi/diamond';
19
+ import type {
20
+ InstantAirdropParams,
21
+ VestedAirdropParams,
22
+ LockParams,
23
+ VestingParams,
24
+ CampaignInfo,
25
+ TransactionResult,
26
+ } from '../types';
27
+
28
+ /**
29
+ * Campaign Kind enum matching the contract
30
+ */
31
+ export enum CampaignKind {
32
+ INSTANT = 0,
33
+ VESTED = 1,
34
+ LOCK = 2,
35
+ VESTING = 3,
36
+ }
37
+
38
+ /**
39
+ * Change policy enum matching the contract
40
+ */
41
+ export enum ChangePolicy {
42
+ ONLY_RECIPIENT = 0,
43
+ ONLY_CREATOR = 1,
44
+ BOTH = 2,
45
+ NO_ONE = 3,
46
+ }
47
+
48
+ /**
49
+ * CreateArgs struct matching the contract
50
+ */
51
+ interface CreateArgs {
52
+ kind: number;
53
+ token: Address;
54
+ start: bigint;
55
+ end: bigint;
56
+ step: bigint;
57
+ cliffReleaseTime: bigint;
58
+ cliffBps: number;
59
+ autoClaimIncentiveBps: number;
60
+ cancelable: boolean;
61
+ claimOnce: boolean;
62
+ changePolicy: number;
63
+ claimGrace: bigint;
64
+ recipients: Address[];
65
+ amounts: bigint[];
66
+ feeBuffer: bigint;
67
+ attachDefaultRegistry: boolean;
68
+ unwrapWETH: boolean;
69
+ weth: Address;
70
+ }
71
+
72
+ /**
73
+ * Campaigns module for creating and managing token distribution campaigns
74
+ */
75
+ export class CampaignsModule {
76
+ constructor(
77
+ private readonly publicClient: PublicClient,
78
+ private readonly walletClient: WalletClient | undefined,
79
+ private readonly diamondAddress: Address
80
+ ) {}
81
+
82
+ // ============================================================================
83
+ // Campaign Creation
84
+ // ============================================================================
85
+
86
+ /**
87
+ * Create an Instant Airdrop campaign
88
+ *
89
+ * Tokens are immediately claimable by recipients after funding.
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * const result = await sdk.campaigns.createInstantAirdrop({
94
+ * token: '0x...',
95
+ * recipients: [
96
+ * { address: '0x...', amount: parseEther('100') },
97
+ * { address: '0x...', amount: parseEther('50') },
98
+ * ],
99
+ * });
100
+ * console.log('Campaign created, tx:', result.hash);
101
+ * ```
102
+ */
103
+ async createInstantAirdrop(
104
+ params: InstantAirdropParams
105
+ ): Promise<TransactionResult> {
106
+ const wallet = this.requireWallet();
107
+
108
+ const recipients = params.recipients.map((r) => r.address);
109
+ const amounts = params.recipients.map((r) => r.amount);
110
+
111
+ const now = BigInt(Math.floor(Date.now() / 1000));
112
+ const args: CreateArgs = {
113
+ kind: CampaignKind.INSTANT,
114
+ token: params.token,
115
+ start: now,
116
+ end: now, // Instant airdrop - same start/end
117
+ step: 0n,
118
+ cliffReleaseTime: 0n,
119
+ cliffBps: 0,
120
+ autoClaimIncentiveBps: params.autoClaimIncentiveBps ?? 0,
121
+ cancelable: params.cancelable ?? true,
122
+ claimOnce: params.claimOnce ?? true,
123
+ changePolicy: this.changePolicyToEnum(params.changePolicy),
124
+ claimGrace: BigInt(params.claimGrace ?? 0),
125
+ recipients,
126
+ amounts,
127
+ feeBuffer: params.feeBuffer ?? 0n,
128
+ attachDefaultRegistry: params.attachDefaultRegistry ?? false,
129
+ unwrapWETH: params.unwrapWETH ?? false,
130
+ weth: params.weth ?? zeroAddress,
131
+ };
132
+
133
+ const hash = await wallet.writeContract({
134
+ chain: wallet.chain,
135
+ account: wallet.account!,
136
+ address: this.diamondAddress,
137
+ abi: DIAMOND_ABI,
138
+ functionName: 'createCampaign',
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ args: [args] as any,
141
+ });
142
+
143
+ return this.createTransactionResult(hash);
144
+ }
145
+
146
+ /**
147
+ * Create a Vested Airdrop campaign
148
+ *
149
+ * Tokens vest linearly over time with optional cliff period.
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * const result = await sdk.campaigns.createVestedAirdrop({
154
+ * token: '0x...',
155
+ * recipients: [
156
+ * { address: '0x...', amount: parseEther('1000') },
157
+ * ],
158
+ * startTime: Math.floor(Date.now() / 1000), // Start now
159
+ * duration: 365 * 24 * 60 * 60, // 1 year
160
+ * cliffDuration: 90 * 24 * 60 * 60, // 3 month cliff
161
+ * steps: 12, // Monthly releases
162
+ * });
163
+ * ```
164
+ */
165
+ async createVestedAirdrop(
166
+ params: VestedAirdropParams
167
+ ): Promise<TransactionResult> {
168
+ const wallet = this.requireWallet();
169
+
170
+ const recipients = params.recipients.map((r) => r.address);
171
+ const amounts = params.recipients.map((r) => r.amount);
172
+
173
+ const startTime = BigInt(params.startTime);
174
+ const endTime = startTime + BigInt(params.duration);
175
+ const steps = params.steps ?? 1;
176
+ const step = steps > 1 ? BigInt(params.duration) / BigInt(steps) : 0n;
177
+ const cliffReleaseTime = params.cliffDuration
178
+ ? startTime + BigInt(params.cliffDuration)
179
+ : 0n;
180
+
181
+ const args: CreateArgs = {
182
+ kind: CampaignKind.VESTED,
183
+ token: params.token,
184
+ start: startTime,
185
+ end: endTime,
186
+ step,
187
+ cliffReleaseTime,
188
+ cliffBps: params.cliffBps ?? 0,
189
+ autoClaimIncentiveBps: params.autoClaimIncentiveBps ?? 0,
190
+ cancelable: params.cancelable ?? true,
191
+ claimOnce: false, // Vested can claim multiple times
192
+ changePolicy: this.changePolicyToEnum(params.changePolicy),
193
+ claimGrace: BigInt(params.claimGrace ?? 0),
194
+ recipients,
195
+ amounts,
196
+ feeBuffer: params.feeBuffer ?? 0n,
197
+ attachDefaultRegistry: params.attachDefaultRegistry ?? false,
198
+ unwrapWETH: params.unwrapWETH ?? false,
199
+ weth: params.weth ?? zeroAddress,
200
+ };
201
+
202
+ const hash = await wallet.writeContract({
203
+ chain: wallet.chain,
204
+ account: wallet.account!,
205
+ address: this.diamondAddress,
206
+ abi: DIAMOND_ABI,
207
+ functionName: 'createCampaign',
208
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
209
+ args: [args] as any,
210
+ });
211
+
212
+ return this.createTransactionResult(hash);
213
+ }
214
+
215
+ /**
216
+ * Create a Token Lock
217
+ *
218
+ * Tokens are locked until a specific unlock time, then fully releasable.
219
+ *
220
+ * @example
221
+ * ```typescript
222
+ * const result = await sdk.campaigns.createLock({
223
+ * token: '0x...',
224
+ * recipient: '0x...',
225
+ * amount: parseEther('1000000'),
226
+ * unlockTime: Math.floor(Date.now() / 1000) + 180 * 24 * 60 * 60, // 6 months
227
+ * });
228
+ * ```
229
+ */
230
+ async createLock(params: LockParams): Promise<TransactionResult> {
231
+ const wallet = this.requireWallet();
232
+
233
+ const now = BigInt(Math.floor(Date.now() / 1000));
234
+ const unlockTime = BigInt(params.unlockTime);
235
+
236
+ const args: CreateArgs = {
237
+ kind: CampaignKind.LOCK,
238
+ token: params.token,
239
+ start: now,
240
+ end: unlockTime,
241
+ step: 0n,
242
+ cliffReleaseTime: unlockTime, // Lock releases at this time
243
+ cliffBps: 10000, // 100% released at cliff
244
+ autoClaimIncentiveBps: 0,
245
+ cancelable: params.cancelable ?? false,
246
+ claimOnce: true,
247
+ changePolicy: this.changePolicyToEnum(params.changePolicy),
248
+ claimGrace: 0n,
249
+ recipients: [params.recipient],
250
+ amounts: [params.amount],
251
+ feeBuffer: params.feeBuffer ?? 0n,
252
+ attachDefaultRegistry: false,
253
+ unwrapWETH: params.unwrapWETH ?? false,
254
+ weth: params.weth ?? zeroAddress,
255
+ };
256
+
257
+ const hash = await wallet.writeContract({
258
+ chain: wallet.chain,
259
+ account: wallet.account!,
260
+ address: this.diamondAddress,
261
+ abi: DIAMOND_ABI,
262
+ functionName: 'createCampaign',
263
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
264
+ args: [args] as any,
265
+ });
266
+
267
+ return this.createTransactionResult(hash);
268
+ }
269
+
270
+ /**
271
+ * Create a Vesting Stream (Payment)
272
+ *
273
+ * Single-recipient vesting with optional automation.
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * const result = await sdk.campaigns.createVesting({
278
+ * token: '0x...',
279
+ * recipient: '0x...',
280
+ * amount: parseEther('120000'), // 120k tokens
281
+ * startTime: Math.floor(Date.now() / 1000),
282
+ * duration: 365 * 24 * 60 * 60, // 1 year
283
+ * cliffDuration: 0, // No cliff
284
+ * steps: 12, // Monthly
285
+ * });
286
+ * ```
287
+ */
288
+ async createVesting(params: VestingParams): Promise<TransactionResult> {
289
+ const wallet = this.requireWallet();
290
+
291
+ const startTime = BigInt(params.startTime);
292
+ const endTime = startTime + BigInt(params.duration);
293
+ const steps = params.steps ?? 1;
294
+ const step = steps > 1 ? BigInt(params.duration) / BigInt(steps) : 0n;
295
+ const cliffReleaseTime = params.cliffDuration
296
+ ? startTime + BigInt(params.cliffDuration)
297
+ : 0n;
298
+
299
+ const args: CreateArgs = {
300
+ kind: CampaignKind.VESTING,
301
+ token: params.token,
302
+ start: startTime,
303
+ end: endTime,
304
+ step,
305
+ cliffReleaseTime,
306
+ cliffBps: params.cliffBps ?? 0,
307
+ autoClaimIncentiveBps: params.autoClaimIncentiveBps ?? 0,
308
+ cancelable: params.cancelable ?? true,
309
+ claimOnce: false,
310
+ changePolicy: this.changePolicyToEnum(params.changePolicy),
311
+ claimGrace: BigInt(params.claimGrace ?? 0),
312
+ recipients: [params.recipient],
313
+ amounts: [params.amount],
314
+ feeBuffer: params.feeBuffer ?? 0n,
315
+ attachDefaultRegistry: false,
316
+ unwrapWETH: params.unwrapWETH ?? false,
317
+ weth: params.weth ?? zeroAddress,
318
+ };
319
+
320
+ const hash = await wallet.writeContract({
321
+ chain: wallet.chain,
322
+ account: wallet.account!,
323
+ address: this.diamondAddress,
324
+ abi: DIAMOND_ABI,
325
+ functionName: 'createCampaign',
326
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
327
+ args: [args] as any,
328
+ });
329
+
330
+ return this.createTransactionResult(hash);
331
+ }
332
+
333
+ /**
334
+ * Create a campaign with raw CreateArgs
335
+ *
336
+ * Low-level function for full control over campaign parameters.
337
+ */
338
+ async createCampaign(args: CreateArgs): Promise<TransactionResult> {
339
+ const wallet = this.requireWallet();
340
+
341
+ const hash = await wallet.writeContract({
342
+ chain: wallet.chain,
343
+ account: wallet.account!,
344
+ address: this.diamondAddress,
345
+ abi: DIAMOND_ABI,
346
+ functionName: 'createCampaign',
347
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
348
+ args: [args] as any,
349
+ });
350
+
351
+ return this.createTransactionResult(hash);
352
+ }
353
+
354
+ // ============================================================================
355
+ // Campaign Funding
356
+ // ============================================================================
357
+
358
+ /**
359
+ * Fund a campaign using EIP-2612 Permit (gasless approval)
360
+ *
361
+ * @example
362
+ * ```typescript
363
+ * const result = await sdk.campaigns.fundWithPermit({
364
+ * campaignId,
365
+ * amount: parseEther('1000'),
366
+ * deadline: Math.floor(Date.now() / 1000) + 3600,
367
+ * v: 28,
368
+ * r: '0x...',
369
+ * s: '0x...',
370
+ * });
371
+ * ```
372
+ */
373
+ async fundWithPermit(params: {
374
+ campaignId: bigint;
375
+ amount: bigint;
376
+ deadline: bigint;
377
+ v: number;
378
+ r: Hex;
379
+ s: Hex;
380
+ }): Promise<TransactionResult> {
381
+ const wallet = this.requireWallet();
382
+
383
+ const hash = await wallet.writeContract({
384
+ chain: wallet.chain,
385
+ account: wallet.account!,
386
+ address: this.diamondAddress,
387
+ abi: DIAMOND_ABI,
388
+ functionName: 'fundWithPermit',
389
+ args: [
390
+ params.campaignId,
391
+ params.amount,
392
+ params.deadline,
393
+ params.v,
394
+ params.r,
395
+ params.s,
396
+ ],
397
+ });
398
+
399
+ return this.createTransactionResult(hash);
400
+ }
401
+
402
+ /**
403
+ * Fund a campaign using Permit2 (gasless approval)
404
+ *
405
+ * @example
406
+ * ```typescript
407
+ * const result = await sdk.campaigns.fundWithPermit2({
408
+ * campaignId,
409
+ * from: userAddress,
410
+ * amount: parseEther('1000'),
411
+ * permit2Data: encodedPermit2Data,
412
+ * });
413
+ * ```
414
+ */
415
+ async fundWithPermit2(params: {
416
+ campaignId: bigint;
417
+ from: Address;
418
+ amount: bigint;
419
+ permit2Data: Hex;
420
+ }): Promise<TransactionResult> {
421
+ const wallet = this.requireWallet();
422
+
423
+ const hash = await wallet.writeContract({
424
+ chain: wallet.chain,
425
+ account: wallet.account!,
426
+ address: this.diamondAddress,
427
+ abi: DIAMOND_ABI,
428
+ functionName: 'fundWithPermit2',
429
+ args: [
430
+ params.campaignId,
431
+ params.from,
432
+ params.amount,
433
+ params.permit2Data,
434
+ ],
435
+ });
436
+
437
+ return this.createTransactionResult(hash);
438
+ }
439
+
440
+ /**
441
+ * Return leftover tokens to creator
442
+ *
443
+ * @example
444
+ * ```typescript
445
+ * const result = await sdk.campaigns.returnLeftover(campaignId, parseEther('100'));
446
+ * ```
447
+ */
448
+ async returnLeftover(
449
+ campaignId: bigint,
450
+ amountToReturn: bigint
451
+ ): Promise<TransactionResult> {
452
+ const wallet = this.requireWallet();
453
+
454
+ const hash = await wallet.writeContract({
455
+ chain: wallet.chain,
456
+ account: wallet.account!,
457
+ address: this.diamondAddress,
458
+ abi: DIAMOND_ABI,
459
+ functionName: 'returnLeftover',
460
+ args: [campaignId, amountToReturn],
461
+ });
462
+
463
+ return this.createTransactionResult(hash);
464
+ }
465
+
466
+ /**
467
+ * Add native tips to a campaign for automation
468
+ */
469
+ async addNativeTips(
470
+ campaignId: bigint,
471
+ tipAmount: bigint
472
+ ): Promise<TransactionResult> {
473
+ const wallet = this.requireWallet();
474
+
475
+ const hash = await wallet.writeContract({
476
+ chain: wallet.chain,
477
+ account: wallet.account!,
478
+ address: this.diamondAddress,
479
+ abi: DIAMOND_ABI,
480
+ functionName: 'addNativeTips',
481
+ args: [campaignId, tipAmount],
482
+ value: tipAmount,
483
+ });
484
+
485
+ return this.createTransactionResult(hash);
486
+ }
487
+
488
+ // ============================================================================
489
+ // Campaign Queries
490
+ // ============================================================================
491
+
492
+ /**
493
+ * Get campaign summary by ID
494
+ *
495
+ * Returns full campaign configuration and state.
496
+ *
497
+ * @example
498
+ * ```typescript
499
+ * const campaign = await sdk.campaigns.getCampaign(1n);
500
+ * console.log('Campaign token:', campaign.token);
501
+ * console.log('Total claimed:', campaign.totalClaimed);
502
+ * ```
503
+ */
504
+ async getCampaign(campaignId: bigint): Promise<CampaignInfo> {
505
+ const result = await this.publicClient.readContract({
506
+ address: this.diamondAddress,
507
+ abi: DIAMOND_ABI,
508
+ functionName: 'campaignSummary',
509
+ args: [campaignId],
510
+ });
511
+
512
+ return this.parseCampaignSummary(campaignId, result as Record<string, unknown>);
513
+ }
514
+
515
+ /**
516
+ * Get campaign configuration only
517
+ */
518
+ async getCampaignConfig(campaignId: bigint): Promise<{
519
+ kind: number;
520
+ cancelable: boolean;
521
+ claimOnce: boolean;
522
+ canceled: boolean;
523
+ changePolicy: number;
524
+ token: Address;
525
+ creator: Address;
526
+ cliffBps: number;
527
+ autoClaimIncentiveBps: number;
528
+ start: number;
529
+ end: number;
530
+ step: number;
531
+ canceledAt: number;
532
+ cliffReleaseTime: number;
533
+ claimGrace: number;
534
+ unwrapWETH: boolean;
535
+ weth: Address;
536
+ }> {
537
+ const result = await this.publicClient.readContract({
538
+ address: this.diamondAddress,
539
+ abi: DIAMOND_ABI,
540
+ functionName: 'campaignConfig',
541
+ args: [campaignId],
542
+ }) as Record<string, unknown>;
543
+
544
+ return {
545
+ kind: Number(result.kind),
546
+ cancelable: Boolean(result.cancelable),
547
+ claimOnce: Boolean(result.claimOnce),
548
+ canceled: Boolean(result.canceled),
549
+ changePolicy: Number(result.changePolicy),
550
+ token: result.token as Address,
551
+ creator: result.creator as Address,
552
+ cliffBps: Number(result.cliffBps),
553
+ autoClaimIncentiveBps: Number(result.autoClaimIncentiveBps),
554
+ start: Number(result.start),
555
+ end: Number(result.end),
556
+ step: Number(result.step),
557
+ canceledAt: Number(result.canceledAt),
558
+ cliffReleaseTime: Number(result.cliffReleaseTime),
559
+ claimGrace: Number(result.claimGrace),
560
+ unwrapWETH: Boolean(result.unwrapWETH),
561
+ weth: result.weth as Address,
562
+ };
563
+ }
564
+
565
+ /**
566
+ * Get campaign state (totals and accounting)
567
+ */
568
+ async getCampaignState(campaignId: bigint): Promise<{
569
+ totalAllocated: bigint;
570
+ totalClaimed: bigint;
571
+ nativeTips: bigint;
572
+ returned: bigint;
573
+ feeBuffer: bigint;
574
+ feeLoss: bigint;
575
+ totalFunded: bigint;
576
+ statusBits: number;
577
+ uniqueRecipients: bigint;
578
+ }> {
579
+ const result = await this.publicClient.readContract({
580
+ address: this.diamondAddress,
581
+ abi: DIAMOND_ABI,
582
+ functionName: 'campaignState',
583
+ args: [campaignId],
584
+ }) as Record<string, unknown>;
585
+
586
+ return {
587
+ totalAllocated: result.totalAllocated as bigint,
588
+ totalClaimed: result.totalClaimed as bigint,
589
+ nativeTips: result.nativeTips as bigint,
590
+ returned: result.returned as bigint,
591
+ feeBuffer: result.feeBuffer as bigint,
592
+ feeLoss: result.feeLoss as bigint,
593
+ totalFunded: result.totalFunded as bigint,
594
+ statusBits: Number(result.statusBits),
595
+ uniqueRecipients: result.uniqueRecipients as bigint,
596
+ };
597
+ }
598
+
599
+ /**
600
+ * Get campaign status bits
601
+ */
602
+ async getCampaignStatus(campaignId: bigint): Promise<number> {
603
+ const result = await this.publicClient.readContract({
604
+ address: this.diamondAddress,
605
+ abi: DIAMOND_ABI,
606
+ functionName: 'campaignStatus',
607
+ args: [campaignId],
608
+ });
609
+
610
+ return Number(result);
611
+ }
612
+
613
+ // ============================================================================
614
+ // Campaign Administration
615
+ // ============================================================================
616
+
617
+ /**
618
+ * Pause a campaign (creator or admin only)
619
+ *
620
+ * @param campaignId - Campaign ID
621
+ * @param reason - Pause reason as status bits (e.g., 1 for creator pause)
622
+ */
623
+ async pause(campaignId: bigint, reason: number = 1): Promise<TransactionResult> {
624
+ const wallet = this.requireWallet();
625
+
626
+ const hash = await wallet.writeContract({
627
+ chain: wallet.chain,
628
+ account: wallet.account!,
629
+ address: this.diamondAddress,
630
+ abi: DIAMOND_ABI,
631
+ functionName: 'pauseCampaign',
632
+ args: [campaignId, reason],
633
+ });
634
+
635
+ return this.createTransactionResult(hash);
636
+ }
637
+
638
+ /**
639
+ * Resume a paused campaign
640
+ *
641
+ * @param campaignId - Campaign ID
642
+ * @param clearBits - Status bits to clear (e.g., 1 for creator pause)
643
+ */
644
+ async resume(campaignId: bigint, clearBits: number = 1): Promise<TransactionResult> {
645
+ const wallet = this.requireWallet();
646
+
647
+ const hash = await wallet.writeContract({
648
+ chain: wallet.chain,
649
+ account: wallet.account!,
650
+ address: this.diamondAddress,
651
+ abi: DIAMOND_ABI,
652
+ functionName: 'resumeCampaign',
653
+ args: [campaignId, clearBits],
654
+ });
655
+
656
+ return this.createTransactionResult(hash);
657
+ }
658
+
659
+ /**
660
+ * Finalize a campaign (no more claims possible)
661
+ */
662
+ async finalize(campaignId: bigint): Promise<TransactionResult> {
663
+ const wallet = this.requireWallet();
664
+
665
+ const hash = await wallet.writeContract({
666
+ chain: wallet.chain,
667
+ account: wallet.account!,
668
+ address: this.diamondAddress,
669
+ abi: DIAMOND_ABI,
670
+ functionName: 'finalizeCampaign',
671
+ args: [campaignId],
672
+ });
673
+
674
+ return this.createTransactionResult(hash);
675
+ }
676
+
677
+ /**
678
+ * Cancel a campaign and return funds to creator
679
+ */
680
+ async cancel(campaignId: bigint): Promise<TransactionResult> {
681
+ const wallet = this.requireWallet();
682
+
683
+ const hash = await wallet.writeContract({
684
+ chain: wallet.chain,
685
+ account: wallet.account!,
686
+ address: this.diamondAddress,
687
+ abi: DIAMOND_ABI,
688
+ functionName: 'cancel',
689
+ args: [campaignId],
690
+ });
691
+
692
+ return this.createTransactionResult(hash);
693
+ }
694
+
695
+ /**
696
+ * Change a recipient address (for blocked recipients)
697
+ */
698
+ async changeRecipient(
699
+ campaignId: bigint,
700
+ fromAddress: Address,
701
+ toAddress: Address
702
+ ): Promise<TransactionResult> {
703
+ const wallet = this.requireWallet();
704
+
705
+ const hash = await wallet.writeContract({
706
+ chain: wallet.chain,
707
+ account: wallet.account!,
708
+ address: this.diamondAddress,
709
+ abi: DIAMOND_ABI,
710
+ functionName: 'changeRecipient',
711
+ args: [campaignId, fromAddress, toAddress],
712
+ });
713
+
714
+ return this.createTransactionResult(hash);
715
+ }
716
+
717
+ /**
718
+ * Add recipients to an existing campaign
719
+ */
720
+ async addRecipients(
721
+ campaignId: bigint,
722
+ recipients: Address[],
723
+ amounts: bigint[],
724
+ additionalBuffer: bigint = 0n
725
+ ): Promise<TransactionResult> {
726
+ const wallet = this.requireWallet();
727
+
728
+ const hash = await wallet.writeContract({
729
+ chain: wallet.chain,
730
+ account: wallet.account!,
731
+ address: this.diamondAddress,
732
+ abi: DIAMOND_ABI,
733
+ functionName: 'addRecipients',
734
+ args: [campaignId, recipients, amounts, additionalBuffer],
735
+ });
736
+
737
+ return this.createTransactionResult(hash);
738
+ }
739
+
740
+ // ============================================================================
741
+ // Helpers
742
+ // ============================================================================
743
+
744
+ private requireWallet(): WalletClient {
745
+ if (!this.walletClient) {
746
+ throw new Error(
747
+ 'Wallet client required for write operations. Initialize SDK with a signer.'
748
+ );
749
+ }
750
+ return this.walletClient;
751
+ }
752
+
753
+ private changePolicyToEnum(
754
+ policy?: 'only_recipient' | 'only_creator' | 'both' | 'no_one'
755
+ ): number {
756
+ switch (policy) {
757
+ case 'only_recipient':
758
+ return ChangePolicy.ONLY_RECIPIENT;
759
+ case 'only_creator':
760
+ return ChangePolicy.ONLY_CREATOR;
761
+ case 'both':
762
+ return ChangePolicy.BOTH;
763
+ case 'no_one':
764
+ return ChangePolicy.NO_ONE;
765
+ default:
766
+ return ChangePolicy.ONLY_RECIPIENT;
767
+ }
768
+ }
769
+
770
+ private parseCampaignSummary(id: bigint, data: Record<string, unknown>): CampaignInfo {
771
+ const cfg = data.cfg as Record<string, unknown>;
772
+ const state = data.state as Record<string, unknown>;
773
+
774
+ const kindMap: Record<number, CampaignInfo['kind']> = {
775
+ 0: 'instant_airdrop',
776
+ 1: 'vested_airdrop',
777
+ 2: 'lock',
778
+ 3: 'vesting',
779
+ };
780
+
781
+ const kind = Number(cfg.kind);
782
+
783
+ const result: CampaignInfo = {
784
+ id,
785
+ kind: kindMap[kind] ?? 'instant_airdrop',
786
+ token: cfg.token as Address,
787
+ creator: cfg.creator as Address,
788
+ name: '', // Not stored on-chain
789
+ totalAllocated: state.totalAllocated as bigint,
790
+ totalFunded: state.totalFunded as bigint,
791
+ totalClaimed: state.totalClaimed as bigint,
792
+ totalReturned: state.returned as bigint,
793
+ feeLoss: state.feeLoss as bigint,
794
+ feeBuffer: state.feeBuffer as bigint,
795
+ startTime: Number(cfg.start),
796
+ endTime: Number(cfg.end),
797
+ cliffTime: Number(cfg.cliffReleaseTime),
798
+ steps: Number(cfg.step) > 0 ? Math.ceil((Number(cfg.end) - Number(cfg.start)) / Number(cfg.step)) : 1,
799
+ recipientCount: Number(state.uniqueRecipients),
800
+ statusBits: Number(state.statusBits),
801
+ changePolicy: Number(cfg.changePolicy),
802
+ hasAutomation: (Number(state.statusBits) & (1 << 5)) !== 0,
803
+ };
804
+
805
+ // Add optional fields
806
+ if (data.hasRegistry) {
807
+ result.hasRegistry = true;
808
+ }
809
+ if (data.merkleRoot) {
810
+ result.merkleRoot = data.merkleRoot as Hex;
811
+ }
812
+
813
+ return result;
814
+ }
815
+
816
+ private createTransactionResult(hash: Hash): TransactionResult {
817
+ return {
818
+ hash,
819
+ wait: async () => {
820
+ const receipt = await this.publicClient.waitForTransactionReceipt({
821
+ hash,
822
+ });
823
+ return {
824
+ blockNumber: receipt.blockNumber,
825
+ transactionHash: receipt.transactionHash,
826
+ gasUsed: receipt.gasUsed,
827
+ status: receipt.status,
828
+ logs: receipt.logs,
829
+ };
830
+ },
831
+ };
832
+ }
833
+ }
834
+
835
+ export default CampaignsModule;