pyre-agent-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.d.ts +3 -0
- package/dist/action.js +98 -0
- package/dist/agent.d.ts +4 -0
- package/dist/agent.js +276 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +475 -0
- package/dist/defaults.d.ts +10 -0
- package/dist/defaults.js +109 -0
- package/dist/error.d.ts +6 -0
- package/dist/error.js +66 -0
- package/dist/executor.d.ts +24 -0
- package/dist/executor.js +409 -0
- package/dist/faction.d.ts +10 -0
- package/dist/faction.js +110 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +220 -0
- package/dist/stronghold.d.ts +7 -0
- package/dist/stronghold.js +88 -0
- package/dist/tx.d.ts +2 -0
- package/dist/tx.js +40 -0
- package/dist/types.d.ts +118 -0
- package/dist/types.js +2 -0
- package/dist/util.d.ts +6 -0
- package/dist/util.js +19 -0
- package/package.json +30 -0
- package/readme.md +556 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendAndConfirm = exports.ensureStronghold = exports.VOICE_NUDGES = exports.personalityDesc = exports.PERSONALITY_WEIGHTS = exports.PERSONALITY_SOL = exports.assignPersonality = void 0;
|
|
4
|
+
exports.createPyreAgent = createPyreAgent;
|
|
5
|
+
const pyre_world_kit_1 = require("pyre-world-kit");
|
|
6
|
+
const defaults_1 = require("./defaults");
|
|
7
|
+
const action_1 = require("./action");
|
|
8
|
+
const agent_1 = require("./agent");
|
|
9
|
+
const executor_1 = require("./executor");
|
|
10
|
+
const stronghold_1 = require("./stronghold");
|
|
11
|
+
const util_1 = require("./util");
|
|
12
|
+
var defaults_2 = require("./defaults");
|
|
13
|
+
Object.defineProperty(exports, "assignPersonality", { enumerable: true, get: function () { return defaults_2.assignPersonality; } });
|
|
14
|
+
Object.defineProperty(exports, "PERSONALITY_SOL", { enumerable: true, get: function () { return defaults_2.PERSONALITY_SOL; } });
|
|
15
|
+
Object.defineProperty(exports, "PERSONALITY_WEIGHTS", { enumerable: true, get: function () { return defaults_2.PERSONALITY_WEIGHTS; } });
|
|
16
|
+
Object.defineProperty(exports, "personalityDesc", { enumerable: true, get: function () { return defaults_2.personalityDesc; } });
|
|
17
|
+
Object.defineProperty(exports, "VOICE_NUDGES", { enumerable: true, get: function () { return defaults_2.VOICE_NUDGES; } });
|
|
18
|
+
var stronghold_2 = require("./stronghold");
|
|
19
|
+
Object.defineProperty(exports, "ensureStronghold", { enumerable: true, get: function () { return stronghold_2.ensureStronghold; } });
|
|
20
|
+
var tx_1 = require("./tx");
|
|
21
|
+
Object.defineProperty(exports, "sendAndConfirm", { enumerable: true, get: function () { return tx_1.sendAndConfirm; } });
|
|
22
|
+
async function createPyreAgent(config) {
|
|
23
|
+
const { connection, keypair, network, llm, maxFoundedFactions = 2, strongholdFundSol, strongholdTopupThresholdSol, strongholdTopupReserveSol, } = config;
|
|
24
|
+
const publicKey = keypair.publicKey.toBase58();
|
|
25
|
+
const personality = config.personality ?? config.state?.personality ?? (0, defaults_1.assignPersonality)();
|
|
26
|
+
const solRange = config.solRange ?? defaults_1.PERSONALITY_SOL[personality];
|
|
27
|
+
const logger = config.logger ?? ((msg) => console.log(`[${(0, util_1.ts)()}] ${msg}`));
|
|
28
|
+
const strongholdOpts = {
|
|
29
|
+
fundSol: strongholdFundSol,
|
|
30
|
+
topupThresholdSol: strongholdTopupThresholdSol,
|
|
31
|
+
topupReserveSol: strongholdTopupReserveSol,
|
|
32
|
+
};
|
|
33
|
+
// Build agent state from serialized or fresh
|
|
34
|
+
const prior = config.state;
|
|
35
|
+
const state = {
|
|
36
|
+
keypair,
|
|
37
|
+
publicKey,
|
|
38
|
+
personality,
|
|
39
|
+
holdings: new Map(Object.entries(prior?.holdings ?? {})),
|
|
40
|
+
founded: prior?.founded ?? [],
|
|
41
|
+
rallied: new Set(prior?.rallied ?? []),
|
|
42
|
+
voted: new Set([...(prior?.voted ?? []), ...Object.keys(prior?.holdings ?? {})]),
|
|
43
|
+
hasStronghold: prior?.hasStronghold ?? false,
|
|
44
|
+
activeLoans: new Set(prior?.activeLoans ?? []),
|
|
45
|
+
infiltrated: new Set(prior?.infiltrated ?? []),
|
|
46
|
+
sentiment: new Map(Object.entries(prior?.sentiment ?? {})),
|
|
47
|
+
allies: new Set(prior?.allies ?? []),
|
|
48
|
+
rivals: new Set(prior?.rivals ?? []),
|
|
49
|
+
actionCount: prior?.actionCount ?? 0,
|
|
50
|
+
lastAction: prior?.lastAction ?? 'none',
|
|
51
|
+
recentHistory: prior?.recentHistory ?? [],
|
|
52
|
+
};
|
|
53
|
+
// Track known factions and used names
|
|
54
|
+
const knownFactions = [];
|
|
55
|
+
const usedFactionNames = new Set();
|
|
56
|
+
const recentMessages = [];
|
|
57
|
+
// Discover existing factions
|
|
58
|
+
try {
|
|
59
|
+
const result = await (0, pyre_world_kit_1.getFactions)(connection, { limit: 50, sort: 'newest' });
|
|
60
|
+
for (const t of result.factions) {
|
|
61
|
+
if (!(0, pyre_world_kit_1.isPyreMint)(t.mint))
|
|
62
|
+
continue;
|
|
63
|
+
knownFactions.push({ mint: t.mint, name: t.name, symbol: t.symbol, status: t.status });
|
|
64
|
+
usedFactionNames.add(t.name);
|
|
65
|
+
}
|
|
66
|
+
logger(`[${publicKey.slice(0, 8)}] discovered ${knownFactions.length} factions`);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
logger(`[${publicKey.slice(0, 8)}] faction discovery failed`);
|
|
70
|
+
}
|
|
71
|
+
// Ensure stronghold exists
|
|
72
|
+
await (0, stronghold_1.ensureStronghold)(connection, state, logger, strongholdOpts);
|
|
73
|
+
function serialize() {
|
|
74
|
+
return {
|
|
75
|
+
publicKey: state.publicKey,
|
|
76
|
+
personality: state.personality,
|
|
77
|
+
holdings: Object.fromEntries(state.holdings),
|
|
78
|
+
founded: state.founded,
|
|
79
|
+
rallied: Array.from(state.rallied),
|
|
80
|
+
voted: Array.from(state.voted),
|
|
81
|
+
hasStronghold: state.hasStronghold,
|
|
82
|
+
activeLoans: Array.from(state.activeLoans),
|
|
83
|
+
infiltrated: Array.from(state.infiltrated),
|
|
84
|
+
sentiment: Object.fromEntries(state.sentiment),
|
|
85
|
+
allies: Array.from(state.allies).slice(0, 20),
|
|
86
|
+
rivals: Array.from(state.rivals).slice(0, 20),
|
|
87
|
+
actionCount: state.actionCount,
|
|
88
|
+
lastAction: state.lastAction,
|
|
89
|
+
recentHistory: state.recentHistory.slice(-10),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function tick(factions) {
|
|
93
|
+
const activeFactions = factions ?? knownFactions;
|
|
94
|
+
// Try LLM decision first, fall back to weighted random
|
|
95
|
+
let decision = null;
|
|
96
|
+
let usedLLM = false;
|
|
97
|
+
if (llm && activeFactions.length > 0) {
|
|
98
|
+
decision = await (0, agent_1.llmDecide)(state, activeFactions, connection, recentMessages, llm, logger, solRange);
|
|
99
|
+
if (decision)
|
|
100
|
+
usedLLM = true;
|
|
101
|
+
}
|
|
102
|
+
// Fallback: weighted random
|
|
103
|
+
if (!decision) {
|
|
104
|
+
const canRally = activeFactions.some(f => !state.rallied.has(f.mint));
|
|
105
|
+
const action = (0, action_1.chooseAction)(state.personality, state, canRally, activeFactions);
|
|
106
|
+
const [minSol, maxSol] = solRange;
|
|
107
|
+
decision = { action, sol: (0, util_1.randRange)(minSol, maxSol) };
|
|
108
|
+
// Pick a target faction for the fallback
|
|
109
|
+
if (action === 'join') {
|
|
110
|
+
const f = activeFactions.length > 0 ? (0, util_1.pick)(activeFactions) : null;
|
|
111
|
+
if (!f)
|
|
112
|
+
return { action, success: false, error: 'no factions', usedLLM: false };
|
|
113
|
+
decision.faction = f.symbol;
|
|
114
|
+
decision.sol = (0, action_1.sentimentBuySize)(state, f.mint);
|
|
115
|
+
}
|
|
116
|
+
else if (action === 'message' || action === 'fud') {
|
|
117
|
+
return { action, success: false, error: 'no LLM for message', usedLLM: false };
|
|
118
|
+
}
|
|
119
|
+
else if (action === 'defect') {
|
|
120
|
+
const infiltratedHeld = [...state.holdings.entries()].filter(([m, b]) => b > 0 && state.infiltrated.has(m));
|
|
121
|
+
const regularHeld = [...state.holdings.entries()].filter(([, b]) => b > 0);
|
|
122
|
+
const held = infiltratedHeld.length > 0 ? infiltratedHeld : regularHeld;
|
|
123
|
+
if (held.length === 0)
|
|
124
|
+
return { action, success: false, error: 'no holdings', usedLLM: false };
|
|
125
|
+
const [mint] = (0, util_1.pick)(held);
|
|
126
|
+
const f = activeFactions.find(ff => ff.mint === mint);
|
|
127
|
+
if (!f)
|
|
128
|
+
return { action, success: false, error: 'faction not found', usedLLM: false };
|
|
129
|
+
decision.faction = f.symbol;
|
|
130
|
+
}
|
|
131
|
+
else if (action === 'rally') {
|
|
132
|
+
const eligible = activeFactions.filter(f => !state.rallied.has(f.mint));
|
|
133
|
+
if (eligible.length === 0)
|
|
134
|
+
return { action, success: false, error: 'nothing to rally', usedLLM: false };
|
|
135
|
+
decision.faction = (0, util_1.pick)(eligible).symbol;
|
|
136
|
+
}
|
|
137
|
+
else if (action === 'war_loan') {
|
|
138
|
+
const held = [...state.holdings.entries()].filter(([, b]) => b > 0);
|
|
139
|
+
const heldAscended = held.filter(([mint]) => activeFactions.find(f => f.mint === mint)?.status === 'ascended');
|
|
140
|
+
if (heldAscended.length === 0)
|
|
141
|
+
return { action, success: false, error: 'no ascended holdings', usedLLM: false };
|
|
142
|
+
const [mint] = (0, util_1.pick)(heldAscended);
|
|
143
|
+
const f = activeFactions.find(ff => ff.mint === mint);
|
|
144
|
+
if (!f)
|
|
145
|
+
return { action, success: false, error: 'faction not found', usedLLM: false };
|
|
146
|
+
decision.faction = f.symbol;
|
|
147
|
+
}
|
|
148
|
+
else if (action === 'repay_loan') {
|
|
149
|
+
const loanMints = [...state.activeLoans];
|
|
150
|
+
if (loanMints.length === 0)
|
|
151
|
+
return { action, success: false, error: 'no loans', usedLLM: false };
|
|
152
|
+
const mint = (0, util_1.pick)(loanMints);
|
|
153
|
+
const f = activeFactions.find(ff => ff.mint === mint);
|
|
154
|
+
if (!f)
|
|
155
|
+
return { action, success: false, error: 'faction not found', usedLLM: false };
|
|
156
|
+
decision.faction = f.symbol;
|
|
157
|
+
}
|
|
158
|
+
else if (action === 'ascend') {
|
|
159
|
+
const ready = activeFactions.filter(f => f.status === 'ready');
|
|
160
|
+
if (ready.length === 0)
|
|
161
|
+
return { action, success: false, error: 'no ready factions', usedLLM: false };
|
|
162
|
+
decision.faction = (0, util_1.pick)(ready).symbol;
|
|
163
|
+
}
|
|
164
|
+
else if (action === 'raze') {
|
|
165
|
+
const razeable = activeFactions.filter(f => f.status === 'rising');
|
|
166
|
+
if (razeable.length === 0)
|
|
167
|
+
return { action, success: false, error: 'no rising factions', usedLLM: false };
|
|
168
|
+
const bearish = razeable.filter(f => (state.sentiment.get(f.mint) ?? 0) < -2);
|
|
169
|
+
decision.faction = (bearish.length > 0 ? (0, util_1.pick)(bearish) : (0, util_1.pick)(razeable)).symbol;
|
|
170
|
+
}
|
|
171
|
+
else if (action === 'siege') {
|
|
172
|
+
const ascended = activeFactions.filter(f => f.status === 'ascended');
|
|
173
|
+
if (ascended.length === 0)
|
|
174
|
+
return { action, success: false, error: 'no ascended factions', usedLLM: false };
|
|
175
|
+
decision.faction = (0, util_1.pick)(ascended).symbol;
|
|
176
|
+
}
|
|
177
|
+
else if (action === 'tithe') {
|
|
178
|
+
if (activeFactions.length === 0)
|
|
179
|
+
return { action, success: false, error: 'no factions', usedLLM: false };
|
|
180
|
+
const bearish = activeFactions.filter(f => (state.sentiment.get(f.mint) ?? 0) < -2);
|
|
181
|
+
decision.faction = (bearish.length > 0 ? (0, util_1.pick)(bearish) : (0, util_1.pick)(activeFactions)).symbol;
|
|
182
|
+
}
|
|
183
|
+
else if (action === 'infiltrate') {
|
|
184
|
+
const heldMints = [...state.holdings.keys()];
|
|
185
|
+
const rivals = activeFactions.filter(f => !heldMints.includes(f.mint));
|
|
186
|
+
if (rivals.length === 0)
|
|
187
|
+
return { action, success: false, error: 'no rival factions', usedLLM: false };
|
|
188
|
+
const target = (0, util_1.pick)(rivals);
|
|
189
|
+
decision.faction = target.symbol;
|
|
190
|
+
decision.sol = (0, action_1.sentimentBuySize)(state, target.mint) * 1.5;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const result = await (0, executor_1.executeAction)({
|
|
194
|
+
connection, agent: state, factions: activeFactions, decision, brain: usedLLM ? 'LLM' : 'RNG',
|
|
195
|
+
log: logger, llm, maxFoundedFactions, usedFactionNames, strongholdOpts,
|
|
196
|
+
});
|
|
197
|
+
// Record message to prevent repetition
|
|
198
|
+
if (result.success && decision.message) {
|
|
199
|
+
recentMessages.push(decision.message.replace(/^<+/, '').replace(/>+\s*$/, '').toLowerCase());
|
|
200
|
+
if (recentMessages.length > 30)
|
|
201
|
+
recentMessages.shift();
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
action: decision.action,
|
|
205
|
+
faction: decision.faction,
|
|
206
|
+
message: decision.message,
|
|
207
|
+
reasoning: decision.reasoning,
|
|
208
|
+
success: result.success,
|
|
209
|
+
error: result.error,
|
|
210
|
+
usedLLM,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
publicKey,
|
|
215
|
+
personality,
|
|
216
|
+
tick,
|
|
217
|
+
getState: () => state,
|
|
218
|
+
serialize,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Connection } from '@solana/web3.js';
|
|
2
|
+
import { AgentState } from './types';
|
|
3
|
+
export declare const ensureStronghold: (connection: Connection, agent: AgentState, log: (msg: string) => void, opts?: {
|
|
4
|
+
fundSol?: number;
|
|
5
|
+
topupThresholdSol?: number;
|
|
6
|
+
topupReserveSol?: number;
|
|
7
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureStronghold = void 0;
|
|
4
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
5
|
+
const pyre_world_kit_1 = require("pyre-world-kit");
|
|
6
|
+
const tx_1 = require("./tx");
|
|
7
|
+
const defaults_1 = require("./defaults");
|
|
8
|
+
const ensureStronghold = async (connection, agent, log, opts) => {
|
|
9
|
+
const short = agent.publicKey.slice(0, 8);
|
|
10
|
+
const fundSol = opts?.fundSol ?? defaults_1.STRONGHOLD_FUND_SOL;
|
|
11
|
+
const topupThreshold = opts?.topupThresholdSol ?? defaults_1.STRONGHOLD_TOPUP_THRESHOLD_SOL;
|
|
12
|
+
const topupReserve = opts?.topupReserveSol ?? defaults_1.STRONGHOLD_TOPUP_RESERVE_SOL;
|
|
13
|
+
if (agent.hasStronghold) {
|
|
14
|
+
// Already known — just check if vault needs a top-up
|
|
15
|
+
try {
|
|
16
|
+
const existing = await (0, pyre_world_kit_1.getStronghold)(connection, agent.publicKey);
|
|
17
|
+
const vaultBal = existing?.sol_balance ?? 0;
|
|
18
|
+
const threshold = topupThreshold * pyre_world_kit_1.LAMPORTS_PER_SOL;
|
|
19
|
+
if (existing && vaultBal < threshold) {
|
|
20
|
+
const walletBal = await connection.getBalance(new web3_js_1.PublicKey(agent.publicKey));
|
|
21
|
+
const reserve = topupReserve * pyre_world_kit_1.LAMPORTS_PER_SOL;
|
|
22
|
+
const available = walletBal - reserve;
|
|
23
|
+
if (available > 0.01 * pyre_world_kit_1.LAMPORTS_PER_SOL) {
|
|
24
|
+
const fundAmt = Math.floor(available);
|
|
25
|
+
const fundResult = await (0, pyre_world_kit_1.fundStronghold)(connection, {
|
|
26
|
+
depositor: agent.publicKey,
|
|
27
|
+
stronghold_creator: agent.publicKey,
|
|
28
|
+
amount_sol: fundAmt,
|
|
29
|
+
});
|
|
30
|
+
await (0, tx_1.sendAndConfirm)(connection, agent.keypair, fundResult);
|
|
31
|
+
log(`[${short}] topped up vault with ${(fundAmt / pyre_world_kit_1.LAMPORTS_PER_SOL).toFixed(2)} SOL`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
log(`[${short}] vault topup check failed: ${err.message?.slice(0, 80) ?? err}`);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// Check if stronghold already exists on-chain (from a previous run)
|
|
41
|
+
try {
|
|
42
|
+
const existing = await (0, pyre_world_kit_1.getStronghold)(connection, agent.publicKey);
|
|
43
|
+
if (existing) {
|
|
44
|
+
agent.hasStronghold = true;
|
|
45
|
+
if (existing.sol_balance < topupThreshold * pyre_world_kit_1.LAMPORTS_PER_SOL) {
|
|
46
|
+
try {
|
|
47
|
+
const walletBal = await connection.getBalance(new web3_js_1.PublicKey(agent.publicKey));
|
|
48
|
+
const reserve = topupReserve * pyre_world_kit_1.LAMPORTS_PER_SOL;
|
|
49
|
+
const available = walletBal - reserve;
|
|
50
|
+
if (available > 0.01 * pyre_world_kit_1.LAMPORTS_PER_SOL) {
|
|
51
|
+
const fundAmt = Math.floor(available);
|
|
52
|
+
const fundResult = await (0, pyre_world_kit_1.fundStronghold)(connection, {
|
|
53
|
+
depositor: agent.publicKey,
|
|
54
|
+
stronghold_creator: agent.publicKey,
|
|
55
|
+
amount_sol: fundAmt,
|
|
56
|
+
});
|
|
57
|
+
await (0, tx_1.sendAndConfirm)(connection, agent.keypair, fundResult);
|
|
58
|
+
log(`[${short}] topped up vault with ${(fundAmt / pyre_world_kit_1.LAMPORTS_PER_SOL).toFixed(2)} SOL`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch { /* top-up failed, continue anyway */ }
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch { /* not found, create one */ }
|
|
67
|
+
try {
|
|
68
|
+
const result = await (0, pyre_world_kit_1.createStronghold)(connection, { creator: agent.publicKey });
|
|
69
|
+
await (0, tx_1.sendAndConfirm)(connection, agent.keypair, result);
|
|
70
|
+
agent.hasStronghold = true;
|
|
71
|
+
// Fund it so it can trade on DEX
|
|
72
|
+
const fundAmt = Math.floor(fundSol * pyre_world_kit_1.LAMPORTS_PER_SOL);
|
|
73
|
+
try {
|
|
74
|
+
const fundResult = await (0, pyre_world_kit_1.fundStronghold)(connection, {
|
|
75
|
+
depositor: agent.publicKey,
|
|
76
|
+
stronghold_creator: agent.publicKey,
|
|
77
|
+
amount_sol: fundAmt,
|
|
78
|
+
});
|
|
79
|
+
await (0, tx_1.sendAndConfirm)(connection, agent.keypair, fundResult);
|
|
80
|
+
}
|
|
81
|
+
catch { /* fund failed, stronghold still created */ }
|
|
82
|
+
log(`[${short}] auto-created stronghold`);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
log(`[${short}] failed to create stronghold: ${err.message?.slice(0, 80)}`);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
exports.ensureStronghold = ensureStronghold;
|
package/dist/tx.d.ts
ADDED
package/dist/tx.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendAndConfirm = void 0;
|
|
4
|
+
async function confirm(connection, sig) {
|
|
5
|
+
try {
|
|
6
|
+
await connection.confirmTransaction(sig, 'confirmed');
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
// WebSocket failed — fall back to polling
|
|
10
|
+
const start = Date.now();
|
|
11
|
+
while (Date.now() - start < 60000) {
|
|
12
|
+
const status = await connection.getSignatureStatus(sig);
|
|
13
|
+
if (status?.value?.confirmationStatus === 'confirmed' || status?.value?.confirmationStatus === 'finalized') {
|
|
14
|
+
if (status.value.err)
|
|
15
|
+
throw new Error(`Transaction failed: ${JSON.stringify(status.value.err)}`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`Transaction confirmation timeout: ${sig}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const sendAndConfirm = async (connection, keypair, result) => {
|
|
24
|
+
const tx = result.transaction;
|
|
25
|
+
tx.partialSign(keypair);
|
|
26
|
+
const sig = await connection.sendRawTransaction(tx.serialize(), {
|
|
27
|
+
skipPreflight: false,
|
|
28
|
+
preflightCommitment: 'confirmed',
|
|
29
|
+
});
|
|
30
|
+
await confirm(connection, sig);
|
|
31
|
+
if (result.additionalTransactions) {
|
|
32
|
+
for (const addlTx of result.additionalTransactions) {
|
|
33
|
+
addlTx.partialSign(keypair);
|
|
34
|
+
const addlSig = await connection.sendRawTransaction(addlTx.serialize());
|
|
35
|
+
await confirm(connection, addlSig);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return sig;
|
|
39
|
+
};
|
|
40
|
+
exports.sendAndConfirm = sendAndConfirm;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Connection, Keypair } from '@solana/web3.js';
|
|
2
|
+
export type Personality = 'loyalist' | 'mercenary' | 'provocateur' | 'scout' | 'whale';
|
|
3
|
+
export type Action = 'join' | 'defect' | 'rally' | 'launch' | 'message' | 'stronghold' | 'war_loan' | 'repay_loan' | 'siege' | 'ascend' | 'raze' | 'tithe' | 'infiltrate' | 'fud';
|
|
4
|
+
export interface LLMDecision {
|
|
5
|
+
action: Action;
|
|
6
|
+
faction?: string;
|
|
7
|
+
sol?: number;
|
|
8
|
+
message?: string;
|
|
9
|
+
reasoning?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface FactionInfo {
|
|
12
|
+
mint: string;
|
|
13
|
+
name: string;
|
|
14
|
+
symbol: string;
|
|
15
|
+
status: 'rising' | 'ready' | 'ascended' | 'razed';
|
|
16
|
+
}
|
|
17
|
+
export interface FactionIntel {
|
|
18
|
+
symbol: string;
|
|
19
|
+
members: {
|
|
20
|
+
address: string;
|
|
21
|
+
percentage: number;
|
|
22
|
+
}[];
|
|
23
|
+
totalMembers: number;
|
|
24
|
+
recentComms: {
|
|
25
|
+
sender: string;
|
|
26
|
+
memo: string;
|
|
27
|
+
}[];
|
|
28
|
+
}
|
|
29
|
+
export interface AgentState {
|
|
30
|
+
keypair: Keypair;
|
|
31
|
+
publicKey: string;
|
|
32
|
+
personality: Personality;
|
|
33
|
+
holdings: Map<string, number>;
|
|
34
|
+
founded: string[];
|
|
35
|
+
rallied: Set<string>;
|
|
36
|
+
voted: Set<string>;
|
|
37
|
+
hasStronghold: boolean;
|
|
38
|
+
activeLoans: Set<string>;
|
|
39
|
+
infiltrated: Set<string>;
|
|
40
|
+
sentiment: Map<string, number>;
|
|
41
|
+
allies: Set<string>;
|
|
42
|
+
rivals: Set<string>;
|
|
43
|
+
actionCount: number;
|
|
44
|
+
lastAction: string;
|
|
45
|
+
recentHistory: string[];
|
|
46
|
+
}
|
|
47
|
+
/** Pluggable LLM interface — bring your own model */
|
|
48
|
+
export interface LLMAdapter {
|
|
49
|
+
generate: (prompt: string) => Promise<string | null>;
|
|
50
|
+
}
|
|
51
|
+
/** Configuration for creating a Pyre agent */
|
|
52
|
+
export interface PyreAgentConfig {
|
|
53
|
+
/** Solana RPC connection */
|
|
54
|
+
connection: Connection;
|
|
55
|
+
/** Agent's keypair (use createEphemeralAgent from torchsdk to generate one) */
|
|
56
|
+
keypair: Keypair;
|
|
57
|
+
/** Network to operate on */
|
|
58
|
+
network: 'devnet' | 'mainnet';
|
|
59
|
+
/** LLM adapter for intelligent decisions (omit for random fallback) */
|
|
60
|
+
llm?: LLMAdapter;
|
|
61
|
+
/** Override personality (auto-assigned if omitted) */
|
|
62
|
+
personality?: Personality;
|
|
63
|
+
/** Override SOL spend range [min, max] per action */
|
|
64
|
+
solRange?: [number, number];
|
|
65
|
+
/** Max factions this agent can found (default: 2) */
|
|
66
|
+
maxFoundedFactions?: number;
|
|
67
|
+
/** SOL to fund stronghold vault on creation */
|
|
68
|
+
strongholdFundSol?: number;
|
|
69
|
+
/** Vault balance threshold below which to top up */
|
|
70
|
+
strongholdTopupThresholdSol?: number;
|
|
71
|
+
/** SOL reserve to keep in wallet (don't spend below this) */
|
|
72
|
+
strongholdTopupReserveSol?: number;
|
|
73
|
+
/** Restore from a previously serialized state */
|
|
74
|
+
state?: SerializedAgentState;
|
|
75
|
+
/** Logger function (defaults to console.log) */
|
|
76
|
+
logger?: (msg: string) => void;
|
|
77
|
+
}
|
|
78
|
+
/** Result of a single agent tick */
|
|
79
|
+
export interface AgentTickResult {
|
|
80
|
+
action: Action;
|
|
81
|
+
faction?: string;
|
|
82
|
+
message?: string;
|
|
83
|
+
reasoning?: string;
|
|
84
|
+
success: boolean;
|
|
85
|
+
error?: string;
|
|
86
|
+
usedLLM: boolean;
|
|
87
|
+
}
|
|
88
|
+
/** Serializable agent state for persistence */
|
|
89
|
+
export interface SerializedAgentState {
|
|
90
|
+
publicKey: string;
|
|
91
|
+
personality: Personality;
|
|
92
|
+
holdings: Record<string, number>;
|
|
93
|
+
founded: string[];
|
|
94
|
+
rallied: string[];
|
|
95
|
+
voted: string[];
|
|
96
|
+
hasStronghold: boolean;
|
|
97
|
+
activeLoans: string[];
|
|
98
|
+
infiltrated: string[];
|
|
99
|
+
sentiment: Record<string, number>;
|
|
100
|
+
allies: string[];
|
|
101
|
+
rivals: string[];
|
|
102
|
+
actionCount: number;
|
|
103
|
+
lastAction: string;
|
|
104
|
+
recentHistory: string[];
|
|
105
|
+
}
|
|
106
|
+
/** The Pyre agent instance */
|
|
107
|
+
export interface PyreAgent {
|
|
108
|
+
/** Agent's public key */
|
|
109
|
+
readonly publicKey: string;
|
|
110
|
+
/** Agent's personality */
|
|
111
|
+
readonly personality: Personality;
|
|
112
|
+
/** Run one decision+action cycle */
|
|
113
|
+
tick(factions?: FactionInfo[]): Promise<AgentTickResult>;
|
|
114
|
+
/** Get current mutable state */
|
|
115
|
+
getState(): AgentState;
|
|
116
|
+
/** Serialize state for persistence */
|
|
117
|
+
serialize(): SerializedAgentState;
|
|
118
|
+
}
|
package/dist/types.js
ADDED
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const pick: <T>(arr: T[]) => T;
|
|
2
|
+
export declare const randRange: (min: number, max: number) => number;
|
|
3
|
+
export declare const sleep: (ms: number) => Promise<unknown>;
|
|
4
|
+
export declare const ts: () => string;
|
|
5
|
+
export declare const log: (agent: string, msg: string) => void;
|
|
6
|
+
export declare const logGlobal: (msg: string) => void;
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logGlobal = exports.log = exports.ts = exports.sleep = exports.randRange = exports.pick = void 0;
|
|
4
|
+
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
|
5
|
+
exports.pick = pick;
|
|
6
|
+
const randRange = (min, max) => min + Math.random() * (max - min);
|
|
7
|
+
exports.randRange = randRange;
|
|
8
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
9
|
+
exports.sleep = sleep;
|
|
10
|
+
const ts = () => new Date().toISOString().substring(11, 19);
|
|
11
|
+
exports.ts = ts;
|
|
12
|
+
const log = (agent, msg) => {
|
|
13
|
+
console.log(`[${(0, exports.ts)()}] [${agent}] ${msg}`);
|
|
14
|
+
};
|
|
15
|
+
exports.log = log;
|
|
16
|
+
const logGlobal = (msg) => {
|
|
17
|
+
console.log(`[${(0, exports.ts)()}] [SWARM] ${msg}`);
|
|
18
|
+
};
|
|
19
|
+
exports.logGlobal = logGlobal;
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pyre-agent-kit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Autonomous agent kit for Pyre — plug in your own LLM and play pyre.world",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pyre-agent-kit": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"clean": "rm -rf dist",
|
|
13
|
+
"test": "npx tsx tests/test_e2e.ts"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@solana/spl-token": "^0.4.6",
|
|
17
|
+
"@solana/web3.js": "^1.98.4",
|
|
18
|
+
"pyre-world-kit": "1.0.21"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^25.4.0",
|
|
22
|
+
"tsx": "^4.19.4",
|
|
23
|
+
"typescript": "^5.4.0"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"packageManager": "pnpm@9.10.0+sha512.73a29afa36a0d092ece5271de5177ecbf8318d454ecd701343131b8ebc0c1a91c487da46ab77c8e596d6acf1461e3594ced4becedf8921b074fbd8653ed7051c"
|
|
30
|
+
}
|