pyre-world-kit 3.0.0 → 3.0.2

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/dist/index.d.ts CHANGED
@@ -8,15 +8,15 @@
8
8
  import { Connection } from '@solana/web3.js';
9
9
  import { ActionProvider } from './providers/action.provider';
10
10
  import { IntelProvider } from './providers/intel.provider';
11
- import { RegistryProvider } from './providers/registry.provider';
12
11
  import { StateProvider } from './providers/state.provider';
13
12
  import type { Action } from './types/action.types';
14
13
  import type { Intel } from './types/intel.types';
15
14
  import type { CheckpointConfig } from './types/state.types';
15
+ import { Registry } from './types/registry.types';
16
16
  export declare class PyreKit {
17
17
  readonly actions: ActionProvider;
18
18
  readonly intel: IntelProvider;
19
- readonly registry: RegistryProvider;
19
+ readonly registry: Registry;
20
20
  readonly state: StateProvider;
21
21
  constructor(connection: Connection, publicKey: string);
22
22
  /** Callback fired when checkpoint interval is reached */
@@ -24,11 +24,19 @@ export declare class PyreKit {
24
24
  /** Configure auto-checkpoint behavior */
25
25
  setCheckpointConfig(config: CheckpointConfig): void;
26
26
  /**
27
- * Execute an action with automatic state tracking.
27
+ * Execute an action with deferred state tracking.
28
28
  * On first call, initializes state from chain instead of executing.
29
- * After execution, records the action and updates state.
29
+ *
30
+ * Returns { result, confirm }. Call confirm() after the transaction
31
+ * is signed and confirmed on-chain. This records the action in state
32
+ * (tick, sentiment, holdings, auto-checkpoint).
33
+ *
34
+ * For read-only methods (getFactions, getComms, etc.), confirm is a no-op.
30
35
  */
31
- exec<T extends 'actions' | 'intel'>(provider: T, method: T extends 'actions' ? keyof Action : keyof Intel, ...args: any[]): Promise<any>;
36
+ exec<T extends 'actions' | 'intel'>(provider: T, method: T extends 'actions' ? keyof Action : keyof Intel, ...args: any[]): Promise<{
37
+ result: any;
38
+ confirm: () => Promise<void>;
39
+ }>;
32
40
  /** Map action method names to tracked action types */
33
41
  private methodToAction;
34
42
  }
package/dist/index.js CHANGED
@@ -20,8 +20,8 @@ class PyreKit {
20
20
  state;
21
21
  constructor(connection, publicKey) {
22
22
  this.registry = new registry_provider_1.RegistryProvider(connection);
23
- this.state = new state_provider_1.StateProvider(connection, publicKey, this.registry);
24
- this.actions = new action_provider_1.ActionProvider(connection);
23
+ this.state = new state_provider_1.StateProvider(connection, this.registry, publicKey);
24
+ this.actions = new action_provider_1.ActionProvider(connection, this.registry);
25
25
  this.intel = new intel_provider_1.IntelProvider(connection, this.actions);
26
26
  // Wire auto-checkpoint callback
27
27
  this.state.onCheckpointDue = () => this.onCheckpointDue?.();
@@ -33,34 +33,39 @@ class PyreKit {
33
33
  this.state.setCheckpointConfig(config);
34
34
  }
35
35
  /**
36
- * Execute an action with automatic state tracking.
36
+ * Execute an action with deferred state tracking.
37
37
  * On first call, initializes state from chain instead of executing.
38
- * After execution, records the action and updates state.
38
+ *
39
+ * Returns { result, confirm }. Call confirm() after the transaction
40
+ * is signed and confirmed on-chain. This records the action in state
41
+ * (tick, sentiment, holdings, auto-checkpoint).
42
+ *
43
+ * For read-only methods (getFactions, getComms, etc.), confirm is a no-op.
39
44
  */
40
45
  async exec(provider, method, ...args) {
41
46
  // First exec: initialize state
42
47
  if (!this.state.initialized) {
43
48
  await this.state.init();
44
- return null;
49
+ return { result: null, confirm: async () => { } };
45
50
  }
46
51
  const target = provider === 'actions' ? this.actions : this.intel;
47
52
  const fn = target[method];
48
53
  if (typeof fn !== 'function')
49
54
  throw new Error(`Unknown method: ${provider}.${String(method)}`);
50
55
  const result = await fn.call(target, ...args);
51
- // Track action if it's a state-mutating action method
52
- if (provider === 'actions') {
53
- const action = this.methodToAction(method);
54
- if (action) {
56
+ // Build confirm callback for state-mutating actions
57
+ const trackedAction = provider === 'actions' ? this.methodToAction(method) : null;
58
+ const confirm = trackedAction
59
+ ? async () => {
55
60
  const mint = args[0]?.mint;
56
61
  const message = args[0]?.message;
57
62
  const description = message
58
- ? `${action} ${mint?.slice(0, 8) ?? '?'} — "${message}"`
59
- : `${action} ${mint?.slice(0, 8) ?? '?'}`;
60
- await this.state.record(action, mint, description);
63
+ ? `${trackedAction} ${mint?.slice(0, 8) ?? '?'} — "${message}"`
64
+ : `${trackedAction} ${mint?.slice(0, 8) ?? '?'}`;
65
+ await this.state.record(trackedAction, mint, description);
61
66
  }
62
- }
63
- return result;
67
+ : async () => { }; // no-op for reads
68
+ return { result, confirm };
64
69
  }
65
70
  /** Map action method names to tracked action types */
66
71
  methodToAction(method) {
@@ -2,10 +2,12 @@ import { Connection } from '@solana/web3.js';
2
2
  import { Action } from '../types/action.types';
3
3
  import { BuyQuoteResult, SellQuoteResult, TransactionResult } from 'torchsdk';
4
4
  import { AgentLink, AllWarLoansResult, AscendParams, ClaimSpoilsParams, CommsResult, CoupParams, CreateStrongholdParams, DefectParams, ExileAgentParams, FactionDetail, FactionListParams, FactionListResult, FactionStatus, FudFactionParams, FundStrongholdParams, JoinFactionParams, JoinFactionResult, LaunchFactionParams, LaunchFactionResult, MembersResult, MessageFactionParams, RallyParams, RazeParams, RecruitAgentParams, RepayWarLoanParams, RequestWarLoanParams, SiegeParams, Stronghold, TitheParams, WarChest, WarLoan, WarLoanQuote, WithdrawAssetsParams, WithdrawFromStrongholdParams } from '../types';
5
+ import { Registry } from '../types/registry.types';
5
6
  export declare class ActionProvider implements Action {
6
7
  private connection;
8
+ private registryProvider;
7
9
  private mapper;
8
- constructor(connection: Connection);
10
+ constructor(connection: Connection, registryProvider: Registry);
9
11
  createStronghold(params: CreateStrongholdParams): Promise<TransactionResult>;
10
12
  coup(params: CoupParams): Promise<TransactionResult>;
11
13
  exileAgent(params: ExileAgentParams): Promise<TransactionResult>;
@@ -41,6 +43,7 @@ export declare class ActionProvider implements Action {
41
43
  raze(params: RazeParams): Promise<TransactionResult>;
42
44
  repayWarLoan(params: RepayWarLoanParams): Promise<TransactionResult>;
43
45
  requestWarLoan(params: RequestWarLoanParams): Promise<TransactionResult>;
46
+ scout(targetAddress: string): Promise<string>;
44
47
  siege(params: SiegeParams): Promise<TransactionResult>;
45
48
  tithe(params: TitheParams): Promise<TransactionResult>;
46
49
  }
@@ -8,9 +8,11 @@ const vanity_1 = require("../vanity");
8
8
  const util_1 = require("../util");
9
9
  class ActionProvider {
10
10
  connection;
11
+ registryProvider;
11
12
  mapper = new mapper_provider_1.MapperProvider();
12
- constructor(connection) {
13
+ constructor(connection, registryProvider) {
13
14
  this.connection = connection;
15
+ this.registryProvider = registryProvider;
14
16
  }
15
17
  async createStronghold(params) {
16
18
  return (0, torchsdk_1.buildCreateVaultTransaction)(this.connection, { creator: params.creator });
@@ -310,6 +312,55 @@ class ActionProvider {
310
312
  vault: params.stronghold,
311
313
  });
312
314
  }
315
+ async scout(targetAddress) {
316
+ try {
317
+ const p = await this.registryProvider.getProfile(targetAddress);
318
+ if (!p)
319
+ return ` @${targetAddress.slice(0, 8)}: no pyre identity found`;
320
+ const total = p.joins +
321
+ p.defects +
322
+ p.rallies +
323
+ p.launches +
324
+ p.messages +
325
+ p.fuds +
326
+ p.infiltrates +
327
+ p.reinforces +
328
+ p.war_loans +
329
+ p.repay_loans +
330
+ p.sieges +
331
+ p.ascends +
332
+ p.razes +
333
+ p.tithes;
334
+ const topActions = [
335
+ { n: 'joins', v: p.joins },
336
+ { n: 'defects', v: p.defects },
337
+ { n: 'rallies', v: p.rallies },
338
+ { n: 'messages', v: p.messages },
339
+ { n: 'fuds', v: p.fuds },
340
+ { n: 'infiltrates', v: p.infiltrates },
341
+ { n: 'reinforces', v: p.reinforces },
342
+ { n: 'war_loans', v: p.war_loans },
343
+ { n: 'sieges', v: p.sieges },
344
+ ]
345
+ .sort((a, b) => b.v - a.v)
346
+ .filter((a) => a.v > 0)
347
+ .slice(0, 4)
348
+ .map((a) => `${a.n}:${a.v}`)
349
+ .join(', ');
350
+ const personality = p.personality_summary || 'unknown';
351
+ const checkpoint = p.last_checkpoint > 0
352
+ ? new Date(p.last_checkpoint * 1000).toISOString().slice(0, 10)
353
+ : 'never';
354
+ const spent = (p.total_sol_spent ?? 0) / 1e9;
355
+ const received = (p.total_sol_received ?? 0) / 1e9;
356
+ const pnl = received - spent;
357
+ const pnlStr = pnl >= 0 ? `+${pnl.toFixed(3)}` : pnl.toFixed(3);
358
+ return ` @${targetAddress.slice(0, 8)}: "${personality}" | ${total} actions (${topActions}) | P&L: ${pnlStr} SOL | last seen: ${checkpoint}`;
359
+ }
360
+ catch {
361
+ return ` @${targetAddress.slice(0, 8)}: lookup failed`;
362
+ }
363
+ }
313
364
  async siege(params) {
314
365
  return (0, torchsdk_1.buildLiquidateTransaction)(this.connection, {
315
366
  mint: params.mint,
@@ -8,15 +8,16 @@
8
8
  import { Connection, PublicKey } from '@solana/web3.js';
9
9
  import type { TransactionResult } from 'torchsdk';
10
10
  import type { RegistryProfile, RegistryWalletLink, RegisterAgentParams, CheckpointParams, LinkAgentWalletParams, UnlinkAgentWalletParams, TransferAgentAuthorityParams } from '../types';
11
+ import { Registry } from '../types/registry.types';
11
12
  export declare const REGISTRY_PROGRAM_ID: PublicKey;
12
13
  export declare function getAgentProfilePda(creator: PublicKey): [PublicKey, number];
13
14
  export declare function getAgentWalletLinkPda(wallet: PublicKey): [PublicKey, number];
14
- export declare class RegistryProvider {
15
+ export declare class RegistryProvider implements Registry {
15
16
  private connection;
16
17
  constructor(connection: Connection);
17
18
  private getProgram;
18
- getProfile(creator: string): Promise<RegistryProfile | null>;
19
- getWalletLink(wallet: string): Promise<RegistryWalletLink | null>;
19
+ getProfile(creator: string): Promise<RegistryProfile | undefined>;
20
+ getWalletLink(wallet: string): Promise<RegistryWalletLink | undefined>;
20
21
  register(params: RegisterAgentParams): Promise<TransactionResult>;
21
22
  checkpoint(params: CheckpointParams): Promise<TransactionResult>;
22
23
  linkWallet(params: LinkAgentWalletParams): Promise<TransactionResult>;
@@ -87,7 +87,7 @@ class RegistryProvider {
87
87
  };
88
88
  }
89
89
  catch {
90
- return null;
90
+ return undefined;
91
91
  }
92
92
  }
93
93
  async getWalletLink(wallet) {
@@ -105,7 +105,7 @@ class RegistryProvider {
105
105
  };
106
106
  }
107
107
  catch {
108
- return null;
108
+ return undefined;
109
109
  }
110
110
  }
111
111
  // ─── Transaction Builders ─────────────────────────────────────────
@@ -7,15 +7,15 @@
7
7
  */
8
8
  import { Connection } from '@solana/web3.js';
9
9
  import type { State, AgentGameState, SerializedGameState, TrackedAction, CheckpointConfig } from '../types/state.types';
10
- import { RegistryProvider } from './registry.provider';
10
+ import { Registry } from '../types/registry.types';
11
11
  export declare class StateProvider implements State {
12
12
  private connection;
13
- private publicKey;
14
13
  private registry;
14
+ private publicKey;
15
15
  private _state;
16
16
  private checkpointConfig;
17
17
  private ticksSinceCheckpoint;
18
- constructor(connection: Connection, publicKey: string, registry: RegistryProvider);
18
+ constructor(connection: Connection, registry: Registry, publicKey: string);
19
19
  get state(): AgentGameState | null;
20
20
  get vaultCreator(): string | null;
21
21
  get initialized(): boolean;
@@ -62,15 +62,15 @@ const EMPTY_COUNTS = {
62
62
  };
63
63
  class StateProvider {
64
64
  connection;
65
- publicKey;
66
65
  registry;
66
+ publicKey;
67
67
  _state = null;
68
68
  checkpointConfig = null;
69
69
  ticksSinceCheckpoint = 0;
70
- constructor(connection, publicKey, registry) {
70
+ constructor(connection, registry, publicKey) {
71
71
  this.connection = connection;
72
- this.publicKey = publicKey;
73
72
  this.registry = registry;
73
+ this.publicKey = publicKey;
74
74
  }
75
75
  // ─── Readonly accessors ─────────────────────────────────────────
76
76
  get state() {
@@ -36,6 +36,7 @@ export interface Action {
36
36
  raze(params: RazeParams): Promise<TransactionResult>;
37
37
  repayWarLoan(params: RepayWarLoanParams): Promise<TransactionResult>;
38
38
  requestWarLoan(params: RequestWarLoanParams): Promise<TransactionResult>;
39
+ scout(targetAddress: string): Promise<string>;
39
40
  siege(params: SiegeParams): Promise<TransactionResult>;
40
41
  tithe(params: TitheParams): Promise<TransactionResult>;
41
42
  }
@@ -0,0 +1,10 @@
1
+ import { CheckpointParams, LinkAgentWalletParams, RegisterAgentParams, RegistryProfile, RegistryWalletLink, TransactionResult, TransferAgentAuthorityParams, UnlinkAgentWalletParams } from '../types';
2
+ export interface Registry {
3
+ getProfile(creator: string): Promise<RegistryProfile | undefined>;
4
+ getWalletLink(wallet: string): Promise<RegistryWalletLink | undefined>;
5
+ register(params: RegisterAgentParams): Promise<TransactionResult>;
6
+ checkpoint(params: CheckpointParams): Promise<TransactionResult>;
7
+ linkWallet(params: LinkAgentWalletParams): Promise<TransactionResult>;
8
+ unlinkWallet(params: UnlinkAgentWalletParams): Promise<TransactionResult>;
9
+ transferAuthority(params: TransferAgentAuthorityParams): Promise<TransactionResult>;
10
+ }
@@ -1 +1,2 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pyre-world-kit",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Agent-first faction warfare kit — game-semantic wrapper over torchsdk",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/readme.md CHANGED
@@ -42,67 +42,98 @@ const connection = new Connection('https://api.mainnet-beta.solana.com')
42
42
  const agent = createEphemeralAgent()
43
43
  const kit = new PyreKit(connection, agent.publicKey)
44
44
 
45
- // Initialize state (resolves vault, loads holdings + registry checkpoint)
46
- await kit.state.init()
45
+ // exec() is the primary interface it builds the transaction,
46
+ // and returns a confirm callback that records state after signing.
47
+ // On first call, it auto-initializes state from chain.
47
48
 
48
- // Launch a faction
49
- const launch = await kit.actions.launch({
49
+ const { result, confirm } = await kit.exec('actions', 'join', {
50
+ mint, agent: agent.publicKey, amount_sol: 0.1 * LAMPORTS_PER_SOL,
51
+ strategy: 'fortify', message: 'Pledging allegiance.',
52
+ stronghold: agent.publicKey,
53
+ })
54
+ const signed = agent.sign(result.transaction)
55
+ await connection.sendRawTransaction(signed.serialize())
56
+ await confirm() // records tick, sentiment, holdings
57
+ ```
58
+
59
+ ## `exec()` — The Game Pipeline
60
+
61
+ `exec()` is a single method that runs the entire game pipeline: state initialization, action execution, and state tracking. It is the primary way agents interact with the kit.
62
+
63
+ ```typescript
64
+ const { result, confirm } = await kit.exec(provider, method, ...args)
65
+ ```
66
+
67
+ **How it works:**
68
+
69
+ 1. **First call auto-initializes** — resolves vault link, loads holdings, loads action counts and personality from the on-chain registry checkpoint. No manual `init()` needed.
70
+ 2. **Builds the transaction** — delegates to the appropriate provider method (e.g. `kit.actions.join(params)`) and returns the unsigned transaction.
71
+ 3. **Returns a `confirm` callback** — the agent signs and sends the transaction. If the tx succeeds, call `confirm()` to record the action in state. If the tx fails, don't call it — state stays clean.
72
+
73
+ **What `confirm()` does:**
74
+ - Increments the monotonic tick counter
75
+ - Updates the action count for the action type
76
+ - Adjusts sentiment for the target faction (join +1, defect -2, rally +3, etc.)
77
+ - Refreshes token holdings from chain (wallet + vault)
78
+ - Appends to action history (for LLM memory)
79
+ - Triggers auto-checkpoint if the configured tick interval is reached
80
+
81
+ ```typescript
82
+ // Type-safe provider dispatch
83
+ const { result, confirm } = await kit.exec('actions', 'join', params) // ActionProvider.join
84
+ const { result, confirm } = await kit.exec('actions', 'defect', params) // ActionProvider.defect
85
+ const { result, confirm } = await kit.exec('actions', 'fud', params) // ActionProvider.fud
86
+ const { result, confirm } = await kit.exec('intel', 'getFactionPower', mint) // IntelProvider (no-op confirm)
87
+ ```
88
+
89
+ **Read-only methods** (getFactions, getComms, intel queries) return a no-op `confirm` — call it or don't, nothing happens.
90
+
91
+ **Example: full action lifecycle**
92
+
93
+ ```typescript
94
+ const kit = new PyreKit(connection, agent.publicKey)
95
+
96
+ // First exec auto-initializes state from chain
97
+ const { result: launchTx, confirm: confirmLaunch } = await kit.exec('actions', 'launch', {
50
98
  founder: agent.publicKey,
51
99
  name: 'Iron Vanguard',
52
100
  symbol: 'IRON',
53
- metadata_uri: 'https://example.com/metadata.json',
101
+ metadata_uri: 'https://pyre.gg/factions/iron.json',
54
102
  community_faction: true,
55
103
  })
56
- const signed = agent.sign(launch.transaction)
57
- await connection.sendRawTransaction(signed.serialize())
58
- const mint = launch.mint.toBase58()
59
-
60
- // Record the action updates tick, sentiment, holdings
61
- await kit.state.record('launch', mint, 'launched IRON')
62
-
63
- // Join a faction (auto-routes bonding curve or DEX based on `ascended`)
64
- const join = await kit.actions.join({
65
- mint,
66
- agent: agent.publicKey,
67
- amount_sol: 0.1 * LAMPORTS_PER_SOL,
68
- strategy: 'fortify',
69
- message: 'Pledging allegiance.',
70
- stronghold: agent.publicKey,
71
- })
72
- agent.sign(join.transaction)
73
- // ... send + confirm ...
74
- await kit.state.record('join', mint, 'joined IRON — "Pledging allegiance."')
75
-
76
- // Defect (sell + message, auto-routes bonding curve or DEX)
77
- await kit.actions.defect({
78
- mint,
79
- agent: agent.publicKey,
80
- amount_tokens: 1000,
81
- message: 'Found a stronger faction.',
104
+ // launchTx is null on first call (state init happened instead)
105
+ // On second call, it returns the transaction:
106
+
107
+ const { result: joinTx, confirm: confirmJoin } = await kit.exec('actions', 'join', {
108
+ mint, agent: agent.publicKey, amount_sol: 0.5 * LAMPORTS_PER_SOL,
109
+ strategy: 'fortify', message: 'All in.',
82
110
  stronghold: agent.publicKey,
83
- ascended: false, // set true for DEX-traded factions
84
111
  })
112
+ agent.sign(joinTx.transaction)
113
+ await connection.sendRawTransaction(joinTx.transaction.serialize())
114
+ await confirmJoin() // tick: 1, sentiment: +1, holdings refreshed
85
115
 
86
- // FUD "argued in" (micro sell + negative message)
87
- await kit.actions.fud({
88
- mint,
89
- agent: agent.publicKey,
90
- message: 'This faction is done.',
116
+ const { result: defectTx, confirm: confirmDefect } = await kit.exec('actions', 'defect', {
117
+ mint, agent: agent.publicKey, amount_tokens: 500000,
118
+ message: 'Taking profits.',
91
119
  stronghold: agent.publicKey,
92
120
  })
93
-
94
- // Check state after actions
95
- console.log(kit.state.tick) // monotonic action counter
96
- console.log(kit.state.getSentiment(mint)) // -10 to +10
97
- console.log(kit.state.getBalance(mint)) // token balance (wallet + vault)
98
- console.log(kit.state.history) // recent action descriptions
121
+ agent.sign(defectTx.transaction)
122
+ await connection.sendRawTransaction(defectTx.transaction.serialize())
123
+ await confirmDefect() // tick: 2, sentiment: -2, holdings refreshed
124
+
125
+ // State is always up to date
126
+ console.log(kit.state.tick) // 2
127
+ console.log(kit.state.getSentiment(mint)) // -1 (join +1, defect -2)
128
+ console.log(kit.state.getBalance(mint)) // updated from chain
129
+ console.log(kit.state.history) // ['join ...', 'defect ...']
99
130
  ```
100
131
 
101
132
  ## Architecture
102
133
 
103
134
  ```
104
135
  src/
105
- index.ts — PyreKit top-level class + public exports
136
+ index.ts — PyreKit top-level class + exec() + public exports
106
137
  types.ts — game-semantic type definitions
107
138
  types/
108
139
  action.types.ts — Action provider interface
@@ -129,8 +160,9 @@ Top-level class that wires all providers as singletons:
129
160
 
130
161
  ```typescript
131
162
  const kit = new PyreKit(connection, agentPublicKey)
132
- kit.actions // ActionProviderfaction operations
133
- kit.intel // IntelProviderstrategic intelligence
163
+ kit.exec(provider, method, ...args) // primary interface runs full pipeline
164
+ kit.actions // ActionProviderdirect access (bypasses state tracking)
165
+ kit.intel // IntelProvider — direct access
134
166
  kit.state // StateProvider — objective game state
135
167
  kit.registry // RegistryProvider — on-chain identity
136
168
  ```
@@ -164,11 +196,9 @@ kit.actions.getDefectQuote(mint, n) // sell price quote
164
196
 
165
197
  ### StateProvider
166
198
 
167
- Objective game state tracking. Initialized from chain (vault link + registry checkpoint). Updated via `record()` after each confirmed action.
199
+ Objective game state tracking. Initialized from chain (vault link + registry checkpoint). Updated automatically via `exec()` confirm callbacks.
168
200
 
169
201
  ```typescript
170
- await kit.state.init() // resolve vault, load holdings + checkpoint
171
- await kit.state.record('join', mint, desc) // increment tick, update sentiment + holdings
172
202
  kit.state.tick // monotonic action counter
173
203
  kit.state.getSentiment(mint) // -10 to +10
174
204
  kit.state.sentimentMap // all sentiment entries
@@ -180,7 +210,7 @@ kit.state.serialize() // persist to JSON
180
210
  kit.state.hydrate(saved) // restore from JSON (skip chain reconstruction)
181
211
  ```
182
212
 
183
- **Sentiment scoring** (auto-applied on `record()`):
213
+ **Sentiment scoring** (auto-applied on confirm):
184
214
  - join: +1, reinforce: +1.5, rally: +3, launch: +3
185
215
  - defect: -2, fud: -1.5, infiltrate: -5
186
216
  - message: +0.5, war_loan: +1
package/src/index.ts CHANGED
@@ -14,19 +14,20 @@ import { StateProvider } from './providers/state.provider'
14
14
  import type { Action } from './types/action.types'
15
15
  import type { Intel } from './types/intel.types'
16
16
  import type { State, CheckpointConfig, TrackedAction } from './types/state.types'
17
+ import { Registry } from './types/registry.types'
17
18
 
18
19
  // ─── Top-level Kit ────────────────────────────────────────────────
19
20
 
20
21
  export class PyreKit {
21
22
  readonly actions: ActionProvider
22
23
  readonly intel: IntelProvider
23
- readonly registry: RegistryProvider
24
+ readonly registry: Registry
24
25
  readonly state: StateProvider
25
26
 
26
27
  constructor(connection: Connection, publicKey: string) {
27
28
  this.registry = new RegistryProvider(connection)
28
- this.state = new StateProvider(connection, publicKey, this.registry)
29
- this.actions = new ActionProvider(connection)
29
+ this.state = new StateProvider(connection, this.registry, publicKey)
30
+ this.actions = new ActionProvider(connection, this.registry)
30
31
  this.intel = new IntelProvider(connection, this.actions)
31
32
 
32
33
  // Wire auto-checkpoint callback
@@ -42,19 +43,24 @@ export class PyreKit {
42
43
  }
43
44
 
44
45
  /**
45
- * Execute an action with automatic state tracking.
46
+ * Execute an action with deferred state tracking.
46
47
  * On first call, initializes state from chain instead of executing.
47
- * After execution, records the action and updates state.
48
+ *
49
+ * Returns { result, confirm }. Call confirm() after the transaction
50
+ * is signed and confirmed on-chain. This records the action in state
51
+ * (tick, sentiment, holdings, auto-checkpoint).
52
+ *
53
+ * For read-only methods (getFactions, getComms, etc.), confirm is a no-op.
48
54
  */
49
55
  async exec<T extends 'actions' | 'intel'>(
50
56
  provider: T,
51
57
  method: T extends 'actions' ? keyof Action : keyof Intel,
52
58
  ...args: any[]
53
- ): Promise<any> {
59
+ ): Promise<{ result: any; confirm: () => Promise<void> }> {
54
60
  // First exec: initialize state
55
61
  if (!this.state.initialized) {
56
62
  await this.state.init()
57
- return null
63
+ return { result: null, confirm: async () => {} }
58
64
  }
59
65
 
60
66
  const target = provider === 'actions' ? this.actions : this.intel
@@ -63,20 +69,21 @@ export class PyreKit {
63
69
 
64
70
  const result = await fn.call(target, ...args)
65
71
 
66
- // Track action if it's a state-mutating action method
67
- if (provider === 'actions') {
68
- const action = this.methodToAction(method as string)
69
- if (action) {
70
- const mint = args[0]?.mint
71
- const message = args[0]?.message
72
- const description = message
73
- ? `${action} ${mint?.slice(0, 8) ?? '?'} — "${message}"`
74
- : `${action} ${mint?.slice(0, 8) ?? '?'}`
75
- await this.state.record(action, mint, description)
76
- }
77
- }
78
-
79
- return result
72
+ // Build confirm callback for state-mutating actions
73
+ const trackedAction = provider === 'actions' ? this.methodToAction(method as string) : null
74
+
75
+ const confirm = trackedAction
76
+ ? async () => {
77
+ const mint = args[0]?.mint
78
+ const message = args[0]?.message
79
+ const description = message
80
+ ? `${trackedAction} ${mint?.slice(0, 8) ?? '?'} — "${message}"`
81
+ : `${trackedAction} ${mint?.slice(0, 8) ?? '?'}`
82
+ await this.state.record(trackedAction, mint, description)
83
+ }
84
+ : async () => {} // no-op for reads
85
+
86
+ return { result, confirm }
80
87
  }
81
88
 
82
89
  /** Map action method names to tracked action types */
@@ -83,10 +83,14 @@ import {
83
83
  isPyreMint,
84
84
  } from '../vanity'
85
85
  import { isBlacklistedMint } from '../util'
86
+ import { Registry } from '../types/registry.types'
86
87
 
87
88
  export class ActionProvider implements Action {
88
89
  private mapper = new MapperProvider()
89
- constructor(private connection: Connection) {}
90
+ constructor(
91
+ private connection: Connection,
92
+ private registryProvider: Registry,
93
+ ) {}
90
94
 
91
95
  async createStronghold(params: CreateStrongholdParams): Promise<TransactionResult> {
92
96
  return buildCreateVaultTransaction(this.connection, { creator: params.creator })
@@ -422,6 +426,61 @@ export class ActionProvider implements Action {
422
426
  })
423
427
  }
424
428
 
429
+ async scout(targetAddress: string): Promise<string> {
430
+ try {
431
+ const p = await this.registryProvider.getProfile(targetAddress)
432
+ if (!p) return ` @${targetAddress.slice(0, 8)}: no pyre identity found`
433
+
434
+ const total =
435
+ p.joins +
436
+ p.defects +
437
+ p.rallies +
438
+ p.launches +
439
+ p.messages +
440
+ p.fuds +
441
+ p.infiltrates +
442
+ p.reinforces +
443
+ p.war_loans +
444
+ p.repay_loans +
445
+ p.sieges +
446
+ p.ascends +
447
+ p.razes +
448
+ p.tithes
449
+
450
+ const topActions = [
451
+ { n: 'joins', v: p.joins },
452
+ { n: 'defects', v: p.defects },
453
+ { n: 'rallies', v: p.rallies },
454
+ { n: 'messages', v: p.messages },
455
+ { n: 'fuds', v: p.fuds },
456
+ { n: 'infiltrates', v: p.infiltrates },
457
+ { n: 'reinforces', v: p.reinforces },
458
+ { n: 'war_loans', v: p.war_loans },
459
+ { n: 'sieges', v: p.sieges },
460
+ ]
461
+ .sort((a, b) => b.v - a.v)
462
+ .filter((a) => a.v > 0)
463
+ .slice(0, 4)
464
+ .map((a) => `${a.n}:${a.v}`)
465
+ .join(', ')
466
+
467
+ const personality = p.personality_summary || 'unknown'
468
+ const checkpoint =
469
+ p.last_checkpoint > 0
470
+ ? new Date(p.last_checkpoint * 1000).toISOString().slice(0, 10)
471
+ : 'never'
472
+
473
+ const spent = (p.total_sol_spent ?? 0) / 1e9
474
+ const received = (p.total_sol_received ?? 0) / 1e9
475
+ const pnl = received - spent
476
+ const pnlStr = pnl >= 0 ? `+${pnl.toFixed(3)}` : pnl.toFixed(3)
477
+
478
+ return ` @${targetAddress.slice(0, 8)}: "${personality}" | ${total} actions (${topActions}) | P&L: ${pnlStr} SOL | last seen: ${checkpoint}`
479
+ } catch {
480
+ return ` @${targetAddress.slice(0, 8)}: lookup failed`
481
+ }
482
+ }
483
+
425
484
  async siege(params: SiegeParams): Promise<TransactionResult> {
426
485
  return buildLiquidateTransaction(this.connection, {
427
486
  mint: params.mint,
@@ -20,6 +20,7 @@ import type {
20
20
  } from '../types'
21
21
 
22
22
  import idl from '../pyre_world.json'
23
+ import { Registry } from '../types/registry.types'
23
24
 
24
25
  // ─── Program ID ─────────────────────────────────────────────────────
25
26
 
@@ -69,7 +70,7 @@ async function finalizeTransaction(
69
70
 
70
71
  // ─── Provider ───────────────────────────────────────────────────────
71
72
 
72
- export class RegistryProvider {
73
+ export class RegistryProvider implements Registry {
73
74
  constructor(private connection: Connection) {}
74
75
 
75
76
  private getProgram(payer: PublicKey): Program {
@@ -79,7 +80,7 @@ export class RegistryProvider {
79
80
 
80
81
  // ─── Read Operations ──────────────────────────────────────────────
81
82
 
82
- async getProfile(creator: string): Promise<RegistryProfile | null> {
83
+ async getProfile(creator: string): Promise<RegistryProfile | undefined> {
83
84
  const creatorPk = new PublicKey(creator)
84
85
  const [profilePda] = getAgentProfilePda(creatorPk)
85
86
  const program = this.getProgram(creatorPk)
@@ -113,11 +114,11 @@ export class RegistryProvider {
113
114
  total_sol_received: account.totalSolReceived?.toNumber() ?? 0,
114
115
  }
115
116
  } catch {
116
- return null
117
+ return undefined
117
118
  }
118
119
  }
119
120
 
120
- async getWalletLink(wallet: string): Promise<RegistryWalletLink | null> {
121
+ async getWalletLink(wallet: string): Promise<RegistryWalletLink | undefined> {
121
122
  const walletPk = new PublicKey(wallet)
122
123
  const [linkPda] = getAgentWalletLinkPda(walletPk)
123
124
  const program = this.getProgram(walletPk)
@@ -132,7 +133,7 @@ export class RegistryProvider {
132
133
  bump: account.bump,
133
134
  }
134
135
  } catch {
135
- return null
136
+ return undefined
136
137
  }
137
138
  }
138
139
 
@@ -17,6 +17,7 @@ import type {
17
17
  import { RegistryProvider } from './registry.provider'
18
18
  import { isPyreMint } from '../vanity'
19
19
  import { isBlacklistedMint } from '../util'
20
+ import { Registry } from '../types/registry.types'
20
21
 
21
22
  const EMPTY_COUNTS: Record<TrackedAction, number> = {
22
23
  join: 0,
@@ -40,11 +41,7 @@ export class StateProvider implements State {
40
41
  private checkpointConfig: CheckpointConfig | null = null
41
42
  private ticksSinceCheckpoint = 0
42
43
 
43
- constructor(
44
- private connection: Connection,
45
- private publicKey: string,
46
- private registry: RegistryProvider,
47
- ) {}
44
+ constructor(private connection: Connection, private registry: Registry, private publicKey: string) {}
48
45
 
49
46
  // ─── Readonly accessors ─────────────────────────────────────────
50
47
 
@@ -71,6 +71,7 @@ export interface Action {
71
71
  raze(params: RazeParams): Promise<TransactionResult>
72
72
  repayWarLoan(params: RepayWarLoanParams): Promise<TransactionResult>
73
73
  requestWarLoan(params: RequestWarLoanParams): Promise<TransactionResult>
74
+ scout(targetAddress: string): Promise<string>
74
75
  siege(params: SiegeParams): Promise<TransactionResult>
75
76
  tithe(params: TitheParams): Promise<TransactionResult>
76
77
  }
@@ -0,0 +1,21 @@
1
+ import {
2
+ CheckpointParams,
3
+ LinkAgentWalletParams,
4
+ RegisterAgentParams,
5
+ RegistryProfile,
6
+ RegistryWalletLink,
7
+ TransactionResult,
8
+ TransferAgentAuthorityParams,
9
+ UnlinkAgentWalletParams,
10
+ } from '../types'
11
+
12
+ export interface Registry {
13
+ getProfile(creator: string): Promise<RegistryProfile | undefined>
14
+ getWalletLink(wallet: string): Promise<RegistryWalletLink | undefined>
15
+
16
+ register(params: RegisterAgentParams): Promise<TransactionResult>
17
+ checkpoint(params: CheckpointParams): Promise<TransactionResult>
18
+ linkWallet(params: LinkAgentWalletParams): Promise<TransactionResult>
19
+ unlinkWallet(params: UnlinkAgentWalletParams): Promise<TransactionResult>
20
+ transferAuthority(params: TransferAgentAuthorityParams): Promise<TransactionResult>
21
+ }