@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
package/src/client.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { Connection, PublicKey, Keypair } from '@solana/web3.js';
|
|
|
12
12
|
import * as pdas from './pdas/index.js';
|
|
13
13
|
import * as accounts from './accounts/index.js';
|
|
14
14
|
import * as instructions from './instructions/index.js';
|
|
15
|
-
import type { TierConfig } from './types/index.js';
|
|
15
|
+
import type { TierConfig, SubAllocation } from './types/index.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Wallet interface required by the client
|
|
@@ -158,6 +158,31 @@ export class RaiseClient {
|
|
|
158
158
|
return pdas.getAdminConfigPDA(this.programId);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
getAllocationProposalPDA(projectPda: PublicKey, proposalIndex: number): PublicKey {
|
|
162
|
+
return pdas.getAllocationProposalPDA(projectPda, proposalIndex, this.programId);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getAllocationVotePDA(proposalPda: PublicKey, nftMint: PublicKey): PublicKey {
|
|
166
|
+
return pdas.getAllocationVotePDA(proposalPda, nftMint, this.programId);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getSubAllocationVestingPDA(projectPda: PublicKey, subAllocationId: number): PublicKey {
|
|
170
|
+
return pdas.getSubAllocationVestingPDA(projectPda, subAllocationId, this.programId);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
getTokenomicsPDA(projectPda: PublicKey): PublicKey {
|
|
174
|
+
return pdas.getTokenomicsPDA(projectPda, this.programId);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Per-Milestone Vesting PDAs
|
|
178
|
+
getInvestorMilestoneVestingPDA(projectPda: PublicKey, milestoneIndex: number, investmentPda: PublicKey): PublicKey {
|
|
179
|
+
return pdas.getInvestorMilestoneVestingPDA(projectPda, milestoneIndex, investmentPda, this.programId);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
getFounderMilestoneVestingPDA(projectPda: PublicKey, milestoneIndex: number): PublicKey {
|
|
183
|
+
return pdas.getFounderMilestoneVestingPDA(projectPda, milestoneIndex, this.programId);
|
|
184
|
+
}
|
|
185
|
+
|
|
161
186
|
// ===========================================================================
|
|
162
187
|
// Account Fetchers
|
|
163
188
|
// ===========================================================================
|
|
@@ -202,6 +227,38 @@ export class RaiseClient {
|
|
|
202
227
|
return accounts.fetchAdminConfig(this.program);
|
|
203
228
|
}
|
|
204
229
|
|
|
230
|
+
async fetchTokenVault(projectId: BN) {
|
|
231
|
+
return accounts.fetchTokenVault(this.program, projectId);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async fetchTokenomics(projectId: BN) {
|
|
235
|
+
return accounts.fetchTokenomics(this.program, projectId);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async fetchAllocationProposal(projectId: BN, proposalIndex: number) {
|
|
239
|
+
return accounts.fetchAllocationProposal(this.program, projectId, proposalIndex);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async fetchAllAllocationProposals(projectId: BN) {
|
|
243
|
+
return accounts.fetchAllAllocationProposals(this.program, projectId);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async fetchAllocationVote(projectId: BN, proposalIndex: number, nftMint: PublicKey) {
|
|
247
|
+
return accounts.fetchAllocationVote(this.program, projectId, proposalIndex, nftMint);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async fetchSubAllocationVesting(projectId: BN, subAllocationId: number) {
|
|
251
|
+
return accounts.fetchSubAllocationVesting(this.program, projectId, subAllocationId);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async fetchInvestorMilestoneVesting(projectId: BN, milestoneIndex: number, nftMint: PublicKey) {
|
|
255
|
+
return accounts.fetchInvestorMilestoneVesting(this.program, projectId, milestoneIndex, nftMint);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async fetchFounderMilestoneVesting(projectId: BN, milestoneIndex: number) {
|
|
259
|
+
return accounts.fetchFounderMilestoneVesting(this.program, projectId, milestoneIndex);
|
|
260
|
+
}
|
|
261
|
+
|
|
205
262
|
// ===========================================================================
|
|
206
263
|
// Admin Instructions
|
|
207
264
|
// ===========================================================================
|
|
@@ -242,6 +299,8 @@ export class RaiseClient {
|
|
|
242
299
|
tokenomics: instructions.TokenomicsInput;
|
|
243
300
|
/** Milestone 1 deadline - Unix timestamp (required) */
|
|
244
301
|
milestone1Deadline: BN;
|
|
302
|
+
/** Milestone Price Increases: Price multipliers in BPS (optional, defaults to all 10000) */
|
|
303
|
+
priceMultipliers?: number[];
|
|
245
304
|
}): Promise<string> {
|
|
246
305
|
return instructions.initializeProject(
|
|
247
306
|
this.program,
|
|
@@ -278,6 +337,9 @@ export class RaiseClient {
|
|
|
278
337
|
milestoneIndex: number;
|
|
279
338
|
percentage: number;
|
|
280
339
|
description: string;
|
|
340
|
+
vestingDurationMonths?: number | null;
|
|
341
|
+
cliffMonths?: number | null;
|
|
342
|
+
instantReleaseBps?: number | null;
|
|
281
343
|
}): Promise<string> {
|
|
282
344
|
return instructions.createMilestone(
|
|
283
345
|
this.program,
|
|
@@ -686,6 +748,170 @@ export class RaiseClient {
|
|
|
686
748
|
);
|
|
687
749
|
}
|
|
688
750
|
|
|
751
|
+
// ===========================================================================
|
|
752
|
+
// Early Token Release Instructions
|
|
753
|
+
// ===========================================================================
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Claim early tokens as an investor (5% of token allocation)
|
|
757
|
+
*
|
|
758
|
+
* Early Token Release: Investors can claim 5% of their token allocation
|
|
759
|
+
* 24 hours after investing. These tokens are fully liquid immediately.
|
|
760
|
+
*
|
|
761
|
+
* Prerequisites:
|
|
762
|
+
* - 24h cooling period must have passed since investment
|
|
763
|
+
* - Investment must not have already claimed early tokens
|
|
764
|
+
* - Investment must have active voting rights
|
|
765
|
+
*/
|
|
766
|
+
async claimEarlyTokens(args: {
|
|
767
|
+
projectId: BN;
|
|
768
|
+
nftMint: PublicKey;
|
|
769
|
+
investorTokenAccount: PublicKey;
|
|
770
|
+
}): Promise<string> {
|
|
771
|
+
return instructions.claimEarlyTokens(
|
|
772
|
+
this.program,
|
|
773
|
+
args,
|
|
774
|
+
this.walletPublicKey
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Claim early tokens as a founder (5% of founder allocation)
|
|
780
|
+
*
|
|
781
|
+
* Early Token Release: Founders can claim 5% of their token allocation
|
|
782
|
+
* when the project becomes Funded. These tokens are fully liquid immediately.
|
|
783
|
+
*
|
|
784
|
+
* Prerequisites:
|
|
785
|
+
* - Project must be in Funded, InProgress, or Completed state
|
|
786
|
+
* - Founder must not have already claimed early tokens
|
|
787
|
+
*/
|
|
788
|
+
async claimFounderEarlyTokens(args: {
|
|
789
|
+
projectId: BN;
|
|
790
|
+
founderTokenAccount: PublicKey;
|
|
791
|
+
}): Promise<string> {
|
|
792
|
+
return instructions.claimFounderEarlyTokens(
|
|
793
|
+
this.program,
|
|
794
|
+
args,
|
|
795
|
+
this.walletPublicKey
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Claim founder milestone-based tokens for a specific milestone
|
|
801
|
+
*
|
|
802
|
+
* Early Token Release: Founders can claim their milestone-based portion
|
|
803
|
+
* (default 47.5% of founder allocation) when milestones are unlocked.
|
|
804
|
+
*
|
|
805
|
+
* Prerequisites:
|
|
806
|
+
* - Milestone must be in Unlocked state
|
|
807
|
+
* - Founder must not have already claimed tokens for this milestone
|
|
808
|
+
*/
|
|
809
|
+
async claimFounderMilestoneTokens(args: {
|
|
810
|
+
projectId: BN;
|
|
811
|
+
milestoneIndex: number;
|
|
812
|
+
founderTokenAccount: PublicKey;
|
|
813
|
+
}): Promise<string> {
|
|
814
|
+
return instructions.claimFounderMilestoneTokens(
|
|
815
|
+
this.program,
|
|
816
|
+
args,
|
|
817
|
+
this.walletPublicKey
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// ===========================================================================
|
|
822
|
+
// Per-Milestone Vesting Instructions (add-per-milestone-vesting)
|
|
823
|
+
// ===========================================================================
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Claim instant tokens for a passed milestone (investor)
|
|
827
|
+
*
|
|
828
|
+
* Per-milestone vesting: Investors claim the instant portion (e.g., 5-50%)
|
|
829
|
+
* immediately when a milestone passes. Creates vesting PDA on first claim.
|
|
830
|
+
*
|
|
831
|
+
* @param milestoneIndex - The milestone index to claim tokens from
|
|
832
|
+
* @param nftMint - The NFT mint proving investment ownership
|
|
833
|
+
* @param investorTokenAccount - Investor's token account to receive claimed tokens
|
|
834
|
+
*/
|
|
835
|
+
async claimMilestoneInstantTokens(args: {
|
|
836
|
+
projectId: BN;
|
|
837
|
+
milestoneIndex: number;
|
|
838
|
+
nftMint: PublicKey;
|
|
839
|
+
investorTokenAccount: PublicKey;
|
|
840
|
+
}): Promise<string> {
|
|
841
|
+
return instructions.claimMilestoneInstantTokens(
|
|
842
|
+
this.program,
|
|
843
|
+
args,
|
|
844
|
+
this.walletPublicKey
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Claim vested tokens for a milestone after cliff period (investor)
|
|
850
|
+
*
|
|
851
|
+
* Per-milestone vesting: Investors claim vested tokens linearly after
|
|
852
|
+
* the cliff period passes. Must have already claimed instant tokens
|
|
853
|
+
* to initialize the vesting PDA.
|
|
854
|
+
*
|
|
855
|
+
* @param milestoneIndex - The milestone index to claim vested tokens from
|
|
856
|
+
* @param nftMint - The NFT mint proving investment ownership
|
|
857
|
+
* @param investorTokenAccount - Investor's token account to receive claimed tokens
|
|
858
|
+
*/
|
|
859
|
+
async claimMilestoneVestedTokens(args: {
|
|
860
|
+
projectId: BN;
|
|
861
|
+
milestoneIndex: number;
|
|
862
|
+
nftMint: PublicKey;
|
|
863
|
+
investorTokenAccount: PublicKey;
|
|
864
|
+
}): Promise<string> {
|
|
865
|
+
return instructions.claimMilestoneVestedTokens(
|
|
866
|
+
this.program,
|
|
867
|
+
args,
|
|
868
|
+
this.walletPublicKey
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Claim instant tokens for a passed milestone (founder)
|
|
874
|
+
*
|
|
875
|
+
* Per-milestone vesting: Founders claim the instant portion (e.g., 5-50%)
|
|
876
|
+
* of their milestone-based allocation when a milestone passes.
|
|
877
|
+
*
|
|
878
|
+
* @param milestoneIndex - The milestone index to claim tokens from
|
|
879
|
+
* @param founderTokenAccount - Founder's token account to receive claimed tokens
|
|
880
|
+
*/
|
|
881
|
+
async claimFounderMsInstantTokens(args: {
|
|
882
|
+
projectId: BN;
|
|
883
|
+
milestoneIndex: number;
|
|
884
|
+
founderTokenAccount: PublicKey;
|
|
885
|
+
}): Promise<string> {
|
|
886
|
+
return instructions.claimFounderMsInstantTokens(
|
|
887
|
+
this.program,
|
|
888
|
+
args,
|
|
889
|
+
this.walletPublicKey
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Claim vested tokens for a milestone after cliff period (founder)
|
|
895
|
+
*
|
|
896
|
+
* Per-milestone vesting: Founders claim vested tokens linearly after
|
|
897
|
+
* the cliff period passes. Must have already claimed instant tokens
|
|
898
|
+
* to initialize the vesting PDA.
|
|
899
|
+
*
|
|
900
|
+
* @param milestoneIndex - The milestone index to claim vested tokens from
|
|
901
|
+
* @param founderTokenAccount - Founder's token account to receive claimed tokens
|
|
902
|
+
*/
|
|
903
|
+
async claimFounderMsVestedTokens(args: {
|
|
904
|
+
projectId: BN;
|
|
905
|
+
milestoneIndex: number;
|
|
906
|
+
founderTokenAccount: PublicKey;
|
|
907
|
+
}): Promise<string> {
|
|
908
|
+
return instructions.claimFounderMsVestedTokens(
|
|
909
|
+
this.program,
|
|
910
|
+
args,
|
|
911
|
+
this.walletPublicKey
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
|
|
689
915
|
// ===========================================================================
|
|
690
916
|
// Abandonment Instructions
|
|
691
917
|
// ===========================================================================
|
|
@@ -712,4 +938,907 @@ export class RaiseClient {
|
|
|
712
938
|
this.walletPublicKey
|
|
713
939
|
);
|
|
714
940
|
}
|
|
941
|
+
|
|
942
|
+
// ===========================================================================
|
|
943
|
+
// Sub-Allocation Vesting Instructions
|
|
944
|
+
// ===========================================================================
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Initialize sub-allocation vesting after MAE (Market Access Event)
|
|
948
|
+
*
|
|
949
|
+
* Creates SubAllocationVesting PDA for a specific sub-allocation ID.
|
|
950
|
+
* Project must be in Completed state with MAE completed.
|
|
951
|
+
* Permissionless - anyone can pay to initialize.
|
|
952
|
+
*/
|
|
953
|
+
async initializeSubAllocationVesting(args: {
|
|
954
|
+
projectId: BN;
|
|
955
|
+
subAllocationId: number;
|
|
956
|
+
}): Promise<string> {
|
|
957
|
+
return instructions.initializeSubAllocationVesting(
|
|
958
|
+
this.program,
|
|
959
|
+
args,
|
|
960
|
+
this.walletPublicKey
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Claim vested tokens from a sub-allocation
|
|
966
|
+
*
|
|
967
|
+
* Only the designated recipient wallet can claim tokens.
|
|
968
|
+
* Project must be Completed and MAE must be completed.
|
|
969
|
+
*/
|
|
970
|
+
async claimSubAllocationTokens(args: {
|
|
971
|
+
projectId: BN;
|
|
972
|
+
subAllocationId: number;
|
|
973
|
+
recipientTokenAccount: PublicKey;
|
|
974
|
+
}): Promise<string> {
|
|
975
|
+
return instructions.claimSubAllocationTokens(
|
|
976
|
+
this.program,
|
|
977
|
+
args,
|
|
978
|
+
this.walletPublicKey
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// ===========================================================================
|
|
983
|
+
// Dynamic Tokenomics Instructions
|
|
984
|
+
// ===========================================================================
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Add a sub-allocation from the reserve pool (Draft state only)
|
|
988
|
+
*
|
|
989
|
+
* Allows founders to add named allocations (advisors, marketing, etc.)
|
|
990
|
+
* from their reserve pool before submitting for approval.
|
|
991
|
+
*/
|
|
992
|
+
async addSubAllocation(args: {
|
|
993
|
+
projectId: BN;
|
|
994
|
+
name: string;
|
|
995
|
+
bps: number;
|
|
996
|
+
recipient: PublicKey;
|
|
997
|
+
vestingMonths: number;
|
|
998
|
+
cliffMonths: number;
|
|
999
|
+
}): Promise<string> {
|
|
1000
|
+
return instructions.addSubAllocation(
|
|
1001
|
+
this.program,
|
|
1002
|
+
args,
|
|
1003
|
+
this.walletPublicKey
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Propose an allocation change via governance (post-Draft states)
|
|
1009
|
+
*
|
|
1010
|
+
* Creates a 7-day voting period for investors to approve/reject
|
|
1011
|
+
* a new sub-allocation from the reserve pool.
|
|
1012
|
+
*/
|
|
1013
|
+
async proposeAllocationChange(args: {
|
|
1014
|
+
projectId: BN;
|
|
1015
|
+
name: string;
|
|
1016
|
+
bps: number;
|
|
1017
|
+
recipient: PublicKey;
|
|
1018
|
+
vestingMonths: number;
|
|
1019
|
+
cliffMonths: number;
|
|
1020
|
+
}): Promise<string> {
|
|
1021
|
+
return instructions.proposeAllocationChange(
|
|
1022
|
+
this.program,
|
|
1023
|
+
args,
|
|
1024
|
+
this.walletPublicKey
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Vote on an allocation change proposal
|
|
1030
|
+
*
|
|
1031
|
+
* Investors can vote for/against using their Investment NFT.
|
|
1032
|
+
* Must meet 7-day hold period for voting eligibility.
|
|
1033
|
+
*/
|
|
1034
|
+
async voteAllocationChange(args: {
|
|
1035
|
+
projectId: BN;
|
|
1036
|
+
proposalIndex: number;
|
|
1037
|
+
nftMint: PublicKey;
|
|
1038
|
+
voteFor: boolean;
|
|
1039
|
+
}): Promise<string> {
|
|
1040
|
+
return instructions.voteAllocationChange(
|
|
1041
|
+
this.program,
|
|
1042
|
+
args,
|
|
1043
|
+
this.walletPublicKey
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Execute an approved allocation change proposal
|
|
1049
|
+
*
|
|
1050
|
+
* Permissionless - anyone can call after voting ends.
|
|
1051
|
+
* Proposal must have passed (>51% approval).
|
|
1052
|
+
*/
|
|
1053
|
+
async executeAllocationChange(args: {
|
|
1054
|
+
projectId: BN;
|
|
1055
|
+
proposalIndex: number;
|
|
1056
|
+
}): Promise<string> {
|
|
1057
|
+
return instructions.executeAllocationChange(
|
|
1058
|
+
this.program,
|
|
1059
|
+
args,
|
|
1060
|
+
this.walletPublicKey
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// ===========================================================================
|
|
1065
|
+
// Dynamic Tokenomics Helpers
|
|
1066
|
+
// ===========================================================================
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* Get remaining founder pool capacity for sub-allocations in basis points
|
|
1070
|
+
*
|
|
1071
|
+
* Calculates: founder_allocation_bps - sum(sub_allocations.bps)
|
|
1072
|
+
* Sub-allocations (treasury, advisors, marketing) draw from founder's allocation pool.
|
|
1073
|
+
*/
|
|
1074
|
+
async getRemainingFounderPool(projectId: BN): Promise<number> {
|
|
1075
|
+
const tokenomics = await this.fetchTokenomics(projectId);
|
|
1076
|
+
if (!tokenomics) {
|
|
1077
|
+
throw new Error('Tokenomics account not found');
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const founderBps = tokenomics.founderAllocationBps || 0;
|
|
1081
|
+
const usedBps = (tokenomics.subAllocations || [])
|
|
1082
|
+
.filter((sa: SubAllocation) => sa.bps > 0)
|
|
1083
|
+
.reduce((sum: number, sa: SubAllocation) => sum + sa.bps, 0);
|
|
1084
|
+
|
|
1085
|
+
return founderBps - usedBps;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Get all active (non-executed, non-expired) allocation proposals
|
|
1090
|
+
*/
|
|
1091
|
+
async getActiveProposals(projectId: BN) {
|
|
1092
|
+
const proposals = await this.fetchAllAllocationProposals(projectId);
|
|
1093
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1094
|
+
|
|
1095
|
+
return proposals.filter((p) => {
|
|
1096
|
+
const account = p.account;
|
|
1097
|
+
// Filter for non-executed proposals that haven't expired
|
|
1098
|
+
return !account.executed && account.votingEndsAt.toNumber() > now;
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* Get vesting status for a sub-allocation
|
|
1104
|
+
*
|
|
1105
|
+
* Returns claimable amount, total vested, and remaining locked tokens.
|
|
1106
|
+
*/
|
|
1107
|
+
async getSubAllocationVestingStatus(projectId: BN, subAllocationId: number) {
|
|
1108
|
+
const vesting = await this.fetchSubAllocationVesting(projectId, subAllocationId);
|
|
1109
|
+
if (!vesting) {
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1114
|
+
// Use the pre-computed timestamps from the on-chain account
|
|
1115
|
+
const cliffEnd = vesting.cliffEnd.toNumber();
|
|
1116
|
+
const vestingEnd = vesting.vestingEnd.toNumber();
|
|
1117
|
+
const totalAmount = vesting.totalAmount.toNumber();
|
|
1118
|
+
const claimedAmount = vesting.claimedAmount.toNumber();
|
|
1119
|
+
|
|
1120
|
+
// Before cliff - nothing vested
|
|
1121
|
+
if (now < cliffEnd) {
|
|
1122
|
+
return {
|
|
1123
|
+
totalAmount,
|
|
1124
|
+
vestedAmount: 0,
|
|
1125
|
+
claimedAmount,
|
|
1126
|
+
claimableAmount: 0,
|
|
1127
|
+
lockedAmount: totalAmount - claimedAmount,
|
|
1128
|
+
vestingProgress: 0,
|
|
1129
|
+
cliffPassed: false,
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// After full vesting period - everything vested
|
|
1134
|
+
if (now >= vestingEnd) {
|
|
1135
|
+
return {
|
|
1136
|
+
totalAmount,
|
|
1137
|
+
vestedAmount: totalAmount,
|
|
1138
|
+
claimedAmount,
|
|
1139
|
+
claimableAmount: totalAmount - claimedAmount,
|
|
1140
|
+
lockedAmount: 0,
|
|
1141
|
+
vestingProgress: 100,
|
|
1142
|
+
cliffPassed: true,
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// Linear vesting calculation
|
|
1147
|
+
const vestingDuration = vestingEnd - cliffEnd;
|
|
1148
|
+
const elapsed = now - cliffEnd;
|
|
1149
|
+
const vestedAmount = Math.floor((totalAmount * elapsed) / vestingDuration);
|
|
1150
|
+
|
|
1151
|
+
return {
|
|
1152
|
+
totalAmount,
|
|
1153
|
+
vestedAmount,
|
|
1154
|
+
claimedAmount,
|
|
1155
|
+
claimableAmount: vestedAmount - claimedAmount,
|
|
1156
|
+
lockedAmount: totalAmount - vestedAmount,
|
|
1157
|
+
vestingProgress: Math.floor((elapsed / vestingDuration) * 100),
|
|
1158
|
+
cliffPassed: true,
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// ===========================================================================
|
|
1163
|
+
// Milestone Price Increase Helpers
|
|
1164
|
+
// ===========================================================================
|
|
1165
|
+
|
|
1166
|
+
/** Base price multiplier constant (10000 BPS = 1.0x) */
|
|
1167
|
+
static readonly PRICE_MULTIPLIER_BASE = 10000;
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* Get the current price multiplier for a project
|
|
1171
|
+
*
|
|
1172
|
+
* Returns the multiplier in BPS (10000 = 1.0x, 15000 = 1.5x)
|
|
1173
|
+
*
|
|
1174
|
+
* Supports two modes:
|
|
1175
|
+
* - Dynamic mode (priceMultipliers[0] === 0): Uses currentPriceMultiplierBps
|
|
1176
|
+
* calculated dynamically via ZEMYTH formula at each milestone claim
|
|
1177
|
+
* - Static mode (legacy): Uses pre-configured priceMultipliers array lookup
|
|
1178
|
+
*/
|
|
1179
|
+
async getCurrentMultiplier(projectId: BN): Promise<number> {
|
|
1180
|
+
const project = await this.fetchProject(projectId);
|
|
1181
|
+
if (!project) {
|
|
1182
|
+
throw new Error('Project account not found');
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
const { priceMultipliers, milestonesPassed, milestoneCount, currentPriceMultiplierBps } = project;
|
|
1186
|
+
|
|
1187
|
+
// Check for dynamic mode sentinel (priceMultipliers[0] === 0)
|
|
1188
|
+
if (priceMultipliers[0] === 0) {
|
|
1189
|
+
// Dynamic mode: return the dynamically calculated multiplier
|
|
1190
|
+
return currentPriceMultiplierBps || RaiseClient.PRICE_MULTIPLIER_BASE;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Static mode (legacy): use array lookup
|
|
1194
|
+
// Clamp index to valid range (0 to milestoneCount - 1)
|
|
1195
|
+
const index = milestoneCount === 0
|
|
1196
|
+
? 0
|
|
1197
|
+
: Math.min(milestonesPassed, milestoneCount - 1);
|
|
1198
|
+
|
|
1199
|
+
return priceMultipliers[index] || RaiseClient.PRICE_MULTIPLIER_BASE;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Check if a project uses dynamic price multiplier mode
|
|
1204
|
+
*/
|
|
1205
|
+
async isDynamicMultiplierMode(projectId: BN): Promise<boolean> {
|
|
1206
|
+
const project = await this.fetchProject(projectId);
|
|
1207
|
+
if (!project) {
|
|
1208
|
+
throw new Error('Project account not found');
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
return project.priceMultipliers[0] === 0;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* Calculate effective tier prices with current multiplier
|
|
1216
|
+
*
|
|
1217
|
+
* Returns array of tiers with their effective prices after applying
|
|
1218
|
+
* the current milestone multiplier.
|
|
1219
|
+
*/
|
|
1220
|
+
async getEffectiveTierPrices(projectId: BN): Promise<Array<{
|
|
1221
|
+
tierIndex: number;
|
|
1222
|
+
baseAmount: BN;
|
|
1223
|
+
effectiveAmount: BN;
|
|
1224
|
+
multiplierBps: number;
|
|
1225
|
+
}>> {
|
|
1226
|
+
const project = await this.fetchProject(projectId);
|
|
1227
|
+
if (!project) {
|
|
1228
|
+
throw new Error('Project account not found');
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const multiplier = await this.getCurrentMultiplier(projectId);
|
|
1232
|
+
const tiers = project.tiers.slice(0, project.tierCount);
|
|
1233
|
+
|
|
1234
|
+
return tiers.map((tier: { amount: BN }, index: number) => ({
|
|
1235
|
+
tierIndex: index,
|
|
1236
|
+
baseAmount: tier.amount,
|
|
1237
|
+
effectiveAmount: tier.amount
|
|
1238
|
+
.mul(new BN(multiplier))
|
|
1239
|
+
.div(new BN(RaiseClient.PRICE_MULTIPLIER_BASE)),
|
|
1240
|
+
multiplierBps: multiplier,
|
|
1241
|
+
}));
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* Get price schedule showing multipliers after each milestone
|
|
1246
|
+
*
|
|
1247
|
+
* Returns array showing what the price multiplier will be
|
|
1248
|
+
* after each milestone passes.
|
|
1249
|
+
*
|
|
1250
|
+
* For dynamic mode projects (priceMultipliers[0] === 0), returns null
|
|
1251
|
+
* as multipliers are calculated dynamically based on deadline timing.
|
|
1252
|
+
*/
|
|
1253
|
+
async getPriceSchedule(projectId: BN): Promise<Array<{
|
|
1254
|
+
afterMilestone: number;
|
|
1255
|
+
multiplierBps: number;
|
|
1256
|
+
multiplierPercent: number;
|
|
1257
|
+
}> | null> {
|
|
1258
|
+
const project = await this.fetchProject(projectId);
|
|
1259
|
+
if (!project) {
|
|
1260
|
+
throw new Error('Project account not found');
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
const { priceMultipliers, milestoneCount } = project;
|
|
1264
|
+
|
|
1265
|
+
// Dynamic mode: no pre-set schedule
|
|
1266
|
+
if (priceMultipliers[0] === 0) {
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// Static mode: return pre-configured schedule
|
|
1271
|
+
return priceMultipliers
|
|
1272
|
+
.slice(0, milestoneCount)
|
|
1273
|
+
.map((multiplier: number, index: number) => ({
|
|
1274
|
+
afterMilestone: index,
|
|
1275
|
+
multiplierBps: multiplier,
|
|
1276
|
+
multiplierPercent: multiplier / 100, // 10000 -> 100%
|
|
1277
|
+
}));
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* Preview multiplier calculation for dynamic mode projects
|
|
1282
|
+
*
|
|
1283
|
+
* Uses the ZEMYTH formula to calculate what the multiplier would be
|
|
1284
|
+
* given a specific number of remaining milestones and days to deadline.
|
|
1285
|
+
*
|
|
1286
|
+
* Formula: totalMultiplier = 1 + (sqrt(n) / sqrt(15)) × (1 / ln(d/7 + e))
|
|
1287
|
+
*
|
|
1288
|
+
* @param remainingMilestones Number of milestones left (1-10)
|
|
1289
|
+
* @param daysToDeadline Days until next milestone deadline (1-365)
|
|
1290
|
+
* @returns Preview of the multiplier in BPS (10000 = 1.0x, max 20000 = 2.0x)
|
|
1291
|
+
*/
|
|
1292
|
+
previewDynamicMultiplier(remainingMilestones: number, daysToDeadline: number): number {
|
|
1293
|
+
// Edge case: 0 remaining milestones = base multiplier
|
|
1294
|
+
if (remainingMilestones === 0) {
|
|
1295
|
+
return RaiseClient.PRICE_MULTIPLIER_BASE;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Clamp days to reasonable range
|
|
1299
|
+
const days = Math.max(1, Math.min(365, daysToDeadline));
|
|
1300
|
+
|
|
1301
|
+
// Integer approximation of ZEMYTH formula
|
|
1302
|
+
// sqrt(n) / sqrt(15) ≈ (sqrt(n) * 1000) / 3873
|
|
1303
|
+
const sqrtN = Math.sqrt(remainingMilestones) * 1000;
|
|
1304
|
+
const milestoneFactor = sqrtN / 3.873;
|
|
1305
|
+
|
|
1306
|
+
// 1/ln(d/7 + e) approximation
|
|
1307
|
+
const timeFactor = this.timeFactorLookup(days);
|
|
1308
|
+
|
|
1309
|
+
// Combined factor
|
|
1310
|
+
const combined = (milestoneFactor * timeFactor) / 1000;
|
|
1311
|
+
const bonusBps = Math.min(combined, 10000);
|
|
1312
|
+
|
|
1313
|
+
// Base + bonus, clamped to [10000, 20000]
|
|
1314
|
+
return Math.min(20000, Math.max(10000, Math.round(10000 + bonusBps)));
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
/** Time factor lookup for dynamic multiplier preview (approximates 1/ln(d/7 + e) × 1000) */
|
|
1318
|
+
private timeFactorLookup(days: number): number {
|
|
1319
|
+
if (days <= 7) return 950 - (days * 25);
|
|
1320
|
+
if (days <= 14) return 775 - ((days - 7) * 20);
|
|
1321
|
+
if (days <= 30) return 635 - ((days - 14) * 8);
|
|
1322
|
+
if (days <= 60) return 507 - ((days - 30) * 4);
|
|
1323
|
+
if (days <= 90) return 387 - ((days - 60) * 3);
|
|
1324
|
+
if (days <= 180) return 297 - ((days - 90) * 1);
|
|
1325
|
+
return 250;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// ===========================================================================
|
|
1329
|
+
// Linear Progressive Pricing Helpers (add-linear-progressive-pricing)
|
|
1330
|
+
// ===========================================================================
|
|
1331
|
+
|
|
1332
|
+
/** Constants for progressive pricing */
|
|
1333
|
+
static readonly TIME_ALLOCATION_PERCENT = 50;
|
|
1334
|
+
static readonly MILESTONE_ALLOCATION_PERCENT = 50;
|
|
1335
|
+
/** DEV MODE: Time acceleration multiplier (10 real seconds = 1 simulated day) */
|
|
1336
|
+
static readonly PROGRESSIVE_TIME_MULTIPLIER = 8640;
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* Check if progressive pricing is active for a project
|
|
1340
|
+
*
|
|
1341
|
+
* Progressive pricing is active when milestonePeriodStartedAt > 0,
|
|
1342
|
+
* meaning the first milestone period has been initialized.
|
|
1343
|
+
*/
|
|
1344
|
+
isProgressivePricingActive(project: { milestonePeriodStartedAt: BN }): boolean {
|
|
1345
|
+
return project.milestonePeriodStartedAt.gt(new BN(0));
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/**
|
|
1349
|
+
* Calculate time progress for a milestone period
|
|
1350
|
+
*
|
|
1351
|
+
* @param project Project account with progressive pricing fields
|
|
1352
|
+
* @param currentTime Current unix timestamp (seconds)
|
|
1353
|
+
* @returns Progress as ratio 0-1 (capped at 1.0)
|
|
1354
|
+
*/
|
|
1355
|
+
getTimeProgress(
|
|
1356
|
+
project: {
|
|
1357
|
+
milestonePeriodStartedAt: BN;
|
|
1358
|
+
expectedMilestoneDeadlineTs: BN;
|
|
1359
|
+
},
|
|
1360
|
+
currentTime: number
|
|
1361
|
+
): number {
|
|
1362
|
+
const startedAt = project.milestonePeriodStartedAt.toNumber();
|
|
1363
|
+
const deadline = project.expectedMilestoneDeadlineTs.toNumber();
|
|
1364
|
+
|
|
1365
|
+
if (startedAt === 0 || deadline <= startedAt) {
|
|
1366
|
+
return 0;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// DEV MODE: Apply time acceleration (10 real seconds = 1 simulated day)
|
|
1370
|
+
const rawElapsed = Math.max(0, currentTime - startedAt);
|
|
1371
|
+
const elapsed = rawElapsed * RaiseClient.PROGRESSIVE_TIME_MULTIPLIER;
|
|
1372
|
+
const expectedDuration = Math.max(1, deadline - startedAt);
|
|
1373
|
+
|
|
1374
|
+
return Math.min(1, elapsed / expectedDuration);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* Calculate the effective multiplier including time-based component
|
|
1379
|
+
*
|
|
1380
|
+
* @param project Project account with progressive pricing fields
|
|
1381
|
+
* @param currentTime Current unix timestamp (seconds)
|
|
1382
|
+
* @returns Effective multiplier in BPS (10000 = 1.0x)
|
|
1383
|
+
*/
|
|
1384
|
+
calculateEffectiveMultiplier(
|
|
1385
|
+
project: {
|
|
1386
|
+
milestonePeriodStartedAt: BN;
|
|
1387
|
+
expectedMilestoneDeadlineTs: BN;
|
|
1388
|
+
milestoneBaseMultiplierBps: number;
|
|
1389
|
+
timeAllocationBps: number;
|
|
1390
|
+
currentPriceMultiplierBps: number;
|
|
1391
|
+
priceMultipliers: number[];
|
|
1392
|
+
},
|
|
1393
|
+
currentTime: number
|
|
1394
|
+
): number {
|
|
1395
|
+
// If progressive pricing not active, use legacy behavior
|
|
1396
|
+
if (!this.isProgressivePricingActive(project)) {
|
|
1397
|
+
// Legacy: use currentPriceMultiplierBps or static array
|
|
1398
|
+
if (project.priceMultipliers[0] === 0) {
|
|
1399
|
+
return project.currentPriceMultiplierBps || RaiseClient.PRICE_MULTIPLIER_BASE;
|
|
1400
|
+
}
|
|
1401
|
+
return project.currentPriceMultiplierBps || RaiseClient.PRICE_MULTIPLIER_BASE;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// Progressive pricing active: base + time increment
|
|
1405
|
+
const timeProgress = this.getTimeProgress(project, currentTime);
|
|
1406
|
+
const timeIncrement = Math.floor(project.timeAllocationBps * timeProgress);
|
|
1407
|
+
|
|
1408
|
+
return project.milestoneBaseMultiplierBps + timeIncrement;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/**
|
|
1412
|
+
* Preview what the milestone spike will be at a given claim time
|
|
1413
|
+
*
|
|
1414
|
+
* If founder claims early, unused time allocation accumulates to spike.
|
|
1415
|
+
*
|
|
1416
|
+
* @param project Project account with progressive pricing fields
|
|
1417
|
+
* @param claimTime Unix timestamp when claim will happen
|
|
1418
|
+
* @returns Object with spike details
|
|
1419
|
+
*/
|
|
1420
|
+
previewMilestoneSpike(
|
|
1421
|
+
project: {
|
|
1422
|
+
milestonePeriodStartedAt: BN;
|
|
1423
|
+
expectedMilestoneDeadlineTs: BN;
|
|
1424
|
+
milestoneBaseMultiplierBps: number;
|
|
1425
|
+
timeAllocationBps: number;
|
|
1426
|
+
milestoneAllocationBps: number;
|
|
1427
|
+
},
|
|
1428
|
+
claimTime: number
|
|
1429
|
+
): {
|
|
1430
|
+
timeProgress: number;
|
|
1431
|
+
consumedTimeBps: number;
|
|
1432
|
+
unusedTimeBps: number;
|
|
1433
|
+
milestoneSpikeBps: number;
|
|
1434
|
+
effectiveMultiplierBps: number;
|
|
1435
|
+
newBaseBps: number;
|
|
1436
|
+
} {
|
|
1437
|
+
const timeProgress = this.getTimeProgress(project, claimTime);
|
|
1438
|
+
|
|
1439
|
+
const consumedTimeBps = Math.floor(project.timeAllocationBps * timeProgress);
|
|
1440
|
+
const unusedTimeBps = project.timeAllocationBps - consumedTimeBps;
|
|
1441
|
+
const milestoneSpikeBps = project.milestoneAllocationBps + unusedTimeBps;
|
|
1442
|
+
|
|
1443
|
+
const effectiveMultiplierBps = project.milestoneBaseMultiplierBps + consumedTimeBps;
|
|
1444
|
+
const newBaseBps = effectiveMultiplierBps + milestoneSpikeBps;
|
|
1445
|
+
|
|
1446
|
+
return {
|
|
1447
|
+
timeProgress,
|
|
1448
|
+
consumedTimeBps,
|
|
1449
|
+
unusedTimeBps,
|
|
1450
|
+
milestoneSpikeBps,
|
|
1451
|
+
effectiveMultiplierBps,
|
|
1452
|
+
newBaseBps,
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/**
|
|
1457
|
+
* Get effective tier prices with time-based progressive pricing
|
|
1458
|
+
*
|
|
1459
|
+
* Unlike getEffectiveTierPrices which fetches the project, this takes
|
|
1460
|
+
* the project and current time to calculate prices including time component.
|
|
1461
|
+
*/
|
|
1462
|
+
getProgressiveTierPrices(
|
|
1463
|
+
project: {
|
|
1464
|
+
tiers: Array<{ amount: BN }>;
|
|
1465
|
+
tierCount: number;
|
|
1466
|
+
milestonePeriodStartedAt: BN;
|
|
1467
|
+
expectedMilestoneDeadlineTs: BN;
|
|
1468
|
+
milestoneBaseMultiplierBps: number;
|
|
1469
|
+
timeAllocationBps: number;
|
|
1470
|
+
currentPriceMultiplierBps: number;
|
|
1471
|
+
priceMultipliers: number[];
|
|
1472
|
+
},
|
|
1473
|
+
currentTime: number
|
|
1474
|
+
): Array<{
|
|
1475
|
+
tierIndex: number;
|
|
1476
|
+
baseAmount: BN;
|
|
1477
|
+
effectiveAmount: BN;
|
|
1478
|
+
multiplierBps: number;
|
|
1479
|
+
timeProgressPercent: number;
|
|
1480
|
+
}> {
|
|
1481
|
+
const multiplier = this.calculateEffectiveMultiplier(project, currentTime);
|
|
1482
|
+
const timeProgress = this.getTimeProgress(project, currentTime);
|
|
1483
|
+
const tiers = project.tiers.slice(0, project.tierCount);
|
|
1484
|
+
|
|
1485
|
+
return tiers.map((tier, index) => ({
|
|
1486
|
+
tierIndex: index,
|
|
1487
|
+
baseAmount: tier.amount,
|
|
1488
|
+
effectiveAmount: tier.amount
|
|
1489
|
+
.mul(new BN(multiplier))
|
|
1490
|
+
.div(new BN(RaiseClient.PRICE_MULTIPLIER_BASE)),
|
|
1491
|
+
multiplierBps: multiplier,
|
|
1492
|
+
timeProgressPercent: Math.round(timeProgress * 100),
|
|
1493
|
+
}));
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// ===========================================================================
|
|
1497
|
+
// Multi-Round Fundraising PDAs
|
|
1498
|
+
// ===========================================================================
|
|
1499
|
+
|
|
1500
|
+
getFundingRoundPDA(projectPda: PublicKey, roundNumber: number): PublicKey {
|
|
1501
|
+
return pdas.getFundingRoundPDA(projectPda, roundNumber, this.programId);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
getRoundEscrowPDA(projectPda: PublicKey, roundNumber: number): PublicKey {
|
|
1505
|
+
return pdas.getRoundEscrowPDA(projectPda, roundNumber, this.programId);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
getRoundMilestonePDA(projectPda: PublicKey, roundNumber: number, milestoneIndex: number): PublicKey {
|
|
1509
|
+
return pdas.getRoundMilestonePDA(projectPda, roundNumber, milestoneIndex, this.programId);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
getRoundInvestmentPDA(projectPda: PublicKey, roundNumber: number, nftMint: PublicKey): PublicKey {
|
|
1513
|
+
return pdas.getRoundInvestmentPDA(projectPda, roundNumber, nftMint, this.programId);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
getRoundInvestorMilestoneVestingPDA(
|
|
1517
|
+
projectPda: PublicKey,
|
|
1518
|
+
roundNumber: number,
|
|
1519
|
+
milestoneIndex: number,
|
|
1520
|
+
investmentPda: PublicKey
|
|
1521
|
+
): PublicKey {
|
|
1522
|
+
return pdas.getRoundInvestorMilestoneVestingPDA(
|
|
1523
|
+
projectPda,
|
|
1524
|
+
roundNumber,
|
|
1525
|
+
milestoneIndex,
|
|
1526
|
+
investmentPda,
|
|
1527
|
+
this.programId
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
getFutureRoundVaultPDA(projectPda: PublicKey): PublicKey {
|
|
1532
|
+
return pdas.getFutureRoundVaultPDA(projectPda, this.programId);
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
getFutureRoundTokenVaultPDA(projectPda: PublicKey): PublicKey {
|
|
1536
|
+
return pdas.getFutureRoundTokenVaultPDA(projectPda, this.programId);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// ===========================================================================
|
|
1540
|
+
// Multi-Round Fundraising Account Fetchers
|
|
1541
|
+
// ===========================================================================
|
|
1542
|
+
|
|
1543
|
+
async fetchFundingRound(projectId: BN, roundNumber: number) {
|
|
1544
|
+
return accounts.fetchFundingRound(this.program, projectId, roundNumber);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
async fetchAllFundingRounds(projectId: BN) {
|
|
1548
|
+
return accounts.fetchAllFundingRounds(this.program, projectId);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
async fetchRoundMilestone(projectId: BN, roundNumber: number, milestoneIndex: number) {
|
|
1552
|
+
return accounts.fetchRoundMilestone(this.program, projectId, roundNumber, milestoneIndex);
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
async fetchRoundInvestment(projectId: BN, roundNumber: number, nftMint: PublicKey) {
|
|
1556
|
+
return accounts.fetchRoundInvestment(this.program, projectId, roundNumber, nftMint);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
async fetchFutureRoundVault(projectId: BN) {
|
|
1560
|
+
return accounts.fetchFutureRoundVault(this.program, projectId);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// ===========================================================================
|
|
1564
|
+
// Multi-Round Fundraising Instructions
|
|
1565
|
+
// ===========================================================================
|
|
1566
|
+
|
|
1567
|
+
/**
|
|
1568
|
+
* Open a new funding round (R2, R3, R4...)
|
|
1569
|
+
*
|
|
1570
|
+
* Multi-Round Fundraising: Creates FundingRound PDA, round escrow,
|
|
1571
|
+
* and milestone PDAs for the new round. Tokens come from FutureRoundVault.
|
|
1572
|
+
*
|
|
1573
|
+
* Prerequisites:
|
|
1574
|
+
* - Project must be InProgress or Completed state
|
|
1575
|
+
* - At least 1 milestone must have passed (for InProgress projects)
|
|
1576
|
+
* - No other round currently Open
|
|
1577
|
+
* - Current round must be fully funded before opening next
|
|
1578
|
+
* - Must have future_round_allocation configured in tokenomics
|
|
1579
|
+
*/
|
|
1580
|
+
async openFundingRound(args: {
|
|
1581
|
+
projectId: BN;
|
|
1582
|
+
roundAllocationBps: number;
|
|
1583
|
+
fundingGoal: BN;
|
|
1584
|
+
tiers: Array<{ amount: BN; maxLots: number; tokenRatio: BN }>;
|
|
1585
|
+
milestones: Array<{
|
|
1586
|
+
percentage: number;
|
|
1587
|
+
description: string;
|
|
1588
|
+
vestingDurationMonths?: number | null;
|
|
1589
|
+
cliffMonths?: number | null;
|
|
1590
|
+
instantReleaseBps?: number | null;
|
|
1591
|
+
}>;
|
|
1592
|
+
usdcMint: PublicKey;
|
|
1593
|
+
previousFundingRoundPda?: PublicKey | null;
|
|
1594
|
+
}): Promise<string> {
|
|
1595
|
+
return instructions.openFundingRound(
|
|
1596
|
+
this.program,
|
|
1597
|
+
args,
|
|
1598
|
+
this.walletPublicKey
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
/**
|
|
1603
|
+
* Invest in a funding round (R2, R3, R4...)
|
|
1604
|
+
*
|
|
1605
|
+
* Multi-Round Fundraising: Creates investment NFT and deposits USDC
|
|
1606
|
+
* to the round-specific escrow. Tokens come from FutureRoundVault.
|
|
1607
|
+
*/
|
|
1608
|
+
async investInRound(args: {
|
|
1609
|
+
projectId: BN;
|
|
1610
|
+
roundNumber: number;
|
|
1611
|
+
amount: BN;
|
|
1612
|
+
investorTokenAccount: PublicKey;
|
|
1613
|
+
investmentCount: number;
|
|
1614
|
+
}): Promise<string> {
|
|
1615
|
+
return instructions.investInRound(
|
|
1616
|
+
this.program,
|
|
1617
|
+
args,
|
|
1618
|
+
this.walletPublicKey
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
/**
|
|
1623
|
+
* Cancel round investment within 24-hour cooling-off period
|
|
1624
|
+
*
|
|
1625
|
+
* Multi-Round Fundraising: Returns USDC from round escrow,
|
|
1626
|
+
* closes investment account.
|
|
1627
|
+
*/
|
|
1628
|
+
async cancelRoundInvestment(args: {
|
|
1629
|
+
projectId: BN;
|
|
1630
|
+
roundNumber: number;
|
|
1631
|
+
nftMint: PublicKey;
|
|
1632
|
+
investorNftAccount: PublicKey;
|
|
1633
|
+
investorUsdcAccount: PublicKey;
|
|
1634
|
+
}): Promise<string> {
|
|
1635
|
+
return instructions.cancelRoundInvestment(
|
|
1636
|
+
this.program,
|
|
1637
|
+
args,
|
|
1638
|
+
this.walletPublicKey
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/**
|
|
1643
|
+
* Submit round milestone for investor review
|
|
1644
|
+
*
|
|
1645
|
+
* Multi-Round Fundraising: Transitions milestone to UnderReview,
|
|
1646
|
+
* sets voting deadline.
|
|
1647
|
+
*/
|
|
1648
|
+
async submitRoundMilestone(args: {
|
|
1649
|
+
projectId: BN;
|
|
1650
|
+
roundNumber: number;
|
|
1651
|
+
milestoneIndex: number;
|
|
1652
|
+
}): Promise<string> {
|
|
1653
|
+
return instructions.submitRoundMilestone(
|
|
1654
|
+
this.program,
|
|
1655
|
+
args,
|
|
1656
|
+
this.walletPublicKey
|
|
1657
|
+
);
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
/**
|
|
1661
|
+
* Vote on a round milestone (unified voting - ANY investor can vote)
|
|
1662
|
+
*
|
|
1663
|
+
* Multi-Round Fundraising: R1, R2, R3... investors can all vote
|
|
1664
|
+
* on any round's milestones. Investment can be from any round.
|
|
1665
|
+
*/
|
|
1666
|
+
async voteOnRoundMilestone(args: {
|
|
1667
|
+
projectId: BN;
|
|
1668
|
+
roundNumber: number;
|
|
1669
|
+
milestoneIndex: number;
|
|
1670
|
+
nftMint: PublicKey | string;
|
|
1671
|
+
choice: { good: object } | { bad: object };
|
|
1672
|
+
investmentRoundNumber: number;
|
|
1673
|
+
}): Promise<string> {
|
|
1674
|
+
return instructions.voteOnRoundMilestone(
|
|
1675
|
+
this.program,
|
|
1676
|
+
args,
|
|
1677
|
+
this.walletPublicKey
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
/**
|
|
1682
|
+
* Finalize voting on a round milestone
|
|
1683
|
+
*
|
|
1684
|
+
* Multi-Round Fundraising: Processes vote results, transitions
|
|
1685
|
+
* milestone to Passed or Failed state.
|
|
1686
|
+
*/
|
|
1687
|
+
async finalizeRoundVoting(args: {
|
|
1688
|
+
projectId: BN;
|
|
1689
|
+
roundNumber: number;
|
|
1690
|
+
milestoneIndex: number;
|
|
1691
|
+
}): Promise<string> {
|
|
1692
|
+
return instructions.finalizeRoundVoting(
|
|
1693
|
+
this.program,
|
|
1694
|
+
args
|
|
1695
|
+
);
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
/**
|
|
1699
|
+
* Claim milestone funds from a round (founder)
|
|
1700
|
+
*
|
|
1701
|
+
* Multi-Round Fundraising: Transfers USDC from round escrow
|
|
1702
|
+
* to founder's account.
|
|
1703
|
+
*
|
|
1704
|
+
* @param nextMilestoneDeadline - Deadline for next milestone (required for non-final)
|
|
1705
|
+
* Set to BN(0) for final milestone claims
|
|
1706
|
+
*/
|
|
1707
|
+
async claimRoundMilestoneFunds(args: {
|
|
1708
|
+
projectId: BN;
|
|
1709
|
+
roundNumber: number;
|
|
1710
|
+
milestoneIndex: number;
|
|
1711
|
+
founderUsdcAccount: PublicKey;
|
|
1712
|
+
nextMilestoneDeadline: BN;
|
|
1713
|
+
}): Promise<string> {
|
|
1714
|
+
return instructions.claimRoundMilestoneFunds(
|
|
1715
|
+
this.program,
|
|
1716
|
+
args,
|
|
1717
|
+
this.walletPublicKey
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
/**
|
|
1722
|
+
* Claim instant tokens for a round milestone (investor)
|
|
1723
|
+
*
|
|
1724
|
+
* Multi-Round Fundraising: R2+ investors claim instant portion
|
|
1725
|
+
* when milestone passes. Tokens come from FutureRoundVault.
|
|
1726
|
+
*/
|
|
1727
|
+
async claimRoundInstantTokens(args: {
|
|
1728
|
+
projectId: BN;
|
|
1729
|
+
roundNumber: number;
|
|
1730
|
+
milestoneIndex: number;
|
|
1731
|
+
nftMint: PublicKey;
|
|
1732
|
+
investorTokenAccount: PublicKey;
|
|
1733
|
+
}): Promise<string> {
|
|
1734
|
+
return instructions.claimRoundInstantTokens(
|
|
1735
|
+
this.program,
|
|
1736
|
+
args,
|
|
1737
|
+
this.walletPublicKey
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
/**
|
|
1742
|
+
* Claim vested tokens for a round milestone (investor)
|
|
1743
|
+
*
|
|
1744
|
+
* Multi-Round Fundraising: R2+ investors claim vested portion
|
|
1745
|
+
* after cliff period. Tokens come from FutureRoundVault.
|
|
1746
|
+
*/
|
|
1747
|
+
async claimRoundVestedTokens(args: {
|
|
1748
|
+
projectId: BN;
|
|
1749
|
+
roundNumber: number;
|
|
1750
|
+
milestoneIndex: number;
|
|
1751
|
+
nftMint: PublicKey;
|
|
1752
|
+
investorTokenAccount: PublicKey;
|
|
1753
|
+
}): Promise<string> {
|
|
1754
|
+
return instructions.claimRoundVestedTokens(
|
|
1755
|
+
this.program,
|
|
1756
|
+
args,
|
|
1757
|
+
this.walletPublicKey
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
/**
|
|
1762
|
+
* Claim early tokens for a round investment (5% after 24h)
|
|
1763
|
+
*
|
|
1764
|
+
* Multi-Round Fundraising: R2+ investors claim 5% of token allocation
|
|
1765
|
+
* 24 hours after investing. Tokens come from FutureRoundVault.
|
|
1766
|
+
*/
|
|
1767
|
+
async claimRoundEarlyTokens(args: {
|
|
1768
|
+
projectId: BN;
|
|
1769
|
+
roundNumber: number;
|
|
1770
|
+
nftMint: PublicKey;
|
|
1771
|
+
investorTokenAccount: PublicKey;
|
|
1772
|
+
}): Promise<string> {
|
|
1773
|
+
return instructions.claimRoundEarlyTokens(
|
|
1774
|
+
this.program,
|
|
1775
|
+
args,
|
|
1776
|
+
this.walletPublicKey
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// ===========================================================================
|
|
1781
|
+
// Multi-Round Fundraising Helpers
|
|
1782
|
+
// ===========================================================================
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* Check if the next funding round can be opened
|
|
1786
|
+
*
|
|
1787
|
+
* Returns eligibility status with reason and next round number.
|
|
1788
|
+
*/
|
|
1789
|
+
async canOpenNextRound(projectId: BN): Promise<{
|
|
1790
|
+
canOpen: boolean;
|
|
1791
|
+
reason?: string;
|
|
1792
|
+
nextRoundNumber?: number;
|
|
1793
|
+
}> {
|
|
1794
|
+
return accounts.canOpenNextRound(this.program, projectId);
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
/**
|
|
1798
|
+
* Get remaining future round allocation in BPS
|
|
1799
|
+
*/
|
|
1800
|
+
getRemainingFutureRoundAllocation(tokenomics: {
|
|
1801
|
+
futureRoundAllocationBps: number;
|
|
1802
|
+
usedFutureRoundBps?: number;
|
|
1803
|
+
}): number {
|
|
1804
|
+
return accounts.getRemainingFutureRoundAllocation(tokenomics);
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
/**
|
|
1808
|
+
* Check if a specific round is fully funded
|
|
1809
|
+
*/
|
|
1810
|
+
async isRoundFullyFunded(projectId: BN, roundNumber: number): Promise<boolean> {
|
|
1811
|
+
return accounts.isRoundFullyFunded(this.program, projectId, roundNumber);
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
/**
|
|
1815
|
+
* Get round details with computed state
|
|
1816
|
+
*
|
|
1817
|
+
* Returns funding round data with additional computed fields.
|
|
1818
|
+
*/
|
|
1819
|
+
async getRoundDetails(projectId: BN, roundNumber: number) {
|
|
1820
|
+
const fundingRound = await this.fetchFundingRound(projectId, roundNumber);
|
|
1821
|
+
if (!fundingRound) {
|
|
1822
|
+
return null;
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
const milestones = [];
|
|
1826
|
+
for (let i = 0; i < fundingRound.milestoneCount; i++) {
|
|
1827
|
+
const milestone = await this.fetchRoundMilestone(projectId, roundNumber, i);
|
|
1828
|
+
if (milestone) {
|
|
1829
|
+
milestones.push(milestone);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
const totalRaised = fundingRound.totalRaised.toNumber();
|
|
1834
|
+
const fundingGoal = fundingRound.fundingGoal.toNumber();
|
|
1835
|
+
const fundingProgress = fundingGoal > 0 ? (totalRaised / fundingGoal) * 100 : 0;
|
|
1836
|
+
|
|
1837
|
+
return {
|
|
1838
|
+
...fundingRound,
|
|
1839
|
+
milestones,
|
|
1840
|
+
fundingProgress,
|
|
1841
|
+
isFullyFunded: totalRaised >= fundingGoal,
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
715
1844
|
}
|