pyre-world-kit 2.0.11 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc.json +6 -0
- package/dist/actions.js +16 -0
- package/dist/index.d.ts +38 -4
- package/dist/index.js +100 -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 +134 -122
- package/src/index.ts +127 -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 -703
- 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/tests/test_sim.ts
DELETED
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pyre Kit Faction Warfare Simulation
|
|
3
|
-
*
|
|
4
|
-
* Spins up 500 agent wallets and runs a random walk simulation
|
|
5
|
-
* of faction warfare: launching, joining, defecting, rallying.
|
|
6
|
-
*
|
|
7
|
-
* Prerequisites:
|
|
8
|
-
* surfpool start --network mainnet --no-tui
|
|
9
|
-
*
|
|
10
|
-
* Run:
|
|
11
|
-
* npx tsx tests/test_sim.ts
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { Connection, LAMPORTS_PER_SOL } from '@solana/web3.js';
|
|
15
|
-
import {
|
|
16
|
-
createEphemeralAgent,
|
|
17
|
-
launchFaction,
|
|
18
|
-
directJoinFaction,
|
|
19
|
-
defect,
|
|
20
|
-
rally,
|
|
21
|
-
getFactions,
|
|
22
|
-
getFaction,
|
|
23
|
-
getMembers,
|
|
24
|
-
getComms,
|
|
25
|
-
getFactionLeaderboard,
|
|
26
|
-
getWorldStats,
|
|
27
|
-
detectAlliances,
|
|
28
|
-
} from '../src/index.js';
|
|
29
|
-
|
|
30
|
-
const RPC_URL = process.env.RPC_URL ?? 'http://localhost:8899';
|
|
31
|
-
|
|
32
|
-
const AGENT_COUNT = 500;
|
|
33
|
-
const FACTION_COUNT = 15;
|
|
34
|
-
const ROUNDS = 20;
|
|
35
|
-
const AGENTS_PER_ROUND = 80;
|
|
36
|
-
const JOIN_SOL = 0.05 * LAMPORTS_PER_SOL;
|
|
37
|
-
const AIRDROP_SOL = 2 * LAMPORTS_PER_SOL;
|
|
38
|
-
|
|
39
|
-
type Agent = ReturnType<typeof createEphemeralAgent>;
|
|
40
|
-
|
|
41
|
-
interface AgentState {
|
|
42
|
-
agent: Agent;
|
|
43
|
-
holdings: Map<string, number>; // mint -> token balance
|
|
44
|
-
founded: string | null; // mint they founded
|
|
45
|
-
voted: Set<string>; // mints already voted on
|
|
46
|
-
rallied: Set<string>; // mints already rallied
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface FactionState {
|
|
50
|
-
mint: string;
|
|
51
|
-
name: string;
|
|
52
|
-
founder: string; // pubkey of founding agent
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// ─── Helpers ──────────────────────────────────────────────────────
|
|
56
|
-
|
|
57
|
-
async function sendAndConfirm(connection: Connection, agent: Agent, result: any): Promise<string> {
|
|
58
|
-
const tx = result.transaction;
|
|
59
|
-
const signed = agent.sign(tx);
|
|
60
|
-
const sig = await connection.sendRawTransaction(signed.serialize());
|
|
61
|
-
await connection.confirmTransaction(sig, 'confirmed');
|
|
62
|
-
|
|
63
|
-
if (result.additionalTransactions) {
|
|
64
|
-
for (const addlTx of result.additionalTransactions) {
|
|
65
|
-
const addlSigned = agent.sign(addlTx);
|
|
66
|
-
const addlSig = await connection.sendRawTransaction(addlSigned.serialize());
|
|
67
|
-
await connection.confirmTransaction(addlSig, 'confirmed');
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return sig;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function pick<T>(arr: T[]): T {
|
|
75
|
-
return arr[Math.floor(Math.random() * arr.length)];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function pickN<T>(arr: T[], n: number): T[] {
|
|
79
|
-
const shuffled = [...arr].sort(() => Math.random() - 0.5);
|
|
80
|
-
return shuffled.slice(0, n);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const FACTION_NAMES = [
|
|
84
|
-
'Iron Vanguard', 'Obsidian Order', 'Crimson Dawn', 'Shadow Covenant',
|
|
85
|
-
'Ember Collective', 'Void Walkers', 'Solar Reign', 'Frost Legion',
|
|
86
|
-
'Thunder Pact', 'Ash Republic', 'Neon Syndicate', 'Storm Brigade',
|
|
87
|
-
'Lunar Assembly', 'Flame Sentinels', 'Dark Meridian', 'Phoenix Accord',
|
|
88
|
-
'Steel Dominion', 'Crystal Enclave', 'Rogue Alliance', 'Titan Front',
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
const FACTION_SYMBOLS = [
|
|
92
|
-
'IRON', 'OBSD', 'CRIM', 'SHAD', 'EMBR', 'VOID', 'SOLR', 'FRST',
|
|
93
|
-
'THDR', 'ASHR', 'NEON', 'STRM', 'LUNR', 'FLMS', 'DARK', 'PHNX',
|
|
94
|
-
'STEL', 'CRYS', 'ROGU', 'TITN',
|
|
95
|
-
];
|
|
96
|
-
|
|
97
|
-
const JOIN_MESSAGES = [
|
|
98
|
-
'Pledging allegiance.', 'Reporting for duty.', 'This faction will rise.',
|
|
99
|
-
'Strategic position acquired.', 'In for the long haul.', 'Joining the cause.',
|
|
100
|
-
'Scouting this faction.', 'Alliance confirmed.', 'Deploying capital.',
|
|
101
|
-
'Interesting opportunity.', 'Following the signal.', 'Reconnaissance buy.',
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
const DEFECT_MESSAGES = [
|
|
105
|
-
'Strategic withdrawal.', 'This pyre burns too dim.', 'Found a stronger faction.',
|
|
106
|
-
'Tactical repositioning.', 'The leadership is weak.', 'Cutting losses.',
|
|
107
|
-
'Better opportunities elsewhere.', 'Betrayal is just strategy.', 'Moving on.',
|
|
108
|
-
'The war chest is empty.', 'This faction peaked.', 'Exit protocol initiated.',
|
|
109
|
-
];
|
|
110
|
-
|
|
111
|
-
// ─── Simulation ───────────────────────────────────────────────────
|
|
112
|
-
|
|
113
|
-
async function main() {
|
|
114
|
-
const connection = new Connection(RPC_URL, 'confirmed');
|
|
115
|
-
console.log(`Pyre Faction Warfare Simulation`);
|
|
116
|
-
console.log(` RPC: ${RPC_URL}`);
|
|
117
|
-
console.log(` Agents: ${AGENT_COUNT}`);
|
|
118
|
-
console.log(` Factions: ${FACTION_COUNT}`);
|
|
119
|
-
console.log(` Rounds: ${ROUNDS}`);
|
|
120
|
-
console.log(` Agents/round: ${AGENTS_PER_ROUND}\n`);
|
|
121
|
-
|
|
122
|
-
// ── Phase 1: Spawn agents ──────────────────────────────────────
|
|
123
|
-
|
|
124
|
-
console.log('Phase 1: Spawning agents...');
|
|
125
|
-
const agents: AgentState[] = [];
|
|
126
|
-
for (let i = 0; i < AGENT_COUNT; i++) {
|
|
127
|
-
agents.push({
|
|
128
|
-
agent: createEphemeralAgent(),
|
|
129
|
-
holdings: new Map(),
|
|
130
|
-
founded: null,
|
|
131
|
-
voted: new Set(),
|
|
132
|
-
rallied: new Set(),
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
console.log(` ${AGENT_COUNT} agents created\n`);
|
|
136
|
-
|
|
137
|
-
// ── Phase 2: Airdrop SOL ───────────────────────────────────────
|
|
138
|
-
|
|
139
|
-
console.log('Phase 2: Airdropping SOL...');
|
|
140
|
-
const AIRDROP_BATCH = 25;
|
|
141
|
-
for (let i = 0; i < agents.length; i += AIRDROP_BATCH) {
|
|
142
|
-
const batch = agents.slice(i, i + AIRDROP_BATCH);
|
|
143
|
-
await Promise.all(
|
|
144
|
-
batch.map(async (a) => {
|
|
145
|
-
const sig = await connection.requestAirdrop(a.agent.keypair.publicKey, AIRDROP_SOL);
|
|
146
|
-
await connection.confirmTransaction(sig, 'confirmed');
|
|
147
|
-
})
|
|
148
|
-
);
|
|
149
|
-
if ((i + AIRDROP_BATCH) % 100 === 0 || i + AIRDROP_BATCH >= agents.length) {
|
|
150
|
-
console.log(` ${Math.min(i + AIRDROP_BATCH, agents.length)}/${AGENT_COUNT} funded`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
console.log();
|
|
154
|
-
|
|
155
|
-
// ── Phase 3: Launch factions ───────────────────────────────────
|
|
156
|
-
|
|
157
|
-
console.log('Phase 3: Launching factions...');
|
|
158
|
-
const founders = pickN(agents, FACTION_COUNT);
|
|
159
|
-
const factions: FactionState[] = [];
|
|
160
|
-
|
|
161
|
-
for (let i = 0; i < founders.length; i++) {
|
|
162
|
-
const a = founders[i];
|
|
163
|
-
const name = FACTION_NAMES[i];
|
|
164
|
-
const symbol = FACTION_SYMBOLS[i];
|
|
165
|
-
try {
|
|
166
|
-
const result = await launchFaction(connection, {
|
|
167
|
-
founder: a.agent.publicKey,
|
|
168
|
-
name,
|
|
169
|
-
symbol,
|
|
170
|
-
metadata_uri: `https://pyre.gg/factions/${symbol.toLowerCase()}.json`,
|
|
171
|
-
community_faction: true,
|
|
172
|
-
});
|
|
173
|
-
await sendAndConfirm(connection, a.agent, result);
|
|
174
|
-
const mint = result.mint.toBase58();
|
|
175
|
-
factions.push({ mint, name, founder: a.agent.publicKey });
|
|
176
|
-
a.founded = mint;
|
|
177
|
-
console.log(` [${symbol}] ${name} — founded by ${a.agent.publicKey.slice(0, 8)}...`);
|
|
178
|
-
} catch (err: any) {
|
|
179
|
-
console.log(` FAIL launching ${name}: ${err.message}`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
console.log(` ${factions.length} factions live\n`);
|
|
183
|
-
|
|
184
|
-
if (factions.length === 0) {
|
|
185
|
-
console.error('No factions launched. Aborting simulation.');
|
|
186
|
-
process.exit(1);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// ── Phase 4: Random walk simulation ────────────────────────────
|
|
190
|
-
|
|
191
|
-
console.log('Phase 4: Random walk simulation...\n');
|
|
192
|
-
|
|
193
|
-
let totalJoins = 0;
|
|
194
|
-
let totalDefections = 0;
|
|
195
|
-
let totalRallies = 0;
|
|
196
|
-
let totalErrors = 0;
|
|
197
|
-
|
|
198
|
-
for (let round = 1; round <= ROUNDS; round++) {
|
|
199
|
-
const roundAgents = pickN(agents, AGENTS_PER_ROUND);
|
|
200
|
-
let roundJoins = 0;
|
|
201
|
-
let roundDefections = 0;
|
|
202
|
-
let roundRallies = 0;
|
|
203
|
-
let roundErrors = 0;
|
|
204
|
-
|
|
205
|
-
console.log(`─── Round ${round}/${ROUNDS} ───`);
|
|
206
|
-
|
|
207
|
-
// Process agents in batches to avoid overwhelming the RPC
|
|
208
|
-
const ROUND_BATCH = 10;
|
|
209
|
-
for (let i = 0; i < roundAgents.length; i += ROUND_BATCH) {
|
|
210
|
-
const batch = roundAgents.slice(i, i + ROUND_BATCH);
|
|
211
|
-
await Promise.all(
|
|
212
|
-
batch.map(async (a) => {
|
|
213
|
-
await executeRandomAction(connection, a, factions);
|
|
214
|
-
})
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Tally round results by checking what actions succeeded
|
|
219
|
-
for (const a of roundAgents) {
|
|
220
|
-
// We track joins/defections in executeRandomAction via the stats object
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Get round stats from the counter
|
|
224
|
-
const stats = getRoundStats();
|
|
225
|
-
roundJoins = stats.joins;
|
|
226
|
-
roundDefections = stats.defections;
|
|
227
|
-
roundRallies = stats.rallies;
|
|
228
|
-
roundErrors += stats.errors;
|
|
229
|
-
|
|
230
|
-
totalJoins += roundJoins;
|
|
231
|
-
totalDefections += roundDefections;
|
|
232
|
-
totalRallies += roundRallies;
|
|
233
|
-
totalErrors += roundErrors;
|
|
234
|
-
|
|
235
|
-
console.log(` Joins: ${roundJoins} | Defections: ${roundDefections} | Rallies: ${roundRallies} | Errors: ${roundErrors}`);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
console.log(`\n─── Simulation Complete ───`);
|
|
239
|
-
console.log(` Total joins: ${totalJoins}`);
|
|
240
|
-
console.log(` Total defections: ${totalDefections}`);
|
|
241
|
-
console.log(` Total rallies: ${totalRallies}`);
|
|
242
|
-
console.log(` Total errors: ${totalErrors}\n`);
|
|
243
|
-
|
|
244
|
-
// ── Phase 5: Intel debrief ─────────────────────────────────────
|
|
245
|
-
|
|
246
|
-
console.log('Phase 5: Intelligence debrief...\n');
|
|
247
|
-
|
|
248
|
-
// Leaderboard
|
|
249
|
-
console.log('Faction Leaderboard:');
|
|
250
|
-
try {
|
|
251
|
-
const leaderboard = await getFactionLeaderboard(connection, { limit: factions.length });
|
|
252
|
-
for (let i = 0; i < leaderboard.length; i++) {
|
|
253
|
-
const f = leaderboard[i];
|
|
254
|
-
console.log(` ${i + 1}. [${f.symbol}] ${f.name} — power: ${f.score.toFixed(2)}, mcap: ${f.market_cap_sol.toFixed(4)} SOL, members: ${f.members}`);
|
|
255
|
-
}
|
|
256
|
-
} catch (err: any) {
|
|
257
|
-
console.log(` Could not fetch leaderboard: ${err.message}`);
|
|
258
|
-
}
|
|
259
|
-
console.log();
|
|
260
|
-
|
|
261
|
-
// Alliance detection
|
|
262
|
-
if (factions.length >= 2) {
|
|
263
|
-
console.log('Alliance Detection:');
|
|
264
|
-
try {
|
|
265
|
-
const alliances = await detectAlliances(
|
|
266
|
-
connection,
|
|
267
|
-
factions.map(f => f.mint),
|
|
268
|
-
20,
|
|
269
|
-
);
|
|
270
|
-
if (alliances.length === 0) {
|
|
271
|
-
console.log(' No alliances detected');
|
|
272
|
-
}
|
|
273
|
-
for (const a of alliances.slice(0, 10)) {
|
|
274
|
-
const names = a.factions.map(m => factions.find(f => f.mint === m)?.name ?? m.slice(0, 8));
|
|
275
|
-
console.log(` ${names.join(' + ')} — ${a.shared_members} shared (${a.overlap_percent.toFixed(1)}% overlap)`);
|
|
276
|
-
}
|
|
277
|
-
} catch (err: any) {
|
|
278
|
-
console.log(` Could not detect alliances: ${err.message}`);
|
|
279
|
-
}
|
|
280
|
-
console.log();
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Faction details for top 3
|
|
284
|
-
console.log('Top Faction Details:');
|
|
285
|
-
for (const f of factions.slice(0, 3)) {
|
|
286
|
-
try {
|
|
287
|
-
const detail = await getFaction(connection, f.mint);
|
|
288
|
-
const members = await getMembers(connection, f.mint, 5);
|
|
289
|
-
const comms = await getComms(connection, f.mint, 5);
|
|
290
|
-
|
|
291
|
-
console.log(` [${detail.symbol}] ${detail.name}`);
|
|
292
|
-
console.log(` Status: ${detail.status} | Tier: ${detail.tier}`);
|
|
293
|
-
console.log(` Price: ${detail.price_sol.toFixed(6)} SOL | MCap: ${detail.market_cap_sol.toFixed(4)} SOL`);
|
|
294
|
-
console.log(` Members: ${members.total_members} | Rallies: ${detail.rallies}`);
|
|
295
|
-
console.log(` War Chest: ${detail.war_chest_sol.toFixed(4)} SOL`);
|
|
296
|
-
console.log(` Votes: scorched_earth=${detail.votes_scorched_earth} fortify=${detail.votes_fortify}`);
|
|
297
|
-
if (comms.comms.length > 0) {
|
|
298
|
-
console.log(` Recent comms:`);
|
|
299
|
-
for (const c of comms.comms.slice(0, 3)) {
|
|
300
|
-
console.log(` ${c.sender.slice(0, 8)}...: "${c.memo}"`);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
if (members.members.length > 0) {
|
|
304
|
-
console.log(` Top holders:`);
|
|
305
|
-
for (const m of members.members.slice(0, 3)) {
|
|
306
|
-
console.log(` ${m.address.slice(0, 8)}... — ${m.percentage.toFixed(2)}%`);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
} catch (err: any) {
|
|
310
|
-
console.log(` Could not fetch ${f.name}: ${err.message}`);
|
|
311
|
-
}
|
|
312
|
-
console.log();
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// World stats
|
|
316
|
-
console.log('World Stats:');
|
|
317
|
-
try {
|
|
318
|
-
const world = await getWorldStats(connection);
|
|
319
|
-
console.log(` Total factions: ${world.total_factions}`);
|
|
320
|
-
console.log(` Rising: ${world.rising_factions} | Ascended: ${world.ascended_factions}`);
|
|
321
|
-
console.log(` Total SOL locked: ${world.total_sol_locked.toFixed(4)} SOL`);
|
|
322
|
-
if (world.most_powerful) {
|
|
323
|
-
console.log(` Most powerful: [${world.most_powerful.symbol}] ${world.most_powerful.name} (score: ${world.most_powerful.score.toFixed(2)})`);
|
|
324
|
-
}
|
|
325
|
-
} catch (err: any) {
|
|
326
|
-
console.log(` Could not fetch world stats: ${err.message}`);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
console.log('\nSimulation complete.');
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// ─── Round Stats Tracking ─────────────────────────────────────────
|
|
333
|
-
|
|
334
|
-
let _roundJoins = 0;
|
|
335
|
-
let _roundDefections = 0;
|
|
336
|
-
let _roundRallies = 0;
|
|
337
|
-
let _roundErrors = 0;
|
|
338
|
-
|
|
339
|
-
function resetRoundStats() {
|
|
340
|
-
_roundJoins = 0;
|
|
341
|
-
_roundDefections = 0;
|
|
342
|
-
_roundRallies = 0;
|
|
343
|
-
_roundErrors = 0;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function getRoundStats() {
|
|
347
|
-
const stats = { joins: _roundJoins, defections: _roundDefections, rallies: _roundRallies, errors: _roundErrors };
|
|
348
|
-
resetRoundStats();
|
|
349
|
-
return stats;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// ─── Action Execution ─────────────────────────────────────────────
|
|
353
|
-
|
|
354
|
-
async function executeRandomAction(
|
|
355
|
-
connection: Connection,
|
|
356
|
-
agentState: AgentState,
|
|
357
|
-
factions: FactionState[],
|
|
358
|
-
) {
|
|
359
|
-
const holdsFactions = [...agentState.holdings.entries()].filter(([, bal]) => bal > 0);
|
|
360
|
-
const canDefect = holdsFactions.length > 0;
|
|
361
|
-
|
|
362
|
-
// Weight actions: 60% join, 20% defect (if possible), 20% rally
|
|
363
|
-
const roll = Math.random();
|
|
364
|
-
let action: 'join' | 'defect' | 'rally';
|
|
365
|
-
|
|
366
|
-
if (roll < 0.6 || (!canDefect)) {
|
|
367
|
-
action = 'join';
|
|
368
|
-
} else if (roll < 0.8 && canDefect) {
|
|
369
|
-
action = 'defect';
|
|
370
|
-
} else {
|
|
371
|
-
action = 'rally';
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
switch (action) {
|
|
375
|
-
case 'join': {
|
|
376
|
-
const faction = pick(factions);
|
|
377
|
-
const alreadyVoted = agentState.voted.has(faction.mint);
|
|
378
|
-
const message = pick(JOIN_MESSAGES);
|
|
379
|
-
const params: any = {
|
|
380
|
-
mint: faction.mint,
|
|
381
|
-
agent: agentState.agent.publicKey,
|
|
382
|
-
amount_sol: JOIN_SOL,
|
|
383
|
-
message,
|
|
384
|
-
};
|
|
385
|
-
if (!alreadyVoted) {
|
|
386
|
-
params.strategy = Math.random() > 0.5 ? 'fortify' : 'scorched_earth';
|
|
387
|
-
}
|
|
388
|
-
try {
|
|
389
|
-
const result = await directJoinFaction(connection, params);
|
|
390
|
-
await sendAndConfirm(connection, agentState.agent, result);
|
|
391
|
-
const prev = agentState.holdings.get(faction.mint) ?? 0;
|
|
392
|
-
agentState.holdings.set(faction.mint, prev + 1);
|
|
393
|
-
agentState.voted.add(faction.mint);
|
|
394
|
-
_roundJoins++;
|
|
395
|
-
} catch (err: any) {
|
|
396
|
-
console.log(` [JOIN ERROR] ${agentState.agent.publicKey.slice(0, 8)}... -> ${faction.name}: ${err.message}`);
|
|
397
|
-
_roundErrors++;
|
|
398
|
-
}
|
|
399
|
-
break;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
case 'defect': {
|
|
403
|
-
const [mint, balance] = pick(holdsFactions);
|
|
404
|
-
const faction = factions.find(f => f.mint === mint);
|
|
405
|
-
if (!faction) { _roundErrors++; break; }
|
|
406
|
-
const sellAmount = Math.max(1, Math.floor(balance * (0.3 + Math.random() * 0.7)));
|
|
407
|
-
const message = pick(DEFECT_MESSAGES);
|
|
408
|
-
try {
|
|
409
|
-
const result = await defect(connection, {
|
|
410
|
-
mint: faction.mint,
|
|
411
|
-
agent: agentState.agent.publicKey,
|
|
412
|
-
amount_tokens: sellAmount,
|
|
413
|
-
message,
|
|
414
|
-
});
|
|
415
|
-
await sendAndConfirm(connection, agentState.agent, result);
|
|
416
|
-
const remaining = (agentState.holdings.get(mint) ?? 0) - sellAmount;
|
|
417
|
-
if (remaining <= 0) {
|
|
418
|
-
agentState.holdings.delete(mint);
|
|
419
|
-
} else {
|
|
420
|
-
agentState.holdings.set(mint, remaining);
|
|
421
|
-
}
|
|
422
|
-
_roundDefections++;
|
|
423
|
-
} catch (err: any) {
|
|
424
|
-
console.log(` [DEFECT ERROR] ${agentState.agent.publicKey.slice(0, 8)}... -> ${faction.name} (${sellAmount} tokens): ${err.message}`);
|
|
425
|
-
_roundErrors++;
|
|
426
|
-
}
|
|
427
|
-
break;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
case 'rally': {
|
|
431
|
-
// Can't rally your own faction or one you already rallied
|
|
432
|
-
const eligible = factions.filter(f =>
|
|
433
|
-
f.founder !== agentState.agent.publicKey &&
|
|
434
|
-
!agentState.rallied.has(f.mint)
|
|
435
|
-
);
|
|
436
|
-
if (eligible.length === 0) { break; } // nothing to rally, not an error
|
|
437
|
-
const faction = pick(eligible);
|
|
438
|
-
try {
|
|
439
|
-
const result = await rally(connection, {
|
|
440
|
-
mint: faction.mint,
|
|
441
|
-
agent: agentState.agent.publicKey,
|
|
442
|
-
});
|
|
443
|
-
await sendAndConfirm(connection, agentState.agent, result);
|
|
444
|
-
agentState.rallied.add(faction.mint);
|
|
445
|
-
_roundRallies++;
|
|
446
|
-
} catch (err: any) {
|
|
447
|
-
console.log(` [RALLY ERROR] ${agentState.agent.publicKey.slice(0, 8)}... -> ${faction.name}: ${err.message}`);
|
|
448
|
-
_roundErrors++;
|
|
449
|
-
}
|
|
450
|
-
break;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
main().catch((err) => {
|
|
456
|
-
console.error('\nSimulation failed:', err);
|
|
457
|
-
process.exit(1);
|
|
458
|
-
});
|