@viwoapp/sdk 0.1.8 → 2.0.1
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 +667 -427
- package/dist/index.d.mts +307 -13
- package/dist/index.d.ts +307 -13
- package/dist/index.js +360 -17
- package/dist/index.mjs +360 -17
- package/package.json +82 -82
package/dist/index.mjs
CHANGED
|
@@ -204,9 +204,22 @@ var GOVERNANCE_CONSTANTS = {
|
|
|
204
204
|
vetoWindow: 24 * 3600,
|
|
205
205
|
// 1 day
|
|
206
206
|
quorumBps: 400,
|
|
207
|
-
// 4%
|
|
208
|
-
|
|
209
|
-
|
|
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
|
+
}
|
|
210
223
|
};
|
|
211
224
|
var SECURITY_CONSTANTS = {
|
|
212
225
|
// H-02: Two-step authority transfer
|
|
@@ -241,8 +254,21 @@ var SECURITY_CONSTANTS = {
|
|
|
241
254
|
// H-NEW-02: Max proof levels (supports 4B+ users)
|
|
242
255
|
maxEpochBitmap: 1023,
|
|
243
256
|
// H-NEW-04: Max epoch with bitmap storage (85+ years)
|
|
244
|
-
votingPowerVerifiedOnChain: true
|
|
257
|
+
votingPowerVerifiedOnChain: true,
|
|
245
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
|
|
246
272
|
};
|
|
247
273
|
var VALID_URI_PREFIXES = ["ipfs://", "https://", "ar://"];
|
|
248
274
|
var MAX_URI_LENGTH = 128;
|
|
@@ -340,6 +366,22 @@ var PDAs = class {
|
|
|
340
366
|
);
|
|
341
367
|
return pda;
|
|
342
368
|
}
|
|
369
|
+
/** H-NEW-03: Delegation PDA (one per delegator) */
|
|
370
|
+
getDelegation(delegator) {
|
|
371
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
372
|
+
[Buffer.from(SEEDS.delegation), delegator.toBuffer()],
|
|
373
|
+
this.programIds.governanceProtocol
|
|
374
|
+
);
|
|
375
|
+
return pda;
|
|
376
|
+
}
|
|
377
|
+
/** H-NEW-03: Delegate stats PDA (one per delegate) */
|
|
378
|
+
getDelegateStats(delegate) {
|
|
379
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
380
|
+
[Buffer.from("delegate-stats"), delegate.toBuffer()],
|
|
381
|
+
this.programIds.governanceProtocol
|
|
382
|
+
);
|
|
383
|
+
return pda;
|
|
384
|
+
}
|
|
343
385
|
// SSCRE PDAs
|
|
344
386
|
getRewardsPoolConfig() {
|
|
345
387
|
const [pda] = PublicKey2.findProgramAddressSync(
|
|
@@ -679,6 +721,10 @@ var StakingClient = class {
|
|
|
679
721
|
}
|
|
680
722
|
/**
|
|
681
723
|
* Get tier name
|
|
724
|
+
*
|
|
725
|
+
* M-05: The on-chain update_tier instruction will reject no-op tier updates
|
|
726
|
+
* with TierUnchanged error. Only call updateTier when the user's stake amount
|
|
727
|
+
* actually qualifies for a different tier.
|
|
682
728
|
*/
|
|
683
729
|
getTierName(tier) {
|
|
684
730
|
const names = ["None", "Bronze", "Silver", "Gold", "Platinum"];
|
|
@@ -790,11 +836,14 @@ var GovernanceClient = class {
|
|
|
790
836
|
if (!accountInfo) {
|
|
791
837
|
return null;
|
|
792
838
|
}
|
|
839
|
+
const data = accountInfo.data;
|
|
793
840
|
return {
|
|
794
|
-
authority: new PublicKey4(
|
|
795
|
-
proposalCount: new BN3(
|
|
796
|
-
vevcoinMint: new PublicKey4(
|
|
797
|
-
paused:
|
|
841
|
+
authority: new PublicKey4(data.slice(8, 40)),
|
|
842
|
+
proposalCount: new BN3(data.slice(40, 48), "le"),
|
|
843
|
+
vevcoinMint: new PublicKey4(data.slice(48, 80)),
|
|
844
|
+
paused: data[80] !== 0,
|
|
845
|
+
pendingAuthority: new PublicKey4(data.slice(81, 113)),
|
|
846
|
+
pendingAuthorityActivatedAt: new BN3(data.slice(113, 121), "le")
|
|
798
847
|
};
|
|
799
848
|
} catch {
|
|
800
849
|
return null;
|
|
@@ -867,7 +916,11 @@ var GovernanceClient = class {
|
|
|
867
916
|
proposal: new PublicKey4(data.slice(40, 72)),
|
|
868
917
|
votePower: new BN3(data.slice(72, 80), "le"),
|
|
869
918
|
support: data[80] !== 0,
|
|
870
|
-
votedAt: new BN3(data.slice(81, 89), "le")
|
|
919
|
+
votedAt: new BN3(data.slice(81, 89), "le"),
|
|
920
|
+
// Private vote ciphertexts (if present, at byte offsets after public vote fields)
|
|
921
|
+
ctFor: new Uint8Array(data.slice(89, 153)),
|
|
922
|
+
ctAgainst: new Uint8Array(data.slice(153, 217)),
|
|
923
|
+
ctAbstain: new Uint8Array(data.slice(217, 281))
|
|
871
924
|
};
|
|
872
925
|
} catch {
|
|
873
926
|
return null;
|
|
@@ -882,6 +935,9 @@ var GovernanceClient = class {
|
|
|
882
935
|
}
|
|
883
936
|
/**
|
|
884
937
|
* Calculate user's voting power
|
|
938
|
+
*
|
|
939
|
+
* @note M-01 fix: 5A boost formula is now `1000 + ((five_a_score * 100) / 1000)`
|
|
940
|
+
* to fix precision loss for small scores.
|
|
885
941
|
*/
|
|
886
942
|
async getVotingPower(user) {
|
|
887
943
|
const target = user || this.client.publicKey;
|
|
@@ -924,6 +980,9 @@ var GovernanceClient = class {
|
|
|
924
980
|
}
|
|
925
981
|
/**
|
|
926
982
|
* Get proposal progress
|
|
983
|
+
*
|
|
984
|
+
* @note C-03: Quorum is now calculated as votesFor + votesAgainst only.
|
|
985
|
+
* Abstains do NOT count toward quorum.
|
|
927
986
|
*/
|
|
928
987
|
async getProposalProgress(proposalId) {
|
|
929
988
|
const proposal = await this.getProposal(proposalId);
|
|
@@ -947,7 +1006,7 @@ var GovernanceClient = class {
|
|
|
947
1006
|
timeRemaining
|
|
948
1007
|
};
|
|
949
1008
|
}
|
|
950
|
-
// ============ Transaction Building ============
|
|
1009
|
+
// ============ Transaction Building (Public Voting) ============
|
|
951
1010
|
/**
|
|
952
1011
|
* Build create proposal transaction
|
|
953
1012
|
*/
|
|
@@ -963,12 +1022,12 @@ var GovernanceClient = class {
|
|
|
963
1022
|
return tx;
|
|
964
1023
|
}
|
|
965
1024
|
/**
|
|
966
|
-
* Build vote transaction
|
|
967
|
-
*
|
|
968
|
-
* @note v2.8.0 (C-NEW-01): Voting power parameters (vevcoin_balance, five_a_score, tier)
|
|
1025
|
+
* Build vote transaction (public)
|
|
1026
|
+
*
|
|
1027
|
+
* @note v2.8.0 (C-NEW-01): Voting power parameters (vevcoin_balance, five_a_score, tier)
|
|
969
1028
|
* are now read from on-chain state, not passed as parameters. This prevents vote manipulation.
|
|
970
1029
|
* The transaction only needs: proposal_id and choice (VoteChoice enum)
|
|
971
|
-
*
|
|
1030
|
+
*
|
|
972
1031
|
* @param proposalId - The proposal to vote on
|
|
973
1032
|
* @param support - true = For, false = Against (use VoteChoice for more options)
|
|
974
1033
|
*/
|
|
@@ -998,6 +1057,217 @@ var GovernanceClient = class {
|
|
|
998
1057
|
const tx = new Transaction3();
|
|
999
1058
|
return tx;
|
|
1000
1059
|
}
|
|
1060
|
+
// ============ Vote Delegation (H-NEW-03) ============
|
|
1061
|
+
/**
|
|
1062
|
+
* Get delegation for a user
|
|
1063
|
+
*/
|
|
1064
|
+
async getDelegation(delegator) {
|
|
1065
|
+
const target = delegator || this.client.publicKey;
|
|
1066
|
+
if (!target) {
|
|
1067
|
+
throw new Error("No user specified and wallet not connected");
|
|
1068
|
+
}
|
|
1069
|
+
try {
|
|
1070
|
+
const delegationPda = this.client.pdas.getDelegation(target);
|
|
1071
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(delegationPda);
|
|
1072
|
+
if (!accountInfo) {
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
const data = accountInfo.data;
|
|
1076
|
+
return {
|
|
1077
|
+
delegator: new PublicKey4(data.slice(8, 40)),
|
|
1078
|
+
delegate: new PublicKey4(data.slice(40, 72)),
|
|
1079
|
+
delegationType: data[72],
|
|
1080
|
+
categories: data[73],
|
|
1081
|
+
delegatedAmount: new BN3(data.slice(74, 82), "le"),
|
|
1082
|
+
delegatedAt: new BN3(data.slice(82, 90), "le"),
|
|
1083
|
+
expiresAt: new BN3(data.slice(90, 98), "le"),
|
|
1084
|
+
revocable: data[98] !== 0
|
|
1085
|
+
};
|
|
1086
|
+
} catch {
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Build delegate votes transaction
|
|
1092
|
+
*
|
|
1093
|
+
* H-NEW-03: Validates delegation amount against actual veVCoin balance on-chain.
|
|
1094
|
+
* The delegated amount must be <= the delegator's veVCoin balance from the staking protocol.
|
|
1095
|
+
*
|
|
1096
|
+
* @param params - Delegation parameters
|
|
1097
|
+
*/
|
|
1098
|
+
async buildDelegateVotesTransaction(params) {
|
|
1099
|
+
if (!this.client.publicKey) {
|
|
1100
|
+
throw new Error("Wallet not connected");
|
|
1101
|
+
}
|
|
1102
|
+
const delegationPda = this.client.pdas.getDelegation(this.client.publicKey);
|
|
1103
|
+
const delegateStatsPda = this.client.pdas.getDelegateStats(params.delegate);
|
|
1104
|
+
const configPda = this.client.pdas.getGovernanceConfig();
|
|
1105
|
+
const userStakePda = this.client.pdas.getUserStake(this.client.publicKey);
|
|
1106
|
+
const existing = await this.getDelegation();
|
|
1107
|
+
if (existing) {
|
|
1108
|
+
throw new Error("Active delegation already exists. Revoke it first.");
|
|
1109
|
+
}
|
|
1110
|
+
const tx = new Transaction3();
|
|
1111
|
+
return tx;
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Build revoke delegation transaction
|
|
1115
|
+
*/
|
|
1116
|
+
async buildRevokeDelegationTransaction() {
|
|
1117
|
+
if (!this.client.publicKey) {
|
|
1118
|
+
throw new Error("Wallet not connected");
|
|
1119
|
+
}
|
|
1120
|
+
const existing = await this.getDelegation();
|
|
1121
|
+
if (!existing) {
|
|
1122
|
+
throw new Error("No active delegation to revoke");
|
|
1123
|
+
}
|
|
1124
|
+
const delegationPda = this.client.pdas.getDelegation(this.client.publicKey);
|
|
1125
|
+
const delegateStatsPda = this.client.pdas.getDelegateStats(existing.delegate);
|
|
1126
|
+
const tx = new Transaction3();
|
|
1127
|
+
return tx;
|
|
1128
|
+
}
|
|
1129
|
+
// ============ ZK Private Voting ============
|
|
1130
|
+
/**
|
|
1131
|
+
* Build cast private vote transaction
|
|
1132
|
+
*
|
|
1133
|
+
* Uses Twisted ElGamal encryption on Ristretto255 with compressed sigma proofs.
|
|
1134
|
+
* The voter encrypts their choice into 3 ciphertexts (for/against/abstain) and
|
|
1135
|
+
* generates a validity proof that exactly one ciphertext encrypts their weight.
|
|
1136
|
+
*
|
|
1137
|
+
* Use the `zk-voting-sdk` crate to generate ciphertexts and proofs off-chain:
|
|
1138
|
+
* ```rust
|
|
1139
|
+
* let (ct_for, ct_against, ct_abstain, proof) = encrypt_and_prove(&pubkey, choice, weight);
|
|
1140
|
+
* ```
|
|
1141
|
+
*
|
|
1142
|
+
* @param params - Private vote parameters with ciphertexts and proof
|
|
1143
|
+
*/
|
|
1144
|
+
async buildCastPrivateVoteTransaction(params) {
|
|
1145
|
+
if (!this.client.publicKey) {
|
|
1146
|
+
throw new Error("Wallet not connected");
|
|
1147
|
+
}
|
|
1148
|
+
if (params.ctFor.length !== GOVERNANCE_CONSTANTS.zk.ciphertextSize) {
|
|
1149
|
+
throw new Error(`ctFor must be ${GOVERNANCE_CONSTANTS.zk.ciphertextSize} bytes`);
|
|
1150
|
+
}
|
|
1151
|
+
if (params.ctAgainst.length !== GOVERNANCE_CONSTANTS.zk.ciphertextSize) {
|
|
1152
|
+
throw new Error(`ctAgainst must be ${GOVERNANCE_CONSTANTS.zk.ciphertextSize} bytes`);
|
|
1153
|
+
}
|
|
1154
|
+
if (params.ctAbstain.length !== GOVERNANCE_CONSTANTS.zk.ciphertextSize) {
|
|
1155
|
+
throw new Error(`ctAbstain must be ${GOVERNANCE_CONSTANTS.zk.ciphertextSize} bytes`);
|
|
1156
|
+
}
|
|
1157
|
+
if (params.proofData.length !== GOVERNANCE_CONSTANTS.zk.voteProofSize) {
|
|
1158
|
+
throw new Error(`proofData must be ${GOVERNANCE_CONSTANTS.zk.voteProofSize} bytes`);
|
|
1159
|
+
}
|
|
1160
|
+
const hasVoted = await this.hasVoted(params.proposalId);
|
|
1161
|
+
if (hasVoted) {
|
|
1162
|
+
throw new Error("Already voted on this proposal");
|
|
1163
|
+
}
|
|
1164
|
+
const tx = new Transaction3();
|
|
1165
|
+
return tx;
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Build enable private voting transaction
|
|
1169
|
+
*
|
|
1170
|
+
* Called by the governance authority to enable ZK private voting on a proposal.
|
|
1171
|
+
* Requires specifying the decryption committee and their ElGamal public keys.
|
|
1172
|
+
*
|
|
1173
|
+
* @param params - Private voting configuration
|
|
1174
|
+
*/
|
|
1175
|
+
async buildEnablePrivateVotingTransaction(params) {
|
|
1176
|
+
if (!this.client.publicKey) {
|
|
1177
|
+
throw new Error("Wallet not connected");
|
|
1178
|
+
}
|
|
1179
|
+
if (params.encryptionPubkey.length !== 32) {
|
|
1180
|
+
throw new Error("encryptionPubkey must be 32 bytes (Ristretto255 point)");
|
|
1181
|
+
}
|
|
1182
|
+
if (params.committeeSize > GOVERNANCE_CONSTANTS.zk.maxCommitteeSize) {
|
|
1183
|
+
throw new Error(`Committee size exceeds max of ${GOVERNANCE_CONSTANTS.zk.maxCommitteeSize}`);
|
|
1184
|
+
}
|
|
1185
|
+
if (params.decryptionThreshold > params.committeeSize) {
|
|
1186
|
+
throw new Error("Threshold cannot exceed committee size");
|
|
1187
|
+
}
|
|
1188
|
+
for (let i = 0; i < params.committeeSize; i++) {
|
|
1189
|
+
if (!params.committeeElgamalPubkeys[i] || params.committeeElgamalPubkeys[i].length !== 32) {
|
|
1190
|
+
throw new Error(`Committee member ${i} ElGamal pubkey must be 32 bytes`);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
const tx = new Transaction3();
|
|
1194
|
+
return tx;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Build submit decryption share transaction
|
|
1198
|
+
*
|
|
1199
|
+
* Called by a committee member during the reveal phase.
|
|
1200
|
+
* Each member computes partial decryptions and a DLEQ proof off-chain:
|
|
1201
|
+
* ```rust
|
|
1202
|
+
* let partial = generate_partial_decryption(&secret_share, &pk, &r_for, &r_against, &r_abstain);
|
|
1203
|
+
* ```
|
|
1204
|
+
*
|
|
1205
|
+
* @param params - Decryption share with DLEQ proof
|
|
1206
|
+
*/
|
|
1207
|
+
async buildSubmitDecryptionShareTransaction(params) {
|
|
1208
|
+
if (!this.client.publicKey) {
|
|
1209
|
+
throw new Error("Wallet not connected");
|
|
1210
|
+
}
|
|
1211
|
+
const fields = [
|
|
1212
|
+
{ name: "partialFor", value: params.partialFor },
|
|
1213
|
+
{ name: "partialAgainst", value: params.partialAgainst },
|
|
1214
|
+
{ name: "partialAbstain", value: params.partialAbstain },
|
|
1215
|
+
{ name: "dleqChallenge", value: params.dleqChallenge },
|
|
1216
|
+
{ name: "dleqResponse", value: params.dleqResponse }
|
|
1217
|
+
];
|
|
1218
|
+
for (const { name, value } of fields) {
|
|
1219
|
+
if (value.length !== 32) {
|
|
1220
|
+
throw new Error(`${name} must be 32 bytes`);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
const tx = new Transaction3();
|
|
1224
|
+
return tx;
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Build aggregate revealed votes transaction (permissionless)
|
|
1228
|
+
*
|
|
1229
|
+
* Anyone can submit the aggregated tally since the on-chain program
|
|
1230
|
+
* cryptographically verifies: tally * H == C_sum - D for each category.
|
|
1231
|
+
* This prevents fabrication of results.
|
|
1232
|
+
*
|
|
1233
|
+
* Use the `zk-voting-sdk` to compute the tally off-chain:
|
|
1234
|
+
* ```rust
|
|
1235
|
+
* let lagrange = compute_lagrange_coefficients(&selected_indices);
|
|
1236
|
+
* let d = combine_partials(&lagrange, &partials);
|
|
1237
|
+
* let tally = recover_tally(&d, &accumulated_c, max_votes).unwrap();
|
|
1238
|
+
* ```
|
|
1239
|
+
*
|
|
1240
|
+
* @param params - Tally values and Lagrange coefficients
|
|
1241
|
+
*/
|
|
1242
|
+
async buildAggregateRevealedVotesTransaction(params) {
|
|
1243
|
+
for (let i = 0; i < params.lagrangeCoefficients.length; i++) {
|
|
1244
|
+
if (params.lagrangeCoefficients[i].length !== 32) {
|
|
1245
|
+
throw new Error(`Lagrange coefficient ${i} must be 32 bytes`);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
const tx = new Transaction3();
|
|
1249
|
+
return tx;
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Check if authority transfer timelock has elapsed (H-02)
|
|
1253
|
+
*/
|
|
1254
|
+
async canAcceptAuthority() {
|
|
1255
|
+
const config = await this.getConfig();
|
|
1256
|
+
if (!config) {
|
|
1257
|
+
return { canAccept: false, reason: "Config not found" };
|
|
1258
|
+
}
|
|
1259
|
+
if (!config.pendingAuthorityActivatedAt || config.pendingAuthorityActivatedAt.isZero()) {
|
|
1260
|
+
return { canAccept: false, reason: "No pending authority transfer" };
|
|
1261
|
+
}
|
|
1262
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1263
|
+
const timelockEnd = config.pendingAuthorityActivatedAt.toNumber() + GOVERNANCE_CONSTANTS.authorityTransferTimelock;
|
|
1264
|
+
if (now < timelockEnd) {
|
|
1265
|
+
const remaining = timelockEnd - now;
|
|
1266
|
+
const hours = Math.ceil(remaining / 3600);
|
|
1267
|
+
return { canAccept: false, reason: `Timelock: ${hours} hours remaining` };
|
|
1268
|
+
}
|
|
1269
|
+
return { canAccept: true };
|
|
1270
|
+
}
|
|
1001
1271
|
};
|
|
1002
1272
|
|
|
1003
1273
|
// src/rewards/index.ts
|
|
@@ -1137,9 +1407,16 @@ var RewardsClient = class {
|
|
|
1137
1407
|
}
|
|
1138
1408
|
/**
|
|
1139
1409
|
* Calculate gasless fee for claim
|
|
1410
|
+
*
|
|
1411
|
+
* C-05: Uses ceiling division to prevent fee rounding to zero on small amounts.
|
|
1412
|
+
* Formula: fee = ceil(amount * feeBps / 10000)
|
|
1140
1413
|
*/
|
|
1141
1414
|
calculateGaslessFee(amount) {
|
|
1142
|
-
const
|
|
1415
|
+
const numerator = amount.muln(SSCRE_CONSTANTS.gaslessFeeBps).addn(9999);
|
|
1416
|
+
let fee = numerator.divn(1e4);
|
|
1417
|
+
if (fee.gt(amount)) {
|
|
1418
|
+
fee = amount;
|
|
1419
|
+
}
|
|
1143
1420
|
return fee;
|
|
1144
1421
|
}
|
|
1145
1422
|
/**
|
|
@@ -1196,6 +1473,9 @@ var RewardsClient = class {
|
|
|
1196
1473
|
// ============ Transaction Building ============
|
|
1197
1474
|
/**
|
|
1198
1475
|
* Build claim rewards transaction
|
|
1476
|
+
*
|
|
1477
|
+
* H-NEW-02: Merkle proof size is limited to 32 levels (supports 4B+ users).
|
|
1478
|
+
* Proofs exceeding this limit will be rejected on-chain with MerkleProofTooLarge.
|
|
1199
1479
|
*/
|
|
1200
1480
|
async buildClaimTransaction(params) {
|
|
1201
1481
|
if (!this.client.publicKey) {
|
|
@@ -1204,6 +1484,9 @@ var RewardsClient = class {
|
|
|
1204
1484
|
if (params.amount.lt(new BN4(SSCRE_CONSTANTS.minClaimAmount * 1e9))) {
|
|
1205
1485
|
throw new Error(`Claim amount below minimum: ${SSCRE_CONSTANTS.minClaimAmount} VCoin`);
|
|
1206
1486
|
}
|
|
1487
|
+
if (params.merkleProof.length > 32) {
|
|
1488
|
+
throw new Error("Merkle proof exceeds maximum size of 32 levels");
|
|
1489
|
+
}
|
|
1207
1490
|
const hasClaimed = await this.hasClaimedEpoch(params.epoch);
|
|
1208
1491
|
if (hasClaimed) {
|
|
1209
1492
|
throw new Error("Already claimed for this epoch");
|
|
@@ -1365,9 +1648,16 @@ var ViLinkClient = class {
|
|
|
1365
1648
|
}
|
|
1366
1649
|
/**
|
|
1367
1650
|
* Calculate platform fee for tip
|
|
1651
|
+
*
|
|
1652
|
+
* C-06: Uses ceiling division to prevent fee rounding to zero on small amounts.
|
|
1653
|
+
* Formula: fee = ceil(amount * feeBps / 10000)
|
|
1368
1654
|
*/
|
|
1369
1655
|
calculateFee(amount) {
|
|
1370
|
-
const
|
|
1656
|
+
const numerator = amount.muln(VILINK_CONSTANTS.platformFeeBps).addn(9999);
|
|
1657
|
+
let fee = numerator.divn(1e4);
|
|
1658
|
+
if (fee.gt(amount)) {
|
|
1659
|
+
fee = amount;
|
|
1660
|
+
}
|
|
1371
1661
|
return {
|
|
1372
1662
|
fee,
|
|
1373
1663
|
net: amount.sub(fee)
|
|
@@ -1449,6 +1739,13 @@ var ViLinkClient = class {
|
|
|
1449
1739
|
* Build execute tip action transaction
|
|
1450
1740
|
* @param creator - The action creator's public key
|
|
1451
1741
|
* @param nonce - M-04: The action nonce (NOT timestamp)
|
|
1742
|
+
*
|
|
1743
|
+
* H-05: The on-chain handler validates that the executor's token account has
|
|
1744
|
+
* no active delegation (delegate is None or delegated_amount is 0).
|
|
1745
|
+
* This prevents delegated tokens from being spent without explicit approval.
|
|
1746
|
+
*
|
|
1747
|
+
* C-06: Platform fee uses ceiling division to prevent zero-fee exploitation on
|
|
1748
|
+
* small amounts.
|
|
1452
1749
|
*/
|
|
1453
1750
|
async buildExecuteTipAction(creator, nonce) {
|
|
1454
1751
|
if (!this.client.publicKey) {
|
|
@@ -1556,6 +1853,9 @@ var GaslessClient = class {
|
|
|
1556
1853
|
}
|
|
1557
1854
|
/**
|
|
1558
1855
|
* Get user gasless statistics
|
|
1856
|
+
*
|
|
1857
|
+
* H-AUDIT-12: Now includes active_sessions field for per-user session limit tracking.
|
|
1858
|
+
* The protocol enforces a maximum of 5 concurrent active sessions per user.
|
|
1559
1859
|
*/
|
|
1560
1860
|
async getUserStats(user) {
|
|
1561
1861
|
const target = user || this.client.publicKey;
|
|
@@ -1575,7 +1875,8 @@ var GaslessClient = class {
|
|
|
1575
1875
|
totalSubsidized: new BN6(data.slice(48, 56), "le"),
|
|
1576
1876
|
totalVcoinFees: new BN6(data.slice(56, 64), "le"),
|
|
1577
1877
|
sessionsCreated: data.readUInt32LE(72),
|
|
1578
|
-
|
|
1878
|
+
activeSessions: data[76],
|
|
1879
|
+
activeSession: new PublicKey7(data.slice(77, 109))
|
|
1579
1880
|
};
|
|
1580
1881
|
} catch (error) {
|
|
1581
1882
|
console.warn("[ViWoSDK] gasless.getUserStats failed:", error instanceof Error ? error.message : error);
|
|
@@ -1712,6 +2013,9 @@ var GaslessClient = class {
|
|
|
1712
2013
|
// ============ Transaction Building ============
|
|
1713
2014
|
/**
|
|
1714
2015
|
* Build create session key transaction
|
|
2016
|
+
*
|
|
2017
|
+
* H-AUDIT-12: The protocol enforces a maximum of 5 concurrent active sessions per user.
|
|
2018
|
+
* Creating a session when the limit is reached will fail with MaxSessionsReached error.
|
|
1715
2019
|
*/
|
|
1716
2020
|
async buildCreateSessionTransaction(params) {
|
|
1717
2021
|
if (!this.client.publicKey) {
|
|
@@ -1723,6 +2027,10 @@ var GaslessClient = class {
|
|
|
1723
2027
|
if (!params.scope || params.scope === 0) {
|
|
1724
2028
|
throw new Error("At least one scope required");
|
|
1725
2029
|
}
|
|
2030
|
+
const stats = await this.getUserStats();
|
|
2031
|
+
if (stats && stats.activeSessions >= 5) {
|
|
2032
|
+
throw new Error("Maximum active sessions reached (5). Revoke an existing session first.");
|
|
2033
|
+
}
|
|
1726
2034
|
const duration = params.durationSeconds || GASLESS_CONSTANTS.sessionDuration;
|
|
1727
2035
|
const maxActions = params.maxActions || GASLESS_CONSTANTS.maxSessionActions;
|
|
1728
2036
|
const maxSpend = params.maxSpend || new BN6(GASLESS_CONSTANTS.maxSessionSpend * 1e9);
|
|
@@ -1833,6 +2141,23 @@ var IdentityClient = class {
|
|
|
1833
2141
|
return benefits[level] || [];
|
|
1834
2142
|
}
|
|
1835
2143
|
// ============ Transaction Building ============
|
|
2144
|
+
/**
|
|
2145
|
+
* Build subscribe transaction
|
|
2146
|
+
*
|
|
2147
|
+
* C-AUDIT-22: Non-free subscription tiers require actual USDC payment via SPL
|
|
2148
|
+
* transfer_checked. The transaction must include the user's USDC token account,
|
|
2149
|
+
* the USDC mint, and the treasury token account.
|
|
2150
|
+
*/
|
|
2151
|
+
async buildSubscribeTransaction(tier) {
|
|
2152
|
+
if (!this.client.publicKey) {
|
|
2153
|
+
throw new Error("Wallet not connected");
|
|
2154
|
+
}
|
|
2155
|
+
if (tier < 0 || tier > 4) {
|
|
2156
|
+
throw new Error("Invalid subscription tier (0-4)");
|
|
2157
|
+
}
|
|
2158
|
+
const tx = new Transaction7();
|
|
2159
|
+
return tx;
|
|
2160
|
+
}
|
|
1836
2161
|
/**
|
|
1837
2162
|
* Build create identity transaction
|
|
1838
2163
|
*/
|
|
@@ -1985,6 +2310,12 @@ var FiveAClient = class {
|
|
|
1985
2310
|
}
|
|
1986
2311
|
/**
|
|
1987
2312
|
* Check if user can vouch for another
|
|
2313
|
+
*
|
|
2314
|
+
* C-08: Mutual vouching is prevented — if the target has already vouched for you,
|
|
2315
|
+
* you cannot vouch for them. This is enforced on-chain via reverse_vouch_record check.
|
|
2316
|
+
*
|
|
2317
|
+
* M-18: Vouches expire after 1 year (MAX_VOUCH_AGE = 365 days). Expired vouches
|
|
2318
|
+
* cannot be evaluated and must be re-created.
|
|
1988
2319
|
*/
|
|
1989
2320
|
async canVouchFor(target) {
|
|
1990
2321
|
if (!this.client.publicKey) {
|
|
@@ -2027,6 +2358,12 @@ var FiveAClient = class {
|
|
|
2027
2358
|
// ============ Transaction Building ============
|
|
2028
2359
|
/**
|
|
2029
2360
|
* Build vouch transaction
|
|
2361
|
+
*
|
|
2362
|
+
* C-08: On-chain handler requires a reverse_vouch_record account to verify
|
|
2363
|
+
* mutual vouching is not occurring. The transaction must include this PDA.
|
|
2364
|
+
*
|
|
2365
|
+
* M-18: Vouches have a maximum age of 1 year. After that, evaluate_vouch
|
|
2366
|
+
* will reject with VouchExpired error.
|
|
2030
2367
|
*/
|
|
2031
2368
|
async buildVouchTransaction(target) {
|
|
2032
2369
|
if (!this.client.publicKey) {
|
|
@@ -2195,6 +2532,9 @@ var ContentClient = class {
|
|
|
2195
2532
|
}
|
|
2196
2533
|
/**
|
|
2197
2534
|
* Get content stats
|
|
2535
|
+
*
|
|
2536
|
+
* C-AUDIT-10: Engagement scores can only increase — the on-chain update_engagement
|
|
2537
|
+
* instruction enforces monotonic increase to prevent manipulation.
|
|
2198
2538
|
*/
|
|
2199
2539
|
async getContentStats(contentId) {
|
|
2200
2540
|
const content = await this.getContent(contentId);
|
|
@@ -2214,6 +2554,9 @@ var ContentClient = class {
|
|
|
2214
2554
|
// ============ Transaction Building ============
|
|
2215
2555
|
/**
|
|
2216
2556
|
* Build create content transaction
|
|
2557
|
+
*
|
|
2558
|
+
* C-AUDIT-11: Energy tiers are validated on-chain (valid range: 1-4).
|
|
2559
|
+
* Tier determines max energy and regen rate.
|
|
2217
2560
|
*/
|
|
2218
2561
|
async buildCreateContentTransaction(contentHash) {
|
|
2219
2562
|
if (!this.client.publicKey) {
|