@viwoapp/sdk 0.1.7 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +581 -394
- package/dist/index.d.mts +320 -13
- package/dist/index.d.ts +320 -13
- package/dist/index.js +342 -44
- package/dist/index.mjs +342 -44
- package/package.json +82 -83
package/dist/index.mjs
CHANGED
|
@@ -19,7 +19,18 @@ var PROGRAM_IDS = {
|
|
|
19
19
|
governanceProtocol: new PublicKey("3R256kBN9iXozjypQFRAmegBhd6HJqXWqdNG7Th78HYe"),
|
|
20
20
|
sscreProtocol: new PublicKey("6AJNcQSfoiE2UAeUDyJUBumS9SBwhAdSznoAeYpXrxXZ"),
|
|
21
21
|
vilinkProtocol: new PublicKey("CFGXTS2MueQwTYTMMTBQbRWzJtSTC2p4ZRuKPpLDmrv7"),
|
|
22
|
-
gaslessProtocol: new PublicKey("FcXJAjzJs8eVY2WTRFXynQBpC7WZUqKZppyp9xS6PaB3")
|
|
22
|
+
gaslessProtocol: new PublicKey("FcXJAjzJs8eVY2WTRFXynQBpC7WZUqKZppyp9xS6PaB3"),
|
|
23
|
+
/**
|
|
24
|
+
* VCoin Token Mint Address (Token-2022)
|
|
25
|
+
*
|
|
26
|
+
* NOTE: This is a placeholder. Override via ViWoClient config.programIds.vcoinMint
|
|
27
|
+
* after deploying your VCoin mint on devnet/mainnet.
|
|
28
|
+
*
|
|
29
|
+
* Finding #2 Fix: SDK now filters token accounts by mint address to prevent
|
|
30
|
+
* summing balances from other Token-2022 tokens.
|
|
31
|
+
*/
|
|
32
|
+
vcoinMint: new PublicKey("11111111111111111111111111111111")
|
|
33
|
+
// Placeholder - override in config
|
|
23
34
|
};
|
|
24
35
|
var SEEDS = {
|
|
25
36
|
// VCoin
|
|
@@ -193,9 +204,22 @@ var GOVERNANCE_CONSTANTS = {
|
|
|
193
204
|
vetoWindow: 24 * 3600,
|
|
194
205
|
// 1 day
|
|
195
206
|
quorumBps: 400,
|
|
196
|
-
// 4%
|
|
197
|
-
|
|
198
|
-
|
|
207
|
+
// 4% (C-03: abstains no longer count toward quorum)
|
|
208
|
+
/** Authority transfer timelock (H-02) */
|
|
209
|
+
authorityTransferTimelock: 24 * 3600,
|
|
210
|
+
// 24 hours
|
|
211
|
+
/** ZK private voting constants */
|
|
212
|
+
zk: {
|
|
213
|
+
/** Vote validity proof size (3 OR proofs + 1 sum proof) */
|
|
214
|
+
voteProofSize: 352,
|
|
215
|
+
/** ElGamal ciphertext size (R || C) */
|
|
216
|
+
ciphertextSize: 64,
|
|
217
|
+
/** Max committee members */
|
|
218
|
+
maxCommitteeSize: 5,
|
|
219
|
+
/** Account sizes */
|
|
220
|
+
privateVotingConfigSize: 680,
|
|
221
|
+
decryptionShareSize: 242
|
|
222
|
+
}
|
|
199
223
|
};
|
|
200
224
|
var SECURITY_CONSTANTS = {
|
|
201
225
|
// H-02: Two-step authority transfer
|
|
@@ -230,8 +254,21 @@ var SECURITY_CONSTANTS = {
|
|
|
230
254
|
// H-NEW-02: Max proof levels (supports 4B+ users)
|
|
231
255
|
maxEpochBitmap: 1023,
|
|
232
256
|
// H-NEW-04: Max epoch with bitmap storage (85+ years)
|
|
233
|
-
votingPowerVerifiedOnChain: true
|
|
257
|
+
votingPowerVerifiedOnChain: true,
|
|
234
258
|
// C-NEW-01: Params read from chain, not passed
|
|
259
|
+
// Audit remediation additions
|
|
260
|
+
/** H-AUDIT-12: Maximum concurrent sessions per user */
|
|
261
|
+
maxSessionsPerUser: 5,
|
|
262
|
+
/** C-AUDIT-15: Maximum day delta for daily budget reset (clock skew protection) */
|
|
263
|
+
maxDayDelta: 2,
|
|
264
|
+
/** H-AUDIT-09: Maximum daily activity score for transfer hook */
|
|
265
|
+
maxDailyActivityScore: 5e3,
|
|
266
|
+
/** M-AUDIT-10: Wash flag decay period (7 days) */
|
|
267
|
+
washFlagDecayPeriod: 7 * 24 * 3600,
|
|
268
|
+
/** M-18: Maximum vouch age before expiry (1 year) */
|
|
269
|
+
maxVouchAge: 365 * 24 * 3600,
|
|
270
|
+
/** C-AUDIT-11: Valid content energy tiers (1-4) */
|
|
271
|
+
maxContentTier: 4
|
|
235
272
|
};
|
|
236
273
|
var VALID_URI_PREFIXES = ["ipfs://", "https://", "ar://"];
|
|
237
274
|
var MAX_URI_LENGTH = 128;
|
|
@@ -668,6 +705,10 @@ var StakingClient = class {
|
|
|
668
705
|
}
|
|
669
706
|
/**
|
|
670
707
|
* Get tier name
|
|
708
|
+
*
|
|
709
|
+
* M-05: The on-chain update_tier instruction will reject no-op tier updates
|
|
710
|
+
* with TierUnchanged error. Only call updateTier when the user's stake amount
|
|
711
|
+
* actually qualifies for a different tier.
|
|
671
712
|
*/
|
|
672
713
|
getTierName(tier) {
|
|
673
714
|
const names = ["None", "Bronze", "Silver", "Gold", "Platinum"];
|
|
@@ -779,11 +820,14 @@ var GovernanceClient = class {
|
|
|
779
820
|
if (!accountInfo) {
|
|
780
821
|
return null;
|
|
781
822
|
}
|
|
823
|
+
const data = accountInfo.data;
|
|
782
824
|
return {
|
|
783
|
-
authority: new PublicKey4(
|
|
784
|
-
proposalCount: new BN3(
|
|
785
|
-
vevcoinMint: new PublicKey4(
|
|
786
|
-
paused:
|
|
825
|
+
authority: new PublicKey4(data.slice(8, 40)),
|
|
826
|
+
proposalCount: new BN3(data.slice(40, 48), "le"),
|
|
827
|
+
vevcoinMint: new PublicKey4(data.slice(48, 80)),
|
|
828
|
+
paused: data[80] !== 0,
|
|
829
|
+
pendingAuthority: new PublicKey4(data.slice(81, 113)),
|
|
830
|
+
pendingAuthorityActivatedAt: new BN3(data.slice(113, 121), "le")
|
|
787
831
|
};
|
|
788
832
|
} catch {
|
|
789
833
|
return null;
|
|
@@ -856,7 +900,11 @@ var GovernanceClient = class {
|
|
|
856
900
|
proposal: new PublicKey4(data.slice(40, 72)),
|
|
857
901
|
votePower: new BN3(data.slice(72, 80), "le"),
|
|
858
902
|
support: data[80] !== 0,
|
|
859
|
-
votedAt: new BN3(data.slice(81, 89), "le")
|
|
903
|
+
votedAt: new BN3(data.slice(81, 89), "le"),
|
|
904
|
+
// Private vote ciphertexts (if present, at byte offsets after public vote fields)
|
|
905
|
+
ctFor: new Uint8Array(data.slice(89, 153)),
|
|
906
|
+
ctAgainst: new Uint8Array(data.slice(153, 217)),
|
|
907
|
+
ctAbstain: new Uint8Array(data.slice(217, 281))
|
|
860
908
|
};
|
|
861
909
|
} catch {
|
|
862
910
|
return null;
|
|
@@ -871,6 +919,9 @@ var GovernanceClient = class {
|
|
|
871
919
|
}
|
|
872
920
|
/**
|
|
873
921
|
* Calculate user's voting power
|
|
922
|
+
*
|
|
923
|
+
* @note M-01 fix: 5A boost formula is now `1000 + ((five_a_score * 100) / 1000)`
|
|
924
|
+
* to fix precision loss for small scores.
|
|
874
925
|
*/
|
|
875
926
|
async getVotingPower(user) {
|
|
876
927
|
const target = user || this.client.publicKey;
|
|
@@ -913,6 +964,9 @@ var GovernanceClient = class {
|
|
|
913
964
|
}
|
|
914
965
|
/**
|
|
915
966
|
* Get proposal progress
|
|
967
|
+
*
|
|
968
|
+
* @note C-03: Quorum is now calculated as votesFor + votesAgainst only.
|
|
969
|
+
* Abstains do NOT count toward quorum.
|
|
916
970
|
*/
|
|
917
971
|
async getProposalProgress(proposalId) {
|
|
918
972
|
const proposal = await this.getProposal(proposalId);
|
|
@@ -936,7 +990,7 @@ var GovernanceClient = class {
|
|
|
936
990
|
timeRemaining
|
|
937
991
|
};
|
|
938
992
|
}
|
|
939
|
-
// ============ Transaction Building ============
|
|
993
|
+
// ============ Transaction Building (Public Voting) ============
|
|
940
994
|
/**
|
|
941
995
|
* Build create proposal transaction
|
|
942
996
|
*/
|
|
@@ -952,12 +1006,12 @@ var GovernanceClient = class {
|
|
|
952
1006
|
return tx;
|
|
953
1007
|
}
|
|
954
1008
|
/**
|
|
955
|
-
* Build vote transaction
|
|
956
|
-
*
|
|
957
|
-
* @note v2.8.0 (C-NEW-01): Voting power parameters (vevcoin_balance, five_a_score, tier)
|
|
1009
|
+
* Build vote transaction (public)
|
|
1010
|
+
*
|
|
1011
|
+
* @note v2.8.0 (C-NEW-01): Voting power parameters (vevcoin_balance, five_a_score, tier)
|
|
958
1012
|
* are now read from on-chain state, not passed as parameters. This prevents vote manipulation.
|
|
959
1013
|
* The transaction only needs: proposal_id and choice (VoteChoice enum)
|
|
960
|
-
*
|
|
1014
|
+
*
|
|
961
1015
|
* @param proposalId - The proposal to vote on
|
|
962
1016
|
* @param support - true = For, false = Against (use VoteChoice for more options)
|
|
963
1017
|
*/
|
|
@@ -987,6 +1041,148 @@ var GovernanceClient = class {
|
|
|
987
1041
|
const tx = new Transaction3();
|
|
988
1042
|
return tx;
|
|
989
1043
|
}
|
|
1044
|
+
// ============ ZK Private Voting ============
|
|
1045
|
+
/**
|
|
1046
|
+
* Build cast private vote transaction
|
|
1047
|
+
*
|
|
1048
|
+
* Uses Twisted ElGamal encryption on Ristretto255 with compressed sigma proofs.
|
|
1049
|
+
* The voter encrypts their choice into 3 ciphertexts (for/against/abstain) and
|
|
1050
|
+
* generates a validity proof that exactly one ciphertext encrypts their weight.
|
|
1051
|
+
*
|
|
1052
|
+
* Use the `zk-voting-sdk` crate to generate ciphertexts and proofs off-chain:
|
|
1053
|
+
* ```rust
|
|
1054
|
+
* let (ct_for, ct_against, ct_abstain, proof) = encrypt_and_prove(&pubkey, choice, weight);
|
|
1055
|
+
* ```
|
|
1056
|
+
*
|
|
1057
|
+
* @param params - Private vote parameters with ciphertexts and proof
|
|
1058
|
+
*/
|
|
1059
|
+
async buildCastPrivateVoteTransaction(params) {
|
|
1060
|
+
if (!this.client.publicKey) {
|
|
1061
|
+
throw new Error("Wallet not connected");
|
|
1062
|
+
}
|
|
1063
|
+
if (params.ctFor.length !== GOVERNANCE_CONSTANTS.zk.ciphertextSize) {
|
|
1064
|
+
throw new Error(`ctFor must be ${GOVERNANCE_CONSTANTS.zk.ciphertextSize} bytes`);
|
|
1065
|
+
}
|
|
1066
|
+
if (params.ctAgainst.length !== GOVERNANCE_CONSTANTS.zk.ciphertextSize) {
|
|
1067
|
+
throw new Error(`ctAgainst must be ${GOVERNANCE_CONSTANTS.zk.ciphertextSize} bytes`);
|
|
1068
|
+
}
|
|
1069
|
+
if (params.ctAbstain.length !== GOVERNANCE_CONSTANTS.zk.ciphertextSize) {
|
|
1070
|
+
throw new Error(`ctAbstain must be ${GOVERNANCE_CONSTANTS.zk.ciphertextSize} bytes`);
|
|
1071
|
+
}
|
|
1072
|
+
if (params.proofData.length !== GOVERNANCE_CONSTANTS.zk.voteProofSize) {
|
|
1073
|
+
throw new Error(`proofData must be ${GOVERNANCE_CONSTANTS.zk.voteProofSize} bytes`);
|
|
1074
|
+
}
|
|
1075
|
+
const hasVoted = await this.hasVoted(params.proposalId);
|
|
1076
|
+
if (hasVoted) {
|
|
1077
|
+
throw new Error("Already voted on this proposal");
|
|
1078
|
+
}
|
|
1079
|
+
const tx = new Transaction3();
|
|
1080
|
+
return tx;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Build enable private voting transaction
|
|
1084
|
+
*
|
|
1085
|
+
* Called by the governance authority to enable ZK private voting on a proposal.
|
|
1086
|
+
* Requires specifying the decryption committee and their ElGamal public keys.
|
|
1087
|
+
*
|
|
1088
|
+
* @param params - Private voting configuration
|
|
1089
|
+
*/
|
|
1090
|
+
async buildEnablePrivateVotingTransaction(params) {
|
|
1091
|
+
if (!this.client.publicKey) {
|
|
1092
|
+
throw new Error("Wallet not connected");
|
|
1093
|
+
}
|
|
1094
|
+
if (params.encryptionPubkey.length !== 32) {
|
|
1095
|
+
throw new Error("encryptionPubkey must be 32 bytes (Ristretto255 point)");
|
|
1096
|
+
}
|
|
1097
|
+
if (params.committeeSize > GOVERNANCE_CONSTANTS.zk.maxCommitteeSize) {
|
|
1098
|
+
throw new Error(`Committee size exceeds max of ${GOVERNANCE_CONSTANTS.zk.maxCommitteeSize}`);
|
|
1099
|
+
}
|
|
1100
|
+
if (params.decryptionThreshold > params.committeeSize) {
|
|
1101
|
+
throw new Error("Threshold cannot exceed committee size");
|
|
1102
|
+
}
|
|
1103
|
+
for (let i = 0; i < params.committeeSize; i++) {
|
|
1104
|
+
if (!params.committeeElgamalPubkeys[i] || params.committeeElgamalPubkeys[i].length !== 32) {
|
|
1105
|
+
throw new Error(`Committee member ${i} ElGamal pubkey must be 32 bytes`);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
const tx = new Transaction3();
|
|
1109
|
+
return tx;
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Build submit decryption share transaction
|
|
1113
|
+
*
|
|
1114
|
+
* Called by a committee member during the reveal phase.
|
|
1115
|
+
* Each member computes partial decryptions and a DLEQ proof off-chain:
|
|
1116
|
+
* ```rust
|
|
1117
|
+
* let partial = generate_partial_decryption(&secret_share, &pk, &r_for, &r_against, &r_abstain);
|
|
1118
|
+
* ```
|
|
1119
|
+
*
|
|
1120
|
+
* @param params - Decryption share with DLEQ proof
|
|
1121
|
+
*/
|
|
1122
|
+
async buildSubmitDecryptionShareTransaction(params) {
|
|
1123
|
+
if (!this.client.publicKey) {
|
|
1124
|
+
throw new Error("Wallet not connected");
|
|
1125
|
+
}
|
|
1126
|
+
const fields = [
|
|
1127
|
+
{ name: "partialFor", value: params.partialFor },
|
|
1128
|
+
{ name: "partialAgainst", value: params.partialAgainst },
|
|
1129
|
+
{ name: "partialAbstain", value: params.partialAbstain },
|
|
1130
|
+
{ name: "dleqChallenge", value: params.dleqChallenge },
|
|
1131
|
+
{ name: "dleqResponse", value: params.dleqResponse }
|
|
1132
|
+
];
|
|
1133
|
+
for (const { name, value } of fields) {
|
|
1134
|
+
if (value.length !== 32) {
|
|
1135
|
+
throw new Error(`${name} must be 32 bytes`);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
const tx = new Transaction3();
|
|
1139
|
+
return tx;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Build aggregate revealed votes transaction (permissionless)
|
|
1143
|
+
*
|
|
1144
|
+
* Anyone can submit the aggregated tally since the on-chain program
|
|
1145
|
+
* cryptographically verifies: tally * H == C_sum - D for each category.
|
|
1146
|
+
* This prevents fabrication of results.
|
|
1147
|
+
*
|
|
1148
|
+
* Use the `zk-voting-sdk` to compute the tally off-chain:
|
|
1149
|
+
* ```rust
|
|
1150
|
+
* let lagrange = compute_lagrange_coefficients(&selected_indices);
|
|
1151
|
+
* let d = combine_partials(&lagrange, &partials);
|
|
1152
|
+
* let tally = recover_tally(&d, &accumulated_c, max_votes).unwrap();
|
|
1153
|
+
* ```
|
|
1154
|
+
*
|
|
1155
|
+
* @param params - Tally values and Lagrange coefficients
|
|
1156
|
+
*/
|
|
1157
|
+
async buildAggregateRevealedVotesTransaction(params) {
|
|
1158
|
+
for (let i = 0; i < params.lagrangeCoefficients.length; i++) {
|
|
1159
|
+
if (params.lagrangeCoefficients[i].length !== 32) {
|
|
1160
|
+
throw new Error(`Lagrange coefficient ${i} must be 32 bytes`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
const tx = new Transaction3();
|
|
1164
|
+
return tx;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Check if authority transfer timelock has elapsed (H-02)
|
|
1168
|
+
*/
|
|
1169
|
+
async canAcceptAuthority() {
|
|
1170
|
+
const config = await this.getConfig();
|
|
1171
|
+
if (!config) {
|
|
1172
|
+
return { canAccept: false, reason: "Config not found" };
|
|
1173
|
+
}
|
|
1174
|
+
if (!config.pendingAuthorityActivatedAt || config.pendingAuthorityActivatedAt.isZero()) {
|
|
1175
|
+
return { canAccept: false, reason: "No pending authority transfer" };
|
|
1176
|
+
}
|
|
1177
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1178
|
+
const timelockEnd = config.pendingAuthorityActivatedAt.toNumber() + GOVERNANCE_CONSTANTS.authorityTransferTimelock;
|
|
1179
|
+
if (now < timelockEnd) {
|
|
1180
|
+
const remaining = timelockEnd - now;
|
|
1181
|
+
const hours = Math.ceil(remaining / 3600);
|
|
1182
|
+
return { canAccept: false, reason: `Timelock: ${hours} hours remaining` };
|
|
1183
|
+
}
|
|
1184
|
+
return { canAccept: true };
|
|
1185
|
+
}
|
|
990
1186
|
};
|
|
991
1187
|
|
|
992
1188
|
// src/rewards/index.ts
|
|
@@ -1126,9 +1322,16 @@ var RewardsClient = class {
|
|
|
1126
1322
|
}
|
|
1127
1323
|
/**
|
|
1128
1324
|
* Calculate gasless fee for claim
|
|
1325
|
+
*
|
|
1326
|
+
* C-05: Uses ceiling division to prevent fee rounding to zero on small amounts.
|
|
1327
|
+
* Formula: fee = ceil(amount * feeBps / 10000)
|
|
1129
1328
|
*/
|
|
1130
1329
|
calculateGaslessFee(amount) {
|
|
1131
|
-
const
|
|
1330
|
+
const numerator = amount.muln(SSCRE_CONSTANTS.gaslessFeeBps).addn(9999);
|
|
1331
|
+
let fee = numerator.divn(1e4);
|
|
1332
|
+
if (fee.gt(amount)) {
|
|
1333
|
+
fee = amount;
|
|
1334
|
+
}
|
|
1132
1335
|
return fee;
|
|
1133
1336
|
}
|
|
1134
1337
|
/**
|
|
@@ -1185,6 +1388,9 @@ var RewardsClient = class {
|
|
|
1185
1388
|
// ============ Transaction Building ============
|
|
1186
1389
|
/**
|
|
1187
1390
|
* Build claim rewards transaction
|
|
1391
|
+
*
|
|
1392
|
+
* H-NEW-02: Merkle proof size is limited to 32 levels (supports 4B+ users).
|
|
1393
|
+
* Proofs exceeding this limit will be rejected on-chain with MerkleProofTooLarge.
|
|
1188
1394
|
*/
|
|
1189
1395
|
async buildClaimTransaction(params) {
|
|
1190
1396
|
if (!this.client.publicKey) {
|
|
@@ -1193,6 +1399,9 @@ var RewardsClient = class {
|
|
|
1193
1399
|
if (params.amount.lt(new BN4(SSCRE_CONSTANTS.minClaimAmount * 1e9))) {
|
|
1194
1400
|
throw new Error(`Claim amount below minimum: ${SSCRE_CONSTANTS.minClaimAmount} VCoin`);
|
|
1195
1401
|
}
|
|
1402
|
+
if (params.merkleProof.length > 32) {
|
|
1403
|
+
throw new Error("Merkle proof exceeds maximum size of 32 levels");
|
|
1404
|
+
}
|
|
1196
1405
|
const hasClaimed = await this.hasClaimedEpoch(params.epoch);
|
|
1197
1406
|
if (hasClaimed) {
|
|
1198
1407
|
throw new Error("Already claimed for this epoch");
|
|
@@ -1211,6 +1420,9 @@ var ViLinkClient = class {
|
|
|
1211
1420
|
}
|
|
1212
1421
|
/**
|
|
1213
1422
|
* Get ViLink configuration
|
|
1423
|
+
*
|
|
1424
|
+
* Finding #8 (related): Corrected byte offsets to match on-chain ViLinkConfig struct.
|
|
1425
|
+
* Added pending_authority field that was missing after H-02 security fix.
|
|
1214
1426
|
*/
|
|
1215
1427
|
async getConfig() {
|
|
1216
1428
|
try {
|
|
@@ -1222,16 +1434,18 @@ var ViLinkClient = class {
|
|
|
1222
1434
|
const data = accountInfo.data;
|
|
1223
1435
|
return {
|
|
1224
1436
|
authority: new PublicKey6(data.slice(8, 40)),
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1437
|
+
pendingAuthority: new PublicKey6(data.slice(40, 72)),
|
|
1438
|
+
vcoinMint: new PublicKey6(data.slice(72, 104)),
|
|
1439
|
+
treasury: new PublicKey6(data.slice(104, 136)),
|
|
1440
|
+
enabledActions: data[296],
|
|
1441
|
+
totalActionsCreated: new BN5(data.slice(297, 305), "le"),
|
|
1442
|
+
totalActionsExecuted: new BN5(data.slice(305, 313), "le"),
|
|
1443
|
+
totalTipVolume: new BN5(data.slice(313, 321), "le"),
|
|
1444
|
+
paused: data[321] !== 0,
|
|
1445
|
+
platformFeeBps: data.readUInt16LE(322)
|
|
1233
1446
|
};
|
|
1234
|
-
} catch {
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
console.warn("[ViWoSDK] vilink.getConfig failed:", error instanceof Error ? error.message : error);
|
|
1235
1449
|
return null;
|
|
1236
1450
|
}
|
|
1237
1451
|
}
|
|
@@ -1261,7 +1475,8 @@ var ViLinkClient = class {
|
|
|
1261
1475
|
actionNonce: nonce
|
|
1262
1476
|
// M-04: Store nonce for reference
|
|
1263
1477
|
};
|
|
1264
|
-
} catch {
|
|
1478
|
+
} catch (error) {
|
|
1479
|
+
console.warn("[ViWoSDK] vilink.getAction failed:", error instanceof Error ? error.message : error);
|
|
1265
1480
|
return null;
|
|
1266
1481
|
}
|
|
1267
1482
|
}
|
|
@@ -1295,7 +1510,8 @@ var ViLinkClient = class {
|
|
|
1295
1510
|
vcoinSent: new BN5(data.slice(72, 80), "le"),
|
|
1296
1511
|
vcoinReceived: new BN5(data.slice(80, 88), "le")
|
|
1297
1512
|
};
|
|
1298
|
-
} catch {
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
console.warn("[ViWoSDK] vilink.getUserStats failed:", error instanceof Error ? error.message : error);
|
|
1299
1515
|
return null;
|
|
1300
1516
|
}
|
|
1301
1517
|
}
|
|
@@ -1347,9 +1563,16 @@ var ViLinkClient = class {
|
|
|
1347
1563
|
}
|
|
1348
1564
|
/**
|
|
1349
1565
|
* Calculate platform fee for tip
|
|
1566
|
+
*
|
|
1567
|
+
* C-06: Uses ceiling division to prevent fee rounding to zero on small amounts.
|
|
1568
|
+
* Formula: fee = ceil(amount * feeBps / 10000)
|
|
1350
1569
|
*/
|
|
1351
1570
|
calculateFee(amount) {
|
|
1352
|
-
const
|
|
1571
|
+
const numerator = amount.muln(VILINK_CONSTANTS.platformFeeBps).addn(9999);
|
|
1572
|
+
let fee = numerator.divn(1e4);
|
|
1573
|
+
if (fee.gt(amount)) {
|
|
1574
|
+
fee = amount;
|
|
1575
|
+
}
|
|
1353
1576
|
return {
|
|
1354
1577
|
fee,
|
|
1355
1578
|
net: amount.sub(fee)
|
|
@@ -1431,6 +1654,13 @@ var ViLinkClient = class {
|
|
|
1431
1654
|
* Build execute tip action transaction
|
|
1432
1655
|
* @param creator - The action creator's public key
|
|
1433
1656
|
* @param nonce - M-04: The action nonce (NOT timestamp)
|
|
1657
|
+
*
|
|
1658
|
+
* H-05: The on-chain handler validates that the executor's token account has
|
|
1659
|
+
* no active delegation (delegate is None or delegated_amount is 0).
|
|
1660
|
+
* This prevents delegated tokens from being spent without explicit approval.
|
|
1661
|
+
*
|
|
1662
|
+
* C-06: Platform fee uses ceiling division to prevent zero-fee exploitation on
|
|
1663
|
+
* small amounts.
|
|
1434
1664
|
*/
|
|
1435
1665
|
async buildExecuteTipAction(creator, nonce) {
|
|
1436
1666
|
if (!this.client.publicKey) {
|
|
@@ -1469,6 +1699,10 @@ var GaslessClient = class {
|
|
|
1469
1699
|
}
|
|
1470
1700
|
/**
|
|
1471
1701
|
* Get gasless configuration
|
|
1702
|
+
*
|
|
1703
|
+
* Finding #8 Fix: Corrected byte offsets to match on-chain GaslessConfig struct.
|
|
1704
|
+
* Added missing fields: pendingAuthority, feeVault, sscreProgram, sscreDeductionBps,
|
|
1705
|
+
* maxSubsidizedPerUser, totalSolSpent, currentDay, daySpent, maxSlippageBps.
|
|
1472
1706
|
*/
|
|
1473
1707
|
async getConfig() {
|
|
1474
1708
|
try {
|
|
@@ -1480,16 +1714,26 @@ var GaslessClient = class {
|
|
|
1480
1714
|
const data = accountInfo.data;
|
|
1481
1715
|
return {
|
|
1482
1716
|
authority: new PublicKey7(data.slice(8, 40)),
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1717
|
+
pendingAuthority: new PublicKey7(data.slice(40, 72)),
|
|
1718
|
+
feePayer: new PublicKey7(data.slice(72, 104)),
|
|
1719
|
+
vcoinMint: new PublicKey7(data.slice(104, 136)),
|
|
1720
|
+
feeVault: new PublicKey7(data.slice(136, 168)),
|
|
1721
|
+
sscreProgram: new PublicKey7(data.slice(168, 200)),
|
|
1722
|
+
dailySubsidyBudget: new BN6(data.slice(200, 208), "le"),
|
|
1723
|
+
solFeePerTx: new BN6(data.slice(208, 216), "le"),
|
|
1724
|
+
vcoinFeeMultiplier: new BN6(data.slice(216, 224), "le"),
|
|
1725
|
+
sscreDeductionBps: data.readUInt16LE(224),
|
|
1726
|
+
maxSubsidizedPerUser: data.readUInt32LE(226),
|
|
1727
|
+
totalSubsidizedTx: new BN6(data.slice(230, 238), "le"),
|
|
1728
|
+
totalSolSpent: new BN6(data.slice(238, 246), "le"),
|
|
1729
|
+
totalVcoinCollected: new BN6(data.slice(246, 254), "le"),
|
|
1730
|
+
paused: data[254] !== 0,
|
|
1731
|
+
currentDay: data.readUInt32LE(255),
|
|
1732
|
+
daySpent: new BN6(data.slice(259, 267), "le"),
|
|
1733
|
+
maxSlippageBps: data.readUInt16LE(267)
|
|
1491
1734
|
};
|
|
1492
|
-
} catch {
|
|
1735
|
+
} catch (error) {
|
|
1736
|
+
console.warn("[ViWoSDK] gasless.getConfig failed:", error instanceof Error ? error.message : error);
|
|
1493
1737
|
return null;
|
|
1494
1738
|
}
|
|
1495
1739
|
}
|
|
@@ -1517,12 +1761,16 @@ var GaslessClient = class {
|
|
|
1517
1761
|
isRevoked: data[114] !== 0,
|
|
1518
1762
|
feeMethod: data[123]
|
|
1519
1763
|
};
|
|
1520
|
-
} catch {
|
|
1764
|
+
} catch (error) {
|
|
1765
|
+
console.warn("[ViWoSDK] gasless.getSessionKey failed:", error instanceof Error ? error.message : error);
|
|
1521
1766
|
return null;
|
|
1522
1767
|
}
|
|
1523
1768
|
}
|
|
1524
1769
|
/**
|
|
1525
1770
|
* Get user gasless statistics
|
|
1771
|
+
*
|
|
1772
|
+
* H-AUDIT-12: Now includes active_sessions field for per-user session limit tracking.
|
|
1773
|
+
* The protocol enforces a maximum of 5 concurrent active sessions per user.
|
|
1526
1774
|
*/
|
|
1527
1775
|
async getUserStats(user) {
|
|
1528
1776
|
const target = user || this.client.publicKey;
|
|
@@ -1542,9 +1790,11 @@ var GaslessClient = class {
|
|
|
1542
1790
|
totalSubsidized: new BN6(data.slice(48, 56), "le"),
|
|
1543
1791
|
totalVcoinFees: new BN6(data.slice(56, 64), "le"),
|
|
1544
1792
|
sessionsCreated: data.readUInt32LE(72),
|
|
1545
|
-
|
|
1793
|
+
activeSessions: data[76],
|
|
1794
|
+
activeSession: new PublicKey7(data.slice(77, 109))
|
|
1546
1795
|
};
|
|
1547
|
-
} catch {
|
|
1796
|
+
} catch (error) {
|
|
1797
|
+
console.warn("[ViWoSDK] gasless.getUserStats failed:", error instanceof Error ? error.message : error);
|
|
1548
1798
|
return null;
|
|
1549
1799
|
}
|
|
1550
1800
|
}
|
|
@@ -1678,6 +1928,9 @@ var GaslessClient = class {
|
|
|
1678
1928
|
// ============ Transaction Building ============
|
|
1679
1929
|
/**
|
|
1680
1930
|
* Build create session key transaction
|
|
1931
|
+
*
|
|
1932
|
+
* H-AUDIT-12: The protocol enforces a maximum of 5 concurrent active sessions per user.
|
|
1933
|
+
* Creating a session when the limit is reached will fail with MaxSessionsReached error.
|
|
1681
1934
|
*/
|
|
1682
1935
|
async buildCreateSessionTransaction(params) {
|
|
1683
1936
|
if (!this.client.publicKey) {
|
|
@@ -1689,6 +1942,10 @@ var GaslessClient = class {
|
|
|
1689
1942
|
if (!params.scope || params.scope === 0) {
|
|
1690
1943
|
throw new Error("At least one scope required");
|
|
1691
1944
|
}
|
|
1945
|
+
const stats = await this.getUserStats();
|
|
1946
|
+
if (stats && stats.activeSessions >= 5) {
|
|
1947
|
+
throw new Error("Maximum active sessions reached (5). Revoke an existing session first.");
|
|
1948
|
+
}
|
|
1692
1949
|
const duration = params.durationSeconds || GASLESS_CONSTANTS.sessionDuration;
|
|
1693
1950
|
const maxActions = params.maxActions || GASLESS_CONSTANTS.maxSessionActions;
|
|
1694
1951
|
const maxSpend = params.maxSpend || new BN6(GASLESS_CONSTANTS.maxSessionSpend * 1e9);
|
|
@@ -1799,6 +2056,23 @@ var IdentityClient = class {
|
|
|
1799
2056
|
return benefits[level] || [];
|
|
1800
2057
|
}
|
|
1801
2058
|
// ============ Transaction Building ============
|
|
2059
|
+
/**
|
|
2060
|
+
* Build subscribe transaction
|
|
2061
|
+
*
|
|
2062
|
+
* C-AUDIT-22: Non-free subscription tiers require actual USDC payment via SPL
|
|
2063
|
+
* transfer_checked. The transaction must include the user's USDC token account,
|
|
2064
|
+
* the USDC mint, and the treasury token account.
|
|
2065
|
+
*/
|
|
2066
|
+
async buildSubscribeTransaction(tier) {
|
|
2067
|
+
if (!this.client.publicKey) {
|
|
2068
|
+
throw new Error("Wallet not connected");
|
|
2069
|
+
}
|
|
2070
|
+
if (tier < 0 || tier > 4) {
|
|
2071
|
+
throw new Error("Invalid subscription tier (0-4)");
|
|
2072
|
+
}
|
|
2073
|
+
const tx = new Transaction7();
|
|
2074
|
+
return tx;
|
|
2075
|
+
}
|
|
1802
2076
|
/**
|
|
1803
2077
|
* Build create identity transaction
|
|
1804
2078
|
*/
|
|
@@ -1951,6 +2225,12 @@ var FiveAClient = class {
|
|
|
1951
2225
|
}
|
|
1952
2226
|
/**
|
|
1953
2227
|
* Check if user can vouch for another
|
|
2228
|
+
*
|
|
2229
|
+
* C-08: Mutual vouching is prevented — if the target has already vouched for you,
|
|
2230
|
+
* you cannot vouch for them. This is enforced on-chain via reverse_vouch_record check.
|
|
2231
|
+
*
|
|
2232
|
+
* M-18: Vouches expire after 1 year (MAX_VOUCH_AGE = 365 days). Expired vouches
|
|
2233
|
+
* cannot be evaluated and must be re-created.
|
|
1954
2234
|
*/
|
|
1955
2235
|
async canVouchFor(target) {
|
|
1956
2236
|
if (!this.client.publicKey) {
|
|
@@ -1993,6 +2273,12 @@ var FiveAClient = class {
|
|
|
1993
2273
|
// ============ Transaction Building ============
|
|
1994
2274
|
/**
|
|
1995
2275
|
* Build vouch transaction
|
|
2276
|
+
*
|
|
2277
|
+
* C-08: On-chain handler requires a reverse_vouch_record account to verify
|
|
2278
|
+
* mutual vouching is not occurring. The transaction must include this PDA.
|
|
2279
|
+
*
|
|
2280
|
+
* M-18: Vouches have a maximum age of 1 year. After that, evaluate_vouch
|
|
2281
|
+
* will reject with VouchExpired error.
|
|
1996
2282
|
*/
|
|
1997
2283
|
async buildVouchTransaction(target) {
|
|
1998
2284
|
if (!this.client.publicKey) {
|
|
@@ -2161,6 +2447,9 @@ var ContentClient = class {
|
|
|
2161
2447
|
}
|
|
2162
2448
|
/**
|
|
2163
2449
|
* Get content stats
|
|
2450
|
+
*
|
|
2451
|
+
* C-AUDIT-10: Engagement scores can only increase — the on-chain update_engagement
|
|
2452
|
+
* instruction enforces monotonic increase to prevent manipulation.
|
|
2164
2453
|
*/
|
|
2165
2454
|
async getContentStats(contentId) {
|
|
2166
2455
|
const content = await this.getContent(contentId);
|
|
@@ -2180,6 +2469,9 @@ var ContentClient = class {
|
|
|
2180
2469
|
// ============ Transaction Building ============
|
|
2181
2470
|
/**
|
|
2182
2471
|
* Build create content transaction
|
|
2472
|
+
*
|
|
2473
|
+
* C-AUDIT-11: Energy tiers are validated on-chain (valid range: 1-4).
|
|
2474
|
+
* Tier determines max energy and regen rate.
|
|
2183
2475
|
*/
|
|
2184
2476
|
async buildCreateContentTransaction(contentHash) {
|
|
2185
2477
|
if (!this.client.publicKey) {
|
|
@@ -2319,6 +2611,9 @@ var ViWoClient = class {
|
|
|
2319
2611
|
}
|
|
2320
2612
|
/**
|
|
2321
2613
|
* Get VCoin balance
|
|
2614
|
+
*
|
|
2615
|
+
* Finding #2 Fix: Now filters by VCoin mint address instead of summing all Token-2022 accounts.
|
|
2616
|
+
* Make sure to set programIds.vcoinMint in your ViWoClient config.
|
|
2322
2617
|
*/
|
|
2323
2618
|
async getVCoinBalance(user) {
|
|
2324
2619
|
const target = user || this.publicKey;
|
|
@@ -2328,7 +2623,7 @@ var ViWoClient = class {
|
|
|
2328
2623
|
try {
|
|
2329
2624
|
const tokenAccounts = await this.connection.connection.getTokenAccountsByOwner(
|
|
2330
2625
|
target,
|
|
2331
|
-
{ programId: TOKEN_2022_PROGRAM_ID }
|
|
2626
|
+
{ mint: this.programIds.vcoinMint, programId: TOKEN_2022_PROGRAM_ID }
|
|
2332
2627
|
);
|
|
2333
2628
|
let balance = new BN10(0);
|
|
2334
2629
|
for (const { account } of tokenAccounts.value) {
|
|
@@ -2337,7 +2632,8 @@ var ViWoClient = class {
|
|
|
2337
2632
|
balance = balance.add(new BN10(amount, "le"));
|
|
2338
2633
|
}
|
|
2339
2634
|
return balance;
|
|
2340
|
-
} catch {
|
|
2635
|
+
} catch (error) {
|
|
2636
|
+
console.warn("[ViWoSDK] getVCoinBalance failed:", error instanceof Error ? error.message : error);
|
|
2341
2637
|
return new BN10(0);
|
|
2342
2638
|
}
|
|
2343
2639
|
}
|
|
@@ -2352,7 +2648,8 @@ var ViWoClient = class {
|
|
|
2352
2648
|
try {
|
|
2353
2649
|
const stakeData = await this.staking.getUserStake(target);
|
|
2354
2650
|
return stakeData?.vevcoinBalance || new BN10(0);
|
|
2355
|
-
} catch {
|
|
2651
|
+
} catch (error) {
|
|
2652
|
+
console.warn("[ViWoSDK] getVeVCoinBalance failed:", error instanceof Error ? error.message : error);
|
|
2356
2653
|
return new BN10(0);
|
|
2357
2654
|
}
|
|
2358
2655
|
}
|
|
@@ -2367,7 +2664,8 @@ var ViWoClient = class {
|
|
|
2367
2664
|
]);
|
|
2368
2665
|
const blockTime = await this.connection.getBlockTime();
|
|
2369
2666
|
return { connected, slot, blockTime };
|
|
2370
|
-
} catch {
|
|
2667
|
+
} catch (error) {
|
|
2668
|
+
console.warn("[ViWoSDK] healthCheck failed:", error instanceof Error ? error.message : error);
|
|
2371
2669
|
return { connected: false, slot: null, blockTime: null };
|
|
2372
2670
|
}
|
|
2373
2671
|
}
|