@ghostspeak/sdk 2.0.6 → 2.0.7
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 +152 -30
- package/dist/GhostSpeakClient-CWmGaM9Q.d.ts +1007 -0
- package/dist/StakingModule-C5rzuOWb.d.ts +2526 -0
- package/dist/{agent-M74TCRON.js → agent-5YLZ7DAC.js} +4 -4
- package/dist/{agent-M74TCRON.js.map → agent-5YLZ7DAC.js.map} +1 -1
- package/dist/batch-operations-45CQFEID.js +4 -0
- package/dist/batch-operations-45CQFEID.js.map +1 -0
- package/dist/browser.d.ts +45 -554
- package/dist/browser.js +15 -842
- package/dist/browser.js.map +1 -1
- package/dist/chunk-AL3HQN73.js +754 -0
- package/dist/chunk-AL3HQN73.js.map +1 -0
- package/dist/chunk-BF3IQ35I.js +284 -0
- package/dist/chunk-BF3IQ35I.js.map +1 -0
- package/dist/chunk-BQDGRTVP.js +168 -0
- package/dist/chunk-BQDGRTVP.js.map +1 -0
- package/dist/chunk-C5CDA3WX.js +7314 -0
- package/dist/chunk-C5CDA3WX.js.map +1 -0
- package/dist/chunk-E3FD2CNY.js +1869 -0
- package/dist/chunk-E3FD2CNY.js.map +1 -0
- package/dist/{chunk-F3DZMBUA.js → chunk-G7S6B6WB.js} +327 -493
- package/dist/chunk-G7S6B6WB.js.map +1 -0
- package/dist/chunk-IHVDQ4YI.js +4231 -0
- package/dist/chunk-IHVDQ4YI.js.map +1 -0
- package/dist/chunk-JV2SWONF.js +98 -0
- package/dist/chunk-JV2SWONF.js.map +1 -0
- package/dist/chunk-KB6CKIUK.js +231 -0
- package/dist/chunk-KB6CKIUK.js.map +1 -0
- package/dist/chunk-S74EH3KD.js +7890 -0
- package/dist/chunk-S74EH3KD.js.map +1 -0
- package/dist/chunk-SFTSZ3LC.js +156 -0
- package/dist/chunk-SFTSZ3LC.js.map +1 -0
- package/dist/chunk-SKMJJ3Q6.js +125 -0
- package/dist/chunk-SKMJJ3Q6.js.map +1 -0
- package/dist/chunk-SZGFSCNU.js +3682 -0
- package/dist/chunk-SZGFSCNU.js.map +1 -0
- package/dist/chunk-TTB4OS2D.js +69 -0
- package/dist/chunk-TTB4OS2D.js.map +1 -0
- package/dist/chunk-UP2VWCW5.js +33 -0
- package/dist/{chunk-NSBPE2FW.js.map → chunk-UP2VWCW5.js.map} +1 -1
- package/dist/{chunk-UJUGGLMT.js → chunk-VQZQCHUT.js} +5 -5
- package/dist/{chunk-UJUGGLMT.js.map → chunk-VQZQCHUT.js.map} +1 -1
- package/dist/client.d.ts +5 -4
- package/dist/client.js +11 -10
- package/dist/createAgentAuthorization-ULG47ZJI.js +5 -0
- package/dist/createAgentAuthorization-ULG47ZJI.js.map +1 -0
- package/dist/credentials.js +1 -1
- package/dist/crypto.js +2 -2
- package/dist/errors.js +1 -1
- package/dist/feature-flags-B1g0DCPe.d.ts +1181 -0
- package/dist/generated-EG5USUFG.js +9 -0
- package/dist/{generated-VNLHMR6Y.js.map → generated-EG5USUFG.js.map} +1 -1
- package/dist/{ghostspeak_wasm-SB2RPJ3D.js → ghostspeak_wasm-F227HOSM.js} +3 -3
- package/dist/{ghostspeak_wasm-SB2RPJ3D.js.map → ghostspeak_wasm-F227HOSM.js.map} +1 -1
- package/dist/index.d.ts +1209 -1506
- package/dist/index.js +600 -3532
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/minimal/core-minimal.d.ts +2383 -1264
- package/dist/minimal/core-minimal.js +9 -9
- package/dist/minimal/core-minimal.js.map +1 -1
- package/dist/nacl-fast-W5BJ3KZ2.js +2229 -0
- package/dist/nacl-fast-W5BJ3KZ2.js.map +1 -0
- package/dist/pda-4KP7CURF.js +4 -0
- package/dist/pda-4KP7CURF.js.map +1 -0
- package/dist/pda-Ce7VYg4T.d.ts +25 -0
- package/dist/reputation-types-Yebf0Rm_.d.ts +1071 -0
- package/dist/revokeAuthorization-OK7E7OK3.js +5 -0
- package/dist/revokeAuthorization-OK7E7OK3.js.map +1 -0
- package/dist/signature-verification-DGxR4aYQ.d.ts +448 -0
- package/dist/types.js +1 -1
- package/dist/updateReputationWithAuth-Y4ONEVSP.js +5 -0
- package/dist/updateReputationWithAuth-Y4ONEVSP.js.map +1 -0
- package/dist/utils.d.ts +69 -203
- package/dist/utils.js +15 -153
- package/dist/utils.js.map +1 -1
- package/package.json +24 -31
- package/dist/.tsbuildinfo +0 -1
- package/dist/GhostSpeakClient-D_66Uzsf.d.ts +0 -707
- package/dist/GovernanceModule-DQYYys-H.d.ts +0 -1766
- package/dist/chunk-APCKGD23.js +0 -1328
- package/dist/chunk-APCKGD23.js.map +0 -1
- package/dist/chunk-ASQXX4IT.js +0 -572
- package/dist/chunk-ASQXX4IT.js.map +0 -1
- package/dist/chunk-COGZFWOT.js +0 -19657
- package/dist/chunk-COGZFWOT.js.map +0 -1
- package/dist/chunk-F3DZMBUA.js.map +0 -1
- package/dist/chunk-GMHIUK2R.js +0 -7526
- package/dist/chunk-GMHIUK2R.js.map +0 -1
- package/dist/chunk-IAWBZYPE.js +0 -356
- package/dist/chunk-IAWBZYPE.js.map +0 -1
- package/dist/chunk-NSBPE2FW.js +0 -15
- package/dist/chunk-OWYHJG6H.js +0 -13311
- package/dist/chunk-OWYHJG6H.js.map +0 -1
- package/dist/chunk-RDDPOFR5.js +0 -3
- package/dist/chunk-RDDPOFR5.js.map +0 -1
- package/dist/chunk-RERCHKZP.js +0 -35
- package/dist/chunk-RERCHKZP.js.map +0 -1
- package/dist/chunk-TVVGXYCI.js +0 -2887
- package/dist/chunk-TVVGXYCI.js.map +0 -1
- package/dist/chunk-ZGP5552B.js +0 -377
- package/dist/chunk-ZGP5552B.js.map +0 -1
- package/dist/chunk-ZWOYNHVK.js +0 -196
- package/dist/chunk-ZWOYNHVK.js.map +0 -1
- package/dist/dist/.tsbuildinfo +0 -1
- package/dist/elgamal-VZLWB3XK.js +0 -5
- package/dist/elgamal-VZLWB3XK.js.map +0 -1
- package/dist/feature-flags-V722ZuXO.d.ts +0 -3512
- package/dist/generated-VNLHMR6Y.js +0 -5
- package/dist/ipfs-types-BOt9ZNg4.d.ts +0 -592
- package/dist/multisigConfig-BzEhy6jy.d.ts +0 -58
- package/dist/pda-B_nS8SbD.d.ts +0 -114
- package/dist/pda-S4BFJVGE.js +0 -4
- package/dist/pda-S4BFJVGE.js.map +0 -1
- package/dist/system-addresses-BFNLEbFx.d.ts +0 -857
- package/dist/token-2022-rpc-RALH4RK7.js +0 -593
- package/dist/token-2022-rpc-RALH4RK7.js.map +0 -1
|
@@ -0,0 +1,3682 @@
|
|
|
1
|
+
import { init_reputation_tag_engine, createErrorContext, logEnhancedError, IPFSClient, SYSTEM_PROGRAM_ADDRESS, ReputationCalculator, ReputationTagEngine, REPUTATION_CONSTANTS } from './chunk-C5CDA3WX.js';
|
|
2
|
+
import { getInitializeGovernanceProposalInstructionAsync, getInitializeStakingConfigInstructionAsync, getStakeGhostInstructionAsync, getUnstakeGhostInstructionAsync, getRegisterAgentInstructionAsync, getRegisterAgentCompressedInstructionAsync, getUpdateAgentInstruction, getVerifyAgentInstructionAsync, getDeactivateAgentInstruction, getActivateAgentInstruction } from './chunk-S74EH3KD.js';
|
|
3
|
+
import { __export, __esm } from './chunk-UP2VWCW5.js';
|
|
4
|
+
import { createSolanaRpc, createSolanaRpcSubscriptions, lamports, pipe, createTransactionMessage, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstructions, signTransactionMessageWithSigners, setTransactionMessageFeePayer, compileTransactionMessage, getBase64EncodedWireTransaction, getProgramDerivedAddress, getAddressEncoder } from '@solana/kit';
|
|
5
|
+
import { LRUCache } from 'lru-cache';
|
|
6
|
+
import bs58 from 'bs58';
|
|
7
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
8
|
+
import { EventEmitter } from 'events';
|
|
9
|
+
|
|
10
|
+
// src/modules/reputation/MultiSourceAggregator.ts
|
|
11
|
+
var MultiSourceAggregator_exports = {};
|
|
12
|
+
__export(MultiSourceAggregator_exports, {
|
|
13
|
+
MultiSourceAggregator: () => MultiSourceAggregator
|
|
14
|
+
});
|
|
15
|
+
var MultiSourceAggregator;
|
|
16
|
+
var init_MultiSourceAggregator = __esm({
|
|
17
|
+
"src/modules/reputation/MultiSourceAggregator.ts"() {
|
|
18
|
+
MultiSourceAggregator = class {
|
|
19
|
+
adapters = /* @__PURE__ */ new Map();
|
|
20
|
+
configs = /* @__PURE__ */ new Map();
|
|
21
|
+
/** Conflict threshold (30% variance) */
|
|
22
|
+
CONFLICT_THRESHOLD = 300;
|
|
23
|
+
// 30% of 1000 scale
|
|
24
|
+
/**
|
|
25
|
+
* Create a new multi-source aggregator
|
|
26
|
+
*/
|
|
27
|
+
constructor() {
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Add a reputation source
|
|
31
|
+
*
|
|
32
|
+
* @param adapter - Reputation source adapter
|
|
33
|
+
* @param config - Source configuration
|
|
34
|
+
*/
|
|
35
|
+
addSource(adapter, config) {
|
|
36
|
+
if (!config.enabled) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.adapters.set(adapter.source, adapter);
|
|
40
|
+
this.configs.set(adapter.source, config);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Remove a reputation source
|
|
44
|
+
*
|
|
45
|
+
* @param source - Source to remove
|
|
46
|
+
*/
|
|
47
|
+
removeSource(source) {
|
|
48
|
+
this.adapters.delete(source);
|
|
49
|
+
this.configs.delete(source);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Update source weight
|
|
53
|
+
*
|
|
54
|
+
* @param source - Source to update
|
|
55
|
+
* @param weight - New weight in basis points (0-10000)
|
|
56
|
+
*/
|
|
57
|
+
updateSourceWeight(source, weight) {
|
|
58
|
+
const config = this.configs.get(source);
|
|
59
|
+
if (config) {
|
|
60
|
+
config.weight = weight;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Update source reliability
|
|
65
|
+
*
|
|
66
|
+
* @param source - Source to update
|
|
67
|
+
* @param reliability - New reliability in basis points (0-10000)
|
|
68
|
+
*/
|
|
69
|
+
updateSourceReliability(source, reliability) {
|
|
70
|
+
const config = this.configs.get(source);
|
|
71
|
+
if (config) {
|
|
72
|
+
config.reliability = reliability;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Aggregate reputation from all sources
|
|
77
|
+
*
|
|
78
|
+
* @param agentId - Agent identifier
|
|
79
|
+
* @returns Aggregated reputation data
|
|
80
|
+
*/
|
|
81
|
+
async aggregateReputation(agentId) {
|
|
82
|
+
const sourceDataList = [];
|
|
83
|
+
for (const [source, adapter] of this.adapters.entries()) {
|
|
84
|
+
try {
|
|
85
|
+
const data = await adapter.fetchReputationData(agentId.toString());
|
|
86
|
+
if (adapter.validateData(data)) {
|
|
87
|
+
sourceDataList.push(data);
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.warn(`Failed to fetch reputation from ${source}:`, error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const aggregateScore = this.calculateWeightedScore(sourceDataList);
|
|
94
|
+
const { hasConflicts, conflicts } = this.detectConflicts(sourceDataList);
|
|
95
|
+
const sourceScores = this.buildSourceBreakdowns(sourceDataList);
|
|
96
|
+
const totalDataPoints = sourceDataList.reduce((sum, data) => sum + data.dataPoints, 0);
|
|
97
|
+
return {
|
|
98
|
+
agentId,
|
|
99
|
+
aggregateScore,
|
|
100
|
+
sourceScores,
|
|
101
|
+
hasConflicts,
|
|
102
|
+
conflicts,
|
|
103
|
+
totalDataPoints,
|
|
104
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Calculate weighted aggregate score
|
|
109
|
+
*
|
|
110
|
+
* Formula: Σ(score × weight × reliability) / Σ(weight × reliability)
|
|
111
|
+
*
|
|
112
|
+
* @param sourceDataList - Data from all sources
|
|
113
|
+
* @returns Weighted aggregate score (0-1000)
|
|
114
|
+
*/
|
|
115
|
+
calculateWeightedScore(sourceDataList) {
|
|
116
|
+
if (sourceDataList.length === 0) {
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
let totalContribution = 0;
|
|
120
|
+
let totalNormalization = 0;
|
|
121
|
+
for (const data of sourceDataList) {
|
|
122
|
+
const config = this.configs.get(data.source);
|
|
123
|
+
if (!config) continue;
|
|
124
|
+
const weight = config.weight / 1e4;
|
|
125
|
+
const reliability = data.reliability;
|
|
126
|
+
const contribution = data.score * weight * reliability;
|
|
127
|
+
const normalization = weight * reliability;
|
|
128
|
+
totalContribution += contribution;
|
|
129
|
+
totalNormalization += normalization;
|
|
130
|
+
}
|
|
131
|
+
if (totalNormalization === 0) {
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
return Math.round(totalContribution / totalNormalization);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Detect conflicts between sources
|
|
138
|
+
*
|
|
139
|
+
* Flags conflicts if max_score - min_score > 30%
|
|
140
|
+
*
|
|
141
|
+
* @param sourceDataList - Data from all sources
|
|
142
|
+
* @returns Conflict detection result
|
|
143
|
+
*/
|
|
144
|
+
detectConflicts(sourceDataList) {
|
|
145
|
+
if (sourceDataList.length < 2) {
|
|
146
|
+
return { hasConflicts: false, conflicts: [] };
|
|
147
|
+
}
|
|
148
|
+
const scores = sourceDataList.map((d) => d.score);
|
|
149
|
+
const maxScore = Math.max(...scores);
|
|
150
|
+
const minScore = Math.min(...scores);
|
|
151
|
+
const variance = maxScore - minScore;
|
|
152
|
+
const conflicts = [];
|
|
153
|
+
if (variance > this.CONFLICT_THRESHOLD) {
|
|
154
|
+
const maxSource = sourceDataList.find((d) => d.score === maxScore)?.source;
|
|
155
|
+
const minSource = sourceDataList.find((d) => d.score === minScore)?.source;
|
|
156
|
+
conflicts.push(
|
|
157
|
+
`High variance detected: ${variance} (${(variance / 1e3 * 100).toFixed(1)}%) between ${maxSource} (${maxScore}) and ${minSource} (${minScore})`
|
|
158
|
+
);
|
|
159
|
+
return { hasConflicts: true, conflicts };
|
|
160
|
+
}
|
|
161
|
+
return { hasConflicts: false, conflicts: [] };
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Build source score breakdowns
|
|
165
|
+
*
|
|
166
|
+
* @param sourceDataList - Data from all sources
|
|
167
|
+
* @returns Source score breakdowns
|
|
168
|
+
*/
|
|
169
|
+
buildSourceBreakdowns(sourceDataList) {
|
|
170
|
+
return sourceDataList.map((data) => {
|
|
171
|
+
const config = this.configs.get(data.source);
|
|
172
|
+
if (!config) {
|
|
173
|
+
throw new Error(`No config found for source: ${data.source}`);
|
|
174
|
+
}
|
|
175
|
+
const weight = config.weight;
|
|
176
|
+
const reliability = data.reliability * 1e4;
|
|
177
|
+
const contribution = data.score * (weight / 1e4) * (reliability / 1e4) / this.calculateNormalizationFactor(sourceDataList);
|
|
178
|
+
return {
|
|
179
|
+
source: data.source,
|
|
180
|
+
score: data.score,
|
|
181
|
+
weight,
|
|
182
|
+
reliability: Math.round(reliability),
|
|
183
|
+
dataPoints: data.dataPoints,
|
|
184
|
+
contribution: Math.round(contribution),
|
|
185
|
+
lastUpdated: data.timestamp
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Calculate normalization factor for weighted scoring
|
|
191
|
+
*/
|
|
192
|
+
calculateNormalizationFactor(sourceDataList) {
|
|
193
|
+
let total = 0;
|
|
194
|
+
for (const data of sourceDataList) {
|
|
195
|
+
const config = this.configs.get(data.source);
|
|
196
|
+
if (!config) continue;
|
|
197
|
+
const weight = config.weight / 1e4;
|
|
198
|
+
const reliability = data.reliability;
|
|
199
|
+
total += weight * reliability;
|
|
200
|
+
}
|
|
201
|
+
return total || 1;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get detailed source breakdown for an agent
|
|
205
|
+
*
|
|
206
|
+
* @param agentId - Agent identifier
|
|
207
|
+
* @returns Source breakdowns with full details
|
|
208
|
+
*/
|
|
209
|
+
async getSourceBreakdown(agentId) {
|
|
210
|
+
const result = await this.aggregateReputation(agentId);
|
|
211
|
+
return result.sourceScores;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get list of registered sources
|
|
215
|
+
*/
|
|
216
|
+
getRegisteredSources() {
|
|
217
|
+
return Array.from(this.adapters.keys());
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get source configuration
|
|
221
|
+
*
|
|
222
|
+
* @param source - Source identifier
|
|
223
|
+
* @returns Source configuration or undefined
|
|
224
|
+
*/
|
|
225
|
+
getSourceConfig(source) {
|
|
226
|
+
return this.configs.get(source);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Enable a source
|
|
230
|
+
*
|
|
231
|
+
* @param source - Source to enable
|
|
232
|
+
*/
|
|
233
|
+
enableSource(source) {
|
|
234
|
+
const config = this.configs.get(source);
|
|
235
|
+
if (config) {
|
|
236
|
+
config.enabled = true;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Disable a source
|
|
241
|
+
*
|
|
242
|
+
* @param source - Source to disable
|
|
243
|
+
*/
|
|
244
|
+
disableSource(source) {
|
|
245
|
+
const config = this.configs.get(source);
|
|
246
|
+
if (config) {
|
|
247
|
+
config.enabled = false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Check if source is enabled
|
|
252
|
+
*
|
|
253
|
+
* @param source - Source to check
|
|
254
|
+
* @returns True if enabled
|
|
255
|
+
*/
|
|
256
|
+
isSourceEnabled(source) {
|
|
257
|
+
const config = this.configs.get(source);
|
|
258
|
+
return config?.enabled ?? false;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
var RpcClient = class {
|
|
264
|
+
rpc;
|
|
265
|
+
rpcSubscriptions;
|
|
266
|
+
commitment;
|
|
267
|
+
endpoint;
|
|
268
|
+
maxRetries;
|
|
269
|
+
retryDelay;
|
|
270
|
+
timeout;
|
|
271
|
+
constructor(config) {
|
|
272
|
+
this.endpoint = config.endpoint;
|
|
273
|
+
this.rpc = createSolanaRpc(config.endpoint);
|
|
274
|
+
this.commitment = config.commitment ?? "confirmed";
|
|
275
|
+
this.maxRetries = config.maxRetries ?? 3;
|
|
276
|
+
this.retryDelay = config.retryDelay ?? 1e3;
|
|
277
|
+
this.timeout = config.timeout ?? 6e4;
|
|
278
|
+
if (config.wsEndpoint) {
|
|
279
|
+
this.rpcSubscriptions = createSolanaRpcSubscriptions(config.wsEndpoint);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get account information with automatic retries
|
|
284
|
+
*/
|
|
285
|
+
async getAccountInfo(address, options) {
|
|
286
|
+
return this.withRetry(async () => {
|
|
287
|
+
const result = await this.rpc.getAccountInfo(address, {
|
|
288
|
+
commitment: options?.commitment ?? this.commitment,
|
|
289
|
+
encoding: "base64"
|
|
290
|
+
}).send();
|
|
291
|
+
if (!result.value) return null;
|
|
292
|
+
return this.parseAccountInfo(result.value);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Get multiple accounts efficiently
|
|
297
|
+
*/
|
|
298
|
+
async getMultipleAccounts(addresses, options) {
|
|
299
|
+
return this.withRetry(async () => {
|
|
300
|
+
const result = await this.rpc.getMultipleAccounts(addresses, {
|
|
301
|
+
commitment: options?.commitment ?? this.commitment,
|
|
302
|
+
encoding: "base64"
|
|
303
|
+
}).send();
|
|
304
|
+
return result.value.map(
|
|
305
|
+
(account) => account ? this.parseAccountInfo(account) : null
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Get program accounts with filters
|
|
311
|
+
*/
|
|
312
|
+
async getProgramAccounts(programId, options) {
|
|
313
|
+
return this.withRetry(async () => {
|
|
314
|
+
const result = await this.rpc.getProgramAccounts(programId, {
|
|
315
|
+
commitment: options?.commitment ?? this.commitment,
|
|
316
|
+
encoding: "base64",
|
|
317
|
+
filters: options?.filters
|
|
318
|
+
}).send();
|
|
319
|
+
return result.map((item) => {
|
|
320
|
+
const typedItem = item;
|
|
321
|
+
return {
|
|
322
|
+
pubkey: typedItem.pubkey,
|
|
323
|
+
account: this.parseAccountInfo(typedItem.account)
|
|
324
|
+
};
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get latest blockhash with automatic caching
|
|
330
|
+
*/
|
|
331
|
+
blockhashCache = null;
|
|
332
|
+
async getLatestBlockhash() {
|
|
333
|
+
const now = Date.now();
|
|
334
|
+
if (this.blockhashCache && now - this.blockhashCache.timestamp < 1e3) {
|
|
335
|
+
return this.blockhashCache.value;
|
|
336
|
+
}
|
|
337
|
+
const result = await this.withRetry(async () => {
|
|
338
|
+
const response = await this.rpc.getLatestBlockhash({
|
|
339
|
+
commitment: this.commitment
|
|
340
|
+
}).send();
|
|
341
|
+
return {
|
|
342
|
+
blockhash: response.value.blockhash,
|
|
343
|
+
lastValidBlockHeight: BigInt(response.value.lastValidBlockHeight)
|
|
344
|
+
};
|
|
345
|
+
});
|
|
346
|
+
this.blockhashCache = { value: result, timestamp: now };
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Send transaction with enhanced error handling
|
|
351
|
+
*/
|
|
352
|
+
async sendTransaction(transaction, options) {
|
|
353
|
+
return this.withRetry(async () => {
|
|
354
|
+
const result = await this.rpc.sendTransaction(transaction, {
|
|
355
|
+
encoding: "base64",
|
|
356
|
+
skipPreflight: options?.skipPreflight ?? false,
|
|
357
|
+
preflightCommitment: options?.preflightCommitment ?? this.commitment,
|
|
358
|
+
maxRetries: options?.maxRetries ? BigInt(options.maxRetries) : void 0
|
|
359
|
+
}).send();
|
|
360
|
+
return result;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Get signature statuses with batch support
|
|
365
|
+
*/
|
|
366
|
+
async getSignatureStatuses(signatures) {
|
|
367
|
+
return this.withRetry(async () => {
|
|
368
|
+
const result = await this.rpc.getSignatureStatuses(signatures).send();
|
|
369
|
+
return result.value.map((status) => {
|
|
370
|
+
if (!status) return null;
|
|
371
|
+
const typedStatus = status;
|
|
372
|
+
return {
|
|
373
|
+
slot: typedStatus.slot,
|
|
374
|
+
confirmations: typedStatus.confirmations,
|
|
375
|
+
err: typedStatus.err,
|
|
376
|
+
confirmationStatus: typedStatus.confirmationStatus
|
|
377
|
+
};
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Simulate transaction with detailed error info
|
|
383
|
+
*/
|
|
384
|
+
async simulateTransaction(transaction, options) {
|
|
385
|
+
return this.withRetry(async () => {
|
|
386
|
+
const result = await this.rpc.simulateTransaction(transaction, {
|
|
387
|
+
encoding: "base64",
|
|
388
|
+
commitment: options?.commitment ?? this.commitment,
|
|
389
|
+
replaceRecentBlockhash: options?.replaceRecentBlockhash ?? false
|
|
390
|
+
}).send();
|
|
391
|
+
return {
|
|
392
|
+
err: result.value.err,
|
|
393
|
+
logs: result.value.logs ?? [],
|
|
394
|
+
unitsConsumed: result.value.unitsConsumed ? BigInt(result.value.unitsConsumed) : void 0,
|
|
395
|
+
returnData: result.value.returnData
|
|
396
|
+
};
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Get fee for message
|
|
401
|
+
*/
|
|
402
|
+
async getFeeForMessage(encodedMessage) {
|
|
403
|
+
return this.withRetry(async () => {
|
|
404
|
+
const result = await this.rpc.getFeeForMessage(encodedMessage, {
|
|
405
|
+
commitment: this.commitment
|
|
406
|
+
}).send();
|
|
407
|
+
return result.value ? BigInt(result.value) : null;
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Health check
|
|
412
|
+
*/
|
|
413
|
+
async isHealthy() {
|
|
414
|
+
try {
|
|
415
|
+
await this.rpc.getHealth().send();
|
|
416
|
+
return true;
|
|
417
|
+
} catch {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Get RPC node version
|
|
423
|
+
*/
|
|
424
|
+
async getVersion() {
|
|
425
|
+
const result = await this.rpc.getVersion().send();
|
|
426
|
+
return result;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Subscribe to account changes (WebSocket)
|
|
430
|
+
* Note: This is a placeholder implementation. In production, you would use the actual subscription API
|
|
431
|
+
*/
|
|
432
|
+
async subscribeToAccount(address, callback) {
|
|
433
|
+
if (!this.rpcSubscriptions) {
|
|
434
|
+
throw new Error("WebSocket endpoint not configured");
|
|
435
|
+
}
|
|
436
|
+
console.warn("Account subscription is not fully implemented in this version");
|
|
437
|
+
const intervalId = setInterval(async () => {
|
|
438
|
+
try {
|
|
439
|
+
const accountInfo = await this.getAccountInfo(address);
|
|
440
|
+
callback(accountInfo);
|
|
441
|
+
} catch (error) {
|
|
442
|
+
console.error("Subscription polling error:", error);
|
|
443
|
+
}
|
|
444
|
+
}, 5e3);
|
|
445
|
+
return () => {
|
|
446
|
+
clearInterval(intervalId);
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
// Private helper methods
|
|
450
|
+
async withRetry(operation, retries = this.maxRetries) {
|
|
451
|
+
let lastError;
|
|
452
|
+
for (let i = 0; i <= retries; i++) {
|
|
453
|
+
try {
|
|
454
|
+
return await operation();
|
|
455
|
+
} catch (error) {
|
|
456
|
+
lastError = error;
|
|
457
|
+
if (i < retries) {
|
|
458
|
+
await new Promise((resolve) => setTimeout(resolve, this.retryDelay * Math.pow(2, i)));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
throw lastError;
|
|
463
|
+
}
|
|
464
|
+
parseAccountInfo(rawAccount) {
|
|
465
|
+
const account = rawAccount;
|
|
466
|
+
const dataArray = account.data;
|
|
467
|
+
const base64Data = Array.isArray(dataArray) ? dataArray[0] : dataArray;
|
|
468
|
+
return {
|
|
469
|
+
executable: account.executable,
|
|
470
|
+
lamports: typeof account.lamports === "number" ? lamports(BigInt(account.lamports)) : account.lamports,
|
|
471
|
+
owner: account.owner,
|
|
472
|
+
rentEpoch: account.rentEpoch !== void 0 ? typeof account.rentEpoch === "number" ? BigInt(account.rentEpoch) : account.rentEpoch : BigInt(0),
|
|
473
|
+
data: Buffer.from(base64Data, "base64"),
|
|
474
|
+
space: account.space ? typeof account.space === "number" ? BigInt(account.space) : account.space : void 0
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
// src/utils/transaction-urls.ts
|
|
480
|
+
function getSolanaExplorerUrl(signature, cluster = "mainnet-beta") {
|
|
481
|
+
const baseUrl = "https://explorer.solana.com/tx";
|
|
482
|
+
switch (cluster) {
|
|
483
|
+
case "devnet":
|
|
484
|
+
return `${baseUrl}/${signature}?cluster=devnet`;
|
|
485
|
+
case "testnet":
|
|
486
|
+
return `${baseUrl}/${signature}?cluster=testnet`;
|
|
487
|
+
case "localnet":
|
|
488
|
+
return `${baseUrl}/${signature}?cluster=custom&customUrl=http://localhost:8899`;
|
|
489
|
+
default:
|
|
490
|
+
return `${baseUrl}/${signature}`;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function getSolscanUrl(signature, cluster = "mainnet-beta") {
|
|
494
|
+
const baseUrl = "https://solscan.io/tx";
|
|
495
|
+
switch (cluster) {
|
|
496
|
+
case "devnet":
|
|
497
|
+
return `${baseUrl}/${signature}?cluster=devnet`;
|
|
498
|
+
case "testnet":
|
|
499
|
+
return `${baseUrl}/${signature}?cluster=testnet`;
|
|
500
|
+
case "localnet":
|
|
501
|
+
return `Local transaction: ${signature} (not viewable on Solscan)`;
|
|
502
|
+
default:
|
|
503
|
+
return `${baseUrl}/${signature}`;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function getSolanaFMUrl(signature, cluster = "mainnet-beta") {
|
|
507
|
+
const baseUrl = "https://solana.fm/tx";
|
|
508
|
+
switch (cluster) {
|
|
509
|
+
case "devnet":
|
|
510
|
+
return `${baseUrl}/${signature}?cluster=devnet-solana`;
|
|
511
|
+
case "testnet":
|
|
512
|
+
return `${baseUrl}/${signature}?cluster=testnet-solana`;
|
|
513
|
+
case "localnet":
|
|
514
|
+
return `Local transaction: ${signature} (not viewable on SolanaFM)`;
|
|
515
|
+
default:
|
|
516
|
+
return `${baseUrl}/${signature}`;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function getXrayUrl(signature, cluster = "mainnet-beta") {
|
|
520
|
+
const baseUrl = "https://xray.helius.xyz/tx";
|
|
521
|
+
switch (cluster) {
|
|
522
|
+
case "devnet":
|
|
523
|
+
return `${baseUrl}/${signature}?network=devnet`;
|
|
524
|
+
case "testnet":
|
|
525
|
+
return `${baseUrl}/${signature}?network=testnet`;
|
|
526
|
+
case "localnet":
|
|
527
|
+
return `Local transaction: ${signature} (not viewable on XRAY)`;
|
|
528
|
+
default:
|
|
529
|
+
return `${baseUrl}/${signature}`;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function generateExplorerUrls(signature, cluster = "mainnet-beta") {
|
|
533
|
+
return {
|
|
534
|
+
solanaExplorer: getSolanaExplorerUrl(signature, cluster),
|
|
535
|
+
solscan: getSolscanUrl(signature, cluster),
|
|
536
|
+
solanaFM: getSolanaFMUrl(signature, cluster),
|
|
537
|
+
xray: getXrayUrl(signature, cluster)
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function createTransactionResult(signature, cluster, commitment = "confirmed") {
|
|
541
|
+
return {
|
|
542
|
+
signature,
|
|
543
|
+
cluster,
|
|
544
|
+
urls: generateExplorerUrls(signature, cluster),
|
|
545
|
+
commitment,
|
|
546
|
+
timestamp: Date.now()
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/core/DevTools.ts
|
|
551
|
+
var DevTools = class _DevTools {
|
|
552
|
+
static instance = null;
|
|
553
|
+
rpcClient;
|
|
554
|
+
config;
|
|
555
|
+
isDevelopment;
|
|
556
|
+
logs = [];
|
|
557
|
+
timings = /* @__PURE__ */ new Map();
|
|
558
|
+
constructor(config) {
|
|
559
|
+
this.config = config;
|
|
560
|
+
this.rpcClient = new RpcClient({
|
|
561
|
+
endpoint: config.rpcEndpoint ?? "https://api.devnet.solana.com",
|
|
562
|
+
commitment: config.commitment
|
|
563
|
+
});
|
|
564
|
+
this.isDevelopment = process.env.NODE_ENV === "development" || config.cluster === "localnet" || false;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Get singleton instance
|
|
568
|
+
*/
|
|
569
|
+
static getInstance(config) {
|
|
570
|
+
if (!_DevTools.instance && config) {
|
|
571
|
+
_DevTools.instance = new _DevTools(config);
|
|
572
|
+
}
|
|
573
|
+
if (!_DevTools.instance) {
|
|
574
|
+
throw new Error("DevTools not initialized. Call with config first.");
|
|
575
|
+
}
|
|
576
|
+
return _DevTools.instance;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Enable development mode
|
|
580
|
+
*/
|
|
581
|
+
enableDevMode() {
|
|
582
|
+
this.isDevelopment = true;
|
|
583
|
+
console.log("\u{1F6E0}\uFE0F GhostSpeak Development Mode Enabled");
|
|
584
|
+
console.log(" - Transaction simulation before sending");
|
|
585
|
+
console.log(" - Cost estimates for all operations");
|
|
586
|
+
console.log(" - Enhanced error messages");
|
|
587
|
+
console.log(" - Performance timing");
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Check if in development mode
|
|
591
|
+
*/
|
|
592
|
+
isDevMode() {
|
|
593
|
+
return this.isDevelopment;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Analyze transaction instructions
|
|
597
|
+
*/
|
|
598
|
+
analyzeTransaction(instructions) {
|
|
599
|
+
const writableAccounts = /* @__PURE__ */ new Set();
|
|
600
|
+
const readonlyAccounts = /* @__PURE__ */ new Set();
|
|
601
|
+
const signers = /* @__PURE__ */ new Set();
|
|
602
|
+
const warnings = [];
|
|
603
|
+
let totalSize = 64;
|
|
604
|
+
const instructionAnalyses = instructions.map((instr, index) => {
|
|
605
|
+
totalSize += 32;
|
|
606
|
+
totalSize += (instr.accounts?.length ?? 0) * 32;
|
|
607
|
+
totalSize += instr.data?.length ?? 0;
|
|
608
|
+
const accounts = (instr.accounts ?? []).map((acc) => {
|
|
609
|
+
const isWritable = acc.role.toString().includes("writable") || acc.role.toString().includes("WRITABLE") || acc.role === 1 || // AccountRole.WRITABLE = 1
|
|
610
|
+
acc.role === 3;
|
|
611
|
+
const isSigner = acc.role.toString().includes("signer") || acc.role.toString().includes("SIGNER") || typeof acc === "object" && "signer" in acc || acc.role === 3;
|
|
612
|
+
if (isWritable) {
|
|
613
|
+
writableAccounts.add(acc.address);
|
|
614
|
+
} else {
|
|
615
|
+
readonlyAccounts.add(acc.address);
|
|
616
|
+
}
|
|
617
|
+
if (isSigner) {
|
|
618
|
+
signers.add(acc.address);
|
|
619
|
+
}
|
|
620
|
+
return {
|
|
621
|
+
address: acc.address,
|
|
622
|
+
isWritable,
|
|
623
|
+
isSigner,
|
|
624
|
+
role: acc.role
|
|
625
|
+
};
|
|
626
|
+
});
|
|
627
|
+
const humanReadable = this.getInstructionDescription(instr, index);
|
|
628
|
+
return {
|
|
629
|
+
index,
|
|
630
|
+
programId: instr.programAddress,
|
|
631
|
+
accountCount: accounts.length,
|
|
632
|
+
dataSize: instr.data?.length ?? 0,
|
|
633
|
+
humanReadable,
|
|
634
|
+
accounts
|
|
635
|
+
};
|
|
636
|
+
});
|
|
637
|
+
const estimatedComputeUnits = this.estimateComputeUnits(instructions);
|
|
638
|
+
const estimatedFee = this.estimateFee(estimatedComputeUnits, instructions.length);
|
|
639
|
+
if (totalSize > 1232) {
|
|
640
|
+
warnings.push(`Transaction size (${totalSize} bytes) exceeds limit (1232 bytes)`);
|
|
641
|
+
}
|
|
642
|
+
if (signers.size === 0) {
|
|
643
|
+
warnings.push("No signers found in transaction");
|
|
644
|
+
}
|
|
645
|
+
if (estimatedComputeUnits > BigInt(14e5)) {
|
|
646
|
+
warnings.push(`High compute usage: ${estimatedComputeUnits} units`);
|
|
647
|
+
}
|
|
648
|
+
return {
|
|
649
|
+
instructions: instructionAnalyses,
|
|
650
|
+
totalAccounts: writableAccounts.size + readonlyAccounts.size,
|
|
651
|
+
signerCount: signers.size,
|
|
652
|
+
writableAccounts: Array.from(writableAccounts),
|
|
653
|
+
readonlyAccounts: Array.from(readonlyAccounts),
|
|
654
|
+
estimatedSize: totalSize,
|
|
655
|
+
estimatedComputeUnits,
|
|
656
|
+
estimatedFee,
|
|
657
|
+
warnings
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Get human-readable instruction description
|
|
662
|
+
*/
|
|
663
|
+
getInstructionDescription(instruction, _index) {
|
|
664
|
+
const programId = instruction.programAddress;
|
|
665
|
+
if (programId === this.config.programId) {
|
|
666
|
+
return this.decodeGhostSpeakInstruction(instruction);
|
|
667
|
+
}
|
|
668
|
+
if (programId === "11111111111111111111111111111111") {
|
|
669
|
+
return "System Program: Transfer or Create Account";
|
|
670
|
+
}
|
|
671
|
+
if (programId === "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") {
|
|
672
|
+
return "Token Program: Token Operation";
|
|
673
|
+
}
|
|
674
|
+
if (programId === "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb") {
|
|
675
|
+
return "Token-2022 Program: Advanced Token Operation";
|
|
676
|
+
}
|
|
677
|
+
return `Unknown Instruction`;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Decode GhostSpeak instruction
|
|
681
|
+
*/
|
|
682
|
+
decodeGhostSpeakInstruction(instruction) {
|
|
683
|
+
if (!instruction.data || instruction.data.length < 8) {
|
|
684
|
+
return "GhostSpeak: Unknown Instruction";
|
|
685
|
+
}
|
|
686
|
+
const discriminator = Array.from(instruction.data.slice(0, 8)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
687
|
+
const instructionMap = {
|
|
688
|
+
"a8c5e109d3d1b8d5": "Register Agent",
|
|
689
|
+
"b7f3c8e0a2d4e9f1": "Create Escrow",
|
|
690
|
+
"c4d2f7a9b8e1c3f5": "Send Message",
|
|
691
|
+
"d9e8f2b5c1a7e4f8": "Create Channel"
|
|
692
|
+
// Add more mappings as needed
|
|
693
|
+
};
|
|
694
|
+
return `GhostSpeak: ${instructionMap[discriminator] ?? "Custom Instruction"}`;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Estimate compute units for instructions
|
|
698
|
+
*/
|
|
699
|
+
estimateComputeUnits(instructions) {
|
|
700
|
+
let totalUnits = BigInt(0);
|
|
701
|
+
for (const instr of instructions) {
|
|
702
|
+
totalUnits += BigInt(200);
|
|
703
|
+
totalUnits += BigInt((instr.accounts?.length ?? 0) * 150);
|
|
704
|
+
totalUnits += BigInt((instr.data?.length ?? 0) * 10);
|
|
705
|
+
if (instr.programAddress === this.config.programId) {
|
|
706
|
+
totalUnits += BigInt(5e3);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
return totalUnits;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Estimate transaction fee
|
|
713
|
+
*/
|
|
714
|
+
estimateFee(computeUnits, instructionCount) {
|
|
715
|
+
const baseFee = BigInt(5e3);
|
|
716
|
+
const computeFee = computeUnits * BigInt(5) / BigInt(1e6);
|
|
717
|
+
const priorityFee = BigInt(instructionCount * 1e3);
|
|
718
|
+
return baseFee + computeFee + priorityFee;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Log debug message
|
|
722
|
+
*/
|
|
723
|
+
log(message, data) {
|
|
724
|
+
if (!this.isDevelopment) return;
|
|
725
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
726
|
+
const logEntry = `[${timestamp}] ${message}`;
|
|
727
|
+
this.logs.push(logEntry);
|
|
728
|
+
console.log(`\u{1F50D} ${logEntry}`);
|
|
729
|
+
if (data) {
|
|
730
|
+
console.log(" Data:", data);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Start timing an operation
|
|
735
|
+
*/
|
|
736
|
+
startTiming(label) {
|
|
737
|
+
if (!this.isDevelopment) return;
|
|
738
|
+
const perfNow = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
739
|
+
this.timings.set(label, perfNow);
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* End timing and log result
|
|
743
|
+
*/
|
|
744
|
+
endTiming(label) {
|
|
745
|
+
if (!this.isDevelopment) return;
|
|
746
|
+
const start = this.timings.get(label);
|
|
747
|
+
if (!start) return;
|
|
748
|
+
const perfNow = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
749
|
+
const duration = perfNow - start;
|
|
750
|
+
this.log(`${label} took ${duration.toFixed(2)}ms`);
|
|
751
|
+
this.timings.delete(label);
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Format transaction for display
|
|
755
|
+
*/
|
|
756
|
+
formatTransaction(analysis) {
|
|
757
|
+
const lines = [
|
|
758
|
+
"\u{1F4CB} Transaction Analysis",
|
|
759
|
+
"\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
760
|
+
`Instructions: ${analysis.instructions.length}`,
|
|
761
|
+
`Total Accounts: ${analysis.totalAccounts}`,
|
|
762
|
+
`Signers: ${analysis.signerCount}`,
|
|
763
|
+
`Estimated Size: ${analysis.estimatedSize} bytes`,
|
|
764
|
+
`Estimated Compute Units: ${analysis.estimatedComputeUnits.toLocaleString()}`,
|
|
765
|
+
`Estimated Fee: ${(Number(analysis.estimatedFee) / 1e9).toFixed(6)} SOL`,
|
|
766
|
+
""
|
|
767
|
+
];
|
|
768
|
+
lines.push("Instructions:");
|
|
769
|
+
for (const instr of analysis.instructions) {
|
|
770
|
+
lines.push(` ${instr.index + 1}. ${instr.humanReadable}`);
|
|
771
|
+
lines.push(` Program: ${instr.programId.slice(0, 8)}...`);
|
|
772
|
+
lines.push(` Accounts: ${instr.accountCount}, Data: ${instr.dataSize} bytes`);
|
|
773
|
+
}
|
|
774
|
+
if (analysis.warnings.length > 0) {
|
|
775
|
+
lines.push("");
|
|
776
|
+
lines.push("\u26A0\uFE0F Warnings:");
|
|
777
|
+
for (const warning of analysis.warnings) {
|
|
778
|
+
lines.push(` - ${warning}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
lines.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
782
|
+
return lines.join("\n");
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Export debug logs
|
|
786
|
+
*/
|
|
787
|
+
exportLogs() {
|
|
788
|
+
return [...this.logs];
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Clear debug logs
|
|
792
|
+
*/
|
|
793
|
+
clearLogs() {
|
|
794
|
+
this.logs = [];
|
|
795
|
+
this.timings.clear();
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
function validateInstruction(instruction) {
|
|
799
|
+
const inst = instruction;
|
|
800
|
+
if (!inst.programAddress) {
|
|
801
|
+
throw new Error("Invalid instruction format");
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
var InstructionBuilder = class {
|
|
805
|
+
rpcClient;
|
|
806
|
+
config;
|
|
807
|
+
devTools;
|
|
808
|
+
debugMode = false;
|
|
809
|
+
constructor(config) {
|
|
810
|
+
this.config = config;
|
|
811
|
+
this.rpcClient = new RpcClient({
|
|
812
|
+
endpoint: config.rpcEndpoint ?? "https://api.devnet.solana.com",
|
|
813
|
+
wsEndpoint: config.wsEndpoint,
|
|
814
|
+
commitment: config.commitment ?? "confirmed"
|
|
815
|
+
});
|
|
816
|
+
this.devTools = DevTools.getInstance(config);
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Execute a single instruction with unified error handling and transaction patterns
|
|
820
|
+
*/
|
|
821
|
+
async execute(instructionName, instructionGetter, signers, options) {
|
|
822
|
+
const context = createErrorContext(
|
|
823
|
+
"execute",
|
|
824
|
+
instructionName,
|
|
825
|
+
signers.map((s) => ({ address: s.address, name: "signer" })),
|
|
826
|
+
{ programId: this.config.programId }
|
|
827
|
+
);
|
|
828
|
+
try {
|
|
829
|
+
if (this.devTools.isDevMode()) {
|
|
830
|
+
this.devTools.startTiming(instructionName);
|
|
831
|
+
}
|
|
832
|
+
const instruction = await Promise.resolve(instructionGetter());
|
|
833
|
+
validateInstruction(instruction);
|
|
834
|
+
if (this.debugMode || this.devTools.isDevMode()) {
|
|
835
|
+
const analysis = this.devTools.analyzeTransaction([instruction]);
|
|
836
|
+
console.log(this.devTools.formatTransaction(analysis));
|
|
837
|
+
this.debugMode = false;
|
|
838
|
+
}
|
|
839
|
+
if (options?.simulate) {
|
|
840
|
+
return await this.simulateInstruction(instruction, signers);
|
|
841
|
+
}
|
|
842
|
+
const latestBlockhashResult = await this.rpcClient.getLatestBlockhash();
|
|
843
|
+
const latestBlockhash = {
|
|
844
|
+
blockhash: latestBlockhashResult.blockhash,
|
|
845
|
+
lastValidBlockHeight: latestBlockhashResult.lastValidBlockHeight
|
|
846
|
+
};
|
|
847
|
+
const transactionMessage = pipe(
|
|
848
|
+
createTransactionMessage({ version: 0 }),
|
|
849
|
+
(tx) => setTransactionMessageFeePayerSigner(signers[0], tx),
|
|
850
|
+
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
|
|
851
|
+
(tx) => appendTransactionMessageInstructions([instruction], tx)
|
|
852
|
+
);
|
|
853
|
+
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
|
|
854
|
+
const signatureResult = await this.sendAndConfirm(
|
|
855
|
+
signedTransaction,
|
|
856
|
+
options?.skipPreflight ?? false,
|
|
857
|
+
options?.maxRetries ?? 30
|
|
858
|
+
);
|
|
859
|
+
if (typeof signatureResult !== "string") {
|
|
860
|
+
throw new Error("Transaction signature is not a string");
|
|
861
|
+
}
|
|
862
|
+
const signature = signatureResult;
|
|
863
|
+
if (options?.returnDetails) {
|
|
864
|
+
const cluster = this.config.cluster ?? "devnet";
|
|
865
|
+
const result = createTransactionResult(signature, cluster, this.config.commitment ?? "confirmed");
|
|
866
|
+
if (this.devTools.isDevMode()) {
|
|
867
|
+
this.devTools.endTiming(instructionName);
|
|
868
|
+
}
|
|
869
|
+
return result;
|
|
870
|
+
}
|
|
871
|
+
if (this.devTools.isDevMode()) {
|
|
872
|
+
this.devTools.endTiming(instructionName);
|
|
873
|
+
}
|
|
874
|
+
return signature;
|
|
875
|
+
} catch (error) {
|
|
876
|
+
logEnhancedError(error, context);
|
|
877
|
+
throw error;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Execute multiple instructions in a single transaction
|
|
882
|
+
*/
|
|
883
|
+
async executeBatch(batchName, instructionGetters, signers, options) {
|
|
884
|
+
const context = createErrorContext(
|
|
885
|
+
"executeBatch",
|
|
886
|
+
batchName,
|
|
887
|
+
signers.map((s) => ({ address: s.address, name: "signer" })),
|
|
888
|
+
{ programId: this.config.programId, instructionCount: instructionGetters.length }
|
|
889
|
+
);
|
|
890
|
+
try {
|
|
891
|
+
const instructions = await Promise.all(
|
|
892
|
+
instructionGetters.map(async (getter, i) => {
|
|
893
|
+
const instruction = await Promise.resolve(getter());
|
|
894
|
+
try {
|
|
895
|
+
validateInstruction(instruction);
|
|
896
|
+
return instruction;
|
|
897
|
+
} catch (error) {
|
|
898
|
+
throw new Error(`Instruction ${i} in ${batchName}: ${error.message}`);
|
|
899
|
+
}
|
|
900
|
+
})
|
|
901
|
+
);
|
|
902
|
+
const estimatedSize = this.estimateTransactionSize(instructions);
|
|
903
|
+
if (estimatedSize > 1232) {
|
|
904
|
+
throw new Error(`Transaction too large: ${estimatedSize} bytes (max: 1232)`);
|
|
905
|
+
}
|
|
906
|
+
if (options?.simulate) {
|
|
907
|
+
return await this.simulateBatch(instructions, signers);
|
|
908
|
+
}
|
|
909
|
+
const latestBlockhashResult = await this.rpcClient.getLatestBlockhash();
|
|
910
|
+
const latestBlockhash = {
|
|
911
|
+
blockhash: latestBlockhashResult.blockhash,
|
|
912
|
+
lastValidBlockHeight: latestBlockhashResult.lastValidBlockHeight
|
|
913
|
+
};
|
|
914
|
+
const transactionMessage = pipe(
|
|
915
|
+
createTransactionMessage({ version: 0 }),
|
|
916
|
+
(tx) => setTransactionMessageFeePayerSigner(signers[0], tx),
|
|
917
|
+
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
|
|
918
|
+
(tx) => appendTransactionMessageInstructions(instructions, tx)
|
|
919
|
+
);
|
|
920
|
+
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
|
|
921
|
+
const signatureResult = await this.sendAndConfirm(
|
|
922
|
+
signedTransaction,
|
|
923
|
+
options?.skipPreflight ?? false
|
|
924
|
+
);
|
|
925
|
+
if (typeof signatureResult !== "string") {
|
|
926
|
+
throw new Error("Transaction signature is not a string");
|
|
927
|
+
}
|
|
928
|
+
const signature = signatureResult;
|
|
929
|
+
if (options?.returnDetails) {
|
|
930
|
+
const cluster = this.config.cluster ?? "devnet";
|
|
931
|
+
const result = createTransactionResult(signature, cluster, this.config.commitment ?? "confirmed");
|
|
932
|
+
if (this.devTools.isDevMode()) {
|
|
933
|
+
this.devTools.endTiming(batchName);
|
|
934
|
+
}
|
|
935
|
+
return result;
|
|
936
|
+
}
|
|
937
|
+
if (this.devTools.isDevMode()) {
|
|
938
|
+
this.devTools.endTiming(batchName);
|
|
939
|
+
}
|
|
940
|
+
return signature;
|
|
941
|
+
} catch (error) {
|
|
942
|
+
logEnhancedError(error, context);
|
|
943
|
+
throw error;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Get and decode account data with unified error handling
|
|
948
|
+
*/
|
|
949
|
+
async getAccount(address, decoderImportName) {
|
|
950
|
+
try {
|
|
951
|
+
const accountInfo = await this.rpcClient.getAccountInfo(address, {
|
|
952
|
+
commitment: this.config.commitment
|
|
953
|
+
});
|
|
954
|
+
if (!accountInfo) return null;
|
|
955
|
+
const generated = await import('./generated-EG5USUFG.js');
|
|
956
|
+
const decoderGetter = generated[decoderImportName];
|
|
957
|
+
const decoder = decoderGetter();
|
|
958
|
+
const rawData = this.extractRawData(accountInfo.data);
|
|
959
|
+
return decoder.decode(rawData);
|
|
960
|
+
} catch (error) {
|
|
961
|
+
console.warn(`Failed to fetch account ${address}:`, error);
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Get multiple accounts with unified pattern
|
|
967
|
+
*/
|
|
968
|
+
async getAccounts(addresses, decoderImportName) {
|
|
969
|
+
try {
|
|
970
|
+
const accounts = await this.rpcClient.getMultipleAccounts(addresses, {
|
|
971
|
+
commitment: this.config.commitment
|
|
972
|
+
});
|
|
973
|
+
const generated = await import('./generated-EG5USUFG.js');
|
|
974
|
+
const decoderGetter = generated[decoderImportName];
|
|
975
|
+
const decoder = decoderGetter();
|
|
976
|
+
return accounts.map((accountInfo) => {
|
|
977
|
+
if (!accountInfo) return null;
|
|
978
|
+
try {
|
|
979
|
+
const rawData = this.extractRawData(accountInfo.data);
|
|
980
|
+
return decoder.decode(rawData);
|
|
981
|
+
} catch {
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
} catch (error) {
|
|
986
|
+
console.warn("Failed to fetch multiple accounts:", error);
|
|
987
|
+
return addresses.map(() => null);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Get program accounts with filters
|
|
992
|
+
*/
|
|
993
|
+
async getProgramAccounts(decoderImportName, filters) {
|
|
994
|
+
try {
|
|
995
|
+
const convertedFilters = filters?.map((filter) => {
|
|
996
|
+
if ("dataSize" in filter) {
|
|
997
|
+
return { dataSize: filter.dataSize };
|
|
998
|
+
} else {
|
|
999
|
+
const encoding = filter.memcmp.encoding ?? "base58";
|
|
1000
|
+
if (encoding === "base64") {
|
|
1001
|
+
return {
|
|
1002
|
+
memcmp: {
|
|
1003
|
+
offset: filter.memcmp.offset,
|
|
1004
|
+
bytes: filter.memcmp.bytes,
|
|
1005
|
+
encoding: "base64"
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
} else {
|
|
1009
|
+
return {
|
|
1010
|
+
memcmp: {
|
|
1011
|
+
offset: filter.memcmp.offset,
|
|
1012
|
+
bytes: filter.memcmp.bytes,
|
|
1013
|
+
encoding: "base58"
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
const accounts = await this.rpcClient.getProgramAccounts(this.config.programId, {
|
|
1020
|
+
commitment: this.config.commitment,
|
|
1021
|
+
filters: convertedFilters ?? []
|
|
1022
|
+
});
|
|
1023
|
+
const generated = await import('./generated-EG5USUFG.js');
|
|
1024
|
+
const decoderGetter = generated[decoderImportName];
|
|
1025
|
+
const decoder = decoderGetter();
|
|
1026
|
+
const decodedAccounts = [];
|
|
1027
|
+
for (const { pubkey, account } of accounts) {
|
|
1028
|
+
try {
|
|
1029
|
+
const rawData = this.extractRawData(account.data);
|
|
1030
|
+
const decodedData = decoder.decode(rawData);
|
|
1031
|
+
decodedAccounts.push({ address: pubkey, data: decodedData });
|
|
1032
|
+
} catch {
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return decodedAccounts;
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
console.error("Failed to get program accounts:", error);
|
|
1038
|
+
return [];
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Enable debug mode for next transaction
|
|
1043
|
+
*/
|
|
1044
|
+
enableDebug() {
|
|
1045
|
+
this.debugMode = true;
|
|
1046
|
+
return this;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Debug transaction - analyze without executing
|
|
1050
|
+
*/
|
|
1051
|
+
async debug(instructionName, instructionGetters) {
|
|
1052
|
+
this.devTools.log(`Debugging ${instructionName}`);
|
|
1053
|
+
const instructions = await Promise.all(
|
|
1054
|
+
instructionGetters.map(async (getter) => {
|
|
1055
|
+
const instruction = await Promise.resolve(getter());
|
|
1056
|
+
validateInstruction(instruction);
|
|
1057
|
+
return instruction;
|
|
1058
|
+
})
|
|
1059
|
+
);
|
|
1060
|
+
const analysis = this.devTools.analyzeTransaction(instructions);
|
|
1061
|
+
console.log(this.devTools.formatTransaction(analysis));
|
|
1062
|
+
return analysis;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Get human-readable explanation of transaction
|
|
1066
|
+
*/
|
|
1067
|
+
async explain(instructionName, instructionGetters) {
|
|
1068
|
+
const analysis = await this.debug(instructionName, instructionGetters);
|
|
1069
|
+
const lines = [
|
|
1070
|
+
`\u{1F50D} Transaction: ${instructionName}`,
|
|
1071
|
+
"",
|
|
1072
|
+
"This transaction will:",
|
|
1073
|
+
...analysis.instructions.map((instr, i) => ` ${i + 1}. ${instr.humanReadable}`),
|
|
1074
|
+
"",
|
|
1075
|
+
`Cost: ~${(Number(analysis.estimatedFee) / 1e9).toFixed(6)} SOL`,
|
|
1076
|
+
`Size: ${analysis.estimatedSize} bytes`,
|
|
1077
|
+
`Compute: ${analysis.estimatedComputeUnits.toLocaleString()} units`
|
|
1078
|
+
];
|
|
1079
|
+
if (analysis.warnings.length > 0) {
|
|
1080
|
+
lines.push("", "\u26A0\uFE0F Warnings:");
|
|
1081
|
+
lines.push(...analysis.warnings.map((w) => ` - ${w}`));
|
|
1082
|
+
}
|
|
1083
|
+
return lines.join("\n");
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Estimate transaction cost
|
|
1087
|
+
*/
|
|
1088
|
+
async estimateCost(instructionGetters) {
|
|
1089
|
+
try {
|
|
1090
|
+
const instructions = await Promise.all(
|
|
1091
|
+
instructionGetters.map(async (getter) => {
|
|
1092
|
+
const instruction = await Promise.resolve(getter());
|
|
1093
|
+
validateInstruction(instruction);
|
|
1094
|
+
return instruction;
|
|
1095
|
+
})
|
|
1096
|
+
);
|
|
1097
|
+
const latestBlockhashResult = await this.rpcClient.getLatestBlockhash();
|
|
1098
|
+
const latestBlockhash = {
|
|
1099
|
+
blockhash: latestBlockhashResult.blockhash,
|
|
1100
|
+
lastValidBlockHeight: latestBlockhashResult.lastValidBlockHeight
|
|
1101
|
+
};
|
|
1102
|
+
const transactionMessage = pipe(
|
|
1103
|
+
createTransactionMessage({ version: 0 }),
|
|
1104
|
+
(tx) => setTransactionMessageFeePayer(this.config.defaultFeePayer, tx),
|
|
1105
|
+
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
|
|
1106
|
+
(tx) => appendTransactionMessageInstructions(instructions, tx)
|
|
1107
|
+
);
|
|
1108
|
+
const compiledMessage = compileTransactionMessage(transactionMessage);
|
|
1109
|
+
const encodedMessage = Buffer.from(compiledMessage).toString("base64");
|
|
1110
|
+
const fee = await this.rpcClient.getFeeForMessage(encodedMessage);
|
|
1111
|
+
return BigInt(fee ?? 0);
|
|
1112
|
+
} catch {
|
|
1113
|
+
const baseFee = 5000n;
|
|
1114
|
+
const perInstructionFee = 1000n;
|
|
1115
|
+
return baseFee + BigInt(instructionGetters.length) * perInstructionFee;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
// Private helper methods
|
|
1119
|
+
async sendAndConfirm(signedTransaction, skipPreflight, maxRetries = 30) {
|
|
1120
|
+
const wireTransaction = getBase64EncodedWireTransaction(signedTransaction);
|
|
1121
|
+
const signature = await this.rpcClient.sendTransaction(wireTransaction, {
|
|
1122
|
+
skipPreflight,
|
|
1123
|
+
preflightCommitment: this.config.commitment
|
|
1124
|
+
});
|
|
1125
|
+
console.log("\u{1F50D} Transaction sent, signature:", signature);
|
|
1126
|
+
console.log("\u{1F50D} Signature length:", signature.length);
|
|
1127
|
+
console.log("\u{1F50D} Signature type:", typeof signature);
|
|
1128
|
+
let confirmed = false;
|
|
1129
|
+
let attempts = 0;
|
|
1130
|
+
let currentDelay = 1e3;
|
|
1131
|
+
const maxConfirmationTime = 3e4;
|
|
1132
|
+
const startTime = Date.now();
|
|
1133
|
+
while (!confirmed && attempts < maxRetries && Date.now() - startTime < maxConfirmationTime) {
|
|
1134
|
+
try {
|
|
1135
|
+
console.log(`\u{1F50D} Confirmation attempt ${attempts + 1}/${maxRetries}`);
|
|
1136
|
+
const statuses = await this.rpcClient.getSignatureStatuses([signature]);
|
|
1137
|
+
if (statuses[0]) {
|
|
1138
|
+
console.log("\u{1F50D} Status found:", statuses[0]);
|
|
1139
|
+
if (statuses[0].err) {
|
|
1140
|
+
throw new Error(`Transaction failed: ${JSON.stringify(statuses[0].err, (_, v) => typeof v === "bigint" ? v.toString() : v)}`);
|
|
1141
|
+
}
|
|
1142
|
+
const confirmationStatus = statuses[0].confirmationStatus;
|
|
1143
|
+
if (confirmationStatus === this.config.commitment || this.config.commitment === "confirmed" && confirmationStatus === "finalized") {
|
|
1144
|
+
confirmed = true;
|
|
1145
|
+
console.log("\u2705 Transaction confirmed via status check");
|
|
1146
|
+
break;
|
|
1147
|
+
}
|
|
1148
|
+
} else {
|
|
1149
|
+
console.log("\u{1F50D} No status found, trying transaction details...");
|
|
1150
|
+
try {
|
|
1151
|
+
const { createSolanaRpc: createSolanaRpc2 } = await import('@solana/kit');
|
|
1152
|
+
const directRpc = createSolanaRpc2(this.config.rpcEndpoint ?? "https://api.devnet.solana.com");
|
|
1153
|
+
const transaction = await directRpc.getTransaction(signature, {
|
|
1154
|
+
commitment: this.config.commitment ?? "confirmed",
|
|
1155
|
+
encoding: "json",
|
|
1156
|
+
maxSupportedTransactionVersion: 0
|
|
1157
|
+
}).send();
|
|
1158
|
+
if (transaction && transaction.meta) {
|
|
1159
|
+
if (transaction.meta.err) {
|
|
1160
|
+
throw new Error(`Transaction failed: ${JSON.stringify(transaction.meta.err, (_, v) => typeof v === "bigint" ? v.toString() : v)}`);
|
|
1161
|
+
}
|
|
1162
|
+
confirmed = true;
|
|
1163
|
+
console.log("\u2705 Transaction confirmed via direct lookup");
|
|
1164
|
+
break;
|
|
1165
|
+
}
|
|
1166
|
+
} catch {
|
|
1167
|
+
console.log("\u{1F50D} Transaction details not yet available");
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
attempts++;
|
|
1171
|
+
await new Promise((resolve) => setTimeout(resolve, currentDelay));
|
|
1172
|
+
currentDelay = Math.min(currentDelay * 1.5, 5e3);
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
if (error instanceof Error && error.message.includes("Transaction failed")) {
|
|
1175
|
+
throw error;
|
|
1176
|
+
}
|
|
1177
|
+
console.log(`\u{1F50D} Confirmation attempt failed:`, error.message);
|
|
1178
|
+
attempts++;
|
|
1179
|
+
await new Promise((resolve) => setTimeout(resolve, currentDelay * 2));
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
if (!confirmed) {
|
|
1183
|
+
console.log("\u{1F50D} Final confirmation attempt via transaction lookup...");
|
|
1184
|
+
try {
|
|
1185
|
+
const { createSolanaRpc: createSolanaRpc2 } = await import('@solana/kit');
|
|
1186
|
+
const directRpc = createSolanaRpc2(this.config.rpcEndpoint ?? "https://api.devnet.solana.com");
|
|
1187
|
+
const transaction = await directRpc.getTransaction(signature, {
|
|
1188
|
+
commitment: this.config.commitment ?? "confirmed",
|
|
1189
|
+
encoding: "json",
|
|
1190
|
+
maxSupportedTransactionVersion: 0
|
|
1191
|
+
}).send();
|
|
1192
|
+
if (transaction && transaction.meta) {
|
|
1193
|
+
if (transaction.meta.err) {
|
|
1194
|
+
throw new Error(`Transaction failed: ${JSON.stringify(transaction.meta.err, (_, v) => typeof v === "bigint" ? v.toString() : v)}`);
|
|
1195
|
+
}
|
|
1196
|
+
console.log("\u2705 Transaction confirmed on final check - returning success");
|
|
1197
|
+
return signature;
|
|
1198
|
+
}
|
|
1199
|
+
} catch (finalError) {
|
|
1200
|
+
console.log("\u{1F50D} Final check failed:", finalError.message);
|
|
1201
|
+
}
|
|
1202
|
+
console.log("\u26A0\uFE0F Transaction confirmation timed out, but transaction was sent");
|
|
1203
|
+
console.log(` Check status at: https://explorer.solana.com/tx/${signature}?cluster=${this.config.cluster || "devnet"}`);
|
|
1204
|
+
return signature;
|
|
1205
|
+
}
|
|
1206
|
+
return signature;
|
|
1207
|
+
}
|
|
1208
|
+
async simulateInstruction(instruction, signers) {
|
|
1209
|
+
const latestBlockhashResult = await this.rpcClient.getLatestBlockhash();
|
|
1210
|
+
const latestBlockhash = {
|
|
1211
|
+
blockhash: latestBlockhashResult.blockhash,
|
|
1212
|
+
lastValidBlockHeight: latestBlockhashResult.lastValidBlockHeight
|
|
1213
|
+
};
|
|
1214
|
+
const transactionMessage = pipe(
|
|
1215
|
+
createTransactionMessage({ version: 0 }),
|
|
1216
|
+
(tx) => setTransactionMessageFeePayerSigner(signers[0], tx),
|
|
1217
|
+
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
|
|
1218
|
+
(tx) => appendTransactionMessageInstructions([instruction], tx)
|
|
1219
|
+
);
|
|
1220
|
+
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
|
|
1221
|
+
const wireTransaction = getBase64EncodedWireTransaction(signedTransaction);
|
|
1222
|
+
return this.rpcClient.simulateTransaction(wireTransaction, {
|
|
1223
|
+
commitment: this.config.commitment,
|
|
1224
|
+
replaceRecentBlockhash: true
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
async simulateBatch(instructions, signers) {
|
|
1228
|
+
const latestBlockhashResult = await this.rpcClient.getLatestBlockhash();
|
|
1229
|
+
const latestBlockhash = {
|
|
1230
|
+
blockhash: latestBlockhashResult.blockhash,
|
|
1231
|
+
lastValidBlockHeight: latestBlockhashResult.lastValidBlockHeight
|
|
1232
|
+
};
|
|
1233
|
+
const transactionMessage = pipe(
|
|
1234
|
+
createTransactionMessage({ version: 0 }),
|
|
1235
|
+
(tx) => setTransactionMessageFeePayerSigner(signers[0], tx),
|
|
1236
|
+
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
|
|
1237
|
+
(tx) => appendTransactionMessageInstructions(instructions, tx)
|
|
1238
|
+
);
|
|
1239
|
+
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
|
|
1240
|
+
const wireTransaction = getBase64EncodedWireTransaction(signedTransaction);
|
|
1241
|
+
return this.rpcClient.simulateTransaction(wireTransaction, {
|
|
1242
|
+
commitment: this.config.commitment,
|
|
1243
|
+
replaceRecentBlockhash: true
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
estimateTransactionSize(instructions) {
|
|
1247
|
+
let totalSize = 64;
|
|
1248
|
+
for (const instruction of instructions) {
|
|
1249
|
+
totalSize += 32;
|
|
1250
|
+
totalSize += (instruction.accounts?.length ?? 0) * 32;
|
|
1251
|
+
totalSize += instruction.data.length;
|
|
1252
|
+
}
|
|
1253
|
+
return totalSize;
|
|
1254
|
+
}
|
|
1255
|
+
extractRawData(data) {
|
|
1256
|
+
if (Buffer.isBuffer(data) || data instanceof Uint8Array) {
|
|
1257
|
+
return new Uint8Array(data);
|
|
1258
|
+
}
|
|
1259
|
+
if (typeof data === "object" && data !== null && "data" in data) {
|
|
1260
|
+
return Buffer.from(data.data, "base64");
|
|
1261
|
+
}
|
|
1262
|
+
if (typeof data === "string") {
|
|
1263
|
+
return Buffer.from(data, "base64");
|
|
1264
|
+
}
|
|
1265
|
+
throw new Error("Invalid account data format");
|
|
1266
|
+
}
|
|
1267
|
+
// =====================================================
|
|
1268
|
+
// H2A PROTOCOL INSTRUCTION METHODS
|
|
1269
|
+
// =====================================================
|
|
1270
|
+
/**
|
|
1271
|
+
* Create a communication session instruction
|
|
1272
|
+
*/
|
|
1273
|
+
async createCommunicationSession(_params) {
|
|
1274
|
+
return {
|
|
1275
|
+
programAddress: this.config.programId,
|
|
1276
|
+
accounts: [],
|
|
1277
|
+
data: new Uint8Array(0)
|
|
1278
|
+
// Placeholder - would contain serialized instruction data
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Send a communication message instruction
|
|
1283
|
+
*/
|
|
1284
|
+
async sendCommunicationMessage(_sessionAddress, _params) {
|
|
1285
|
+
return {
|
|
1286
|
+
programAddress: this.config.programId,
|
|
1287
|
+
accounts: [],
|
|
1288
|
+
data: new Uint8Array(0)
|
|
1289
|
+
// Placeholder - would contain serialized instruction data
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Update participant status instruction
|
|
1294
|
+
*/
|
|
1295
|
+
async updateParticipantStatus(_params) {
|
|
1296
|
+
return {
|
|
1297
|
+
programAddress: this.config.programId,
|
|
1298
|
+
accounts: [],
|
|
1299
|
+
data: new Uint8Array(0)
|
|
1300
|
+
// Placeholder - would contain serialized instruction data
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
var DEFAULT_TTLS = {
|
|
1305
|
+
processed: 500,
|
|
1306
|
+
// 500ms - very volatile
|
|
1307
|
+
confirmed: 2e3,
|
|
1308
|
+
// 2s - less volatile
|
|
1309
|
+
finalized: 3e4
|
|
1310
|
+
// 30s - stable
|
|
1311
|
+
};
|
|
1312
|
+
var CacheManager = class {
|
|
1313
|
+
accountCache;
|
|
1314
|
+
pdaCache;
|
|
1315
|
+
config;
|
|
1316
|
+
ttls;
|
|
1317
|
+
constructor(config = {}) {
|
|
1318
|
+
this.config = {
|
|
1319
|
+
enabled: config.enabled ?? false,
|
|
1320
|
+
maxSize: config.maxSize ?? 1e3,
|
|
1321
|
+
ttlOverrides: config.ttlOverrides ?? {}
|
|
1322
|
+
};
|
|
1323
|
+
this.ttls = {
|
|
1324
|
+
processed: config.ttlOverrides?.processed ?? DEFAULT_TTLS.processed,
|
|
1325
|
+
confirmed: config.ttlOverrides?.confirmed ?? DEFAULT_TTLS.confirmed,
|
|
1326
|
+
finalized: config.ttlOverrides?.finalized ?? DEFAULT_TTLS.finalized
|
|
1327
|
+
};
|
|
1328
|
+
this.accountCache = new LRUCache({
|
|
1329
|
+
max: this.config.maxSize,
|
|
1330
|
+
ttl: DEFAULT_TTLS.finalized
|
|
1331
|
+
// Default TTL (overridden per entry)
|
|
1332
|
+
});
|
|
1333
|
+
this.pdaCache = new LRUCache({
|
|
1334
|
+
max: this.config.maxSize
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Check if caching is enabled
|
|
1339
|
+
*/
|
|
1340
|
+
isEnabled() {
|
|
1341
|
+
return this.config.enabled;
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Get cached account data
|
|
1345
|
+
*
|
|
1346
|
+
* @param address - Account address
|
|
1347
|
+
* @param commitment - Commitment level
|
|
1348
|
+
* @param currentSlot - Current blockchain slot (for staleness check)
|
|
1349
|
+
* @returns Cached data or undefined
|
|
1350
|
+
*/
|
|
1351
|
+
getAccount(address, commitment, currentSlot) {
|
|
1352
|
+
if (!this.config.enabled) return void 0;
|
|
1353
|
+
const key = `${address}:${commitment}`;
|
|
1354
|
+
const entry = this.accountCache.get(key);
|
|
1355
|
+
if (!entry) return void 0;
|
|
1356
|
+
if (currentSlot !== void 0 && entry.slot < currentSlot) {
|
|
1357
|
+
this.accountCache.delete(key);
|
|
1358
|
+
return void 0;
|
|
1359
|
+
}
|
|
1360
|
+
return entry.data;
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Cache account data
|
|
1364
|
+
*
|
|
1365
|
+
* @param address - Account address
|
|
1366
|
+
* @param data - Account data to cache
|
|
1367
|
+
* @param commitment - Commitment level
|
|
1368
|
+
* @param slot - Blockchain slot when data was fetched
|
|
1369
|
+
*/
|
|
1370
|
+
setAccount(address, data, commitment, slot) {
|
|
1371
|
+
if (!this.config.enabled) return;
|
|
1372
|
+
const key = `${address}:${commitment}`;
|
|
1373
|
+
const entry = {
|
|
1374
|
+
data,
|
|
1375
|
+
slot,
|
|
1376
|
+
commitment,
|
|
1377
|
+
timestamp: Date.now()
|
|
1378
|
+
};
|
|
1379
|
+
this.accountCache.set(key, entry, {
|
|
1380
|
+
ttl: this.ttls[commitment]
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Get cached PDA
|
|
1385
|
+
*
|
|
1386
|
+
* PDAs are cached indefinitely as they're deterministic.
|
|
1387
|
+
*
|
|
1388
|
+
* @param seeds - Serialized seed components
|
|
1389
|
+
* @returns Cached PDA or undefined
|
|
1390
|
+
*/
|
|
1391
|
+
getPDA(seeds) {
|
|
1392
|
+
if (!this.config.enabled) return void 0;
|
|
1393
|
+
return this.pdaCache.get(seeds);
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Cache PDA derivation
|
|
1397
|
+
*
|
|
1398
|
+
* @param seeds - Serialized seed components (use JSON.stringify for consistency)
|
|
1399
|
+
* @param pda - Derived PDA address
|
|
1400
|
+
*/
|
|
1401
|
+
setPDA(seeds, pda) {
|
|
1402
|
+
if (!this.config.enabled) return;
|
|
1403
|
+
this.pdaCache.set(seeds, pda);
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Invalidate account cache entry
|
|
1407
|
+
*
|
|
1408
|
+
* @param address - Account address to invalidate
|
|
1409
|
+
* @param commitment - Optional commitment level (invalidates all if not specified)
|
|
1410
|
+
*/
|
|
1411
|
+
invalidateAccount(address, commitment) {
|
|
1412
|
+
if (!this.config.enabled) return;
|
|
1413
|
+
if (commitment) {
|
|
1414
|
+
this.accountCache.delete(`${address}:${commitment}`);
|
|
1415
|
+
} else {
|
|
1416
|
+
["processed", "confirmed", "finalized"].forEach((c) => {
|
|
1417
|
+
this.accountCache.delete(`${address}:${c}`);
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Clear all caches
|
|
1423
|
+
*/
|
|
1424
|
+
clear() {
|
|
1425
|
+
this.accountCache.clear();
|
|
1426
|
+
this.pdaCache.clear();
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Get cache statistics
|
|
1430
|
+
*
|
|
1431
|
+
* @returns Cache size and hit/miss stats
|
|
1432
|
+
*/
|
|
1433
|
+
getStats() {
|
|
1434
|
+
return {
|
|
1435
|
+
accountCache: {
|
|
1436
|
+
size: this.accountCache.size,
|
|
1437
|
+
max: this.config.maxSize
|
|
1438
|
+
},
|
|
1439
|
+
pdaCache: {
|
|
1440
|
+
size: this.pdaCache.size,
|
|
1441
|
+
max: this.config.maxSize
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
|
|
1447
|
+
// src/core/BaseModule.ts
|
|
1448
|
+
var BaseModule = class {
|
|
1449
|
+
builder;
|
|
1450
|
+
config;
|
|
1451
|
+
logger;
|
|
1452
|
+
cacheManager;
|
|
1453
|
+
_debugMode = false;
|
|
1454
|
+
constructor(config) {
|
|
1455
|
+
this.config = config;
|
|
1456
|
+
this.builder = new InstructionBuilder(config);
|
|
1457
|
+
this.cacheManager = new CacheManager(config.cache);
|
|
1458
|
+
this.logger = config.logger;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Enable debug mode for next operation
|
|
1462
|
+
*/
|
|
1463
|
+
debug() {
|
|
1464
|
+
this._debugMode = true;
|
|
1465
|
+
this.builder.enableDebug();
|
|
1466
|
+
return this;
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Execute a single instruction
|
|
1470
|
+
*/
|
|
1471
|
+
async execute(instructionName, instructionGetter, signers) {
|
|
1472
|
+
return this.builder.execute(
|
|
1473
|
+
instructionName,
|
|
1474
|
+
instructionGetter,
|
|
1475
|
+
signers
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Execute a single instruction with detailed result
|
|
1480
|
+
*/
|
|
1481
|
+
async executeWithDetails(instructionName, instructionGetter, signers) {
|
|
1482
|
+
return this.builder.execute(
|
|
1483
|
+
instructionName,
|
|
1484
|
+
instructionGetter,
|
|
1485
|
+
signers,
|
|
1486
|
+
{ returnDetails: true }
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Execute multiple instructions in a batch
|
|
1491
|
+
*/
|
|
1492
|
+
async executeBatch(batchName, instructionGetters, signers) {
|
|
1493
|
+
return this.builder.executeBatch(
|
|
1494
|
+
batchName,
|
|
1495
|
+
instructionGetters.map((getter) => () => Promise.resolve(getter())),
|
|
1496
|
+
signers
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Simulate an instruction
|
|
1501
|
+
*/
|
|
1502
|
+
async simulate(instructionName, instructionGetter, signers) {
|
|
1503
|
+
return this.builder.execute(
|
|
1504
|
+
instructionName,
|
|
1505
|
+
() => Promise.resolve(instructionGetter()),
|
|
1506
|
+
signers,
|
|
1507
|
+
{ simulate: true }
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Simulate an instruction (public accessor)
|
|
1512
|
+
*/
|
|
1513
|
+
async simulateInstruction(instructionName, instructionGetter, signers) {
|
|
1514
|
+
return this.simulate(instructionName, instructionGetter, signers);
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Estimate transaction cost
|
|
1518
|
+
*/
|
|
1519
|
+
async estimateCost(instructionGetters) {
|
|
1520
|
+
return this.builder.estimateCost(instructionGetters);
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Get cost estimation for an instruction
|
|
1524
|
+
*/
|
|
1525
|
+
async getCost(instructionName, instructionGetter) {
|
|
1526
|
+
return this.builder.estimateCost([instructionGetter]);
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Get human-readable explanation
|
|
1530
|
+
*/
|
|
1531
|
+
async explain(instructionName, instructionGetter) {
|
|
1532
|
+
return this.builder.explain(instructionName, [instructionGetter]);
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Debug analyze without executing
|
|
1536
|
+
*/
|
|
1537
|
+
async analyze(instructionName, instructionGetter) {
|
|
1538
|
+
return this.builder.debug(instructionName, [instructionGetter]);
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Get decoded account (with optional caching)
|
|
1542
|
+
*/
|
|
1543
|
+
async getAccount(address, decoderImportName) {
|
|
1544
|
+
if (this.cacheManager.isEnabled()) {
|
|
1545
|
+
const cached = this.cacheManager.getAccount(address, this.commitment);
|
|
1546
|
+
if (cached !== void 0) {
|
|
1547
|
+
this.logger?.info(`[Cache HIT] ${address}`);
|
|
1548
|
+
return cached;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
const account = await this.builder.getAccount(address, decoderImportName);
|
|
1552
|
+
if (this.cacheManager.isEnabled() && account !== null) {
|
|
1553
|
+
const slot = 0;
|
|
1554
|
+
this.cacheManager.setAccount(address, account, this.commitment, slot);
|
|
1555
|
+
this.logger?.info(`[Cache SET] ${address}`);
|
|
1556
|
+
}
|
|
1557
|
+
return account;
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* Get multiple decoded accounts (with optional caching)
|
|
1561
|
+
*/
|
|
1562
|
+
async getAccounts(addresses, decoderImportName) {
|
|
1563
|
+
if (!this.cacheManager.isEnabled()) {
|
|
1564
|
+
return this.builder.getAccounts(addresses, decoderImportName);
|
|
1565
|
+
}
|
|
1566
|
+
const results = new Array(addresses.length);
|
|
1567
|
+
const uncachedIndices = [];
|
|
1568
|
+
const uncachedAddresses = [];
|
|
1569
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
1570
|
+
const cached = this.cacheManager.getAccount(addresses[i], this.commitment);
|
|
1571
|
+
if (cached !== void 0) {
|
|
1572
|
+
results[i] = cached;
|
|
1573
|
+
this.logger?.info(`[Cache HIT] ${addresses[i]}`);
|
|
1574
|
+
} else {
|
|
1575
|
+
uncachedIndices.push(i);
|
|
1576
|
+
uncachedAddresses.push(addresses[i]);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
if (uncachedAddresses.length > 0) {
|
|
1580
|
+
this.logger?.info(`[Cache MISS] Fetching ${uncachedAddresses.length}/${addresses.length} accounts`);
|
|
1581
|
+
const fetched = await this.builder.getAccounts(uncachedAddresses, decoderImportName);
|
|
1582
|
+
const slot = 0;
|
|
1583
|
+
for (let i = 0; i < fetched.length; i++) {
|
|
1584
|
+
const originalIndex = uncachedIndices[i];
|
|
1585
|
+
const account = fetched[i];
|
|
1586
|
+
results[originalIndex] = account;
|
|
1587
|
+
if (account !== null) {
|
|
1588
|
+
this.cacheManager.setAccount(uncachedAddresses[i], account, this.commitment, slot);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
return results;
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Get program accounts
|
|
1596
|
+
*/
|
|
1597
|
+
async getProgramAccounts(decoderImportName, filters) {
|
|
1598
|
+
return this.builder.getProgramAccounts(decoderImportName, filters);
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Get program ID
|
|
1602
|
+
*/
|
|
1603
|
+
get programId() {
|
|
1604
|
+
return this.config.programId;
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Get program ID (public accessor)
|
|
1608
|
+
*/
|
|
1609
|
+
getProgramId() {
|
|
1610
|
+
return this.config.programId;
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Get commitment level
|
|
1614
|
+
*/
|
|
1615
|
+
get commitment() {
|
|
1616
|
+
return this.config.commitment ?? "confirmed";
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Get commitment level (public accessor)
|
|
1620
|
+
*/
|
|
1621
|
+
getCommitment() {
|
|
1622
|
+
return this.config.commitment ?? "confirmed";
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* Invalidate cache for specific account
|
|
1626
|
+
*/
|
|
1627
|
+
invalidateCache(address) {
|
|
1628
|
+
this.cacheManager.invalidateAccount(address);
|
|
1629
|
+
this.logger?.info(`[Cache INVALIDATE] ${address}`);
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Clear all caches
|
|
1633
|
+
*/
|
|
1634
|
+
clearCache() {
|
|
1635
|
+
this.cacheManager.clear();
|
|
1636
|
+
this.logger?.info("[Cache CLEAR] All caches cleared");
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Get cache statistics
|
|
1640
|
+
*/
|
|
1641
|
+
getCacheStats() {
|
|
1642
|
+
return this.cacheManager.getStats();
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1646
|
+
// src/utils/ipfs-utils.ts
|
|
1647
|
+
var DEFAULT_IPFS_CONFIG = {
|
|
1648
|
+
provider: {
|
|
1649
|
+
name: "pinata",
|
|
1650
|
+
endpoint: "https://api.pinata.cloud"
|
|
1651
|
+
},
|
|
1652
|
+
gateways: [
|
|
1653
|
+
"https://gateway.pinata.cloud",
|
|
1654
|
+
"https://ipfs.io",
|
|
1655
|
+
"https://cloudflare-ipfs.com",
|
|
1656
|
+
"https://gateway.ipfs.io"
|
|
1657
|
+
],
|
|
1658
|
+
autoPinning: true,
|
|
1659
|
+
sizeThreshold: 800,
|
|
1660
|
+
// 800 bytes threshold to stay under Solana transaction limits
|
|
1661
|
+
maxRetries: 3,
|
|
1662
|
+
retryDelay: 1e3,
|
|
1663
|
+
enableCache: true,
|
|
1664
|
+
cacheTTL: 3e5
|
|
1665
|
+
// 5 minutes
|
|
1666
|
+
};
|
|
1667
|
+
var IPFSUtils = class {
|
|
1668
|
+
client;
|
|
1669
|
+
constructor(config) {
|
|
1670
|
+
this.client = new IPFSClient(config);
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Store agent metadata with automatic IPFS/inline decision
|
|
1674
|
+
*/
|
|
1675
|
+
async storeAgentMetadata(metadata, options) {
|
|
1676
|
+
const metadataJson = JSON.stringify({
|
|
1677
|
+
...metadata,
|
|
1678
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1679
|
+
version: "1.0.0"
|
|
1680
|
+
});
|
|
1681
|
+
return this.client.storeContent(metadataJson, "agent-metadata", {
|
|
1682
|
+
filename: `agent-${metadata.agentId ?? "metadata"}.json`,
|
|
1683
|
+
metadata: {
|
|
1684
|
+
type: "agent-metadata",
|
|
1685
|
+
agentId: metadata.agentId,
|
|
1686
|
+
name: metadata.name
|
|
1687
|
+
},
|
|
1688
|
+
contentType: "application/json",
|
|
1689
|
+
...options
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Store channel message content with automatic IPFS/inline decision
|
|
1694
|
+
*/
|
|
1695
|
+
async storeChannelMessage(message, options) {
|
|
1696
|
+
const messageJson = JSON.stringify({
|
|
1697
|
+
...message,
|
|
1698
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1699
|
+
version: "1.0.0"
|
|
1700
|
+
});
|
|
1701
|
+
return this.client.storeContent(messageJson, "channel-message", {
|
|
1702
|
+
filename: `message-${Date.now()}.json`,
|
|
1703
|
+
metadata: {
|
|
1704
|
+
type: "channel-message",
|
|
1705
|
+
channelId: message.channelId,
|
|
1706
|
+
messageType: message.messageType ?? 0
|
|
1707
|
+
},
|
|
1708
|
+
contentType: "application/json",
|
|
1709
|
+
...options
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* Store file attachments on IPFS
|
|
1714
|
+
*/
|
|
1715
|
+
async storeFileAttachment(fileContent, filename, contentType, options) {
|
|
1716
|
+
const content = typeof fileContent === "string" ? fileContent : new TextDecoder().decode(fileContent);
|
|
1717
|
+
return this.client.storeContent(content, "file-attachment", {
|
|
1718
|
+
filename,
|
|
1719
|
+
contentType,
|
|
1720
|
+
metadata: {
|
|
1721
|
+
type: "file-attachment",
|
|
1722
|
+
originalFilename: filename,
|
|
1723
|
+
mimeType: contentType
|
|
1724
|
+
},
|
|
1725
|
+
...options
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Retrieve and parse agent metadata
|
|
1730
|
+
*/
|
|
1731
|
+
async retrieveAgentMetadata(uri) {
|
|
1732
|
+
const content = await this.client.retrieveContent(uri);
|
|
1733
|
+
const parsed = JSON.parse(content);
|
|
1734
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
1735
|
+
throw new Error("Retrieved agent metadata is not a valid object");
|
|
1736
|
+
}
|
|
1737
|
+
return parsed;
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Retrieve and parse channel message content
|
|
1741
|
+
*/
|
|
1742
|
+
async retrieveChannelMessage(uri) {
|
|
1743
|
+
const content = await this.client.retrieveContent(uri);
|
|
1744
|
+
const parsed = JSON.parse(content);
|
|
1745
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
1746
|
+
throw new Error("Retrieved channel message is not a valid object");
|
|
1747
|
+
}
|
|
1748
|
+
return parsed;
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Batch upload multiple content items
|
|
1752
|
+
*/
|
|
1753
|
+
async batchUpload(items) {
|
|
1754
|
+
const results = await Promise.allSettled(
|
|
1755
|
+
items.map(
|
|
1756
|
+
(item) => this.client.storeContent(item.content, item.type, {
|
|
1757
|
+
filename: item.filename,
|
|
1758
|
+
...item.options
|
|
1759
|
+
})
|
|
1760
|
+
)
|
|
1761
|
+
);
|
|
1762
|
+
return results.map((result) => {
|
|
1763
|
+
if (result.status === "fulfilled") {
|
|
1764
|
+
return {
|
|
1765
|
+
success: true,
|
|
1766
|
+
data: result.value
|
|
1767
|
+
};
|
|
1768
|
+
} else {
|
|
1769
|
+
return {
|
|
1770
|
+
success: false,
|
|
1771
|
+
error: "UPLOAD_FAILED",
|
|
1772
|
+
message: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Pin existing content by hash
|
|
1779
|
+
*/
|
|
1780
|
+
async pinContent(hash) {
|
|
1781
|
+
const result = await this.client.pin(hash);
|
|
1782
|
+
return {
|
|
1783
|
+
...result,
|
|
1784
|
+
data: result.success
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Check if a URI uses IPFS storage
|
|
1789
|
+
*/
|
|
1790
|
+
isIPFSUri(uri) {
|
|
1791
|
+
return uri.startsWith("ipfs://");
|
|
1792
|
+
}
|
|
1793
|
+
/**
|
|
1794
|
+
* Extract IPFS hash from URI
|
|
1795
|
+
*/
|
|
1796
|
+
extractIPFSHash(uri) {
|
|
1797
|
+
if (!this.isIPFSUri(uri)) {
|
|
1798
|
+
return null;
|
|
1799
|
+
}
|
|
1800
|
+
return uri.replace("ipfs://", "");
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Convert IPFS hash to gateway URLs
|
|
1804
|
+
*/
|
|
1805
|
+
getGatewayUrls(hash, gateways) {
|
|
1806
|
+
const defaultGateways = gateways ?? DEFAULT_IPFS_CONFIG.gateways ?? [];
|
|
1807
|
+
return defaultGateways.map((gateway) => `${gateway}/ipfs/${hash}`);
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Validate IPFS hash format
|
|
1811
|
+
*/
|
|
1812
|
+
isValidIPFSHash(hash) {
|
|
1813
|
+
if (hash.length < 44) return false;
|
|
1814
|
+
if (hash.startsWith("Qm") && hash.length === 46) {
|
|
1815
|
+
return /^Qm[A-Za-z0-9]{44}$/.test(hash);
|
|
1816
|
+
}
|
|
1817
|
+
if (hash.length > 46) {
|
|
1818
|
+
return /^[A-Za-z0-9]+$/.test(hash);
|
|
1819
|
+
}
|
|
1820
|
+
return false;
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Calculate content size to determine if IPFS should be used
|
|
1824
|
+
*/
|
|
1825
|
+
shouldUseIPFS(content, threshold) {
|
|
1826
|
+
const size = new TextEncoder().encode(content).length;
|
|
1827
|
+
const sizeThreshold = threshold ?? DEFAULT_IPFS_CONFIG.sizeThreshold ?? 800;
|
|
1828
|
+
return size > sizeThreshold;
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Compress content before IPFS upload using modern compression APIs
|
|
1832
|
+
*/
|
|
1833
|
+
async compressContent(content) {
|
|
1834
|
+
try {
|
|
1835
|
+
if (typeof CompressionStream !== "undefined") {
|
|
1836
|
+
const compressedStream = new CompressionStream("gzip");
|
|
1837
|
+
const writer = compressedStream.writable.getWriter();
|
|
1838
|
+
const reader = compressedStream.readable.getReader();
|
|
1839
|
+
const encoder = new TextEncoder();
|
|
1840
|
+
const input = encoder.encode(content);
|
|
1841
|
+
const writePromise = writer.write(input).then(() => writer.close());
|
|
1842
|
+
const chunks = [];
|
|
1843
|
+
const readCompressed = async () => {
|
|
1844
|
+
const result = await reader.read();
|
|
1845
|
+
const { done, value } = result;
|
|
1846
|
+
if (!done && value) {
|
|
1847
|
+
chunks.push(value);
|
|
1848
|
+
await readCompressed();
|
|
1849
|
+
}
|
|
1850
|
+
};
|
|
1851
|
+
await Promise.all([writePromise, readCompressed()]);
|
|
1852
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
1853
|
+
const compressedData = new Uint8Array(totalLength);
|
|
1854
|
+
let offset = 0;
|
|
1855
|
+
for (const chunk of chunks) {
|
|
1856
|
+
compressedData.set(chunk, offset);
|
|
1857
|
+
offset += chunk.length;
|
|
1858
|
+
}
|
|
1859
|
+
const compressed = Buffer.from(compressedData).toString("base64");
|
|
1860
|
+
return {
|
|
1861
|
+
compressed,
|
|
1862
|
+
algorithm: "gzip"
|
|
1863
|
+
};
|
|
1864
|
+
} else {
|
|
1865
|
+
console.warn("Compression not available in this environment, storing uncompressed");
|
|
1866
|
+
return {
|
|
1867
|
+
compressed: content,
|
|
1868
|
+
algorithm: "none"
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
} catch (error) {
|
|
1872
|
+
console.warn("Compression failed, falling back to uncompressed:", error);
|
|
1873
|
+
return {
|
|
1874
|
+
compressed: content,
|
|
1875
|
+
algorithm: "none"
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Decompress content after IPFS retrieval using modern decompression APIs
|
|
1881
|
+
*/
|
|
1882
|
+
async decompressContent(compressed, algorithm) {
|
|
1883
|
+
if (algorithm === "none") {
|
|
1884
|
+
return compressed;
|
|
1885
|
+
}
|
|
1886
|
+
try {
|
|
1887
|
+
if (algorithm === "gzip") {
|
|
1888
|
+
if (typeof DecompressionStream !== "undefined") {
|
|
1889
|
+
const decompressedStream = new DecompressionStream("gzip");
|
|
1890
|
+
const writer = decompressedStream.writable.getWriter();
|
|
1891
|
+
const reader = decompressedStream.readable.getReader();
|
|
1892
|
+
const compressedData = Buffer.from(compressed, "base64");
|
|
1893
|
+
const writePromise = writer.write(compressedData).then(() => writer.close());
|
|
1894
|
+
const chunks = [];
|
|
1895
|
+
const readDecompressed = async () => {
|
|
1896
|
+
const result = await reader.read();
|
|
1897
|
+
const { done, value } = result;
|
|
1898
|
+
if (!done && value) {
|
|
1899
|
+
chunks.push(value);
|
|
1900
|
+
await readDecompressed();
|
|
1901
|
+
}
|
|
1902
|
+
};
|
|
1903
|
+
await Promise.all([writePromise, readDecompressed()]);
|
|
1904
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
1905
|
+
const decompressedData = new Uint8Array(totalLength);
|
|
1906
|
+
let offset = 0;
|
|
1907
|
+
for (const chunk of chunks) {
|
|
1908
|
+
decompressedData.set(chunk, offset);
|
|
1909
|
+
offset += chunk.length;
|
|
1910
|
+
}
|
|
1911
|
+
const decoder = new TextDecoder();
|
|
1912
|
+
return decoder.decode(decompressedData);
|
|
1913
|
+
} else {
|
|
1914
|
+
console.warn("Decompression not available in this environment, returning compressed data");
|
|
1915
|
+
return compressed;
|
|
1916
|
+
}
|
|
1917
|
+
} else {
|
|
1918
|
+
console.warn(`Unknown compression algorithm: ${algorithm}`);
|
|
1919
|
+
return compressed;
|
|
1920
|
+
}
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
console.error("Decompression failed, returning compressed data:", error);
|
|
1923
|
+
return compressed;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Get client stats
|
|
1928
|
+
*/
|
|
1929
|
+
getStats() {
|
|
1930
|
+
return {
|
|
1931
|
+
cacheStats: this.client.getCacheStats(),
|
|
1932
|
+
config: DEFAULT_IPFS_CONFIG
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Clear client cache
|
|
1937
|
+
*/
|
|
1938
|
+
clearCache() {
|
|
1939
|
+
this.client.clearCache();
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
function createIPFSUtils(config) {
|
|
1943
|
+
const finalConfig = {
|
|
1944
|
+
...DEFAULT_IPFS_CONFIG,
|
|
1945
|
+
...config,
|
|
1946
|
+
provider: {
|
|
1947
|
+
...DEFAULT_IPFS_CONFIG.provider,
|
|
1948
|
+
...config?.provider
|
|
1949
|
+
}
|
|
1950
|
+
};
|
|
1951
|
+
return new IPFSUtils(finalConfig);
|
|
1952
|
+
}
|
|
1953
|
+
function determineStorageMethod(content, options) {
|
|
1954
|
+
if (options?.forceInline) return "inline";
|
|
1955
|
+
if (options?.forceIPFS) return "ipfs";
|
|
1956
|
+
const size = new TextEncoder().encode(content).length;
|
|
1957
|
+
const threshold = options?.sizeThreshold ?? DEFAULT_IPFS_CONFIG.sizeThreshold ?? 800;
|
|
1958
|
+
return size > threshold ? "ipfs" : "inline";
|
|
1959
|
+
}
|
|
1960
|
+
async function createMetadataUri(metadata, ipfsUtils, options) {
|
|
1961
|
+
const metadataJson = JSON.stringify(metadata);
|
|
1962
|
+
const storageMethod = determineStorageMethod(metadataJson, {
|
|
1963
|
+
forceIPFS: options?.forceIPFS
|
|
1964
|
+
});
|
|
1965
|
+
if (storageMethod === "inline" || !ipfsUtils) {
|
|
1966
|
+
const encoded = Buffer.from(metadataJson).toString("base64");
|
|
1967
|
+
return `data:application/json;base64,${encoded}`;
|
|
1968
|
+
} else {
|
|
1969
|
+
const result = await ipfsUtils.client.storeContent(
|
|
1970
|
+
metadataJson,
|
|
1971
|
+
options?.type ?? "custom",
|
|
1972
|
+
{
|
|
1973
|
+
filename: options?.filename,
|
|
1974
|
+
contentType: "application/json"
|
|
1975
|
+
}
|
|
1976
|
+
);
|
|
1977
|
+
return result.uri;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
// src/core/modules/AgentModule.ts
|
|
1982
|
+
var AgentModule = class extends BaseModule {
|
|
1983
|
+
ipfsUtils = null;
|
|
1984
|
+
constructor(config) {
|
|
1985
|
+
super(config);
|
|
1986
|
+
if (config.ipfsConfig) {
|
|
1987
|
+
this.ipfsUtils = createIPFSUtils(config.ipfsConfig);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* Register a new agent
|
|
1992
|
+
*/
|
|
1993
|
+
async register(signer, params) {
|
|
1994
|
+
const pricingModel = params.pricingModel ?? 0 /* Fixed */;
|
|
1995
|
+
const registerGetter = async () => {
|
|
1996
|
+
const agentAccount = await this.deriveAgentPda(params.agentId, signer.address);
|
|
1997
|
+
const ix = await getRegisterAgentInstructionAsync({
|
|
1998
|
+
agentAccount,
|
|
1999
|
+
signer,
|
|
2000
|
+
systemProgram: this.systemProgramId,
|
|
2001
|
+
agentType: params.agentType,
|
|
2002
|
+
name: params.name,
|
|
2003
|
+
description: params.description,
|
|
2004
|
+
metadataUri: params.metadataUri,
|
|
2005
|
+
agentId: params.agentId,
|
|
2006
|
+
pricingModel
|
|
2007
|
+
});
|
|
2008
|
+
return ix;
|
|
2009
|
+
};
|
|
2010
|
+
const heapGetter = () => {
|
|
2011
|
+
const heapData = new Uint8Array(5);
|
|
2012
|
+
heapData[0] = 1;
|
|
2013
|
+
new DataView(heapData.buffer).setUint32(1, 256 * 1024, true);
|
|
2014
|
+
return {
|
|
2015
|
+
programAddress: "ComputeBudget111111111111111111111111111111",
|
|
2016
|
+
accounts: [],
|
|
2017
|
+
data: heapData
|
|
2018
|
+
};
|
|
2019
|
+
};
|
|
2020
|
+
this.debug();
|
|
2021
|
+
if (params.skipSimulation) {
|
|
2022
|
+
console.log("\u{1F680} SKIPPING SIMULATION - Sending transaction directly");
|
|
2023
|
+
return this.builder.executeBatch(
|
|
2024
|
+
"registerAgent",
|
|
2025
|
+
[heapGetter, registerGetter],
|
|
2026
|
+
[signer],
|
|
2027
|
+
{ simulate: false, skipPreflight: true }
|
|
2028
|
+
);
|
|
2029
|
+
}
|
|
2030
|
+
return this.builder.executeBatch(
|
|
2031
|
+
"registerAgent",
|
|
2032
|
+
[heapGetter, registerGetter],
|
|
2033
|
+
[signer]
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
// registerX402Agent method removed - use PayAI integration
|
|
2037
|
+
/**
|
|
2038
|
+
* Register a compressed agent (5000x cheaper)
|
|
2039
|
+
*/
|
|
2040
|
+
async registerCompressed(signer, params) {
|
|
2041
|
+
const instructionGetter = async () => {
|
|
2042
|
+
const treeConfig = params.treeConfig || await this.deriveTreeConfigPda(signer.address);
|
|
2043
|
+
const result = await getRegisterAgentCompressedInstructionAsync({
|
|
2044
|
+
merkleTree: params.merkleTree,
|
|
2045
|
+
treeAuthority: treeConfig,
|
|
2046
|
+
// Map to correct instruction field (it's treeAuthority in IDL?)
|
|
2047
|
+
signer,
|
|
2048
|
+
systemProgram: this.systemProgramId,
|
|
2049
|
+
compressionProgram: this.compressionProgramId,
|
|
2050
|
+
agentType: params.agentType,
|
|
2051
|
+
name: params.name,
|
|
2052
|
+
description: params.description,
|
|
2053
|
+
metadataUri: params.metadataUri,
|
|
2054
|
+
agentId: params.agentId,
|
|
2055
|
+
pricingModel: params.pricingModel ?? 0 /* Fixed */,
|
|
2056
|
+
logWrapper: "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV"
|
|
2057
|
+
// Explicitly provide Noop program
|
|
2058
|
+
});
|
|
2059
|
+
return result;
|
|
2060
|
+
};
|
|
2061
|
+
return this.execute(
|
|
2062
|
+
"registerAgentCompressed",
|
|
2063
|
+
instructionGetter,
|
|
2064
|
+
[signer]
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Update agent metadata
|
|
2069
|
+
*/
|
|
2070
|
+
async update(signer, params) {
|
|
2071
|
+
const instructionGetter = () => {
|
|
2072
|
+
const result = getUpdateAgentInstruction({
|
|
2073
|
+
agentAccount: params.agentAddress,
|
|
2074
|
+
signer,
|
|
2075
|
+
metadataUri: params.metadataUri,
|
|
2076
|
+
agentType: params.agentType,
|
|
2077
|
+
agentId: params.agentId,
|
|
2078
|
+
name: params.name ?? null,
|
|
2079
|
+
description: params.description ?? null,
|
|
2080
|
+
pricingModel: params.pricingModel ?? 0 /* Fixed */
|
|
2081
|
+
});
|
|
2082
|
+
return result;
|
|
2083
|
+
};
|
|
2084
|
+
return this.execute(
|
|
2085
|
+
"updateAgent",
|
|
2086
|
+
instructionGetter,
|
|
2087
|
+
[signer]
|
|
2088
|
+
);
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Verify an agent
|
|
2092
|
+
*/
|
|
2093
|
+
async verify(signer, params) {
|
|
2094
|
+
const instructionGetter = async () => {
|
|
2095
|
+
const result = await getVerifyAgentInstructionAsync({
|
|
2096
|
+
agent: params.agentAddress,
|
|
2097
|
+
verifier: signer,
|
|
2098
|
+
agentPubkey: params.agentPubkey,
|
|
2099
|
+
serviceEndpoint: params.serviceEndpoint,
|
|
2100
|
+
supportedCapabilities: params.supportedCapabilities,
|
|
2101
|
+
verifiedAt: params.verifiedAt
|
|
2102
|
+
});
|
|
2103
|
+
return result;
|
|
2104
|
+
};
|
|
2105
|
+
return this.execute(
|
|
2106
|
+
"verifyAgent",
|
|
2107
|
+
instructionGetter,
|
|
2108
|
+
[signer]
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
/**
|
|
2112
|
+
* Deactivate an agent
|
|
2113
|
+
*/
|
|
2114
|
+
async deactivate(signer, params) {
|
|
2115
|
+
const instructionGetter = () => {
|
|
2116
|
+
const result = getDeactivateAgentInstruction({
|
|
2117
|
+
agentAccount: params.agentAddress,
|
|
2118
|
+
signer,
|
|
2119
|
+
agentId: params.agentId
|
|
2120
|
+
});
|
|
2121
|
+
return result;
|
|
2122
|
+
};
|
|
2123
|
+
return this.execute(
|
|
2124
|
+
"deactivateAgent",
|
|
2125
|
+
instructionGetter,
|
|
2126
|
+
[signer]
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
/**
|
|
2130
|
+
* Activate an agent
|
|
2131
|
+
*/
|
|
2132
|
+
async activate(signer, params) {
|
|
2133
|
+
const instructionGetter = () => {
|
|
2134
|
+
const result = getActivateAgentInstruction({
|
|
2135
|
+
agentAccount: params.agentAddress,
|
|
2136
|
+
signer,
|
|
2137
|
+
agentId: params.agentId
|
|
2138
|
+
});
|
|
2139
|
+
return result;
|
|
2140
|
+
};
|
|
2141
|
+
return this.execute(
|
|
2142
|
+
"activateAgent",
|
|
2143
|
+
instructionGetter,
|
|
2144
|
+
[signer]
|
|
2145
|
+
);
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Get agent account
|
|
2149
|
+
*/
|
|
2150
|
+
async getAgentAccount(address) {
|
|
2151
|
+
return super.getAccount(address, "getAgentDecoder");
|
|
2152
|
+
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Get all agents
|
|
2155
|
+
*/
|
|
2156
|
+
async getAllAgents() {
|
|
2157
|
+
return this.getProgramAccounts("getAgentDecoder");
|
|
2158
|
+
}
|
|
2159
|
+
/**
|
|
2160
|
+
* Get agents by type
|
|
2161
|
+
*/
|
|
2162
|
+
async getAgentsByType(agentType) {
|
|
2163
|
+
const typeBytes = Buffer.alloc(1);
|
|
2164
|
+
typeBytes.writeUInt8(agentType, 0);
|
|
2165
|
+
const filters = [{
|
|
2166
|
+
memcmp: {
|
|
2167
|
+
offset: BigInt(8),
|
|
2168
|
+
// Skip discriminator
|
|
2169
|
+
bytes: typeBytes.toString("base64"),
|
|
2170
|
+
encoding: "base64"
|
|
2171
|
+
}
|
|
2172
|
+
}];
|
|
2173
|
+
return this.getProgramAccounts("getAgentDecoder", filters);
|
|
2174
|
+
}
|
|
2175
|
+
/**
|
|
2176
|
+
* Get user's agents
|
|
2177
|
+
*/
|
|
2178
|
+
async getUserAgents(authority) {
|
|
2179
|
+
const filters = [{
|
|
2180
|
+
memcmp: {
|
|
2181
|
+
offset: BigInt(9),
|
|
2182
|
+
// Skip discriminator + type
|
|
2183
|
+
bytes: authority,
|
|
2184
|
+
encoding: "base58"
|
|
2185
|
+
}
|
|
2186
|
+
}];
|
|
2187
|
+
return this.getProgramAccounts("getAgentDecoder", filters);
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Batch get multiple agent accounts
|
|
2191
|
+
*
|
|
2192
|
+
* Uses efficient batching (100 accounts per RPC call) with optional caching.
|
|
2193
|
+
*
|
|
2194
|
+
* @param addresses - Agent addresses to fetch
|
|
2195
|
+
* @param onProgress - Optional progress callback
|
|
2196
|
+
* @returns Array of agent accounts (null for non-existent)
|
|
2197
|
+
*
|
|
2198
|
+
* @example
|
|
2199
|
+
* ```typescript
|
|
2200
|
+
* const agents = await client.agents.batchGetAgents(
|
|
2201
|
+
* ['agent1...', 'agent2...', 'agent3...'],
|
|
2202
|
+
* (completed, total) => console.log(`${completed}/${total}`)
|
|
2203
|
+
* )
|
|
2204
|
+
* ```
|
|
2205
|
+
*/
|
|
2206
|
+
async batchGetAgents(addresses, onProgress) {
|
|
2207
|
+
return super.getAccounts(addresses, "getAgentDecoder");
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Batch get only existing agent accounts
|
|
2211
|
+
*
|
|
2212
|
+
* Filters out non-existent addresses.
|
|
2213
|
+
*
|
|
2214
|
+
* @param addresses - Agent addresses to fetch
|
|
2215
|
+
* @param onProgress - Optional progress callback
|
|
2216
|
+
* @returns Array of existing agents with their addresses
|
|
2217
|
+
*
|
|
2218
|
+
* @example
|
|
2219
|
+
* ```typescript
|
|
2220
|
+
* const existingAgents = await client.agents.batchGetExistingAgents(['addr1', 'addr2'])
|
|
2221
|
+
* // Returns: [{ address: 'addr1', account: Agent }, ...]
|
|
2222
|
+
* ```
|
|
2223
|
+
*/
|
|
2224
|
+
async batchGetExistingAgents(addresses, onProgress) {
|
|
2225
|
+
const { batchGetExistingAccounts } = await import('./batch-operations-45CQFEID.js');
|
|
2226
|
+
return batchGetExistingAccounts(
|
|
2227
|
+
this.config.rpc,
|
|
2228
|
+
addresses,
|
|
2229
|
+
{ onProgress }
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* Batch get and map agents to a simplified format
|
|
2234
|
+
*
|
|
2235
|
+
* Useful for creating agent summaries or lists.
|
|
2236
|
+
*
|
|
2237
|
+
* @param addresses - Agent addresses to fetch
|
|
2238
|
+
* @param mapper - Transform function
|
|
2239
|
+
* @returns Array of mapped results
|
|
2240
|
+
*
|
|
2241
|
+
* @example
|
|
2242
|
+
* ```typescript
|
|
2243
|
+
* const summaries = await client.agents.batchGetAndMapAgents(
|
|
2244
|
+
* addresses,
|
|
2245
|
+
* (agent, address) => agent ? {
|
|
2246
|
+
* address,
|
|
2247
|
+
* name: agent.name,
|
|
2248
|
+
* type: agent.agentType,
|
|
2249
|
+
* active: agent.isActive
|
|
2250
|
+
* } : null
|
|
2251
|
+
* )
|
|
2252
|
+
* ```
|
|
2253
|
+
*/
|
|
2254
|
+
async batchGetAndMapAgents(addresses, mapper) {
|
|
2255
|
+
const { batchGetAndMap } = await import('./batch-operations-45CQFEID.js');
|
|
2256
|
+
return batchGetAndMap(
|
|
2257
|
+
this.config.rpc,
|
|
2258
|
+
addresses,
|
|
2259
|
+
mapper
|
|
2260
|
+
);
|
|
2261
|
+
}
|
|
2262
|
+
// Helper methods
|
|
2263
|
+
async deriveAgentPda(agentId, owner) {
|
|
2264
|
+
const { deriveAgentPda } = await import('./pda-4KP7CURF.js');
|
|
2265
|
+
const [address] = await deriveAgentPda({ programAddress: this.programId, owner, agentId });
|
|
2266
|
+
return address;
|
|
2267
|
+
}
|
|
2268
|
+
async deriveUserRegistryPda(owner) {
|
|
2269
|
+
const { deriveUserRegistryPda } = await import('./pda-4KP7CURF.js');
|
|
2270
|
+
return deriveUserRegistryPda(this.programId, owner);
|
|
2271
|
+
}
|
|
2272
|
+
async deriveTreeConfigPda(owner) {
|
|
2273
|
+
const { getProgramDerivedAddress: getProgramDerivedAddress2, getAddressEncoder: getAddressEncoder2 } = await import('@solana/addresses');
|
|
2274
|
+
const addressEncoder = getAddressEncoder2();
|
|
2275
|
+
const ownerBytes = addressEncoder.encode(owner);
|
|
2276
|
+
const [pda] = await getProgramDerivedAddress2({
|
|
2277
|
+
programAddress: this.programId,
|
|
2278
|
+
seeds: [
|
|
2279
|
+
new TextEncoder().encode("agent_tree_config"),
|
|
2280
|
+
ownerBytes
|
|
2281
|
+
]
|
|
2282
|
+
});
|
|
2283
|
+
return pda;
|
|
2284
|
+
}
|
|
2285
|
+
get systemProgramId() {
|
|
2286
|
+
return SYSTEM_PROGRAM_ADDRESS;
|
|
2287
|
+
}
|
|
2288
|
+
get compressionProgramId() {
|
|
2289
|
+
return "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK";
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
|
|
2293
|
+
// src/modules/governance/GovernanceModule.ts
|
|
2294
|
+
var GovernanceModule = class extends BaseModule {
|
|
2295
|
+
// =====================================================
|
|
2296
|
+
// DIRECT INSTRUCTION ACCESS
|
|
2297
|
+
// These methods provide direct access to generated instructions
|
|
2298
|
+
// with minimal wrapping for maximum flexibility
|
|
2299
|
+
// =====================================================
|
|
2300
|
+
/**
|
|
2301
|
+
* Get initialize governance proposal instruction
|
|
2302
|
+
*/
|
|
2303
|
+
getInitializeGovernanceProposalInstruction(params) {
|
|
2304
|
+
return getInitializeGovernanceProposalInstructionAsync(params);
|
|
2305
|
+
}
|
|
2306
|
+
// =====================================================
|
|
2307
|
+
// CONVENIENCE METHODS
|
|
2308
|
+
// These methods provide simplified access to common operations
|
|
2309
|
+
// =====================================================
|
|
2310
|
+
/**
|
|
2311
|
+
* Create a new governance proposal
|
|
2312
|
+
*/
|
|
2313
|
+
async createProposal(params) {
|
|
2314
|
+
const proposalAddress = this.deriveProposalPda(params.signer.address, params.title);
|
|
2315
|
+
const instruction = await this.getInitializeGovernanceProposalInstruction({
|
|
2316
|
+
proposal: proposalAddress,
|
|
2317
|
+
proposer: params.signer,
|
|
2318
|
+
title: params.title,
|
|
2319
|
+
description: params.description,
|
|
2320
|
+
proposalType: { kind: params.proposalType, data: {} },
|
|
2321
|
+
executionParams: {
|
|
2322
|
+
instructions: [],
|
|
2323
|
+
accounts: [],
|
|
2324
|
+
targetProgram: proposalAddress,
|
|
2325
|
+
executeAfter: BigInt(params.executionDelay ?? 0)
|
|
2326
|
+
},
|
|
2327
|
+
proposalId: BigInt(Date.now())
|
|
2328
|
+
});
|
|
2329
|
+
return this.execute("createProposal", () => instruction, [params.signer]);
|
|
2330
|
+
}
|
|
2331
|
+
// =====================================================
|
|
2332
|
+
// QUERY OPERATIONS
|
|
2333
|
+
// =====================================================
|
|
2334
|
+
/**
|
|
2335
|
+
* Get governance proposal account
|
|
2336
|
+
*/
|
|
2337
|
+
async getProposal(address) {
|
|
2338
|
+
return super.getAccount(address, "getGovernanceProposalDecoder");
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Get all active proposals
|
|
2342
|
+
*/
|
|
2343
|
+
async getActiveProposals() {
|
|
2344
|
+
return this.getProgramAccounts("getGovernanceProposalDecoder");
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Get proposals by proposer
|
|
2348
|
+
*/
|
|
2349
|
+
async getProposalsByProposer(proposer) {
|
|
2350
|
+
const filters = [{
|
|
2351
|
+
memcmp: {
|
|
2352
|
+
offset: BigInt(8),
|
|
2353
|
+
// Skip discriminator
|
|
2354
|
+
bytes: proposer,
|
|
2355
|
+
encoding: "base58"
|
|
2356
|
+
}
|
|
2357
|
+
}];
|
|
2358
|
+
return this.getProgramAccounts("getGovernanceProposalDecoder", filters);
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Get proposals by status
|
|
2362
|
+
*/
|
|
2363
|
+
async getProposalsByStatus(_status) {
|
|
2364
|
+
const allProposals = await this.getProgramAccounts("getGovernanceProposalDecoder");
|
|
2365
|
+
return allProposals.filter((_proposal) => {
|
|
2366
|
+
return allProposals.length > 0;
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
// =====================================================
|
|
2370
|
+
// HELPER METHODS
|
|
2371
|
+
// =====================================================
|
|
2372
|
+
deriveProposalPda(proposer, title) {
|
|
2373
|
+
return `proposal_${proposer}_${title}`;
|
|
2374
|
+
}
|
|
2375
|
+
};
|
|
2376
|
+
|
|
2377
|
+
// src/modules/multisig/MultisigModule.ts
|
|
2378
|
+
var MultisigModule = class extends BaseModule {
|
|
2379
|
+
/**
|
|
2380
|
+
* Create a new multisig account
|
|
2381
|
+
*/
|
|
2382
|
+
async createMultisig(params) {
|
|
2383
|
+
throw new Error("createMultisig: Instruction not available - requires IDL regeneration");
|
|
2384
|
+
}
|
|
2385
|
+
/**
|
|
2386
|
+
* Create a proposal (Uses Governance Protocol)
|
|
2387
|
+
*
|
|
2388
|
+
* Note: This creates a GovernanceProposal. The proposer must be a signer.
|
|
2389
|
+
*/
|
|
2390
|
+
async createProposal(params) {
|
|
2391
|
+
const instruction = await getInitializeGovernanceProposalInstructionAsync({
|
|
2392
|
+
proposer: params.proposer,
|
|
2393
|
+
proposalId: params.proposalId,
|
|
2394
|
+
title: params.title,
|
|
2395
|
+
description: params.description,
|
|
2396
|
+
proposalType: params.proposalType,
|
|
2397
|
+
executionParams: params.executionParams
|
|
2398
|
+
}, { programAddress: this.programId });
|
|
2399
|
+
return this.execute("createProposal", () => instruction, [params.proposer]);
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Execute a proposal (Note: Approval/voting removed, use protocol_config instead)
|
|
2403
|
+
*/
|
|
2404
|
+
async executeProposal(params) {
|
|
2405
|
+
throw new Error("executeProposal: Use protocol_config instructions for execution");
|
|
2406
|
+
}
|
|
2407
|
+
};
|
|
2408
|
+
|
|
2409
|
+
// src/modules/did/did-types.ts
|
|
2410
|
+
var DID_DOCUMENT_SEED = "did_document";
|
|
2411
|
+
var VerificationMethodType = /* @__PURE__ */ ((VerificationMethodType2) => {
|
|
2412
|
+
VerificationMethodType2["Ed25519VerificationKey2020"] = "Ed25519VerificationKey2020";
|
|
2413
|
+
VerificationMethodType2["X25519KeyAgreementKey2020"] = "X25519KeyAgreementKey2020";
|
|
2414
|
+
VerificationMethodType2["EcdsaSecp256k1VerificationKey2019"] = "EcdsaSecp256k1VerificationKey2019";
|
|
2415
|
+
return VerificationMethodType2;
|
|
2416
|
+
})(VerificationMethodType || {});
|
|
2417
|
+
var VerificationRelationship = /* @__PURE__ */ ((VerificationRelationship2) => {
|
|
2418
|
+
VerificationRelationship2["Authentication"] = "authentication";
|
|
2419
|
+
VerificationRelationship2["AssertionMethod"] = "assertionMethod";
|
|
2420
|
+
VerificationRelationship2["KeyAgreement"] = "keyAgreement";
|
|
2421
|
+
VerificationRelationship2["CapabilityInvocation"] = "capabilityInvocation";
|
|
2422
|
+
VerificationRelationship2["CapabilityDelegation"] = "capabilityDelegation";
|
|
2423
|
+
return VerificationRelationship2;
|
|
2424
|
+
})(VerificationRelationship || {});
|
|
2425
|
+
var ServiceEndpointType = /* @__PURE__ */ ((ServiceEndpointType2) => {
|
|
2426
|
+
ServiceEndpointType2["AIAgentService"] = "AIAgentService";
|
|
2427
|
+
ServiceEndpointType2["DIDCommMessaging"] = "DIDCommMessaging";
|
|
2428
|
+
ServiceEndpointType2["CredentialRepository"] = "CredentialRepository";
|
|
2429
|
+
ServiceEndpointType2["LinkedDomains"] = "LinkedDomains";
|
|
2430
|
+
ServiceEndpointType2["Custom"] = "Custom";
|
|
2431
|
+
return ServiceEndpointType2;
|
|
2432
|
+
})(ServiceEndpointType || {});
|
|
2433
|
+
var DidError = /* @__PURE__ */ ((DidError2) => {
|
|
2434
|
+
DidError2["AlreadyDeactivated"] = "AlreadyDeactivated";
|
|
2435
|
+
DidError2["TooManyVerificationMethods"] = "TooManyVerificationMethods";
|
|
2436
|
+
DidError2["TooManyServiceEndpoints"] = "TooManyServiceEndpoints";
|
|
2437
|
+
DidError2["DuplicateMethodId"] = "DuplicateMethodId";
|
|
2438
|
+
DidError2["DuplicateServiceId"] = "DuplicateServiceId";
|
|
2439
|
+
DidError2["MethodNotFound"] = "MethodNotFound";
|
|
2440
|
+
DidError2["ServiceNotFound"] = "ServiceNotFound";
|
|
2441
|
+
DidError2["InvalidDidFormat"] = "InvalidDidFormat";
|
|
2442
|
+
DidError2["UnauthorizedDidOperation"] = "UnauthorizedDidOperation";
|
|
2443
|
+
DidError2["DidDeactivated"] = "DidDeactivated";
|
|
2444
|
+
return DidError2;
|
|
2445
|
+
})(DidError || {});
|
|
2446
|
+
var DidErrorClass = class extends Error {
|
|
2447
|
+
code;
|
|
2448
|
+
constructor(code, message) {
|
|
2449
|
+
super(message || code);
|
|
2450
|
+
this.name = "DidError";
|
|
2451
|
+
this.code = code;
|
|
2452
|
+
}
|
|
2453
|
+
};
|
|
2454
|
+
async function deriveDidDocumentPda(programId, controller) {
|
|
2455
|
+
const [address, bump] = await getProgramDerivedAddress({
|
|
2456
|
+
programAddress: programId,
|
|
2457
|
+
seeds: [
|
|
2458
|
+
new TextEncoder().encode(DID_DOCUMENT_SEED),
|
|
2459
|
+
getAddressEncoder().encode(controller)
|
|
2460
|
+
]
|
|
2461
|
+
});
|
|
2462
|
+
return [address, bump];
|
|
2463
|
+
}
|
|
2464
|
+
function generateDidString(network, pubkey) {
|
|
2465
|
+
const normalizedNetwork = network === "mainnet" ? "mainnet-beta" : network;
|
|
2466
|
+
return `did:sol:${normalizedNetwork}:${pubkey.toString()}`;
|
|
2467
|
+
}
|
|
2468
|
+
function validateDidString(did) {
|
|
2469
|
+
if (!did.startsWith("did:sol:")) {
|
|
2470
|
+
throw new DidErrorClass(
|
|
2471
|
+
"InvalidDidFormat" /* InvalidDidFormat */,
|
|
2472
|
+
'DID must start with "did:sol:"'
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
const parts = did.split(":");
|
|
2476
|
+
if (parts.length !== 4) {
|
|
2477
|
+
throw new DidErrorClass(
|
|
2478
|
+
"InvalidDidFormat" /* InvalidDidFormat */,
|
|
2479
|
+
'DID must have format "did:sol:network:identifier"'
|
|
2480
|
+
);
|
|
2481
|
+
}
|
|
2482
|
+
const validNetworks = ["mainnet-beta", "devnet", "testnet", "localnet"];
|
|
2483
|
+
if (!validNetworks.includes(parts[2])) {
|
|
2484
|
+
throw new DidErrorClass(
|
|
2485
|
+
"InvalidDidFormat" /* InvalidDidFormat */,
|
|
2486
|
+
`Invalid network "${parts[2]}". Must be one of: ${validNetworks.join(", ")}`
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
try {
|
|
2490
|
+
bs58.decode(parts[3]);
|
|
2491
|
+
} catch {
|
|
2492
|
+
throw new DidErrorClass(
|
|
2493
|
+
"InvalidDidFormat" /* InvalidDidFormat */,
|
|
2494
|
+
"DID identifier must be a valid base58-encoded Solana public key"
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
function parseDidString(did) {
|
|
2499
|
+
validateDidString(did);
|
|
2500
|
+
const parts = did.split(":");
|
|
2501
|
+
return {
|
|
2502
|
+
method: parts[1],
|
|
2503
|
+
network: parts[2],
|
|
2504
|
+
identifier: parts[3]
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
function exportAsW3CDidDocument(didDocument) {
|
|
2508
|
+
const verificationMethod = didDocument.verificationMethods.filter((m) => !m.revoked).map((method) => ({
|
|
2509
|
+
id: `${didDocument.did}#${method.id}`,
|
|
2510
|
+
type: method.methodType,
|
|
2511
|
+
controller: method.controller,
|
|
2512
|
+
publicKeyMultibase: method.publicKeyMultibase
|
|
2513
|
+
}));
|
|
2514
|
+
const authentication = didDocument.verificationMethods.filter((m) => !m.revoked && m.relationships.includes("authentication" /* Authentication */)).map((m) => `${didDocument.did}#${m.id}`);
|
|
2515
|
+
const assertionMethod = didDocument.verificationMethods.filter((m) => !m.revoked && m.relationships.includes("assertionMethod" /* AssertionMethod */)).map((m) => `${didDocument.did}#${m.id}`);
|
|
2516
|
+
const keyAgreement = didDocument.verificationMethods.filter((m) => !m.revoked && m.relationships.includes("keyAgreement" /* KeyAgreement */)).map((m) => `${didDocument.did}#${m.id}`);
|
|
2517
|
+
const capabilityInvocation = didDocument.verificationMethods.filter((m) => !m.revoked && m.relationships.includes("capabilityInvocation" /* CapabilityInvocation */)).map((m) => `${didDocument.did}#${m.id}`);
|
|
2518
|
+
const capabilityDelegation = didDocument.verificationMethods.filter((m) => !m.revoked && m.relationships.includes("capabilityDelegation" /* CapabilityDelegation */)).map((m) => `${didDocument.did}#${m.id}`);
|
|
2519
|
+
const service = didDocument.serviceEndpoints.map((endpoint) => ({
|
|
2520
|
+
id: `${didDocument.did}#${endpoint.id}`,
|
|
2521
|
+
type: endpoint.serviceType,
|
|
2522
|
+
serviceEndpoint: endpoint.serviceEndpoint,
|
|
2523
|
+
description: endpoint.description || void 0
|
|
2524
|
+
}));
|
|
2525
|
+
return {
|
|
2526
|
+
"@context": didDocument.context,
|
|
2527
|
+
id: didDocument.did,
|
|
2528
|
+
controller: didDocument.controller.toString(),
|
|
2529
|
+
verificationMethod,
|
|
2530
|
+
authentication: authentication.length > 0 ? authentication : void 0,
|
|
2531
|
+
assertionMethod: assertionMethod.length > 0 ? assertionMethod : void 0,
|
|
2532
|
+
keyAgreement: keyAgreement.length > 0 ? keyAgreement : void 0,
|
|
2533
|
+
capabilityInvocation: capabilityInvocation.length > 0 ? capabilityInvocation : void 0,
|
|
2534
|
+
capabilityDelegation: capabilityDelegation.length > 0 ? capabilityDelegation : void 0,
|
|
2535
|
+
service: service.length > 0 ? service : void 0,
|
|
2536
|
+
alsoKnownAs: didDocument.alsoKnownAs.length > 0 ? didDocument.alsoKnownAs : void 0
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
function createEd25519VerificationMethod(id, controller, publicKey, relationships = ["authentication" /* Authentication */]) {
|
|
2540
|
+
const publicKeyMultibase = `z${publicKey}`;
|
|
2541
|
+
return {
|
|
2542
|
+
id,
|
|
2543
|
+
methodType: "Ed25519VerificationKey2020" /* Ed25519VerificationKey2020 */,
|
|
2544
|
+
controller,
|
|
2545
|
+
publicKeyMultibase,
|
|
2546
|
+
relationships,
|
|
2547
|
+
createdAt: Math.floor(Date.now() / 1e3),
|
|
2548
|
+
revoked: false
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
function createServiceEndpoint(id, serviceType, serviceEndpoint, description = "") {
|
|
2552
|
+
return {
|
|
2553
|
+
id,
|
|
2554
|
+
serviceType,
|
|
2555
|
+
serviceEndpoint,
|
|
2556
|
+
description
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
function isDidActive(didDocument) {
|
|
2560
|
+
return !didDocument.deactivated;
|
|
2561
|
+
}
|
|
2562
|
+
function getMethodsForRelationship(didDocument, relationship) {
|
|
2563
|
+
return didDocument.verificationMethods.filter(
|
|
2564
|
+
(m) => !m.revoked && m.relationships.includes(relationship)
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
function canPerformAction(didDocument, publicKey, relationship) {
|
|
2568
|
+
if (publicKey.toString() === didDocument.controller.toString()) {
|
|
2569
|
+
return true;
|
|
2570
|
+
}
|
|
2571
|
+
const pubkeyMultibase = `z${publicKey.toString()}`;
|
|
2572
|
+
return didDocument.verificationMethods.some(
|
|
2573
|
+
(method) => !method.revoked && method.publicKeyMultibase === pubkeyMultibase && method.relationships.includes(relationship)
|
|
2574
|
+
);
|
|
2575
|
+
}
|
|
2576
|
+
function didDocumentToJson(didDocument, pretty = true) {
|
|
2577
|
+
const w3cDoc = exportAsW3CDidDocument(didDocument);
|
|
2578
|
+
return pretty ? JSON.stringify(w3cDoc, null, 2) : JSON.stringify(w3cDoc);
|
|
2579
|
+
}
|
|
2580
|
+
function getNetworkFromDid(did) {
|
|
2581
|
+
const parts = parseDidString(did);
|
|
2582
|
+
return parts.network;
|
|
2583
|
+
}
|
|
2584
|
+
function getIdentifierFromDid(did) {
|
|
2585
|
+
const parts = parseDidString(did);
|
|
2586
|
+
return parts.identifier;
|
|
2587
|
+
}
|
|
2588
|
+
var base58Encode = (data) => bs58.encode(data);
|
|
2589
|
+
var DEFAULT_PROGRAM_ID = "GHosT3wqDfNq9bKz8dNEQ1F5mLuN7bKdNYx3Z1111111";
|
|
2590
|
+
var CredentialKind = /* @__PURE__ */ ((CredentialKind2) => {
|
|
2591
|
+
CredentialKind2["AgentIdentity"] = "AgentIdentity";
|
|
2592
|
+
CredentialKind2["ReputationScore"] = "ReputationScore";
|
|
2593
|
+
CredentialKind2["JobCompletion"] = "JobCompletion";
|
|
2594
|
+
CredentialKind2["DelegatedSigner"] = "DelegatedSigner";
|
|
2595
|
+
CredentialKind2["Custom"] = "Custom";
|
|
2596
|
+
return CredentialKind2;
|
|
2597
|
+
})(CredentialKind || {});
|
|
2598
|
+
var CredentialStatus = /* @__PURE__ */ ((CredentialStatus2) => {
|
|
2599
|
+
CredentialStatus2["Pending"] = "Pending";
|
|
2600
|
+
CredentialStatus2["Active"] = "Active";
|
|
2601
|
+
CredentialStatus2["Revoked"] = "Revoked";
|
|
2602
|
+
CredentialStatus2["Expired"] = "Expired";
|
|
2603
|
+
return CredentialStatus2;
|
|
2604
|
+
})(CredentialStatus || {});
|
|
2605
|
+
var CredentialModule = class {
|
|
2606
|
+
programId;
|
|
2607
|
+
didModule;
|
|
2608
|
+
constructor(programId = DEFAULT_PROGRAM_ID, didModule) {
|
|
2609
|
+
this.programId = programId;
|
|
2610
|
+
this.didModule = didModule;
|
|
2611
|
+
}
|
|
2612
|
+
/**
|
|
2613
|
+
* Set the DID module for enhanced DID resolution
|
|
2614
|
+
* This enables automatic DID creation and resolution for credentials
|
|
2615
|
+
*/
|
|
2616
|
+
setDidModule(didModule) {
|
|
2617
|
+
this.didModule = didModule;
|
|
2618
|
+
}
|
|
2619
|
+
// --------------------------------------------------------------------------
|
|
2620
|
+
// Hashing
|
|
2621
|
+
// --------------------------------------------------------------------------
|
|
2622
|
+
hashSubjectData(subjectData) {
|
|
2623
|
+
const json = JSON.stringify(subjectData, Object.keys(subjectData).sort());
|
|
2624
|
+
return sha256(new TextEncoder().encode(json));
|
|
2625
|
+
}
|
|
2626
|
+
generateCredentialId(kind, subject) {
|
|
2627
|
+
const timestamp = Date.now();
|
|
2628
|
+
const input = `${kind}-${subject}-${timestamp}`;
|
|
2629
|
+
const hash = sha256(new TextEncoder().encode(input));
|
|
2630
|
+
const shortHash = base58Encode(hash.slice(0, 8));
|
|
2631
|
+
return `${kind.toLowerCase()}-${shortHash}`;
|
|
2632
|
+
}
|
|
2633
|
+
// --------------------------------------------------------------------------
|
|
2634
|
+
// Export W3C
|
|
2635
|
+
// --------------------------------------------------------------------------
|
|
2636
|
+
exportAsW3CCredential(credential, template, credentialType, subjectData, options) {
|
|
2637
|
+
const network = options?.network || "mainnet";
|
|
2638
|
+
const issuerDid = generateDidString(network, this.programId);
|
|
2639
|
+
const subjectDid = generateDidString(network, credential.subject);
|
|
2640
|
+
const statusId = `solana:${this.programId}:credential:${credential.credentialId}`;
|
|
2641
|
+
return {
|
|
2642
|
+
"@context": [
|
|
2643
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
2644
|
+
"https://w3id.org/security/data-integrity/v2",
|
|
2645
|
+
"https://ghostspeak.io/ns/credentials/v1"
|
|
2646
|
+
],
|
|
2647
|
+
type: ["VerifiableCredential", `GhostSpeak${credentialType.kind}Credential`],
|
|
2648
|
+
id: `urn:ghostspeak:${credential.credentialId}`,
|
|
2649
|
+
issuer: {
|
|
2650
|
+
id: issuerDid,
|
|
2651
|
+
name: "GhostSpeak Protocol"
|
|
2652
|
+
},
|
|
2653
|
+
validFrom: new Date(credential.issuedAt * 1e3).toISOString(),
|
|
2654
|
+
validUntil: credential.expiresAt ? new Date(credential.expiresAt * 1e3).toISOString() : void 0,
|
|
2655
|
+
credentialSubject: {
|
|
2656
|
+
id: subjectDid,
|
|
2657
|
+
...subjectData
|
|
2658
|
+
},
|
|
2659
|
+
credentialSchema: {
|
|
2660
|
+
id: credentialType.schemaUri,
|
|
2661
|
+
type: "JsonSchema"
|
|
2662
|
+
},
|
|
2663
|
+
credentialStatus: {
|
|
2664
|
+
id: statusId,
|
|
2665
|
+
type: "SolanaAccountStatus2025",
|
|
2666
|
+
statusPurpose: "revocation"
|
|
2667
|
+
},
|
|
2668
|
+
relatedResource: options?.includeRelatedResource ? [
|
|
2669
|
+
{
|
|
2670
|
+
id: credential.subjectDataUri,
|
|
2671
|
+
digestMultibase: `mEi${base58Encode(credential.subjectDataHash)}`
|
|
2672
|
+
}
|
|
2673
|
+
] : void 0,
|
|
2674
|
+
proof: {
|
|
2675
|
+
type: "DataIntegrityProof",
|
|
2676
|
+
created: new Date(credential.issuedAt * 1e3).toISOString(),
|
|
2677
|
+
verificationMethod: `${issuerDid}#key-1`,
|
|
2678
|
+
cryptosuite: "eddsa-rdfc-2022",
|
|
2679
|
+
proofPurpose: "assertionMethod",
|
|
2680
|
+
proofValue: base58Encode(credential.signature)
|
|
2681
|
+
}
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2684
|
+
/**
|
|
2685
|
+
* Export credential with DID resolution
|
|
2686
|
+
* Resolves DIDs for issuer and subject to get full DID documents
|
|
2687
|
+
*
|
|
2688
|
+
* @param credential - Credential to export
|
|
2689
|
+
* @param template - Credential template
|
|
2690
|
+
* @param credentialType - Credential type
|
|
2691
|
+
* @param subjectData - Subject data
|
|
2692
|
+
* @param options - Export options
|
|
2693
|
+
* @returns W3C credential with resolved DIDs
|
|
2694
|
+
*/
|
|
2695
|
+
async exportWithDidResolution(credential, template, credentialType, subjectData, options) {
|
|
2696
|
+
if (!this.didModule) {
|
|
2697
|
+
return this.exportAsW3CCredential(
|
|
2698
|
+
credential,
|
|
2699
|
+
template,
|
|
2700
|
+
credentialType,
|
|
2701
|
+
subjectData,
|
|
2702
|
+
options
|
|
2703
|
+
);
|
|
2704
|
+
}
|
|
2705
|
+
const network = options?.network || "mainnet";
|
|
2706
|
+
const issuerDid = generateDidString(network, this.programId);
|
|
2707
|
+
const subjectDid = generateDidString(network, credential.subject);
|
|
2708
|
+
const issuerDidDoc = await this.didModule.resolve(this.programId).catch(() => null);
|
|
2709
|
+
await this.didModule.resolve(credential.subject).catch(() => null);
|
|
2710
|
+
const statusId = `solana:${this.programId}:credential:${credential.credentialId}`;
|
|
2711
|
+
const verificationMethod = issuerDidDoc?.verificationMethods?.[0] ? `${issuerDid}#${issuerDidDoc.verificationMethods[0].id}` : `${issuerDid}#key-1`;
|
|
2712
|
+
return {
|
|
2713
|
+
"@context": [
|
|
2714
|
+
"https://www.w3.org/ns/credentials/v2",
|
|
2715
|
+
"https://w3id.org/security/data-integrity/v2",
|
|
2716
|
+
"https://ghostspeak.io/ns/credentials/v1"
|
|
2717
|
+
],
|
|
2718
|
+
type: ["VerifiableCredential", `GhostSpeak${credentialType.kind}Credential`],
|
|
2719
|
+
id: `urn:ghostspeak:${credential.credentialId}`,
|
|
2720
|
+
issuer: {
|
|
2721
|
+
id: issuerDid,
|
|
2722
|
+
name: "GhostSpeak Protocol"
|
|
2723
|
+
},
|
|
2724
|
+
validFrom: new Date(credential.issuedAt * 1e3).toISOString(),
|
|
2725
|
+
validUntil: credential.expiresAt ? new Date(credential.expiresAt * 1e3).toISOString() : void 0,
|
|
2726
|
+
credentialSubject: {
|
|
2727
|
+
id: subjectDid,
|
|
2728
|
+
...subjectData
|
|
2729
|
+
},
|
|
2730
|
+
credentialSchema: {
|
|
2731
|
+
id: credentialType.schemaUri,
|
|
2732
|
+
type: "JsonSchema"
|
|
2733
|
+
},
|
|
2734
|
+
credentialStatus: {
|
|
2735
|
+
id: statusId,
|
|
2736
|
+
type: "SolanaAccountStatus2025",
|
|
2737
|
+
statusPurpose: "revocation"
|
|
2738
|
+
},
|
|
2739
|
+
relatedResource: options?.includeRelatedResource ? [
|
|
2740
|
+
{
|
|
2741
|
+
id: credential.subjectDataUri,
|
|
2742
|
+
digestMultibase: `mEi${base58Encode(credential.subjectDataHash)}`
|
|
2743
|
+
}
|
|
2744
|
+
] : void 0,
|
|
2745
|
+
proof: {
|
|
2746
|
+
type: "DataIntegrityProof",
|
|
2747
|
+
created: new Date(credential.issuedAt * 1e3).toISOString(),
|
|
2748
|
+
verificationMethod,
|
|
2749
|
+
cryptosuite: "eddsa-rdfc-2022",
|
|
2750
|
+
proofPurpose: "assertionMethod",
|
|
2751
|
+
proofValue: base58Encode(credential.signature)
|
|
2752
|
+
}
|
|
2753
|
+
};
|
|
2754
|
+
}
|
|
2755
|
+
// --------------------------------------------------------------------------
|
|
2756
|
+
// Helpers for Subject Building
|
|
2757
|
+
// --------------------------------------------------------------------------
|
|
2758
|
+
static buildAgentIdentitySubject(params) {
|
|
2759
|
+
return params;
|
|
2760
|
+
}
|
|
2761
|
+
// --------------------------------------------------------------------------
|
|
2762
|
+
// x402 Agent Credential Issuance
|
|
2763
|
+
// --------------------------------------------------------------------------
|
|
2764
|
+
/**
|
|
2765
|
+
* Issue an AgentIdentity credential for a newly registered x402 agent
|
|
2766
|
+
*
|
|
2767
|
+
* This creates a W3C Verifiable Credential that can be:
|
|
2768
|
+
* - Stored on-chain for reputation
|
|
2769
|
+
* - Exported as standard W3C VC JSON
|
|
2770
|
+
* - Verified by third parties
|
|
2771
|
+
*
|
|
2772
|
+
* @example
|
|
2773
|
+
* ```typescript
|
|
2774
|
+
* const result = await credentialModule.issueX402AgentCredential({
|
|
2775
|
+
* agentAddress: 'EPjFWdd5...',
|
|
2776
|
+
* agentId: 'x402-abc123',
|
|
2777
|
+
* owner: 'HN7cAB...',
|
|
2778
|
+
* name: 'My Coinbase Agent',
|
|
2779
|
+
* serviceEndpoint: 'https://my-agent.com/api',
|
|
2780
|
+
* frameworkOrigin: 'coinbase-x402',
|
|
2781
|
+
* x402PaymentAddress: 'EPjFWdd5...',
|
|
2782
|
+
* x402AcceptedTokens: ['EPjFWdd5...'],
|
|
2783
|
+
* x402PricePerCall: '1000000'
|
|
2784
|
+
* })
|
|
2785
|
+
* ```
|
|
2786
|
+
*/
|
|
2787
|
+
issueX402AgentCredential(params) {
|
|
2788
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2789
|
+
const subjectData = {
|
|
2790
|
+
agentId: params.agentId,
|
|
2791
|
+
owner: params.owner,
|
|
2792
|
+
name: params.name,
|
|
2793
|
+
capabilities: params.capabilities || [],
|
|
2794
|
+
serviceEndpoint: params.serviceEndpoint,
|
|
2795
|
+
frameworkOrigin: params.frameworkOrigin,
|
|
2796
|
+
x402Enabled: true,
|
|
2797
|
+
x402PaymentAddress: params.x402PaymentAddress,
|
|
2798
|
+
x402AcceptedTokens: params.x402AcceptedTokens,
|
|
2799
|
+
x402PricePerCall: params.x402PricePerCall,
|
|
2800
|
+
registeredAt: now,
|
|
2801
|
+
verifiedAt: now,
|
|
2802
|
+
type: "external-x402-agent",
|
|
2803
|
+
verificationResponseTimeMs: params.verificationResponseTimeMs
|
|
2804
|
+
};
|
|
2805
|
+
const credentialId = this.generateCredentialId(
|
|
2806
|
+
"AgentIdentity" /* AgentIdentity */,
|
|
2807
|
+
params.agentAddress
|
|
2808
|
+
);
|
|
2809
|
+
const subjectDataHash = this.hashSubjectData(subjectData);
|
|
2810
|
+
const credential = {
|
|
2811
|
+
template: `x402-agent-identity-template`,
|
|
2812
|
+
subject: params.agentAddress,
|
|
2813
|
+
issuer: this.programId,
|
|
2814
|
+
credentialId,
|
|
2815
|
+
subjectDataHash,
|
|
2816
|
+
subjectDataUri: `data:application/json;base64,${Buffer.from(JSON.stringify(subjectData)).toString("base64")}`,
|
|
2817
|
+
status: "Active" /* Active */,
|
|
2818
|
+
signature: subjectDataHash,
|
|
2819
|
+
// Self-signed with hash (for demo; real impl would sign with authority key)
|
|
2820
|
+
issuedAt: now
|
|
2821
|
+
};
|
|
2822
|
+
const credentialType = {
|
|
2823
|
+
authority: this.programId,
|
|
2824
|
+
name: "x402 Agent Identity",
|
|
2825
|
+
kind: "AgentIdentity" /* AgentIdentity */,
|
|
2826
|
+
schemaUri: "https://ghostspeak.io/schemas/x402-agent-identity-v1.json",
|
|
2827
|
+
description: "Verifiable credential for x402-compatible AI agents registered with GhostSpeak",
|
|
2828
|
+
isActive: true,
|
|
2829
|
+
totalIssued: 1,
|
|
2830
|
+
createdAt: now
|
|
2831
|
+
};
|
|
2832
|
+
const template = {
|
|
2833
|
+
credentialType: credentialType.authority,
|
|
2834
|
+
name: "x402 Agent Identity",
|
|
2835
|
+
imageUri: "https://ghostspeak.io/assets/credential-badge-x402.png",
|
|
2836
|
+
issuer: this.programId,
|
|
2837
|
+
isActive: true,
|
|
2838
|
+
totalIssued: 1,
|
|
2839
|
+
createdAt: now
|
|
2840
|
+
};
|
|
2841
|
+
const w3cCredential = this.exportAsW3CCredential(
|
|
2842
|
+
credential,
|
|
2843
|
+
template,
|
|
2844
|
+
credentialType,
|
|
2845
|
+
subjectData,
|
|
2846
|
+
{ network: params.network || "devnet", includeRelatedResource: true }
|
|
2847
|
+
);
|
|
2848
|
+
return {
|
|
2849
|
+
credentialId,
|
|
2850
|
+
credential,
|
|
2851
|
+
w3cCredential,
|
|
2852
|
+
subjectData
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
/**
|
|
2856
|
+
* Export an existing credential to W3C JSON format
|
|
2857
|
+
*/
|
|
2858
|
+
exportCredentialToJSON(credential, subjectData, options) {
|
|
2859
|
+
const template = {
|
|
2860
|
+
credentialType: "x402-agent",
|
|
2861
|
+
name: credential.credentialId.includes("agentidentity") ? "Agent Identity" : "Custom",
|
|
2862
|
+
imageUri: "https://ghostspeak.io/assets/credential-badge.png",
|
|
2863
|
+
issuer: credential.issuer,
|
|
2864
|
+
isActive: true,
|
|
2865
|
+
totalIssued: 1,
|
|
2866
|
+
createdAt: credential.issuedAt
|
|
2867
|
+
};
|
|
2868
|
+
const credentialType = {
|
|
2869
|
+
authority: credential.issuer,
|
|
2870
|
+
name: "GhostSpeak Credential",
|
|
2871
|
+
kind: "AgentIdentity" /* AgentIdentity */,
|
|
2872
|
+
schemaUri: "https://ghostspeak.io/schemas/credential-v1.json",
|
|
2873
|
+
description: "GhostSpeak verifiable credential",
|
|
2874
|
+
isActive: true,
|
|
2875
|
+
totalIssued: 1,
|
|
2876
|
+
createdAt: credential.issuedAt
|
|
2877
|
+
};
|
|
2878
|
+
const w3c = this.exportAsW3CCredential(
|
|
2879
|
+
credential,
|
|
2880
|
+
template,
|
|
2881
|
+
credentialType,
|
|
2882
|
+
subjectData,
|
|
2883
|
+
{ network: options?.network || "devnet", includeRelatedResource: true }
|
|
2884
|
+
);
|
|
2885
|
+
return options?.pretty ? JSON.stringify(w3c, null, 2) : JSON.stringify(w3c);
|
|
2886
|
+
}
|
|
2887
|
+
};
|
|
2888
|
+
var DEFAULT_FACILITATOR_URL = "https://facilitator.payai.network";
|
|
2889
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
2890
|
+
var DEFAULT_RETRY = { attempts: 3, delayMs: 1e3 };
|
|
2891
|
+
function isPaymentRequired(response) {
|
|
2892
|
+
return response.status === 402;
|
|
2893
|
+
}
|
|
2894
|
+
async function extractPaymentRequirements(response) {
|
|
2895
|
+
const body = await response.text();
|
|
2896
|
+
try {
|
|
2897
|
+
const parsed = JSON.parse(body);
|
|
2898
|
+
if (parsed.accepts && Array.isArray(parsed.accepts)) {
|
|
2899
|
+
return parsed.accepts;
|
|
2900
|
+
}
|
|
2901
|
+
if (parsed.paymentRequirements && Array.isArray(parsed.paymentRequirements)) {
|
|
2902
|
+
return parsed.paymentRequirements;
|
|
2903
|
+
}
|
|
2904
|
+
if (parsed.scheme && parsed.payTo) {
|
|
2905
|
+
return [parsed];
|
|
2906
|
+
}
|
|
2907
|
+
} catch {
|
|
2908
|
+
}
|
|
2909
|
+
const paymentHeader = response.headers.get("x-payment-required");
|
|
2910
|
+
if (paymentHeader) {
|
|
2911
|
+
try {
|
|
2912
|
+
return JSON.parse(paymentHeader);
|
|
2913
|
+
} catch {
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
return [];
|
|
2917
|
+
}
|
|
2918
|
+
var PayAIClient = class extends EventEmitter {
|
|
2919
|
+
config;
|
|
2920
|
+
localRecords = [];
|
|
2921
|
+
constructor(config) {
|
|
2922
|
+
super();
|
|
2923
|
+
this.config = {
|
|
2924
|
+
facilitatorUrl: config.facilitatorUrl ?? DEFAULT_FACILITATOR_URL,
|
|
2925
|
+
rpcUrl: config.rpcUrl,
|
|
2926
|
+
wallet: config.wallet,
|
|
2927
|
+
timeout: config.timeout ?? DEFAULT_TIMEOUT,
|
|
2928
|
+
retry: config.retry ?? DEFAULT_RETRY
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
// =====================================================
|
|
2932
|
+
// PUBLIC METHODS
|
|
2933
|
+
// =====================================================
|
|
2934
|
+
/**
|
|
2935
|
+
* Make a fetch request with automatic x402 payment handling
|
|
2936
|
+
*
|
|
2937
|
+
* @param url - The resource URL
|
|
2938
|
+
* @param init - Fetch options
|
|
2939
|
+
* @returns Response from the resource
|
|
2940
|
+
*/
|
|
2941
|
+
async fetch(url, init) {
|
|
2942
|
+
this.emit("request:started", url);
|
|
2943
|
+
const startTime = Date.now();
|
|
2944
|
+
try {
|
|
2945
|
+
let response = await this.fetchWithTimeout(url, init);
|
|
2946
|
+
if (isPaymentRequired(response)) {
|
|
2947
|
+
const requirements = await extractPaymentRequirements(response);
|
|
2948
|
+
this.emit("payment:required", requirements);
|
|
2949
|
+
if (requirements.length === 0) {
|
|
2950
|
+
throw new Error("Payment required but no payment options provided");
|
|
2951
|
+
}
|
|
2952
|
+
const requirement = this.selectPaymentOption(requirements);
|
|
2953
|
+
if (!requirement) {
|
|
2954
|
+
throw new Error("No compatible payment option found");
|
|
2955
|
+
}
|
|
2956
|
+
response = await this.makePaymentAndRetry(url, init ?? {}, requirement);
|
|
2957
|
+
}
|
|
2958
|
+
const durationMs = Date.now() - startTime;
|
|
2959
|
+
this.emit("request:completed", url, response.ok, durationMs);
|
|
2960
|
+
return response;
|
|
2961
|
+
} catch (error) {
|
|
2962
|
+
const durationMs = Date.now() - startTime;
|
|
2963
|
+
this.emit("request:completed", url, false, durationMs);
|
|
2964
|
+
if (error instanceof Error) {
|
|
2965
|
+
this.emit("payment:failed", error);
|
|
2966
|
+
}
|
|
2967
|
+
throw error;
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
/**
|
|
2971
|
+
* Verify a payment through the PayAI facilitator
|
|
2972
|
+
*
|
|
2973
|
+
* @param paymentHeader - The payment header/payload
|
|
2974
|
+
* @param requirement - The payment requirement
|
|
2975
|
+
* @returns Verification result
|
|
2976
|
+
*/
|
|
2977
|
+
async verifyPayment(paymentHeader, requirement) {
|
|
2978
|
+
const response = await this.fetchWithTimeout(
|
|
2979
|
+
`${this.config.facilitatorUrl}/verify`,
|
|
2980
|
+
{
|
|
2981
|
+
method: "POST",
|
|
2982
|
+
headers: { "Content-Type": "application/json" },
|
|
2983
|
+
body: JSON.stringify({
|
|
2984
|
+
paymentHeader,
|
|
2985
|
+
paymentRequirements: requirement
|
|
2986
|
+
})
|
|
2987
|
+
}
|
|
2988
|
+
);
|
|
2989
|
+
return response.json();
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Settle a payment through the PayAI facilitator
|
|
2993
|
+
*
|
|
2994
|
+
* @param paymentHeader - The payment header/payload
|
|
2995
|
+
* @param requirement - The payment requirement
|
|
2996
|
+
* @returns Settlement result
|
|
2997
|
+
*/
|
|
2998
|
+
async settlePayment(paymentHeader, requirement) {
|
|
2999
|
+
const response = await this.fetchWithTimeout(
|
|
3000
|
+
`${this.config.facilitatorUrl}/settle`,
|
|
3001
|
+
{
|
|
3002
|
+
method: "POST",
|
|
3003
|
+
headers: { "Content-Type": "application/json" },
|
|
3004
|
+
body: JSON.stringify({
|
|
3005
|
+
paymentHeader,
|
|
3006
|
+
paymentRequirements: requirement
|
|
3007
|
+
})
|
|
3008
|
+
}
|
|
3009
|
+
);
|
|
3010
|
+
return response.json();
|
|
3011
|
+
}
|
|
3012
|
+
/**
|
|
3013
|
+
* List available resources from the PayAI facilitator
|
|
3014
|
+
*
|
|
3015
|
+
* @param options - Filter options
|
|
3016
|
+
* @returns List of available resources
|
|
3017
|
+
*/
|
|
3018
|
+
async listResources(options) {
|
|
3019
|
+
const params = new URLSearchParams();
|
|
3020
|
+
if (options?.network) params.set("network", options.network);
|
|
3021
|
+
if (options?.capability) params.set("capability", options.capability);
|
|
3022
|
+
if (options?.maxPrice) params.set("maxPrice", options.maxPrice.toString());
|
|
3023
|
+
const response = await this.fetchWithTimeout(
|
|
3024
|
+
`${this.config.facilitatorUrl}/list?${params.toString()}`
|
|
3025
|
+
);
|
|
3026
|
+
return response.json();
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* Get locally tracked reputation records
|
|
3030
|
+
* (For payments made through this client instance)
|
|
3031
|
+
*/
|
|
3032
|
+
getLocalReputationRecords() {
|
|
3033
|
+
return [...this.localRecords];
|
|
3034
|
+
}
|
|
3035
|
+
/**
|
|
3036
|
+
* Clear local reputation records
|
|
3037
|
+
*/
|
|
3038
|
+
clearLocalReputationRecords() {
|
|
3039
|
+
this.localRecords.length = 0;
|
|
3040
|
+
}
|
|
3041
|
+
// =====================================================
|
|
3042
|
+
// PRIVATE METHODS
|
|
3043
|
+
// =====================================================
|
|
3044
|
+
/**
|
|
3045
|
+
* Select the best payment option from requirements
|
|
3046
|
+
*/
|
|
3047
|
+
selectPaymentOption(requirements) {
|
|
3048
|
+
const solanaOption = requirements.find((r) => r.network === "solana");
|
|
3049
|
+
if (solanaOption) return solanaOption;
|
|
3050
|
+
return requirements[0] ?? null;
|
|
3051
|
+
}
|
|
3052
|
+
/**
|
|
3053
|
+
* Make payment and retry the original request
|
|
3054
|
+
*/
|
|
3055
|
+
async makePaymentAndRetry(url, init, requirement) {
|
|
3056
|
+
this.emit("payment:started", requirement);
|
|
3057
|
+
const paymentHeader = await this.createPaymentHeader(requirement);
|
|
3058
|
+
const verification = await this.verifyPayment(paymentHeader, requirement);
|
|
3059
|
+
if (!verification.valid) {
|
|
3060
|
+
throw new Error(`Payment verification failed: ${verification.error ?? "Unknown error"}`);
|
|
3061
|
+
}
|
|
3062
|
+
const settlement = await this.settlePayment(paymentHeader, requirement);
|
|
3063
|
+
if (!settlement.success) {
|
|
3064
|
+
throw new Error(`Payment settlement failed: ${settlement.error ?? "Unknown error"}`);
|
|
3065
|
+
}
|
|
3066
|
+
this.emit("payment:completed", {
|
|
3067
|
+
success: true,
|
|
3068
|
+
transactionSignature: settlement.transaction,
|
|
3069
|
+
paymentId: `pay_${Date.now()}`
|
|
3070
|
+
});
|
|
3071
|
+
const startTime = Date.now();
|
|
3072
|
+
const response = await this.fetchWithTimeout(url, {
|
|
3073
|
+
...init,
|
|
3074
|
+
headers: {
|
|
3075
|
+
...init.headers,
|
|
3076
|
+
"X-Payment": paymentHeader,
|
|
3077
|
+
"X-Payment-Signature": settlement.transaction ?? ""
|
|
3078
|
+
}
|
|
3079
|
+
});
|
|
3080
|
+
const responseTime = Date.now() - startTime;
|
|
3081
|
+
if (settlement.transaction) {
|
|
3082
|
+
this.localRecords.push({
|
|
3083
|
+
agentAddress: requirement.payTo,
|
|
3084
|
+
paymentSignature: settlement.transaction,
|
|
3085
|
+
amount: BigInt(requirement.maxAmountRequired),
|
|
3086
|
+
success: response.ok,
|
|
3087
|
+
responseTimeMs: responseTime,
|
|
3088
|
+
payerAddress: this.config.wallet?.publicKey ?? "unknown",
|
|
3089
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
3090
|
+
network: requirement.network
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
3093
|
+
return response;
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
3096
|
+
* Create a payment header for a requirement
|
|
3097
|
+
* (In production, this would sign a real Solana transaction)
|
|
3098
|
+
*/
|
|
3099
|
+
async createPaymentHeader(requirement) {
|
|
3100
|
+
if (!this.config.wallet) {
|
|
3101
|
+
throw new Error("Wallet not configured for payments");
|
|
3102
|
+
}
|
|
3103
|
+
const paymentData = {
|
|
3104
|
+
version: "1.0",
|
|
3105
|
+
scheme: requirement.scheme,
|
|
3106
|
+
network: requirement.network,
|
|
3107
|
+
payer: this.config.wallet.publicKey,
|
|
3108
|
+
payTo: requirement.payTo,
|
|
3109
|
+
amount: requirement.maxAmountRequired,
|
|
3110
|
+
asset: requirement.asset,
|
|
3111
|
+
resource: requirement.resource,
|
|
3112
|
+
nonce: Date.now().toString(),
|
|
3113
|
+
signature: `mock_sig_${Date.now()}`
|
|
3114
|
+
};
|
|
3115
|
+
return Buffer.from(JSON.stringify(paymentData)).toString("base64");
|
|
3116
|
+
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Fetch with timeout
|
|
3119
|
+
*/
|
|
3120
|
+
async fetchWithTimeout(url, init) {
|
|
3121
|
+
const controller = new AbortController();
|
|
3122
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
3123
|
+
try {
|
|
3124
|
+
const response = await fetch(url, {
|
|
3125
|
+
...init,
|
|
3126
|
+
signal: controller.signal
|
|
3127
|
+
});
|
|
3128
|
+
return response;
|
|
3129
|
+
} finally {
|
|
3130
|
+
clearTimeout(timeoutId);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
};
|
|
3134
|
+
function createPayAIClient(config) {
|
|
3135
|
+
return new PayAIClient(config);
|
|
3136
|
+
}
|
|
3137
|
+
async function payAIFetch(url, config, init) {
|
|
3138
|
+
const client = createPayAIClient(config);
|
|
3139
|
+
return client.fetch(url, init);
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
// src/modules/reputation/ReputationModule.ts
|
|
3143
|
+
init_reputation_tag_engine();
|
|
3144
|
+
var ReputationModule = class extends BaseModule {
|
|
3145
|
+
calculator;
|
|
3146
|
+
tagEngine;
|
|
3147
|
+
constructor(config) {
|
|
3148
|
+
super(config);
|
|
3149
|
+
this.calculator = new ReputationCalculator();
|
|
3150
|
+
this.tagEngine = new ReputationTagEngine();
|
|
3151
|
+
}
|
|
3152
|
+
/**
|
|
3153
|
+
* Calculate reputation change for a job
|
|
3154
|
+
*/
|
|
3155
|
+
calculateReputationChange(currentData, jobPerformance) {
|
|
3156
|
+
return this.calculator.calculateReputation(currentData, jobPerformance);
|
|
3157
|
+
}
|
|
3158
|
+
/**
|
|
3159
|
+
* Get tier name from tier enum
|
|
3160
|
+
*/
|
|
3161
|
+
getTierName(tier) {
|
|
3162
|
+
switch (tier) {
|
|
3163
|
+
case "None" /* None */:
|
|
3164
|
+
return "Unranked";
|
|
3165
|
+
case "Bronze" /* Bronze */:
|
|
3166
|
+
return "Bronze";
|
|
3167
|
+
case "Silver" /* Silver */:
|
|
3168
|
+
return "Silver";
|
|
3169
|
+
case "Gold" /* Gold */:
|
|
3170
|
+
return "Gold";
|
|
3171
|
+
case "Platinum" /* Platinum */:
|
|
3172
|
+
return "Platinum";
|
|
3173
|
+
default:
|
|
3174
|
+
return "Unknown";
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Get tier from score
|
|
3179
|
+
*/
|
|
3180
|
+
getTierFromScore(score) {
|
|
3181
|
+
if (score >= REPUTATION_CONSTANTS.PLATINUM_TIER_THRESHOLD) {
|
|
3182
|
+
return "Platinum" /* Platinum */;
|
|
3183
|
+
} else if (score >= REPUTATION_CONSTANTS.GOLD_TIER_THRESHOLD) {
|
|
3184
|
+
return "Gold" /* Gold */;
|
|
3185
|
+
} else if (score >= REPUTATION_CONSTANTS.SILVER_TIER_THRESHOLD) {
|
|
3186
|
+
return "Silver" /* Silver */;
|
|
3187
|
+
} else if (score >= REPUTATION_CONSTANTS.BRONZE_TIER_THRESHOLD) {
|
|
3188
|
+
return "Bronze" /* Bronze */;
|
|
3189
|
+
}
|
|
3190
|
+
return "None" /* None */;
|
|
3191
|
+
}
|
|
3192
|
+
/**
|
|
3193
|
+
* Get badge display name
|
|
3194
|
+
*/
|
|
3195
|
+
getBadgeName(badge) {
|
|
3196
|
+
switch (badge) {
|
|
3197
|
+
case "FirstJob" /* FirstJob */:
|
|
3198
|
+
return "First Job";
|
|
3199
|
+
case "TenJobs" /* TenJobs */:
|
|
3200
|
+
return "10 Jobs";
|
|
3201
|
+
case "HundredJobs" /* HundredJobs */:
|
|
3202
|
+
return "100 Jobs";
|
|
3203
|
+
case "ThousandJobs" /* ThousandJobs */:
|
|
3204
|
+
return "1000 Jobs";
|
|
3205
|
+
case "PerfectRating" /* PerfectRating */:
|
|
3206
|
+
return "Perfect Rating";
|
|
3207
|
+
case "QuickResponder" /* QuickResponder */:
|
|
3208
|
+
return "Quick Responder";
|
|
3209
|
+
case "DisputeResolver" /* DisputeResolver */:
|
|
3210
|
+
return "Dispute Resolver";
|
|
3211
|
+
case "CategoryExpert" /* CategoryExpert */:
|
|
3212
|
+
return "Category Expert";
|
|
3213
|
+
case "CrossCategoryMaster" /* CrossCategoryMaster */:
|
|
3214
|
+
return "Cross-Category Master";
|
|
3215
|
+
default:
|
|
3216
|
+
return "Unknown Badge";
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
/**
|
|
3220
|
+
* Calculate estimated APY boost from reputation
|
|
3221
|
+
*/
|
|
3222
|
+
calculateApyBoost(score) {
|
|
3223
|
+
return Math.floor(score / 1e3) * 50;
|
|
3224
|
+
}
|
|
3225
|
+
/**
|
|
3226
|
+
* Get reputation tier color for UI
|
|
3227
|
+
*/
|
|
3228
|
+
getTierColor(tier) {
|
|
3229
|
+
switch (tier) {
|
|
3230
|
+
case "Platinum" /* Platinum */:
|
|
3231
|
+
return "#E5E4E2";
|
|
3232
|
+
// Platinum gray
|
|
3233
|
+
case "Gold" /* Gold */:
|
|
3234
|
+
return "#FFD700";
|
|
3235
|
+
// Gold
|
|
3236
|
+
case "Silver" /* Silver */:
|
|
3237
|
+
return "#C0C0C0";
|
|
3238
|
+
// Silver
|
|
3239
|
+
case "Bronze" /* Bronze */:
|
|
3240
|
+
return "#CD7F32";
|
|
3241
|
+
// Bronze
|
|
3242
|
+
default:
|
|
3243
|
+
return "#808080";
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
/**
|
|
3247
|
+
* Create default reputation data for new agents
|
|
3248
|
+
*/
|
|
3249
|
+
createDefaultReputationData(agentAddress) {
|
|
3250
|
+
return {
|
|
3251
|
+
agent: agentAddress,
|
|
3252
|
+
overallScore: 5e3,
|
|
3253
|
+
// Start at 50%
|
|
3254
|
+
totalJobsCompleted: 0,
|
|
3255
|
+
totalJobsFailed: 0,
|
|
3256
|
+
avgResponseTime: 0,
|
|
3257
|
+
disputesAgainst: 0,
|
|
3258
|
+
disputesResolved: 0,
|
|
3259
|
+
lastUpdated: Math.floor(Date.now() / 1e3),
|
|
3260
|
+
categoryReputations: [],
|
|
3261
|
+
badges: [],
|
|
3262
|
+
performanceHistory: [],
|
|
3263
|
+
factors: {
|
|
3264
|
+
completionWeight: 25,
|
|
3265
|
+
qualityWeight: 25,
|
|
3266
|
+
timelinessWeight: 20,
|
|
3267
|
+
satisfactionWeight: 20,
|
|
3268
|
+
disputeWeight: 10
|
|
3269
|
+
}
|
|
3270
|
+
};
|
|
3271
|
+
}
|
|
3272
|
+
/**
|
|
3273
|
+
* Check if agent qualifies for a specific tier
|
|
3274
|
+
*/
|
|
3275
|
+
qualifiesForTier(score, tier) {
|
|
3276
|
+
switch (tier) {
|
|
3277
|
+
case "Platinum" /* Platinum */:
|
|
3278
|
+
return score >= REPUTATION_CONSTANTS.PLATINUM_TIER_THRESHOLD;
|
|
3279
|
+
case "Gold" /* Gold */:
|
|
3280
|
+
return score >= REPUTATION_CONSTANTS.GOLD_TIER_THRESHOLD;
|
|
3281
|
+
case "Silver" /* Silver */:
|
|
3282
|
+
return score >= REPUTATION_CONSTANTS.SILVER_TIER_THRESHOLD;
|
|
3283
|
+
case "Bronze" /* Bronze */:
|
|
3284
|
+
return score >= REPUTATION_CONSTANTS.BRONZE_TIER_THRESHOLD;
|
|
3285
|
+
default:
|
|
3286
|
+
return true;
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
/**
|
|
3290
|
+
* Calculate points needed for next tier
|
|
3291
|
+
*/
|
|
3292
|
+
pointsToNextTier(score) {
|
|
3293
|
+
const currentTier = this.getTierFromScore(score);
|
|
3294
|
+
switch (currentTier) {
|
|
3295
|
+
case "None" /* None */:
|
|
3296
|
+
return { nextTier: "Bronze" /* Bronze */, pointsNeeded: REPUTATION_CONSTANTS.BRONZE_TIER_THRESHOLD - score };
|
|
3297
|
+
case "Bronze" /* Bronze */:
|
|
3298
|
+
return { nextTier: "Silver" /* Silver */, pointsNeeded: REPUTATION_CONSTANTS.SILVER_TIER_THRESHOLD - score };
|
|
3299
|
+
case "Silver" /* Silver */:
|
|
3300
|
+
return { nextTier: "Gold" /* Gold */, pointsNeeded: REPUTATION_CONSTANTS.GOLD_TIER_THRESHOLD - score };
|
|
3301
|
+
case "Gold" /* Gold */:
|
|
3302
|
+
return { nextTier: "Platinum" /* Platinum */, pointsNeeded: REPUTATION_CONSTANTS.PLATINUM_TIER_THRESHOLD - score };
|
|
3303
|
+
case "Platinum" /* Platinum */:
|
|
3304
|
+
return null;
|
|
3305
|
+
// Already at max tier
|
|
3306
|
+
default:
|
|
3307
|
+
return null;
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
// =====================================================
|
|
3311
|
+
// PAYAI INTEGRATION
|
|
3312
|
+
// =====================================================
|
|
3313
|
+
/**
|
|
3314
|
+
* Record a PayAI payment event to update reputation
|
|
3315
|
+
*
|
|
3316
|
+
* Converts PayAI webhook data to JobPerformance format and
|
|
3317
|
+
* calculates reputation change.
|
|
3318
|
+
*
|
|
3319
|
+
* @param record - PayAI reputation record from webhook
|
|
3320
|
+
* @param currentData - Current agent reputation data (fetched from on-chain or cache)
|
|
3321
|
+
* @returns Reputation calculation result
|
|
3322
|
+
*/
|
|
3323
|
+
recordPayAIPayment(record, currentData) {
|
|
3324
|
+
const jobPerformance = this.payAIRecordToJobPerformance(record);
|
|
3325
|
+
return this.calculateReputationChange(currentData, jobPerformance);
|
|
3326
|
+
}
|
|
3327
|
+
/**
|
|
3328
|
+
* Convert a PayAI reputation record to JobPerformance format
|
|
3329
|
+
*
|
|
3330
|
+
* PayAI provides basic payment data, so we derive quality metrics
|
|
3331
|
+
* from response time and success status.
|
|
3332
|
+
*/
|
|
3333
|
+
payAIRecordToJobPerformance(record) {
|
|
3334
|
+
const qualityRating = this.estimateQualityFromResponseTime(record.responseTimeMs);
|
|
3335
|
+
const expectedDuration = this.estimateExpectedDuration(record.amount);
|
|
3336
|
+
return {
|
|
3337
|
+
completed: record.success,
|
|
3338
|
+
qualityRating,
|
|
3339
|
+
expectedDuration,
|
|
3340
|
+
actualDuration: Math.ceil(record.responseTimeMs / 1e3),
|
|
3341
|
+
// Convert to seconds
|
|
3342
|
+
clientSatisfaction: record.success ? qualityRating : 20,
|
|
3343
|
+
// Lower satisfaction on failure
|
|
3344
|
+
hadDispute: false,
|
|
3345
|
+
// PayAI webhook doesn't include dispute info
|
|
3346
|
+
disputeResolvedFavorably: false,
|
|
3347
|
+
category: this.categorizeFromNetwork(record.network),
|
|
3348
|
+
paymentAmount: Number(record.amount) / 1e6
|
|
3349
|
+
// Convert from base units (e.g., USDC 6 decimals)
|
|
3350
|
+
};
|
|
3351
|
+
}
|
|
3352
|
+
/**
|
|
3353
|
+
* Estimate quality rating from response time
|
|
3354
|
+
*
|
|
3355
|
+
* Fast responses (< 500ms) = 100 quality
|
|
3356
|
+
* Average responses (500ms-2s) = 70-90 quality
|
|
3357
|
+
* Slow responses (2s-10s) = 40-70 quality
|
|
3358
|
+
* Very slow responses (> 10s) = 20-40 quality
|
|
3359
|
+
*/
|
|
3360
|
+
estimateQualityFromResponseTime(responseTimeMs) {
|
|
3361
|
+
if (responseTimeMs <= 500) {
|
|
3362
|
+
return 100;
|
|
3363
|
+
} else if (responseTimeMs <= 2e3) {
|
|
3364
|
+
return Math.round(90 - (responseTimeMs - 500) / 1500 * 20);
|
|
3365
|
+
} else if (responseTimeMs <= 1e4) {
|
|
3366
|
+
return Math.round(70 - (responseTimeMs - 2e3) / 8e3 * 30);
|
|
3367
|
+
} else {
|
|
3368
|
+
return Math.max(20, Math.round(40 - (responseTimeMs - 1e4) / 5e4 * 20));
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
/**
|
|
3372
|
+
* Estimate expected duration based on payment amount
|
|
3373
|
+
*
|
|
3374
|
+
* Larger payments = longer expected processing time
|
|
3375
|
+
*/
|
|
3376
|
+
estimateExpectedDuration(amountBaseUnits) {
|
|
3377
|
+
const amountUSDC = Number(amountBaseUnits) / 1e6;
|
|
3378
|
+
return Math.max(2, Math.ceil(2 + amountUSDC * 10));
|
|
3379
|
+
}
|
|
3380
|
+
/**
|
|
3381
|
+
* Categorize payment by network
|
|
3382
|
+
*/
|
|
3383
|
+
categorizeFromNetwork(network) {
|
|
3384
|
+
switch (network) {
|
|
3385
|
+
case "solana":
|
|
3386
|
+
return "ai-services-solana";
|
|
3387
|
+
case "base":
|
|
3388
|
+
return "ai-services-base";
|
|
3389
|
+
case "ethereum":
|
|
3390
|
+
return "ai-services-ethereum";
|
|
3391
|
+
default:
|
|
3392
|
+
return "ai-services";
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
/**
|
|
3396
|
+
* Create a PayAI-compatible performance snapshot
|
|
3397
|
+
* Useful for tracking payment patterns
|
|
3398
|
+
*/
|
|
3399
|
+
createPayAIPerformanceSnapshot(record, reputationResult) {
|
|
3400
|
+
return {
|
|
3401
|
+
timestamp: record.timestamp.getTime(),
|
|
3402
|
+
paymentId: record.paymentSignature,
|
|
3403
|
+
network: record.network,
|
|
3404
|
+
amount: record.amount.toString(),
|
|
3405
|
+
success: record.success,
|
|
3406
|
+
responseTimeMs: record.responseTimeMs,
|
|
3407
|
+
reputationChange: reputationResult.jobScore,
|
|
3408
|
+
newScore: reputationResult.overallScore,
|
|
3409
|
+
tier: reputationResult.tier
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
// =====================================================
|
|
3413
|
+
// REPUTATION TAGGING METHODS
|
|
3414
|
+
// =====================================================
|
|
3415
|
+
/**
|
|
3416
|
+
* Calculate tags for an agent based on metrics
|
|
3417
|
+
*
|
|
3418
|
+
* Automatically evaluates all tag criteria and assigns tags
|
|
3419
|
+
* with appropriate confidence scores.
|
|
3420
|
+
*
|
|
3421
|
+
* @param metrics - Agent reputation metrics
|
|
3422
|
+
* @returns Array of tag scores
|
|
3423
|
+
*/
|
|
3424
|
+
async calculateTagsForAgent(metrics) {
|
|
3425
|
+
return this.tagEngine.calculateTags(metrics);
|
|
3426
|
+
}
|
|
3427
|
+
/**
|
|
3428
|
+
* Get tags by category
|
|
3429
|
+
*
|
|
3430
|
+
* Filters tags to only those in the specified category.
|
|
3431
|
+
*
|
|
3432
|
+
* @param tags - All tag scores
|
|
3433
|
+
* @param category - Category to filter by
|
|
3434
|
+
* @returns Filtered tag scores
|
|
3435
|
+
*/
|
|
3436
|
+
getTagsByCategory(tags, category) {
|
|
3437
|
+
return this.tagEngine.filterTags(tags, { category });
|
|
3438
|
+
}
|
|
3439
|
+
/**
|
|
3440
|
+
* Check if agent has a specific tag
|
|
3441
|
+
*
|
|
3442
|
+
* @param tags - Agent's tag scores
|
|
3443
|
+
* @param tagName - Tag to check for
|
|
3444
|
+
* @returns Whether the tag exists
|
|
3445
|
+
*/
|
|
3446
|
+
hasTag(tags, tagName) {
|
|
3447
|
+
return tags.some((tag) => tag.tagName === tagName);
|
|
3448
|
+
}
|
|
3449
|
+
/**
|
|
3450
|
+
* Get tag confidence score
|
|
3451
|
+
*
|
|
3452
|
+
* @param tags - Agent's tag scores
|
|
3453
|
+
* @param tagName - Tag to check
|
|
3454
|
+
* @returns Confidence score or undefined if tag doesn't exist
|
|
3455
|
+
*/
|
|
3456
|
+
getTagConfidence(tags, tagName) {
|
|
3457
|
+
const tag = tags.find((t) => t.tagName === tagName);
|
|
3458
|
+
return tag?.confidence;
|
|
3459
|
+
}
|
|
3460
|
+
/**
|
|
3461
|
+
* Filter tags by criteria
|
|
3462
|
+
*
|
|
3463
|
+
* @param tags - Tags to filter
|
|
3464
|
+
* @param filters - Filter criteria
|
|
3465
|
+
* @returns Filtered tags
|
|
3466
|
+
*/
|
|
3467
|
+
filterTags(tags, filters) {
|
|
3468
|
+
return this.tagEngine.filterTags(tags, filters);
|
|
3469
|
+
}
|
|
3470
|
+
/**
|
|
3471
|
+
* Apply tag decay based on age
|
|
3472
|
+
*
|
|
3473
|
+
* Reduces confidence scores for old tags and removes stale tags.
|
|
3474
|
+
*
|
|
3475
|
+
* @param tags - Current tag scores
|
|
3476
|
+
* @param currentTimestamp - Current Unix timestamp (optional)
|
|
3477
|
+
* @returns Tags with decay applied
|
|
3478
|
+
*/
|
|
3479
|
+
applyTagDecay(tags, currentTimestamp) {
|
|
3480
|
+
return this.tagEngine.applyTagDecay(tags, currentTimestamp);
|
|
3481
|
+
}
|
|
3482
|
+
/**
|
|
3483
|
+
* Merge new tags with existing tags
|
|
3484
|
+
*
|
|
3485
|
+
* Updates existing tags or adds new ones, preferring higher confidence.
|
|
3486
|
+
*
|
|
3487
|
+
* @param existingTags - Current tags
|
|
3488
|
+
* @param newTags - New tags to merge
|
|
3489
|
+
* @returns Merged tag list
|
|
3490
|
+
*/
|
|
3491
|
+
mergeTags(existingTags, newTags) {
|
|
3492
|
+
return this.tagEngine.mergeTags(existingTags, newTags);
|
|
3493
|
+
}
|
|
3494
|
+
/**
|
|
3495
|
+
* Categorize tags by type
|
|
3496
|
+
*
|
|
3497
|
+
* Organizes tags into skill, behavior, and compliance categories.
|
|
3498
|
+
*
|
|
3499
|
+
* @param tags - Tags to categorize
|
|
3500
|
+
* @returns Categorized tag result
|
|
3501
|
+
*/
|
|
3502
|
+
categorizeTags(tags) {
|
|
3503
|
+
return this.tagEngine.categorizeTags(tags);
|
|
3504
|
+
}
|
|
3505
|
+
/**
|
|
3506
|
+
* Get top N tags by confidence
|
|
3507
|
+
*
|
|
3508
|
+
* @param tags - Tags to sort
|
|
3509
|
+
* @param count - Number of tags to return
|
|
3510
|
+
* @returns Top tags
|
|
3511
|
+
*/
|
|
3512
|
+
getTopTags(tags, count) {
|
|
3513
|
+
return this.tagEngine.getTopTags(tags, count);
|
|
3514
|
+
}
|
|
3515
|
+
/**
|
|
3516
|
+
* Sort tags by confidence (descending)
|
|
3517
|
+
*
|
|
3518
|
+
* @param tags - Tags to sort
|
|
3519
|
+
* @returns Sorted tags
|
|
3520
|
+
*/
|
|
3521
|
+
sortTagsByConfidence(tags) {
|
|
3522
|
+
return this.tagEngine.sortByConfidence(tags);
|
|
3523
|
+
}
|
|
3524
|
+
/**
|
|
3525
|
+
* Sort tags by evidence count (descending)
|
|
3526
|
+
*
|
|
3527
|
+
* @param tags - Tags to sort
|
|
3528
|
+
* @returns Sorted tags
|
|
3529
|
+
*/
|
|
3530
|
+
sortTagsByEvidence(tags) {
|
|
3531
|
+
return this.tagEngine.sortByEvidence(tags);
|
|
3532
|
+
}
|
|
3533
|
+
/**
|
|
3534
|
+
* Sort tags by most recently updated
|
|
3535
|
+
*
|
|
3536
|
+
* @param tags - Tags to sort
|
|
3537
|
+
* @returns Sorted tags
|
|
3538
|
+
*/
|
|
3539
|
+
sortTagsByRecent(tags) {
|
|
3540
|
+
return this.tagEngine.sortByRecent(tags);
|
|
3541
|
+
}
|
|
3542
|
+
/**
|
|
3543
|
+
* Get confidence level description
|
|
3544
|
+
*
|
|
3545
|
+
* @param confidence - Confidence score (0-10000)
|
|
3546
|
+
* @returns Human-readable confidence level
|
|
3547
|
+
*/
|
|
3548
|
+
getConfidenceLevel(confidence) {
|
|
3549
|
+
return this.tagEngine.getConfidenceLevel(confidence);
|
|
3550
|
+
}
|
|
3551
|
+
/**
|
|
3552
|
+
* Validate tag name length
|
|
3553
|
+
*
|
|
3554
|
+
* @param tagName - Tag name to validate
|
|
3555
|
+
* @returns Whether tag name is valid
|
|
3556
|
+
*/
|
|
3557
|
+
validateTagName(tagName) {
|
|
3558
|
+
return this.tagEngine.validateTagName(tagName);
|
|
3559
|
+
}
|
|
3560
|
+
/**
|
|
3561
|
+
* Validate confidence score
|
|
3562
|
+
*
|
|
3563
|
+
* @param confidence - Confidence to validate
|
|
3564
|
+
* @returns Whether confidence is valid (0-10000)
|
|
3565
|
+
*/
|
|
3566
|
+
validateConfidence(confidence) {
|
|
3567
|
+
return this.tagEngine.validateConfidence(confidence);
|
|
3568
|
+
}
|
|
3569
|
+
/**
|
|
3570
|
+
* Convert on-chain ReputationMetrics to TagEngine format
|
|
3571
|
+
*
|
|
3572
|
+
* Helper to convert blockchain data to the format needed for tag calculation.
|
|
3573
|
+
*
|
|
3574
|
+
* @param onChainMetrics - Metrics from blockchain
|
|
3575
|
+
* @returns Metrics in TagEngine format
|
|
3576
|
+
*/
|
|
3577
|
+
convertMetricsForTagging(onChainMetrics) {
|
|
3578
|
+
const avgResponseTime = onChainMetrics.responseTimeCount > 0n ? Number(onChainMetrics.totalResponseTime / onChainMetrics.responseTimeCount) : 0;
|
|
3579
|
+
const totalPayments = onChainMetrics.successfulPayments + onChainMetrics.failedPayments;
|
|
3580
|
+
const successRate = totalPayments > 0n ? Number(onChainMetrics.successfulPayments * 10000n / totalPayments) : 0;
|
|
3581
|
+
const avgRating = onChainMetrics.totalRatingsCount > 0 ? Math.floor(
|
|
3582
|
+
onChainMetrics.totalRating * 100 / (onChainMetrics.totalRatingsCount * 5)
|
|
3583
|
+
) : 0;
|
|
3584
|
+
const disputeResolutionRate = onChainMetrics.totalDisputes > 0 ? Math.floor(onChainMetrics.disputesResolved * 1e4 / onChainMetrics.totalDisputes) : 1e4;
|
|
3585
|
+
return {
|
|
3586
|
+
successfulPayments: onChainMetrics.successfulPayments,
|
|
3587
|
+
failedPayments: onChainMetrics.failedPayments,
|
|
3588
|
+
totalResponseTime: onChainMetrics.totalResponseTime,
|
|
3589
|
+
responseTimeCount: onChainMetrics.responseTimeCount,
|
|
3590
|
+
totalDisputes: onChainMetrics.totalDisputes,
|
|
3591
|
+
disputesResolved: onChainMetrics.disputesResolved,
|
|
3592
|
+
totalRating: onChainMetrics.totalRating,
|
|
3593
|
+
totalRatingsCount: onChainMetrics.totalRatingsCount,
|
|
3594
|
+
createdAt: onChainMetrics.createdAt,
|
|
3595
|
+
updatedAt: onChainMetrics.updatedAt,
|
|
3596
|
+
avgResponseTime,
|
|
3597
|
+
successRate,
|
|
3598
|
+
avgRating,
|
|
3599
|
+
disputeResolutionRate
|
|
3600
|
+
};
|
|
3601
|
+
}
|
|
3602
|
+
};
|
|
3603
|
+
|
|
3604
|
+
// src/modules/staking/StakingModule.ts
|
|
3605
|
+
var StakingModule = class extends BaseModule {
|
|
3606
|
+
/**
|
|
3607
|
+
* Initialize the global staking configuration (admin only)
|
|
3608
|
+
*/
|
|
3609
|
+
async initializeStakingConfig(params) {
|
|
3610
|
+
const instruction = await getInitializeStakingConfigInstructionAsync({
|
|
3611
|
+
authority: params.authority,
|
|
3612
|
+
minStake: params.minStake,
|
|
3613
|
+
treasury: params.treasury
|
|
3614
|
+
}, { programAddress: this.programId });
|
|
3615
|
+
return this.execute("initializeStakingConfig", () => instruction, [params.authority]);
|
|
3616
|
+
}
|
|
3617
|
+
/**
|
|
3618
|
+
* Stake GHOST tokens for an agent
|
|
3619
|
+
*
|
|
3620
|
+
* @param params - Staking parameters
|
|
3621
|
+
* @returns Transaction signature
|
|
3622
|
+
*/
|
|
3623
|
+
async stake(params) {
|
|
3624
|
+
const instruction = await getStakeGhostInstructionAsync({
|
|
3625
|
+
ownerTokenAccount: params.agentTokenAccount,
|
|
3626
|
+
stakingVault: params.stakingVault,
|
|
3627
|
+
stakingConfig: params.stakingConfig,
|
|
3628
|
+
ghostMint: params.ghostMint,
|
|
3629
|
+
owner: params.agentOwner,
|
|
3630
|
+
amount: params.amount,
|
|
3631
|
+
lockDuration: params.lockDuration
|
|
3632
|
+
}, { programAddress: this.programId });
|
|
3633
|
+
return this.execute("stakeGhost", () => instruction, [params.agentOwner]);
|
|
3634
|
+
}
|
|
3635
|
+
/**
|
|
3636
|
+
* Unstake GHOST tokens from an agent
|
|
3637
|
+
*
|
|
3638
|
+
* @param params - Unstaking parameters
|
|
3639
|
+
* @returns Transaction signature
|
|
3640
|
+
*/
|
|
3641
|
+
async unstake(params) {
|
|
3642
|
+
const instruction = await getUnstakeGhostInstructionAsync({
|
|
3643
|
+
stakingAccount: params.stakingAccount,
|
|
3644
|
+
stakingVault: params.stakingVault,
|
|
3645
|
+
ownerTokenAccount: params.agentTokenAccount,
|
|
3646
|
+
owner: params.agentOwner
|
|
3647
|
+
}, { programAddress: this.programId });
|
|
3648
|
+
return this.execute("unstakeGhost", () => instruction, [params.agentOwner]);
|
|
3649
|
+
}
|
|
3650
|
+
/**
|
|
3651
|
+
* Get staking account for an agent
|
|
3652
|
+
*
|
|
3653
|
+
* @param stakingAccountAddress - The staking account address
|
|
3654
|
+
* @returns Staking account data or null if not found
|
|
3655
|
+
*/
|
|
3656
|
+
async getStakingAccount(stakingAccountAddress) {
|
|
3657
|
+
try {
|
|
3658
|
+
return await this.getAccount(stakingAccountAddress, "getStakingAccountDecoder");
|
|
3659
|
+
} catch (error) {
|
|
3660
|
+
console.error("Error fetching staking account:", error);
|
|
3661
|
+
return null;
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
/**
|
|
3665
|
+
* Get the global staking configuration
|
|
3666
|
+
*
|
|
3667
|
+
* @param stakingConfigAddress - The staking config account address
|
|
3668
|
+
* @returns Staking config data or null if not initialized
|
|
3669
|
+
*/
|
|
3670
|
+
async getStakingConfig(stakingConfigAddress) {
|
|
3671
|
+
try {
|
|
3672
|
+
return await this.getAccount(stakingConfigAddress, "getStakingConfigDecoder");
|
|
3673
|
+
} catch (error) {
|
|
3674
|
+
console.error("Error fetching staking config:", error);
|
|
3675
|
+
return null;
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
};
|
|
3679
|
+
|
|
3680
|
+
export { AgentModule, BaseModule, CacheManager, CredentialKind, CredentialModule, CredentialStatus, DEFAULT_IPFS_CONFIG, DidError, DidErrorClass, GovernanceModule, IPFSUtils, InstructionBuilder, MultiSourceAggregator, MultiSourceAggregator_exports, MultisigModule, PayAIClient, ReputationModule, RpcClient, ServiceEndpointType, StakingModule, VerificationMethodType, VerificationRelationship, canPerformAction, createEd25519VerificationMethod, createIPFSUtils, createMetadataUri, createPayAIClient, createServiceEndpoint, deriveDidDocumentPda, determineStorageMethod, didDocumentToJson, exportAsW3CDidDocument, extractPaymentRequirements, generateDidString, getIdentifierFromDid, getMethodsForRelationship, getNetworkFromDid, init_MultiSourceAggregator, isDidActive, isPaymentRequired, parseDidString, payAIFetch, validateDidString };
|
|
3681
|
+
//# sourceMappingURL=chunk-SZGFSCNU.js.map
|
|
3682
|
+
//# sourceMappingURL=chunk-SZGFSCNU.js.map
|