@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/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
- zkVotingEnabled: false
198
- // C-01: Disabled until proper ZK infrastructure
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(accountInfo.data.slice(8, 40)),
784
- proposalCount: new BN3(accountInfo.data.slice(40, 48), "le"),
785
- vevcoinMint: new PublicKey4(accountInfo.data.slice(48, 80)),
786
- paused: accountInfo.data[80] !== 0
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 fee = amount.muln(SSCRE_CONSTANTS.gaslessFeeBps).divn(1e4);
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
- vcoinMint: new PublicKey6(data.slice(40, 72)),
1226
- treasury: new PublicKey6(data.slice(72, 104)),
1227
- enabledActions: data[200],
1228
- totalActionsCreated: new BN5(data.slice(201, 209), "le"),
1229
- totalActionsExecuted: new BN5(data.slice(209, 217), "le"),
1230
- totalTipVolume: new BN5(data.slice(217, 225), "le"),
1231
- paused: data[225] !== 0,
1232
- platformFeeBps: data.readUInt16LE(226)
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 fee = amount.muln(VILINK_CONSTANTS.platformFeeBps).divn(1e4);
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
- feePayer: new PublicKey7(data.slice(40, 72)),
1484
- vcoinMint: new PublicKey7(data.slice(72, 104)),
1485
- dailySubsidyBudget: new BN6(data.slice(136, 144), "le"),
1486
- solFeePerTx: new BN6(data.slice(144, 152), "le"),
1487
- vcoinFeeMultiplier: new BN6(data.slice(152, 160), "le"),
1488
- totalSubsidizedTx: new BN6(data.slice(168, 176), "le"),
1489
- totalVcoinCollected: new BN6(data.slice(184, 192), "le"),
1490
- paused: data[192] !== 0
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
- activeSession: new PublicKey7(data.slice(76, 108))
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
  }