@zemyth/raise-sdk 0.1.1 → 0.1.3

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 (47) hide show
  1. package/README.md +11 -9
  2. package/dist/accounts/index.cjs +531 -3
  3. package/dist/accounts/index.cjs.map +1 -1
  4. package/dist/accounts/index.d.cts +307 -2
  5. package/dist/accounts/index.d.ts +307 -2
  6. package/dist/accounts/index.js +503 -4
  7. package/dist/accounts/index.js.map +1 -1
  8. package/dist/constants/index.cjs +41 -3
  9. package/dist/constants/index.cjs.map +1 -1
  10. package/dist/constants/index.d.cts +38 -3
  11. package/dist/constants/index.d.ts +38 -3
  12. package/dist/constants/index.js +40 -4
  13. package/dist/constants/index.js.map +1 -1
  14. package/dist/index.cjs +2297 -361
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +566 -7
  17. package/dist/index.d.ts +566 -7
  18. package/dist/index.js +2279 -379
  19. package/dist/index.js.map +1 -1
  20. package/dist/instructions/index.cjs +783 -40
  21. package/dist/instructions/index.cjs.map +1 -1
  22. package/dist/instructions/index.d.cts +492 -6
  23. package/dist/instructions/index.d.ts +492 -6
  24. package/dist/instructions/index.js +762 -42
  25. package/dist/instructions/index.js.map +1 -1
  26. package/dist/pdas/index.cjs +163 -1
  27. package/dist/pdas/index.cjs.map +1 -1
  28. package/dist/pdas/index.d.cts +131 -1
  29. package/dist/pdas/index.d.ts +131 -1
  30. package/dist/pdas/index.js +151 -2
  31. package/dist/pdas/index.js.map +1 -1
  32. package/dist/types/index.cjs +9 -0
  33. package/dist/types/index.cjs.map +1 -1
  34. package/dist/types/index.d.cts +586 -3
  35. package/dist/types/index.d.ts +586 -3
  36. package/dist/types/index.js +9 -1
  37. package/dist/types/index.js.map +1 -1
  38. package/package.json +5 -3
  39. package/src/__tests__/dynamic-tokenomics.test.ts +358 -0
  40. package/src/accounts/index.ts +852 -1
  41. package/src/client.ts +1130 -1
  42. package/src/constants/index.ts +48 -2
  43. package/src/index.ts +58 -0
  44. package/src/instructions/index.ts +1383 -40
  45. package/src/pdas/index.ts +346 -0
  46. package/src/types/index.ts +698 -2
  47. package/src/utils/index.ts +90 -0
@@ -29,6 +29,20 @@ import {
29
29
  getTreasuryVaultPDA,
30
30
  getLpUsdcVaultPDA,
31
31
  getFounderVestingPDA,
32
+ getSubAllocationVestingPDA,
33
+ // Per-Milestone Vesting PDAs
34
+ getInvestorMilestoneVestingPDA,
35
+ getFounderMilestoneVestingPDA,
36
+ // Future Round PDAs
37
+ getFutureRoundTokenVaultPDA,
38
+ getFutureRoundVaultPDA,
39
+ // Multi-Round Fundraising PDAs
40
+ getFundingRoundPDA,
41
+ getRoundEscrowPDA,
42
+ getRoundMilestonePDA,
43
+ getRoundNftMintPDA,
44
+ getRoundInvestmentPDA,
45
+ getRoundInvestorMilestoneVestingPDA,
32
46
  } from '../pdas/index.js';
33
47
  import { getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token';
34
48
 
@@ -79,7 +93,7 @@ export async function initializeAdmin(
79
93
  ): Promise<string> {
80
94
  return getMethods(program)
81
95
  .initializeAdmin()
82
- .accounts({
96
+ .accountsPartial({
83
97
  admin,
84
98
  payer,
85
99
  })
@@ -96,7 +110,7 @@ export async function transferAdmin(
96
110
  ): Promise<string> {
97
111
  return getMethods(program)
98
112
  .transferAdmin()
99
- .accounts({
113
+ .accountsPartial({
100
114
  authority: adminKeypair.publicKey,
101
115
  newAdmin,
102
116
  })
@@ -113,7 +127,7 @@ export async function acceptAdmin(
113
127
  ): Promise<string> {
114
128
  return getMethods(program)
115
129
  .acceptAdmin()
116
- .accounts({
130
+ .accountsPartial({
117
131
  newAuthority,
118
132
  })
119
133
  .rpc();
@@ -126,6 +140,7 @@ export async function acceptAdmin(
126
140
  /**
127
141
  * TierConfig input type for initializeProject
128
142
  * Matches the on-chain TierConfig struct
143
+ * Note: voteMultiplier is auto-calculated on-chain using logarithmic formula
129
144
  */
130
145
  interface TierConfigInput {
131
146
  /** USDC amount per lot */
@@ -134,8 +149,6 @@ interface TierConfigInput {
134
149
  maxLots: number;
135
150
  /** Token allocation per $1 invested */
136
151
  tokenRatio: BN;
137
- /** Vote weight multiplier (basis points, 100 = 1.0x) */
138
- voteMultiplier: number;
139
152
  }
140
153
 
141
154
  /**
@@ -153,16 +166,22 @@ export interface TokenomicsInput {
153
166
  lpTokenAllocationBps: number;
154
167
  /** LP USDC allocation in basis points (min 500 = 5% of raised USDC) */
155
168
  lpUsdcAllocationBps: number;
156
- /** Founder allocation in basis points (optional) */
169
+ /** Founder allocation in basis points (optional). Sub-allocations (treasury, advisors, marketing) draw from this pool. */
157
170
  founderAllocationBps?: number | null;
158
- /** Treasury allocation in basis points (optional) */
159
- treasuryAllocationBps?: number | null;
171
+ /** Zemyth platform fee in basis points (minimum 100 = 1%). Vests proportionally per milestone. */
172
+ zemythAllocationBps?: number | null;
160
173
  /** Founder wallet for vesting (required if founder_allocation_bps > 0) */
161
174
  founderWallet?: PublicKey | null;
162
175
  /** Vesting duration in months (required if founder_allocation_bps > 0) */
163
176
  vestingDurationMonths?: number | null;
164
177
  /** Cliff period in months (optional) */
165
178
  cliffMonths?: number | null;
179
+ /** Early Token Release: Founder milestone-based vesting BPS (optional, default: 4750 = 47.5% of founder allocation) */
180
+ founderMilestoneVestingBps?: number | null;
181
+ /** Early Token Release: Founder time-based vesting BPS (optional, default: 4750 = 47.5% of founder allocation) */
182
+ founderTimeVestingBps?: number | null;
183
+ /** Future round allocation in basis points (optional, 0 = none, >= 1000 = 10% minimum if enabled) */
184
+ futureRoundAllocationBps?: number | null;
166
185
  }
167
186
 
168
187
  /**
@@ -262,6 +281,11 @@ export function validateDeadline(
262
281
  * @param milestone1Deadline - Unix timestamp for M1 deadline (required)
263
282
  * Must be >= current_time + MIN_DEADLINE_DURATION_SECONDS (7 days prod, 60s dev)
264
283
  * Must be <= current_time + MAX_DEADLINE_DURATION_SECONDS (1 year)
284
+ * @param priceMultipliers - Optional price multipliers in BPS for each milestone
285
+ * - If not provided (default): Uses DYNAMIC mode - multipliers calculated automatically
286
+ * via ZEMYTH formula when founder claims milestone funds and sets next deadline
287
+ * - If provided: Uses STATIC mode - pre-configured multipliers array (legacy behavior)
288
+ * multipliers[i] = price after milestone i passes (10000 = 1.0x, 15000 = 1.5x)
265
289
  */
266
290
  export async function initializeProject(
267
291
  program: AnyProgram,
@@ -275,6 +299,8 @@ export async function initializeProject(
275
299
  tokenomics: TokenomicsInput;
276
300
  /** Milestone 1 deadline - Unix timestamp (required) */
277
301
  milestone1Deadline: BN;
302
+ /** Price multipliers: Optional pre-configured BPS array (defaults to dynamic ZEMYTH formula) */
303
+ priceMultipliers?: number[];
278
304
  },
279
305
  founder: PublicKey
280
306
  ): Promise<string> {
@@ -291,14 +317,18 @@ export async function initializeProject(
291
317
  lpTokenAllocationBps: args.tokenomics.lpTokenAllocationBps,
292
318
  lpUsdcAllocationBps: args.tokenomics.lpUsdcAllocationBps,
293
319
  founderAllocationBps: args.tokenomics.founderAllocationBps ?? null,
294
- treasuryAllocationBps: args.tokenomics.treasuryAllocationBps ?? null,
320
+ zemythAllocationBps: args.tokenomics.zemythAllocationBps ?? null,
295
321
  founderWallet: args.tokenomics.founderWallet ?? null,
296
322
  vestingDurationMonths: args.tokenomics.vestingDurationMonths ?? null,
297
323
  cliffMonths: args.tokenomics.cliffMonths ?? null,
324
+ founderMilestoneVestingBps: args.tokenomics.founderMilestoneVestingBps ?? null,
325
+ founderTimeVestingBps: args.tokenomics.founderTimeVestingBps ?? null,
326
+ futureRoundAllocationBps: args.tokenomics.futureRoundAllocationBps ?? null,
298
327
  },
299
328
  milestone1Deadline: args.milestone1Deadline,
329
+ priceMultipliers: args.priceMultipliers ?? null,
300
330
  })
301
- .accounts({
331
+ .accountsPartial({
302
332
  founder,
303
333
  })
304
334
  .rpc();
@@ -316,7 +346,7 @@ export async function submitForApproval(
316
346
 
317
347
  return getMethods(program)
318
348
  .submitForApproval()
319
- .accounts({
349
+ .accountsPartial({
320
350
  project: projectPda,
321
351
  founder,
322
352
  })
@@ -346,10 +376,17 @@ export async function approveProject(
346
376
  const lpTokenVaultPda = getLpTokenVaultPDA(projectPda, program.programId);
347
377
  const treasuryVaultPda = getTreasuryVaultPDA(projectPda, program.programId);
348
378
  const lpUsdcVaultPda = getLpUsdcVaultPDA(projectPda, program.programId);
379
+ const futureRoundTokenVaultPda = getFutureRoundTokenVaultPDA(projectPda, program.programId);
380
+ const futureRoundVaultPda = getFutureRoundVaultPDA(projectPda, program.programId);
381
+
382
+ // Request 400k CUs - this instruction creates 10 accounts + 7 CPIs + 5 events
383
+ const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({
384
+ units: 400_000,
385
+ });
349
386
 
350
387
  return getMethods(program)
351
388
  .approveProject()
352
- .accounts({
389
+ .accountsPartial({
353
390
  project: projectPda,
354
391
  tokenomics: tokenomicsPda,
355
392
  tokenVault: tokenVaultPda,
@@ -360,10 +397,13 @@ export async function approveProject(
360
397
  lpTokenVault: lpTokenVaultPda,
361
398
  treasuryVault: treasuryVaultPda,
362
399
  lpUsdcVault: lpUsdcVaultPda,
400
+ futureRoundTokenVault: futureRoundTokenVaultPda,
401
+ futureRoundVault: futureRoundVaultPda,
363
402
  usdcMint: args.usdcMint,
364
403
  authority: adminKeypair.publicKey,
365
404
  payer: adminKeypair.publicKey,
366
405
  })
406
+ .preInstructions([computeBudgetIx])
367
407
  .signers([adminKeypair])
368
408
  .rpc();
369
409
  }
@@ -374,6 +414,10 @@ export async function approveProject(
374
414
 
375
415
  /**
376
416
  * Create a milestone for a project
417
+ *
418
+ * @param vestingDurationMonths - Vesting duration in months (6-24), null for default (12)
419
+ * @param cliffMonths - Cliff duration in months (0-3), null for default (0)
420
+ * @param instantReleaseBps - Instant release basis points (500-5000), null for default (500 = 5%)
377
421
  */
378
422
  export async function createMilestone(
379
423
  program: AnyProgram,
@@ -382,6 +426,9 @@ export async function createMilestone(
382
426
  milestoneIndex: number;
383
427
  percentage: number;
384
428
  description: string;
429
+ vestingDurationMonths?: number | null;
430
+ cliffMonths?: number | null;
431
+ instantReleaseBps?: number | null;
385
432
  },
386
433
  founder: PublicKey
387
434
  ): Promise<string> {
@@ -393,8 +440,11 @@ export async function createMilestone(
393
440
  milestoneIndex: args.milestoneIndex,
394
441
  percentage: args.percentage,
395
442
  description: args.description,
443
+ vestingDurationMonths: args.vestingDurationMonths ?? null,
444
+ cliffMonths: args.cliffMonths ?? null,
445
+ instantReleaseBps: args.instantReleaseBps ?? null,
396
446
  })
397
- .accounts({
447
+ .accountsPartial({
398
448
  project: projectPda,
399
449
  milestone: milestonePda,
400
450
  founder,
@@ -416,7 +466,7 @@ export async function submitMilestone(
416
466
 
417
467
  return getMethods(program)
418
468
  .submitMilestone()
419
- .accounts({
469
+ .accountsPartial({
420
470
  project: projectPda,
421
471
  milestone: milestonePda,
422
472
  founder,
@@ -463,7 +513,7 @@ export async function voteOnMilestone(
463
513
 
464
514
  return getMethods(program)
465
515
  .voteOnMilestone({ choice: args.choice })
466
- .accounts({
516
+ .accountsPartial({
467
517
  milestone: milestonePda,
468
518
  project: projectPda,
469
519
  investment: investmentPda,
@@ -488,7 +538,7 @@ export async function finalizeVoting(
488
538
 
489
539
  return getMethods(program)
490
540
  .finalizeVoting()
491
- .accounts({
541
+ .accountsPartial({
492
542
  project: projectPda,
493
543
  milestone: milestonePda,
494
544
  })
@@ -536,7 +586,7 @@ export async function claimMilestoneFunds(
536
586
 
537
587
  return getMethods(program)
538
588
  .claimMilestoneFunds({ nextMilestoneDeadline: args.nextMilestoneDeadline })
539
- .accounts({
589
+ .accountsPartial({
540
590
  milestone: milestonePda,
541
591
  project: projectPda,
542
592
  founder,
@@ -574,7 +624,7 @@ export async function resubmitMilestone(
574
624
 
575
625
  return getMethods(program)
576
626
  .resubmitMilestone()
577
- .accounts({
627
+ .accountsPartial({
578
628
  project: projectPda,
579
629
  milestone: milestonePda,
580
630
  founder,
@@ -607,7 +657,7 @@ export async function setMilestoneDeadline(
607
657
  milestoneIndex: args.milestoneIndex,
608
658
  deadline: args.deadline,
609
659
  })
610
- .accounts({
660
+ .accountsPartial({
611
661
  project: projectPda,
612
662
  milestone: milestonePda,
613
663
  founder,
@@ -640,7 +690,7 @@ export async function extendMilestoneDeadline(
640
690
  milestoneIndex: args.milestoneIndex,
641
691
  newDeadline: args.newDeadline,
642
692
  })
643
- .accounts({
693
+ .accountsPartial({
644
694
  project: projectPda,
645
695
  milestone: milestonePda,
646
696
  founder,
@@ -725,7 +775,7 @@ export async function invest(
725
775
  // Metaplex NFT minting requires significantly more than the default 200k CU
726
776
  return getMethods(program)
727
777
  .invest({ amount: args.amount })
728
- .accounts({
778
+ .accountsPartial({
729
779
  project: projectPda,
730
780
  firstMilestone: firstMilestonePda,
731
781
  nftMint: nftMint,
@@ -771,7 +821,7 @@ export async function cancelInvestment(
771
821
 
772
822
  return getMethods(program)
773
823
  .cancelInvestment()
774
- .accounts({
824
+ .accountsPartial({
775
825
  investor,
776
826
  project: projectPda,
777
827
  investment: investmentPda,
@@ -814,7 +864,7 @@ export async function proposePivot(
814
864
  newMetadataUri: args.newMetadataUri,
815
865
  newMilestones: args.newMilestones,
816
866
  })
817
- .accounts({
867
+ .accountsPartial({
818
868
  project: projectPda,
819
869
  founder,
820
870
  pivotProposal: pivotProposalPda,
@@ -850,7 +900,7 @@ export async function approvePivot(
850
900
 
851
901
  return getMethods(program)
852
902
  .approvePivot()
853
- .accounts({
903
+ .accountsPartial({
854
904
  moderator: adminKeypair.publicKey,
855
905
  project: projectPda,
856
906
  pivotProposal: pivotProposalPda,
@@ -897,7 +947,7 @@ export async function withdrawFromPivot(
897
947
 
898
948
  return getMethods(program)
899
949
  .withdrawFromPivot()
900
- .accounts({
950
+ .accountsPartial({
901
951
  investor,
902
952
  project: projectPda,
903
953
  pivotProposal: pivotProposalPda,
@@ -942,7 +992,7 @@ export async function finalizePivot(
942
992
 
943
993
  return getMethods(program)
944
994
  .finalizePivot()
945
- .accounts({
995
+ .accountsPartial({
946
996
  authority,
947
997
  project: projectPda,
948
998
  pivotProposal: pivotProposalPda,
@@ -974,7 +1024,7 @@ export async function setTgeDate(
974
1024
  tgeDate: args.tgeDate,
975
1025
  tokenMint: args.tokenMint,
976
1026
  })
977
- .accounts({
1027
+ .accountsPartial({
978
1028
  project: projectPda,
979
1029
  founder,
980
1030
  })
@@ -998,7 +1048,7 @@ export async function depositTokens(
998
1048
 
999
1049
  return getMethods(program)
1000
1050
  .depositTokens({ amount: args.amount })
1001
- .accounts({
1051
+ .accountsPartial({
1002
1052
  project: projectPda,
1003
1053
  tokenMint: args.tokenMint,
1004
1054
  founderTokenAccount: args.founderTokenAccount,
@@ -1027,7 +1077,7 @@ export async function claimTokens(
1027
1077
 
1028
1078
  return getMethods(program)
1029
1079
  .claimTokens()
1030
- .accounts({
1080
+ .accountsPartial({
1031
1081
  investor,
1032
1082
  project: projectPda,
1033
1083
  investment: investmentPda,
@@ -1057,7 +1107,7 @@ export async function reportScam(
1057
1107
 
1058
1108
  return getMethods(program)
1059
1109
  .reportScam()
1060
- .accounts({
1110
+ .accountsPartial({
1061
1111
  tgeEscrow: tgeEscrowPda,
1062
1112
  project: projectPda,
1063
1113
  investment: investmentPda,
@@ -1082,7 +1132,7 @@ export async function releaseHoldback(
1082
1132
 
1083
1133
  return getMethods(program)
1084
1134
  .releaseHoldback()
1085
- .accounts({
1135
+ .accountsPartial({
1086
1136
  tgeEscrow: tgeEscrowPda,
1087
1137
  project: projectPda,
1088
1138
  founderTokenAccount: args.founderTokenAccount,
@@ -1107,7 +1157,7 @@ export async function checkAbandonment(
1107
1157
 
1108
1158
  return getMethods(program)
1109
1159
  .checkAbandonment()
1110
- .accounts({
1160
+ .accountsPartial({
1111
1161
  project: projectPda,
1112
1162
  milestone: milestonePda,
1113
1163
  })
@@ -1151,7 +1201,7 @@ export async function claimRefund(
1151
1201
 
1152
1202
  return getMethods(program)
1153
1203
  .claimRefund()
1154
- .accounts({
1204
+ .accountsPartial({
1155
1205
  project: projectPda,
1156
1206
  investment: investmentPda,
1157
1207
  nftMint: nftMintPubkey,
@@ -1208,7 +1258,7 @@ export async function claimInvestorTokens(
1208
1258
 
1209
1259
  return getMethods(program)
1210
1260
  .claimInvestorTokens({ milestoneIndex: args.milestoneIndex })
1211
- .accounts({
1261
+ .accountsPartial({
1212
1262
  investor,
1213
1263
  project: projectPda,
1214
1264
  tokenVault: tokenVaultPda,
@@ -1261,7 +1311,7 @@ export async function distributeTokens(
1261
1311
 
1262
1312
  return getMethods(program)
1263
1313
  .distributeTokens({ milestoneIndex: args.milestoneIndex })
1264
- .accounts({
1314
+ .accountsPartial({
1265
1315
  project: projectPda,
1266
1316
  tokenVault: tokenVaultPda,
1267
1317
  investorVault: investorVaultPda,
@@ -1292,7 +1342,7 @@ export async function completeDistribution(
1292
1342
 
1293
1343
  return getMethods(program)
1294
1344
  .completeDistribution({ milestoneIndex: args.milestoneIndex })
1295
- .accounts({
1345
+ .accountsPartial({
1296
1346
  project: projectPda,
1297
1347
  tokenVault: tokenVaultPda,
1298
1348
  payer,
@@ -1372,7 +1422,7 @@ export async function initializeFounderVesting(
1372
1422
 
1373
1423
  return getMethods(program)
1374
1424
  .initializeFounderVesting()
1375
- .accounts({
1425
+ .accountsPartial({
1376
1426
  project: projectPda,
1377
1427
  tokenomics: tokenomicsPda,
1378
1428
  tokenVault: tokenVaultPda,
@@ -1406,7 +1456,7 @@ export async function claimVestedTokens(
1406
1456
 
1407
1457
  return getMethods(program)
1408
1458
  .claimVestedTokens()
1409
- .accounts({
1459
+ .accountsPartial({
1410
1460
  project: projectPda,
1411
1461
  tokenVault: tokenVaultPda,
1412
1462
  founderVesting: founderVestingPda,
@@ -1443,7 +1493,7 @@ export async function forceCompleteDistribution(
1443
1493
 
1444
1494
  return getMethods(program)
1445
1495
  .forceCompleteDistribution()
1446
- .accounts({
1496
+ .accountsPartial({
1447
1497
  admin: adminKeypair.publicKey,
1448
1498
  adminConfig: adminConfigPda,
1449
1499
  project: projectPda,
@@ -1488,7 +1538,7 @@ export async function claimMissedUnlock(
1488
1538
 
1489
1539
  return getMethods(program)
1490
1540
  .claimMissedUnlock({ milestoneIndex: args.milestoneIndex })
1491
- .accounts({
1541
+ .accountsPartial({
1492
1542
  claimer,
1493
1543
  project: projectPda,
1494
1544
  tokenVault: tokenVaultPda,
@@ -1502,3 +1552,1296 @@ export async function claimMissedUnlock(
1502
1552
  })
1503
1553
  .rpc();
1504
1554
  }
1555
+
1556
+ // =============================================================================
1557
+ // Dynamic Tokenomics Instructions
1558
+ // =============================================================================
1559
+
1560
+ import {
1561
+ getAllocationProposalPDA,
1562
+ getAllocationVotePDA,
1563
+ } from '../pdas/index.js';
1564
+
1565
+ /**
1566
+ * Add a sub-allocation from the reserve pool (Draft state only)
1567
+ *
1568
+ * Allows founders to add named allocations (advisors, marketing, etc.)
1569
+ * from their reserve pool before submitting for approval.
1570
+ *
1571
+ * @param name - Sub-allocation name (1-32 ASCII chars)
1572
+ * @param bps - Basis points from reserve pool
1573
+ * @param recipient - Token destination wallet
1574
+ * @param vestingMonths - Vesting duration (0 = immediate)
1575
+ * @param cliffMonths - Cliff period before vesting starts
1576
+ */
1577
+ export async function addSubAllocation(
1578
+ program: AnyProgram,
1579
+ args: {
1580
+ projectId: BN;
1581
+ name: string;
1582
+ bps: number;
1583
+ recipient: PublicKey;
1584
+ vestingMonths: number;
1585
+ cliffMonths: number;
1586
+ },
1587
+ founder: PublicKey
1588
+ ): Promise<string> {
1589
+ const projectPda = getProjectPDA(args.projectId, program.programId);
1590
+ const tokenomicsPda = getTokenomicsPDA(projectPda, program.programId);
1591
+
1592
+ return getMethods(program)
1593
+ .addSubAllocation({
1594
+ name: args.name,
1595
+ bps: args.bps,
1596
+ recipient: args.recipient,
1597
+ vestingMonths: args.vestingMonths,
1598
+ cliffMonths: args.cliffMonths,
1599
+ })
1600
+ .accountsPartial({
1601
+ project: projectPda,
1602
+ tokenomics: tokenomicsPda,
1603
+ founder,
1604
+ })
1605
+ .rpc();
1606
+ }
1607
+
1608
+ /**
1609
+ * Propose an allocation change via governance (post-Draft states)
1610
+ *
1611
+ * Creates a 7-day voting period for investors to approve/reject
1612
+ * a new sub-allocation from the reserve pool.
1613
+ */
1614
+ export async function proposeAllocationChange(
1615
+ program: AnyProgram,
1616
+ args: {
1617
+ projectId: BN;
1618
+ name: string;
1619
+ bps: number;
1620
+ recipient: PublicKey;
1621
+ vestingMonths: number;
1622
+ cliffMonths: number;
1623
+ },
1624
+ founder: PublicKey
1625
+ ): Promise<string> {
1626
+ const projectPda = getProjectPDA(args.projectId, program.programId);
1627
+ const tokenomicsPda = getTokenomicsPDA(projectPda, program.programId);
1628
+
1629
+ // Fetch tokenomics to get current proposal_count
1630
+ const tokenomics = await getAccountNamespace(program).tokenomics.fetch(tokenomicsPda);
1631
+ const proposalIndex = tokenomics.proposalCount || 0;
1632
+ const proposalPda = getAllocationProposalPDA(projectPda, proposalIndex, program.programId);
1633
+
1634
+ return getMethods(program)
1635
+ .proposeAllocationChange({
1636
+ name: args.name,
1637
+ bps: args.bps,
1638
+ recipient: args.recipient,
1639
+ vestingMonths: args.vestingMonths,
1640
+ cliffMonths: args.cliffMonths,
1641
+ })
1642
+ .accountsPartial({
1643
+ project: projectPda,
1644
+ tokenomics: tokenomicsPda,
1645
+ proposal: proposalPda,
1646
+ founder,
1647
+ systemProgram: SystemProgram.programId,
1648
+ })
1649
+ .rpc();
1650
+ }
1651
+
1652
+ /**
1653
+ * Vote on an allocation change proposal
1654
+ *
1655
+ * Investors can vote for/against using their Investment NFT.
1656
+ * Must meet 7-day hold period for voting eligibility.
1657
+ * One vote per NFT (prevents double voting).
1658
+ */
1659
+ export async function voteAllocationChange(
1660
+ program: AnyProgram,
1661
+ args: {
1662
+ projectId: BN;
1663
+ proposalIndex: number;
1664
+ nftMint: PublicKey;
1665
+ voteFor: boolean;
1666
+ },
1667
+ voter: PublicKey
1668
+ ): Promise<string> {
1669
+ const nftMintPubkey = ensurePublicKey(args.nftMint);
1670
+ const projectPda = getProjectPDA(args.projectId, program.programId);
1671
+ const proposalPda = getAllocationProposalPDA(projectPda, args.proposalIndex, program.programId);
1672
+ const investmentPda = getInvestmentPDA(projectPda, nftMintPubkey, program.programId);
1673
+ const votePda = getAllocationVotePDA(proposalPda, nftMintPubkey, program.programId);
1674
+
1675
+ // Get voter's NFT token account (ATA)
1676
+ const investorNftAccount = getAssociatedTokenAddressSync(
1677
+ nftMintPubkey,
1678
+ voter,
1679
+ false,
1680
+ TOKEN_PROGRAM_ID
1681
+ );
1682
+
1683
+ return getMethods(program)
1684
+ .voteAllocationChange({ voteFor: args.voteFor })
1685
+ .accountsPartial({
1686
+ project: projectPda,
1687
+ proposal: proposalPda,
1688
+ investment: investmentPda,
1689
+ nftMint: nftMintPubkey,
1690
+ investorNftAccount,
1691
+ voteRecord: votePda,
1692
+ voter,
1693
+ systemProgram: SystemProgram.programId,
1694
+ })
1695
+ .rpc();
1696
+ }
1697
+
1698
+ /**
1699
+ * Execute an approved allocation change proposal
1700
+ *
1701
+ * Permissionless - anyone can call after voting ends.
1702
+ * Proposal must have passed (>51% approval).
1703
+ * Adds the sub-allocation to tokenomics.
1704
+ */
1705
+ export async function executeAllocationChange(
1706
+ program: AnyProgram,
1707
+ args: {
1708
+ projectId: BN;
1709
+ proposalIndex: number;
1710
+ },
1711
+ executor: PublicKey
1712
+ ): Promise<string> {
1713
+ const projectPda = getProjectPDA(args.projectId, program.programId);
1714
+ const tokenomicsPda = getTokenomicsPDA(projectPda, program.programId);
1715
+ const proposalPda = getAllocationProposalPDA(projectPda, args.proposalIndex, program.programId);
1716
+
1717
+ return getMethods(program)
1718
+ .executeAllocationChange()
1719
+ .accountsPartial({
1720
+ project: projectPda,
1721
+ tokenomics: tokenomicsPda,
1722
+ proposal: proposalPda,
1723
+ executor,
1724
+ })
1725
+ .rpc();
1726
+ }
1727
+
1728
+ // =============================================================================
1729
+ // Early Token Release Instructions
1730
+ // =============================================================================
1731
+
1732
+ /**
1733
+ * Claim early tokens as an investor (5% of token allocation)
1734
+ *
1735
+ * Early Token Release: Investors can claim 5% of their token allocation
1736
+ * 24 hours after investing. These tokens are fully liquid immediately.
1737
+ *
1738
+ * Prerequisites:
1739
+ * - 24h cooling period must have passed since investment
1740
+ * - Investment must not have already claimed early tokens
1741
+ * - Investment must have active voting rights (not withdrawn from pivot)
1742
+ *
1743
+ * @param nftMint - The NFT mint that proves investment ownership
1744
+ * @param investorTokenAccount - Investor's token account to receive claimed tokens
1745
+ */
1746
+ export async function claimEarlyTokens(
1747
+ program: AnyProgram,
1748
+ args: {
1749
+ projectId: BN;
1750
+ /** NFT mint that proves investment ownership */
1751
+ nftMint: PublicKey;
1752
+ /** Investor's token account to receive claimed tokens */
1753
+ investorTokenAccount: PublicKey;
1754
+ },
1755
+ investor: PublicKey
1756
+ ): Promise<string> {
1757
+ const nftMintPubkey = ensurePublicKey(args.nftMint);
1758
+ const projectPda = getProjectPDA(args.projectId, program.programId);
1759
+ const tokenVaultPda = getTokenVaultPDA(projectPda, program.programId);
1760
+ const investmentPda = getInvestmentPDA(projectPda, nftMintPubkey, program.programId);
1761
+ const investorVaultPda = getInvestorVaultPDA(projectPda, program.programId);
1762
+ const vaultAuthorityPda = getVaultAuthorityPDA(projectPda, program.programId);
1763
+
1764
+ // Get investor's NFT token account (ATA)
1765
+ const investorNftAccount = getAssociatedTokenAddressSync(
1766
+ nftMintPubkey,
1767
+ investor,
1768
+ false,
1769
+ TOKEN_PROGRAM_ID
1770
+ );
1771
+
1772
+ return getMethods(program)
1773
+ .claimEarlyTokens()
1774
+ .accountsPartial({
1775
+ investor,
1776
+ project: projectPda,
1777
+ tokenVault: tokenVaultPda,
1778
+ investment: investmentPda,
1779
+ nftMint: nftMintPubkey,
1780
+ investorNftAccount,
1781
+ investorVault: investorVaultPda,
1782
+ investorTokenAccount: args.investorTokenAccount,
1783
+ vaultAuthority: vaultAuthorityPda,
1784
+ tokenProgram: TOKEN_PROGRAM_ID,
1785
+ })
1786
+ .rpc();
1787
+ }
1788
+
1789
+ /**
1790
+ * Claim early tokens as a founder (5% of founder allocation)
1791
+ *
1792
+ * Early Token Release: Founders can claim 5% of their token allocation
1793
+ * when the project becomes Funded. These tokens are fully liquid immediately.
1794
+ *
1795
+ * Prerequisites:
1796
+ * - Project must be in Funded, InProgress, or Completed state
1797
+ * - Founder must not have already claimed early tokens
1798
+ * - Founder allocation must be configured in tokenomics
1799
+ *
1800
+ * @param founderTokenAccount - Founder's token account to receive claimed tokens
1801
+ */
1802
+ export async function claimFounderEarlyTokens(
1803
+ program: AnyProgram,
1804
+ args: {
1805
+ projectId: BN;
1806
+ /** Founder's token account to receive claimed tokens */
1807
+ founderTokenAccount: PublicKey;
1808
+ },
1809
+ founder: PublicKey
1810
+ ): Promise<string> {
1811
+ const projectPda = getProjectPDA(args.projectId, program.programId);
1812
+ const tokenomicsPda = getTokenomicsPDA(projectPda, program.programId);
1813
+ const tokenVaultPda = getTokenVaultPDA(projectPda, program.programId);
1814
+ const founderVaultPda = getFounderVaultPDA(projectPda, program.programId);
1815
+ const vaultAuthorityPda = getVaultAuthorityPDA(projectPda, program.programId);
1816
+
1817
+ return getMethods(program)
1818
+ .claimFounderEarlyTokens()
1819
+ .accountsPartial({
1820
+ founder,
1821
+ project: projectPda,
1822
+ tokenomics: tokenomicsPda,
1823
+ tokenVault: tokenVaultPda,
1824
+ founderVault: founderVaultPda,
1825
+ founderTokenAccount: args.founderTokenAccount,
1826
+ vaultAuthority: vaultAuthorityPda,
1827
+ tokenProgram: TOKEN_PROGRAM_ID,
1828
+ })
1829
+ .rpc();
1830
+ }
1831
+
1832
+ /**
1833
+ * Claim founder milestone-based tokens for a specific milestone
1834
+ *
1835
+ * Early Token Release: Founders can claim their milestone-based portion
1836
+ * (default 47.5% of founder allocation) when milestones are unlocked.
1837
+ * Amount per milestone = milestone_tokens * milestone_percentage / 100
1838
+ *
1839
+ * Prerequisites:
1840
+ * - Milestone must be in Unlocked state
1841
+ * - Founder must not have already claimed tokens for this milestone
1842
+ * - Founder milestone vesting must be configured in tokenomics
1843
+ *
1844
+ * @param milestoneIndex - The milestone index to claim tokens from
1845
+ * @param founderTokenAccount - Founder's token account to receive claimed tokens
1846
+ */
1847
+ export async function claimFounderMilestoneTokens(
1848
+ program: AnyProgram,
1849
+ args: {
1850
+ projectId: BN;
1851
+ /** Milestone index to claim tokens from */
1852
+ milestoneIndex: number;
1853
+ /** Founder's token account to receive claimed tokens */
1854
+ founderTokenAccount: PublicKey;
1855
+ },
1856
+ founder: PublicKey
1857
+ ): Promise<string> {
1858
+ const projectPda = getProjectPDA(args.projectId, program.programId);
1859
+ const milestonePda = getMilestonePDA(projectPda, args.milestoneIndex, program.programId);
1860
+ const tokenomicsPda = getTokenomicsPDA(projectPda, program.programId);
1861
+ const tokenVaultPda = getTokenVaultPDA(projectPda, program.programId);
1862
+ const founderVaultPda = getFounderVaultPDA(projectPda, program.programId);
1863
+ const vaultAuthorityPda = getVaultAuthorityPDA(projectPda, program.programId);
1864
+
1865
+ return getMethods(program)
1866
+ .claimFounderMilestoneTokens({ milestoneIndex: args.milestoneIndex })
1867
+ .accountsPartial({
1868
+ founder,
1869
+ project: projectPda,
1870
+ milestone: milestonePda,
1871
+ tokenomics: tokenomicsPda,
1872
+ tokenVault: tokenVaultPda,
1873
+ founderVault: founderVaultPda,
1874
+ founderTokenAccount: args.founderTokenAccount,
1875
+ vaultAuthority: vaultAuthorityPda,
1876
+ tokenProgram: TOKEN_PROGRAM_ID,
1877
+ })
1878
+ .rpc();
1879
+ }
1880
+
1881
+ // =============================================================================
1882
+ // Sub-Allocation Vesting Instructions
1883
+ // =============================================================================
1884
+
1885
+ /**
1886
+ * Initialize sub-allocation vesting after MAE (Market Access Event)
1887
+ *
1888
+ * Creates SubAllocationVesting PDA for a specific sub-allocation ID.
1889
+ * Project must be in Completed state with MAE completed.
1890
+ * Permissionless - anyone can pay to initialize.
1891
+ *
1892
+ * @param subAllocationId - Sub-allocation ID to initialize vesting for (0-9)
1893
+ */
1894
+ export async function initializeSubAllocationVesting(
1895
+ program: AnyProgram,
1896
+ args: {
1897
+ projectId: BN;
1898
+ /** Sub-allocation ID to initialize vesting for */
1899
+ subAllocationId: number;
1900
+ },
1901
+ payer: PublicKey
1902
+ ): Promise<string> {
1903
+ const projectPda = getProjectPDA(args.projectId, program.programId);
1904
+ const tokenomicsPda = getTokenomicsPDA(projectPda, program.programId);
1905
+ const tokenVaultPda = getTokenVaultPDA(projectPda, program.programId);
1906
+ const subAllocationVestingPda = getSubAllocationVestingPDA(projectPda, args.subAllocationId, program.programId);
1907
+
1908
+ return getMethods(program)
1909
+ .initializeSubAllocationVesting({ subAllocationId: args.subAllocationId })
1910
+ .accountsPartial({
1911
+ project: projectPda,
1912
+ tokenomics: tokenomicsPda,
1913
+ tokenVault: tokenVaultPda,
1914
+ subAllocationVesting: subAllocationVestingPda,
1915
+ payer,
1916
+ systemProgram: SystemProgram.programId,
1917
+ })
1918
+ .rpc();
1919
+ }
1920
+
1921
+ /**
1922
+ * Claim vested tokens from a sub-allocation
1923
+ *
1924
+ * Only the designated recipient wallet can claim tokens.
1925
+ * Project must be Completed and MAE must be completed.
1926
+ * Calculates claimable amount based on cliff + linear vesting schedule.
1927
+ *
1928
+ * @param subAllocationId - Sub-allocation ID to claim from (0-9)
1929
+ * @param recipientTokenAccount - Recipient's token account to receive claimed tokens
1930
+ */
1931
+ export async function claimSubAllocationTokens(
1932
+ program: AnyProgram,
1933
+ args: {
1934
+ projectId: BN;
1935
+ /** Sub-allocation ID to claim from */
1936
+ subAllocationId: number;
1937
+ /** Recipient's token account to receive claimed tokens */
1938
+ recipientTokenAccount: PublicKey;
1939
+ },
1940
+ recipient: PublicKey
1941
+ ): Promise<string> {
1942
+ const projectPda = getProjectPDA(args.projectId, program.programId);
1943
+ const tokenomicsPda = getTokenomicsPDA(projectPda, program.programId);
1944
+ const tokenVaultPda = getTokenVaultPDA(projectPda, program.programId);
1945
+ const subAllocationVestingPda = getSubAllocationVestingPDA(projectPda, args.subAllocationId, program.programId);
1946
+ const treasuryVaultPda = getTreasuryVaultPDA(projectPda, program.programId);
1947
+ const vaultAuthorityPda = getVaultAuthorityPDA(projectPda, program.programId);
1948
+
1949
+ return getMethods(program)
1950
+ .claimSubAllocationTokens({ subAllocationId: args.subAllocationId })
1951
+ .accountsPartial({
1952
+ project: projectPda,
1953
+ tokenomics: tokenomicsPda,
1954
+ tokenVault: tokenVaultPda,
1955
+ subAllocationVesting: subAllocationVestingPda,
1956
+ reserveVault: treasuryVaultPda,
1957
+ vaultAuthority: vaultAuthorityPda,
1958
+ recipientTokenAccount: args.recipientTokenAccount,
1959
+ recipient,
1960
+ tokenProgram: TOKEN_PROGRAM_ID,
1961
+ })
1962
+ .rpc();
1963
+ }
1964
+
1965
+ // =============================================================================
1966
+ // Per-Milestone Vesting Instructions (add-per-milestone-vesting)
1967
+ // =============================================================================
1968
+
1969
+ /**
1970
+ * Claim instant tokens for a passed milestone (investor)
1971
+ *
1972
+ * Per-milestone vesting: Investors claim the instant portion (e.g., 5-50%)
1973
+ * immediately when a milestone passes. Creates vesting PDA on first claim.
1974
+ *
1975
+ * Prerequisites:
1976
+ * - Milestone must be in Passed or Unlocked state
1977
+ * - Milestone must use per-milestone vesting (instant_release_bps < 10000)
1978
+ * - Investor must hold the NFT proving ownership
1979
+ *
1980
+ * @param milestoneIndex - The milestone index to claim tokens from
1981
+ * @param nftMint - The NFT mint proving investment ownership
1982
+ * @param investorTokenAccount - Investor's token account to receive claimed tokens
1983
+ */
1984
+ export async function claimMilestoneInstantTokens(
1985
+ program: AnyProgram,
1986
+ args: {
1987
+ projectId: BN;
1988
+ /** Milestone index to claim instant tokens from */
1989
+ milestoneIndex: number;
1990
+ /** NFT mint that proves investment ownership */
1991
+ nftMint: PublicKey;
1992
+ /** Investor's token account to receive claimed tokens */
1993
+ investorTokenAccount: PublicKey;
1994
+ },
1995
+ investor: PublicKey
1996
+ ): Promise<string> {
1997
+ const nftMintPubkey = ensurePublicKey(args.nftMint);
1998
+ const projectPda = getProjectPDA(args.projectId, program.programId);
1999
+ const milestonePda = getMilestonePDA(projectPda, args.milestoneIndex, program.programId);
2000
+ const investmentPda = getInvestmentPDA(projectPda, nftMintPubkey, program.programId);
2001
+ const tokenVaultPda = getTokenVaultPDA(projectPda, program.programId);
2002
+ const vestingPda = getInvestorMilestoneVestingPDA(projectPda, args.milestoneIndex, investmentPda, program.programId);
2003
+ const investorVaultPda = getInvestorVaultPDA(projectPda, program.programId);
2004
+ const vaultAuthorityPda = getVaultAuthorityPDA(projectPda, program.programId);
2005
+
2006
+ // Get investor's NFT token account (ATA)
2007
+ const investorNftAccount = getAssociatedTokenAddressSync(
2008
+ nftMintPubkey,
2009
+ investor,
2010
+ false,
2011
+ TOKEN_PROGRAM_ID
2012
+ );
2013
+
2014
+ return getMethods(program)
2015
+ .claimMilestoneInstantTokens({ milestoneIndex: args.milestoneIndex })
2016
+ .accountsPartial({
2017
+ investor,
2018
+ project: projectPda,
2019
+ milestone: milestonePda,
2020
+ investment: investmentPda,
2021
+ tokenVault: tokenVaultPda,
2022
+ vesting: vestingPda,
2023
+ nftMint: nftMintPubkey,
2024
+ investorNftAccount,
2025
+ investorVault: investorVaultPda,
2026
+ investorTokenAccount: args.investorTokenAccount,
2027
+ vaultAuthority: vaultAuthorityPda,
2028
+ tokenProgram: TOKEN_PROGRAM_ID,
2029
+ systemProgram: SystemProgram.programId,
2030
+ })
2031
+ .rpc();
2032
+ }
2033
+
2034
+ /**
2035
+ * Claim vested tokens for a milestone after cliff period (investor)
2036
+ *
2037
+ * Per-milestone vesting: Investors claim vested tokens linearly after
2038
+ * the cliff period passes. Must have already claimed instant tokens
2039
+ * to initialize the vesting PDA.
2040
+ *
2041
+ * Prerequisites:
2042
+ * - Vesting PDA must exist (created by claimMilestoneInstantTokens)
2043
+ * - Cliff period must have passed
2044
+ * - Investor must hold the NFT proving ownership
2045
+ *
2046
+ * @param milestoneIndex - The milestone index to claim vested tokens from
2047
+ * @param nftMint - The NFT mint proving investment ownership
2048
+ * @param investorTokenAccount - Investor's token account to receive claimed tokens
2049
+ */
2050
+ export async function claimMilestoneVestedTokens(
2051
+ program: AnyProgram,
2052
+ args: {
2053
+ projectId: BN;
2054
+ /** Milestone index to claim vested tokens from */
2055
+ milestoneIndex: number;
2056
+ /** NFT mint that proves investment ownership */
2057
+ nftMint: PublicKey;
2058
+ /** Investor's token account to receive claimed tokens */
2059
+ investorTokenAccount: PublicKey;
2060
+ },
2061
+ investor: PublicKey
2062
+ ): Promise<string> {
2063
+ const nftMintPubkey = ensurePublicKey(args.nftMint);
2064
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2065
+ const milestonePda = getMilestonePDA(projectPda, args.milestoneIndex, program.programId);
2066
+ const investmentPda = getInvestmentPDA(projectPda, nftMintPubkey, program.programId);
2067
+ const tokenVaultPda = getTokenVaultPDA(projectPda, program.programId);
2068
+ const vestingPda = getInvestorMilestoneVestingPDA(projectPda, args.milestoneIndex, investmentPda, program.programId);
2069
+ const investorVaultPda = getInvestorVaultPDA(projectPda, program.programId);
2070
+ const vaultAuthorityPda = getVaultAuthorityPDA(projectPda, program.programId);
2071
+
2072
+ // Get investor's NFT token account (ATA)
2073
+ const investorNftAccount = getAssociatedTokenAddressSync(
2074
+ nftMintPubkey,
2075
+ investor,
2076
+ false,
2077
+ TOKEN_PROGRAM_ID
2078
+ );
2079
+
2080
+ return getMethods(program)
2081
+ .claimMilestoneVestedTokens({ milestoneIndex: args.milestoneIndex })
2082
+ .accountsPartial({
2083
+ investor,
2084
+ project: projectPda,
2085
+ milestone: milestonePda,
2086
+ investment: investmentPda,
2087
+ tokenVault: tokenVaultPda,
2088
+ vesting: vestingPda,
2089
+ nftMint: nftMintPubkey,
2090
+ investorNftAccount,
2091
+ investorVault: investorVaultPda,
2092
+ investorTokenAccount: args.investorTokenAccount,
2093
+ vaultAuthority: vaultAuthorityPda,
2094
+ tokenProgram: TOKEN_PROGRAM_ID,
2095
+ })
2096
+ .rpc();
2097
+ }
2098
+
2099
+ /**
2100
+ * Claim instant tokens for a passed milestone (founder)
2101
+ *
2102
+ * Per-milestone vesting: Founders claim the instant portion (e.g., 5-50%)
2103
+ * of their milestone-based allocation when a milestone passes.
2104
+ * Creates vesting PDA on first claim.
2105
+ *
2106
+ * Prerequisites:
2107
+ * - Milestone must be in Passed or Unlocked state
2108
+ * - Milestone must use per-milestone vesting (instant_release_bps < 10000)
2109
+ * - Founder must have milestone-based allocation configured
2110
+ * - Caller must be the project founder
2111
+ *
2112
+ * @param milestoneIndex - The milestone index to claim tokens from
2113
+ * @param founderTokenAccount - Founder's token account to receive claimed tokens
2114
+ */
2115
+ export async function claimFounderMsInstantTokens(
2116
+ program: AnyProgram,
2117
+ args: {
2118
+ projectId: BN;
2119
+ /** Milestone index to claim instant tokens from */
2120
+ milestoneIndex: number;
2121
+ /** Founder's token account to receive claimed tokens */
2122
+ founderTokenAccount: PublicKey;
2123
+ },
2124
+ founder: PublicKey
2125
+ ): Promise<string> {
2126
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2127
+ const milestonePda = getMilestonePDA(projectPda, args.milestoneIndex, program.programId);
2128
+ const tokenomicsPda = getTokenomicsPDA(projectPda, program.programId);
2129
+ const tokenVaultPda = getTokenVaultPDA(projectPda, program.programId);
2130
+ const vestingPda = getFounderMilestoneVestingPDA(projectPda, args.milestoneIndex, program.programId);
2131
+ const founderVaultPda = getFounderVaultPDA(projectPda, program.programId);
2132
+ const vaultAuthorityPda = getVaultAuthorityPDA(projectPda, program.programId);
2133
+
2134
+ return getMethods(program)
2135
+ .claimFounderMsInstantTokens({ milestoneIndex: args.milestoneIndex })
2136
+ .accountsPartial({
2137
+ founder,
2138
+ project: projectPda,
2139
+ tokenomics: tokenomicsPda,
2140
+ milestone: milestonePda,
2141
+ tokenVault: tokenVaultPda,
2142
+ vesting: vestingPda,
2143
+ founderVault: founderVaultPda,
2144
+ founderTokenAccount: args.founderTokenAccount,
2145
+ vaultAuthority: vaultAuthorityPda,
2146
+ tokenProgram: TOKEN_PROGRAM_ID,
2147
+ systemProgram: SystemProgram.programId,
2148
+ })
2149
+ .rpc();
2150
+ }
2151
+
2152
+ /**
2153
+ * Claim vested tokens for a milestone after cliff period (founder)
2154
+ *
2155
+ * Per-milestone vesting: Founders claim vested tokens linearly after
2156
+ * the cliff period passes. Must have already claimed instant tokens
2157
+ * to initialize the vesting PDA.
2158
+ *
2159
+ * Prerequisites:
2160
+ * - Vesting PDA must exist (created by claimFounderMsInstantTokens)
2161
+ * - Cliff period must have passed
2162
+ * - Caller must be the project founder
2163
+ *
2164
+ * @param milestoneIndex - The milestone index to claim vested tokens from
2165
+ * @param founderTokenAccount - Founder's token account to receive claimed tokens
2166
+ */
2167
+ export async function claimFounderMsVestedTokens(
2168
+ program: AnyProgram,
2169
+ args: {
2170
+ projectId: BN;
2171
+ /** Milestone index to claim vested tokens from */
2172
+ milestoneIndex: number;
2173
+ /** Founder's token account to receive claimed tokens */
2174
+ founderTokenAccount: PublicKey;
2175
+ },
2176
+ founder: PublicKey
2177
+ ): Promise<string> {
2178
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2179
+ const tokenVaultPda = getTokenVaultPDA(projectPda, program.programId);
2180
+ const vestingPda = getFounderMilestoneVestingPDA(projectPda, args.milestoneIndex, program.programId);
2181
+ const founderVaultPda = getFounderVaultPDA(projectPda, program.programId);
2182
+ const vaultAuthorityPda = getVaultAuthorityPDA(projectPda, program.programId);
2183
+
2184
+ return getMethods(program)
2185
+ .claimFounderMsVestedTokens({ milestoneIndex: args.milestoneIndex })
2186
+ .accountsPartial({
2187
+ founder,
2188
+ project: projectPda,
2189
+ tokenVault: tokenVaultPda,
2190
+ vesting: vestingPda,
2191
+ founderVault: founderVaultPda,
2192
+ founderTokenAccount: args.founderTokenAccount,
2193
+ vaultAuthority: vaultAuthorityPda,
2194
+ tokenProgram: TOKEN_PROGRAM_ID,
2195
+ })
2196
+ .rpc();
2197
+ }
2198
+
2199
+ // =============================================================================
2200
+ // Multi-Round Fundraising Instructions (add-second-round-fundraising)
2201
+ // =============================================================================
2202
+
2203
+ /**
2204
+ * TierConfig input type for openFundingRound
2205
+ */
2206
+ interface RoundTierConfigInput {
2207
+ /** USDC amount per lot */
2208
+ amount: BN;
2209
+ /** Maximum lots available */
2210
+ maxLots: number;
2211
+ /** Token allocation per $1 invested */
2212
+ tokenRatio: BN;
2213
+ }
2214
+
2215
+ /**
2216
+ * Milestone configuration for new funding round
2217
+ */
2218
+ interface RoundMilestoneConfigInput {
2219
+ /** Percentage of funding_goal (1-100) */
2220
+ percentage: number;
2221
+ /** Milestone description (max 32 chars) */
2222
+ description: string;
2223
+ /** Vesting duration in months (6-24, 0 = instant unlock) */
2224
+ vestingDurationMonths?: number | null;
2225
+ /** Cliff duration in months (0-3) */
2226
+ cliffMonths?: number | null;
2227
+ /** Instant release basis points (500-5000) */
2228
+ instantReleaseBps?: number | null;
2229
+ }
2230
+
2231
+ /**
2232
+ * Derive Round Escrow Authority PDA
2233
+ */
2234
+ function getRoundEscrowAuthorityPDA(
2235
+ projectPda: PublicKey,
2236
+ roundNumber: number,
2237
+ programId: PublicKey
2238
+ ): [PublicKey, number] {
2239
+ return PublicKey.findProgramAddressSync(
2240
+ [
2241
+ Buffer.from('round_escrow'),
2242
+ projectPda.toBuffer(),
2243
+ Buffer.from([roundNumber]),
2244
+ Buffer.from('authority'),
2245
+ ],
2246
+ programId
2247
+ );
2248
+ }
2249
+
2250
+ /**
2251
+ * Open a new funding round (R2, R3, R4...)
2252
+ *
2253
+ * Multi-Round Fundraising: Creates FundingRound PDA, round escrow,
2254
+ * and milestone PDAs for the new round. Tokens come from FutureRoundVault.
2255
+ *
2256
+ * Prerequisites:
2257
+ * - Project must be InProgress or Completed state
2258
+ * - At least 1 milestone must have passed (for InProgress projects)
2259
+ * - No other round currently Open
2260
+ * - Current round must be fully funded before opening next
2261
+ * - Must have future_round_allocation configured in tokenomics
2262
+ *
2263
+ * @param roundAllocationBps - BPS from future_round_allocation to use (e.g., 1000 = 10%)
2264
+ * @param fundingGoal - USDC funding goal for this round
2265
+ * @param tiers - Tier configuration (1-10 tiers)
2266
+ * @param milestones - Milestone configuration (2-10 milestones, sum to 100%)
2267
+ * @param previousFundingRoundPda - Previous round PDA (required for R3+)
2268
+ */
2269
+ export async function openFundingRound(
2270
+ program: AnyProgram,
2271
+ args: {
2272
+ projectId: BN;
2273
+ /** BPS from future_round_allocation to use (e.g., 1000 = 10%) */
2274
+ roundAllocationBps: number;
2275
+ /** USDC funding goal for this round */
2276
+ fundingGoal: BN;
2277
+ /** Tier configuration (1-10 tiers) */
2278
+ tiers: RoundTierConfigInput[];
2279
+ /** Milestone configuration (2-10 milestones) */
2280
+ milestones: RoundMilestoneConfigInput[];
2281
+ /** USDC mint address */
2282
+ usdcMint: PublicKey;
2283
+ /** Previous funding round PDA (required for R3+, optional for R2) */
2284
+ previousFundingRoundPda?: PublicKey | null;
2285
+ },
2286
+ founder: PublicKey
2287
+ ): Promise<string> {
2288
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2289
+ const tokenomicsPda = getTokenomicsPDA(projectPda, program.programId);
2290
+
2291
+ // Fetch project to get round_count
2292
+ const project = await getAccountNamespace(program).project.fetch(projectPda);
2293
+ const newRoundNumber = (project.roundCount || 1) + 1;
2294
+
2295
+ // Derive new round PDAs
2296
+ const fundingRoundPda = getFundingRoundPDA(projectPda, newRoundNumber, program.programId);
2297
+ const roundEscrowPda = getRoundEscrowPDA(projectPda, newRoundNumber, program.programId);
2298
+ const [roundEscrowAuthorityPda] = getRoundEscrowAuthorityPDA(projectPda, newRoundNumber, program.programId);
2299
+
2300
+ // Build milestone PDAs as remaining accounts
2301
+ const remainingAccounts = args.milestones.map((_, i) => {
2302
+ const milestonePda = getRoundMilestonePDA(projectPda, newRoundNumber, i, program.programId);
2303
+ return {
2304
+ pubkey: milestonePda,
2305
+ isSigner: false,
2306
+ isWritable: true,
2307
+ };
2308
+ });
2309
+
2310
+ // Transform milestones for instruction
2311
+ const milestonesParam = args.milestones.map((m) => ({
2312
+ percentage: m.percentage,
2313
+ description: m.description,
2314
+ vestingDurationMonths: m.vestingDurationMonths ?? null,
2315
+ cliffMonths: m.cliffMonths ?? null,
2316
+ instantReleaseBps: m.instantReleaseBps ?? null,
2317
+ }));
2318
+
2319
+ return getMethods(program)
2320
+ .openFundingRound({
2321
+ roundAllocationBps: args.roundAllocationBps,
2322
+ fundingGoal: args.fundingGoal,
2323
+ tiers: args.tiers,
2324
+ milestones: milestonesParam,
2325
+ })
2326
+ .accountsPartial({
2327
+ project: projectPda,
2328
+ tokenomics: tokenomicsPda,
2329
+ fundingRound: fundingRoundPda,
2330
+ roundEscrow: roundEscrowPda,
2331
+ roundEscrowAuthority: roundEscrowAuthorityPda,
2332
+ usdcMint: args.usdcMint,
2333
+ previousFundingRound: args.previousFundingRoundPda ?? null,
2334
+ founder,
2335
+ tokenProgram: TOKEN_PROGRAM_ID,
2336
+ systemProgram: SystemProgram.programId,
2337
+ rent: SYSVAR_RENT_PUBKEY,
2338
+ })
2339
+ .remainingAccounts(remainingAccounts)
2340
+ .rpc();
2341
+ }
2342
+
2343
+ /**
2344
+ * Invest in a funding round (R2, R3, R4...)
2345
+ *
2346
+ * Multi-Round Fundraising: Creates investment NFT and deposits USDC
2347
+ * to the round-specific escrow. Tokens come from FutureRoundVault.
2348
+ *
2349
+ * @param roundNumber - Round number to invest in (2, 3, 4...)
2350
+ * @param amount - USDC amount to invest
2351
+ * @param investorTokenAccount - Investor's USDC token account
2352
+ * @param investmentCount - Current investment count from FundingRound
2353
+ */
2354
+ export async function investInRound(
2355
+ program: AnyProgram,
2356
+ args: {
2357
+ projectId: BN;
2358
+ /** Round number to invest in */
2359
+ roundNumber: number;
2360
+ /** USDC amount to invest */
2361
+ amount: BN;
2362
+ /** Investor's USDC token account */
2363
+ investorTokenAccount: PublicKey;
2364
+ /** Current investment count from FundingRound */
2365
+ investmentCount: number;
2366
+ },
2367
+ investor: PublicKey
2368
+ ): Promise<string> {
2369
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2370
+ const fundingRoundPda = getFundingRoundPDA(projectPda, args.roundNumber, program.programId);
2371
+ const roundEscrowPda = getRoundEscrowPDA(projectPda, args.roundNumber, program.programId);
2372
+
2373
+ // Derive first round milestone PDA (for state transition when funded)
2374
+ const firstRoundMilestonePda = getRoundMilestonePDA(projectPda, args.roundNumber, 0, program.programId);
2375
+
2376
+ // Derive NFT mint PDA with round number
2377
+ const [nftMint] = getRoundNftMintPDA(args.projectId, args.roundNumber, investor, args.investmentCount, program.programId);
2378
+
2379
+ // Derive investment PDA with round number
2380
+ const investmentPda = getRoundInvestmentPDA(projectPda, args.roundNumber, nftMint, program.programId);
2381
+
2382
+ // Derive investor's NFT token account (ATA)
2383
+ const investorNftAccount = getAssociatedTokenAddressSync(nftMint, investor);
2384
+
2385
+ // Derive Metaplex metadata and master edition PDAs
2386
+ const metadataAccount = getMetadataPDA(nftMint);
2387
+ const masterEdition = getMasterEditionPDA(nftMint);
2388
+
2389
+ // Derive program authority PDA
2390
+ const [programAuthority] = getProgramAuthorityPDA(program.programId);
2391
+
2392
+ // Add compute budget for NFT minting
2393
+ return getMethods(program)
2394
+ .investInRound({ amount: args.amount })
2395
+ .accountsPartial({
2396
+ project: projectPda,
2397
+ fundingRound: fundingRoundPda,
2398
+ firstRoundMilestone: firstRoundMilestonePda,
2399
+ nftMint: nftMint,
2400
+ investment: investmentPda,
2401
+ investorNftAccount: investorNftAccount,
2402
+ metadataAccount: metadataAccount,
2403
+ masterEdition: masterEdition,
2404
+ roundEscrow: roundEscrowPda,
2405
+ investorTokenAccount: args.investorTokenAccount,
2406
+ programAuthority: programAuthority,
2407
+ investor,
2408
+ tokenProgram: TOKEN_PROGRAM_ID,
2409
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
2410
+ systemProgram: SystemProgram.programId,
2411
+ rent: SYSVAR_RENT_PUBKEY,
2412
+ tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
2413
+ sysvarInstructions: SYSVAR_INSTRUCTIONS_PUBKEY,
2414
+ })
2415
+ .preInstructions([
2416
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }),
2417
+ ])
2418
+ .rpc();
2419
+ }
2420
+
2421
+ /**
2422
+ * Cancel round investment within 24-hour cooling-off period
2423
+ *
2424
+ * Multi-Round Fundraising: Returns USDC from round escrow,
2425
+ * closes investment account.
2426
+ */
2427
+ export async function cancelRoundInvestment(
2428
+ program: AnyProgram,
2429
+ args: {
2430
+ projectId: BN;
2431
+ /** Round number of the investment */
2432
+ roundNumber: number;
2433
+ /** NFT mint of the investment */
2434
+ nftMint: PublicKey;
2435
+ /** Investor's NFT token account */
2436
+ investorNftAccount: PublicKey;
2437
+ /** Investor's USDC token account for refund */
2438
+ investorUsdcAccount: PublicKey;
2439
+ },
2440
+ investor: PublicKey
2441
+ ): Promise<string> {
2442
+ const nftMintPubkey = ensurePublicKey(args.nftMint);
2443
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2444
+ const fundingRoundPda = getFundingRoundPDA(projectPda, args.roundNumber, program.programId);
2445
+ const investmentPda = getRoundInvestmentPDA(projectPda, args.roundNumber, nftMintPubkey, program.programId);
2446
+ const roundEscrowPda = getRoundEscrowPDA(projectPda, args.roundNumber, program.programId);
2447
+ const [roundEscrowAuthorityPda] = getRoundEscrowAuthorityPDA(projectPda, args.roundNumber, program.programId);
2448
+
2449
+ return getMethods(program)
2450
+ .cancelRoundInvestment()
2451
+ .accountsPartial({
2452
+ investor,
2453
+ project: projectPda,
2454
+ fundingRound: fundingRoundPda,
2455
+ investment: investmentPda,
2456
+ nftMint: nftMintPubkey,
2457
+ investorNftAccount: args.investorNftAccount,
2458
+ roundEscrow: roundEscrowPda,
2459
+ roundEscrowAuthority: roundEscrowAuthorityPda,
2460
+ investorUsdcAccount: args.investorUsdcAccount,
2461
+ tokenProgram: TOKEN_PROGRAM_ID,
2462
+ })
2463
+ .rpc();
2464
+ }
2465
+
2466
+ /**
2467
+ * Submit round milestone for investor review
2468
+ *
2469
+ * Multi-Round Fundraising: Transitions milestone to UnderReview,
2470
+ * sets voting deadline.
2471
+ */
2472
+ export async function submitRoundMilestone(
2473
+ program: AnyProgram,
2474
+ args: {
2475
+ projectId: BN;
2476
+ /** Round number */
2477
+ roundNumber: number;
2478
+ /** Milestone index to submit */
2479
+ milestoneIndex: number;
2480
+ },
2481
+ founder: PublicKey
2482
+ ): Promise<string> {
2483
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2484
+ const fundingRoundPda = getFundingRoundPDA(projectPda, args.roundNumber, program.programId);
2485
+ const milestonePda = getRoundMilestonePDA(projectPda, args.roundNumber, args.milestoneIndex, program.programId);
2486
+
2487
+ return getMethods(program)
2488
+ .submitRoundMilestone()
2489
+ .accountsPartial({
2490
+ founder,
2491
+ project: projectPda,
2492
+ fundingRound: fundingRoundPda,
2493
+ milestone: milestonePda,
2494
+ clock: SYSVAR_CLOCK_PUBKEY,
2495
+ })
2496
+ .rpc();
2497
+ }
2498
+
2499
+ /**
2500
+ * Vote on a round milestone (unified voting - ANY investor can vote)
2501
+ *
2502
+ * Multi-Round Fundraising: R1, R2, R3... investors can all vote
2503
+ * on any round's milestones. Investment can be from any round.
2504
+ *
2505
+ * @param roundNumber - Round number of the milestone being voted on
2506
+ * @param milestoneIndex - Milestone index to vote on
2507
+ * @param nftMint - Voter's NFT mint (can be from any round)
2508
+ * @param choice - Vote choice (good or bad)
2509
+ * @param investmentRoundNumber - Round number of the voter's investment
2510
+ */
2511
+ export async function voteOnRoundMilestone(
2512
+ program: AnyProgram,
2513
+ args: {
2514
+ projectId: BN;
2515
+ /** Round number of the milestone being voted on */
2516
+ roundNumber: number;
2517
+ /** Milestone index to vote on */
2518
+ milestoneIndex: number;
2519
+ /** Voter's NFT mint (can be from any round) */
2520
+ nftMint: PublicKey | string;
2521
+ /** Vote choice */
2522
+ choice: { good: object } | { bad: object };
2523
+ /** Round number of the voter's investment (for PDA derivation) */
2524
+ investmentRoundNumber: number;
2525
+ },
2526
+ voter: PublicKey
2527
+ ): Promise<string> {
2528
+ const nftMintPubkey = ensurePublicKey(args.nftMint);
2529
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2530
+ const fundingRoundPda = getFundingRoundPDA(projectPda, args.roundNumber, program.programId);
2531
+ const milestonePda = getRoundMilestonePDA(projectPda, args.roundNumber, args.milestoneIndex, program.programId);
2532
+
2533
+ // Investment can be from any round (unified voting)
2534
+ let investmentPda: PublicKey;
2535
+ if (args.investmentRoundNumber === 1) {
2536
+ // R1 investments use standard PDA without round number
2537
+ investmentPda = getInvestmentPDA(projectPda, nftMintPubkey, program.programId);
2538
+ } else {
2539
+ // R2+ investments include round number
2540
+ investmentPda = getRoundInvestmentPDA(projectPda, args.investmentRoundNumber, nftMintPubkey, program.programId);
2541
+ }
2542
+
2543
+ // Fetch milestone to get current voting_round for vote PDA derivation
2544
+ const milestone = await getAccountNamespace(program).milestone.fetch(milestonePda);
2545
+ const votingRound = milestone.votingRound ?? 0;
2546
+ const votePda = getVotePDA(milestonePda, voter, votingRound, program.programId);
2547
+
2548
+ // Get voter's NFT token account (ATA)
2549
+ const voterNftAccount = getAssociatedTokenAddressSync(
2550
+ nftMintPubkey,
2551
+ voter,
2552
+ false,
2553
+ TOKEN_PROGRAM_ID
2554
+ );
2555
+
2556
+ return getMethods(program)
2557
+ .voteOnRoundMilestone({ choice: args.choice })
2558
+ .accountsPartial({
2559
+ vote: votePda,
2560
+ project: projectPda,
2561
+ fundingRound: fundingRoundPda,
2562
+ milestone: milestonePda,
2563
+ investment: investmentPda,
2564
+ nftMint: nftMintPubkey,
2565
+ voterNftAccount,
2566
+ voter,
2567
+ systemProgram: SystemProgram.programId,
2568
+ })
2569
+ .rpc();
2570
+ }
2571
+
2572
+ /**
2573
+ * Finalize voting on a round milestone
2574
+ *
2575
+ * Multi-Round Fundraising: Processes vote results, transitions
2576
+ * milestone to Passed or Failed state.
2577
+ */
2578
+ export async function finalizeRoundVoting(
2579
+ program: AnyProgram,
2580
+ args: {
2581
+ projectId: BN;
2582
+ /** Round number */
2583
+ roundNumber: number;
2584
+ /** Milestone index to finalize */
2585
+ milestoneIndex: number;
2586
+ }
2587
+ ): Promise<string> {
2588
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2589
+ const fundingRoundPda = getFundingRoundPDA(projectPda, args.roundNumber, program.programId);
2590
+ const milestonePda = getRoundMilestonePDA(projectPda, args.roundNumber, args.milestoneIndex, program.programId);
2591
+
2592
+ return getMethods(program)
2593
+ .finalizeRoundVoting()
2594
+ .accountsPartial({
2595
+ project: projectPda,
2596
+ fundingRound: fundingRoundPda,
2597
+ milestone: milestonePda,
2598
+ clock: SYSVAR_CLOCK_PUBKEY,
2599
+ })
2600
+ .rpc();
2601
+ }
2602
+
2603
+ /**
2604
+ * Claim milestone funds from a round (founder)
2605
+ *
2606
+ * Multi-Round Fundraising: Transfers USDC from round escrow
2607
+ * to founder's account.
2608
+ *
2609
+ * @param nextMilestoneDeadline - Deadline for next milestone (required for non-final)
2610
+ * Set to BN(0) for final milestone claims
2611
+ */
2612
+ export async function claimRoundMilestoneFunds(
2613
+ program: AnyProgram,
2614
+ args: {
2615
+ projectId: BN;
2616
+ /** Round number */
2617
+ roundNumber: number;
2618
+ /** Milestone index to claim */
2619
+ milestoneIndex: number;
2620
+ /** Founder's USDC token account */
2621
+ founderUsdcAccount: PublicKey;
2622
+ /** Deadline for next milestone - required for non-final milestones, use BN(0) for final */
2623
+ nextMilestoneDeadline: BN;
2624
+ },
2625
+ founder: PublicKey
2626
+ ): Promise<string> {
2627
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2628
+ const fundingRoundPda = getFundingRoundPDA(projectPda, args.roundNumber, program.programId);
2629
+ const milestonePda = getRoundMilestonePDA(projectPda, args.roundNumber, args.milestoneIndex, program.programId);
2630
+ const roundEscrowPda = getRoundEscrowPDA(projectPda, args.roundNumber, program.programId);
2631
+ const [roundEscrowAuthorityPda] = getRoundEscrowAuthorityPDA(projectPda, args.roundNumber, program.programId);
2632
+
2633
+ // For non-final milestones, derive next milestone PDA
2634
+ const nextMilestonePda = args.nextMilestoneDeadline.gt(new BN(0))
2635
+ ? getRoundMilestonePDA(projectPda, args.roundNumber, args.milestoneIndex + 1, program.programId)
2636
+ : null;
2637
+
2638
+ return getMethods(program)
2639
+ .claimRoundMilestoneFunds({ nextMilestoneDeadline: args.nextMilestoneDeadline })
2640
+ .accountsPartial({
2641
+ project: projectPda,
2642
+ fundingRound: fundingRoundPda,
2643
+ milestone: milestonePda,
2644
+ founder,
2645
+ roundEscrow: roundEscrowPda,
2646
+ roundEscrowAuthority: roundEscrowAuthorityPda,
2647
+ founderUsdcAccount: args.founderUsdcAccount,
2648
+ nextMilestone: nextMilestonePda,
2649
+ systemProgram: SystemProgram.programId,
2650
+ tokenProgram: TOKEN_PROGRAM_ID,
2651
+ })
2652
+ .rpc();
2653
+ }
2654
+
2655
+ /**
2656
+ * Claim instant tokens for a round milestone (investor)
2657
+ *
2658
+ * Multi-Round Fundraising: R2+ investors claim instant portion
2659
+ * when milestone passes. Tokens come from FutureRoundVault.
2660
+ */
2661
+ export async function claimRoundInstantTokens(
2662
+ program: AnyProgram,
2663
+ args: {
2664
+ projectId: BN;
2665
+ /** Round number of the investment */
2666
+ roundNumber: number;
2667
+ /** Milestone index to claim tokens from */
2668
+ milestoneIndex: number;
2669
+ /** NFT mint proving investment ownership */
2670
+ nftMint: PublicKey;
2671
+ /** Investor's token account to receive claimed tokens */
2672
+ investorTokenAccount: PublicKey;
2673
+ },
2674
+ investor: PublicKey
2675
+ ): Promise<string> {
2676
+ const nftMintPubkey = ensurePublicKey(args.nftMint);
2677
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2678
+ const fundingRoundPda = getFundingRoundPDA(projectPda, args.roundNumber, program.programId);
2679
+ const milestonePda = getRoundMilestonePDA(projectPda, args.roundNumber, args.milestoneIndex, program.programId);
2680
+ const investmentPda = getRoundInvestmentPDA(projectPda, args.roundNumber, nftMintPubkey, program.programId);
2681
+ const tokenVaultPda = getTokenVaultPDA(projectPda, program.programId);
2682
+ const futureRoundVaultPda = getFutureRoundVaultPDA(projectPda, program.programId);
2683
+ const futureRoundTokenVaultPda = getFutureRoundTokenVaultPDA(projectPda, program.programId);
2684
+ const vaultAuthorityPda = getVaultAuthorityPDA(projectPda, program.programId);
2685
+
2686
+ // Vesting PDA with round number
2687
+ const vestingPda = getRoundInvestorMilestoneVestingPDA(
2688
+ projectPda,
2689
+ args.roundNumber,
2690
+ args.milestoneIndex,
2691
+ investmentPda,
2692
+ program.programId
2693
+ );
2694
+
2695
+ // Get investor's NFT token account (ATA)
2696
+ const investorNftAccount = getAssociatedTokenAddressSync(
2697
+ nftMintPubkey,
2698
+ investor,
2699
+ false,
2700
+ TOKEN_PROGRAM_ID
2701
+ );
2702
+
2703
+ return getMethods(program)
2704
+ .claimRoundInstantTokens({ milestoneIndex: args.milestoneIndex })
2705
+ .accountsPartial({
2706
+ investor,
2707
+ project: projectPda,
2708
+ fundingRound: fundingRoundPda,
2709
+ milestone: milestonePda,
2710
+ investment: investmentPda,
2711
+ tokenVault: tokenVaultPda,
2712
+ futureRoundVault: futureRoundVaultPda,
2713
+ vesting: vestingPda,
2714
+ nftMint: nftMintPubkey,
2715
+ investorNftAccount,
2716
+ futureRoundTokenVault: futureRoundTokenVaultPda,
2717
+ investorTokenAccount: args.investorTokenAccount,
2718
+ vaultAuthority: vaultAuthorityPda,
2719
+ tokenProgram: TOKEN_PROGRAM_ID,
2720
+ systemProgram: SystemProgram.programId,
2721
+ })
2722
+ .rpc();
2723
+ }
2724
+
2725
+ /**
2726
+ * Claim vested tokens for a round milestone (investor)
2727
+ *
2728
+ * Multi-Round Fundraising: R2+ investors claim vested portion
2729
+ * after cliff period. Tokens come from FutureRoundVault.
2730
+ */
2731
+ export async function claimRoundVestedTokens(
2732
+ program: AnyProgram,
2733
+ args: {
2734
+ projectId: BN;
2735
+ /** Round number of the investment */
2736
+ roundNumber: number;
2737
+ /** Milestone index to claim tokens from */
2738
+ milestoneIndex: number;
2739
+ /** NFT mint proving investment ownership */
2740
+ nftMint: PublicKey;
2741
+ /** Investor's token account to receive claimed tokens */
2742
+ investorTokenAccount: PublicKey;
2743
+ },
2744
+ investor: PublicKey
2745
+ ): Promise<string> {
2746
+ const nftMintPubkey = ensurePublicKey(args.nftMint);
2747
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2748
+ const fundingRoundPda = getFundingRoundPDA(projectPda, args.roundNumber, program.programId);
2749
+ const milestonePda = getRoundMilestonePDA(projectPda, args.roundNumber, args.milestoneIndex, program.programId);
2750
+ const investmentPda = getRoundInvestmentPDA(projectPda, args.roundNumber, nftMintPubkey, program.programId);
2751
+ const tokenVaultPda = getTokenVaultPDA(projectPda, program.programId);
2752
+ const futureRoundVaultPda = getFutureRoundVaultPDA(projectPda, program.programId);
2753
+ const futureRoundTokenVaultPda = getFutureRoundTokenVaultPDA(projectPda, program.programId);
2754
+ const vaultAuthorityPda = getVaultAuthorityPDA(projectPda, program.programId);
2755
+
2756
+ // Vesting PDA with round number
2757
+ const vestingPda = getRoundInvestorMilestoneVestingPDA(
2758
+ projectPda,
2759
+ args.roundNumber,
2760
+ args.milestoneIndex,
2761
+ investmentPda,
2762
+ program.programId
2763
+ );
2764
+
2765
+ // Get investor's NFT token account (ATA)
2766
+ const investorNftAccount = getAssociatedTokenAddressSync(
2767
+ nftMintPubkey,
2768
+ investor,
2769
+ false,
2770
+ TOKEN_PROGRAM_ID
2771
+ );
2772
+
2773
+ return getMethods(program)
2774
+ .claimRoundVestedTokens({ milestoneIndex: args.milestoneIndex })
2775
+ .accountsPartial({
2776
+ investor,
2777
+ project: projectPda,
2778
+ fundingRound: fundingRoundPda,
2779
+ milestone: milestonePda,
2780
+ investment: investmentPda,
2781
+ tokenVault: tokenVaultPda,
2782
+ futureRoundVault: futureRoundVaultPda,
2783
+ vesting: vestingPda,
2784
+ nftMint: nftMintPubkey,
2785
+ investorNftAccount,
2786
+ futureRoundTokenVault: futureRoundTokenVaultPda,
2787
+ investorTokenAccount: args.investorTokenAccount,
2788
+ vaultAuthority: vaultAuthorityPda,
2789
+ tokenProgram: TOKEN_PROGRAM_ID,
2790
+ })
2791
+ .rpc();
2792
+ }
2793
+
2794
+ /**
2795
+ * Claim early tokens for a round investment (5% after 24h)
2796
+ *
2797
+ * Multi-Round Fundraising: R2+ investors claim 5% of token allocation
2798
+ * 24 hours after investing. Tokens come from FutureRoundVault.
2799
+ */
2800
+ export async function claimRoundEarlyTokens(
2801
+ program: AnyProgram,
2802
+ args: {
2803
+ projectId: BN;
2804
+ /** Round number of the investment */
2805
+ roundNumber: number;
2806
+ /** NFT mint proving investment ownership */
2807
+ nftMint: PublicKey;
2808
+ /** Investor's token account to receive claimed tokens */
2809
+ investorTokenAccount: PublicKey;
2810
+ },
2811
+ investor: PublicKey
2812
+ ): Promise<string> {
2813
+ const nftMintPubkey = ensurePublicKey(args.nftMint);
2814
+ const projectPda = getProjectPDA(args.projectId, program.programId);
2815
+ const fundingRoundPda = getFundingRoundPDA(projectPda, args.roundNumber, program.programId);
2816
+ const investmentPda = getRoundInvestmentPDA(projectPda, args.roundNumber, nftMintPubkey, program.programId);
2817
+ const tokenVaultPda = getTokenVaultPDA(projectPda, program.programId);
2818
+ const futureRoundVaultPda = getFutureRoundVaultPDA(projectPda, program.programId);
2819
+ const futureRoundTokenVaultPda = getFutureRoundTokenVaultPDA(projectPda, program.programId);
2820
+ const vaultAuthorityPda = getVaultAuthorityPDA(projectPda, program.programId);
2821
+
2822
+ // Get investor's NFT token account (ATA)
2823
+ const investorNftAccount = getAssociatedTokenAddressSync(
2824
+ nftMintPubkey,
2825
+ investor,
2826
+ false,
2827
+ TOKEN_PROGRAM_ID
2828
+ );
2829
+
2830
+ return getMethods(program)
2831
+ .claimRoundEarlyTokens()
2832
+ .accountsPartial({
2833
+ investor,
2834
+ project: projectPda,
2835
+ fundingRound: fundingRoundPda,
2836
+ investment: investmentPda,
2837
+ tokenVault: tokenVaultPda,
2838
+ futureRoundVault: futureRoundVaultPda,
2839
+ nftMint: nftMintPubkey,
2840
+ investorNftAccount,
2841
+ futureRoundTokenVault: futureRoundTokenVaultPda,
2842
+ investorTokenAccount: args.investorTokenAccount,
2843
+ vaultAuthority: vaultAuthorityPda,
2844
+ tokenProgram: TOKEN_PROGRAM_ID,
2845
+ })
2846
+ .rpc();
2847
+ }