@viwoapp/sdk 0.1.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 +310 -0
- package/dist/index.d.mts +1199 -0
- package/dist/index.d.ts +1199 -0
- package/dist/index.js +2346 -0
- package/dist/index.mjs +2285 -0
- package/package.json +80 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2285 @@
|
|
|
1
|
+
// src/core/index.ts
|
|
2
|
+
import {
|
|
3
|
+
Connection,
|
|
4
|
+
PublicKey as PublicKey2,
|
|
5
|
+
Transaction
|
|
6
|
+
} from "@solana/web3.js";
|
|
7
|
+
import { BN } from "@coral-xyz/anchor";
|
|
8
|
+
|
|
9
|
+
// src/constants.ts
|
|
10
|
+
import { PublicKey } from "@solana/web3.js";
|
|
11
|
+
var PROGRAM_IDS = {
|
|
12
|
+
vcoinToken: new PublicKey("Gg1dtrjAfGYi6NLC31WaJjZNBoucvD98rK2h1u9qrUjn"),
|
|
13
|
+
vevcoinToken: new PublicKey("FB39ae9x53FxVL3pER9LqCPEx2TRnEnQP55i838Upnjx"),
|
|
14
|
+
stakingProtocol: new PublicKey("6EFcistyr2E81adLUcuBJRr8W2xzpt3D3dFYEcMewpWu"),
|
|
15
|
+
transferHook: new PublicKey("9K14FcDRrBeHKD9FPNYeVJaEqJQTac2xspJyb1mM6m48"),
|
|
16
|
+
identityProtocol: new PublicKey("3egAds3pFR5oog6iQCN42KPvgih8HQz2FGybNjiVWixG"),
|
|
17
|
+
fiveAProtocol: new PublicKey("783PbtJw5cc7yatnr9fsvTGSnkKaV6iJe6E8VUPTYrT8"),
|
|
18
|
+
contentRegistry: new PublicKey("MJn1A4MPCBPJGWWuZrtq7bHSo2G289sUwW3ej2wcmLV"),
|
|
19
|
+
governanceProtocol: new PublicKey("3R256kBN9iXozjypQFRAmegBhd6HJqXWqdNG7Th78HYe"),
|
|
20
|
+
sscreProtocol: new PublicKey("6AJNcQSfoiE2UAeUDyJUBumS9SBwhAdSznoAeYpXrxXZ"),
|
|
21
|
+
vilinkProtocol: new PublicKey("CFGXTS2MueQwTYTMMTBQbRWzJtSTC2p4ZRuKPpLDmrv7"),
|
|
22
|
+
gaslessProtocol: new PublicKey("FcXJAjzJs8eVY2WTRFXynQBpC7WZUqKZppyp9xS6PaB3")
|
|
23
|
+
};
|
|
24
|
+
var SEEDS = {
|
|
25
|
+
// VCoin
|
|
26
|
+
vcoinConfig: "vcoin-config",
|
|
27
|
+
// veVCoin
|
|
28
|
+
vevcoinConfig: "vevcoin-config",
|
|
29
|
+
userVevcoin: "user-vevcoin",
|
|
30
|
+
// Staking
|
|
31
|
+
stakingPool: "staking-pool",
|
|
32
|
+
userStake: "user-stake",
|
|
33
|
+
// Governance
|
|
34
|
+
governanceConfig: "governance-config",
|
|
35
|
+
proposal: "proposal",
|
|
36
|
+
voteRecord: "vote",
|
|
37
|
+
delegation: "delegation",
|
|
38
|
+
// SSCRE
|
|
39
|
+
poolConfig: "pool-config",
|
|
40
|
+
epoch: "epoch",
|
|
41
|
+
userClaim: "user-claim",
|
|
42
|
+
// ViLink
|
|
43
|
+
vilinkConfig: "vilink-config",
|
|
44
|
+
action: "action",
|
|
45
|
+
userStats: "user-stats",
|
|
46
|
+
dapp: "dapp",
|
|
47
|
+
// Gasless
|
|
48
|
+
gaslessConfig: "gasless-config",
|
|
49
|
+
sessionKey: "session-key",
|
|
50
|
+
userGasless: "user-gasless",
|
|
51
|
+
feeVault: "fee-vault",
|
|
52
|
+
// Identity
|
|
53
|
+
identityConfig: "identity-config",
|
|
54
|
+
identity: "identity",
|
|
55
|
+
// 5A
|
|
56
|
+
fiveAConfig: "five-a-config",
|
|
57
|
+
userScore: "user-score",
|
|
58
|
+
vouch: "vouch",
|
|
59
|
+
// Content
|
|
60
|
+
registryConfig: "registry-config",
|
|
61
|
+
content: "content",
|
|
62
|
+
userEnergy: "user-energy"
|
|
63
|
+
};
|
|
64
|
+
var VCOIN_DECIMALS = 9;
|
|
65
|
+
var VEVCOIN_DECIMALS = 9;
|
|
66
|
+
var VCOIN_TOTAL_SUPPLY = 1e9;
|
|
67
|
+
var VCOIN_INITIAL_CIRCULATING = 1e8;
|
|
68
|
+
var STAKING_TIERS = {
|
|
69
|
+
none: { minStake: 0, feeDiscount: 0, boost: 1, minLock: 0 },
|
|
70
|
+
bronze: { minStake: 1e3, feeDiscount: 10, boost: 1.1, minLock: 0 },
|
|
71
|
+
silver: { minStake: 5e3, feeDiscount: 20, boost: 1.2, minLock: 0 },
|
|
72
|
+
gold: { minStake: 2e4, feeDiscount: 30, boost: 1.3, minLock: 0 },
|
|
73
|
+
platinum: { minStake: 1e5, feeDiscount: 50, boost: 1.4, minLock: 0 }
|
|
74
|
+
};
|
|
75
|
+
var LOCK_DURATIONS = {
|
|
76
|
+
none: 0,
|
|
77
|
+
oneMonth: 30 * 24 * 3600,
|
|
78
|
+
threeMonths: 90 * 24 * 3600,
|
|
79
|
+
sixMonths: 180 * 24 * 3600,
|
|
80
|
+
oneYear: 365 * 24 * 3600
|
|
81
|
+
};
|
|
82
|
+
var SSCRE_CONSTANTS = {
|
|
83
|
+
primaryReserves: 35e7,
|
|
84
|
+
// 350M VCoin
|
|
85
|
+
secondaryReserves: 4e7,
|
|
86
|
+
// 40M VCoin
|
|
87
|
+
epochDuration: 30 * 24 * 3600,
|
|
88
|
+
// 30 days
|
|
89
|
+
claimWindow: 90 * 24 * 3600,
|
|
90
|
+
// 90 days
|
|
91
|
+
gaslessFeeBps: 100,
|
|
92
|
+
// 1%
|
|
93
|
+
minClaimAmount: 1
|
|
94
|
+
// 1 VCoin
|
|
95
|
+
};
|
|
96
|
+
var VILINK_CONSTANTS = {
|
|
97
|
+
maxActionExpiry: 7 * 24 * 3600,
|
|
98
|
+
// 7 days
|
|
99
|
+
minTipAmount: 0.1,
|
|
100
|
+
// 0.1 VCoin
|
|
101
|
+
maxTipAmount: 1e4,
|
|
102
|
+
// 10,000 VCoin
|
|
103
|
+
platformFeeBps: 250
|
|
104
|
+
// 2.5%
|
|
105
|
+
};
|
|
106
|
+
var ACTION_SCOPES = {
|
|
107
|
+
tip: 1 << 0,
|
|
108
|
+
vouch: 1 << 1,
|
|
109
|
+
content: 1 << 2,
|
|
110
|
+
governance: 1 << 3,
|
|
111
|
+
transfer: 1 << 4,
|
|
112
|
+
stake: 1 << 5,
|
|
113
|
+
claim: 1 << 6,
|
|
114
|
+
follow: 1 << 7,
|
|
115
|
+
all: 65535
|
|
116
|
+
};
|
|
117
|
+
var GASLESS_CONSTANTS = {
|
|
118
|
+
sessionDuration: 24 * 3600,
|
|
119
|
+
// 24 hours
|
|
120
|
+
maxSessionActions: 1e3,
|
|
121
|
+
maxSessionSpend: 1e5,
|
|
122
|
+
// 100,000 VCoin
|
|
123
|
+
defaultSolFee: 5e3,
|
|
124
|
+
// 0.000005 SOL
|
|
125
|
+
vcoinFeeMultiplier: 100,
|
|
126
|
+
sscreDeductionBps: 100,
|
|
127
|
+
// 1%
|
|
128
|
+
dailySubsidyBudget: 10,
|
|
129
|
+
// 10 SOL
|
|
130
|
+
maxSubsidizedPerUser: 50
|
|
131
|
+
};
|
|
132
|
+
var FIVE_A_CONSTANTS = {
|
|
133
|
+
maxScore: 1e4,
|
|
134
|
+
// 100.00 with 2 decimal precision
|
|
135
|
+
scoreWeights: {
|
|
136
|
+
authenticity: 25,
|
|
137
|
+
// A1 - "Are you a real person?"
|
|
138
|
+
accuracy: 20,
|
|
139
|
+
// A2 - "Is your content quality?"
|
|
140
|
+
agility: 15,
|
|
141
|
+
// A3 - "Are you fast?"
|
|
142
|
+
activity: 25,
|
|
143
|
+
// A4 - "Do you show up daily?"
|
|
144
|
+
approved: 15
|
|
145
|
+
// A5 - "Does the community like you?"
|
|
146
|
+
},
|
|
147
|
+
scoreMultipliers: {
|
|
148
|
+
"0-20": 0.1,
|
|
149
|
+
"20-40": 0.4,
|
|
150
|
+
"40-60": 0.7,
|
|
151
|
+
"60-80": 1,
|
|
152
|
+
"80-100": 1.2
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var CONTENT_CONSTANTS = {
|
|
156
|
+
maxEnergy: 100,
|
|
157
|
+
energyRegenRate: 10,
|
|
158
|
+
// per hour
|
|
159
|
+
createCost: 10,
|
|
160
|
+
editCost: 5,
|
|
161
|
+
deleteCost: 0
|
|
162
|
+
};
|
|
163
|
+
var GOVERNANCE_CONSTANTS = {
|
|
164
|
+
minProposalThreshold: 100,
|
|
165
|
+
// 100 veVCoin
|
|
166
|
+
votingDuration: 7 * 24 * 3600,
|
|
167
|
+
// 7 days
|
|
168
|
+
executionDelay: 2 * 24 * 3600,
|
|
169
|
+
// 2 days
|
|
170
|
+
vetoWindow: 24 * 3600,
|
|
171
|
+
// 1 day
|
|
172
|
+
quorumBps: 400
|
|
173
|
+
// 4%
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// src/core/index.ts
|
|
177
|
+
var ViWoConnection = class {
|
|
178
|
+
constructor(config) {
|
|
179
|
+
this.commitment = config.commitment || "confirmed";
|
|
180
|
+
this.connection = new Connection(
|
|
181
|
+
config.endpoint,
|
|
182
|
+
{
|
|
183
|
+
commitment: this.commitment,
|
|
184
|
+
wsEndpoint: config.wsEndpoint
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get current slot
|
|
190
|
+
*/
|
|
191
|
+
async getSlot() {
|
|
192
|
+
return this.connection.getSlot(this.commitment);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get current block time
|
|
196
|
+
*/
|
|
197
|
+
async getBlockTime() {
|
|
198
|
+
const slot = await this.getSlot();
|
|
199
|
+
return this.connection.getBlockTime(slot);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Check if connection is healthy
|
|
203
|
+
*/
|
|
204
|
+
async isHealthy() {
|
|
205
|
+
try {
|
|
206
|
+
await this.connection.getVersion();
|
|
207
|
+
return true;
|
|
208
|
+
} catch {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
var PDAs = class {
|
|
214
|
+
constructor(programIds = PROGRAM_IDS) {
|
|
215
|
+
this.programIds = programIds;
|
|
216
|
+
}
|
|
217
|
+
// VCoin PDAs
|
|
218
|
+
getVCoinConfig() {
|
|
219
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
220
|
+
[Buffer.from(SEEDS.vcoinConfig)],
|
|
221
|
+
this.programIds.vcoinToken
|
|
222
|
+
);
|
|
223
|
+
return pda;
|
|
224
|
+
}
|
|
225
|
+
// Staking PDAs
|
|
226
|
+
getStakingPool() {
|
|
227
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
228
|
+
[Buffer.from(SEEDS.stakingPool)],
|
|
229
|
+
this.programIds.stakingProtocol
|
|
230
|
+
);
|
|
231
|
+
return pda;
|
|
232
|
+
}
|
|
233
|
+
getUserStake(user) {
|
|
234
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
235
|
+
[Buffer.from(SEEDS.userStake), user.toBuffer()],
|
|
236
|
+
this.programIds.stakingProtocol
|
|
237
|
+
);
|
|
238
|
+
return pda;
|
|
239
|
+
}
|
|
240
|
+
// Governance PDAs
|
|
241
|
+
getGovernanceConfig() {
|
|
242
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
243
|
+
[Buffer.from(SEEDS.governanceConfig)],
|
|
244
|
+
this.programIds.governanceProtocol
|
|
245
|
+
);
|
|
246
|
+
return pda;
|
|
247
|
+
}
|
|
248
|
+
getProposal(proposalId) {
|
|
249
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
250
|
+
[Buffer.from(SEEDS.proposal), proposalId.toArrayLike(Buffer, "le", 8)],
|
|
251
|
+
this.programIds.governanceProtocol
|
|
252
|
+
);
|
|
253
|
+
return pda;
|
|
254
|
+
}
|
|
255
|
+
getVoteRecord(user, proposal) {
|
|
256
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
257
|
+
[Buffer.from(SEEDS.voteRecord), user.toBuffer(), proposal.toBuffer()],
|
|
258
|
+
this.programIds.governanceProtocol
|
|
259
|
+
);
|
|
260
|
+
return pda;
|
|
261
|
+
}
|
|
262
|
+
// SSCRE PDAs
|
|
263
|
+
getRewardsPoolConfig() {
|
|
264
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
265
|
+
[Buffer.from(SEEDS.poolConfig)],
|
|
266
|
+
this.programIds.sscreProtocol
|
|
267
|
+
);
|
|
268
|
+
return pda;
|
|
269
|
+
}
|
|
270
|
+
getEpochDistribution(epoch) {
|
|
271
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
272
|
+
[Buffer.from(SEEDS.epoch), epoch.toArrayLike(Buffer, "le", 8)],
|
|
273
|
+
this.programIds.sscreProtocol
|
|
274
|
+
);
|
|
275
|
+
return pda;
|
|
276
|
+
}
|
|
277
|
+
getUserClaim(user) {
|
|
278
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
279
|
+
[Buffer.from(SEEDS.userClaim), user.toBuffer()],
|
|
280
|
+
this.programIds.sscreProtocol
|
|
281
|
+
);
|
|
282
|
+
return pda;
|
|
283
|
+
}
|
|
284
|
+
// ViLink PDAs
|
|
285
|
+
getViLinkConfig() {
|
|
286
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
287
|
+
[Buffer.from(SEEDS.vilinkConfig)],
|
|
288
|
+
this.programIds.vilinkProtocol
|
|
289
|
+
);
|
|
290
|
+
return pda;
|
|
291
|
+
}
|
|
292
|
+
getViLinkAction(creator, timestamp) {
|
|
293
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
294
|
+
[
|
|
295
|
+
Buffer.from(SEEDS.action),
|
|
296
|
+
creator.toBuffer(),
|
|
297
|
+
timestamp.toArrayLike(Buffer, "le", 8)
|
|
298
|
+
],
|
|
299
|
+
this.programIds.vilinkProtocol
|
|
300
|
+
);
|
|
301
|
+
return pda;
|
|
302
|
+
}
|
|
303
|
+
getUserActionStats(user) {
|
|
304
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
305
|
+
[Buffer.from(SEEDS.userStats), user.toBuffer()],
|
|
306
|
+
this.programIds.vilinkProtocol
|
|
307
|
+
);
|
|
308
|
+
return pda;
|
|
309
|
+
}
|
|
310
|
+
// Gasless PDAs
|
|
311
|
+
getGaslessConfig() {
|
|
312
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
313
|
+
[Buffer.from(SEEDS.gaslessConfig)],
|
|
314
|
+
this.programIds.gaslessProtocol
|
|
315
|
+
);
|
|
316
|
+
return pda;
|
|
317
|
+
}
|
|
318
|
+
getSessionKey(user, sessionPubkey) {
|
|
319
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
320
|
+
[
|
|
321
|
+
Buffer.from(SEEDS.sessionKey),
|
|
322
|
+
user.toBuffer(),
|
|
323
|
+
sessionPubkey.toBuffer()
|
|
324
|
+
],
|
|
325
|
+
this.programIds.gaslessProtocol
|
|
326
|
+
);
|
|
327
|
+
return pda;
|
|
328
|
+
}
|
|
329
|
+
getUserGaslessStats(user) {
|
|
330
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
331
|
+
[Buffer.from(SEEDS.userGasless), user.toBuffer()],
|
|
332
|
+
this.programIds.gaslessProtocol
|
|
333
|
+
);
|
|
334
|
+
return pda;
|
|
335
|
+
}
|
|
336
|
+
// Identity PDAs
|
|
337
|
+
getIdentityConfig() {
|
|
338
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
339
|
+
[Buffer.from(SEEDS.identityConfig)],
|
|
340
|
+
this.programIds.identityProtocol
|
|
341
|
+
);
|
|
342
|
+
return pda;
|
|
343
|
+
}
|
|
344
|
+
getUserIdentity(user) {
|
|
345
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
346
|
+
[Buffer.from(SEEDS.identity), user.toBuffer()],
|
|
347
|
+
this.programIds.identityProtocol
|
|
348
|
+
);
|
|
349
|
+
return pda;
|
|
350
|
+
}
|
|
351
|
+
// 5A Protocol PDAs
|
|
352
|
+
getFiveAConfig() {
|
|
353
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
354
|
+
[Buffer.from(SEEDS.fiveAConfig)],
|
|
355
|
+
this.programIds.fiveAProtocol
|
|
356
|
+
);
|
|
357
|
+
return pda;
|
|
358
|
+
}
|
|
359
|
+
getUserScore(user) {
|
|
360
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
361
|
+
[Buffer.from(SEEDS.userScore), user.toBuffer()],
|
|
362
|
+
this.programIds.fiveAProtocol
|
|
363
|
+
);
|
|
364
|
+
return pda;
|
|
365
|
+
}
|
|
366
|
+
// Content PDAs
|
|
367
|
+
getContentRegistryConfig() {
|
|
368
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
369
|
+
[Buffer.from(SEEDS.registryConfig)],
|
|
370
|
+
this.programIds.contentRegistry
|
|
371
|
+
);
|
|
372
|
+
return pda;
|
|
373
|
+
}
|
|
374
|
+
getContentRecord(contentId) {
|
|
375
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
376
|
+
[Buffer.from(SEEDS.content), Buffer.from(contentId)],
|
|
377
|
+
this.programIds.contentRegistry
|
|
378
|
+
);
|
|
379
|
+
return pda;
|
|
380
|
+
}
|
|
381
|
+
getUserEnergy(user) {
|
|
382
|
+
const [pda] = PublicKey2.findProgramAddressSync(
|
|
383
|
+
[Buffer.from(SEEDS.userEnergy), user.toBuffer()],
|
|
384
|
+
this.programIds.contentRegistry
|
|
385
|
+
);
|
|
386
|
+
return pda;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
var TransactionBuilder = class {
|
|
390
|
+
constructor() {
|
|
391
|
+
this.instructions = [];
|
|
392
|
+
}
|
|
393
|
+
add(instruction) {
|
|
394
|
+
this.instructions.push(instruction);
|
|
395
|
+
return this;
|
|
396
|
+
}
|
|
397
|
+
addMany(instructions) {
|
|
398
|
+
this.instructions.push(...instructions);
|
|
399
|
+
return this;
|
|
400
|
+
}
|
|
401
|
+
build() {
|
|
402
|
+
const tx = new Transaction();
|
|
403
|
+
for (const ix of this.instructions) {
|
|
404
|
+
tx.add(ix);
|
|
405
|
+
}
|
|
406
|
+
return tx;
|
|
407
|
+
}
|
|
408
|
+
clear() {
|
|
409
|
+
this.instructions = [];
|
|
410
|
+
return this;
|
|
411
|
+
}
|
|
412
|
+
get length() {
|
|
413
|
+
return this.instructions.length;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
function formatVCoin(amount, decimals = 9) {
|
|
417
|
+
const amountBN = typeof amount === "number" ? new BN(amount) : amount;
|
|
418
|
+
const divisor = new BN(10).pow(new BN(decimals));
|
|
419
|
+
const whole = amountBN.div(divisor).toString();
|
|
420
|
+
const fraction = amountBN.mod(divisor).toString().padStart(decimals, "0");
|
|
421
|
+
return `${whole}.${fraction}`;
|
|
422
|
+
}
|
|
423
|
+
function parseVCoin(amount, decimals = 9) {
|
|
424
|
+
if (typeof amount === "number") {
|
|
425
|
+
amount = amount.toString();
|
|
426
|
+
}
|
|
427
|
+
const [whole, fraction = ""] = amount.split(".");
|
|
428
|
+
const paddedFraction = fraction.padEnd(decimals, "0").slice(0, decimals);
|
|
429
|
+
return new BN(whole + paddedFraction);
|
|
430
|
+
}
|
|
431
|
+
function getCurrentTimestamp() {
|
|
432
|
+
return Math.floor(Date.now() / 1e3);
|
|
433
|
+
}
|
|
434
|
+
function timestampToDate(timestamp) {
|
|
435
|
+
const ts = typeof timestamp === "number" ? timestamp : timestamp.toNumber();
|
|
436
|
+
return new Date(ts * 1e3);
|
|
437
|
+
}
|
|
438
|
+
function dateToTimestamp(date) {
|
|
439
|
+
return Math.floor(date.getTime() / 1e3);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/types.ts
|
|
443
|
+
var StakingTier = /* @__PURE__ */ ((StakingTier2) => {
|
|
444
|
+
StakingTier2[StakingTier2["None"] = 0] = "None";
|
|
445
|
+
StakingTier2[StakingTier2["Bronze"] = 1] = "Bronze";
|
|
446
|
+
StakingTier2[StakingTier2["Silver"] = 2] = "Silver";
|
|
447
|
+
StakingTier2[StakingTier2["Gold"] = 3] = "Gold";
|
|
448
|
+
StakingTier2[StakingTier2["Platinum"] = 4] = "Platinum";
|
|
449
|
+
return StakingTier2;
|
|
450
|
+
})(StakingTier || {});
|
|
451
|
+
var ProposalStatus = /* @__PURE__ */ ((ProposalStatus2) => {
|
|
452
|
+
ProposalStatus2[ProposalStatus2["Active"] = 0] = "Active";
|
|
453
|
+
ProposalStatus2[ProposalStatus2["Passed"] = 1] = "Passed";
|
|
454
|
+
ProposalStatus2[ProposalStatus2["Rejected"] = 2] = "Rejected";
|
|
455
|
+
ProposalStatus2[ProposalStatus2["Executed"] = 3] = "Executed";
|
|
456
|
+
ProposalStatus2[ProposalStatus2["Cancelled"] = 4] = "Cancelled";
|
|
457
|
+
return ProposalStatus2;
|
|
458
|
+
})(ProposalStatus || {});
|
|
459
|
+
var ActionType = /* @__PURE__ */ ((ActionType2) => {
|
|
460
|
+
ActionType2[ActionType2["Tip"] = 0] = "Tip";
|
|
461
|
+
ActionType2[ActionType2["Vouch"] = 1] = "Vouch";
|
|
462
|
+
ActionType2[ActionType2["Follow"] = 2] = "Follow";
|
|
463
|
+
ActionType2[ActionType2["Challenge"] = 3] = "Challenge";
|
|
464
|
+
ActionType2[ActionType2["Stake"] = 4] = "Stake";
|
|
465
|
+
ActionType2[ActionType2["ContentReact"] = 5] = "ContentReact";
|
|
466
|
+
ActionType2[ActionType2["Delegate"] = 6] = "Delegate";
|
|
467
|
+
ActionType2[ActionType2["Vote"] = 7] = "Vote";
|
|
468
|
+
return ActionType2;
|
|
469
|
+
})(ActionType || {});
|
|
470
|
+
var FeeMethod = /* @__PURE__ */ ((FeeMethod2) => {
|
|
471
|
+
FeeMethod2[FeeMethod2["PlatformSubsidized"] = 0] = "PlatformSubsidized";
|
|
472
|
+
FeeMethod2[FeeMethod2["VCoinDeduction"] = 1] = "VCoinDeduction";
|
|
473
|
+
FeeMethod2[FeeMethod2["SSCREDeduction"] = 2] = "SSCREDeduction";
|
|
474
|
+
return FeeMethod2;
|
|
475
|
+
})(FeeMethod || {});
|
|
476
|
+
var VerificationLevel = /* @__PURE__ */ ((VerificationLevel2) => {
|
|
477
|
+
VerificationLevel2[VerificationLevel2["None"] = 0] = "None";
|
|
478
|
+
VerificationLevel2[VerificationLevel2["Basic"] = 1] = "Basic";
|
|
479
|
+
VerificationLevel2[VerificationLevel2["KYC"] = 2] = "KYC";
|
|
480
|
+
VerificationLevel2[VerificationLevel2["Full"] = 3] = "Full";
|
|
481
|
+
VerificationLevel2[VerificationLevel2["Enhanced"] = 4] = "Enhanced";
|
|
482
|
+
return VerificationLevel2;
|
|
483
|
+
})(VerificationLevel || {});
|
|
484
|
+
var ContentState = /* @__PURE__ */ ((ContentState2) => {
|
|
485
|
+
ContentState2[ContentState2["Active"] = 0] = "Active";
|
|
486
|
+
ContentState2[ContentState2["Hidden"] = 1] = "Hidden";
|
|
487
|
+
ContentState2[ContentState2["Deleted"] = 2] = "Deleted";
|
|
488
|
+
ContentState2[ContentState2["Flagged"] = 3] = "Flagged";
|
|
489
|
+
return ContentState2;
|
|
490
|
+
})(ContentState || {});
|
|
491
|
+
|
|
492
|
+
// src/staking/index.ts
|
|
493
|
+
import { PublicKey as PublicKey3, Transaction as Transaction2 } from "@solana/web3.js";
|
|
494
|
+
import { BN as BN2 } from "@coral-xyz/anchor";
|
|
495
|
+
var StakingClient = class {
|
|
496
|
+
constructor(client) {
|
|
497
|
+
this.client = client;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Get staking pool configuration
|
|
501
|
+
*/
|
|
502
|
+
async getPool() {
|
|
503
|
+
try {
|
|
504
|
+
const poolPda = this.client.pdas.getStakingPool();
|
|
505
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(poolPda);
|
|
506
|
+
if (!accountInfo) {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
authority: new PublicKey3(accountInfo.data.slice(8, 40)),
|
|
511
|
+
vcoinMint: new PublicKey3(accountInfo.data.slice(40, 72)),
|
|
512
|
+
vevcoinMint: new PublicKey3(accountInfo.data.slice(72, 104)),
|
|
513
|
+
totalStaked: new BN2(accountInfo.data.slice(104, 112), "le"),
|
|
514
|
+
totalVevcoinMinted: new BN2(accountInfo.data.slice(112, 120), "le"),
|
|
515
|
+
paused: accountInfo.data[120] !== 0
|
|
516
|
+
};
|
|
517
|
+
} catch {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Get user stake information
|
|
523
|
+
*/
|
|
524
|
+
async getUserStake(user) {
|
|
525
|
+
const target = user || this.client.publicKey;
|
|
526
|
+
if (!target) {
|
|
527
|
+
throw new Error("No user specified and wallet not connected");
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
const stakePda = this.client.pdas.getUserStake(target);
|
|
531
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(stakePda);
|
|
532
|
+
if (!accountInfo) {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
user: new PublicKey3(accountInfo.data.slice(8, 40)),
|
|
537
|
+
stakedAmount: new BN2(accountInfo.data.slice(40, 48), "le"),
|
|
538
|
+
vevcoinBalance: new BN2(accountInfo.data.slice(48, 56), "le"),
|
|
539
|
+
tier: accountInfo.data[56],
|
|
540
|
+
lockEndTime: new BN2(accountInfo.data.slice(57, 65), "le"),
|
|
541
|
+
lastUpdateTime: new BN2(accountInfo.data.slice(65, 73), "le")
|
|
542
|
+
};
|
|
543
|
+
} catch {
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Calculate tier based on stake amount
|
|
549
|
+
*/
|
|
550
|
+
calculateTier(stakeAmount) {
|
|
551
|
+
const amount = typeof stakeAmount === "number" ? stakeAmount : stakeAmount.toNumber() / Math.pow(10, VCOIN_DECIMALS);
|
|
552
|
+
if (amount >= STAKING_TIERS.platinum.minStake) return 4;
|
|
553
|
+
if (amount >= STAKING_TIERS.gold.minStake) return 3;
|
|
554
|
+
if (amount >= STAKING_TIERS.silver.minStake) return 2;
|
|
555
|
+
if (amount >= STAKING_TIERS.bronze.minStake) return 1;
|
|
556
|
+
return 0;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Calculate veVCoin amount for given stake
|
|
560
|
+
* Formula: ve_vcoin = staked_amount × (lock_duration / 4_years) × tier_boost
|
|
561
|
+
*/
|
|
562
|
+
calculateVeVCoin(amount, lockDuration) {
|
|
563
|
+
const FOUR_YEARS = 4 * 365 * 24 * 3600;
|
|
564
|
+
const lockRatio = lockDuration / FOUR_YEARS;
|
|
565
|
+
const tier = this.calculateTier(amount);
|
|
566
|
+
const tierBoosts = [1, 1.1, 1.2, 1.3, 1.4];
|
|
567
|
+
const tierBoost = tierBoosts[tier];
|
|
568
|
+
const multiplier = lockRatio * tierBoost;
|
|
569
|
+
const vevcoinAmount = amount.toNumber() * multiplier;
|
|
570
|
+
return new BN2(Math.floor(vevcoinAmount));
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Get tier name
|
|
574
|
+
*/
|
|
575
|
+
getTierName(tier) {
|
|
576
|
+
const names = ["None", "Bronze", "Silver", "Gold", "Platinum"];
|
|
577
|
+
return names[tier] || "Unknown";
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Get tier info
|
|
581
|
+
*/
|
|
582
|
+
getTierInfo(tier) {
|
|
583
|
+
const tiers = [
|
|
584
|
+
STAKING_TIERS.none,
|
|
585
|
+
STAKING_TIERS.bronze,
|
|
586
|
+
STAKING_TIERS.silver,
|
|
587
|
+
STAKING_TIERS.gold,
|
|
588
|
+
STAKING_TIERS.platinum
|
|
589
|
+
];
|
|
590
|
+
return tiers[tier] || STAKING_TIERS.none;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Check if user can unstake
|
|
594
|
+
*/
|
|
595
|
+
async canUnstake(user) {
|
|
596
|
+
const stakeInfo = await this.getUserStake(user);
|
|
597
|
+
if (!stakeInfo) {
|
|
598
|
+
return { canUnstake: false, reason: "No active stake found" };
|
|
599
|
+
}
|
|
600
|
+
if (stakeInfo.stakedAmount.isZero()) {
|
|
601
|
+
return { canUnstake: false, reason: "No staked amount" };
|
|
602
|
+
}
|
|
603
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
604
|
+
if (stakeInfo.lockEndTime.toNumber() > now) {
|
|
605
|
+
const remaining = stakeInfo.lockEndTime.toNumber() - now;
|
|
606
|
+
const days = Math.ceil(remaining / 86400);
|
|
607
|
+
return { canUnstake: false, reason: `Lock period active: ${days} days remaining` };
|
|
608
|
+
}
|
|
609
|
+
return { canUnstake: true };
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Get staking statistics
|
|
613
|
+
*/
|
|
614
|
+
async getStats() {
|
|
615
|
+
const pool = await this.getPool();
|
|
616
|
+
const userStake = this.client.publicKey ? await this.getUserStake() : null;
|
|
617
|
+
return {
|
|
618
|
+
totalStaked: pool ? formatVCoin(pool.totalStaked) : "0",
|
|
619
|
+
totalVevcoin: pool ? formatVCoin(pool.totalVevcoinMinted) : "0",
|
|
620
|
+
userStake: userStake ? formatVCoin(userStake.stakedAmount) : null,
|
|
621
|
+
userVevcoin: userStake ? formatVCoin(userStake.vevcoinBalance) : null,
|
|
622
|
+
userTier: userStake ? this.getTierName(userStake.tier) : null
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
// ============ Transaction Building ============
|
|
626
|
+
// Note: Full implementation would use Anchor IDL
|
|
627
|
+
/**
|
|
628
|
+
* Build stake instruction
|
|
629
|
+
* @param params Stake parameters
|
|
630
|
+
* @returns Transaction to sign and send
|
|
631
|
+
*/
|
|
632
|
+
async buildStakeTransaction(params) {
|
|
633
|
+
if (!this.client.publicKey) {
|
|
634
|
+
throw new Error("Wallet not connected");
|
|
635
|
+
}
|
|
636
|
+
const tx = new Transaction2();
|
|
637
|
+
return tx;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Build unstake instruction
|
|
641
|
+
* @returns Transaction to sign and send
|
|
642
|
+
*/
|
|
643
|
+
async buildUnstakeTransaction() {
|
|
644
|
+
if (!this.client.publicKey) {
|
|
645
|
+
throw new Error("Wallet not connected");
|
|
646
|
+
}
|
|
647
|
+
const { canUnstake, reason } = await this.canUnstake();
|
|
648
|
+
if (!canUnstake) {
|
|
649
|
+
throw new Error(reason);
|
|
650
|
+
}
|
|
651
|
+
const tx = new Transaction2();
|
|
652
|
+
return tx;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Build extend lock instruction
|
|
656
|
+
* @param newDuration New lock duration in seconds
|
|
657
|
+
* @returns Transaction to sign and send
|
|
658
|
+
*/
|
|
659
|
+
async buildExtendLockTransaction(newDuration) {
|
|
660
|
+
if (!this.client.publicKey) {
|
|
661
|
+
throw new Error("Wallet not connected");
|
|
662
|
+
}
|
|
663
|
+
const tx = new Transaction2();
|
|
664
|
+
return tx;
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
// src/governance/index.ts
|
|
669
|
+
import { PublicKey as PublicKey4, Transaction as Transaction3 } from "@solana/web3.js";
|
|
670
|
+
import { BN as BN3 } from "@coral-xyz/anchor";
|
|
671
|
+
var GovernanceClient = class {
|
|
672
|
+
constructor(client) {
|
|
673
|
+
this.client = client;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Get governance configuration
|
|
677
|
+
*/
|
|
678
|
+
async getConfig() {
|
|
679
|
+
try {
|
|
680
|
+
const configPda = this.client.pdas.getGovernanceConfig();
|
|
681
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(configPda);
|
|
682
|
+
if (!accountInfo) {
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
return {
|
|
686
|
+
authority: new PublicKey4(accountInfo.data.slice(8, 40)),
|
|
687
|
+
proposalCount: new BN3(accountInfo.data.slice(40, 48), "le"),
|
|
688
|
+
vevcoinMint: new PublicKey4(accountInfo.data.slice(48, 80)),
|
|
689
|
+
paused: accountInfo.data[80] !== 0
|
|
690
|
+
};
|
|
691
|
+
} catch {
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Get proposal by ID
|
|
697
|
+
*/
|
|
698
|
+
async getProposal(proposalId) {
|
|
699
|
+
try {
|
|
700
|
+
const proposalPda = this.client.pdas.getProposal(proposalId);
|
|
701
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(proposalPda);
|
|
702
|
+
if (!accountInfo) {
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
const data = accountInfo.data;
|
|
706
|
+
return {
|
|
707
|
+
id: new BN3(data.slice(8, 16), "le"),
|
|
708
|
+
proposer: new PublicKey4(data.slice(16, 48)),
|
|
709
|
+
title: Buffer.from(data.slice(48, 112)).toString("utf8").replace(/\0/g, ""),
|
|
710
|
+
descriptionHash: new Uint8Array(data.slice(112, 144)),
|
|
711
|
+
startTime: new BN3(data.slice(144, 152), "le"),
|
|
712
|
+
endTime: new BN3(data.slice(152, 160), "le"),
|
|
713
|
+
votesFor: new BN3(data.slice(160, 168), "le"),
|
|
714
|
+
votesAgainst: new BN3(data.slice(168, 176), "le"),
|
|
715
|
+
status: data[176],
|
|
716
|
+
executed: data[177] !== 0,
|
|
717
|
+
category: data[178]
|
|
718
|
+
};
|
|
719
|
+
} catch {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Get all active proposals
|
|
725
|
+
*/
|
|
726
|
+
async getActiveProposals() {
|
|
727
|
+
const config = await this.getConfig();
|
|
728
|
+
if (!config) return [];
|
|
729
|
+
const proposals = [];
|
|
730
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
731
|
+
const proposalCount = config.proposalCount.toNumber();
|
|
732
|
+
const startFrom = Math.max(0, proposalCount - 20);
|
|
733
|
+
for (let i = startFrom; i < proposalCount; i++) {
|
|
734
|
+
const proposal = await this.getProposal(new BN3(i));
|
|
735
|
+
if (proposal && proposal.endTime.toNumber() > now && proposal.status === 0) {
|
|
736
|
+
proposals.push(proposal);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return proposals;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Get user's vote record for a proposal
|
|
743
|
+
*/
|
|
744
|
+
async getVoteRecord(proposalId, user) {
|
|
745
|
+
const target = user || this.client.publicKey;
|
|
746
|
+
if (!target) {
|
|
747
|
+
throw new Error("No user specified and wallet not connected");
|
|
748
|
+
}
|
|
749
|
+
try {
|
|
750
|
+
const proposalPda = this.client.pdas.getProposal(proposalId);
|
|
751
|
+
const votePda = this.client.pdas.getVoteRecord(target, proposalPda);
|
|
752
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(votePda);
|
|
753
|
+
if (!accountInfo) {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
const data = accountInfo.data;
|
|
757
|
+
return {
|
|
758
|
+
user: new PublicKey4(data.slice(8, 40)),
|
|
759
|
+
proposal: new PublicKey4(data.slice(40, 72)),
|
|
760
|
+
votePower: new BN3(data.slice(72, 80), "le"),
|
|
761
|
+
support: data[80] !== 0,
|
|
762
|
+
votedAt: new BN3(data.slice(81, 89), "le")
|
|
763
|
+
};
|
|
764
|
+
} catch {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Check if user has voted on a proposal
|
|
770
|
+
*/
|
|
771
|
+
async hasVoted(proposalId, user) {
|
|
772
|
+
const voteRecord = await this.getVoteRecord(proposalId, user);
|
|
773
|
+
return voteRecord !== null;
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Calculate user's voting power
|
|
777
|
+
*/
|
|
778
|
+
async getVotingPower(user) {
|
|
779
|
+
const target = user || this.client.publicKey;
|
|
780
|
+
if (!target) {
|
|
781
|
+
throw new Error("No user specified and wallet not connected");
|
|
782
|
+
}
|
|
783
|
+
const vevcoinBalance = await this.client.getVeVCoinBalance(target);
|
|
784
|
+
const fiveAMultiplier = 1;
|
|
785
|
+
return new BN3(Math.floor(vevcoinBalance.toNumber() * fiveAMultiplier));
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Get proposal status text
|
|
789
|
+
*/
|
|
790
|
+
getStatusText(status) {
|
|
791
|
+
const statuses = ["Active", "Passed", "Rejected", "Executed", "Cancelled"];
|
|
792
|
+
return statuses[status] || "Unknown";
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Check if proposal can be executed
|
|
796
|
+
*/
|
|
797
|
+
async canExecute(proposalId) {
|
|
798
|
+
const proposal = await this.getProposal(proposalId);
|
|
799
|
+
if (!proposal) {
|
|
800
|
+
return { canExecute: false, reason: "Proposal not found" };
|
|
801
|
+
}
|
|
802
|
+
if (proposal.executed) {
|
|
803
|
+
return { canExecute: false, reason: "Proposal already executed" };
|
|
804
|
+
}
|
|
805
|
+
if (proposal.status !== 1 /* Passed */) {
|
|
806
|
+
return { canExecute: false, reason: "Proposal has not passed" };
|
|
807
|
+
}
|
|
808
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
809
|
+
const executionDelay = proposal.endTime.toNumber() + GOVERNANCE_CONSTANTS.executionDelay;
|
|
810
|
+
if (now < executionDelay) {
|
|
811
|
+
const remaining = executionDelay - now;
|
|
812
|
+
const hours = Math.ceil(remaining / 3600);
|
|
813
|
+
return { canExecute: false, reason: `Execution delay: ${hours} hours remaining` };
|
|
814
|
+
}
|
|
815
|
+
return { canExecute: true };
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Get proposal progress
|
|
819
|
+
*/
|
|
820
|
+
async getProposalProgress(proposalId) {
|
|
821
|
+
const proposal = await this.getProposal(proposalId);
|
|
822
|
+
if (!proposal) {
|
|
823
|
+
throw new Error("Proposal not found");
|
|
824
|
+
}
|
|
825
|
+
const totalVotes = proposal.votesFor.add(proposal.votesAgainst);
|
|
826
|
+
const forPct = totalVotes.isZero() ? 0 : proposal.votesFor.toNumber() / totalVotes.toNumber() * 100;
|
|
827
|
+
const againstPct = 100 - forPct;
|
|
828
|
+
const quorumThreshold = new BN3(GOVERNANCE_CONSTANTS.quorumBps).mul(new BN3(1e4));
|
|
829
|
+
const quorumReached = totalVotes.gte(quorumThreshold);
|
|
830
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
831
|
+
const timeRemaining = Math.max(0, proposal.endTime.toNumber() - now);
|
|
832
|
+
return {
|
|
833
|
+
votesFor: formatVCoin(proposal.votesFor),
|
|
834
|
+
votesAgainst: formatVCoin(proposal.votesAgainst),
|
|
835
|
+
totalVotes: formatVCoin(totalVotes),
|
|
836
|
+
forPercentage: forPct,
|
|
837
|
+
againstPercentage: againstPct,
|
|
838
|
+
quorumReached,
|
|
839
|
+
timeRemaining
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
// ============ Transaction Building ============
|
|
843
|
+
/**
|
|
844
|
+
* Build create proposal transaction
|
|
845
|
+
*/
|
|
846
|
+
async buildCreateProposalTransaction(params) {
|
|
847
|
+
if (!this.client.publicKey) {
|
|
848
|
+
throw new Error("Wallet not connected");
|
|
849
|
+
}
|
|
850
|
+
const votingPower = await this.getVotingPower();
|
|
851
|
+
if (votingPower.toNumber() < GOVERNANCE_CONSTANTS.minProposalThreshold) {
|
|
852
|
+
throw new Error(`Insufficient voting power. Need ${GOVERNANCE_CONSTANTS.minProposalThreshold} veVCoin`);
|
|
853
|
+
}
|
|
854
|
+
const tx = new Transaction3();
|
|
855
|
+
return tx;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Build vote transaction
|
|
859
|
+
*/
|
|
860
|
+
async buildVoteTransaction(proposalId, support) {
|
|
861
|
+
if (!this.client.publicKey) {
|
|
862
|
+
throw new Error("Wallet not connected");
|
|
863
|
+
}
|
|
864
|
+
const hasVoted = await this.hasVoted(proposalId);
|
|
865
|
+
if (hasVoted) {
|
|
866
|
+
throw new Error("Already voted on this proposal");
|
|
867
|
+
}
|
|
868
|
+
const tx = new Transaction3();
|
|
869
|
+
return tx;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Build execute proposal transaction
|
|
873
|
+
*/
|
|
874
|
+
async buildExecuteTransaction(proposalId) {
|
|
875
|
+
if (!this.client.publicKey) {
|
|
876
|
+
throw new Error("Wallet not connected");
|
|
877
|
+
}
|
|
878
|
+
const { canExecute, reason } = await this.canExecute(proposalId);
|
|
879
|
+
if (!canExecute) {
|
|
880
|
+
throw new Error(reason);
|
|
881
|
+
}
|
|
882
|
+
const tx = new Transaction3();
|
|
883
|
+
return tx;
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
// src/rewards/index.ts
|
|
888
|
+
import { PublicKey as PublicKey5, Transaction as Transaction4 } from "@solana/web3.js";
|
|
889
|
+
import { BN as BN4 } from "@coral-xyz/anchor";
|
|
890
|
+
var RewardsClient = class {
|
|
891
|
+
constructor(client) {
|
|
892
|
+
this.client = client;
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Get rewards pool configuration
|
|
896
|
+
*/
|
|
897
|
+
async getPoolConfig() {
|
|
898
|
+
try {
|
|
899
|
+
const configPda = this.client.pdas.getRewardsPoolConfig();
|
|
900
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(configPda);
|
|
901
|
+
if (!accountInfo) {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
const data = accountInfo.data;
|
|
905
|
+
return {
|
|
906
|
+
authority: new PublicKey5(data.slice(8, 40)),
|
|
907
|
+
vcoinMint: new PublicKey5(data.slice(40, 72)),
|
|
908
|
+
currentEpoch: new BN4(data.slice(72, 80), "le"),
|
|
909
|
+
totalDistributed: new BN4(data.slice(80, 88), "le"),
|
|
910
|
+
remainingReserves: new BN4(data.slice(88, 96), "le"),
|
|
911
|
+
paused: data[96] !== 0
|
|
912
|
+
};
|
|
913
|
+
} catch {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Get epoch distribution details
|
|
919
|
+
*/
|
|
920
|
+
async getEpochDistribution(epoch) {
|
|
921
|
+
try {
|
|
922
|
+
const epochPda = this.client.pdas.getEpochDistribution(epoch);
|
|
923
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(epochPda);
|
|
924
|
+
if (!accountInfo) {
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
const data = accountInfo.data;
|
|
928
|
+
return {
|
|
929
|
+
epoch: new BN4(data.slice(8, 16), "le"),
|
|
930
|
+
merkleRoot: new Uint8Array(data.slice(16, 48)),
|
|
931
|
+
totalAllocation: new BN4(data.slice(48, 56), "le"),
|
|
932
|
+
totalClaimed: new BN4(data.slice(56, 64), "le"),
|
|
933
|
+
claimsCount: new BN4(data.slice(64, 72), "le"),
|
|
934
|
+
isFinalized: data[72] !== 0
|
|
935
|
+
};
|
|
936
|
+
} catch {
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Get current epoch
|
|
942
|
+
*/
|
|
943
|
+
async getCurrentEpoch() {
|
|
944
|
+
const config = await this.getPoolConfig();
|
|
945
|
+
return config?.currentEpoch || new BN4(0);
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Get user claim history
|
|
949
|
+
*/
|
|
950
|
+
async getUserClaim(user) {
|
|
951
|
+
const target = user || this.client.publicKey;
|
|
952
|
+
if (!target) {
|
|
953
|
+
throw new Error("No user specified and wallet not connected");
|
|
954
|
+
}
|
|
955
|
+
try {
|
|
956
|
+
const claimPda = this.client.pdas.getUserClaim(target);
|
|
957
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(claimPda);
|
|
958
|
+
if (!accountInfo) {
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
const data = accountInfo.data;
|
|
962
|
+
return {
|
|
963
|
+
user: new PublicKey5(data.slice(8, 40)),
|
|
964
|
+
lastClaimedEpoch: new BN4(data.slice(40, 48), "le"),
|
|
965
|
+
totalClaimed: new BN4(data.slice(48, 56), "le"),
|
|
966
|
+
claimsCount: data.readUInt32LE(56)
|
|
967
|
+
};
|
|
968
|
+
} catch {
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Check if user has claimed for an epoch
|
|
974
|
+
*/
|
|
975
|
+
async hasClaimedEpoch(epoch, user) {
|
|
976
|
+
const userClaim = await this.getUserClaim(user);
|
|
977
|
+
if (!userClaim) return false;
|
|
978
|
+
const epochNum = epoch.toNumber();
|
|
979
|
+
if (epochNum <= 255) {
|
|
980
|
+
return userClaim.lastClaimedEpoch.gte(epoch);
|
|
981
|
+
}
|
|
982
|
+
return userClaim.lastClaimedEpoch.gte(epoch);
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Get unclaimed epochs
|
|
986
|
+
*/
|
|
987
|
+
async getUnclaimedEpochs(user) {
|
|
988
|
+
const currentEpoch = await this.getCurrentEpoch();
|
|
989
|
+
const userClaim = await this.getUserClaim(user);
|
|
990
|
+
const unclaimed = [];
|
|
991
|
+
const startEpoch = userClaim ? userClaim.lastClaimedEpoch.toNumber() + 1 : 1;
|
|
992
|
+
for (let e = startEpoch; e <= currentEpoch.toNumber(); e++) {
|
|
993
|
+
const epochDist = await this.getEpochDistribution(new BN4(e));
|
|
994
|
+
if (epochDist?.isFinalized) {
|
|
995
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
996
|
+
const claimExpiry = epochDist.epoch.toNumber() * SSCRE_CONSTANTS.epochDuration + SSCRE_CONSTANTS.claimWindow;
|
|
997
|
+
if (now <= claimExpiry) {
|
|
998
|
+
unclaimed.push(new BN4(e));
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return unclaimed;
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Get rewards statistics
|
|
1006
|
+
*/
|
|
1007
|
+
async getStats() {
|
|
1008
|
+
const config = await this.getPoolConfig();
|
|
1009
|
+
const userClaim = this.client.publicKey ? await this.getUserClaim() : null;
|
|
1010
|
+
const totalReserves = SSCRE_CONSTANTS.primaryReserves * 1e9;
|
|
1011
|
+
const remaining = config?.remainingReserves.toNumber() || 0;
|
|
1012
|
+
const reservePct = remaining / totalReserves * 100;
|
|
1013
|
+
return {
|
|
1014
|
+
currentEpoch: config?.currentEpoch.toNumber() || 0,
|
|
1015
|
+
totalDistributed: config ? formatVCoin(config.totalDistributed) : "0",
|
|
1016
|
+
remainingReserves: config ? formatVCoin(config.remainingReserves) : "0",
|
|
1017
|
+
reservePercentage: reservePct,
|
|
1018
|
+
userTotalClaimed: userClaim ? formatVCoin(userClaim.totalClaimed) : null,
|
|
1019
|
+
userClaimsCount: userClaim?.claimsCount || null
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Calculate gasless fee for claim
|
|
1024
|
+
*/
|
|
1025
|
+
calculateGaslessFee(amount) {
|
|
1026
|
+
const fee = amount.muln(SSCRE_CONSTANTS.gaslessFeeBps).divn(1e4);
|
|
1027
|
+
return fee;
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Calculate net claim amount after fee
|
|
1031
|
+
*/
|
|
1032
|
+
calculateNetClaim(amount) {
|
|
1033
|
+
const fee = this.calculateGaslessFee(amount);
|
|
1034
|
+
return amount.sub(fee);
|
|
1035
|
+
}
|
|
1036
|
+
// ============ Merkle Proof Utilities ============
|
|
1037
|
+
/**
|
|
1038
|
+
* Verify merkle proof locally
|
|
1039
|
+
*/
|
|
1040
|
+
verifyMerkleProof(proof, root, leaf) {
|
|
1041
|
+
let computedHash = leaf;
|
|
1042
|
+
for (const proofElement of proof) {
|
|
1043
|
+
const combined = new Uint8Array(64);
|
|
1044
|
+
if (this.compareBytes(computedHash, proofElement) < 0) {
|
|
1045
|
+
combined.set(computedHash, 0);
|
|
1046
|
+
combined.set(proofElement, 32);
|
|
1047
|
+
} else {
|
|
1048
|
+
combined.set(proofElement, 0);
|
|
1049
|
+
combined.set(computedHash, 32);
|
|
1050
|
+
}
|
|
1051
|
+
computedHash = this.hashBytes(combined);
|
|
1052
|
+
}
|
|
1053
|
+
return this.compareBytes(computedHash, root) === 0;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Compute leaf hash from user data
|
|
1057
|
+
*/
|
|
1058
|
+
computeLeaf(user, amount, epoch) {
|
|
1059
|
+
const data = new Uint8Array(48);
|
|
1060
|
+
data.set(user.toBytes(), 0);
|
|
1061
|
+
data.set(amount.toArrayLike(Buffer, "le", 8), 32);
|
|
1062
|
+
data.set(epoch.toArrayLike(Buffer, "le", 8), 40);
|
|
1063
|
+
return this.hashBytes(data);
|
|
1064
|
+
}
|
|
1065
|
+
compareBytes(a, b) {
|
|
1066
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
1067
|
+
if (a[i] !== b[i]) {
|
|
1068
|
+
return a[i] - b[i];
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
return a.length - b.length;
|
|
1072
|
+
}
|
|
1073
|
+
hashBytes(data) {
|
|
1074
|
+
const hash = new Uint8Array(32);
|
|
1075
|
+
for (let i = 0; i < data.length; i++) {
|
|
1076
|
+
hash[i % 32] ^= data[i];
|
|
1077
|
+
}
|
|
1078
|
+
return hash;
|
|
1079
|
+
}
|
|
1080
|
+
// ============ Transaction Building ============
|
|
1081
|
+
/**
|
|
1082
|
+
* Build claim rewards transaction
|
|
1083
|
+
*/
|
|
1084
|
+
async buildClaimTransaction(params) {
|
|
1085
|
+
if (!this.client.publicKey) {
|
|
1086
|
+
throw new Error("Wallet not connected");
|
|
1087
|
+
}
|
|
1088
|
+
if (params.amount.lt(new BN4(SSCRE_CONSTANTS.minClaimAmount * 1e9))) {
|
|
1089
|
+
throw new Error(`Claim amount below minimum: ${SSCRE_CONSTANTS.minClaimAmount} VCoin`);
|
|
1090
|
+
}
|
|
1091
|
+
const hasClaimed = await this.hasClaimedEpoch(params.epoch);
|
|
1092
|
+
if (hasClaimed) {
|
|
1093
|
+
throw new Error("Already claimed for this epoch");
|
|
1094
|
+
}
|
|
1095
|
+
const tx = new Transaction4();
|
|
1096
|
+
return tx;
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
// src/vilink/index.ts
|
|
1101
|
+
import { PublicKey as PublicKey6, Transaction as Transaction5 } from "@solana/web3.js";
|
|
1102
|
+
import { BN as BN5 } from "@coral-xyz/anchor";
|
|
1103
|
+
var ViLinkClient = class {
|
|
1104
|
+
constructor(client) {
|
|
1105
|
+
this.client = client;
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Get ViLink configuration
|
|
1109
|
+
*/
|
|
1110
|
+
async getConfig() {
|
|
1111
|
+
try {
|
|
1112
|
+
const configPda = this.client.pdas.getViLinkConfig();
|
|
1113
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(configPda);
|
|
1114
|
+
if (!accountInfo) {
|
|
1115
|
+
return null;
|
|
1116
|
+
}
|
|
1117
|
+
const data = accountInfo.data;
|
|
1118
|
+
return {
|
|
1119
|
+
authority: new PublicKey6(data.slice(8, 40)),
|
|
1120
|
+
vcoinMint: new PublicKey6(data.slice(40, 72)),
|
|
1121
|
+
treasury: new PublicKey6(data.slice(72, 104)),
|
|
1122
|
+
enabledActions: data[200],
|
|
1123
|
+
totalActionsCreated: new BN5(data.slice(201, 209), "le"),
|
|
1124
|
+
totalActionsExecuted: new BN5(data.slice(209, 217), "le"),
|
|
1125
|
+
totalTipVolume: new BN5(data.slice(217, 225), "le"),
|
|
1126
|
+
paused: data[225] !== 0,
|
|
1127
|
+
platformFeeBps: data.readUInt16LE(226)
|
|
1128
|
+
};
|
|
1129
|
+
} catch {
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Get action by ID
|
|
1135
|
+
*/
|
|
1136
|
+
async getAction(creator, timestamp) {
|
|
1137
|
+
try {
|
|
1138
|
+
const actionPda = this.client.pdas.getViLinkAction(creator, timestamp);
|
|
1139
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(actionPda);
|
|
1140
|
+
if (!accountInfo) {
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
1143
|
+
const data = accountInfo.data;
|
|
1144
|
+
return {
|
|
1145
|
+
actionId: new Uint8Array(data.slice(8, 40)),
|
|
1146
|
+
creator: new PublicKey6(data.slice(40, 72)),
|
|
1147
|
+
target: new PublicKey6(data.slice(72, 104)),
|
|
1148
|
+
actionType: data[104],
|
|
1149
|
+
amount: new BN5(data.slice(105, 113), "le"),
|
|
1150
|
+
expiresAt: new BN5(data.slice(145, 153), "le"),
|
|
1151
|
+
executed: data[153] !== 0,
|
|
1152
|
+
executionCount: data.readUInt32LE(193),
|
|
1153
|
+
maxExecutions: data.readUInt32LE(197)
|
|
1154
|
+
};
|
|
1155
|
+
} catch {
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Get user action statistics
|
|
1161
|
+
*/
|
|
1162
|
+
async getUserStats(user) {
|
|
1163
|
+
const target = user || this.client.publicKey;
|
|
1164
|
+
if (!target) {
|
|
1165
|
+
throw new Error("No user specified and wallet not connected");
|
|
1166
|
+
}
|
|
1167
|
+
try {
|
|
1168
|
+
const statsPda = this.client.pdas.getUserActionStats(target);
|
|
1169
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(statsPda);
|
|
1170
|
+
if (!accountInfo) {
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
const data = accountInfo.data;
|
|
1174
|
+
return {
|
|
1175
|
+
user: new PublicKey6(data.slice(8, 40)),
|
|
1176
|
+
actionsCreated: new BN5(data.slice(40, 48), "le"),
|
|
1177
|
+
actionsExecuted: new BN5(data.slice(48, 56), "le"),
|
|
1178
|
+
tipsSent: new BN5(data.slice(56, 64), "le"),
|
|
1179
|
+
tipsReceived: new BN5(data.slice(64, 72), "le"),
|
|
1180
|
+
vcoinSent: new BN5(data.slice(72, 80), "le"),
|
|
1181
|
+
vcoinReceived: new BN5(data.slice(80, 88), "le")
|
|
1182
|
+
};
|
|
1183
|
+
} catch {
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Get action type name
|
|
1189
|
+
*/
|
|
1190
|
+
getActionTypeName(actionType) {
|
|
1191
|
+
const names = [
|
|
1192
|
+
"Tip",
|
|
1193
|
+
"Vouch",
|
|
1194
|
+
"Follow",
|
|
1195
|
+
"Challenge",
|
|
1196
|
+
"Stake",
|
|
1197
|
+
"ContentReact",
|
|
1198
|
+
"Delegate",
|
|
1199
|
+
"Vote"
|
|
1200
|
+
];
|
|
1201
|
+
return names[actionType] || "Unknown";
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Check if action type is enabled
|
|
1205
|
+
*/
|
|
1206
|
+
async isActionTypeEnabled(actionType) {
|
|
1207
|
+
const config = await this.getConfig();
|
|
1208
|
+
if (!config) return false;
|
|
1209
|
+
return (config.enabledActions & 1 << actionType) !== 0;
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Check if action is valid for execution
|
|
1213
|
+
*/
|
|
1214
|
+
async isActionValid(creator, timestamp) {
|
|
1215
|
+
const action = await this.getAction(creator, timestamp);
|
|
1216
|
+
if (!action) {
|
|
1217
|
+
return { valid: false, reason: "Action not found" };
|
|
1218
|
+
}
|
|
1219
|
+
const now = getCurrentTimestamp();
|
|
1220
|
+
if (now > action.expiresAt.toNumber()) {
|
|
1221
|
+
return { valid: false, reason: "Action has expired" };
|
|
1222
|
+
}
|
|
1223
|
+
if (action.executed && action.maxExecutions === 1) {
|
|
1224
|
+
return { valid: false, reason: "Action already executed" };
|
|
1225
|
+
}
|
|
1226
|
+
if (action.maxExecutions > 0 && action.executionCount >= action.maxExecutions) {
|
|
1227
|
+
return { valid: false, reason: "Max executions reached" };
|
|
1228
|
+
}
|
|
1229
|
+
return { valid: true };
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Calculate platform fee for tip
|
|
1233
|
+
*/
|
|
1234
|
+
calculateFee(amount) {
|
|
1235
|
+
const fee = amount.muln(VILINK_CONSTANTS.platformFeeBps).divn(1e4);
|
|
1236
|
+
return {
|
|
1237
|
+
fee,
|
|
1238
|
+
net: amount.sub(fee)
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
// ============ URI Utilities ============
|
|
1242
|
+
/**
|
|
1243
|
+
* Generate ViLink URI from action ID
|
|
1244
|
+
*/
|
|
1245
|
+
generateUri(actionId, baseUrl = "viwo://action") {
|
|
1246
|
+
const idHex = Buffer.from(actionId).toString("hex");
|
|
1247
|
+
return `${baseUrl}/${idHex}`;
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Parse action ID from URI
|
|
1251
|
+
*/
|
|
1252
|
+
parseUri(uri) {
|
|
1253
|
+
const match = uri.match(/viwo:\/\/action\/([a-f0-9]{64})/i);
|
|
1254
|
+
if (!match) return null;
|
|
1255
|
+
return new Uint8Array(Buffer.from(match[1], "hex"));
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Generate QR code data for action
|
|
1259
|
+
*/
|
|
1260
|
+
generateQRData(actionId) {
|
|
1261
|
+
return this.generateUri(actionId, "https://viwoapp.com/action");
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Generate shareable link with metadata
|
|
1265
|
+
*/
|
|
1266
|
+
generateShareableLink(actionId, metadata) {
|
|
1267
|
+
const baseUri = this.generateUri(actionId, "https://viwoapp.com/action");
|
|
1268
|
+
if (!metadata) return baseUri;
|
|
1269
|
+
const params = new URLSearchParams();
|
|
1270
|
+
if (metadata.title) params.set("t", metadata.title);
|
|
1271
|
+
if (metadata.amount) params.set("a", metadata.amount);
|
|
1272
|
+
return `${baseUri}?${params.toString()}`;
|
|
1273
|
+
}
|
|
1274
|
+
// ============ Transaction Building ============
|
|
1275
|
+
/**
|
|
1276
|
+
* Build create tip action transaction
|
|
1277
|
+
*/
|
|
1278
|
+
async buildCreateTipAction(params) {
|
|
1279
|
+
if (!this.client.publicKey) {
|
|
1280
|
+
throw new Error("Wallet not connected");
|
|
1281
|
+
}
|
|
1282
|
+
const minAmount = parseVCoin(VILINK_CONSTANTS.minTipAmount.toString());
|
|
1283
|
+
const maxAmount = parseVCoin(VILINK_CONSTANTS.maxTipAmount.toString());
|
|
1284
|
+
if (params.amount.lt(minAmount)) {
|
|
1285
|
+
throw new Error(`Tip amount below minimum: ${VILINK_CONSTANTS.minTipAmount} VCoin`);
|
|
1286
|
+
}
|
|
1287
|
+
if (params.amount.gt(maxAmount)) {
|
|
1288
|
+
throw new Error(`Tip amount exceeds maximum: ${VILINK_CONSTANTS.maxTipAmount} VCoin`);
|
|
1289
|
+
}
|
|
1290
|
+
const tx = new Transaction5();
|
|
1291
|
+
return tx;
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Build create vouch action transaction
|
|
1295
|
+
*/
|
|
1296
|
+
async buildCreateVouchAction(params) {
|
|
1297
|
+
if (!this.client.publicKey) {
|
|
1298
|
+
throw new Error("Wallet not connected");
|
|
1299
|
+
}
|
|
1300
|
+
const tx = new Transaction5();
|
|
1301
|
+
return tx;
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Build create follow action transaction
|
|
1305
|
+
*/
|
|
1306
|
+
async buildCreateFollowAction(params) {
|
|
1307
|
+
if (!this.client.publicKey) {
|
|
1308
|
+
throw new Error("Wallet not connected");
|
|
1309
|
+
}
|
|
1310
|
+
const tx = new Transaction5();
|
|
1311
|
+
return tx;
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Build execute tip action transaction
|
|
1315
|
+
*/
|
|
1316
|
+
async buildExecuteTipAction(creator, timestamp) {
|
|
1317
|
+
if (!this.client.publicKey) {
|
|
1318
|
+
throw new Error("Wallet not connected");
|
|
1319
|
+
}
|
|
1320
|
+
const { valid, reason } = await this.isActionValid(creator, timestamp);
|
|
1321
|
+
if (!valid) {
|
|
1322
|
+
throw new Error(reason);
|
|
1323
|
+
}
|
|
1324
|
+
const action = await this.getAction(creator, timestamp);
|
|
1325
|
+
if (action?.creator.equals(this.client.publicKey)) {
|
|
1326
|
+
throw new Error("Cannot execute own action");
|
|
1327
|
+
}
|
|
1328
|
+
const tx = new Transaction5();
|
|
1329
|
+
return tx;
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
|
|
1333
|
+
// src/gasless/index.ts
|
|
1334
|
+
import { PublicKey as PublicKey7, Transaction as Transaction6 } from "@solana/web3.js";
|
|
1335
|
+
import { BN as BN6 } from "@coral-xyz/anchor";
|
|
1336
|
+
var GaslessClient = class {
|
|
1337
|
+
constructor(client) {
|
|
1338
|
+
this.client = client;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Get gasless configuration
|
|
1342
|
+
*/
|
|
1343
|
+
async getConfig() {
|
|
1344
|
+
try {
|
|
1345
|
+
const configPda = this.client.pdas.getGaslessConfig();
|
|
1346
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(configPda);
|
|
1347
|
+
if (!accountInfo) {
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1350
|
+
const data = accountInfo.data;
|
|
1351
|
+
return {
|
|
1352
|
+
authority: new PublicKey7(data.slice(8, 40)),
|
|
1353
|
+
feePayer: new PublicKey7(data.slice(40, 72)),
|
|
1354
|
+
vcoinMint: new PublicKey7(data.slice(72, 104)),
|
|
1355
|
+
dailySubsidyBudget: new BN6(data.slice(136, 144), "le"),
|
|
1356
|
+
solFeePerTx: new BN6(data.slice(144, 152), "le"),
|
|
1357
|
+
vcoinFeeMultiplier: new BN6(data.slice(152, 160), "le"),
|
|
1358
|
+
totalSubsidizedTx: new BN6(data.slice(168, 176), "le"),
|
|
1359
|
+
totalVcoinCollected: new BN6(data.slice(184, 192), "le"),
|
|
1360
|
+
paused: data[192] !== 0
|
|
1361
|
+
};
|
|
1362
|
+
} catch {
|
|
1363
|
+
return null;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Get session key details
|
|
1368
|
+
*/
|
|
1369
|
+
async getSessionKey(user, sessionPubkey) {
|
|
1370
|
+
try {
|
|
1371
|
+
const sessionPda = this.client.pdas.getSessionKey(user, sessionPubkey);
|
|
1372
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(sessionPda);
|
|
1373
|
+
if (!accountInfo) {
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1376
|
+
const data = accountInfo.data;
|
|
1377
|
+
return {
|
|
1378
|
+
user: new PublicKey7(data.slice(8, 40)),
|
|
1379
|
+
sessionPubkey: new PublicKey7(data.slice(40, 72)),
|
|
1380
|
+
scope: data.readUInt16LE(72),
|
|
1381
|
+
createdAt: new BN6(data.slice(74, 82), "le"),
|
|
1382
|
+
expiresAt: new BN6(data.slice(82, 90), "le"),
|
|
1383
|
+
actionsUsed: data.readUInt32LE(90),
|
|
1384
|
+
maxActions: data.readUInt32LE(94),
|
|
1385
|
+
vcoinSpent: new BN6(data.slice(98, 106), "le"),
|
|
1386
|
+
maxSpend: new BN6(data.slice(106, 114), "le"),
|
|
1387
|
+
isRevoked: data[114] !== 0,
|
|
1388
|
+
feeMethod: data[123]
|
|
1389
|
+
};
|
|
1390
|
+
} catch {
|
|
1391
|
+
return null;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Get user gasless statistics
|
|
1396
|
+
*/
|
|
1397
|
+
async getUserStats(user) {
|
|
1398
|
+
const target = user || this.client.publicKey;
|
|
1399
|
+
if (!target) {
|
|
1400
|
+
throw new Error("No user specified and wallet not connected");
|
|
1401
|
+
}
|
|
1402
|
+
try {
|
|
1403
|
+
const statsPda = this.client.pdas.getUserGaslessStats(target);
|
|
1404
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(statsPda);
|
|
1405
|
+
if (!accountInfo) {
|
|
1406
|
+
return null;
|
|
1407
|
+
}
|
|
1408
|
+
const data = accountInfo.data;
|
|
1409
|
+
return {
|
|
1410
|
+
user: new PublicKey7(data.slice(8, 40)),
|
|
1411
|
+
totalGaslessTx: new BN6(data.slice(40, 48), "le"),
|
|
1412
|
+
totalSubsidized: new BN6(data.slice(48, 56), "le"),
|
|
1413
|
+
totalVcoinFees: new BN6(data.slice(56, 64), "le"),
|
|
1414
|
+
sessionsCreated: data.readUInt32LE(72),
|
|
1415
|
+
activeSession: new PublicKey7(data.slice(76, 108))
|
|
1416
|
+
};
|
|
1417
|
+
} catch {
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Check if session is valid
|
|
1423
|
+
*/
|
|
1424
|
+
async isSessionValid(user, sessionPubkey) {
|
|
1425
|
+
const session = await this.getSessionKey(user, sessionPubkey);
|
|
1426
|
+
if (!session) {
|
|
1427
|
+
return { valid: false, reason: "Session not found" };
|
|
1428
|
+
}
|
|
1429
|
+
if (session.isRevoked) {
|
|
1430
|
+
return { valid: false, reason: "Session has been revoked" };
|
|
1431
|
+
}
|
|
1432
|
+
const now = getCurrentTimestamp();
|
|
1433
|
+
if (now > session.expiresAt.toNumber()) {
|
|
1434
|
+
return { valid: false, reason: "Session has expired" };
|
|
1435
|
+
}
|
|
1436
|
+
if (session.actionsUsed >= session.maxActions) {
|
|
1437
|
+
return { valid: false, reason: "Session action limit reached" };
|
|
1438
|
+
}
|
|
1439
|
+
return { valid: true };
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Check if action is in session scope
|
|
1443
|
+
*/
|
|
1444
|
+
isActionInScope(session, actionScope) {
|
|
1445
|
+
return (session.scope & actionScope) !== 0;
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Get remaining session actions
|
|
1449
|
+
*/
|
|
1450
|
+
getRemainingActions(session) {
|
|
1451
|
+
return session.maxActions - session.actionsUsed;
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Get remaining session spend
|
|
1455
|
+
*/
|
|
1456
|
+
getRemainingSpend(session) {
|
|
1457
|
+
return session.maxSpend.sub(session.vcoinSpent);
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Get remaining session time
|
|
1461
|
+
*/
|
|
1462
|
+
getRemainingTime(session) {
|
|
1463
|
+
const now = getCurrentTimestamp();
|
|
1464
|
+
return Math.max(0, session.expiresAt.toNumber() - now);
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Calculate VCoin fee equivalent
|
|
1468
|
+
*/
|
|
1469
|
+
async calculateVCoinFee() {
|
|
1470
|
+
const config = await this.getConfig();
|
|
1471
|
+
if (!config) {
|
|
1472
|
+
return new BN6(GASLESS_CONSTANTS.defaultSolFee * GASLESS_CONSTANTS.vcoinFeeMultiplier);
|
|
1473
|
+
}
|
|
1474
|
+
return config.solFeePerTx.mul(config.vcoinFeeMultiplier);
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Check if user is eligible for subsidized transactions
|
|
1478
|
+
*/
|
|
1479
|
+
async isEligibleForSubsidy(user) {
|
|
1480
|
+
const target = user || this.client.publicKey;
|
|
1481
|
+
if (!target) {
|
|
1482
|
+
throw new Error("No user specified and wallet not connected");
|
|
1483
|
+
}
|
|
1484
|
+
const [config, userStats] = await Promise.all([
|
|
1485
|
+
this.getConfig(),
|
|
1486
|
+
this.getUserStats(target)
|
|
1487
|
+
]);
|
|
1488
|
+
if (!config) {
|
|
1489
|
+
return { eligible: false, remainingToday: 0, reason: "Config not found" };
|
|
1490
|
+
}
|
|
1491
|
+
const maxPerUser = GASLESS_CONSTANTS.maxSubsidizedPerUser;
|
|
1492
|
+
const usedToday = 0;
|
|
1493
|
+
const remaining = maxPerUser - usedToday;
|
|
1494
|
+
if (remaining <= 0) {
|
|
1495
|
+
return {
|
|
1496
|
+
eligible: false,
|
|
1497
|
+
remainingToday: 0,
|
|
1498
|
+
reason: "Daily limit reached"
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
return { eligible: true, remainingToday: remaining };
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Get scope names from scope bitmap
|
|
1505
|
+
*/
|
|
1506
|
+
getScopeNames(scope) {
|
|
1507
|
+
const names = [];
|
|
1508
|
+
const scopeMap = [
|
|
1509
|
+
{ bit: ACTION_SCOPES.tip, name: "Tip" },
|
|
1510
|
+
{ bit: ACTION_SCOPES.vouch, name: "Vouch" },
|
|
1511
|
+
{ bit: ACTION_SCOPES.content, name: "Content" },
|
|
1512
|
+
{ bit: ACTION_SCOPES.governance, name: "Governance" },
|
|
1513
|
+
{ bit: ACTION_SCOPES.transfer, name: "Transfer" },
|
|
1514
|
+
{ bit: ACTION_SCOPES.stake, name: "Stake" },
|
|
1515
|
+
{ bit: ACTION_SCOPES.claim, name: "Claim" },
|
|
1516
|
+
{ bit: ACTION_SCOPES.follow, name: "Follow" }
|
|
1517
|
+
];
|
|
1518
|
+
for (const { bit, name } of scopeMap) {
|
|
1519
|
+
if (scope & bit) {
|
|
1520
|
+
names.push(name);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return names;
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Create scope from action names
|
|
1527
|
+
*/
|
|
1528
|
+
createScope(actions) {
|
|
1529
|
+
let scope = 0;
|
|
1530
|
+
const scopeMap = {
|
|
1531
|
+
tip: ACTION_SCOPES.tip,
|
|
1532
|
+
vouch: ACTION_SCOPES.vouch,
|
|
1533
|
+
content: ACTION_SCOPES.content,
|
|
1534
|
+
governance: ACTION_SCOPES.governance,
|
|
1535
|
+
transfer: ACTION_SCOPES.transfer,
|
|
1536
|
+
stake: ACTION_SCOPES.stake,
|
|
1537
|
+
claim: ACTION_SCOPES.claim,
|
|
1538
|
+
follow: ACTION_SCOPES.follow
|
|
1539
|
+
};
|
|
1540
|
+
for (const action of actions) {
|
|
1541
|
+
const bit = scopeMap[action.toLowerCase()];
|
|
1542
|
+
if (bit) {
|
|
1543
|
+
scope |= bit;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
return scope;
|
|
1547
|
+
}
|
|
1548
|
+
// ============ Transaction Building ============
|
|
1549
|
+
/**
|
|
1550
|
+
* Build create session key transaction
|
|
1551
|
+
*/
|
|
1552
|
+
async buildCreateSessionTransaction(params) {
|
|
1553
|
+
if (!this.client.publicKey) {
|
|
1554
|
+
throw new Error("Wallet not connected");
|
|
1555
|
+
}
|
|
1556
|
+
if (!params.sessionPubkey) {
|
|
1557
|
+
throw new Error("Session public key required");
|
|
1558
|
+
}
|
|
1559
|
+
if (!params.scope || params.scope === 0) {
|
|
1560
|
+
throw new Error("At least one scope required");
|
|
1561
|
+
}
|
|
1562
|
+
const duration = params.durationSeconds || GASLESS_CONSTANTS.sessionDuration;
|
|
1563
|
+
const maxActions = params.maxActions || GASLESS_CONSTANTS.maxSessionActions;
|
|
1564
|
+
const maxSpend = params.maxSpend || new BN6(GASLESS_CONSTANTS.maxSessionSpend * 1e9);
|
|
1565
|
+
const feeMethod = params.feeMethod ?? 1;
|
|
1566
|
+
const tx = new Transaction6();
|
|
1567
|
+
return tx;
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Build revoke session key transaction
|
|
1571
|
+
*/
|
|
1572
|
+
async buildRevokeSessionTransaction(sessionPubkey) {
|
|
1573
|
+
if (!this.client.publicKey) {
|
|
1574
|
+
throw new Error("Wallet not connected");
|
|
1575
|
+
}
|
|
1576
|
+
const session = await this.getSessionKey(this.client.publicKey, sessionPubkey);
|
|
1577
|
+
if (!session) {
|
|
1578
|
+
throw new Error("Session not found");
|
|
1579
|
+
}
|
|
1580
|
+
if (session.isRevoked) {
|
|
1581
|
+
throw new Error("Session already revoked");
|
|
1582
|
+
}
|
|
1583
|
+
const tx = new Transaction6();
|
|
1584
|
+
return tx;
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Build VCoin fee deduction transaction
|
|
1588
|
+
*/
|
|
1589
|
+
async buildDeductFeeTransaction(amount) {
|
|
1590
|
+
if (!this.client.publicKey) {
|
|
1591
|
+
throw new Error("Wallet not connected");
|
|
1592
|
+
}
|
|
1593
|
+
const tx = new Transaction6();
|
|
1594
|
+
return tx;
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
|
|
1598
|
+
// src/identity/index.ts
|
|
1599
|
+
import { PublicKey as PublicKey8, Transaction as Transaction7 } from "@solana/web3.js";
|
|
1600
|
+
import { BN as BN7 } from "@coral-xyz/anchor";
|
|
1601
|
+
var IdentityClient = class {
|
|
1602
|
+
constructor(client) {
|
|
1603
|
+
this.client = client;
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Get user identity
|
|
1607
|
+
*/
|
|
1608
|
+
async getIdentity(user) {
|
|
1609
|
+
const target = user || this.client.publicKey;
|
|
1610
|
+
if (!target) {
|
|
1611
|
+
throw new Error("No user specified and wallet not connected");
|
|
1612
|
+
}
|
|
1613
|
+
try {
|
|
1614
|
+
const identityPda = this.client.pdas.getUserIdentity(target);
|
|
1615
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(identityPda);
|
|
1616
|
+
if (!accountInfo) {
|
|
1617
|
+
return null;
|
|
1618
|
+
}
|
|
1619
|
+
const data = accountInfo.data;
|
|
1620
|
+
return {
|
|
1621
|
+
user: new PublicKey8(data.slice(8, 40)),
|
|
1622
|
+
didHash: new Uint8Array(data.slice(40, 72)),
|
|
1623
|
+
verificationLevel: data[72],
|
|
1624
|
+
createdAt: new BN7(data.slice(73, 81), "le"),
|
|
1625
|
+
updatedAt: new BN7(data.slice(81, 89), "le")
|
|
1626
|
+
};
|
|
1627
|
+
} catch {
|
|
1628
|
+
return null;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Check if user has identity
|
|
1633
|
+
*/
|
|
1634
|
+
async hasIdentity(user) {
|
|
1635
|
+
const identity = await this.getIdentity(user);
|
|
1636
|
+
return identity !== null;
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Get verification level name
|
|
1640
|
+
*/
|
|
1641
|
+
getVerificationLevelName(level) {
|
|
1642
|
+
const levels = ["None", "Basic", "Standard", "Enhanced", "Premium"];
|
|
1643
|
+
return levels[level] || "Unknown";
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Get verification level requirements
|
|
1647
|
+
*/
|
|
1648
|
+
getVerificationRequirements(level) {
|
|
1649
|
+
const requirements = {
|
|
1650
|
+
0: [],
|
|
1651
|
+
1: ["Email verification", "Phone verification"],
|
|
1652
|
+
2: ["Basic requirements", "Social account linking"],
|
|
1653
|
+
3: ["Standard requirements", "ID verification"],
|
|
1654
|
+
4: ["Enhanced requirements", "Face verification", "Address verification"]
|
|
1655
|
+
};
|
|
1656
|
+
return requirements[level] || [];
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Get verification level benefits
|
|
1660
|
+
*/
|
|
1661
|
+
getVerificationBenefits(level) {
|
|
1662
|
+
const benefits = {
|
|
1663
|
+
0: ["Basic platform access"],
|
|
1664
|
+
1: ["Higher withdrawal limits", "Basic rewards eligibility"],
|
|
1665
|
+
2: ["Full rewards eligibility", "Vouch capabilities"],
|
|
1666
|
+
3: ["Priority support", "Enhanced trust score"],
|
|
1667
|
+
4: ["VIP status", "Governance proposal creation", "Maximum limits"]
|
|
1668
|
+
};
|
|
1669
|
+
return benefits[level] || [];
|
|
1670
|
+
}
|
|
1671
|
+
// ============ Transaction Building ============
|
|
1672
|
+
/**
|
|
1673
|
+
* Build create identity transaction
|
|
1674
|
+
*/
|
|
1675
|
+
async buildCreateIdentityTransaction(didHash) {
|
|
1676
|
+
if (!this.client.publicKey) {
|
|
1677
|
+
throw new Error("Wallet not connected");
|
|
1678
|
+
}
|
|
1679
|
+
const existing = await this.getIdentity();
|
|
1680
|
+
if (existing) {
|
|
1681
|
+
throw new Error("Identity already exists");
|
|
1682
|
+
}
|
|
1683
|
+
if (didHash.length !== 32) {
|
|
1684
|
+
throw new Error("DID hash must be 32 bytes");
|
|
1685
|
+
}
|
|
1686
|
+
const tx = new Transaction7();
|
|
1687
|
+
return tx;
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Build update DID hash transaction
|
|
1691
|
+
*/
|
|
1692
|
+
async buildUpdateDidHashTransaction(newDidHash) {
|
|
1693
|
+
if (!this.client.publicKey) {
|
|
1694
|
+
throw new Error("Wallet not connected");
|
|
1695
|
+
}
|
|
1696
|
+
if (newDidHash.length !== 32) {
|
|
1697
|
+
throw new Error("DID hash must be 32 bytes");
|
|
1698
|
+
}
|
|
1699
|
+
const tx = new Transaction7();
|
|
1700
|
+
return tx;
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
|
|
1704
|
+
// src/fivea/index.ts
|
|
1705
|
+
import { PublicKey as PublicKey9, Transaction as Transaction8 } from "@solana/web3.js";
|
|
1706
|
+
import { BN as BN8 } from "@coral-xyz/anchor";
|
|
1707
|
+
var FiveAClient = class {
|
|
1708
|
+
constructor(client) {
|
|
1709
|
+
this.client = client;
|
|
1710
|
+
}
|
|
1711
|
+
/**
|
|
1712
|
+
* Get user's 5A score
|
|
1713
|
+
*/
|
|
1714
|
+
async getScore(user) {
|
|
1715
|
+
const target = user || this.client.publicKey;
|
|
1716
|
+
if (!target) {
|
|
1717
|
+
throw new Error("No user specified and wallet not connected");
|
|
1718
|
+
}
|
|
1719
|
+
try {
|
|
1720
|
+
const scorePda = this.client.pdas.getUserScore(target);
|
|
1721
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(scorePda);
|
|
1722
|
+
if (!accountInfo) {
|
|
1723
|
+
return null;
|
|
1724
|
+
}
|
|
1725
|
+
const data = accountInfo.data;
|
|
1726
|
+
return {
|
|
1727
|
+
user: new PublicKey9(data.slice(8, 40)),
|
|
1728
|
+
authenticity: data.readUInt16LE(40),
|
|
1729
|
+
accuracy: data.readUInt16LE(42),
|
|
1730
|
+
agility: data.readUInt16LE(44),
|
|
1731
|
+
activity: data.readUInt16LE(46),
|
|
1732
|
+
approved: data.readUInt16LE(48),
|
|
1733
|
+
compositeScore: data.readUInt16LE(50),
|
|
1734
|
+
lastUpdated: new BN8(data.slice(52, 60), "le"),
|
|
1735
|
+
isPrivate: data[60] !== 0
|
|
1736
|
+
};
|
|
1737
|
+
} catch {
|
|
1738
|
+
return null;
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* Format score as percentage
|
|
1743
|
+
*/
|
|
1744
|
+
formatScore(score) {
|
|
1745
|
+
return `${(score / 100).toFixed(2)}%`;
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* Get score tier
|
|
1749
|
+
*/
|
|
1750
|
+
getScoreTier(composite) {
|
|
1751
|
+
if (composite >= 8e3) return "Excellent";
|
|
1752
|
+
if (composite >= 6e3) return "Good";
|
|
1753
|
+
if (composite >= 4e3) return "Average";
|
|
1754
|
+
if (composite >= 2e3) return "Below Average";
|
|
1755
|
+
return "Low";
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Get reward multiplier for score
|
|
1759
|
+
*/
|
|
1760
|
+
getRewardMultiplier(composite) {
|
|
1761
|
+
if (composite >= 8e3) return FIVE_A_CONSTANTS.scoreMultipliers["80-100"];
|
|
1762
|
+
if (composite >= 6e3) return FIVE_A_CONSTANTS.scoreMultipliers["60-80"];
|
|
1763
|
+
if (composite >= 4e3) return FIVE_A_CONSTANTS.scoreMultipliers["40-60"];
|
|
1764
|
+
if (composite >= 2e3) return FIVE_A_CONSTANTS.scoreMultipliers["20-40"];
|
|
1765
|
+
return FIVE_A_CONSTANTS.scoreMultipliers["0-20"];
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Get score breakdown
|
|
1769
|
+
*/
|
|
1770
|
+
getScoreBreakdown(score) {
|
|
1771
|
+
const weights = FIVE_A_CONSTANTS.scoreWeights;
|
|
1772
|
+
return [
|
|
1773
|
+
{
|
|
1774
|
+
component: "A1 - Authenticity",
|
|
1775
|
+
description: "Are you a real person?",
|
|
1776
|
+
score: this.formatScore(score.authenticity),
|
|
1777
|
+
weight: weights.authenticity,
|
|
1778
|
+
contribution: this.formatScore(score.authenticity * weights.authenticity / 100)
|
|
1779
|
+
},
|
|
1780
|
+
{
|
|
1781
|
+
component: "A2 - Accuracy",
|
|
1782
|
+
description: "Is your content quality?",
|
|
1783
|
+
score: this.formatScore(score.accuracy),
|
|
1784
|
+
weight: weights.accuracy,
|
|
1785
|
+
contribution: this.formatScore(score.accuracy * weights.accuracy / 100)
|
|
1786
|
+
},
|
|
1787
|
+
{
|
|
1788
|
+
component: "A3 - Agility",
|
|
1789
|
+
description: "Are you fast?",
|
|
1790
|
+
score: this.formatScore(score.agility),
|
|
1791
|
+
weight: weights.agility,
|
|
1792
|
+
contribution: this.formatScore(score.agility * weights.agility / 100)
|
|
1793
|
+
},
|
|
1794
|
+
{
|
|
1795
|
+
component: "A4 - Activity",
|
|
1796
|
+
description: "Do you show up daily?",
|
|
1797
|
+
score: this.formatScore(score.activity),
|
|
1798
|
+
weight: weights.activity,
|
|
1799
|
+
contribution: this.formatScore(score.activity * weights.activity / 100)
|
|
1800
|
+
},
|
|
1801
|
+
{
|
|
1802
|
+
component: "A5 - Approved",
|
|
1803
|
+
description: "Does the community like you?",
|
|
1804
|
+
score: this.formatScore(score.approved),
|
|
1805
|
+
weight: weights.approved,
|
|
1806
|
+
contribution: this.formatScore(score.approved * weights.approved / 100)
|
|
1807
|
+
}
|
|
1808
|
+
];
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Calculate max vouches for score
|
|
1812
|
+
*/
|
|
1813
|
+
getMaxVouches(composite) {
|
|
1814
|
+
if (composite >= 9e3) return 20;
|
|
1815
|
+
if (composite >= 8e3) return 15;
|
|
1816
|
+
if (composite >= 7e3) return 10;
|
|
1817
|
+
if (composite >= 6e3) return 7;
|
|
1818
|
+
if (composite >= 5e3) return 5;
|
|
1819
|
+
if (composite >= 4e3) return 3;
|
|
1820
|
+
return 2;
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Check if user can vouch for another
|
|
1824
|
+
*/
|
|
1825
|
+
async canVouchFor(target) {
|
|
1826
|
+
if (!this.client.publicKey) {
|
|
1827
|
+
return { canVouch: false, reason: "Wallet not connected" };
|
|
1828
|
+
}
|
|
1829
|
+
if (this.client.publicKey.equals(target)) {
|
|
1830
|
+
return { canVouch: false, reason: "Cannot vouch for yourself" };
|
|
1831
|
+
}
|
|
1832
|
+
const myScore = await this.getScore();
|
|
1833
|
+
if (!myScore) {
|
|
1834
|
+
return { canVouch: false, reason: "No 5A score found" };
|
|
1835
|
+
}
|
|
1836
|
+
if (myScore.compositeScore < 6e3) {
|
|
1837
|
+
return { canVouch: false, reason: "Score too low to vouch (min 60%)" };
|
|
1838
|
+
}
|
|
1839
|
+
return { canVouch: true };
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Get score improvement suggestions
|
|
1843
|
+
*/
|
|
1844
|
+
getImprovementSuggestions(score) {
|
|
1845
|
+
const suggestions = [];
|
|
1846
|
+
if (score.authenticity < 6e3) {
|
|
1847
|
+
suggestions.push("Complete identity verification to improve Authenticity (A1)");
|
|
1848
|
+
}
|
|
1849
|
+
if (score.accuracy < 6e3) {
|
|
1850
|
+
suggestions.push("Create quality content to improve Accuracy (A2)");
|
|
1851
|
+
}
|
|
1852
|
+
if (score.agility < 6e3) {
|
|
1853
|
+
suggestions.push("Respond faster to improve Agility (A3)");
|
|
1854
|
+
}
|
|
1855
|
+
if (score.activity < 6e3) {
|
|
1856
|
+
suggestions.push("Engage daily with content to improve Activity (A4)");
|
|
1857
|
+
}
|
|
1858
|
+
if (score.approved < 6e3) {
|
|
1859
|
+
suggestions.push("Get vouched by high-score users to improve Approved (A5)");
|
|
1860
|
+
}
|
|
1861
|
+
return suggestions;
|
|
1862
|
+
}
|
|
1863
|
+
// ============ Transaction Building ============
|
|
1864
|
+
/**
|
|
1865
|
+
* Build vouch transaction
|
|
1866
|
+
*/
|
|
1867
|
+
async buildVouchTransaction(target) {
|
|
1868
|
+
if (!this.client.publicKey) {
|
|
1869
|
+
throw new Error("Wallet not connected");
|
|
1870
|
+
}
|
|
1871
|
+
const { canVouch, reason } = await this.canVouchFor(target);
|
|
1872
|
+
if (!canVouch) {
|
|
1873
|
+
throw new Error(reason);
|
|
1874
|
+
}
|
|
1875
|
+
const tx = new Transaction8();
|
|
1876
|
+
return tx;
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
|
|
1880
|
+
// src/content/index.ts
|
|
1881
|
+
import { PublicKey as PublicKey10, Transaction as Transaction9 } from "@solana/web3.js";
|
|
1882
|
+
import { BN as BN9 } from "@coral-xyz/anchor";
|
|
1883
|
+
var ContentClient = class {
|
|
1884
|
+
constructor(client) {
|
|
1885
|
+
this.client = client;
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Get content record
|
|
1889
|
+
*/
|
|
1890
|
+
async getContent(contentId) {
|
|
1891
|
+
try {
|
|
1892
|
+
const contentPda = this.client.pdas.getContentRecord(contentId);
|
|
1893
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(contentPda);
|
|
1894
|
+
if (!accountInfo) {
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1897
|
+
const data = accountInfo.data;
|
|
1898
|
+
return {
|
|
1899
|
+
contentId: new Uint8Array(data.slice(8, 40)),
|
|
1900
|
+
creator: new PublicKey10(data.slice(40, 72)),
|
|
1901
|
+
contentHash: new Uint8Array(data.slice(72, 104)),
|
|
1902
|
+
state: data[104],
|
|
1903
|
+
createdAt: new BN9(data.slice(105, 113), "le"),
|
|
1904
|
+
editCount: data.readUInt16LE(113),
|
|
1905
|
+
tips: new BN9(data.slice(115, 123), "le"),
|
|
1906
|
+
engagementScore: new BN9(data.slice(123, 131), "le")
|
|
1907
|
+
};
|
|
1908
|
+
} catch {
|
|
1909
|
+
return null;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Get user's energy
|
|
1914
|
+
*/
|
|
1915
|
+
async getEnergy(user) {
|
|
1916
|
+
const target = user || this.client.publicKey;
|
|
1917
|
+
if (!target) {
|
|
1918
|
+
throw new Error("No user specified and wallet not connected");
|
|
1919
|
+
}
|
|
1920
|
+
try {
|
|
1921
|
+
const energyPda = this.client.pdas.getUserEnergy(target);
|
|
1922
|
+
const accountInfo = await this.client.connection.connection.getAccountInfo(energyPda);
|
|
1923
|
+
if (!accountInfo) {
|
|
1924
|
+
return null;
|
|
1925
|
+
}
|
|
1926
|
+
const data = accountInfo.data;
|
|
1927
|
+
return {
|
|
1928
|
+
user: new PublicKey10(data.slice(8, 40)),
|
|
1929
|
+
currentEnergy: data.readUInt16LE(40),
|
|
1930
|
+
maxEnergy: data.readUInt16LE(42),
|
|
1931
|
+
lastRegenTime: new BN9(data.slice(44, 52), "le"),
|
|
1932
|
+
tier: data[52]
|
|
1933
|
+
};
|
|
1934
|
+
} catch {
|
|
1935
|
+
return null;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Get content state name
|
|
1940
|
+
*/
|
|
1941
|
+
getStateName(state) {
|
|
1942
|
+
const states = ["Active", "Hidden", "Deleted", "Flagged"];
|
|
1943
|
+
return states[state] || "Unknown";
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Calculate regenerated energy
|
|
1947
|
+
*/
|
|
1948
|
+
calculateRegenEnergy(energy) {
|
|
1949
|
+
const now = getCurrentTimestamp();
|
|
1950
|
+
const secondsSinceRegen = now - energy.lastRegenTime.toNumber();
|
|
1951
|
+
const hoursSinceRegen = secondsSinceRegen / 3600;
|
|
1952
|
+
const regenAmount = Math.floor(hoursSinceRegen * CONTENT_CONSTANTS.energyRegenRate);
|
|
1953
|
+
const newEnergy = Math.min(
|
|
1954
|
+
energy.maxEnergy,
|
|
1955
|
+
energy.currentEnergy + regenAmount
|
|
1956
|
+
);
|
|
1957
|
+
return newEnergy;
|
|
1958
|
+
}
|
|
1959
|
+
/**
|
|
1960
|
+
* Get time until next energy
|
|
1961
|
+
*/
|
|
1962
|
+
getTimeUntilNextEnergy(energy) {
|
|
1963
|
+
if (energy.currentEnergy >= energy.maxEnergy) {
|
|
1964
|
+
return 0;
|
|
1965
|
+
}
|
|
1966
|
+
const now = getCurrentTimestamp();
|
|
1967
|
+
const secondsSinceRegen = now - energy.lastRegenTime.toNumber();
|
|
1968
|
+
const secondsPerEnergy = 3600 / CONTENT_CONSTANTS.energyRegenRate;
|
|
1969
|
+
const nextRegenIn = secondsPerEnergy - secondsSinceRegen % secondsPerEnergy;
|
|
1970
|
+
return Math.max(0, Math.ceil(nextRegenIn));
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Get time until full energy
|
|
1974
|
+
*/
|
|
1975
|
+
getTimeUntilFull(energy) {
|
|
1976
|
+
const currentEnergy = this.calculateRegenEnergy(energy);
|
|
1977
|
+
if (currentEnergy >= energy.maxEnergy) {
|
|
1978
|
+
return 0;
|
|
1979
|
+
}
|
|
1980
|
+
const energyNeeded = energy.maxEnergy - currentEnergy;
|
|
1981
|
+
const secondsPerEnergy = 3600 / CONTENT_CONSTANTS.energyRegenRate;
|
|
1982
|
+
return Math.ceil(energyNeeded * secondsPerEnergy);
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Check if user can create content
|
|
1986
|
+
*/
|
|
1987
|
+
async canCreateContent(user) {
|
|
1988
|
+
const energy = await this.getEnergy(user);
|
|
1989
|
+
if (!energy) {
|
|
1990
|
+
return { canCreate: false, reason: "Energy account not found" };
|
|
1991
|
+
}
|
|
1992
|
+
const currentEnergy = this.calculateRegenEnergy(energy);
|
|
1993
|
+
const energyNeeded = CONTENT_CONSTANTS.createCost;
|
|
1994
|
+
if (currentEnergy < energyNeeded) {
|
|
1995
|
+
return {
|
|
1996
|
+
canCreate: false,
|
|
1997
|
+
reason: `Insufficient energy (${currentEnergy}/${energyNeeded})`,
|
|
1998
|
+
energyNeeded,
|
|
1999
|
+
energyAvailable: currentEnergy
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
return { canCreate: true, energyNeeded, energyAvailable: currentEnergy };
|
|
2003
|
+
}
|
|
2004
|
+
/**
|
|
2005
|
+
* Check if user can edit content
|
|
2006
|
+
*/
|
|
2007
|
+
async canEditContent(contentId, user) {
|
|
2008
|
+
const target = user || this.client.publicKey;
|
|
2009
|
+
if (!target) {
|
|
2010
|
+
return { canEdit: false, reason: "Wallet not connected" };
|
|
2011
|
+
}
|
|
2012
|
+
const content = await this.getContent(contentId);
|
|
2013
|
+
if (!content) {
|
|
2014
|
+
return { canEdit: false, reason: "Content not found" };
|
|
2015
|
+
}
|
|
2016
|
+
if (!content.creator.equals(target)) {
|
|
2017
|
+
return { canEdit: false, reason: "Not content creator" };
|
|
2018
|
+
}
|
|
2019
|
+
if (content.state === 2) {
|
|
2020
|
+
return { canEdit: false, reason: "Content is deleted" };
|
|
2021
|
+
}
|
|
2022
|
+
const energy = await this.getEnergy(target);
|
|
2023
|
+
if (!energy) {
|
|
2024
|
+
return { canEdit: false, reason: "Energy account not found" };
|
|
2025
|
+
}
|
|
2026
|
+
const currentEnergy = this.calculateRegenEnergy(energy);
|
|
2027
|
+
if (currentEnergy < CONTENT_CONSTANTS.editCost) {
|
|
2028
|
+
return { canEdit: false, reason: "Insufficient energy" };
|
|
2029
|
+
}
|
|
2030
|
+
return { canEdit: true };
|
|
2031
|
+
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Get content stats
|
|
2034
|
+
*/
|
|
2035
|
+
async getContentStats(contentId) {
|
|
2036
|
+
const content = await this.getContent(contentId);
|
|
2037
|
+
if (!content) {
|
|
2038
|
+
throw new Error("Content not found");
|
|
2039
|
+
}
|
|
2040
|
+
const now = getCurrentTimestamp();
|
|
2041
|
+
const age = now - content.createdAt.toNumber();
|
|
2042
|
+
return {
|
|
2043
|
+
tips: formatVCoin(content.tips),
|
|
2044
|
+
engagementScore: content.engagementScore.toString(),
|
|
2045
|
+
editCount: content.editCount,
|
|
2046
|
+
state: this.getStateName(content.state),
|
|
2047
|
+
age
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
// ============ Transaction Building ============
|
|
2051
|
+
/**
|
|
2052
|
+
* Build create content transaction
|
|
2053
|
+
*/
|
|
2054
|
+
async buildCreateContentTransaction(contentHash) {
|
|
2055
|
+
if (!this.client.publicKey) {
|
|
2056
|
+
throw new Error("Wallet not connected");
|
|
2057
|
+
}
|
|
2058
|
+
const { canCreate, reason } = await this.canCreateContent();
|
|
2059
|
+
if (!canCreate) {
|
|
2060
|
+
throw new Error(reason);
|
|
2061
|
+
}
|
|
2062
|
+
if (contentHash.length !== 32) {
|
|
2063
|
+
throw new Error("Content hash must be 32 bytes");
|
|
2064
|
+
}
|
|
2065
|
+
const tx = new Transaction9();
|
|
2066
|
+
return tx;
|
|
2067
|
+
}
|
|
2068
|
+
/**
|
|
2069
|
+
* Build edit content transaction
|
|
2070
|
+
*/
|
|
2071
|
+
async buildEditContentTransaction(contentId, newContentHash) {
|
|
2072
|
+
if (!this.client.publicKey) {
|
|
2073
|
+
throw new Error("Wallet not connected");
|
|
2074
|
+
}
|
|
2075
|
+
const { canEdit, reason } = await this.canEditContent(contentId);
|
|
2076
|
+
if (!canEdit) {
|
|
2077
|
+
throw new Error(reason);
|
|
2078
|
+
}
|
|
2079
|
+
const tx = new Transaction9();
|
|
2080
|
+
return tx;
|
|
2081
|
+
}
|
|
2082
|
+
/**
|
|
2083
|
+
* Build delete content transaction
|
|
2084
|
+
*/
|
|
2085
|
+
async buildDeleteContentTransaction(contentId) {
|
|
2086
|
+
if (!this.client.publicKey) {
|
|
2087
|
+
throw new Error("Wallet not connected");
|
|
2088
|
+
}
|
|
2089
|
+
const content = await this.getContent(contentId);
|
|
2090
|
+
if (!content) {
|
|
2091
|
+
throw new Error("Content not found");
|
|
2092
|
+
}
|
|
2093
|
+
if (!content.creator.equals(this.client.publicKey)) {
|
|
2094
|
+
throw new Error("Not content creator");
|
|
2095
|
+
}
|
|
2096
|
+
const tx = new Transaction9();
|
|
2097
|
+
return tx;
|
|
2098
|
+
}
|
|
2099
|
+
};
|
|
2100
|
+
|
|
2101
|
+
// src/client.ts
|
|
2102
|
+
import { Connection as Connection2 } from "@solana/web3.js";
|
|
2103
|
+
import { AnchorProvider as AnchorProvider2, BN as BN10 } from "@coral-xyz/anchor";
|
|
2104
|
+
import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
|
|
2105
|
+
var ViWoClient = class {
|
|
2106
|
+
constructor(config) {
|
|
2107
|
+
if (config.connection instanceof Connection2) {
|
|
2108
|
+
this.connection = new ViWoConnection({
|
|
2109
|
+
endpoint: config.connection.rpcEndpoint,
|
|
2110
|
+
commitment: "confirmed"
|
|
2111
|
+
});
|
|
2112
|
+
} else {
|
|
2113
|
+
this.connection = new ViWoConnection(config.connection);
|
|
2114
|
+
}
|
|
2115
|
+
this.wallet = config.wallet || null;
|
|
2116
|
+
this.programIds = {
|
|
2117
|
+
...PROGRAM_IDS,
|
|
2118
|
+
...config.programIds
|
|
2119
|
+
};
|
|
2120
|
+
this.pdas = new PDAs(this.programIds);
|
|
2121
|
+
this.staking = new StakingClient(this);
|
|
2122
|
+
this.governance = new GovernanceClient(this);
|
|
2123
|
+
this.rewards = new RewardsClient(this);
|
|
2124
|
+
this.vilink = new ViLinkClient(this);
|
|
2125
|
+
this.gasless = new GaslessClient(this);
|
|
2126
|
+
this.identity = new IdentityClient(this);
|
|
2127
|
+
this.fivea = new FiveAClient(this);
|
|
2128
|
+
this.content = new ContentClient(this);
|
|
2129
|
+
}
|
|
2130
|
+
/**
|
|
2131
|
+
* Get the wallet public key
|
|
2132
|
+
*/
|
|
2133
|
+
get publicKey() {
|
|
2134
|
+
return this.wallet?.publicKey || null;
|
|
2135
|
+
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Check if wallet is connected
|
|
2138
|
+
*/
|
|
2139
|
+
get isConnected() {
|
|
2140
|
+
return this.wallet !== null && this.wallet.publicKey !== null;
|
|
2141
|
+
}
|
|
2142
|
+
/**
|
|
2143
|
+
* Set wallet adapter
|
|
2144
|
+
*/
|
|
2145
|
+
setWallet(wallet) {
|
|
2146
|
+
this.wallet = wallet;
|
|
2147
|
+
this.staking = new StakingClient(this);
|
|
2148
|
+
this.governance = new GovernanceClient(this);
|
|
2149
|
+
this.rewards = new RewardsClient(this);
|
|
2150
|
+
this.vilink = new ViLinkClient(this);
|
|
2151
|
+
this.gasless = new GaslessClient(this);
|
|
2152
|
+
this.identity = new IdentityClient(this);
|
|
2153
|
+
this.fivea = new FiveAClient(this);
|
|
2154
|
+
this.content = new ContentClient(this);
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Get Anchor provider
|
|
2158
|
+
*/
|
|
2159
|
+
getProvider() {
|
|
2160
|
+
if (!this.wallet || !this.wallet.publicKey) {
|
|
2161
|
+
return null;
|
|
2162
|
+
}
|
|
2163
|
+
return new AnchorProvider2(
|
|
2164
|
+
this.connection.connection,
|
|
2165
|
+
this.wallet,
|
|
2166
|
+
{ commitment: this.connection.commitment }
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
/**
|
|
2170
|
+
* Send and confirm transaction
|
|
2171
|
+
*/
|
|
2172
|
+
async sendTransaction(tx) {
|
|
2173
|
+
if (!this.wallet) {
|
|
2174
|
+
throw new Error("Wallet not connected");
|
|
2175
|
+
}
|
|
2176
|
+
const { blockhash, lastValidBlockHeight } = await this.connection.connection.getLatestBlockhash();
|
|
2177
|
+
tx.recentBlockhash = blockhash;
|
|
2178
|
+
tx.feePayer = this.wallet.publicKey;
|
|
2179
|
+
const signedTx = await this.wallet.signTransaction(tx);
|
|
2180
|
+
const signature = await this.connection.connection.sendRawTransaction(
|
|
2181
|
+
signedTx.serialize()
|
|
2182
|
+
);
|
|
2183
|
+
await this.connection.connection.confirmTransaction({
|
|
2184
|
+
signature,
|
|
2185
|
+
blockhash,
|
|
2186
|
+
lastValidBlockHeight
|
|
2187
|
+
});
|
|
2188
|
+
return signature;
|
|
2189
|
+
}
|
|
2190
|
+
/**
|
|
2191
|
+
* Get VCoin balance
|
|
2192
|
+
*/
|
|
2193
|
+
async getVCoinBalance(user) {
|
|
2194
|
+
const target = user || this.publicKey;
|
|
2195
|
+
if (!target) {
|
|
2196
|
+
throw new Error("No user specified and wallet not connected");
|
|
2197
|
+
}
|
|
2198
|
+
try {
|
|
2199
|
+
const tokenAccounts = await this.connection.connection.getTokenAccountsByOwner(
|
|
2200
|
+
target,
|
|
2201
|
+
{ programId: TOKEN_2022_PROGRAM_ID }
|
|
2202
|
+
);
|
|
2203
|
+
let balance = new BN10(0);
|
|
2204
|
+
for (const { account } of tokenAccounts.value) {
|
|
2205
|
+
const data = account.data;
|
|
2206
|
+
const amount = data.slice(64, 72);
|
|
2207
|
+
balance = balance.add(new BN10(amount, "le"));
|
|
2208
|
+
}
|
|
2209
|
+
return balance;
|
|
2210
|
+
} catch {
|
|
2211
|
+
return new BN10(0);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
/**
|
|
2215
|
+
* Get veVCoin balance
|
|
2216
|
+
*/
|
|
2217
|
+
async getVeVCoinBalance(user) {
|
|
2218
|
+
const target = user || this.publicKey;
|
|
2219
|
+
if (!target) {
|
|
2220
|
+
throw new Error("No user specified and wallet not connected");
|
|
2221
|
+
}
|
|
2222
|
+
try {
|
|
2223
|
+
const stakeData = await this.staking.getUserStake(target);
|
|
2224
|
+
return stakeData?.vevcoinBalance || new BN10(0);
|
|
2225
|
+
} catch {
|
|
2226
|
+
return new BN10(0);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
/**
|
|
2230
|
+
* Check connection health
|
|
2231
|
+
*/
|
|
2232
|
+
async healthCheck() {
|
|
2233
|
+
try {
|
|
2234
|
+
const [connected, slot] = await Promise.all([
|
|
2235
|
+
this.connection.isHealthy(),
|
|
2236
|
+
this.connection.getSlot()
|
|
2237
|
+
]);
|
|
2238
|
+
const blockTime = await this.connection.getBlockTime();
|
|
2239
|
+
return { connected, slot, blockTime };
|
|
2240
|
+
} catch {
|
|
2241
|
+
return { connected: false, slot: null, blockTime: null };
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
};
|
|
2245
|
+
export {
|
|
2246
|
+
ACTION_SCOPES,
|
|
2247
|
+
ActionType,
|
|
2248
|
+
BN,
|
|
2249
|
+
CONTENT_CONSTANTS,
|
|
2250
|
+
ContentClient,
|
|
2251
|
+
ContentState,
|
|
2252
|
+
FIVE_A_CONSTANTS,
|
|
2253
|
+
FeeMethod,
|
|
2254
|
+
FiveAClient,
|
|
2255
|
+
GASLESS_CONSTANTS,
|
|
2256
|
+
GOVERNANCE_CONSTANTS,
|
|
2257
|
+
GaslessClient,
|
|
2258
|
+
GovernanceClient,
|
|
2259
|
+
IdentityClient,
|
|
2260
|
+
LOCK_DURATIONS,
|
|
2261
|
+
PDAs,
|
|
2262
|
+
PROGRAM_IDS,
|
|
2263
|
+
ProposalStatus,
|
|
2264
|
+
RewardsClient,
|
|
2265
|
+
SEEDS,
|
|
2266
|
+
SSCRE_CONSTANTS,
|
|
2267
|
+
STAKING_TIERS,
|
|
2268
|
+
StakingClient,
|
|
2269
|
+
StakingTier,
|
|
2270
|
+
TransactionBuilder,
|
|
2271
|
+
VCOIN_DECIMALS,
|
|
2272
|
+
VCOIN_INITIAL_CIRCULATING,
|
|
2273
|
+
VCOIN_TOTAL_SUPPLY,
|
|
2274
|
+
VEVCOIN_DECIMALS,
|
|
2275
|
+
VILINK_CONSTANTS,
|
|
2276
|
+
VerificationLevel,
|
|
2277
|
+
ViLinkClient,
|
|
2278
|
+
ViWoClient,
|
|
2279
|
+
ViWoConnection,
|
|
2280
|
+
dateToTimestamp,
|
|
2281
|
+
formatVCoin,
|
|
2282
|
+
getCurrentTimestamp,
|
|
2283
|
+
parseVCoin,
|
|
2284
|
+
timestampToDate
|
|
2285
|
+
};
|