@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.
- package/README.md +11 -9
- package/dist/accounts/index.cjs +531 -3
- package/dist/accounts/index.cjs.map +1 -1
- package/dist/accounts/index.d.cts +307 -2
- package/dist/accounts/index.d.ts +307 -2
- package/dist/accounts/index.js +503 -4
- package/dist/accounts/index.js.map +1 -1
- package/dist/constants/index.cjs +41 -3
- package/dist/constants/index.cjs.map +1 -1
- package/dist/constants/index.d.cts +38 -3
- package/dist/constants/index.d.ts +38 -3
- package/dist/constants/index.js +40 -4
- package/dist/constants/index.js.map +1 -1
- package/dist/index.cjs +2297 -361
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +566 -7
- package/dist/index.d.ts +566 -7
- package/dist/index.js +2279 -379
- package/dist/index.js.map +1 -1
- package/dist/instructions/index.cjs +783 -40
- package/dist/instructions/index.cjs.map +1 -1
- package/dist/instructions/index.d.cts +492 -6
- package/dist/instructions/index.d.ts +492 -6
- package/dist/instructions/index.js +762 -42
- package/dist/instructions/index.js.map +1 -1
- package/dist/pdas/index.cjs +163 -1
- package/dist/pdas/index.cjs.map +1 -1
- package/dist/pdas/index.d.cts +131 -1
- package/dist/pdas/index.d.ts +131 -1
- package/dist/pdas/index.js +151 -2
- package/dist/pdas/index.js.map +1 -1
- package/dist/types/index.cjs +9 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +586 -3
- package/dist/types/index.d.ts +586 -3
- package/dist/types/index.js +9 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +5 -3
- package/src/__tests__/dynamic-tokenomics.test.ts +358 -0
- package/src/accounts/index.ts +852 -1
- package/src/client.ts +1130 -1
- package/src/constants/index.ts +48 -2
- package/src/index.ts +58 -0
- package/src/instructions/index.ts +1383 -40
- package/src/pdas/index.ts +346 -0
- package/src/types/index.ts +698 -2
- 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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
/**
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
+
}
|