@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.js
CHANGED
|
@@ -273,9 +273,22 @@ var GOVERNANCE_CONSTANTS = {
|
|
|
273
273
|
vetoWindow: 24 * 3600,
|
|
274
274
|
// 1 day
|
|
275
275
|
quorumBps: 400,
|
|
276
|
-
// 4%
|
|
277
|
-
|
|
278
|
-
|
|
276
|
+
// 4% (C-03: abstains no longer count toward quorum)
|
|
277
|
+
/** Authority transfer timelock (H-02) */
|
|
278
|
+
authorityTransferTimelock: 24 * 3600,
|
|
279
|
+
// 24 hours
|
|
280
|
+
/** ZK private voting constants */
|
|
281
|
+
zk: {
|
|
282
|
+
/** Vote validity proof size (3 OR proofs + 1 sum proof) */
|
|
283
|
+
voteProofSize: 352,
|
|
284
|
+
/** ElGamal ciphertext size (R || C) */
|
|
285
|
+
ciphertextSize: 64,
|
|
286
|
+
/** Max committee members */
|
|
287
|
+
maxCommitteeSize: 5,
|
|
288
|
+
/** Account sizes */
|
|
289
|
+
privateVotingConfigSize: 680,
|
|
290
|
+
decryptionShareSize: 242
|
|
291
|
+
}
|
|
279
292
|
};
|
|
280
293
|
var SECURITY_CONSTANTS = {
|
|
281
294
|
// H-02: Two-step authority transfer
|
|
@@ -310,8 +323,21 @@ var SECURITY_CONSTANTS = {
|
|
|
310
323
|
// H-NEW-02: Max proof levels (supports 4B+ users)
|
|
311
324
|
maxEpochBitmap: 1023,
|
|
312
325
|
// H-NEW-04: Max epoch with bitmap storage (85+ years)
|
|
313
|
-
votingPowerVerifiedOnChain: true
|
|
326
|
+
votingPowerVerifiedOnChain: true,
|
|
314
327
|
// C-NEW-01: Params read from chain, not passed
|
|
328
|
+
// Audit remediation additions
|
|
329
|
+
/** H-AUDIT-12: Maximum concurrent sessions per user */
|
|
330
|
+
maxSessionsPerUser: 5,
|
|
331
|
+
/** C-AUDIT-15: Maximum day delta for daily budget reset (clock skew protection) */
|
|
332
|
+
maxDayDelta: 2,
|
|
333
|
+
/** H-AUDIT-09: Maximum daily activity score for transfer hook */
|
|
334
|
+
maxDailyActivityScore: 5e3,
|
|
335
|
+
/** M-AUDIT-10: Wash flag decay period (7 days) */
|
|
336
|
+
washFlagDecayPeriod: 7 * 24 * 3600,
|
|
337
|
+
/** M-18: Maximum vouch age before expiry (1 year) */
|
|
338
|
+
maxVouchAge: 365 * 24 * 3600,
|
|
339
|
+
/** C-AUDIT-11: Valid content energy tiers (1-4) */
|
|
340
|
+
maxContentTier: 4
|
|
315
341
|
};
|
|
316
342
|
var VALID_URI_PREFIXES = ["ipfs://", "https://", "ar://"];
|
|
317
343
|
var MAX_URI_LENGTH = 128;
|
|
@@ -409,6 +435,22 @@ var PDAs = class {
|
|
|
409
435
|
);
|
|
410
436
|
return pda;
|
|
411
437
|
}
|
|
438
|
+
/** H-NEW-03: Delegation PDA (one per delegator) */
|
|
439
|
+
getDelegation(delegator) {
|
|
440
|
+
const [pda] = import_web32.PublicKey.findProgramAddressSync(
|
|
441
|
+
[Buffer.from(SEEDS.delegation), delegator.toBuffer()],
|
|
442
|
+
this.programIds.governanceProtocol
|
|
443
|
+
);
|
|
444
|
+
return pda;
|
|
445
|
+
}
|
|
446
|
+
/** H-NEW-03: Delegate stats PDA (one per delegate) */
|
|
447
|
+
getDelegateStats(delegate) {
|
|
448
|
+
const [pda] = import_web32.PublicKey.findProgramAddressSync(
|
|
449
|
+
[Buffer.from("delegate-stats"), delegate.toBuffer()],
|
|
450
|
+
this.programIds.governanceProtocol
|
|
451
|
+
);
|
|
452
|
+
return pda;
|
|
453
|
+
}
|
|
412
454
|
// SSCRE PDAs
|
|
413
455
|
getRewardsPoolConfig() {
|
|
414
456
|
const [pda] = import_web32.PublicKey.findProgramAddressSync(
|
|
@@ -748,6 +790,10 @@ var StakingClient = class {
|
|
|
748
790
|
}
|
|
749
791
|
/**
|
|
750
792
|
* Get tier name
|
|
793
|
+
*
|
|
794
|
+
* M-05: The on-chain update_tier instruction will reject no-op tier updates
|
|
795
|
+
* with TierUnchanged error. Only call updateTier when the user's stake amount
|
|
796
|
+
* actually qualifies for a different tier.
|
|
751
797
|
*/
|
|
752
798
|
getTierName(tier) {
|
|
753
799
|
const names = ["None", "Bronze", "Silver", "Gold", "Platinum"];
|
|
@@ -859,11 +905,14 @@ var GovernanceClient = class {
|
|
|
859
905
|
if (!accountInfo) {
|
|
860
906
|
return null;
|
|
861
907
|
}
|
|
908
|
+
const data = accountInfo.data;
|
|
862
909
|
return {
|
|
863
|
-
authority: new import_web34.PublicKey(
|
|
864
|
-
proposalCount: new import_anchor3.BN(
|
|
865
|
-
vevcoinMint: new import_web34.PublicKey(
|
|
866
|
-
paused:
|
|
910
|
+
authority: new import_web34.PublicKey(data.slice(8, 40)),
|
|
911
|
+
proposalCount: new import_anchor3.BN(data.slice(40, 48), "le"),
|
|
912
|
+
vevcoinMint: new import_web34.PublicKey(data.slice(48, 80)),
|
|
913
|
+
paused: data[80] !== 0,
|
|
914
|
+
pendingAuthority: new import_web34.PublicKey(data.slice(81, 113)),
|
|
915
|
+
pendingAuthorityActivatedAt: new import_anchor3.BN(data.slice(113, 121), "le")
|
|
867
916
|
};
|
|
868
917
|
} catch {
|
|
869
918
|
return null;
|
|
@@ -936,7 +985,11 @@ var GovernanceClient = class {
|
|
|
936
985
|
proposal: new import_web34.PublicKey(data.slice(40, 72)),
|
|
937
986
|
votePower: new import_anchor3.BN(data.slice(72, 80), "le"),
|
|
938
987
|
support: data[80] !== 0,
|
|
939
|
-
votedAt: new import_anchor3.BN(data.slice(81, 89), "le")
|
|
988
|
+
votedAt: new import_anchor3.BN(data.slice(81, 89), "le"),
|
|
989
|
+
// Private vote ciphertexts (if present, at byte offsets after public vote fields)
|
|
990
|
+
ctFor: new Uint8Array(data.slice(89, 153)),
|
|
991
|
+
ctAgainst: new Uint8Array(data.slice(153, 217)),
|
|
992
|
+
ctAbstain: new Uint8Array(data.slice(217, 281))
|
|
940
993
|
};
|
|
941
994
|
} catch {
|
|
942
995
|
return null;
|
|
@@ -951,6 +1004,9 @@ var GovernanceClient = class {
|
|
|
951
1004
|
}
|
|
952
1005
|
/**
|
|
953
1006
|
* Calculate user's voting power
|
|
1007
|
+
*
|
|
1008
|
+
* @note M-01 fix: 5A boost formula is now `1000 + ((five_a_score * 100) / 1000)`
|
|
1009
|
+
* to fix precision loss for small scores.
|
|
954
1010
|
*/
|
|
955
1011
|
async getVotingPower(user) {
|
|
956
1012
|
const target = user || this.client.publicKey;
|
|
@@ -993,6 +1049,9 @@ var GovernanceClient = class {
|
|
|
993
1049
|
}
|
|
994
1050
|
/**
|
|
995
1051
|
* Get proposal progress
|
|
1052
|
+
*
|
|
1053
|
+
* @note C-03: Quorum is now calculated as votesFor + votesAgainst only.
|
|
1054
|
+
* Abstains do NOT count toward quorum.
|
|
996
1055
|
*/
|
|
997
1056
|
async getProposalProgress(proposalId) {
|
|
998
1057
|
const proposal = await this.getProposal(proposalId);
|
|
@@ -1016,7 +1075,7 @@ var GovernanceClient = class {
|
|
|
1016
1075
|
timeRemaining
|
|
1017
1076
|
};
|
|
1018
1077
|
}
|
|
1019
|
-
// ============ Transaction Building ============
|
|
1078
|
+
// ============ Transaction Building (Public Voting) ============
|
|
1020
1079
|
/**
|
|
1021
1080
|
* Build create proposal transaction
|
|
1022
1081
|
*/
|
|
@@ -1032,12 +1091,12 @@ var GovernanceClient = class {
|
|
|
1032
1091
|
return tx;
|
|
1033
1092
|
}
|
|
1034
1093
|
/**
|
|
1035
|
-
* Build vote transaction
|
|
1036
|
-
*
|
|
1037
|
-
* @note v2.8.0 (C-NEW-01): Voting power parameters (vevcoin_balance, five_a_score, tier)
|
|
1094
|
+
* Build vote transaction (public)
|
|
1095
|
+
*
|
|
1096
|
+
* @note v2.8.0 (C-NEW-01): Voting power parameters (vevcoin_balance, five_a_score, tier)
|
|
1038
1097
|
* are now read from on-chain state, not passed as parameters. This prevents vote manipulation.
|
|
1039
1098
|
* The transaction only needs: proposal_id and choice (VoteChoice enum)
|
|
1040
|
-
*
|
|
1099
|
+
*
|
|
1041
1100
|
* @param proposalId - The proposal to vote on
|
|
1042
1101
|
* @param support - true = For, false = Against (use VoteChoice for more options)
|
|
1043
1102
|
*/
|
|
@@ -1067,6 +1126,217 @@ var GovernanceClient = class {
|
|
|
1067
1126
|
const tx = new import_web34.Transaction();
|
|
1068
1127
|
return tx;
|
|
1069
1128
|
}
|
|
1129
|
+
// ============ Vote Delegation (H-NEW-03) ============
|
|
1130
|
+
/**
|
|
1131
|
+
* Get delegation for a user
|
|
1132
|
+
*/
|
|
1133
|
+
async getDelegation(delegator) {
|
|
1134
|
+
const target = delegator || this.client.publicKey;
|
|
1135
|
+
if (!target) {
|
|
1136
|
+
throw new Error("No user specified and wallet not connected");
|
|
1137
|
+
}
|
|
1138
|
+
try {
|
|
1139
|
+
const delegationPda = this.client.pdas.getDelegation(target);
|
|
1140
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(delegationPda);
|
|
1141
|
+
if (!accountInfo) {
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
const data = accountInfo.data;
|
|
1145
|
+
return {
|
|
1146
|
+
delegator: new import_web34.PublicKey(data.slice(8, 40)),
|
|
1147
|
+
delegate: new import_web34.PublicKey(data.slice(40, 72)),
|
|
1148
|
+
delegationType: data[72],
|
|
1149
|
+
categories: data[73],
|
|
1150
|
+
delegatedAmount: new import_anchor3.BN(data.slice(74, 82), "le"),
|
|
1151
|
+
delegatedAt: new import_anchor3.BN(data.slice(82, 90), "le"),
|
|
1152
|
+
expiresAt: new import_anchor3.BN(data.slice(90, 98), "le"),
|
|
1153
|
+
revocable: data[98] !== 0
|
|
1154
|
+
};
|
|
1155
|
+
} catch {
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Build delegate votes transaction
|
|
1161
|
+
*
|
|
1162
|
+
* H-NEW-03: Validates delegation amount against actual veVCoin balance on-chain.
|
|
1163
|
+
* The delegated amount must be <= the delegator's veVCoin balance from the staking protocol.
|
|
1164
|
+
*
|
|
1165
|
+
* @param params - Delegation parameters
|
|
1166
|
+
*/
|
|
1167
|
+
async buildDelegateVotesTransaction(params) {
|
|
1168
|
+
if (!this.client.publicKey) {
|
|
1169
|
+
throw new Error("Wallet not connected");
|
|
1170
|
+
}
|
|
1171
|
+
const delegationPda = this.client.pdas.getDelegation(this.client.publicKey);
|
|
1172
|
+
const delegateStatsPda = this.client.pdas.getDelegateStats(params.delegate);
|
|
1173
|
+
const configPda = this.client.pdas.getGovernanceConfig();
|
|
1174
|
+
const userStakePda = this.client.pdas.getUserStake(this.client.publicKey);
|
|
1175
|
+
const existing = await this.getDelegation();
|
|
1176
|
+
if (existing) {
|
|
1177
|
+
throw new Error("Active delegation already exists. Revoke it first.");
|
|
1178
|
+
}
|
|
1179
|
+
const tx = new import_web34.Transaction();
|
|
1180
|
+
return tx;
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Build revoke delegation transaction
|
|
1184
|
+
*/
|
|
1185
|
+
async buildRevokeDelegationTransaction() {
|
|
1186
|
+
if (!this.client.publicKey) {
|
|
1187
|
+
throw new Error("Wallet not connected");
|
|
1188
|
+
}
|
|
1189
|
+
const existing = await this.getDelegation();
|
|
1190
|
+
if (!existing) {
|
|
1191
|
+
throw new Error("No active delegation to revoke");
|
|
1192
|
+
}
|
|
1193
|
+
const delegationPda = this.client.pdas.getDelegation(this.client.publicKey);
|
|
1194
|
+
const delegateStatsPda = this.client.pdas.getDelegateStats(existing.delegate);
|
|
1195
|
+
const tx = new import_web34.Transaction();
|
|
1196
|
+
return tx;
|
|
1197
|
+
}
|
|
1198
|
+
// ============ ZK Private Voting ============
|
|
1199
|
+
/**
|
|
1200
|
+
* Build cast private vote transaction
|
|
1201
|
+
*
|
|
1202
|
+
* Uses Twisted ElGamal encryption on Ristretto255 with compressed sigma proofs.
|
|
1203
|
+
* The voter encrypts their choice into 3 ciphertexts (for/against/abstain) and
|
|
1204
|
+
* generates a validity proof that exactly one ciphertext encrypts their weight.
|
|
1205
|
+
*
|
|
1206
|
+
* Use the `zk-voting-sdk` crate to generate ciphertexts and proofs off-chain:
|
|
1207
|
+
* ```rust
|
|
1208
|
+
* let (ct_for, ct_against, ct_abstain, proof) = encrypt_and_prove(&pubkey, choice, weight);
|
|
1209
|
+
* ```
|
|
1210
|
+
*
|
|
1211
|
+
* @param params - Private vote parameters with ciphertexts and proof
|
|
1212
|
+
*/
|
|
1213
|
+
async buildCastPrivateVoteTransaction(params) {
|
|
1214
|
+
if (!this.client.publicKey) {
|
|
1215
|
+
throw new Error("Wallet not connected");
|
|
1216
|
+
}
|
|
1217
|
+
if (params.ctFor.length !== GOVERNANCE_CONSTANTS.zk.ciphertextSize) {
|
|
1218
|
+
throw new Error(`ctFor must be ${GOVERNANCE_CONSTANTS.zk.ciphertextSize} bytes`);
|
|
1219
|
+
}
|
|
1220
|
+
if (params.ctAgainst.length !== GOVERNANCE_CONSTANTS.zk.ciphertextSize) {
|
|
1221
|
+
throw new Error(`ctAgainst must be ${GOVERNANCE_CONSTANTS.zk.ciphertextSize} bytes`);
|
|
1222
|
+
}
|
|
1223
|
+
if (params.ctAbstain.length !== GOVERNANCE_CONSTANTS.zk.ciphertextSize) {
|
|
1224
|
+
throw new Error(`ctAbstain must be ${GOVERNANCE_CONSTANTS.zk.ciphertextSize} bytes`);
|
|
1225
|
+
}
|
|
1226
|
+
if (params.proofData.length !== GOVERNANCE_CONSTANTS.zk.voteProofSize) {
|
|
1227
|
+
throw new Error(`proofData must be ${GOVERNANCE_CONSTANTS.zk.voteProofSize} bytes`);
|
|
1228
|
+
}
|
|
1229
|
+
const hasVoted = await this.hasVoted(params.proposalId);
|
|
1230
|
+
if (hasVoted) {
|
|
1231
|
+
throw new Error("Already voted on this proposal");
|
|
1232
|
+
}
|
|
1233
|
+
const tx = new import_web34.Transaction();
|
|
1234
|
+
return tx;
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Build enable private voting transaction
|
|
1238
|
+
*
|
|
1239
|
+
* Called by the governance authority to enable ZK private voting on a proposal.
|
|
1240
|
+
* Requires specifying the decryption committee and their ElGamal public keys.
|
|
1241
|
+
*
|
|
1242
|
+
* @param params - Private voting configuration
|
|
1243
|
+
*/
|
|
1244
|
+
async buildEnablePrivateVotingTransaction(params) {
|
|
1245
|
+
if (!this.client.publicKey) {
|
|
1246
|
+
throw new Error("Wallet not connected");
|
|
1247
|
+
}
|
|
1248
|
+
if (params.encryptionPubkey.length !== 32) {
|
|
1249
|
+
throw new Error("encryptionPubkey must be 32 bytes (Ristretto255 point)");
|
|
1250
|
+
}
|
|
1251
|
+
if (params.committeeSize > GOVERNANCE_CONSTANTS.zk.maxCommitteeSize) {
|
|
1252
|
+
throw new Error(`Committee size exceeds max of ${GOVERNANCE_CONSTANTS.zk.maxCommitteeSize}`);
|
|
1253
|
+
}
|
|
1254
|
+
if (params.decryptionThreshold > params.committeeSize) {
|
|
1255
|
+
throw new Error("Threshold cannot exceed committee size");
|
|
1256
|
+
}
|
|
1257
|
+
for (let i = 0; i < params.committeeSize; i++) {
|
|
1258
|
+
if (!params.committeeElgamalPubkeys[i] || params.committeeElgamalPubkeys[i].length !== 32) {
|
|
1259
|
+
throw new Error(`Committee member ${i} ElGamal pubkey must be 32 bytes`);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
const tx = new import_web34.Transaction();
|
|
1263
|
+
return tx;
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Build submit decryption share transaction
|
|
1267
|
+
*
|
|
1268
|
+
* Called by a committee member during the reveal phase.
|
|
1269
|
+
* Each member computes partial decryptions and a DLEQ proof off-chain:
|
|
1270
|
+
* ```rust
|
|
1271
|
+
* let partial = generate_partial_decryption(&secret_share, &pk, &r_for, &r_against, &r_abstain);
|
|
1272
|
+
* ```
|
|
1273
|
+
*
|
|
1274
|
+
* @param params - Decryption share with DLEQ proof
|
|
1275
|
+
*/
|
|
1276
|
+
async buildSubmitDecryptionShareTransaction(params) {
|
|
1277
|
+
if (!this.client.publicKey) {
|
|
1278
|
+
throw new Error("Wallet not connected");
|
|
1279
|
+
}
|
|
1280
|
+
const fields = [
|
|
1281
|
+
{ name: "partialFor", value: params.partialFor },
|
|
1282
|
+
{ name: "partialAgainst", value: params.partialAgainst },
|
|
1283
|
+
{ name: "partialAbstain", value: params.partialAbstain },
|
|
1284
|
+
{ name: "dleqChallenge", value: params.dleqChallenge },
|
|
1285
|
+
{ name: "dleqResponse", value: params.dleqResponse }
|
|
1286
|
+
];
|
|
1287
|
+
for (const { name, value } of fields) {
|
|
1288
|
+
if (value.length !== 32) {
|
|
1289
|
+
throw new Error(`${name} must be 32 bytes`);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
const tx = new import_web34.Transaction();
|
|
1293
|
+
return tx;
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Build aggregate revealed votes transaction (permissionless)
|
|
1297
|
+
*
|
|
1298
|
+
* Anyone can submit the aggregated tally since the on-chain program
|
|
1299
|
+
* cryptographically verifies: tally * H == C_sum - D for each category.
|
|
1300
|
+
* This prevents fabrication of results.
|
|
1301
|
+
*
|
|
1302
|
+
* Use the `zk-voting-sdk` to compute the tally off-chain:
|
|
1303
|
+
* ```rust
|
|
1304
|
+
* let lagrange = compute_lagrange_coefficients(&selected_indices);
|
|
1305
|
+
* let d = combine_partials(&lagrange, &partials);
|
|
1306
|
+
* let tally = recover_tally(&d, &accumulated_c, max_votes).unwrap();
|
|
1307
|
+
* ```
|
|
1308
|
+
*
|
|
1309
|
+
* @param params - Tally values and Lagrange coefficients
|
|
1310
|
+
*/
|
|
1311
|
+
async buildAggregateRevealedVotesTransaction(params) {
|
|
1312
|
+
for (let i = 0; i < params.lagrangeCoefficients.length; i++) {
|
|
1313
|
+
if (params.lagrangeCoefficients[i].length !== 32) {
|
|
1314
|
+
throw new Error(`Lagrange coefficient ${i} must be 32 bytes`);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
const tx = new import_web34.Transaction();
|
|
1318
|
+
return tx;
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Check if authority transfer timelock has elapsed (H-02)
|
|
1322
|
+
*/
|
|
1323
|
+
async canAcceptAuthority() {
|
|
1324
|
+
const config = await this.getConfig();
|
|
1325
|
+
if (!config) {
|
|
1326
|
+
return { canAccept: false, reason: "Config not found" };
|
|
1327
|
+
}
|
|
1328
|
+
if (!config.pendingAuthorityActivatedAt || config.pendingAuthorityActivatedAt.isZero()) {
|
|
1329
|
+
return { canAccept: false, reason: "No pending authority transfer" };
|
|
1330
|
+
}
|
|
1331
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1332
|
+
const timelockEnd = config.pendingAuthorityActivatedAt.toNumber() + GOVERNANCE_CONSTANTS.authorityTransferTimelock;
|
|
1333
|
+
if (now < timelockEnd) {
|
|
1334
|
+
const remaining = timelockEnd - now;
|
|
1335
|
+
const hours = Math.ceil(remaining / 3600);
|
|
1336
|
+
return { canAccept: false, reason: `Timelock: ${hours} hours remaining` };
|
|
1337
|
+
}
|
|
1338
|
+
return { canAccept: true };
|
|
1339
|
+
}
|
|
1070
1340
|
};
|
|
1071
1341
|
|
|
1072
1342
|
// src/rewards/index.ts
|
|
@@ -1206,9 +1476,16 @@ var RewardsClient = class {
|
|
|
1206
1476
|
}
|
|
1207
1477
|
/**
|
|
1208
1478
|
* Calculate gasless fee for claim
|
|
1479
|
+
*
|
|
1480
|
+
* C-05: Uses ceiling division to prevent fee rounding to zero on small amounts.
|
|
1481
|
+
* Formula: fee = ceil(amount * feeBps / 10000)
|
|
1209
1482
|
*/
|
|
1210
1483
|
calculateGaslessFee(amount) {
|
|
1211
|
-
const
|
|
1484
|
+
const numerator = amount.muln(SSCRE_CONSTANTS.gaslessFeeBps).addn(9999);
|
|
1485
|
+
let fee = numerator.divn(1e4);
|
|
1486
|
+
if (fee.gt(amount)) {
|
|
1487
|
+
fee = amount;
|
|
1488
|
+
}
|
|
1212
1489
|
return fee;
|
|
1213
1490
|
}
|
|
1214
1491
|
/**
|
|
@@ -1265,6 +1542,9 @@ var RewardsClient = class {
|
|
|
1265
1542
|
// ============ Transaction Building ============
|
|
1266
1543
|
/**
|
|
1267
1544
|
* Build claim rewards transaction
|
|
1545
|
+
*
|
|
1546
|
+
* H-NEW-02: Merkle proof size is limited to 32 levels (supports 4B+ users).
|
|
1547
|
+
* Proofs exceeding this limit will be rejected on-chain with MerkleProofTooLarge.
|
|
1268
1548
|
*/
|
|
1269
1549
|
async buildClaimTransaction(params) {
|
|
1270
1550
|
if (!this.client.publicKey) {
|
|
@@ -1273,6 +1553,9 @@ var RewardsClient = class {
|
|
|
1273
1553
|
if (params.amount.lt(new import_anchor4.BN(SSCRE_CONSTANTS.minClaimAmount * 1e9))) {
|
|
1274
1554
|
throw new Error(`Claim amount below minimum: ${SSCRE_CONSTANTS.minClaimAmount} VCoin`);
|
|
1275
1555
|
}
|
|
1556
|
+
if (params.merkleProof.length > 32) {
|
|
1557
|
+
throw new Error("Merkle proof exceeds maximum size of 32 levels");
|
|
1558
|
+
}
|
|
1276
1559
|
const hasClaimed = await this.hasClaimedEpoch(params.epoch);
|
|
1277
1560
|
if (hasClaimed) {
|
|
1278
1561
|
throw new Error("Already claimed for this epoch");
|
|
@@ -1434,9 +1717,16 @@ var ViLinkClient = class {
|
|
|
1434
1717
|
}
|
|
1435
1718
|
/**
|
|
1436
1719
|
* Calculate platform fee for tip
|
|
1720
|
+
*
|
|
1721
|
+
* C-06: Uses ceiling division to prevent fee rounding to zero on small amounts.
|
|
1722
|
+
* Formula: fee = ceil(amount * feeBps / 10000)
|
|
1437
1723
|
*/
|
|
1438
1724
|
calculateFee(amount) {
|
|
1439
|
-
const
|
|
1725
|
+
const numerator = amount.muln(VILINK_CONSTANTS.platformFeeBps).addn(9999);
|
|
1726
|
+
let fee = numerator.divn(1e4);
|
|
1727
|
+
if (fee.gt(amount)) {
|
|
1728
|
+
fee = amount;
|
|
1729
|
+
}
|
|
1440
1730
|
return {
|
|
1441
1731
|
fee,
|
|
1442
1732
|
net: amount.sub(fee)
|
|
@@ -1518,6 +1808,13 @@ var ViLinkClient = class {
|
|
|
1518
1808
|
* Build execute tip action transaction
|
|
1519
1809
|
* @param creator - The action creator's public key
|
|
1520
1810
|
* @param nonce - M-04: The action nonce (NOT timestamp)
|
|
1811
|
+
*
|
|
1812
|
+
* H-05: The on-chain handler validates that the executor's token account has
|
|
1813
|
+
* no active delegation (delegate is None or delegated_amount is 0).
|
|
1814
|
+
* This prevents delegated tokens from being spent without explicit approval.
|
|
1815
|
+
*
|
|
1816
|
+
* C-06: Platform fee uses ceiling division to prevent zero-fee exploitation on
|
|
1817
|
+
* small amounts.
|
|
1521
1818
|
*/
|
|
1522
1819
|
async buildExecuteTipAction(creator, nonce) {
|
|
1523
1820
|
if (!this.client.publicKey) {
|
|
@@ -1625,6 +1922,9 @@ var GaslessClient = class {
|
|
|
1625
1922
|
}
|
|
1626
1923
|
/**
|
|
1627
1924
|
* Get user gasless statistics
|
|
1925
|
+
*
|
|
1926
|
+
* H-AUDIT-12: Now includes active_sessions field for per-user session limit tracking.
|
|
1927
|
+
* The protocol enforces a maximum of 5 concurrent active sessions per user.
|
|
1628
1928
|
*/
|
|
1629
1929
|
async getUserStats(user) {
|
|
1630
1930
|
const target = user || this.client.publicKey;
|
|
@@ -1644,7 +1944,8 @@ var GaslessClient = class {
|
|
|
1644
1944
|
totalSubsidized: new import_anchor6.BN(data.slice(48, 56), "le"),
|
|
1645
1945
|
totalVcoinFees: new import_anchor6.BN(data.slice(56, 64), "le"),
|
|
1646
1946
|
sessionsCreated: data.readUInt32LE(72),
|
|
1647
|
-
|
|
1947
|
+
activeSessions: data[76],
|
|
1948
|
+
activeSession: new import_web37.PublicKey(data.slice(77, 109))
|
|
1648
1949
|
};
|
|
1649
1950
|
} catch (error) {
|
|
1650
1951
|
console.warn("[ViWoSDK] gasless.getUserStats failed:", error instanceof Error ? error.message : error);
|
|
@@ -1781,6 +2082,9 @@ var GaslessClient = class {
|
|
|
1781
2082
|
// ============ Transaction Building ============
|
|
1782
2083
|
/**
|
|
1783
2084
|
* Build create session key transaction
|
|
2085
|
+
*
|
|
2086
|
+
* H-AUDIT-12: The protocol enforces a maximum of 5 concurrent active sessions per user.
|
|
2087
|
+
* Creating a session when the limit is reached will fail with MaxSessionsReached error.
|
|
1784
2088
|
*/
|
|
1785
2089
|
async buildCreateSessionTransaction(params) {
|
|
1786
2090
|
if (!this.client.publicKey) {
|
|
@@ -1792,6 +2096,10 @@ var GaslessClient = class {
|
|
|
1792
2096
|
if (!params.scope || params.scope === 0) {
|
|
1793
2097
|
throw new Error("At least one scope required");
|
|
1794
2098
|
}
|
|
2099
|
+
const stats = await this.getUserStats();
|
|
2100
|
+
if (stats && stats.activeSessions >= 5) {
|
|
2101
|
+
throw new Error("Maximum active sessions reached (5). Revoke an existing session first.");
|
|
2102
|
+
}
|
|
1795
2103
|
const duration = params.durationSeconds || GASLESS_CONSTANTS.sessionDuration;
|
|
1796
2104
|
const maxActions = params.maxActions || GASLESS_CONSTANTS.maxSessionActions;
|
|
1797
2105
|
const maxSpend = params.maxSpend || new import_anchor6.BN(GASLESS_CONSTANTS.maxSessionSpend * 1e9);
|
|
@@ -1902,6 +2210,23 @@ var IdentityClient = class {
|
|
|
1902
2210
|
return benefits[level] || [];
|
|
1903
2211
|
}
|
|
1904
2212
|
// ============ Transaction Building ============
|
|
2213
|
+
/**
|
|
2214
|
+
* Build subscribe transaction
|
|
2215
|
+
*
|
|
2216
|
+
* C-AUDIT-22: Non-free subscription tiers require actual USDC payment via SPL
|
|
2217
|
+
* transfer_checked. The transaction must include the user's USDC token account,
|
|
2218
|
+
* the USDC mint, and the treasury token account.
|
|
2219
|
+
*/
|
|
2220
|
+
async buildSubscribeTransaction(tier) {
|
|
2221
|
+
if (!this.client.publicKey) {
|
|
2222
|
+
throw new Error("Wallet not connected");
|
|
2223
|
+
}
|
|
2224
|
+
if (tier < 0 || tier > 4) {
|
|
2225
|
+
throw new Error("Invalid subscription tier (0-4)");
|
|
2226
|
+
}
|
|
2227
|
+
const tx = new import_web38.Transaction();
|
|
2228
|
+
return tx;
|
|
2229
|
+
}
|
|
1905
2230
|
/**
|
|
1906
2231
|
* Build create identity transaction
|
|
1907
2232
|
*/
|
|
@@ -2054,6 +2379,12 @@ var FiveAClient = class {
|
|
|
2054
2379
|
}
|
|
2055
2380
|
/**
|
|
2056
2381
|
* Check if user can vouch for another
|
|
2382
|
+
*
|
|
2383
|
+
* C-08: Mutual vouching is prevented — if the target has already vouched for you,
|
|
2384
|
+
* you cannot vouch for them. This is enforced on-chain via reverse_vouch_record check.
|
|
2385
|
+
*
|
|
2386
|
+
* M-18: Vouches expire after 1 year (MAX_VOUCH_AGE = 365 days). Expired vouches
|
|
2387
|
+
* cannot be evaluated and must be re-created.
|
|
2057
2388
|
*/
|
|
2058
2389
|
async canVouchFor(target) {
|
|
2059
2390
|
if (!this.client.publicKey) {
|
|
@@ -2096,6 +2427,12 @@ var FiveAClient = class {
|
|
|
2096
2427
|
// ============ Transaction Building ============
|
|
2097
2428
|
/**
|
|
2098
2429
|
* Build vouch transaction
|
|
2430
|
+
*
|
|
2431
|
+
* C-08: On-chain handler requires a reverse_vouch_record account to verify
|
|
2432
|
+
* mutual vouching is not occurring. The transaction must include this PDA.
|
|
2433
|
+
*
|
|
2434
|
+
* M-18: Vouches have a maximum age of 1 year. After that, evaluate_vouch
|
|
2435
|
+
* will reject with VouchExpired error.
|
|
2099
2436
|
*/
|
|
2100
2437
|
async buildVouchTransaction(target) {
|
|
2101
2438
|
if (!this.client.publicKey) {
|
|
@@ -2264,6 +2601,9 @@ var ContentClient = class {
|
|
|
2264
2601
|
}
|
|
2265
2602
|
/**
|
|
2266
2603
|
* Get content stats
|
|
2604
|
+
*
|
|
2605
|
+
* C-AUDIT-10: Engagement scores can only increase — the on-chain update_engagement
|
|
2606
|
+
* instruction enforces monotonic increase to prevent manipulation.
|
|
2267
2607
|
*/
|
|
2268
2608
|
async getContentStats(contentId) {
|
|
2269
2609
|
const content = await this.getContent(contentId);
|
|
@@ -2283,6 +2623,9 @@ var ContentClient = class {
|
|
|
2283
2623
|
// ============ Transaction Building ============
|
|
2284
2624
|
/**
|
|
2285
2625
|
* Build create content transaction
|
|
2626
|
+
*
|
|
2627
|
+
* C-AUDIT-11: Energy tiers are validated on-chain (valid range: 1-4).
|
|
2628
|
+
* Tier determines max energy and regen rate.
|
|
2286
2629
|
*/
|
|
2287
2630
|
async buildCreateContentTransaction(contentHash) {
|
|
2288
2631
|
if (!this.client.publicKey) {
|