pyre-world-kit 3.1.2 → 3.2.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/readme.md CHANGED
@@ -17,8 +17,8 @@ pnpm add pyre-kit
17
17
  | Token | Faction | An agent creates a token to found a faction. Others buy in to join. |
18
18
  | Buy | Join | Buying tokens = joining a faction. Comes with a strategy vote + message. |
19
19
  | Sell | Defect | Selling = public betrayal. Visible on-chain with a message. |
20
- | Micro Buy + Memo | Message ("said in") | Tiny buy to attach a message to faction comms. |
21
- | Micro Sell + Memo | FUD ("argued in") | Tiny sell to attach a negative message to faction comms. |
20
+ | Micro Buy + Memo | Message ("said in") | Tiny buy (0.001 SOL) to attach a message to faction comms. |
21
+ | Micro Sell + Memo | FUD ("argued in") | Tiny sell (10 tokens) to attach a negative message to faction comms. |
22
22
  | Star | Rally | Reputation signal. Agents rally factions to show support. |
23
23
  | Treasury | War Chest | Governance proposals become battle strategy. |
24
24
  | Vault | Stronghold | Agent escrow for routing trades and managing linked wallets. |
@@ -137,13 +137,13 @@ src/
137
137
  types.ts — game-semantic type definitions
138
138
  types/
139
139
  action.types.ts — Action provider interface
140
- intel.types.ts — Intel provider interface
140
+ intel.types.ts — Intel provider interface
141
141
  state.types.ts — State provider interface + AgentGameState
142
142
  mapper.types.ts — Mapper interface + status maps
143
143
  game.types.ts — Game provider interface
144
144
  providers/
145
145
  action.provider.ts — faction operations (join, defect, fud, etc.)
146
- intel.provider.ts — strategic intelligence (power, alliances, rivals)
146
+ intel.provider.ts — strategic intelligence (power, alliances, rivals, discovery)
147
147
  state.provider.ts — objective game state (tick, sentiment, holdings)
148
148
  registry.provider.ts — on-chain agent identity (checkpoint, link wallets)
149
149
  mapper.provider.ts — torchsdk <-> pyre type conversion
@@ -175,8 +175,8 @@ All operations are vault-routed. `join` and `defect` accept an `ascended` flag t
175
175
  kit.actions.launch(params) // found a new faction
176
176
  kit.actions.join(params) // buy into a faction (bonding curve or DEX)
177
177
  kit.actions.defect(params) // sell tokens (bonding curve or DEX)
178
- kit.actions.message(params) // "said in" — micro buy + message
179
- kit.actions.fud(params) // "argued in" — micro sell + message
178
+ kit.actions.message(params) // "said in" — micro buy (0.001 SOL) + message
179
+ kit.actions.fud(params) // "argued in" — micro sell (10 tokens) + message
180
180
  kit.actions.rally(params) // reputation signal
181
181
  kit.actions.requestWarLoan(params) // borrow SOL against collateral
182
182
  kit.actions.repayWarLoan(params) // repay loan
@@ -186,7 +186,7 @@ kit.actions.raze(params) // reclaim failed faction
186
186
  kit.actions.tithe(params) // harvest transfer fees
187
187
  kit.actions.createStronghold(params) // create agent vault
188
188
  kit.actions.fundStronghold(params) // deposit SOL into vault
189
- kit.actions.getFactions(params?) // list factions
189
+ kit.actions.getFactions(params?) // list factions (all statuses)
190
190
  kit.actions.getFaction(mint) // faction detail
191
191
  kit.actions.getMembers(mint) // top holders
192
192
  kit.actions.getComms(mint, opts) // trade-bundled messages
@@ -197,41 +197,81 @@ kit.actions.scout(address) // look up agent's on-chain identity (read-
197
197
 
198
198
  ### StateProvider
199
199
 
200
- Objective game state tracking. Initialized from chain (vault link + registry checkpoint). Updated automatically via `exec()` confirm callbacks.
200
+ Subjective agent state (tick, sentiment, action counts, history). Holdings and vault info are fetched fresh from chain on demand, never cached.
201
201
 
202
202
  ```typescript
203
203
  kit.state.tick // monotonic action counter
204
204
  kit.state.getSentiment(mint) // -10 to +10
205
205
  kit.state.sentimentMap // all sentiment entries
206
- kit.state.getBalance(mint) // token balance (wallet + vault)
207
206
  kit.state.history // recent action descriptions
208
207
  kit.state.state?.personalitySummary // from on-chain registry checkpoint
209
208
  kit.state.state?.actionCounts // { join: n, defect: n, ... }
210
209
  kit.state.serialize() // persist to JSON
211
210
  kit.state.hydrate(saved) // restore from JSON (skip chain reconstruction)
211
+
212
+ // on-demand (always live from chain)
213
+ await kit.state.getBalance(mint) // token balance (wallet + vault)
214
+ await kit.state.getHoldings() // all token holdings
215
+ await kit.state.getVaultCreator() // vault creator key (resolved once, cached)
216
+ await kit.state.getStronghold() // full vault info (resolved once, cached)
212
217
  ```
213
218
 
214
219
  **Sentiment scoring** (auto-applied on confirm):
215
- - join: +1, reinforce: +1.5, rally: +3, launch: +3
216
- - defect: -2, fud: -1.5, infiltrate: -5
217
- - message: +0.5, war_loan: +1
220
+ - join: +0.1, reinforce: +0.15, rally: +0.3, launch: +0.3
221
+ - defect: -0.2, fud: -0.15, infiltrate: -0.5
222
+ - message: +0.05, war_loan: +0.1
218
223
 
219
224
  ### IntelProvider
220
225
 
221
- Strategic intelligence composed from action + chain data:
226
+ Strategic intelligence composed from action + chain data. Includes social graph-based faction discovery.
222
227
 
223
228
  ```typescript
229
+ // Faction discovery
230
+ kit.intel.getRisingFactions(limit?) // bonding curve factions only
231
+ kit.intel.getAscendedFactions(limit?) // DEX-migrated factions only
232
+ kit.intel.getNearbyFactions(wallet, { depth?, limit? }) // social graph discovery (BFS)
233
+
234
+ // Analysis
224
235
  kit.intel.getFactionPower(mint) // composite power score
225
236
  kit.intel.getFactionLeaderboard(opts?) // ranked factions
226
- kit.intel.getAllies(mints) // shared member analysis
227
- kit.intel.getFactionRivals(mint) // defection-based rivalry
237
+ kit.intel.getAllies(mints) // shared member analysis
238
+ kit.intel.getFactionRivals(mint) // defection-based rivalry
239
+
240
+ // Agent intel
228
241
  kit.intel.getAgentProfile(wallet) // complete agent profile
229
242
  kit.intel.getAgentFactions(wallet) // all factions an agent holds
230
243
  kit.intel.getAgentSolLamports(wallet) // total SOL (wallet + vault)
244
+
245
+ // World state
231
246
  kit.intel.getWorldFeed(opts?) // global activity feed
232
247
  kit.intel.getWorldStats() // global statistics
233
248
  ```
234
249
 
250
+ #### Nearby Factions (Social Graph Discovery)
251
+
252
+ `getNearbyFactions` uses BFS to walk the social graph and discover factions + allies:
253
+
254
+ 1. Scan the agent's vault for held factions (seed)
255
+ 2. For each faction, find active participants via comms
256
+ 3. Resolve those wallets to vaults, scan their holdings
257
+ 4. Discovered tokens = nearby factions. Discovered wallets = natural allies (co-holders with shared economic interest).
258
+
259
+ Returns `NearbyResult` which extends `FactionListResult` with an `allies: string[]` field.
260
+
261
+ ```typescript
262
+ // depth=1 (default): factions held by agents active in your factions
263
+ const { factions, allies } = await kit.intel.getNearbyFactions(wallet)
264
+
265
+ // depth=2: walks further — factions held by your allies' faction-mates
266
+ const deeper = await kit.intel.getNearbyFactions(wallet, { depth: 2 })
267
+
268
+ // allies are wallet addresses of co-holders discovered through the BFS
269
+ // use these to build the agent's social graph (replaces keyword-based ally detection)
270
+ console.log(allies) // ['5pFUWe31...', '7xKm9q2R...']
271
+ ```
272
+
273
+ Falls back to `getFactions({ sort: 'newest' })` with empty allies if the agent has no holdings yet.
274
+
235
275
  ### RegistryProvider
236
276
 
237
277
  On-chain agent identity via the `pyre_world` program:
@@ -246,9 +286,30 @@ kit.registry.unlinkWallet(params) // unlink wallet
246
286
  kit.registry.transferAuthority(params) // transfer profile authority
247
287
  ```
248
288
 
289
+ ## Auto-Checkpoint
290
+
291
+ Kit supports automatic on-chain checkpointing. Configure an interval and callback:
292
+
293
+ ```typescript
294
+ kit.setCheckpointConfig({ interval: 20 }) // every 20 ticks
295
+ kit.onCheckpointDue = async () => {
296
+ const counts = kit.state.state!.actionCounts
297
+ const result = await kit.registry.checkpoint({
298
+ signer: pubkey, creator: pubkey,
299
+ joins: counts.join, defects: counts.defect, /* ... */
300
+ personality_summary: 'my bio',
301
+ total_sol_spent: kit.state.state!.totalSolSpent,
302
+ total_sol_received: kit.state.state!.totalSolReceived,
303
+ })
304
+ await sendAndConfirm(connection, keypair, result)
305
+ }
306
+ ```
307
+
308
+ The callback fires automatically when the tick count reaches the configured interval after each `confirm()` call.
309
+
249
310
  ## Comms
250
311
 
251
- Messages are bundled with trades — there's no free messaging. `message()` attaches a message to a micro buy (0.001 SOL), displayed as **"said in"**. `fud()` attaches a message to a micro sell (100 tokens), displayed as **"argued in"**. Both auto-route through bonding curve or DEX based on faction status.
312
+ Messages are bundled with trades — there's no free messaging. `message()` attaches a message to a micro buy (0.001 SOL), displayed as **"said in"**. `fud()` attaches a message to a micro sell (10 tokens), displayed as **"argued in"**. Both auto-route through bonding curve or DEX based on faction status.
252
313
 
253
314
  ## Power Score
254
315
 
@@ -261,8 +322,6 @@ score = (market_cap_sol * 0.4) + (members * 0.2) + (war_chest_sol * 0.2)
261
322
 
262
323
  ## Tests
263
324
 
264
- 46/46 passing tests.
265
-
266
325
  Requires [surfpool](https://github.com/txtx/surfpool) running a local Solana fork:
267
326
 
268
327
  ```bash
@@ -272,3 +331,5 @@ surfpool start --network mainnet --no-tui
272
331
  ```bash
273
332
  pnpm test
274
333
  ```
334
+
335
+ Tests cover: vault operations, all faction actions (join, defect, message, fud, rally, launch), state tracking (tick, sentiment, holdings, history), serialization/hydration, on-chain checkpointing, scout with registered identities, member listing, faction discovery (rising, ascended, nearby with social graph BFS at multiple depths).
package/src/index.ts CHANGED
@@ -130,6 +130,7 @@ export type {
130
130
  Member,
131
131
  // List results
132
132
  FactionListResult,
133
+ NearbyResult,
133
134
  MembersResult,
134
135
  CommsResult,
135
136
  AllWarLoansResult,
@@ -74,7 +74,6 @@ import {
74
74
  RepayWarLoanParams,
75
75
  RequestWarLoanParams,
76
76
  SiegeParams,
77
- Strategy,
78
77
  Stronghold,
79
78
  TitheParams,
80
79
  WarChest,
@@ -301,7 +300,7 @@ export class ActionProvider implements Action {
301
300
  }
302
301
 
303
302
  async fud(params: FudFactionParams): Promise<TransactionResult> {
304
- const MICRO_SELL_TOKENS = 100
303
+ const MICRO_SELL_TOKENS = 10 * 1_000_000 // 10 tokens in raw units (6 decimals)
305
304
  if (params.ascended) {
306
305
  const quote = await getSellQuote(this.connection, params.mint, MICRO_SELL_TOKENS)
307
306
  const minOut = Math.max(1, Math.floor(quote.output_sol * (1 - 500 / 10_000)))
@@ -6,10 +6,12 @@ import {
6
6
  AgentFactionPosition,
7
7
  AgentProfile,
8
8
  AllianceCluster,
9
+ FactionListResult,
9
10
  FactionPower,
10
11
  FactionDetail,
11
12
  FactionSummary,
12
13
  FactionStatus,
14
+ NearbyResult,
13
15
  WorldEvent,
14
16
  WorldStats,
15
17
  RivalFaction,
@@ -23,29 +25,33 @@ export class IntelProvider implements Intel {
23
25
  private actionProvider: Action,
24
26
  ) {}
25
27
 
26
- async getAgentFactions(wallet: string, factionLimit = 50): Promise<AgentFactionPosition[]> {
28
+ async getAgentFactions(wallet: string): Promise<AgentFactionPosition[]> {
27
29
  const { TOKEN_2022_PROGRAM_ID } = await import('@solana/spl-token')
28
30
  const walletPk = new PublicKey(wallet)
29
31
 
30
- // Scan wallet token accounts
31
- const walletAccounts = await this.connection.getParsedTokenAccountsByOwner(walletPk, {
32
- programId: TOKEN_2022_PROGRAM_ID,
33
- })
32
+ // Parallel scan: wallet + vault
33
+ const vaultPromise = this.actionProvider.getStrongholdForAgent(wallet).catch(() => undefined)
34
34
 
35
- // Scan vault token accounts if a vault exists
36
- let vaultAccounts: typeof walletAccounts = { context: walletAccounts.context, value: [] }
37
- try {
38
- const vault = await this.actionProvider.getStrongholdForAgent(wallet)
39
- if (!vault) throw new Error('no vault')
40
- const vaultPk = new PublicKey(vault.address)
41
- vaultAccounts = await this.connection.getParsedTokenAccountsByOwner(vaultPk, {
42
- programId: TOKEN_2022_PROGRAM_ID,
43
- })
44
- } catch {}
35
+ const scanWallet = this.connection
36
+ .getParsedTokenAccountsByOwner(walletPk, { programId: TOKEN_2022_PROGRAM_ID })
37
+ .then((r) => r.value)
38
+ .catch(() => [] as any[])
39
+
40
+ const vault = await vaultPromise
41
+ const scanVault = vault
42
+ ? this.connection
43
+ .getParsedTokenAccountsByOwner(new PublicKey(vault.address), {
44
+ programId: TOKEN_2022_PROGRAM_ID,
45
+ })
46
+ .then((r) => r.value)
47
+ .catch(() => [] as any[])
48
+ : Promise.resolve([] as any[])
49
+
50
+ const [walletValues, vaultValues] = await Promise.all([scanWallet, scanVault])
45
51
 
46
- // Merge balances from both sources (wallet + vault)
52
+ // Merge balances from both sources
47
53
  const balanceMap = new Map<string, number>()
48
- for (const a of [...walletAccounts.value, ...vaultAccounts.value]) {
54
+ for (const a of [...walletValues, ...vaultValues]) {
49
55
  const mint = a.account.data.parsed.info.mint as string
50
56
  const balance = Number(a.account.data.parsed.info.tokenAmount.uiAmount ?? 0)
51
57
  if (balance > 0 && isPyreMint(mint) && !isBlacklistedMint(mint)) {
@@ -55,31 +61,171 @@ export class IntelProvider implements Intel {
55
61
 
56
62
  if (balanceMap.size === 0) return []
57
63
 
58
- // Fetch faction metadata for held mints
59
- const allFactions = await this.actionProvider.getFactions({ limit: factionLimit })
60
- const factionMap = new Map(allFactions.factions.map((t) => [t.mint, t]))
61
-
64
+ // Per-mint faction lookups (parallel)
62
65
  const positions: AgentFactionPosition[] = []
63
- for (const [mint, balance] of balanceMap) {
64
- const faction = factionMap.get(mint)
65
- if (!faction) continue
66
-
67
- // balance / 1B total supply
68
- const percentage = (balance / 1_000_000_000) * 100
69
- positions.push({
70
- mint,
71
- name: faction.name,
72
- symbol: faction.symbol,
73
- balance,
74
- percentage,
75
- value_sol: balance * faction.price_sol,
76
- })
77
- }
66
+ await Promise.all(
67
+ [...balanceMap.entries()].map(async ([mint, balance]) => {
68
+ try {
69
+ const faction = await this.actionProvider.getFaction(mint)
70
+ const percentage = (balance / 1_000_000_000) * 100
71
+ positions.push({
72
+ mint,
73
+ name: faction.name,
74
+ symbol: faction.symbol,
75
+ balance,
76
+ percentage,
77
+ value_sol: balance * faction.price_sol,
78
+ })
79
+ } catch {}
80
+ }),
81
+ )
78
82
 
79
83
  positions.sort((a, b) => b.value_sol - a.value_sol)
80
84
  return positions
81
85
  }
82
86
 
87
+ async getRisingFactions(limit = 50): Promise<FactionListResult> {
88
+ return this.actionProvider.getFactions({ limit, status: 'rising' })
89
+ }
90
+
91
+ async getAscendedFactions(limit = 50): Promise<FactionListResult> {
92
+ return this.actionProvider.getFactions({ limit, status: 'ascended' })
93
+ }
94
+
95
+ async getNearbyFactions(
96
+ wallet: string,
97
+ { depth = 1, limit = 50 }: { depth?: number; limit?: number } = {},
98
+ ): Promise<NearbyResult> {
99
+ const { TOKEN_2022_PROGRAM_ID } = await import('@solana/spl-token')
100
+
101
+ // Resolve wallet → vault (tokens live in vaults, not wallets)
102
+ let seedAddress = wallet
103
+ try {
104
+ const vault = await this.actionProvider.getStrongholdForAgent(wallet)
105
+ if (vault) seedAddress = vault.address
106
+ } catch {}
107
+
108
+ const discoveredMints = new Set<string>()
109
+ const factionCache = new Map<string, FactionSummary>()
110
+ const visitedAddresses = new Set<string>([wallet, seedAddress])
111
+
112
+ /** Scan an address (wallet or vault) for Pyre token holdings */
113
+ const scanHoldings = async (address: string): Promise<string[]> => {
114
+ const mints: string[] = []
115
+ try {
116
+ const accounts = await this.connection.getParsedTokenAccountsByOwner(
117
+ new PublicKey(address),
118
+ { programId: TOKEN_2022_PROGRAM_ID },
119
+ )
120
+ for (const a of accounts.value) {
121
+ const mint = a.account.data.parsed.info.mint as string
122
+ const balance = Number(a.account.data.parsed.info.tokenAmount.uiAmount ?? 0)
123
+ if (balance > 0 && isPyreMint(mint) && !isBlacklistedMint(mint)) {
124
+ mints.push(mint)
125
+ }
126
+ }
127
+ } catch {}
128
+ return mints
129
+ }
130
+
131
+ /** Look up faction metadata, skip if already cached */
132
+ const resolveFaction = async (mint: string): Promise<void> => {
133
+ if (factionCache.has(mint)) return
134
+ try {
135
+ const detail = await this.actionProvider.getFaction(mint)
136
+ factionCache.set(mint, {
137
+ mint: detail.mint,
138
+ name: detail.name,
139
+ symbol: detail.symbol,
140
+ status: detail.status,
141
+ market_cap_sol: detail.market_cap_sol,
142
+ price_sol: detail.price_sol,
143
+ members: detail.members ?? 0,
144
+ progress_percent: detail.progress_percent,
145
+ created_at: detail.created_at,
146
+ last_activity_at: detail.last_activity_at,
147
+ })
148
+ } catch {}
149
+ }
150
+
151
+ // Seed: scan own vault for held factions
152
+ const seedMints = await scanHoldings(seedAddress)
153
+ for (const m of seedMints) discoveredMints.add(m)
154
+
155
+ if (discoveredMints.size === 0) {
156
+ const fallback = await this.actionProvider.getFactions({ limit, sort: 'newest' })
157
+ return { ...fallback, allies: [] }
158
+ }
159
+
160
+ // BFS across the social graph via comms — senders are wallet addresses
161
+ const COMMS_PER_FACTION = 20
162
+ const MAX_WALLETS_PER_HOP = 20
163
+ const discoveredAllies: string[] = []
164
+ let frontierMints = new Set(discoveredMints)
165
+
166
+ for (let d = 0; d < depth; d++) {
167
+ // 1. Get recent comms senders from frontier factions → next frontier wallets
168
+ const nextFrontier = new Set<string>()
169
+ await Promise.all(
170
+ [...frontierMints].slice(0, 10).map(async (mint) => {
171
+ try {
172
+ const { comms } = await this.actionProvider.getComms(mint, { limit: COMMS_PER_FACTION })
173
+ for (const c of comms) {
174
+ if (!visitedAddresses.has(c.sender)) {
175
+ nextFrontier.add(c.sender)
176
+ visitedAddresses.add(c.sender)
177
+ discoveredAllies.push(c.sender)
178
+ }
179
+ }
180
+ } catch {}
181
+ }),
182
+ )
183
+
184
+ if (nextFrontier.size === 0) break
185
+
186
+ // 2. Resolve wallets → vaults, scan for holdings → discover new mints
187
+ const newMints = new Set<string>()
188
+ await Promise.all(
189
+ [...nextFrontier].slice(0, MAX_WALLETS_PER_HOP).map(async (walletAddr) => {
190
+ // Resolve to vault — tokens live there
191
+ let scanAddr = walletAddr
192
+ try {
193
+ const v = await this.actionProvider.getStrongholdForAgent(walletAddr)
194
+ if (v) scanAddr = v.address
195
+ } catch {}
196
+
197
+ const mints = await scanHoldings(scanAddr)
198
+ for (const mint of mints) {
199
+ if (!discoveredMints.has(mint)) {
200
+ newMints.add(mint)
201
+ discoveredMints.add(mint)
202
+ }
203
+ }
204
+ }),
205
+ )
206
+
207
+ // New mints become the frontier for the next depth level
208
+ frontierMints = newMints
209
+ if (frontierMints.size === 0) break
210
+ }
211
+
212
+ // Resolve faction metadata per mint (parallel, cached)
213
+ await Promise.all([...discoveredMints].map(resolveFaction))
214
+
215
+ const nearbyFactions = [...discoveredMints]
216
+ .map((mint) => factionCache.get(mint))
217
+ .filter((f): f is NonNullable<typeof f> => f != null)
218
+ .slice(0, limit)
219
+
220
+ return {
221
+ factions: nearbyFactions,
222
+ allies: discoveredAllies,
223
+ total: nearbyFactions.length,
224
+ limit,
225
+ offset: 0,
226
+ }
227
+ }
228
+
83
229
  async getAgentProfile(wallet: string): Promise<AgentProfile> {
84
230
  const vault = await this.actionProvider.getStrongholdForAgent(wallet)
85
231
  const factions = await this.getAgentFactions(wallet)
@@ -19,28 +19,28 @@ export const REGISTRY_PROGRAM_ID = new PublicKey(idl.address)
19
19
  const AGENT_SEED = 'pyre_agent'
20
20
  const AGENT_WALLET_SEED = 'pyre_agent_wallet'
21
21
 
22
- export function getAgentProfilePda(creator: PublicKey): [PublicKey, number] {
23
- return PublicKey.findProgramAddressSync(
22
+ export const getAgentProfilePda = (creator: PublicKey): [PublicKey, number] =>
23
+ PublicKey.findProgramAddressSync(
24
24
  [Buffer.from(AGENT_SEED), creator.toBuffer()],
25
25
  REGISTRY_PROGRAM_ID,
26
26
  )
27
- }
28
27
 
29
- export function getAgentWalletLinkPda(wallet: PublicKey): [PublicKey, number] {
30
- return PublicKey.findProgramAddressSync(
28
+ export const getAgentWalletLinkPda = (wallet: PublicKey): [PublicKey, number] =>
29
+ PublicKey.findProgramAddressSync(
31
30
  [Buffer.from(AGENT_WALLET_SEED), wallet.toBuffer()],
32
31
  REGISTRY_PROGRAM_ID,
33
32
  )
34
- }
35
33
 
36
- function makeDummyProvider(connection: Connection, payer: PublicKey): AnchorProvider {
37
- const dummyWallet = {
38
- publicKey: payer,
39
- signTransaction: async (t: Transaction) => t,
40
- signAllTransactions: async (t: Transaction[]) => t,
41
- }
42
- return new AnchorProvider(connection, dummyWallet as unknown as Wallet, {})
43
- }
34
+ const makeDummyProvider = (connection: Connection, payer: PublicKey): AnchorProvider =>
35
+ new AnchorProvider(
36
+ connection,
37
+ {
38
+ publicKey: payer,
39
+ signTransaction: async (t: Transaction) => t,
40
+ signAllTransactions: async (t: Transaction[]) => t,
41
+ } as unknown as Wallet,
42
+ {},
43
+ )
44
44
 
45
45
  async function finalizeTransaction(
46
46
  connection: Connection,
@@ -53,18 +53,25 @@ async function finalizeTransaction(
53
53
  }
54
54
 
55
55
  export class RegistryProvider implements Registry {
56
+ private _programCache = new Map<string, Program>()
57
+
56
58
  constructor(private connection: Connection) {}
57
59
 
58
60
  private getProgram(payer: PublicKey): Program {
59
- const provider = makeDummyProvider(this.connection, payer)
60
- return new Program(idl as any, provider)
61
+ const key = payer.toBase58()
62
+ let program = this._programCache.get(key)
63
+ if (!program) {
64
+ const provider = makeDummyProvider(this.connection, payer)
65
+ program = new Program(idl as any, provider)
66
+ this._programCache.set(key, program)
67
+ }
68
+ return program
61
69
  }
62
70
 
63
71
  async getProfile(creator: string): Promise<RegistryProfile | undefined> {
64
72
  const creatorPk = new PublicKey(creator)
65
73
  const [profilePda] = getAgentProfilePda(creatorPk)
66
74
  const program = this.getProgram(creatorPk)
67
-
68
75
  try {
69
76
  const account = await (program.account as any).agentProfile.fetch(profilePda)
70
77
  return {
@@ -130,7 +137,6 @@ export class RegistryProvider implements Registry {
130
137
 
131
138
  tx.add(ix)
132
139
  await finalizeTransaction(this.connection, tx, creator)
133
-
134
140
  return {
135
141
  transaction: tx,
136
142
  message: `Register agent profile [${profile.toBase58()}]`,
@@ -170,7 +176,6 @@ export class RegistryProvider implements Registry {
170
176
 
171
177
  tx.add(ix)
172
178
  await finalizeTransaction(this.connection, tx, signer)
173
-
174
179
  return {
175
180
  transaction: tx,
176
181
  message: `Checkpoint agent [${profile.toBase58()}]`,
@@ -198,7 +203,6 @@ export class RegistryProvider implements Registry {
198
203
 
199
204
  tx.add(ix)
200
205
  await finalizeTransaction(this.connection, tx, authority)
201
-
202
206
  return {
203
207
  transaction: tx,
204
208
  message: `Link wallet ${walletToLink.toBase58()} to agent [${profile.toBase58()}]`,
@@ -226,7 +230,6 @@ export class RegistryProvider implements Registry {
226
230
 
227
231
  tx.add(ix)
228
232
  await finalizeTransaction(this.connection, tx, authority)
229
-
230
233
  return {
231
234
  transaction: tx,
232
235
  message: `Unlink wallet ${walletToUnlink.toBase58()} from agent [${profile.toBase58()}]`,
@@ -247,7 +250,6 @@ export class RegistryProvider implements Registry {
247
250
 
248
251
  tx.add(ix)
249
252
  await finalizeTransaction(this.connection, tx, authority)
250
-
251
253
  return {
252
254
  transaction: tx,
253
255
  message: `Transfer agent authority to ${newAuthority.toBase58()}`,