@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.js
CHANGED
|
@@ -88,7 +88,18 @@ var PROGRAM_IDS = {
|
|
|
88
88
|
governanceProtocol: new import_web3.PublicKey("3R256kBN9iXozjypQFRAmegBhd6HJqXWqdNG7Th78HYe"),
|
|
89
89
|
sscreProtocol: new import_web3.PublicKey("6AJNcQSfoiE2UAeUDyJUBumS9SBwhAdSznoAeYpXrxXZ"),
|
|
90
90
|
vilinkProtocol: new import_web3.PublicKey("CFGXTS2MueQwTYTMMTBQbRWzJtSTC2p4ZRuKPpLDmrv7"),
|
|
91
|
-
gaslessProtocol: new import_web3.PublicKey("FcXJAjzJs8eVY2WTRFXynQBpC7WZUqKZppyp9xS6PaB3")
|
|
91
|
+
gaslessProtocol: new import_web3.PublicKey("FcXJAjzJs8eVY2WTRFXynQBpC7WZUqKZppyp9xS6PaB3"),
|
|
92
|
+
/**
|
|
93
|
+
* VCoin Token Mint Address (Token-2022)
|
|
94
|
+
*
|
|
95
|
+
* NOTE: This is a placeholder. Override via ViWoClient config.programIds.vcoinMint
|
|
96
|
+
* after deploying your VCoin mint on devnet/mainnet.
|
|
97
|
+
*
|
|
98
|
+
* Finding #2 Fix: SDK now filters token accounts by mint address to prevent
|
|
99
|
+
* summing balances from other Token-2022 tokens.
|
|
100
|
+
*/
|
|
101
|
+
vcoinMint: new import_web3.PublicKey("11111111111111111111111111111111")
|
|
102
|
+
// Placeholder - override in config
|
|
92
103
|
};
|
|
93
104
|
var SEEDS = {
|
|
94
105
|
// VCoin
|
|
@@ -262,9 +273,22 @@ var GOVERNANCE_CONSTANTS = {
|
|
|
262
273
|
vetoWindow: 24 * 3600,
|
|
263
274
|
// 1 day
|
|
264
275
|
quorumBps: 400,
|
|
265
|
-
// 4%
|
|
266
|
-
|
|
267
|
-
|
|
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
|
+
}
|
|
268
292
|
};
|
|
269
293
|
var SECURITY_CONSTANTS = {
|
|
270
294
|
// H-02: Two-step authority transfer
|
|
@@ -299,8 +323,21 @@ var SECURITY_CONSTANTS = {
|
|
|
299
323
|
// H-NEW-02: Max proof levels (supports 4B+ users)
|
|
300
324
|
maxEpochBitmap: 1023,
|
|
301
325
|
// H-NEW-04: Max epoch with bitmap storage (85+ years)
|
|
302
|
-
votingPowerVerifiedOnChain: true
|
|
326
|
+
votingPowerVerifiedOnChain: true,
|
|
303
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
|
|
304
341
|
};
|
|
305
342
|
var VALID_URI_PREFIXES = ["ipfs://", "https://", "ar://"];
|
|
306
343
|
var MAX_URI_LENGTH = 128;
|
|
@@ -737,6 +774,10 @@ var StakingClient = class {
|
|
|
737
774
|
}
|
|
738
775
|
/**
|
|
739
776
|
* Get tier name
|
|
777
|
+
*
|
|
778
|
+
* M-05: The on-chain update_tier instruction will reject no-op tier updates
|
|
779
|
+
* with TierUnchanged error. Only call updateTier when the user's stake amount
|
|
780
|
+
* actually qualifies for a different tier.
|
|
740
781
|
*/
|
|
741
782
|
getTierName(tier) {
|
|
742
783
|
const names = ["None", "Bronze", "Silver", "Gold", "Platinum"];
|
|
@@ -848,11 +889,14 @@ var GovernanceClient = class {
|
|
|
848
889
|
if (!accountInfo) {
|
|
849
890
|
return null;
|
|
850
891
|
}
|
|
892
|
+
const data = accountInfo.data;
|
|
851
893
|
return {
|
|
852
|
-
authority: new import_web34.PublicKey(
|
|
853
|
-
proposalCount: new import_anchor3.BN(
|
|
854
|
-
vevcoinMint: new import_web34.PublicKey(
|
|
855
|
-
paused:
|
|
894
|
+
authority: new import_web34.PublicKey(data.slice(8, 40)),
|
|
895
|
+
proposalCount: new import_anchor3.BN(data.slice(40, 48), "le"),
|
|
896
|
+
vevcoinMint: new import_web34.PublicKey(data.slice(48, 80)),
|
|
897
|
+
paused: data[80] !== 0,
|
|
898
|
+
pendingAuthority: new import_web34.PublicKey(data.slice(81, 113)),
|
|
899
|
+
pendingAuthorityActivatedAt: new import_anchor3.BN(data.slice(113, 121), "le")
|
|
856
900
|
};
|
|
857
901
|
} catch {
|
|
858
902
|
return null;
|
|
@@ -925,7 +969,11 @@ var GovernanceClient = class {
|
|
|
925
969
|
proposal: new import_web34.PublicKey(data.slice(40, 72)),
|
|
926
970
|
votePower: new import_anchor3.BN(data.slice(72, 80), "le"),
|
|
927
971
|
support: data[80] !== 0,
|
|
928
|
-
votedAt: new import_anchor3.BN(data.slice(81, 89), "le")
|
|
972
|
+
votedAt: new import_anchor3.BN(data.slice(81, 89), "le"),
|
|
973
|
+
// Private vote ciphertexts (if present, at byte offsets after public vote fields)
|
|
974
|
+
ctFor: new Uint8Array(data.slice(89, 153)),
|
|
975
|
+
ctAgainst: new Uint8Array(data.slice(153, 217)),
|
|
976
|
+
ctAbstain: new Uint8Array(data.slice(217, 281))
|
|
929
977
|
};
|
|
930
978
|
} catch {
|
|
931
979
|
return null;
|
|
@@ -940,6 +988,9 @@ var GovernanceClient = class {
|
|
|
940
988
|
}
|
|
941
989
|
/**
|
|
942
990
|
* Calculate user's voting power
|
|
991
|
+
*
|
|
992
|
+
* @note M-01 fix: 5A boost formula is now `1000 + ((five_a_score * 100) / 1000)`
|
|
993
|
+
* to fix precision loss for small scores.
|
|
943
994
|
*/
|
|
944
995
|
async getVotingPower(user) {
|
|
945
996
|
const target = user || this.client.publicKey;
|
|
@@ -982,6 +1033,9 @@ var GovernanceClient = class {
|
|
|
982
1033
|
}
|
|
983
1034
|
/**
|
|
984
1035
|
* Get proposal progress
|
|
1036
|
+
*
|
|
1037
|
+
* @note C-03: Quorum is now calculated as votesFor + votesAgainst only.
|
|
1038
|
+
* Abstains do NOT count toward quorum.
|
|
985
1039
|
*/
|
|
986
1040
|
async getProposalProgress(proposalId) {
|
|
987
1041
|
const proposal = await this.getProposal(proposalId);
|
|
@@ -1005,7 +1059,7 @@ var GovernanceClient = class {
|
|
|
1005
1059
|
timeRemaining
|
|
1006
1060
|
};
|
|
1007
1061
|
}
|
|
1008
|
-
// ============ Transaction Building ============
|
|
1062
|
+
// ============ Transaction Building (Public Voting) ============
|
|
1009
1063
|
/**
|
|
1010
1064
|
* Build create proposal transaction
|
|
1011
1065
|
*/
|
|
@@ -1021,12 +1075,12 @@ var GovernanceClient = class {
|
|
|
1021
1075
|
return tx;
|
|
1022
1076
|
}
|
|
1023
1077
|
/**
|
|
1024
|
-
* Build vote transaction
|
|
1025
|
-
*
|
|
1026
|
-
* @note v2.8.0 (C-NEW-01): Voting power parameters (vevcoin_balance, five_a_score, tier)
|
|
1078
|
+
* Build vote transaction (public)
|
|
1079
|
+
*
|
|
1080
|
+
* @note v2.8.0 (C-NEW-01): Voting power parameters (vevcoin_balance, five_a_score, tier)
|
|
1027
1081
|
* are now read from on-chain state, not passed as parameters. This prevents vote manipulation.
|
|
1028
1082
|
* The transaction only needs: proposal_id and choice (VoteChoice enum)
|
|
1029
|
-
*
|
|
1083
|
+
*
|
|
1030
1084
|
* @param proposalId - The proposal to vote on
|
|
1031
1085
|
* @param support - true = For, false = Against (use VoteChoice for more options)
|
|
1032
1086
|
*/
|
|
@@ -1056,6 +1110,148 @@ var GovernanceClient = class {
|
|
|
1056
1110
|
const tx = new import_web34.Transaction();
|
|
1057
1111
|
return tx;
|
|
1058
1112
|
}
|
|
1113
|
+
// ============ ZK Private Voting ============
|
|
1114
|
+
/**
|
|
1115
|
+
* Build cast private vote transaction
|
|
1116
|
+
*
|
|
1117
|
+
* Uses Twisted ElGamal encryption on Ristretto255 with compressed sigma proofs.
|
|
1118
|
+
* The voter encrypts their choice into 3 ciphertexts (for/against/abstain) and
|
|
1119
|
+
* generates a validity proof that exactly one ciphertext encrypts their weight.
|
|
1120
|
+
*
|
|
1121
|
+
* Use the `zk-voting-sdk` crate to generate ciphertexts and proofs off-chain:
|
|
1122
|
+
* ```rust
|
|
1123
|
+
* let (ct_for, ct_against, ct_abstain, proof) = encrypt_and_prove(&pubkey, choice, weight);
|
|
1124
|
+
* ```
|
|
1125
|
+
*
|
|
1126
|
+
* @param params - Private vote parameters with ciphertexts and proof
|
|
1127
|
+
*/
|
|
1128
|
+
async buildCastPrivateVoteTransaction(params) {
|
|
1129
|
+
if (!this.client.publicKey) {
|
|
1130
|
+
throw new Error("Wallet not connected");
|
|
1131
|
+
}
|
|
1132
|
+
if (params.ctFor.length !== GOVERNANCE_CONSTANTS.zk.ciphertextSize) {
|
|
1133
|
+
throw new Error(`ctFor must be ${GOVERNANCE_CONSTANTS.zk.ciphertextSize} bytes`);
|
|
1134
|
+
}
|
|
1135
|
+
if (params.ctAgainst.length !== GOVERNANCE_CONSTANTS.zk.ciphertextSize) {
|
|
1136
|
+
throw new Error(`ctAgainst must be ${GOVERNANCE_CONSTANTS.zk.ciphertextSize} bytes`);
|
|
1137
|
+
}
|
|
1138
|
+
if (params.ctAbstain.length !== GOVERNANCE_CONSTANTS.zk.ciphertextSize) {
|
|
1139
|
+
throw new Error(`ctAbstain must be ${GOVERNANCE_CONSTANTS.zk.ciphertextSize} bytes`);
|
|
1140
|
+
}
|
|
1141
|
+
if (params.proofData.length !== GOVERNANCE_CONSTANTS.zk.voteProofSize) {
|
|
1142
|
+
throw new Error(`proofData must be ${GOVERNANCE_CONSTANTS.zk.voteProofSize} bytes`);
|
|
1143
|
+
}
|
|
1144
|
+
const hasVoted = await this.hasVoted(params.proposalId);
|
|
1145
|
+
if (hasVoted) {
|
|
1146
|
+
throw new Error("Already voted on this proposal");
|
|
1147
|
+
}
|
|
1148
|
+
const tx = new import_web34.Transaction();
|
|
1149
|
+
return tx;
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Build enable private voting transaction
|
|
1153
|
+
*
|
|
1154
|
+
* Called by the governance authority to enable ZK private voting on a proposal.
|
|
1155
|
+
* Requires specifying the decryption committee and their ElGamal public keys.
|
|
1156
|
+
*
|
|
1157
|
+
* @param params - Private voting configuration
|
|
1158
|
+
*/
|
|
1159
|
+
async buildEnablePrivateVotingTransaction(params) {
|
|
1160
|
+
if (!this.client.publicKey) {
|
|
1161
|
+
throw new Error("Wallet not connected");
|
|
1162
|
+
}
|
|
1163
|
+
if (params.encryptionPubkey.length !== 32) {
|
|
1164
|
+
throw new Error("encryptionPubkey must be 32 bytes (Ristretto255 point)");
|
|
1165
|
+
}
|
|
1166
|
+
if (params.committeeSize > GOVERNANCE_CONSTANTS.zk.maxCommitteeSize) {
|
|
1167
|
+
throw new Error(`Committee size exceeds max of ${GOVERNANCE_CONSTANTS.zk.maxCommitteeSize}`);
|
|
1168
|
+
}
|
|
1169
|
+
if (params.decryptionThreshold > params.committeeSize) {
|
|
1170
|
+
throw new Error("Threshold cannot exceed committee size");
|
|
1171
|
+
}
|
|
1172
|
+
for (let i = 0; i < params.committeeSize; i++) {
|
|
1173
|
+
if (!params.committeeElgamalPubkeys[i] || params.committeeElgamalPubkeys[i].length !== 32) {
|
|
1174
|
+
throw new Error(`Committee member ${i} ElGamal pubkey must be 32 bytes`);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
const tx = new import_web34.Transaction();
|
|
1178
|
+
return tx;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Build submit decryption share transaction
|
|
1182
|
+
*
|
|
1183
|
+
* Called by a committee member during the reveal phase.
|
|
1184
|
+
* Each member computes partial decryptions and a DLEQ proof off-chain:
|
|
1185
|
+
* ```rust
|
|
1186
|
+
* let partial = generate_partial_decryption(&secret_share, &pk, &r_for, &r_against, &r_abstain);
|
|
1187
|
+
* ```
|
|
1188
|
+
*
|
|
1189
|
+
* @param params - Decryption share with DLEQ proof
|
|
1190
|
+
*/
|
|
1191
|
+
async buildSubmitDecryptionShareTransaction(params) {
|
|
1192
|
+
if (!this.client.publicKey) {
|
|
1193
|
+
throw new Error("Wallet not connected");
|
|
1194
|
+
}
|
|
1195
|
+
const fields = [
|
|
1196
|
+
{ name: "partialFor", value: params.partialFor },
|
|
1197
|
+
{ name: "partialAgainst", value: params.partialAgainst },
|
|
1198
|
+
{ name: "partialAbstain", value: params.partialAbstain },
|
|
1199
|
+
{ name: "dleqChallenge", value: params.dleqChallenge },
|
|
1200
|
+
{ name: "dleqResponse", value: params.dleqResponse }
|
|
1201
|
+
];
|
|
1202
|
+
for (const { name, value } of fields) {
|
|
1203
|
+
if (value.length !== 32) {
|
|
1204
|
+
throw new Error(`${name} must be 32 bytes`);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
const tx = new import_web34.Transaction();
|
|
1208
|
+
return tx;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Build aggregate revealed votes transaction (permissionless)
|
|
1212
|
+
*
|
|
1213
|
+
* Anyone can submit the aggregated tally since the on-chain program
|
|
1214
|
+
* cryptographically verifies: tally * H == C_sum - D for each category.
|
|
1215
|
+
* This prevents fabrication of results.
|
|
1216
|
+
*
|
|
1217
|
+
* Use the `zk-voting-sdk` to compute the tally off-chain:
|
|
1218
|
+
* ```rust
|
|
1219
|
+
* let lagrange = compute_lagrange_coefficients(&selected_indices);
|
|
1220
|
+
* let d = combine_partials(&lagrange, &partials);
|
|
1221
|
+
* let tally = recover_tally(&d, &accumulated_c, max_votes).unwrap();
|
|
1222
|
+
* ```
|
|
1223
|
+
*
|
|
1224
|
+
* @param params - Tally values and Lagrange coefficients
|
|
1225
|
+
*/
|
|
1226
|
+
async buildAggregateRevealedVotesTransaction(params) {
|
|
1227
|
+
for (let i = 0; i < params.lagrangeCoefficients.length; i++) {
|
|
1228
|
+
if (params.lagrangeCoefficients[i].length !== 32) {
|
|
1229
|
+
throw new Error(`Lagrange coefficient ${i} must be 32 bytes`);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
const tx = new import_web34.Transaction();
|
|
1233
|
+
return tx;
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Check if authority transfer timelock has elapsed (H-02)
|
|
1237
|
+
*/
|
|
1238
|
+
async canAcceptAuthority() {
|
|
1239
|
+
const config = await this.getConfig();
|
|
1240
|
+
if (!config) {
|
|
1241
|
+
return { canAccept: false, reason: "Config not found" };
|
|
1242
|
+
}
|
|
1243
|
+
if (!config.pendingAuthorityActivatedAt || config.pendingAuthorityActivatedAt.isZero()) {
|
|
1244
|
+
return { canAccept: false, reason: "No pending authority transfer" };
|
|
1245
|
+
}
|
|
1246
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1247
|
+
const timelockEnd = config.pendingAuthorityActivatedAt.toNumber() + GOVERNANCE_CONSTANTS.authorityTransferTimelock;
|
|
1248
|
+
if (now < timelockEnd) {
|
|
1249
|
+
const remaining = timelockEnd - now;
|
|
1250
|
+
const hours = Math.ceil(remaining / 3600);
|
|
1251
|
+
return { canAccept: false, reason: `Timelock: ${hours} hours remaining` };
|
|
1252
|
+
}
|
|
1253
|
+
return { canAccept: true };
|
|
1254
|
+
}
|
|
1059
1255
|
};
|
|
1060
1256
|
|
|
1061
1257
|
// src/rewards/index.ts
|
|
@@ -1195,9 +1391,16 @@ var RewardsClient = class {
|
|
|
1195
1391
|
}
|
|
1196
1392
|
/**
|
|
1197
1393
|
* Calculate gasless fee for claim
|
|
1394
|
+
*
|
|
1395
|
+
* C-05: Uses ceiling division to prevent fee rounding to zero on small amounts.
|
|
1396
|
+
* Formula: fee = ceil(amount * feeBps / 10000)
|
|
1198
1397
|
*/
|
|
1199
1398
|
calculateGaslessFee(amount) {
|
|
1200
|
-
const
|
|
1399
|
+
const numerator = amount.muln(SSCRE_CONSTANTS.gaslessFeeBps).addn(9999);
|
|
1400
|
+
let fee = numerator.divn(1e4);
|
|
1401
|
+
if (fee.gt(amount)) {
|
|
1402
|
+
fee = amount;
|
|
1403
|
+
}
|
|
1201
1404
|
return fee;
|
|
1202
1405
|
}
|
|
1203
1406
|
/**
|
|
@@ -1254,6 +1457,9 @@ var RewardsClient = class {
|
|
|
1254
1457
|
// ============ Transaction Building ============
|
|
1255
1458
|
/**
|
|
1256
1459
|
* Build claim rewards transaction
|
|
1460
|
+
*
|
|
1461
|
+
* H-NEW-02: Merkle proof size is limited to 32 levels (supports 4B+ users).
|
|
1462
|
+
* Proofs exceeding this limit will be rejected on-chain with MerkleProofTooLarge.
|
|
1257
1463
|
*/
|
|
1258
1464
|
async buildClaimTransaction(params) {
|
|
1259
1465
|
if (!this.client.publicKey) {
|
|
@@ -1262,6 +1468,9 @@ var RewardsClient = class {
|
|
|
1262
1468
|
if (params.amount.lt(new import_anchor4.BN(SSCRE_CONSTANTS.minClaimAmount * 1e9))) {
|
|
1263
1469
|
throw new Error(`Claim amount below minimum: ${SSCRE_CONSTANTS.minClaimAmount} VCoin`);
|
|
1264
1470
|
}
|
|
1471
|
+
if (params.merkleProof.length > 32) {
|
|
1472
|
+
throw new Error("Merkle proof exceeds maximum size of 32 levels");
|
|
1473
|
+
}
|
|
1265
1474
|
const hasClaimed = await this.hasClaimedEpoch(params.epoch);
|
|
1266
1475
|
if (hasClaimed) {
|
|
1267
1476
|
throw new Error("Already claimed for this epoch");
|
|
@@ -1280,6 +1489,9 @@ var ViLinkClient = class {
|
|
|
1280
1489
|
}
|
|
1281
1490
|
/**
|
|
1282
1491
|
* Get ViLink configuration
|
|
1492
|
+
*
|
|
1493
|
+
* Finding #8 (related): Corrected byte offsets to match on-chain ViLinkConfig struct.
|
|
1494
|
+
* Added pending_authority field that was missing after H-02 security fix.
|
|
1283
1495
|
*/
|
|
1284
1496
|
async getConfig() {
|
|
1285
1497
|
try {
|
|
@@ -1291,16 +1503,18 @@ var ViLinkClient = class {
|
|
|
1291
1503
|
const data = accountInfo.data;
|
|
1292
1504
|
return {
|
|
1293
1505
|
authority: new import_web36.PublicKey(data.slice(8, 40)),
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1506
|
+
pendingAuthority: new import_web36.PublicKey(data.slice(40, 72)),
|
|
1507
|
+
vcoinMint: new import_web36.PublicKey(data.slice(72, 104)),
|
|
1508
|
+
treasury: new import_web36.PublicKey(data.slice(104, 136)),
|
|
1509
|
+
enabledActions: data[296],
|
|
1510
|
+
totalActionsCreated: new import_anchor5.BN(data.slice(297, 305), "le"),
|
|
1511
|
+
totalActionsExecuted: new import_anchor5.BN(data.slice(305, 313), "le"),
|
|
1512
|
+
totalTipVolume: new import_anchor5.BN(data.slice(313, 321), "le"),
|
|
1513
|
+
paused: data[321] !== 0,
|
|
1514
|
+
platformFeeBps: data.readUInt16LE(322)
|
|
1302
1515
|
};
|
|
1303
|
-
} catch {
|
|
1516
|
+
} catch (error) {
|
|
1517
|
+
console.warn("[ViWoSDK] vilink.getConfig failed:", error instanceof Error ? error.message : error);
|
|
1304
1518
|
return null;
|
|
1305
1519
|
}
|
|
1306
1520
|
}
|
|
@@ -1330,7 +1544,8 @@ var ViLinkClient = class {
|
|
|
1330
1544
|
actionNonce: nonce
|
|
1331
1545
|
// M-04: Store nonce for reference
|
|
1332
1546
|
};
|
|
1333
|
-
} catch {
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
console.warn("[ViWoSDK] vilink.getAction failed:", error instanceof Error ? error.message : error);
|
|
1334
1549
|
return null;
|
|
1335
1550
|
}
|
|
1336
1551
|
}
|
|
@@ -1364,7 +1579,8 @@ var ViLinkClient = class {
|
|
|
1364
1579
|
vcoinSent: new import_anchor5.BN(data.slice(72, 80), "le"),
|
|
1365
1580
|
vcoinReceived: new import_anchor5.BN(data.slice(80, 88), "le")
|
|
1366
1581
|
};
|
|
1367
|
-
} catch {
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
console.warn("[ViWoSDK] vilink.getUserStats failed:", error instanceof Error ? error.message : error);
|
|
1368
1584
|
return null;
|
|
1369
1585
|
}
|
|
1370
1586
|
}
|
|
@@ -1416,9 +1632,16 @@ var ViLinkClient = class {
|
|
|
1416
1632
|
}
|
|
1417
1633
|
/**
|
|
1418
1634
|
* Calculate platform fee for tip
|
|
1635
|
+
*
|
|
1636
|
+
* C-06: Uses ceiling division to prevent fee rounding to zero on small amounts.
|
|
1637
|
+
* Formula: fee = ceil(amount * feeBps / 10000)
|
|
1419
1638
|
*/
|
|
1420
1639
|
calculateFee(amount) {
|
|
1421
|
-
const
|
|
1640
|
+
const numerator = amount.muln(VILINK_CONSTANTS.platformFeeBps).addn(9999);
|
|
1641
|
+
let fee = numerator.divn(1e4);
|
|
1642
|
+
if (fee.gt(amount)) {
|
|
1643
|
+
fee = amount;
|
|
1644
|
+
}
|
|
1422
1645
|
return {
|
|
1423
1646
|
fee,
|
|
1424
1647
|
net: amount.sub(fee)
|
|
@@ -1500,6 +1723,13 @@ var ViLinkClient = class {
|
|
|
1500
1723
|
* Build execute tip action transaction
|
|
1501
1724
|
* @param creator - The action creator's public key
|
|
1502
1725
|
* @param nonce - M-04: The action nonce (NOT timestamp)
|
|
1726
|
+
*
|
|
1727
|
+
* H-05: The on-chain handler validates that the executor's token account has
|
|
1728
|
+
* no active delegation (delegate is None or delegated_amount is 0).
|
|
1729
|
+
* This prevents delegated tokens from being spent without explicit approval.
|
|
1730
|
+
*
|
|
1731
|
+
* C-06: Platform fee uses ceiling division to prevent zero-fee exploitation on
|
|
1732
|
+
* small amounts.
|
|
1503
1733
|
*/
|
|
1504
1734
|
async buildExecuteTipAction(creator, nonce) {
|
|
1505
1735
|
if (!this.client.publicKey) {
|
|
@@ -1538,6 +1768,10 @@ var GaslessClient = class {
|
|
|
1538
1768
|
}
|
|
1539
1769
|
/**
|
|
1540
1770
|
* Get gasless configuration
|
|
1771
|
+
*
|
|
1772
|
+
* Finding #8 Fix: Corrected byte offsets to match on-chain GaslessConfig struct.
|
|
1773
|
+
* Added missing fields: pendingAuthority, feeVault, sscreProgram, sscreDeductionBps,
|
|
1774
|
+
* maxSubsidizedPerUser, totalSolSpent, currentDay, daySpent, maxSlippageBps.
|
|
1541
1775
|
*/
|
|
1542
1776
|
async getConfig() {
|
|
1543
1777
|
try {
|
|
@@ -1549,16 +1783,26 @@ var GaslessClient = class {
|
|
|
1549
1783
|
const data = accountInfo.data;
|
|
1550
1784
|
return {
|
|
1551
1785
|
authority: new import_web37.PublicKey(data.slice(8, 40)),
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1786
|
+
pendingAuthority: new import_web37.PublicKey(data.slice(40, 72)),
|
|
1787
|
+
feePayer: new import_web37.PublicKey(data.slice(72, 104)),
|
|
1788
|
+
vcoinMint: new import_web37.PublicKey(data.slice(104, 136)),
|
|
1789
|
+
feeVault: new import_web37.PublicKey(data.slice(136, 168)),
|
|
1790
|
+
sscreProgram: new import_web37.PublicKey(data.slice(168, 200)),
|
|
1791
|
+
dailySubsidyBudget: new import_anchor6.BN(data.slice(200, 208), "le"),
|
|
1792
|
+
solFeePerTx: new import_anchor6.BN(data.slice(208, 216), "le"),
|
|
1793
|
+
vcoinFeeMultiplier: new import_anchor6.BN(data.slice(216, 224), "le"),
|
|
1794
|
+
sscreDeductionBps: data.readUInt16LE(224),
|
|
1795
|
+
maxSubsidizedPerUser: data.readUInt32LE(226),
|
|
1796
|
+
totalSubsidizedTx: new import_anchor6.BN(data.slice(230, 238), "le"),
|
|
1797
|
+
totalSolSpent: new import_anchor6.BN(data.slice(238, 246), "le"),
|
|
1798
|
+
totalVcoinCollected: new import_anchor6.BN(data.slice(246, 254), "le"),
|
|
1799
|
+
paused: data[254] !== 0,
|
|
1800
|
+
currentDay: data.readUInt32LE(255),
|
|
1801
|
+
daySpent: new import_anchor6.BN(data.slice(259, 267), "le"),
|
|
1802
|
+
maxSlippageBps: data.readUInt16LE(267)
|
|
1560
1803
|
};
|
|
1561
|
-
} catch {
|
|
1804
|
+
} catch (error) {
|
|
1805
|
+
console.warn("[ViWoSDK] gasless.getConfig failed:", error instanceof Error ? error.message : error);
|
|
1562
1806
|
return null;
|
|
1563
1807
|
}
|
|
1564
1808
|
}
|
|
@@ -1586,12 +1830,16 @@ var GaslessClient = class {
|
|
|
1586
1830
|
isRevoked: data[114] !== 0,
|
|
1587
1831
|
feeMethod: data[123]
|
|
1588
1832
|
};
|
|
1589
|
-
} catch {
|
|
1833
|
+
} catch (error) {
|
|
1834
|
+
console.warn("[ViWoSDK] gasless.getSessionKey failed:", error instanceof Error ? error.message : error);
|
|
1590
1835
|
return null;
|
|
1591
1836
|
}
|
|
1592
1837
|
}
|
|
1593
1838
|
/**
|
|
1594
1839
|
* Get user gasless statistics
|
|
1840
|
+
*
|
|
1841
|
+
* H-AUDIT-12: Now includes active_sessions field for per-user session limit tracking.
|
|
1842
|
+
* The protocol enforces a maximum of 5 concurrent active sessions per user.
|
|
1595
1843
|
*/
|
|
1596
1844
|
async getUserStats(user) {
|
|
1597
1845
|
const target = user || this.client.publicKey;
|
|
@@ -1611,9 +1859,11 @@ var GaslessClient = class {
|
|
|
1611
1859
|
totalSubsidized: new import_anchor6.BN(data.slice(48, 56), "le"),
|
|
1612
1860
|
totalVcoinFees: new import_anchor6.BN(data.slice(56, 64), "le"),
|
|
1613
1861
|
sessionsCreated: data.readUInt32LE(72),
|
|
1614
|
-
|
|
1862
|
+
activeSessions: data[76],
|
|
1863
|
+
activeSession: new import_web37.PublicKey(data.slice(77, 109))
|
|
1615
1864
|
};
|
|
1616
|
-
} catch {
|
|
1865
|
+
} catch (error) {
|
|
1866
|
+
console.warn("[ViWoSDK] gasless.getUserStats failed:", error instanceof Error ? error.message : error);
|
|
1617
1867
|
return null;
|
|
1618
1868
|
}
|
|
1619
1869
|
}
|
|
@@ -1747,6 +1997,9 @@ var GaslessClient = class {
|
|
|
1747
1997
|
// ============ Transaction Building ============
|
|
1748
1998
|
/**
|
|
1749
1999
|
* Build create session key transaction
|
|
2000
|
+
*
|
|
2001
|
+
* H-AUDIT-12: The protocol enforces a maximum of 5 concurrent active sessions per user.
|
|
2002
|
+
* Creating a session when the limit is reached will fail with MaxSessionsReached error.
|
|
1750
2003
|
*/
|
|
1751
2004
|
async buildCreateSessionTransaction(params) {
|
|
1752
2005
|
if (!this.client.publicKey) {
|
|
@@ -1758,6 +2011,10 @@ var GaslessClient = class {
|
|
|
1758
2011
|
if (!params.scope || params.scope === 0) {
|
|
1759
2012
|
throw new Error("At least one scope required");
|
|
1760
2013
|
}
|
|
2014
|
+
const stats = await this.getUserStats();
|
|
2015
|
+
if (stats && stats.activeSessions >= 5) {
|
|
2016
|
+
throw new Error("Maximum active sessions reached (5). Revoke an existing session first.");
|
|
2017
|
+
}
|
|
1761
2018
|
const duration = params.durationSeconds || GASLESS_CONSTANTS.sessionDuration;
|
|
1762
2019
|
const maxActions = params.maxActions || GASLESS_CONSTANTS.maxSessionActions;
|
|
1763
2020
|
const maxSpend = params.maxSpend || new import_anchor6.BN(GASLESS_CONSTANTS.maxSessionSpend * 1e9);
|
|
@@ -1868,6 +2125,23 @@ var IdentityClient = class {
|
|
|
1868
2125
|
return benefits[level] || [];
|
|
1869
2126
|
}
|
|
1870
2127
|
// ============ Transaction Building ============
|
|
2128
|
+
/**
|
|
2129
|
+
* Build subscribe transaction
|
|
2130
|
+
*
|
|
2131
|
+
* C-AUDIT-22: Non-free subscription tiers require actual USDC payment via SPL
|
|
2132
|
+
* transfer_checked. The transaction must include the user's USDC token account,
|
|
2133
|
+
* the USDC mint, and the treasury token account.
|
|
2134
|
+
*/
|
|
2135
|
+
async buildSubscribeTransaction(tier) {
|
|
2136
|
+
if (!this.client.publicKey) {
|
|
2137
|
+
throw new Error("Wallet not connected");
|
|
2138
|
+
}
|
|
2139
|
+
if (tier < 0 || tier > 4) {
|
|
2140
|
+
throw new Error("Invalid subscription tier (0-4)");
|
|
2141
|
+
}
|
|
2142
|
+
const tx = new import_web38.Transaction();
|
|
2143
|
+
return tx;
|
|
2144
|
+
}
|
|
1871
2145
|
/**
|
|
1872
2146
|
* Build create identity transaction
|
|
1873
2147
|
*/
|
|
@@ -2020,6 +2294,12 @@ var FiveAClient = class {
|
|
|
2020
2294
|
}
|
|
2021
2295
|
/**
|
|
2022
2296
|
* Check if user can vouch for another
|
|
2297
|
+
*
|
|
2298
|
+
* C-08: Mutual vouching is prevented — if the target has already vouched for you,
|
|
2299
|
+
* you cannot vouch for them. This is enforced on-chain via reverse_vouch_record check.
|
|
2300
|
+
*
|
|
2301
|
+
* M-18: Vouches expire after 1 year (MAX_VOUCH_AGE = 365 days). Expired vouches
|
|
2302
|
+
* cannot be evaluated and must be re-created.
|
|
2023
2303
|
*/
|
|
2024
2304
|
async canVouchFor(target) {
|
|
2025
2305
|
if (!this.client.publicKey) {
|
|
@@ -2062,6 +2342,12 @@ var FiveAClient = class {
|
|
|
2062
2342
|
// ============ Transaction Building ============
|
|
2063
2343
|
/**
|
|
2064
2344
|
* Build vouch transaction
|
|
2345
|
+
*
|
|
2346
|
+
* C-08: On-chain handler requires a reverse_vouch_record account to verify
|
|
2347
|
+
* mutual vouching is not occurring. The transaction must include this PDA.
|
|
2348
|
+
*
|
|
2349
|
+
* M-18: Vouches have a maximum age of 1 year. After that, evaluate_vouch
|
|
2350
|
+
* will reject with VouchExpired error.
|
|
2065
2351
|
*/
|
|
2066
2352
|
async buildVouchTransaction(target) {
|
|
2067
2353
|
if (!this.client.publicKey) {
|
|
@@ -2230,6 +2516,9 @@ var ContentClient = class {
|
|
|
2230
2516
|
}
|
|
2231
2517
|
/**
|
|
2232
2518
|
* Get content stats
|
|
2519
|
+
*
|
|
2520
|
+
* C-AUDIT-10: Engagement scores can only increase — the on-chain update_engagement
|
|
2521
|
+
* instruction enforces monotonic increase to prevent manipulation.
|
|
2233
2522
|
*/
|
|
2234
2523
|
async getContentStats(contentId) {
|
|
2235
2524
|
const content = await this.getContent(contentId);
|
|
@@ -2249,6 +2538,9 @@ var ContentClient = class {
|
|
|
2249
2538
|
// ============ Transaction Building ============
|
|
2250
2539
|
/**
|
|
2251
2540
|
* Build create content transaction
|
|
2541
|
+
*
|
|
2542
|
+
* C-AUDIT-11: Energy tiers are validated on-chain (valid range: 1-4).
|
|
2543
|
+
* Tier determines max energy and regen rate.
|
|
2252
2544
|
*/
|
|
2253
2545
|
async buildCreateContentTransaction(contentHash) {
|
|
2254
2546
|
if (!this.client.publicKey) {
|
|
@@ -2388,6 +2680,9 @@ var ViWoClient = class {
|
|
|
2388
2680
|
}
|
|
2389
2681
|
/**
|
|
2390
2682
|
* Get VCoin balance
|
|
2683
|
+
*
|
|
2684
|
+
* Finding #2 Fix: Now filters by VCoin mint address instead of summing all Token-2022 accounts.
|
|
2685
|
+
* Make sure to set programIds.vcoinMint in your ViWoClient config.
|
|
2391
2686
|
*/
|
|
2392
2687
|
async getVCoinBalance(user) {
|
|
2393
2688
|
const target = user || this.publicKey;
|
|
@@ -2397,7 +2692,7 @@ var ViWoClient = class {
|
|
|
2397
2692
|
try {
|
|
2398
2693
|
const tokenAccounts = await this.connection.connection.getTokenAccountsByOwner(
|
|
2399
2694
|
target,
|
|
2400
|
-
{ programId: import_spl_token.TOKEN_2022_PROGRAM_ID }
|
|
2695
|
+
{ mint: this.programIds.vcoinMint, programId: import_spl_token.TOKEN_2022_PROGRAM_ID }
|
|
2401
2696
|
);
|
|
2402
2697
|
let balance = new import_anchor10.BN(0);
|
|
2403
2698
|
for (const { account } of tokenAccounts.value) {
|
|
@@ -2406,7 +2701,8 @@ var ViWoClient = class {
|
|
|
2406
2701
|
balance = balance.add(new import_anchor10.BN(amount, "le"));
|
|
2407
2702
|
}
|
|
2408
2703
|
return balance;
|
|
2409
|
-
} catch {
|
|
2704
|
+
} catch (error) {
|
|
2705
|
+
console.warn("[ViWoSDK] getVCoinBalance failed:", error instanceof Error ? error.message : error);
|
|
2410
2706
|
return new import_anchor10.BN(0);
|
|
2411
2707
|
}
|
|
2412
2708
|
}
|
|
@@ -2421,7 +2717,8 @@ var ViWoClient = class {
|
|
|
2421
2717
|
try {
|
|
2422
2718
|
const stakeData = await this.staking.getUserStake(target);
|
|
2423
2719
|
return stakeData?.vevcoinBalance || new import_anchor10.BN(0);
|
|
2424
|
-
} catch {
|
|
2720
|
+
} catch (error) {
|
|
2721
|
+
console.warn("[ViWoSDK] getVeVCoinBalance failed:", error instanceof Error ? error.message : error);
|
|
2425
2722
|
return new import_anchor10.BN(0);
|
|
2426
2723
|
}
|
|
2427
2724
|
}
|
|
@@ -2436,7 +2733,8 @@ var ViWoClient = class {
|
|
|
2436
2733
|
]);
|
|
2437
2734
|
const blockTime = await this.connection.getBlockTime();
|
|
2438
2735
|
return { connected, slot, blockTime };
|
|
2439
|
-
} catch {
|
|
2736
|
+
} catch (error) {
|
|
2737
|
+
console.warn("[ViWoSDK] healthCheck failed:", error instanceof Error ? error.message : error);
|
|
2440
2738
|
return { connected: false, slot: null, blockTime: null };
|
|
2441
2739
|
}
|
|
2442
2740
|
}
|