pyre-world-kit 2.0.12 → 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.
Files changed (53) hide show
  1. package/.prettierrc.json +6 -0
  2. package/dist/index.d.ts +38 -4
  3. package/dist/index.js +100 -85
  4. package/dist/providers/action.provider.d.ts +46 -0
  5. package/dist/providers/action.provider.js +331 -0
  6. package/dist/providers/intel.provider.d.ts +29 -0
  7. package/dist/providers/intel.provider.js +363 -0
  8. package/dist/providers/mapper.provider.d.ts +197 -0
  9. package/dist/providers/mapper.provider.js +158 -0
  10. package/dist/providers/registry.provider.d.ts +25 -0
  11. package/dist/providers/registry.provider.js +229 -0
  12. package/dist/providers/state.provider.d.ts +42 -0
  13. package/dist/providers/state.provider.js +348 -0
  14. package/dist/pyre_world.json +34 -229
  15. package/dist/types/action.types.d.ts +41 -0
  16. package/dist/types/action.types.js +2 -0
  17. package/dist/types/intel.types.d.ts +20 -0
  18. package/dist/types/intel.types.js +2 -0
  19. package/dist/types/mapper.types.d.ts +27 -0
  20. package/dist/types/mapper.types.js +22 -0
  21. package/dist/types/registry.types.d.ts +0 -0
  22. package/dist/types/registry.types.js +1 -0
  23. package/dist/types/state.types.d.ts +112 -0
  24. package/dist/types/state.types.js +2 -0
  25. package/dist/types.d.ts +8 -24
  26. package/dist/util.d.ts +29 -0
  27. package/dist/util.js +144 -0
  28. package/dist/vanity.d.ts +3 -3
  29. package/dist/vanity.js +18 -15
  30. package/package.json +4 -2
  31. package/readme.md +134 -122
  32. package/src/index.ts +127 -92
  33. package/src/providers/action.provider.ts +443 -0
  34. package/src/providers/intel.provider.ts +383 -0
  35. package/src/providers/mapper.provider.ts +195 -0
  36. package/src/providers/registry.provider.ts +277 -0
  37. package/src/providers/state.provider.ts +357 -0
  38. package/src/pyre_world.json +35 -230
  39. package/src/types/action.types.ts +76 -0
  40. package/src/types/intel.types.ts +22 -0
  41. package/src/types/mapper.types.ts +84 -0
  42. package/src/types/registry.types.ts +0 -0
  43. package/src/types/state.types.ts +144 -0
  44. package/src/types.ts +329 -333
  45. package/src/util.ts +148 -0
  46. package/src/vanity.ts +27 -14
  47. package/tests/test_e2e.ts +339 -172
  48. package/src/actions.ts +0 -719
  49. package/src/intel.ts +0 -521
  50. package/src/mappers.ts +0 -302
  51. package/src/registry.ts +0 -317
  52. package/tests/test_devnet_e2e.ts +0 -401
  53. package/tests/test_sim.ts +0 -458
package/tests/test_e2e.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  * Pyre Kit E2E Test
3
3
  *
4
4
  * Tests the full faction warfare flow against a surfpool fork.
5
+ * All operations are vault-routed. Verifies SOL + token balances,
6
+ * state tracking (tick, sentiment, holdings, history), and PNL.
5
7
  *
6
8
  * Prerequisites:
7
9
  * surfpool start --network mainnet --no-tui
@@ -10,204 +12,369 @@
10
12
  * pnpm test (or: npx tsx tests/test_e2e.ts)
11
13
  */
12
14
 
13
- import { Connection, LAMPORTS_PER_SOL } from '@solana/web3.js';
15
+ import { Connection, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js'
16
+ import { getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'
14
17
  import {
18
+ PyreKit,
15
19
  createEphemeralAgent,
16
- createStronghold,
17
- fundStronghold,
18
- recruitAgent,
19
- launchFaction,
20
- getFactions,
21
- getFaction,
22
- getJoinQuote,
23
- joinFaction,
24
- getComms,
25
- rally,
26
- defect,
27
- getMembers,
28
- getStrongholdForAgent,
29
- } from '../src/index.js';
30
-
31
- const RPC_URL = process.env.RPC_URL ?? 'http://localhost:8899';
20
+ startVaultPnlTracker,
21
+ } from '../src/index.js'
22
+
23
+ const RPC_URL = process.env.RPC_URL ?? 'http://localhost:8899'
24
+ let passed = 0
25
+ let failed = 0
26
+
27
+ function assert(condition: boolean, msg: string) {
28
+ if (!condition) {
29
+ console.error(` ✗ FAIL: ${msg}`)
30
+ failed++
31
+ } else {
32
+ console.log(` ✓ ${msg}`)
33
+ passed++
34
+ }
35
+ }
32
36
 
33
37
  async function sendAndConfirm(connection: Connection, agent: ReturnType<typeof createEphemeralAgent>, result: any) {
34
- const tx = result.transaction;
35
- const signed = agent.sign(tx);
36
- const sig = await connection.sendRawTransaction(signed.serialize());
37
- await connection.confirmTransaction(sig, 'confirmed');
38
- console.log(` tx: ${sig}`);
38
+ const tx = result.transaction
39
+ const signed = agent.sign(tx)
40
+ const sig = await connection.sendRawTransaction(signed.serialize())
41
+ await connection.confirmTransaction(sig, 'confirmed')
42
+ console.log(` tx: ${sig}`)
39
43
 
40
- // Handle additional transactions
41
44
  if (result.additionalTransactions) {
42
45
  for (const addlTx of result.additionalTransactions) {
43
- const addlSigned = agent.sign(addlTx);
44
- const addlSig = await connection.sendRawTransaction(addlSigned.serialize());
45
- await connection.confirmTransaction(addlSig, 'confirmed');
46
- console.log(` additional tx: ${addlSig}`);
46
+ const addlSigned = agent.sign(addlTx)
47
+ const addlSig = await connection.sendRawTransaction(addlSigned.serialize())
48
+ await connection.confirmTransaction(addlSig, 'confirmed')
49
+ console.log(` additional tx: ${addlSig}`)
47
50
  }
48
51
  }
49
52
 
50
- return sig;
53
+ return sig
54
+ }
55
+
56
+ function sol(lamports: number): string {
57
+ return (lamports / LAMPORTS_PER_SOL).toFixed(6)
51
58
  }
52
59
 
53
60
  async function main() {
54
- const connection = new Connection(RPC_URL, 'confirmed');
55
- console.log(`Pyre Kit E2E Test — RPC: ${RPC_URL}\n`);
56
-
57
- // 1. Create ephemeral agents
58
- console.log('1. Creating ephemeral agents...');
59
- const agent = createEphemeralAgent();
60
- const agent2 = createEphemeralAgent();
61
- console.log(` Agent 1: ${agent.publicKey}`);
62
- console.log(` Agent 2: ${agent2.publicKey}`);
63
-
64
- // Airdrop SOL for testing
65
- console.log(' Requesting airdrops...');
61
+ const connection = new Connection(RPC_URL, 'confirmed')
62
+ const agent = createEphemeralAgent()
63
+ const agent2 = createEphemeralAgent()
64
+
65
+ // Each agent gets its own kit (state is per-agent)
66
+ const kit = new PyreKit(connection, agent.publicKey)
67
+ const kit2 = new PyreKit(connection, agent2.publicKey)
68
+
69
+ /** Get token balance across wallet ATA + vault ATA */
70
+ async function getTokenBalance(k: PyreKit, mint: string, owner: string): Promise<number> {
71
+ const mintPk = new PublicKey(mint)
72
+ let total = 0
73
+ try {
74
+ const ata = getAssociatedTokenAddressSync(mintPk, new PublicKey(owner), false, TOKEN_2022_PROGRAM_ID)
75
+ const info = await connection.getTokenAccountBalance(ata)
76
+ total += Number(info.value.amount)
77
+ } catch {}
78
+ try {
79
+ const vault = await k.actions.getStrongholdForAgent(owner)
80
+ if (vault) {
81
+ const vaultPk = new PublicKey(vault.address)
82
+ const vaultAta = getAssociatedTokenAddressSync(mintPk, vaultPk, true, TOKEN_2022_PROGRAM_ID)
83
+ const info = await connection.getTokenAccountBalance(vaultAta)
84
+ total += Number(info.value.amount)
85
+ }
86
+ } catch {}
87
+ return total
88
+ }
89
+
90
+ /** Get vault SOL balance in lamports */
91
+ async function getVaultSolLamports(k: PyreKit, wallet: string): Promise<number> {
92
+ try {
93
+ const vault = await k.actions.getStrongholdForAgent(wallet)
94
+ return vault ? Math.round(vault.sol_balance * LAMPORTS_PER_SOL) : 0
95
+ } catch {
96
+ return 0
97
+ }
98
+ }
99
+
100
+ console.log(`Pyre Kit E2E Test — RPC: ${RPC_URL}\n`)
101
+
102
+ // ═══════════════════════════════════════════════════════════════════
103
+ // SETUP: Agents + Vaults + Faction
104
+ // ═══════════════════════════════════════════════════════════════════
105
+
106
+ console.log('1. Creating agents...')
107
+ console.log(` Agent 1: ${agent.publicKey}`)
108
+ console.log(` Agent 2: ${agent2.publicKey}`)
109
+
110
+ console.log(' Requesting airdrops...')
66
111
  const [airdropSig, airdropSig2] = await Promise.all([
67
112
  connection.requestAirdrop(agent.keypair.publicKey, 10 * LAMPORTS_PER_SOL),
68
- connection.requestAirdrop(agent2.keypair.publicKey, 1 * LAMPORTS_PER_SOL),
69
- ]);
113
+ connection.requestAirdrop(agent2.keypair.publicKey, 10 * LAMPORTS_PER_SOL),
114
+ ])
70
115
  await Promise.all([
71
116
  connection.confirmTransaction(airdropSig, 'confirmed'),
72
117
  connection.confirmTransaction(airdropSig2, 'confirmed'),
73
- ]);
74
- console.log(' Airdrops confirmed');
75
-
76
- // 2. Create stronghold (vault)
77
- console.log('\n2. Creating stronghold...');
78
- const strongholdResult = await createStronghold(connection, {
79
- creator: agent.publicKey,
80
- });
81
- await sendAndConfirm(connection, agent, strongholdResult);
82
- console.log(' Stronghold created');
83
-
84
- // 3. Fund stronghold
85
- console.log('\n3. Funding stronghold...');
86
- const fundResult = await fundStronghold(connection, {
87
- depositor: agent.publicKey,
88
- stronghold_creator: agent.publicKey,
89
- amount_sol: 5 * LAMPORTS_PER_SOL,
90
- });
91
- await sendAndConfirm(connection, agent, fundResult);
92
- console.log(' Funded 5 SOL');
93
-
94
- // 4. Recruit agent (link wallet)
95
- console.log('\n4. Recruiting agent (self-link)...');
96
- // Agent is already linked as creator, but let's verify
97
- const agentLink = await getStrongholdForAgent(connection, agent.publicKey);
98
- console.log(` Stronghold found: ${agentLink ? 'yes' : 'no'}`);
99
- if (agentLink) {
100
- console.log(` SOL balance: ${agentLink.sol_balance / LAMPORTS_PER_SOL} SOL`);
101
- }
118
+ ])
102
119
 
103
- // 5. Launch faction
104
- console.log('\n5. Launching faction...');
105
- const launchResult = await launchFaction(connection, {
106
- founder: agent.publicKey,
107
- name: 'Pyre Test Faction',
108
- symbol: 'PYRE',
109
- metadata_uri: 'https://torch.market/test-metadata.json',
110
- community_faction: true,
111
- });
112
- await sendAndConfirm(connection, agent, launchResult);
113
- const factionMint = launchResult.mint.toBase58();
114
- console.log(` Faction launched: ${factionMint}`);
115
-
116
- // 6. List factions
117
- console.log('\n6. Listing factions...');
118
- const factions = await getFactions(connection, { limit: 10 });
119
- console.log(` Total factions: ${factions.total}`);
120
- const ourFaction = factions.factions.find(f => f.mint === factionMint);
121
- console.log(` Our faction found: ${ourFaction ? 'yes' : 'no'}`);
122
- if (ourFaction) {
123
- console.log(` Status: ${ourFaction.status}, Members: ${ourFaction.members}`);
124
- }
120
+ console.log('\n2. Creating vaults...')
121
+ const vault1 = await kit.actions.createStronghold({ creator: agent.publicKey })
122
+ await sendAndConfirm(connection, agent, vault1)
123
+ const vault2 = await kit2.actions.createStronghold({ creator: agent2.publicKey })
124
+ await sendAndConfirm(connection, agent2, vault2)
125
+
126
+ console.log('\n3. Funding vaults...')
127
+ const fund1 = await kit.actions.fundStronghold({
128
+ depositor: agent.publicKey, stronghold_creator: agent.publicKey, amount_sol: 5 * LAMPORTS_PER_SOL,
129
+ })
130
+ await sendAndConfirm(connection, agent, fund1)
131
+ const fund2 = await kit2.actions.fundStronghold({
132
+ depositor: agent2.publicKey, stronghold_creator: agent2.publicKey, amount_sol: 5 * LAMPORTS_PER_SOL,
133
+ })
134
+ await sendAndConfirm(connection, agent2, fund2)
135
+
136
+ // Initialize state from chain (resolves vault, loads holdings)
137
+ console.log('\n4. Initializing state...')
138
+ const state1 = await kit.state.init()
139
+ const state2 = await kit2.state.init()
140
+ console.log(` Agent 1 vault: ${state1.vaultCreator ?? 'none'}`)
141
+ console.log(` Agent 2 vault: ${state2.vaultCreator ?? 'none'}`)
142
+ assert(state1.initialized, 'agent 1 state initialized')
143
+ assert(state2.initialized, 'agent 2 state initialized')
144
+ assert(state1.vaultCreator !== null, 'agent 1 vault resolved')
145
+ assert(state2.vaultCreator !== null, 'agent 2 vault resolved')
146
+ assert(kit.state.tick === 0, `agent 1 tick starts at 0: ${kit.state.tick}`)
147
+
148
+ console.log('\n5. Launching faction...')
149
+ const launchResult = await kit.actions.launch({
150
+ founder: agent.publicKey, name: 'Balance Test Faction', symbol: 'BTEST',
151
+ metadata_uri: 'https://pyre.gg/test.json', community_faction: true,
152
+ })
153
+ await sendAndConfirm(connection, agent, launchResult)
154
+ await kit.state.record('launch', launchResult.mint.toBase58(), 'launched BTEST')
155
+ const mint = launchResult.mint.toBase58()
156
+ console.log(` Faction: ${mint}`)
157
+
158
+ assert(kit.state.tick === 1, `tick incremented to 1: ${kit.state.tick}`)
159
+ assert(kit.state.state!.actionCounts.launch === 1, `launch count = 1`)
160
+ assert(kit.state.getSentiment(mint) === 3, `launch sentiment = +3: ${kit.state.getSentiment(mint)}`)
161
+ assert(kit.state.history.length === 1, `history has 1 entry`)
162
+
163
+ // ═══════════════════════════════════════════════════════════════════
164
+ // TEST: VAULT BUY + STATE TRACKING
165
+ // ═══════════════════════════════════════════════════════════════════
166
+
167
+ console.log('\n═══ TEST: Vault buy (join) + state tracking ═══')
168
+ const buyLamports = Math.floor(0.5 * LAMPORTS_PER_SOL)
169
+
170
+ const buyVaultBefore = await getVaultSolLamports(kit, agent.publicKey)
171
+ const buyTokensBefore = await getTokenBalance(kit, mint, agent.publicKey)
172
+ const buyTotalBefore = await kit.intel.getAgentSolLamports(agent.publicKey)
173
+
174
+ console.log(` PRE — vault: ${sol(buyVaultBefore)}, tokens: ${buyTokensBefore}, total: ${sol(buyTotalBefore)}`)
175
+
176
+ const pnl = await startVaultPnlTracker(kit.intel, agent.publicKey)
177
+
178
+ const joinResult = await kit.actions.join({
179
+ mint, agent: agent.publicKey, amount_sol: buyLamports,
180
+ strategy: 'fortify', message: 'Vault buy test', stronghold: agent.publicKey,
181
+ })
182
+ await sendAndConfirm(connection, agent, joinResult)
183
+ await kit.state.record('join', mint, 'joined BTEST for 0.5 SOL — "Vault buy test"')
184
+
185
+ const buyVaultAfter = await getVaultSolLamports(kit, agent.publicKey)
186
+ const buyTokensAfter = await getTokenBalance(kit, mint, agent.publicKey)
187
+ const buyTotalAfter = await kit.intel.getAgentSolLamports(agent.publicKey)
188
+ const { spent, received } = await pnl.finish()
189
+
190
+ console.log(` POST — vault: ${sol(buyVaultAfter)}, tokens: ${buyTokensAfter}, total: ${sol(buyTotalAfter)}`)
191
+ console.log(` PNL — spent: ${sol(spent)}, received: ${sol(received)}`)
192
+
193
+ // Balance assertions
194
+ assert(buyTokensAfter > buyTokensBefore, `tokens increased: ${buyTokensBefore} → ${buyTokensAfter}`)
195
+ assert(buyVaultAfter < buyVaultBefore, `vault SOL decreased: ${sol(buyVaultBefore)} → ${sol(buyVaultAfter)}`)
196
+ assert(buyTotalAfter < buyTotalBefore, `total SOL decreased: ${sol(buyTotalBefore)} → ${sol(buyTotalAfter)}`)
197
+ assert(spent > 0, `PNL spent > 0: ${sol(spent)}`)
198
+
199
+ // State assertions
200
+ assert(kit.state.tick === 2, `tick = 2: ${kit.state.tick}`)
201
+ assert(kit.state.state!.actionCounts.join === 1, `join count = 1`)
202
+ assert(kit.state.getSentiment(mint) === 4, `sentiment after launch(+3) + join(+1) = 4: ${kit.state.getSentiment(mint)}`)
203
+ assert(kit.state.getBalance(mint) > 0, `state holdings updated: ${kit.state.getBalance(mint)}`)
204
+
205
+ // ═══════════════════════════════════════════════════════════════════
206
+ // TEST: VAULT SELL (defect) + SENTIMENT
207
+ // ═══════════════════════════════════════════════════════════════════
208
+
209
+ console.log('\n═══ TEST: Vault defect (sell half) + sentiment ═══')
210
+ const sellAmount = Math.floor(buyTokensAfter / 2)
211
+ const sentimentBeforeDefect = kit.state.getSentiment(mint)
212
+
213
+ const defVaultBefore = await getVaultSolLamports(kit, agent.publicKey)
214
+ const defTokensBefore = await getTokenBalance(kit, mint, agent.publicKey)
215
+ const defTotalBefore = await kit.intel.getAgentSolLamports(agent.publicKey)
125
216
 
126
- // 7. Get faction detail
127
- console.log('\n7. Getting faction detail...');
128
- const detail = await getFaction(connection, factionMint);
129
- console.log(` Name: ${detail.name}`);
130
- console.log(` Status: ${detail.status}`);
131
- console.log(` Tier: ${detail.tier}`);
132
- console.log(` Founder: ${detail.founder}`);
133
- console.log(` War chest SOL: ${detail.war_chest_sol}`);
134
-
135
- // 8. Get join quote
136
- console.log('\n8. Getting join quote (0.1 SOL)...');
137
- const quote = await getJoinQuote(connection, factionMint, 0.1 * LAMPORTS_PER_SOL);
138
- console.log(` Tokens out: ${quote.tokens_to_user}`);
139
- console.log(` Price impact: ${quote.price_impact_percent}%`);
140
-
141
- // 9. Join faction with message (comms)
142
- console.log('\n9. Joining faction...');
143
- const joinResult = await joinFaction(connection, {
144
- mint: factionMint,
145
- agent: agent.publicKey,
146
- amount_sol: 0.1 * LAMPORTS_PER_SOL,
147
- strategy: 'fortify',
148
- message: 'For the faction! First to join.',
217
+ console.log(` PRE — vault: ${sol(defVaultBefore)}, tokens: ${defTokensBefore}, total: ${sol(defTotalBefore)}, sentiment: ${sentimentBeforeDefect}`)
218
+
219
+ const defPnl = await startVaultPnlTracker(kit.intel, agent.publicKey)
220
+
221
+ const defectResult = await kit.actions.defect({
222
+ mint, agent: agent.publicKey, amount_tokens: sellAmount,
223
+ message: 'Vault defect test', stronghold: agent.publicKey,
224
+ })
225
+ await sendAndConfirm(connection, agent, defectResult)
226
+ await kit.state.record('defect', mint, 'defected from BTEST — "Vault defect test"')
227
+
228
+ const defVaultAfter = await getVaultSolLamports(kit, agent.publicKey)
229
+ const defTokensAfter = await getTokenBalance(kit, mint, agent.publicKey)
230
+ const defTotalAfter = await kit.intel.getAgentSolLamports(agent.publicKey)
231
+ const defPnlResult = await defPnl.finish()
232
+
233
+ console.log(` POST vault: ${sol(defVaultAfter)}, tokens: ${defTokensAfter}, total: ${sol(defTotalAfter)}, sentiment: ${kit.state.getSentiment(mint)}`)
234
+ console.log(` PNL — spent: ${sol(defPnlResult.spent)}, received: ${sol(defPnlResult.received)}`)
235
+ console.log(` VAULT DELTA: ${sol(defVaultAfter - defVaultBefore)}`)
236
+
237
+ // Balance assertions
238
+ assert(defTokensAfter < defTokensBefore, `tokens decreased: ${defTokensBefore} → ${defTokensAfter}`)
239
+ assert(defTotalAfter > defTotalBefore, `total SOL increased: ${sol(defTotalBefore)} → ${sol(defTotalAfter)}`)
240
+ assert(defVaultAfter > defVaultBefore, `vault SOL increased: ${sol(defVaultBefore)} → ${sol(defVaultAfter)}`)
241
+ assert(defPnlResult.received > 0, `PNL received > 0: ${sol(defPnlResult.received)}`)
242
+
243
+ // State assertions
244
+ assert(kit.state.tick === 3, `tick = 3: ${kit.state.tick}`)
245
+ assert(kit.state.getSentiment(mint) === sentimentBeforeDefect - 2, `defect drops sentiment by 2: ${sentimentBeforeDefect} → ${kit.state.getSentiment(mint)}`)
246
+
247
+ // ═══════════════════════════════════════════════════════════════════
248
+ // TEST: FUD + SENTIMENT
249
+ // ═══════════════════════════════════════════════════════════════════
250
+
251
+ console.log('\n═══ TEST: FUD (micro sell) + sentiment ═══')
252
+ const sentimentBeforeFud = kit.state.getSentiment(mint)
253
+ const fudTokensBefore = await getTokenBalance(kit, mint, agent.publicKey)
254
+
255
+ console.log(` PRE — tokens: ${fudTokensBefore}, sentiment: ${sentimentBeforeFud}`)
256
+
257
+ const fudResult = await kit.actions.fud({
258
+ mint, agent: agent.publicKey, message: 'This faction is weak!',
149
259
  stronghold: agent.publicKey,
150
- });
151
- await sendAndConfirm(connection, agent, joinResult);
152
- console.log(' Joined faction');
153
-
154
- // 10. Read comms
155
- console.log('\n10. Reading faction comms...');
156
- const comms = await getComms(connection, factionMint);
157
- console.log(` Total comms: ${comms.total}`);
158
- for (const c of comms.comms) {
159
- console.log(` [${new Date(c.timestamp * 1000).toISOString()}] ${c.sender.slice(0, 8)}...: ${c.memo}`);
160
- }
260
+ })
261
+ await sendAndConfirm(connection, agent, fudResult)
262
+ await kit.state.record('fud', mint, 'fud BTEST — "This faction is weak!"')
263
+
264
+ const fudTokensAfter = await getTokenBalance(kit, mint, agent.publicKey)
265
+ const fudTokenDelta = fudTokensBefore - fudTokensAfter
266
+
267
+ console.log(` POST — tokens: ${fudTokensAfter}, sentiment: ${kit.state.getSentiment(mint)}`)
268
+ console.log(` TOKEN DELTA: ${fudTokenDelta} (expect ~100)`)
269
+
270
+ assert(fudTokenDelta > 0, `FUD sold tokens: ${fudTokenDelta}`)
271
+ assert(fudTokenDelta <= 100, `FUD micro amount: ${fudTokenDelta}`)
272
+ assert(kit.state.tick === 4, `tick = 4: ${kit.state.tick}`)
273
+ assert(kit.state.getSentiment(mint) === sentimentBeforeFud - 1.5, `fud drops sentiment by 1.5: ${kit.state.getSentiment(mint)}`)
161
274
 
162
- // 11. Rally support (different agent — can't rally your own faction)
163
- console.log('\n11. Rallying faction (agent 2)...');
164
- const rallyResult = await rally(connection, {
165
- mint: factionMint,
166
- agent: agent2.publicKey,
167
- });
168
- await sendAndConfirm(connection, agent2, rallyResult);
169
- console.log(' Rally sent');
170
-
171
- // Verify rally
172
- const detailAfterRally = await getFaction(connection, factionMint);
173
- console.log(` Rallies: ${detailAfterRally.rallies}`);
174
-
175
- // 12. Defect with message
176
- console.log('\n12. Defecting from faction...');
177
- // Sell half of what we bought
178
- const sellAmount = Math.floor(quote.tokens_to_user / 2);
179
- const defectResult = await defect(connection, {
180
- mint: factionMint,
181
- agent: agent.publicKey,
182
- amount_tokens: sellAmount,
183
- message: 'Strategic withdrawal. Will return.',
275
+ // ═══════════════════════════════════════════════════════════════════
276
+ // TEST: MESSAGE + STATE
277
+ // ═══════════════════════════════════════════════════════════════════
278
+
279
+ console.log('\n═══ TEST: Message (micro buy) + state ═══')
280
+ const sentimentBeforeMsg = kit.state.getSentiment(mint)
281
+
282
+ const msgResult = await kit.actions.message({
283
+ mint, agent: agent.publicKey, message: 'We are strong!',
184
284
  stronghold: agent.publicKey,
185
- });
186
- await sendAndConfirm(connection, agent, defectResult);
187
- console.log(` Defected ${sellAmount} tokens`);
188
-
189
- // 13. Check members
190
- console.log('\n13. Checking members...');
191
- const members = await getMembers(connection, factionMint);
192
- console.log(` Total members: ${members.total_members}`);
193
- for (const m of members.members.slice(0, 5)) {
194
- console.log(` ${m.address.slice(0, 8)}... — ${m.balance} (${m.percentage.toFixed(2)}%)`);
195
- }
285
+ })
286
+ await sendAndConfirm(connection, agent, msgResult)
287
+ await kit.state.record('message', mint, 'said in BTEST — "We are strong!"')
288
+
289
+ assert(kit.state.tick === 5, `tick = 5: ${kit.state.tick}`)
290
+ assert(kit.state.getSentiment(mint) === sentimentBeforeMsg + 0.5, `message bumps sentiment by 0.5: ${kit.state.getSentiment(mint)}`)
196
291
 
197
- // 14. Verify stronghold for agent
198
- console.log('\n14. Verifying stronghold link...');
199
- const stronghold = await getStrongholdForAgent(connection, agent.publicKey);
200
- if (stronghold) {
201
- console.log(` Stronghold: ${stronghold.address}`);
202
- console.log(` Authority: ${stronghold.authority}`);
203
- console.log(` SOL balance: ${stronghold.sol_balance / LAMPORTS_PER_SOL} SOL`);
204
- console.log(` Linked agents: ${stronghold.linked_agents}`);
292
+ // ═══════════════════════════════════════════════════════════════════
293
+ // TEST: AGENT 2 JOIN + SEPARATE STATE
294
+ // ═══════════════════════════════════════════════════════════════════
295
+
296
+ console.log('\n═══ TEST: Agent 2 join (separate state) ═══')
297
+ assert(kit2.state.tick === 0, `agent 2 tick starts at 0: ${kit2.state.tick}`)
298
+
299
+ const join2 = await kit2.actions.join({
300
+ mint, agent: agent2.publicKey, amount_sol: Math.floor(0.3 * LAMPORTS_PER_SOL),
301
+ strategy: 'smelt', message: 'Agent 2 joining', stronghold: agent2.publicKey,
302
+ })
303
+ await sendAndConfirm(connection, agent2, join2)
304
+ await kit2.state.record('join', mint, 'joined BTEST')
305
+
306
+ assert(kit2.state.tick === 1, `agent 2 tick = 1: ${kit2.state.tick}`)
307
+ assert(kit.state.tick === 5, `agent 1 tick unchanged: ${kit.state.tick}`)
308
+ assert(kit2.state.getSentiment(mint) === 1, `agent 2 sentiment = +1 (join): ${kit2.state.getSentiment(mint)}`)
309
+
310
+ // ═══════════════════════════════════════════════════════════════════
311
+ // TEST: RALLY + STATE
312
+ // ═══════════════════════════════════════════════════════════════════
313
+
314
+ console.log('\n═══ TEST: Rally + state ═══')
315
+ const detail1 = await kit.actions.getFaction(mint)
316
+
317
+ const rallyResult = await kit2.actions.rally({
318
+ mint, agent: agent2.publicKey, stronghold: agent2.publicKey,
319
+ })
320
+ await sendAndConfirm(connection, agent2, rallyResult)
321
+ await kit2.state.record('rally', mint, 'rallied BTEST')
322
+ kit2.state.markRallied(mint)
323
+
324
+ const detail2 = await kit.actions.getFaction(mint)
325
+ assert(detail2.rallies > detail1.rallies, `rallies increased: ${detail1.rallies} → ${detail2.rallies}`)
326
+ assert(kit2.state.tick === 2, `agent 2 tick = 2: ${kit2.state.tick}`)
327
+ assert(kit2.state.getSentiment(mint) === 4, `agent 2 sentiment after join(+1) + rally(+3) = 4: ${kit2.state.getSentiment(mint)}`)
328
+ assert(kit2.state.hasRallied(mint), `agent 2 marked as rallied`)
329
+
330
+ // ═══════════════════════════════════════════════════════════════════
331
+ // TEST: SERIALIZE / HYDRATE
332
+ // ═══════════════════════════════════════════════════════════════════
333
+
334
+ console.log('\n═══ TEST: Serialize / Hydrate ═══')
335
+ const serialized = kit.state.serialize()
336
+ console.log(` Serialized: tick=${serialized.tick}, actions=${JSON.stringify(serialized.actionCounts)}, sentiment keys=${Object.keys(serialized.sentiment).length}, history=${serialized.recentHistory.length}`)
337
+
338
+ // Create a fresh kit and hydrate
339
+ const kitRestored = new PyreKit(connection, agent.publicKey)
340
+ kitRestored.state.hydrate(serialized)
341
+
342
+ assert(kitRestored.state.tick === kit.state.tick, `hydrated tick matches: ${kitRestored.state.tick}`)
343
+ assert(kitRestored.state.getSentiment(mint) === kit.state.getSentiment(mint), `hydrated sentiment matches: ${kitRestored.state.getSentiment(mint)}`)
344
+ assert(kitRestored.state.state!.actionCounts.join === kit.state.state!.actionCounts.join, `hydrated join count matches`)
345
+ assert(kitRestored.state.state!.actionCounts.defect === kit.state.state!.actionCounts.defect, `hydrated defect count matches`)
346
+ assert(kitRestored.state.history.length === kit.state.history.length, `hydrated history length matches: ${kitRestored.state.history.length}`)
347
+
348
+ // ═══════════════════════════════════════════════════════════════════
349
+ // TEST: MEMBERS
350
+ // ═══════════════════════════════════════════════════════════════════
351
+
352
+ console.log('\n═══ TEST: Members ═══')
353
+ try {
354
+ const members = await kit.actions.getMembers(mint)
355
+ console.log(` Total members: ${members.total_members}`)
356
+ for (const m of members.members.slice(0, 5)) {
357
+ console.log(` ${m.address.slice(0, 8)}... — ${m.balance} (${m.percentage.toFixed(2)}%)`)
358
+ }
359
+ assert(members.total_members > 0, `has members: ${members.total_members}`)
360
+ } catch (err: any) {
361
+ console.log(` Skipped — RPC error: ${err.message?.slice(0, 80)}`)
205
362
  }
206
363
 
207
- console.log('\n✓ All steps completed successfully');
364
+ // ═══════════════════════════════════════════════════════════════════
365
+ // RESULTS
366
+ // ═══════════════════════════════════════════════════════════════════
367
+
368
+ console.log(`\n${'═'.repeat(50)}`)
369
+ console.log(`Results: ${passed} passed, ${failed} failed`)
370
+ console.log(`${'═'.repeat(50)}`)
371
+
372
+ if (failed > 0) {
373
+ process.exit(1)
374
+ }
208
375
  }
209
376
 
210
377
  main().catch((err) => {
211
- console.error('\n✗ Test failed:', err);
212
- process.exit(1);
213
- });
378
+ console.error('\n✗ Test failed:', err)
379
+ process.exit(1)
380
+ })