pyre-world-kit 2.0.12 → 3.0.1
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/.prettierrc.json +6 -0
- package/dist/index.d.ts +46 -4
- package/dist/index.js +105 -85
- package/dist/providers/action.provider.d.ts +46 -0
- package/dist/providers/action.provider.js +331 -0
- package/dist/providers/intel.provider.d.ts +29 -0
- package/dist/providers/intel.provider.js +363 -0
- package/dist/providers/mapper.provider.d.ts +197 -0
- package/dist/providers/mapper.provider.js +158 -0
- package/dist/providers/registry.provider.d.ts +25 -0
- package/dist/providers/registry.provider.js +229 -0
- package/dist/providers/state.provider.d.ts +42 -0
- package/dist/providers/state.provider.js +348 -0
- package/dist/pyre_world.json +34 -229
- package/dist/types/action.types.d.ts +41 -0
- package/dist/types/action.types.js +2 -0
- package/dist/types/intel.types.d.ts +20 -0
- package/dist/types/intel.types.js +2 -0
- package/dist/types/mapper.types.d.ts +27 -0
- package/dist/types/mapper.types.js +22 -0
- package/dist/types/registry.types.d.ts +0 -0
- package/dist/types/registry.types.js +1 -0
- package/dist/types/state.types.d.ts +112 -0
- package/dist/types/state.types.js +2 -0
- package/dist/types.d.ts +8 -24
- package/dist/util.d.ts +29 -0
- package/dist/util.js +144 -0
- package/dist/vanity.d.ts +3 -3
- package/dist/vanity.js +18 -15
- package/package.json +4 -2
- package/readme.md +184 -142
- package/src/index.ts +133 -92
- package/src/providers/action.provider.ts +443 -0
- package/src/providers/intel.provider.ts +383 -0
- package/src/providers/mapper.provider.ts +195 -0
- package/src/providers/registry.provider.ts +277 -0
- package/src/providers/state.provider.ts +357 -0
- package/src/pyre_world.json +35 -230
- package/src/types/action.types.ts +76 -0
- package/src/types/intel.types.ts +22 -0
- package/src/types/mapper.types.ts +84 -0
- package/src/types/registry.types.ts +0 -0
- package/src/types/state.types.ts +144 -0
- package/src/types.ts +329 -333
- package/src/util.ts +148 -0
- package/src/vanity.ts +27 -14
- package/tests/test_e2e.ts +339 -172
- package/src/actions.ts +0 -719
- package/src/intel.ts +0 -521
- package/src/mappers.ts +0 -302
- package/src/registry.ts +0 -317
- package/tests/test_devnet_e2e.ts +0 -401
- package/tests/test_sim.ts +0 -458
package/src/intel.ts
DELETED
|
@@ -1,521 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pyre Kit Intel
|
|
3
|
-
*
|
|
4
|
-
* Game-specific utility functions that compose torchsdk reads into
|
|
5
|
-
* strategic intelligence. Agents use these to reason about the world.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Connection, PublicKey } from '@solana/web3.js';
|
|
9
|
-
import {
|
|
10
|
-
getTokens,
|
|
11
|
-
getToken,
|
|
12
|
-
getHolders,
|
|
13
|
-
getMessages,
|
|
14
|
-
getVaultForWallet,
|
|
15
|
-
verifySaid,
|
|
16
|
-
PROGRAM_ID,
|
|
17
|
-
} from 'torchsdk';
|
|
18
|
-
import type { TokenDetail, TokenSummary } from 'torchsdk';
|
|
19
|
-
|
|
20
|
-
import { mapFactionStatus } from './mappers';
|
|
21
|
-
import { isPyreMint, getBondingCurvePda, getTokenTreasuryPda, getTreasuryLockPda } from './vanity';
|
|
22
|
-
import type {
|
|
23
|
-
FactionPower,
|
|
24
|
-
AllianceCluster,
|
|
25
|
-
RivalFaction,
|
|
26
|
-
AgentProfile,
|
|
27
|
-
AgentFactionPosition,
|
|
28
|
-
WorldEvent,
|
|
29
|
-
WorldStats,
|
|
30
|
-
FactionStatus,
|
|
31
|
-
} from './types';
|
|
32
|
-
|
|
33
|
-
// ─── Faction Power & Rankings ──────────────────────────────────────
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Calculate a faction's power score.
|
|
37
|
-
*
|
|
38
|
-
* Score = (market_cap_sol * 0.4) + (members * 0.2) + (war_chest_sol * 0.2)
|
|
39
|
-
* + (rallies * 0.1) + (progress * 0.1)
|
|
40
|
-
*
|
|
41
|
-
* Normalized to make comparison easy. Higher = stronger.
|
|
42
|
-
*/
|
|
43
|
-
export async function getFactionPower(
|
|
44
|
-
connection: Connection,
|
|
45
|
-
mint: string,
|
|
46
|
-
): Promise<FactionPower> {
|
|
47
|
-
const t = await getToken(connection, mint);
|
|
48
|
-
const score = computePowerScore(t);
|
|
49
|
-
return {
|
|
50
|
-
mint: t.mint,
|
|
51
|
-
name: t.name,
|
|
52
|
-
symbol: t.symbol,
|
|
53
|
-
score,
|
|
54
|
-
market_cap_sol: t.market_cap_sol,
|
|
55
|
-
members: t.holders ?? 0,
|
|
56
|
-
war_chest_sol: t.treasury_sol_balance,
|
|
57
|
-
rallies: t.stars,
|
|
58
|
-
progress_percent: t.progress_percent,
|
|
59
|
-
status: mapFactionStatus(t.status),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Ranked leaderboard of all factions by power score.
|
|
65
|
-
*/
|
|
66
|
-
export async function getFactionLeaderboard(
|
|
67
|
-
connection: Connection,
|
|
68
|
-
opts?: { status?: FactionStatus; limit?: number },
|
|
69
|
-
): Promise<FactionPower[]> {
|
|
70
|
-
// Fetch all tokens (up to 1000)
|
|
71
|
-
const statusMap: Record<string, string> = {
|
|
72
|
-
rising: 'bonding',
|
|
73
|
-
ready: 'complete',
|
|
74
|
-
ascended: 'migrated',
|
|
75
|
-
razed: 'reclaimed',
|
|
76
|
-
};
|
|
77
|
-
const sdkStatus = opts?.status ? statusMap[opts.status] as any : 'all';
|
|
78
|
-
// Fetch more than requested to account for non-pyre tokens being filtered out
|
|
79
|
-
const fetchLimit = Math.min((opts?.limit ?? 20) * 3, 100);
|
|
80
|
-
const result = await getTokens(connection, { limit: fetchLimit, status: sdkStatus });
|
|
81
|
-
const pyreFactions = result.tokens.filter(t => isPyreMint(t.mint));
|
|
82
|
-
|
|
83
|
-
const powers: FactionPower[] = pyreFactions.map((t) => ({
|
|
84
|
-
mint: t.mint,
|
|
85
|
-
name: t.name,
|
|
86
|
-
symbol: t.symbol,
|
|
87
|
-
score: computePowerScoreFromSummary(t),
|
|
88
|
-
market_cap_sol: t.market_cap_sol,
|
|
89
|
-
members: t.holders ?? 0,
|
|
90
|
-
war_chest_sol: 0, // Not available in summary
|
|
91
|
-
rallies: 0, // Not available in summary
|
|
92
|
-
progress_percent: t.progress_percent,
|
|
93
|
-
status: mapFactionStatus(t.status),
|
|
94
|
-
}));
|
|
95
|
-
|
|
96
|
-
powers.sort((a, b) => b.score - a.score);
|
|
97
|
-
return powers;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ─── Alliance & Rivalry Detection ──────────────────────────────────
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Detect alliances: factions with shared members.
|
|
104
|
-
*
|
|
105
|
-
* Given a set of faction mints, finds wallets holding multiple faction tokens.
|
|
106
|
-
* Returns alliance clusters showing which factions share members.
|
|
107
|
-
*/
|
|
108
|
-
export async function detectAlliances(
|
|
109
|
-
connection: Connection,
|
|
110
|
-
mints: string[],
|
|
111
|
-
holderLimit = 50,
|
|
112
|
-
): Promise<AllianceCluster[]> {
|
|
113
|
-
// Fetch holders for each faction in parallel
|
|
114
|
-
const holdersPerFaction = await Promise.all(
|
|
115
|
-
mints.map(async (mint) => {
|
|
116
|
-
const result = await getPyreHolders(connection, mint, holderLimit);
|
|
117
|
-
return { mint, holders: new Set(result.holders.map(h => h.address)) };
|
|
118
|
-
})
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
// Find overlapping holders between faction pairs
|
|
122
|
-
const clusters: AllianceCluster[] = [];
|
|
123
|
-
for (let i = 0; i < holdersPerFaction.length; i++) {
|
|
124
|
-
for (let j = i + 1; j < holdersPerFaction.length; j++) {
|
|
125
|
-
const a = holdersPerFaction[i];
|
|
126
|
-
const b = holdersPerFaction[j];
|
|
127
|
-
const shared = [...a.holders].filter(h => b.holders.has(h));
|
|
128
|
-
if (shared.length > 0) {
|
|
129
|
-
const minSize = Math.min(a.holders.size, b.holders.size);
|
|
130
|
-
clusters.push({
|
|
131
|
-
factions: [a.mint, b.mint],
|
|
132
|
-
shared_members: shared.length,
|
|
133
|
-
overlap_percent: minSize > 0 ? (shared.length / minSize) * 100 : 0,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
clusters.sort((a, b) => b.shared_members - a.shared_members);
|
|
140
|
-
return clusters;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Find rival factions based on recent defection activity.
|
|
145
|
-
*
|
|
146
|
-
* Looks at recent sell messages to detect agents who have defected
|
|
147
|
-
* from or to this faction.
|
|
148
|
-
*/
|
|
149
|
-
export async function getFactionRivals(
|
|
150
|
-
connection: Connection,
|
|
151
|
-
mint: string,
|
|
152
|
-
limit = 50,
|
|
153
|
-
): Promise<RivalFaction[]> {
|
|
154
|
-
// Get recent messages (sells include defection messages)
|
|
155
|
-
const msgs = await getMessages(connection, mint, limit);
|
|
156
|
-
const defectors = new Set(msgs.messages.map(m => m.sender));
|
|
157
|
-
|
|
158
|
-
// For each defector, check what other factions they hold
|
|
159
|
-
// This is a heuristic — we look at the messages to find patterns
|
|
160
|
-
// In practice, agents would track this over time
|
|
161
|
-
const rivalCounts = new Map<string, { in: number; out: number }>();
|
|
162
|
-
|
|
163
|
-
// Get all factions to cross-reference
|
|
164
|
-
const allFactions = await getTokens(connection, { limit: 20, sort: 'volume' });
|
|
165
|
-
for (const faction of allFactions.tokens.filter(t => isPyreMint(t.mint))) {
|
|
166
|
-
if (faction.mint === mint) continue;
|
|
167
|
-
const holders = await getPyreHolders(connection, faction.mint, 50);
|
|
168
|
-
const holderAddrs = new Set(holders.holders.map(h => h.address));
|
|
169
|
-
const overlap = [...defectors].filter(d => holderAddrs.has(d)).length;
|
|
170
|
-
if (overlap > 0) {
|
|
171
|
-
rivalCounts.set(faction.mint, {
|
|
172
|
-
in: overlap, // Agents from this faction who also hold rival
|
|
173
|
-
out: overlap,
|
|
174
|
-
...(rivalCounts.get(faction.mint) ?? {}),
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const rivals: RivalFaction[] = [];
|
|
180
|
-
for (const [rivalMint, counts] of rivalCounts) {
|
|
181
|
-
const faction = allFactions.tokens.find(t => t.mint === rivalMint);
|
|
182
|
-
if (faction) {
|
|
183
|
-
rivals.push({
|
|
184
|
-
mint: rivalMint,
|
|
185
|
-
name: faction.name,
|
|
186
|
-
symbol: faction.symbol,
|
|
187
|
-
defections_in: counts.in,
|
|
188
|
-
defections_out: counts.out,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
rivals.sort((a, b) => (b.defections_in + b.defections_out) - (a.defections_in + a.defections_out));
|
|
194
|
-
return rivals;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ─── Agent Intelligence ────────────────────────────────────────────
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Build an aggregate profile for an agent wallet.
|
|
201
|
-
*/
|
|
202
|
-
export async function getAgentProfile(
|
|
203
|
-
connection: Connection,
|
|
204
|
-
wallet: string,
|
|
205
|
-
): Promise<AgentProfile> {
|
|
206
|
-
// Fetch stronghold and SAID verification in parallel
|
|
207
|
-
const [vault, said] = await Promise.all([
|
|
208
|
-
getVaultForWallet(connection, wallet).catch(() => null),
|
|
209
|
-
verifySaid(wallet).catch(() => null),
|
|
210
|
-
]);
|
|
211
|
-
|
|
212
|
-
// Get factions this agent holds — requires scanning
|
|
213
|
-
// For now, check top factions for this holder
|
|
214
|
-
const factions = await getAgentFactions(connection, wallet);
|
|
215
|
-
|
|
216
|
-
// Find factions this wallet created
|
|
217
|
-
const allFactions = await getTokens(connection, { limit: 100 });
|
|
218
|
-
const founded = allFactions.tokens.filter(t => isPyreMint(t.mint))
|
|
219
|
-
.filter(t => t.mint) // TokenSummary doesn't have creator, so we skip for now
|
|
220
|
-
.map(t => t.mint);
|
|
221
|
-
|
|
222
|
-
const totalValue = factions.reduce((sum, f) => sum + f.value_sol, 0);
|
|
223
|
-
|
|
224
|
-
return {
|
|
225
|
-
wallet,
|
|
226
|
-
stronghold: vault ? {
|
|
227
|
-
address: vault.address,
|
|
228
|
-
creator: vault.creator,
|
|
229
|
-
authority: vault.authority,
|
|
230
|
-
sol_balance: vault.sol_balance,
|
|
231
|
-
total_deposited: vault.total_deposited,
|
|
232
|
-
total_withdrawn: vault.total_withdrawn,
|
|
233
|
-
total_spent: vault.total_spent,
|
|
234
|
-
total_received: vault.total_received,
|
|
235
|
-
linked_agents: vault.linked_wallets,
|
|
236
|
-
created_at: vault.created_at,
|
|
237
|
-
} : null,
|
|
238
|
-
factions_joined: factions,
|
|
239
|
-
factions_founded: [], // Would need per-token creator lookup
|
|
240
|
-
said_verification: said,
|
|
241
|
-
total_value_sol: totalValue + (vault?.sol_balance ?? 0),
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* List all factions an agent holds tokens in.
|
|
247
|
-
*
|
|
248
|
-
* Scans both the wallet's and vault's Token-2022 accounts, merging balances.
|
|
249
|
-
* Agents may hold tokens directly (no vault) or via stronghold (vault).
|
|
250
|
-
*/
|
|
251
|
-
export async function getAgentFactions(
|
|
252
|
-
connection: Connection,
|
|
253
|
-
wallet: string,
|
|
254
|
-
factionLimit = 50,
|
|
255
|
-
): Promise<AgentFactionPosition[]> {
|
|
256
|
-
const { TOKEN_2022_PROGRAM_ID } = await import('@solana/spl-token');
|
|
257
|
-
const walletPk = new PublicKey(wallet);
|
|
258
|
-
|
|
259
|
-
// Scan wallet token accounts
|
|
260
|
-
const walletAccounts = await connection.getParsedTokenAccountsByOwner(walletPk, {
|
|
261
|
-
programId: TOKEN_2022_PROGRAM_ID,
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// Scan vault token accounts if a vault exists
|
|
265
|
-
let vaultAccounts: typeof walletAccounts = { context: walletAccounts.context, value: [] };
|
|
266
|
-
try {
|
|
267
|
-
const vault = await getVaultForWallet(connection, wallet);
|
|
268
|
-
if (!vault) throw new Error('no vault');
|
|
269
|
-
const vaultPk = new PublicKey(vault.address);
|
|
270
|
-
vaultAccounts = await connection.getParsedTokenAccountsByOwner(vaultPk, {
|
|
271
|
-
programId: TOKEN_2022_PROGRAM_ID,
|
|
272
|
-
});
|
|
273
|
-
} catch {}
|
|
274
|
-
|
|
275
|
-
// Merge balances from both sources (wallet + vault)
|
|
276
|
-
const balanceMap = new Map<string, number>();
|
|
277
|
-
for (const a of [...walletAccounts.value, ...vaultAccounts.value]) {
|
|
278
|
-
const mint = a.account.data.parsed.info.mint as string;
|
|
279
|
-
const balance = Number(a.account.data.parsed.info.tokenAmount.uiAmount ?? 0);
|
|
280
|
-
if (balance > 0 && isPyreMint(mint)) {
|
|
281
|
-
balanceMap.set(mint, (balanceMap.get(mint) ?? 0) + balance);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (balanceMap.size === 0) return [];
|
|
286
|
-
|
|
287
|
-
// Fetch faction metadata for held mints
|
|
288
|
-
const allFactions = await getTokens(connection, { limit: factionLimit });
|
|
289
|
-
const factionMap = new Map(
|
|
290
|
-
allFactions.tokens.filter(t => isPyreMint(t.mint)).map(t => [t.mint, t])
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
const positions: AgentFactionPosition[] = [];
|
|
294
|
-
for (const [mint, balance] of balanceMap) {
|
|
295
|
-
const faction = factionMap.get(mint);
|
|
296
|
-
if (!faction) continue;
|
|
297
|
-
|
|
298
|
-
// balance / 1B total supply
|
|
299
|
-
const percentage = (balance / 1_000_000_000) * 100;
|
|
300
|
-
|
|
301
|
-
positions.push({
|
|
302
|
-
mint,
|
|
303
|
-
name: faction.name,
|
|
304
|
-
symbol: faction.symbol,
|
|
305
|
-
balance,
|
|
306
|
-
percentage,
|
|
307
|
-
value_sol: balance * faction.price_sol,
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
positions.sort((a, b) => b.value_sol - a.value_sol);
|
|
312
|
-
return positions;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// ─── World State ───────────────────────────────────────────────────
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Aggregated recent activity across ALL factions.
|
|
319
|
-
*
|
|
320
|
-
* The "Bloomberg terminal" feed — launches, joins, defections, rallies.
|
|
321
|
-
*/
|
|
322
|
-
export async function getWorldFeed(
|
|
323
|
-
connection: Connection,
|
|
324
|
-
opts?: { limit?: number; factionLimit?: number },
|
|
325
|
-
): Promise<WorldEvent[]> {
|
|
326
|
-
const factionLimit = opts?.factionLimit ?? 20;
|
|
327
|
-
const msgLimit = opts?.limit ?? 5;
|
|
328
|
-
|
|
329
|
-
const allFactions = await getTokens(connection, { limit: factionLimit, sort: 'newest' });
|
|
330
|
-
const events: WorldEvent[] = [];
|
|
331
|
-
|
|
332
|
-
// Add launch events for each faction
|
|
333
|
-
for (const faction of allFactions.tokens.filter(t => isPyreMint(t.mint))) {
|
|
334
|
-
events.push({
|
|
335
|
-
type: 'launch',
|
|
336
|
-
faction_mint: faction.mint,
|
|
337
|
-
faction_name: faction.name,
|
|
338
|
-
timestamp: faction.created_at,
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
// Map status to events
|
|
342
|
-
if (faction.status === 'migrated') {
|
|
343
|
-
events.push({
|
|
344
|
-
type: 'ascend',
|
|
345
|
-
faction_mint: faction.mint,
|
|
346
|
-
faction_name: faction.name,
|
|
347
|
-
timestamp: faction.last_activity_at,
|
|
348
|
-
});
|
|
349
|
-
} else if (faction.status === 'reclaimed') {
|
|
350
|
-
events.push({
|
|
351
|
-
type: 'raze',
|
|
352
|
-
faction_mint: faction.mint,
|
|
353
|
-
faction_name: faction.name,
|
|
354
|
-
timestamp: faction.last_activity_at,
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Get recent messages from top factions (messages = trade activity)
|
|
360
|
-
const topFactions = allFactions.tokens.slice(0, 10);
|
|
361
|
-
await Promise.all(
|
|
362
|
-
topFactions.map(async (faction) => {
|
|
363
|
-
try {
|
|
364
|
-
const msgs = await getMessages(connection, faction.mint, msgLimit);
|
|
365
|
-
for (const msg of msgs.messages) {
|
|
366
|
-
events.push({
|
|
367
|
-
type: 'join', // Messages are trade-bundled, most are buys
|
|
368
|
-
faction_mint: faction.mint,
|
|
369
|
-
faction_name: faction.name,
|
|
370
|
-
agent: msg.sender,
|
|
371
|
-
timestamp: msg.timestamp,
|
|
372
|
-
signature: msg.signature,
|
|
373
|
-
message: msg.memo,
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
} catch {
|
|
377
|
-
// Skip factions with no messages
|
|
378
|
-
}
|
|
379
|
-
})
|
|
380
|
-
);
|
|
381
|
-
|
|
382
|
-
events.sort((a, b) => b.timestamp - a.timestamp);
|
|
383
|
-
return events.slice(0, opts?.limit ?? 100);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Global stats: total factions, total agents, total SOL locked.
|
|
388
|
-
*/
|
|
389
|
-
export async function getWorldStats(
|
|
390
|
-
connection: Connection,
|
|
391
|
-
): Promise<WorldStats> {
|
|
392
|
-
const all = await getTokens(connection, { limit: 200, status: 'all' });
|
|
393
|
-
const pyreAll = all.tokens.filter(t => isPyreMint(t.mint));
|
|
394
|
-
const pyreRising = pyreAll.filter(t => t.status === 'bonding');
|
|
395
|
-
const pyreAscended = pyreAll.filter(t => t.status === 'migrated');
|
|
396
|
-
const allFactions = [...pyreRising, ...pyreAscended];
|
|
397
|
-
const totalSolLocked = allFactions.reduce((sum, t) => sum + t.market_cap_sol, 0);
|
|
398
|
-
|
|
399
|
-
// Find most powerful
|
|
400
|
-
let mostPowerful: FactionPower | null = null;
|
|
401
|
-
let maxScore = 0;
|
|
402
|
-
for (const t of allFactions) {
|
|
403
|
-
const score = computePowerScoreFromSummary(t);
|
|
404
|
-
if (score > maxScore) {
|
|
405
|
-
maxScore = score;
|
|
406
|
-
mostPowerful = {
|
|
407
|
-
mint: t.mint,
|
|
408
|
-
name: t.name,
|
|
409
|
-
symbol: t.symbol,
|
|
410
|
-
score,
|
|
411
|
-
market_cap_sol: t.market_cap_sol,
|
|
412
|
-
members: t.holders ?? 0,
|
|
413
|
-
war_chest_sol: 0,
|
|
414
|
-
rallies: 0,
|
|
415
|
-
progress_percent: t.progress_percent,
|
|
416
|
-
status: mapFactionStatus(t.status),
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
return {
|
|
422
|
-
total_factions: pyreAll.length,
|
|
423
|
-
rising_factions: pyreRising.length,
|
|
424
|
-
ascended_factions: pyreAscended.length,
|
|
425
|
-
total_sol_locked: totalSolLocked,
|
|
426
|
-
most_powerful: mostPowerful,
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/** Fetch holders excluding program-owned accounts (bonding curve, treasury, treasury lock) */
|
|
431
|
-
async function getPyreHolders(connection: Connection, mint: string, limit: number) {
|
|
432
|
-
const mintPk = new PublicKey(mint);
|
|
433
|
-
const [bondingCurve] = getBondingCurvePda(mintPk);
|
|
434
|
-
const [treasury] = getTokenTreasuryPda(mintPk);
|
|
435
|
-
const [treasuryLock] = getTreasuryLockPda(mintPk);
|
|
436
|
-
const excluded = new Set([bondingCurve.toString(), treasury.toString(), treasuryLock.toString()]);
|
|
437
|
-
const result = await getHolders(connection, mint, limit + 5);
|
|
438
|
-
result.holders = result.holders.filter(h => !excluded.has(h.address)).slice(0, limit);
|
|
439
|
-
return result;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// ─── Vault P&L ────────────────────────────────────────────────────
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Get total SOL balance in lamports for an agent: vault + wallet.
|
|
446
|
-
* Checks vault first (where most SOL flows), falls back to wallet if no vault.
|
|
447
|
-
* Returns the combined balance so P&L captures all SOL movement.
|
|
448
|
-
*/
|
|
449
|
-
export async function getAgentSolLamports(
|
|
450
|
-
connection: Connection,
|
|
451
|
-
wallet: string,
|
|
452
|
-
): Promise<number> {
|
|
453
|
-
const walletPk = new PublicKey(wallet);
|
|
454
|
-
let total = 0;
|
|
455
|
-
try {
|
|
456
|
-
total += await connection.getBalance(walletPk);
|
|
457
|
-
} catch {}
|
|
458
|
-
try {
|
|
459
|
-
const vault = await getVaultForWallet(connection, wallet);
|
|
460
|
-
if (vault) total += Math.round(vault.sol_balance * 1e9);
|
|
461
|
-
} catch {}
|
|
462
|
-
return total;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Start tracking P&L for a single action/tick.
|
|
467
|
-
*
|
|
468
|
-
* Snapshots wallet + vault SOL before the action. Call `finish()` after
|
|
469
|
-
* to get the diff. Covers both vault and wallet flows so no SOL is missed.
|
|
470
|
-
*
|
|
471
|
-
* Usage:
|
|
472
|
-
* const pnl = await startVaultPnlTracker(connection, wallet)
|
|
473
|
-
* // ... do action ...
|
|
474
|
-
* const { spent, received } = await pnl.finish()
|
|
475
|
-
*/
|
|
476
|
-
export async function startVaultPnlTracker(
|
|
477
|
-
connection: Connection,
|
|
478
|
-
wallet: string,
|
|
479
|
-
): Promise<{ finish: () => Promise<{ spent: number; received: number }> }> {
|
|
480
|
-
const before = await getAgentSolLamports(connection, wallet);
|
|
481
|
-
return {
|
|
482
|
-
async finish() {
|
|
483
|
-
const after = await getAgentSolLamports(connection, wallet);
|
|
484
|
-
const diff = after - before;
|
|
485
|
-
return {
|
|
486
|
-
spent: diff < 0 ? Math.abs(diff) : 0,
|
|
487
|
-
received: diff > 0 ? diff : 0,
|
|
488
|
-
};
|
|
489
|
-
},
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// ─── Internal Helpers ──────────────────────────────────────────────
|
|
494
|
-
|
|
495
|
-
function computePowerScore(t: TokenDetail): number {
|
|
496
|
-
const mcWeight = 0.4;
|
|
497
|
-
const memberWeight = 0.2;
|
|
498
|
-
const chestWeight = 0.2;
|
|
499
|
-
const rallyWeight = 0.1;
|
|
500
|
-
const progressWeight = 0.1;
|
|
501
|
-
|
|
502
|
-
return (
|
|
503
|
-
(t.market_cap_sol * mcWeight) +
|
|
504
|
-
((t.holders ?? 0) * memberWeight) +
|
|
505
|
-
(t.treasury_sol_balance * chestWeight) +
|
|
506
|
-
(t.stars * rallyWeight) +
|
|
507
|
-
(t.progress_percent * progressWeight)
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function computePowerScoreFromSummary(t: TokenSummary): number {
|
|
512
|
-
const mcWeight = 0.4;
|
|
513
|
-
const memberWeight = 0.2;
|
|
514
|
-
const progressWeight = 0.1;
|
|
515
|
-
|
|
516
|
-
return (
|
|
517
|
-
(t.market_cap_sol * mcWeight) +
|
|
518
|
-
((t.holders ?? 0) * memberWeight) +
|
|
519
|
-
(t.progress_percent * progressWeight)
|
|
520
|
-
);
|
|
521
|
-
}
|