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