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
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pyre World Agent Registry Provider
|
|
3
|
+
*
|
|
4
|
+
* On-chain agent identity and state persistence.
|
|
5
|
+
* Agents checkpoint their action distributions and personality summaries
|
|
6
|
+
* so any machine with the wallet key can reconstruct the agent.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Connection, PublicKey, Transaction, SystemProgram } from '@solana/web3.js'
|
|
10
|
+
import { BN, Program, AnchorProvider, type Wallet } from '@coral-xyz/anchor'
|
|
11
|
+
import type { TransactionResult } from 'torchsdk'
|
|
12
|
+
import type {
|
|
13
|
+
RegistryProfile,
|
|
14
|
+
RegistryWalletLink,
|
|
15
|
+
RegisterAgentParams,
|
|
16
|
+
CheckpointParams,
|
|
17
|
+
LinkAgentWalletParams,
|
|
18
|
+
UnlinkAgentWalletParams,
|
|
19
|
+
TransferAgentAuthorityParams,
|
|
20
|
+
} from '../types'
|
|
21
|
+
|
|
22
|
+
import idl from '../pyre_world.json'
|
|
23
|
+
|
|
24
|
+
// ─── Program ID ─────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
export const REGISTRY_PROGRAM_ID = new PublicKey(idl.address)
|
|
27
|
+
|
|
28
|
+
// ─── PDA Seeds ──────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
const AGENT_SEED = 'pyre_agent'
|
|
31
|
+
const AGENT_WALLET_SEED = 'pyre_agent_wallet'
|
|
32
|
+
|
|
33
|
+
// ─── PDA Helpers ────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
export function getAgentProfilePda(creator: PublicKey): [PublicKey, number] {
|
|
36
|
+
return PublicKey.findProgramAddressSync(
|
|
37
|
+
[Buffer.from(AGENT_SEED), creator.toBuffer()],
|
|
38
|
+
REGISTRY_PROGRAM_ID,
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getAgentWalletLinkPda(wallet: PublicKey): [PublicKey, number] {
|
|
43
|
+
return PublicKey.findProgramAddressSync(
|
|
44
|
+
[Buffer.from(AGENT_WALLET_SEED), wallet.toBuffer()],
|
|
45
|
+
REGISTRY_PROGRAM_ID,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── Anchor Program Helper ──────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
function makeDummyProvider(connection: Connection, payer: PublicKey): AnchorProvider {
|
|
52
|
+
const dummyWallet = {
|
|
53
|
+
publicKey: payer,
|
|
54
|
+
signTransaction: async (t: Transaction) => t,
|
|
55
|
+
signAllTransactions: async (t: Transaction[]) => t,
|
|
56
|
+
}
|
|
57
|
+
return new AnchorProvider(connection, dummyWallet as unknown as Wallet, {})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function finalizeTransaction(
|
|
61
|
+
connection: Connection,
|
|
62
|
+
tx: Transaction,
|
|
63
|
+
feePayer: PublicKey,
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
const { blockhash } = await connection.getLatestBlockhash()
|
|
66
|
+
tx.recentBlockhash = blockhash
|
|
67
|
+
tx.feePayer = feePayer
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Provider ───────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
export class RegistryProvider {
|
|
73
|
+
constructor(private connection: Connection) {}
|
|
74
|
+
|
|
75
|
+
private getProgram(payer: PublicKey): Program {
|
|
76
|
+
const provider = makeDummyProvider(this.connection, payer)
|
|
77
|
+
return new Program(idl as any, provider)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── Read Operations ──────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
async getProfile(creator: string): Promise<RegistryProfile | null> {
|
|
83
|
+
const creatorPk = new PublicKey(creator)
|
|
84
|
+
const [profilePda] = getAgentProfilePda(creatorPk)
|
|
85
|
+
const program = this.getProgram(creatorPk)
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const account = await (program.account as any).agentProfile.fetch(profilePda)
|
|
89
|
+
return {
|
|
90
|
+
address: profilePda.toBase58(),
|
|
91
|
+
creator: account.creator.toBase58(),
|
|
92
|
+
authority: account.authority.toBase58(),
|
|
93
|
+
linked_wallet: account.linkedWallet.toBase58(),
|
|
94
|
+
personality_summary: account.personalitySummary,
|
|
95
|
+
last_checkpoint: account.lastCheckpoint.toNumber(),
|
|
96
|
+
joins: account.joins.toNumber(),
|
|
97
|
+
defects: account.defects.toNumber(),
|
|
98
|
+
rallies: account.rallies.toNumber(),
|
|
99
|
+
launches: account.launches.toNumber(),
|
|
100
|
+
messages: account.messages.toNumber(),
|
|
101
|
+
fuds: account.fuds.toNumber(),
|
|
102
|
+
infiltrates: account.infiltrates.toNumber(),
|
|
103
|
+
reinforces: account.reinforces.toNumber(),
|
|
104
|
+
war_loans: account.warLoans.toNumber(),
|
|
105
|
+
repay_loans: account.repayLoans.toNumber(),
|
|
106
|
+
sieges: account.sieges.toNumber(),
|
|
107
|
+
ascends: account.ascends.toNumber(),
|
|
108
|
+
razes: account.razes.toNumber(),
|
|
109
|
+
tithes: account.tithes.toNumber(),
|
|
110
|
+
created_at: account.createdAt.toNumber(),
|
|
111
|
+
bump: account.bump,
|
|
112
|
+
total_sol_spent: account.totalSolSpent?.toNumber() ?? 0,
|
|
113
|
+
total_sol_received: account.totalSolReceived?.toNumber() ?? 0,
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async getWalletLink(wallet: string): Promise<RegistryWalletLink | null> {
|
|
121
|
+
const walletPk = new PublicKey(wallet)
|
|
122
|
+
const [linkPda] = getAgentWalletLinkPda(walletPk)
|
|
123
|
+
const program = this.getProgram(walletPk)
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const account = await (program.account as any).agentWalletLink.fetch(linkPda)
|
|
127
|
+
return {
|
|
128
|
+
address: linkPda.toBase58(),
|
|
129
|
+
profile: account.profile.toBase58(),
|
|
130
|
+
wallet: account.wallet.toBase58(),
|
|
131
|
+
linked_at: account.linkedAt.toNumber(),
|
|
132
|
+
bump: account.bump,
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
return null
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── Transaction Builders ─────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
async register(params: RegisterAgentParams): Promise<TransactionResult> {
|
|
142
|
+
const creator = new PublicKey(params.creator)
|
|
143
|
+
const [profile] = getAgentProfilePda(creator)
|
|
144
|
+
const [walletLink] = getAgentWalletLinkPda(creator)
|
|
145
|
+
const program = this.getProgram(creator)
|
|
146
|
+
|
|
147
|
+
const tx = new Transaction()
|
|
148
|
+
const ix = await (program.methods.register() as any)
|
|
149
|
+
.accounts({ creator, profile, walletLink, systemProgram: SystemProgram.programId })
|
|
150
|
+
.instruction()
|
|
151
|
+
|
|
152
|
+
tx.add(ix)
|
|
153
|
+
await finalizeTransaction(this.connection, tx, creator)
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
transaction: tx,
|
|
157
|
+
message: `Register agent profile [${profile.toBase58()}]`,
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async checkpoint(params: CheckpointParams): Promise<TransactionResult> {
|
|
162
|
+
const signer = new PublicKey(params.signer)
|
|
163
|
+
const creatorPk = new PublicKey(params.creator)
|
|
164
|
+
const [profile] = getAgentProfilePda(creatorPk)
|
|
165
|
+
const program = this.getProgram(signer)
|
|
166
|
+
|
|
167
|
+
const args = {
|
|
168
|
+
joins: new BN(params.joins),
|
|
169
|
+
defects: new BN(params.defects),
|
|
170
|
+
rallies: new BN(params.rallies),
|
|
171
|
+
launches: new BN(params.launches),
|
|
172
|
+
messages: new BN(params.messages),
|
|
173
|
+
fuds: new BN(params.fuds),
|
|
174
|
+
infiltrates: new BN(params.infiltrates),
|
|
175
|
+
reinforces: new BN(params.reinforces),
|
|
176
|
+
warLoans: new BN(params.war_loans),
|
|
177
|
+
repayLoans: new BN(params.repay_loans),
|
|
178
|
+
sieges: new BN(params.sieges),
|
|
179
|
+
ascends: new BN(params.ascends),
|
|
180
|
+
razes: new BN(params.razes),
|
|
181
|
+
tithes: new BN(params.tithes),
|
|
182
|
+
personalitySummary: params.personality_summary,
|
|
183
|
+
totalSolSpent: new BN(params.total_sol_spent),
|
|
184
|
+
totalSolReceived: new BN(params.total_sol_received),
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const tx = new Transaction()
|
|
188
|
+
const ix = await (program.methods.checkpoint(args) as any)
|
|
189
|
+
.accounts({ signer, profile, systemProgram: SystemProgram.programId })
|
|
190
|
+
.instruction()
|
|
191
|
+
|
|
192
|
+
tx.add(ix)
|
|
193
|
+
await finalizeTransaction(this.connection, tx, signer)
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
transaction: tx,
|
|
197
|
+
message: `Checkpoint agent [${profile.toBase58()}]`,
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async linkWallet(params: LinkAgentWalletParams): Promise<TransactionResult> {
|
|
202
|
+
const authority = new PublicKey(params.authority)
|
|
203
|
+
const creatorPk = new PublicKey(params.creator)
|
|
204
|
+
const walletToLink = new PublicKey(params.wallet_to_link)
|
|
205
|
+
const [profile] = getAgentProfilePda(creatorPk)
|
|
206
|
+
const [walletLink] = getAgentWalletLinkPda(walletToLink)
|
|
207
|
+
const program = this.getProgram(authority)
|
|
208
|
+
|
|
209
|
+
const tx = new Transaction()
|
|
210
|
+
const ix = await (program.methods.linkWallet() as any)
|
|
211
|
+
.accounts({
|
|
212
|
+
authority,
|
|
213
|
+
profile,
|
|
214
|
+
walletToLink,
|
|
215
|
+
walletLink,
|
|
216
|
+
systemProgram: SystemProgram.programId,
|
|
217
|
+
})
|
|
218
|
+
.instruction()
|
|
219
|
+
|
|
220
|
+
tx.add(ix)
|
|
221
|
+
await finalizeTransaction(this.connection, tx, authority)
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
transaction: tx,
|
|
225
|
+
message: `Link wallet ${walletToLink.toBase58()} to agent [${profile.toBase58()}]`,
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async unlinkWallet(params: UnlinkAgentWalletParams): Promise<TransactionResult> {
|
|
230
|
+
const authority = new PublicKey(params.authority)
|
|
231
|
+
const creatorPk = new PublicKey(params.creator)
|
|
232
|
+
const walletToUnlink = new PublicKey(params.wallet_to_unlink)
|
|
233
|
+
const [profile] = getAgentProfilePda(creatorPk)
|
|
234
|
+
const [walletLink] = getAgentWalletLinkPda(walletToUnlink)
|
|
235
|
+
const program = this.getProgram(authority)
|
|
236
|
+
|
|
237
|
+
const tx = new Transaction()
|
|
238
|
+
const ix = await (program.methods.unlinkWallet() as any)
|
|
239
|
+
.accounts({
|
|
240
|
+
authority,
|
|
241
|
+
profile,
|
|
242
|
+
walletToUnlink,
|
|
243
|
+
walletLink,
|
|
244
|
+
systemProgram: SystemProgram.programId,
|
|
245
|
+
})
|
|
246
|
+
.instruction()
|
|
247
|
+
|
|
248
|
+
tx.add(ix)
|
|
249
|
+
await finalizeTransaction(this.connection, tx, authority)
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
transaction: tx,
|
|
253
|
+
message: `Unlink wallet ${walletToUnlink.toBase58()} from agent [${profile.toBase58()}]`,
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async transferAuthority(params: TransferAgentAuthorityParams): Promise<TransactionResult> {
|
|
258
|
+
const authority = new PublicKey(params.authority)
|
|
259
|
+
const creatorPk = new PublicKey(params.creator)
|
|
260
|
+
const newAuthority = new PublicKey(params.new_authority)
|
|
261
|
+
const [profile] = getAgentProfilePda(creatorPk)
|
|
262
|
+
const program = this.getProgram(authority)
|
|
263
|
+
|
|
264
|
+
const tx = new Transaction()
|
|
265
|
+
const ix = await (program.methods.transferAuthority() as any)
|
|
266
|
+
.accounts({ authority, profile, newAuthority })
|
|
267
|
+
.instruction()
|
|
268
|
+
|
|
269
|
+
tx.add(ix)
|
|
270
|
+
await finalizeTransaction(this.connection, tx, authority)
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
transaction: tx,
|
|
274
|
+
message: `Transfer agent authority to ${newAuthority.toBase58()}`,
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pyre Kit State Provider
|
|
3
|
+
*
|
|
4
|
+
* Objective game state tracking for an agent.
|
|
5
|
+
* Owns holdings, action counts, vault resolution, tick counter.
|
|
6
|
+
* Injected into ActionProvider so every action automatically updates state.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Connection, PublicKey } from '@solana/web3.js'
|
|
10
|
+
import type {
|
|
11
|
+
State,
|
|
12
|
+
AgentGameState,
|
|
13
|
+
SerializedGameState,
|
|
14
|
+
TrackedAction,
|
|
15
|
+
CheckpointConfig,
|
|
16
|
+
} from '../types/state.types'
|
|
17
|
+
import { RegistryProvider } from './registry.provider'
|
|
18
|
+
import { isPyreMint } from '../vanity'
|
|
19
|
+
import { isBlacklistedMint } from '../util'
|
|
20
|
+
|
|
21
|
+
const EMPTY_COUNTS: Record<TrackedAction, number> = {
|
|
22
|
+
join: 0,
|
|
23
|
+
defect: 0,
|
|
24
|
+
rally: 0,
|
|
25
|
+
launch: 0,
|
|
26
|
+
message: 0,
|
|
27
|
+
reinforce: 0,
|
|
28
|
+
war_loan: 0,
|
|
29
|
+
repay_loan: 0,
|
|
30
|
+
siege: 0,
|
|
31
|
+
ascend: 0,
|
|
32
|
+
raze: 0,
|
|
33
|
+
tithe: 0,
|
|
34
|
+
infiltrate: 0,
|
|
35
|
+
fud: 0,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class StateProvider implements State {
|
|
39
|
+
private _state: AgentGameState | null = null
|
|
40
|
+
private checkpointConfig: CheckpointConfig | null = null
|
|
41
|
+
private ticksSinceCheckpoint = 0
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
private connection: Connection,
|
|
45
|
+
private publicKey: string,
|
|
46
|
+
private registry: RegistryProvider,
|
|
47
|
+
) {}
|
|
48
|
+
|
|
49
|
+
// ─── Readonly accessors ─────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
get state() {
|
|
52
|
+
return this._state
|
|
53
|
+
}
|
|
54
|
+
get vaultCreator() {
|
|
55
|
+
return this._state?.vaultCreator ?? null
|
|
56
|
+
}
|
|
57
|
+
get initialized() {
|
|
58
|
+
return this._state?.initialized ?? false
|
|
59
|
+
}
|
|
60
|
+
get tick() {
|
|
61
|
+
return this._state?.tick ?? 0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Configuration ──────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
/** Configure auto-checkpoint behavior */
|
|
67
|
+
setCheckpointConfig(config: CheckpointConfig) {
|
|
68
|
+
this.checkpointConfig = config
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── Initialization ─────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
async init(): Promise<AgentGameState> {
|
|
74
|
+
if (this._state?.initialized) return this._state
|
|
75
|
+
|
|
76
|
+
const state: AgentGameState = {
|
|
77
|
+
publicKey: this.publicKey,
|
|
78
|
+
vaultCreator: null,
|
|
79
|
+
stronghold: null,
|
|
80
|
+
tick: 0,
|
|
81
|
+
actionCounts: { ...EMPTY_COUNTS },
|
|
82
|
+
holdings: new Map(),
|
|
83
|
+
activeLoans: new Set(),
|
|
84
|
+
founded: [],
|
|
85
|
+
rallied: new Set(),
|
|
86
|
+
voted: new Set(),
|
|
87
|
+
sentiment: new Map(),
|
|
88
|
+
recentHistory: [],
|
|
89
|
+
personalitySummary: null,
|
|
90
|
+
totalSolSpent: 0,
|
|
91
|
+
totalSolReceived: 0,
|
|
92
|
+
initialized: false,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this._state = state
|
|
96
|
+
|
|
97
|
+
// Resolve vault link
|
|
98
|
+
const { getVaultForWallet } = await import('torchsdk')
|
|
99
|
+
try {
|
|
100
|
+
const vault = await getVaultForWallet(this.connection, this.publicKey)
|
|
101
|
+
if (vault) {
|
|
102
|
+
state.vaultCreator = vault.creator
|
|
103
|
+
state.stronghold = {
|
|
104
|
+
address: vault.address,
|
|
105
|
+
creator: vault.creator,
|
|
106
|
+
authority: vault.authority,
|
|
107
|
+
sol_balance: vault.sol_balance,
|
|
108
|
+
total_deposited: vault.total_deposited,
|
|
109
|
+
total_withdrawn: vault.total_withdrawn,
|
|
110
|
+
total_spent: vault.total_spent,
|
|
111
|
+
total_received: vault.total_received,
|
|
112
|
+
linked_agents: vault.linked_wallets,
|
|
113
|
+
created_at: vault.created_at,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch {}
|
|
117
|
+
|
|
118
|
+
// Load registry profile — personality, action counts, SOL totals
|
|
119
|
+
try {
|
|
120
|
+
const profile = await this.registry.getProfile(this.publicKey)
|
|
121
|
+
if (profile) {
|
|
122
|
+
state.personalitySummary = profile.personality_summary || null
|
|
123
|
+
state.totalSolSpent = profile.total_sol_spent
|
|
124
|
+
state.totalSolReceived = profile.total_sol_received
|
|
125
|
+
// Seed action counts from on-chain checkpoint
|
|
126
|
+
state.actionCounts.join = Math.max(state.actionCounts.join, profile.joins)
|
|
127
|
+
state.actionCounts.defect = Math.max(state.actionCounts.defect, profile.defects)
|
|
128
|
+
state.actionCounts.rally = Math.max(state.actionCounts.rally, profile.rallies)
|
|
129
|
+
state.actionCounts.launch = Math.max(state.actionCounts.launch, profile.launches)
|
|
130
|
+
state.actionCounts.message = Math.max(state.actionCounts.message, profile.messages)
|
|
131
|
+
state.actionCounts.reinforce = Math.max(state.actionCounts.reinforce, profile.reinforces)
|
|
132
|
+
state.actionCounts.fud = Math.max(state.actionCounts.fud, profile.fuds)
|
|
133
|
+
state.actionCounts.infiltrate = Math.max(state.actionCounts.infiltrate, profile.infiltrates)
|
|
134
|
+
state.actionCounts.war_loan = Math.max(state.actionCounts.war_loan, profile.war_loans)
|
|
135
|
+
state.actionCounts.repay_loan = Math.max(state.actionCounts.repay_loan, profile.repay_loans)
|
|
136
|
+
state.actionCounts.siege = Math.max(state.actionCounts.siege, profile.sieges)
|
|
137
|
+
state.actionCounts.ascend = Math.max(state.actionCounts.ascend, profile.ascends)
|
|
138
|
+
state.actionCounts.raze = Math.max(state.actionCounts.raze, profile.razes)
|
|
139
|
+
state.actionCounts.tithe = Math.max(state.actionCounts.tithe, profile.tithes)
|
|
140
|
+
// Set tick to total actions from checkpoint
|
|
141
|
+
const totalFromCheckpoint = Object.values(state.actionCounts).reduce((a, b) => a + b, 0)
|
|
142
|
+
state.tick = totalFromCheckpoint
|
|
143
|
+
}
|
|
144
|
+
} catch {}
|
|
145
|
+
|
|
146
|
+
// Load holdings (wallet + vault token accounts)
|
|
147
|
+
await this.refreshHoldings()
|
|
148
|
+
|
|
149
|
+
state.initialized = true
|
|
150
|
+
return state
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─── Action Recording ───────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
async record(action: TrackedAction, mint?: string, description?: string): Promise<void> {
|
|
156
|
+
if (!this._state) throw new Error('State not initialized — call init() first')
|
|
157
|
+
|
|
158
|
+
this._state.tick++
|
|
159
|
+
this._state.actionCounts[action]++
|
|
160
|
+
this.ticksSinceCheckpoint++
|
|
161
|
+
|
|
162
|
+
// Track founded factions
|
|
163
|
+
if (action === 'launch' && mint) {
|
|
164
|
+
this._state.founded.push(mint)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Update sentiment for the target faction
|
|
168
|
+
if (mint) {
|
|
169
|
+
this.updateSentiment(action, mint)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Track recent history for LLM memory block
|
|
173
|
+
if (description) {
|
|
174
|
+
this._state.recentHistory.push(description)
|
|
175
|
+
if (this._state.recentHistory.length > 20) {
|
|
176
|
+
this._state.recentHistory = this._state.recentHistory.slice(-20)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Refresh holdings after any trade action
|
|
181
|
+
await this.refreshHoldings()
|
|
182
|
+
|
|
183
|
+
// Auto-checkpoint check
|
|
184
|
+
if (this.checkpointConfig && this.ticksSinceCheckpoint >= this.checkpointConfig.interval) {
|
|
185
|
+
this.ticksSinceCheckpoint = 0
|
|
186
|
+
this.onCheckpointDue?.()
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Update sentiment score for a faction based on action type */
|
|
191
|
+
private updateSentiment(action: TrackedAction, mint: string): void {
|
|
192
|
+
if (!this._state) return
|
|
193
|
+
|
|
194
|
+
const current = this._state.sentiment.get(mint) ?? 0
|
|
195
|
+
|
|
196
|
+
const SENTIMENT_DELTAS: Partial<Record<TrackedAction, number>> = {
|
|
197
|
+
join: 1,
|
|
198
|
+
reinforce: 1.5,
|
|
199
|
+
defect: -2,
|
|
200
|
+
rally: 3,
|
|
201
|
+
infiltrate: -5,
|
|
202
|
+
message: 0.5,
|
|
203
|
+
fud: -1.5,
|
|
204
|
+
war_loan: 1,
|
|
205
|
+
launch: 3,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const delta = SENTIMENT_DELTAS[action] ?? 0
|
|
209
|
+
if (delta !== 0) {
|
|
210
|
+
this._state.sentiment.set(mint, Math.max(-10, Math.min(10, current + delta)))
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Callback set by PyreKit to handle checkpoint triggers */
|
|
215
|
+
onCheckpointDue: (() => void) | null = null
|
|
216
|
+
|
|
217
|
+
// ─── Holdings ───────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
async refreshHoldings(): Promise<void> {
|
|
220
|
+
if (!this._state) return
|
|
221
|
+
|
|
222
|
+
const { TOKEN_2022_PROGRAM_ID } = await import('@solana/spl-token')
|
|
223
|
+
const walletPk = new PublicKey(this.publicKey)
|
|
224
|
+
|
|
225
|
+
// Scan wallet token accounts
|
|
226
|
+
let walletValues: any[] = []
|
|
227
|
+
try {
|
|
228
|
+
const walletAccounts = await this.connection.getParsedTokenAccountsByOwner(walletPk, {
|
|
229
|
+
programId: TOKEN_2022_PROGRAM_ID,
|
|
230
|
+
})
|
|
231
|
+
walletValues = walletAccounts.value
|
|
232
|
+
} catch {}
|
|
233
|
+
|
|
234
|
+
// Scan vault token accounts
|
|
235
|
+
let vaultValues: any[] = []
|
|
236
|
+
if (this._state.stronghold) {
|
|
237
|
+
try {
|
|
238
|
+
const vaultPk = new PublicKey(this._state.stronghold.address)
|
|
239
|
+
const vaultAccounts = await this.connection.getParsedTokenAccountsByOwner(vaultPk, {
|
|
240
|
+
programId: TOKEN_2022_PROGRAM_ID,
|
|
241
|
+
})
|
|
242
|
+
vaultValues = vaultAccounts.value
|
|
243
|
+
} catch {}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Merge balances
|
|
247
|
+
const newHoldings = new Map<string, number>()
|
|
248
|
+
for (const a of [...walletValues, ...vaultValues]) {
|
|
249
|
+
const mint = a.account.data.parsed.info.mint as string
|
|
250
|
+
const balance = Number(a.account.data.parsed.info.tokenAmount.uiAmount ?? 0)
|
|
251
|
+
if (balance > 0 && isPyreMint(mint) && !isBlacklistedMint(mint)) {
|
|
252
|
+
newHoldings.set(mint, (newHoldings.get(mint) ?? 0) + balance)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Update — clear stale, set fresh
|
|
257
|
+
this._state.holdings.clear()
|
|
258
|
+
for (const [mint, balance] of newHoldings) {
|
|
259
|
+
this._state.holdings.set(mint, balance)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
getSentiment(mint: string): number {
|
|
264
|
+
return this._state?.sentiment.get(mint) ?? 0
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
get sentimentMap(): ReadonlyMap<string, number> {
|
|
268
|
+
return this._state?.sentiment ?? new Map()
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
get history(): readonly string[] {
|
|
272
|
+
return this._state?.recentHistory ?? []
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
getBalance(mint: string): number {
|
|
276
|
+
return this._state?.holdings.get(mint) ?? 0
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ─── Dedup Guards ───────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
hasVoted(mint: string): boolean {
|
|
282
|
+
return this._state?.voted.has(mint) ?? false
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
hasRallied(mint: string): boolean {
|
|
286
|
+
return this._state?.rallied.has(mint) ?? false
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
markVoted(mint: string): void {
|
|
290
|
+
this._state?.voted.add(mint)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
markRallied(mint: string): void {
|
|
294
|
+
this._state?.rallied.add(mint)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ─── Serialization ──────────────────────────────────────────────
|
|
298
|
+
|
|
299
|
+
serialize(): SerializedGameState {
|
|
300
|
+
if (!this._state) {
|
|
301
|
+
return {
|
|
302
|
+
publicKey: this.publicKey,
|
|
303
|
+
vaultCreator: null,
|
|
304
|
+
tick: 0,
|
|
305
|
+
actionCounts: { ...EMPTY_COUNTS },
|
|
306
|
+
holdings: {},
|
|
307
|
+
activeLoans: [],
|
|
308
|
+
founded: [],
|
|
309
|
+
rallied: [],
|
|
310
|
+
voted: [],
|
|
311
|
+
sentiment: {},
|
|
312
|
+
recentHistory: [],
|
|
313
|
+
personalitySummary: null,
|
|
314
|
+
totalSolSpent: 0,
|
|
315
|
+
totalSolReceived: 0,
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
publicKey: this._state.publicKey,
|
|
321
|
+
vaultCreator: this._state.vaultCreator,
|
|
322
|
+
tick: this._state.tick,
|
|
323
|
+
actionCounts: { ...this._state.actionCounts },
|
|
324
|
+
holdings: Object.fromEntries(this._state.holdings),
|
|
325
|
+
activeLoans: Array.from(this._state.activeLoans),
|
|
326
|
+
founded: [...this._state.founded],
|
|
327
|
+
rallied: Array.from(this._state.rallied),
|
|
328
|
+
voted: Array.from(this._state.voted),
|
|
329
|
+
sentiment: Object.fromEntries(this._state.sentiment),
|
|
330
|
+
recentHistory: this._state.recentHistory.slice(-20),
|
|
331
|
+
personalitySummary: this._state.personalitySummary,
|
|
332
|
+
totalSolSpent: this._state.totalSolSpent,
|
|
333
|
+
totalSolReceived: this._state.totalSolReceived,
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
hydrate(saved: SerializedGameState): void {
|
|
338
|
+
this._state = {
|
|
339
|
+
publicKey: saved.publicKey,
|
|
340
|
+
vaultCreator: saved.vaultCreator,
|
|
341
|
+
stronghold: null, // will be resolved on next refreshHoldings or init
|
|
342
|
+
tick: saved.tick,
|
|
343
|
+
actionCounts: { ...EMPTY_COUNTS, ...saved.actionCounts },
|
|
344
|
+
holdings: new Map(Object.entries(saved.holdings)),
|
|
345
|
+
activeLoans: new Set(saved.activeLoans),
|
|
346
|
+
founded: [...saved.founded],
|
|
347
|
+
rallied: new Set(saved.rallied),
|
|
348
|
+
voted: new Set(saved.voted),
|
|
349
|
+
sentiment: new Map(Object.entries(saved.sentiment)),
|
|
350
|
+
recentHistory: [...saved.recentHistory],
|
|
351
|
+
personalitySummary: saved.personalitySummary,
|
|
352
|
+
totalSolSpent: saved.totalSolSpent,
|
|
353
|
+
totalSolReceived: saved.totalSolReceived,
|
|
354
|
+
initialized: true,
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|