pyre-world-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/actions.d.ts +81 -0
- package/dist/actions.js +318 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +74 -0
- package/dist/intel.d.ts +61 -0
- package/dist/intel.js +342 -0
- package/dist/mappers.d.ts +31 -0
- package/dist/mappers.js +258 -0
- package/dist/types.d.ts +353 -0
- package/dist/types.js +9 -0
- package/dist/vanity.d.ts +14 -0
- package/dist/vanity.js +115 -0
- package/package.json +25 -0
- package/readme.md +203 -0
- package/src/actions.ts +523 -0
- package/src/index.ts +141 -0
- package/src/intel.ts +424 -0
- package/src/mappers.ts +302 -0
- package/src/types.ts +425 -0
- package/src/vanity.ts +159 -0
- package/tests/test_devnet_e2e.ts +401 -0
- package/tests/test_e2e.ts +213 -0
- package/tests/test_sim.ts +458 -0
- package/tsconfig.json +17 -0
package/src/vanity.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pyre vanity mint address grinder
|
|
3
|
+
*
|
|
4
|
+
* Grinds for Solana keypairs whose base58 address ends with "pyre".
|
|
5
|
+
* This is how we distinguish pyre faction tokens from regular torch tokens —
|
|
6
|
+
* no registry program needed, just check the mint suffix.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
Connection,
|
|
11
|
+
PublicKey,
|
|
12
|
+
Transaction,
|
|
13
|
+
SystemProgram,
|
|
14
|
+
SYSVAR_RENT_PUBKEY,
|
|
15
|
+
Keypair,
|
|
16
|
+
} from '@solana/web3.js'
|
|
17
|
+
import {
|
|
18
|
+
getAssociatedTokenAddressSync,
|
|
19
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
20
|
+
} from '@solana/spl-token'
|
|
21
|
+
import { BN, Program, AnchorProvider, Wallet } from '@coral-xyz/anchor'
|
|
22
|
+
import type { CreateTokenResult, CreateTokenParams } from 'torchsdk'
|
|
23
|
+
import { PROGRAM_ID } from 'torchsdk'
|
|
24
|
+
|
|
25
|
+
// Token-2022 program ID
|
|
26
|
+
const TOKEN_2022_PROGRAM_ID = new PublicKey('TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb')
|
|
27
|
+
|
|
28
|
+
// PDA seeds (must match the Rust program)
|
|
29
|
+
const GLOBAL_CONFIG_SEED = 'global_config'
|
|
30
|
+
const BONDING_CURVE_SEED = 'bonding_curve'
|
|
31
|
+
const TREASURY_SEED = 'treasury'
|
|
32
|
+
const TREASURY_LOCK_SEED = 'treasury_lock'
|
|
33
|
+
|
|
34
|
+
// IDL loaded from torchsdk dist
|
|
35
|
+
import idl from 'torchsdk/dist/torch_market.json'
|
|
36
|
+
|
|
37
|
+
// ── PDA helpers (copied from torchsdk internals) ──
|
|
38
|
+
|
|
39
|
+
const getGlobalConfigPda = (): [PublicKey, number] =>
|
|
40
|
+
PublicKey.findProgramAddressSync([Buffer.from(GLOBAL_CONFIG_SEED)], PROGRAM_ID)
|
|
41
|
+
|
|
42
|
+
const getBondingCurvePda = (mint: PublicKey): [PublicKey, number] =>
|
|
43
|
+
PublicKey.findProgramAddressSync([Buffer.from(BONDING_CURVE_SEED), mint.toBuffer()], PROGRAM_ID)
|
|
44
|
+
|
|
45
|
+
const getTokenTreasuryPda = (mint: PublicKey): [PublicKey, number] =>
|
|
46
|
+
PublicKey.findProgramAddressSync([Buffer.from(TREASURY_SEED), mint.toBuffer()], PROGRAM_ID)
|
|
47
|
+
|
|
48
|
+
const getTreasuryTokenAccount = (mint: PublicKey, treasury: PublicKey): PublicKey =>
|
|
49
|
+
getAssociatedTokenAddressSync(mint, treasury, true, TOKEN_2022_PROGRAM_ID)
|
|
50
|
+
|
|
51
|
+
const getTreasuryLockPda = (mint: PublicKey): [PublicKey, number] =>
|
|
52
|
+
PublicKey.findProgramAddressSync([Buffer.from(TREASURY_LOCK_SEED), mint.toBuffer()], PROGRAM_ID)
|
|
53
|
+
|
|
54
|
+
const getTreasuryLockTokenAccount = (mint: PublicKey, treasuryLock: PublicKey): PublicKey =>
|
|
55
|
+
getAssociatedTokenAddressSync(mint, treasuryLock, true, TOKEN_2022_PROGRAM_ID)
|
|
56
|
+
|
|
57
|
+
const makeDummyProvider = (connection: Connection, payer: PublicKey): AnchorProvider => {
|
|
58
|
+
const dummyWallet = {
|
|
59
|
+
publicKey: payer,
|
|
60
|
+
signTransaction: async (t: Transaction) => t,
|
|
61
|
+
signAllTransactions: async (t: Transaction[]) => t,
|
|
62
|
+
}
|
|
63
|
+
return new AnchorProvider(connection, dummyWallet as unknown as Wallet, {})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const finalizeTransaction = async (connection: Connection, tx: Transaction, feePayer: PublicKey): Promise<void> => {
|
|
67
|
+
const { blockhash } = await connection.getLatestBlockhash()
|
|
68
|
+
tx.recentBlockhash = blockhash
|
|
69
|
+
tx.feePayer = feePayer
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── Vanity grinder ──
|
|
73
|
+
|
|
74
|
+
const PYRE_SUFFIX = 'py'
|
|
75
|
+
|
|
76
|
+
/** Grind for a keypair whose base58 address ends with "py" */
|
|
77
|
+
export function grindPyreMint(maxAttempts: number = 500_000): Keypair {
|
|
78
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
79
|
+
const kp = Keypair.generate()
|
|
80
|
+
if (kp.publicKey.toBase58().endsWith(PYRE_SUFFIX)) {
|
|
81
|
+
return kp
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Fallback — return last generated keypair (should be extremely rare)
|
|
85
|
+
return Keypair.generate()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Check if a mint address is a pyre faction (ends with "py") */
|
|
89
|
+
export function isPyreMint(mint: string): boolean {
|
|
90
|
+
return mint.endsWith(PYRE_SUFFIX)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Build create transaction with pyre vanity address ──
|
|
94
|
+
|
|
95
|
+
export async function buildCreateFactionTransaction(
|
|
96
|
+
connection: Connection,
|
|
97
|
+
params: CreateTokenParams,
|
|
98
|
+
): Promise<CreateTokenResult> {
|
|
99
|
+
const { creator: creatorStr, name, symbol, metadata_uri, sol_target = 0, community_token = true } = params
|
|
100
|
+
|
|
101
|
+
const creator = new PublicKey(creatorStr)
|
|
102
|
+
|
|
103
|
+
if (name.length > 32) throw new Error('Name must be 32 characters or less')
|
|
104
|
+
if (symbol.length > 10) throw new Error('Symbol must be 10 characters or less')
|
|
105
|
+
|
|
106
|
+
// Grind for "pyre" suffix instead of "tm"
|
|
107
|
+
const mint = grindPyreMint()
|
|
108
|
+
|
|
109
|
+
// Derive PDAs
|
|
110
|
+
const [globalConfig] = getGlobalConfigPda()
|
|
111
|
+
const [bondingCurve] = getBondingCurvePda(mint.publicKey)
|
|
112
|
+
const [treasury] = getTokenTreasuryPda(mint.publicKey)
|
|
113
|
+
const bondingCurveTokenAccount = getAssociatedTokenAddressSync(
|
|
114
|
+
mint.publicKey,
|
|
115
|
+
bondingCurve,
|
|
116
|
+
true,
|
|
117
|
+
TOKEN_2022_PROGRAM_ID,
|
|
118
|
+
)
|
|
119
|
+
const treasuryTokenAccount = getTreasuryTokenAccount(mint.publicKey, treasury)
|
|
120
|
+
const [treasuryLock] = getTreasuryLockPda(mint.publicKey)
|
|
121
|
+
const treasuryLockTokenAccount = getTreasuryLockTokenAccount(mint.publicKey, treasuryLock)
|
|
122
|
+
|
|
123
|
+
const tx = new Transaction()
|
|
124
|
+
|
|
125
|
+
const provider = makeDummyProvider(connection, creator)
|
|
126
|
+
const program = new Program(idl as any, provider)
|
|
127
|
+
|
|
128
|
+
const createIx = await (program.methods
|
|
129
|
+
.createToken({ name, symbol, uri: metadata_uri, solTarget: new BN(sol_target), communityToken: community_token }) as any)
|
|
130
|
+
.accounts({
|
|
131
|
+
creator,
|
|
132
|
+
globalConfig,
|
|
133
|
+
mint: mint.publicKey,
|
|
134
|
+
bondingCurve,
|
|
135
|
+
tokenVault: bondingCurveTokenAccount,
|
|
136
|
+
treasury,
|
|
137
|
+
treasuryTokenAccount,
|
|
138
|
+
treasuryLock,
|
|
139
|
+
treasuryLockTokenAccount,
|
|
140
|
+
token2022Program: TOKEN_2022_PROGRAM_ID,
|
|
141
|
+
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
142
|
+
systemProgram: SystemProgram.programId,
|
|
143
|
+
rent: SYSVAR_RENT_PUBKEY,
|
|
144
|
+
})
|
|
145
|
+
.instruction()
|
|
146
|
+
|
|
147
|
+
tx.add(createIx)
|
|
148
|
+
await finalizeTransaction(connection, tx, creator)
|
|
149
|
+
|
|
150
|
+
// Partially sign with mint keypair
|
|
151
|
+
tx.partialSign(mint)
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
transaction: tx,
|
|
155
|
+
mint: mint.publicKey,
|
|
156
|
+
mintKeypair: mint,
|
|
157
|
+
message: `Create faction "${name}" ($${symbol}) [pyre:${mint.publicKey.toBase58()}]`,
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pyre Kit Devnet E2E Test
|
|
3
|
+
*
|
|
4
|
+
* Tests the full faction warfare flow on Solana devnet.
|
|
5
|
+
* Creates a faction with a "py" vanity mint, joins, rallies, defects.
|
|
6
|
+
*
|
|
7
|
+
* Run:
|
|
8
|
+
* npx tsx tests/test_devnet_e2e.ts
|
|
9
|
+
*
|
|
10
|
+
* Requirements:
|
|
11
|
+
* - Devnet wallet (~/.config/solana/id.json) with ~5 SOL
|
|
12
|
+
* - Torch Market program deployed to devnet
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Must be set before any torchsdk imports
|
|
16
|
+
process.env.TORCH_NETWORK = 'devnet'
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
Connection,
|
|
20
|
+
Keypair,
|
|
21
|
+
LAMPORTS_PER_SOL,
|
|
22
|
+
Transaction,
|
|
23
|
+
SystemProgram,
|
|
24
|
+
} from '@solana/web3.js'
|
|
25
|
+
import {
|
|
26
|
+
createEphemeralAgent,
|
|
27
|
+
createStronghold,
|
|
28
|
+
fundStronghold,
|
|
29
|
+
recruitAgent,
|
|
30
|
+
launchFaction,
|
|
31
|
+
getFactions,
|
|
32
|
+
getFaction,
|
|
33
|
+
getJoinQuote,
|
|
34
|
+
joinFaction,
|
|
35
|
+
directJoinFaction,
|
|
36
|
+
getComms,
|
|
37
|
+
rally,
|
|
38
|
+
defect,
|
|
39
|
+
getMembers,
|
|
40
|
+
getStrongholdForAgent,
|
|
41
|
+
isPyreMint,
|
|
42
|
+
} from '../src/index'
|
|
43
|
+
import * as fs from 'fs'
|
|
44
|
+
import * as path from 'path'
|
|
45
|
+
import * as os from 'os'
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Config
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
const DEVNET_RPC = 'https://api.devnet.solana.com'
|
|
52
|
+
const WALLET_PATH = path.join(os.homedir(), '.config/solana/id.json')
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Helpers
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
const loadWallet = (): Keypair => {
|
|
59
|
+
const raw = JSON.parse(fs.readFileSync(WALLET_PATH, 'utf-8'))
|
|
60
|
+
return Keypair.fromSecretKey(Uint8Array.from(raw))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const log = (msg: string) => {
|
|
64
|
+
const ts = new Date().toISOString().substr(11, 8)
|
|
65
|
+
console.log(`[${ts}] ${msg}`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
|
69
|
+
|
|
70
|
+
const signAndSend = async (
|
|
71
|
+
connection: Connection,
|
|
72
|
+
signer: Keypair,
|
|
73
|
+
tx: Transaction,
|
|
74
|
+
): Promise<string> => {
|
|
75
|
+
tx.partialSign(signer)
|
|
76
|
+
const sig = await connection.sendRawTransaction(tx.serialize(), {
|
|
77
|
+
skipPreflight: false,
|
|
78
|
+
preflightCommitment: 'confirmed',
|
|
79
|
+
})
|
|
80
|
+
await connection.confirmTransaction(sig, 'confirmed')
|
|
81
|
+
return sig
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let passed = 0
|
|
85
|
+
let failed = 0
|
|
86
|
+
const ok = (name: string, detail?: string) => {
|
|
87
|
+
passed++
|
|
88
|
+
log(` ✓ ${name}${detail ? ` — ${detail}` : ''}`)
|
|
89
|
+
}
|
|
90
|
+
const fail = (name: string, err: any) => {
|
|
91
|
+
failed++
|
|
92
|
+
log(` ✗ ${name} — ${err.message || err}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Main
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
async function main() {
|
|
100
|
+
console.log('='.repeat(60))
|
|
101
|
+
console.log('PYRE KIT — DEVNET E2E TEST')
|
|
102
|
+
console.log('='.repeat(60))
|
|
103
|
+
|
|
104
|
+
const connection = new Connection(DEVNET_RPC, 'confirmed')
|
|
105
|
+
const wallet = loadWallet()
|
|
106
|
+
const walletAddr = wallet.publicKey.toBase58()
|
|
107
|
+
|
|
108
|
+
log(`Wallet: ${walletAddr}`)
|
|
109
|
+
const balance = await connection.getBalance(wallet.publicKey)
|
|
110
|
+
log(`Balance: ${(balance / LAMPORTS_PER_SOL).toFixed(2)} SOL`)
|
|
111
|
+
|
|
112
|
+
if (balance < 3 * LAMPORTS_PER_SOL) {
|
|
113
|
+
console.error('Need at least ~3 SOL on devnet.')
|
|
114
|
+
process.exit(1)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ================================================================
|
|
118
|
+
// 1. Create ephemeral agents
|
|
119
|
+
// ================================================================
|
|
120
|
+
log('\n[1] Creating ephemeral agents')
|
|
121
|
+
const agent1 = createEphemeralAgent()
|
|
122
|
+
const agent2 = createEphemeralAgent()
|
|
123
|
+
log(` Agent 1: ${agent1.publicKey}`)
|
|
124
|
+
log(` Agent 2: ${agent2.publicKey}`)
|
|
125
|
+
|
|
126
|
+
// Fund agents from main wallet
|
|
127
|
+
const fundTx = new Transaction().add(
|
|
128
|
+
SystemProgram.transfer({
|
|
129
|
+
fromPubkey: wallet.publicKey,
|
|
130
|
+
toPubkey: agent1.keypair.publicKey,
|
|
131
|
+
lamports: 1.5 * LAMPORTS_PER_SOL,
|
|
132
|
+
}),
|
|
133
|
+
SystemProgram.transfer({
|
|
134
|
+
fromPubkey: wallet.publicKey,
|
|
135
|
+
toPubkey: agent2.keypair.publicKey,
|
|
136
|
+
lamports: 0.5 * LAMPORTS_PER_SOL,
|
|
137
|
+
}),
|
|
138
|
+
)
|
|
139
|
+
const { blockhash } = await connection.getLatestBlockhash()
|
|
140
|
+
fundTx.recentBlockhash = blockhash
|
|
141
|
+
fundTx.feePayer = wallet.publicKey
|
|
142
|
+
await signAndSend(connection, wallet, fundTx)
|
|
143
|
+
ok('Fund agents', '1.5 SOL + 0.5 SOL')
|
|
144
|
+
|
|
145
|
+
await sleep(500)
|
|
146
|
+
|
|
147
|
+
// ================================================================
|
|
148
|
+
// 2. Create stronghold
|
|
149
|
+
// ================================================================
|
|
150
|
+
log('\n[2] Creating stronghold')
|
|
151
|
+
try {
|
|
152
|
+
const result = await createStronghold(connection, {
|
|
153
|
+
creator: agent1.publicKey,
|
|
154
|
+
})
|
|
155
|
+
await signAndSend(connection, agent1.keypair, result.transaction)
|
|
156
|
+
ok('Create stronghold')
|
|
157
|
+
} catch (e: any) {
|
|
158
|
+
if (e.message?.includes('already in use')) {
|
|
159
|
+
ok('Create stronghold', 'already exists')
|
|
160
|
+
} else {
|
|
161
|
+
fail('Create stronghold', e)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
await sleep(500)
|
|
166
|
+
|
|
167
|
+
// ================================================================
|
|
168
|
+
// 3. Fund stronghold
|
|
169
|
+
// ================================================================
|
|
170
|
+
log('\n[3] Funding stronghold')
|
|
171
|
+
try {
|
|
172
|
+
const result = await fundStronghold(connection, {
|
|
173
|
+
depositor: agent1.publicKey,
|
|
174
|
+
stronghold_creator: agent1.publicKey,
|
|
175
|
+
amount_sol: 1 * LAMPORTS_PER_SOL,
|
|
176
|
+
})
|
|
177
|
+
await signAndSend(connection, agent1.keypair, result.transaction)
|
|
178
|
+
ok('Fund stronghold', '1 SOL')
|
|
179
|
+
} catch (e: any) {
|
|
180
|
+
fail('Fund stronghold', e)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
await sleep(500)
|
|
184
|
+
|
|
185
|
+
// ================================================================
|
|
186
|
+
// 4. Verify stronghold link
|
|
187
|
+
// ================================================================
|
|
188
|
+
log('\n[4] Verifying stronghold')
|
|
189
|
+
try {
|
|
190
|
+
const stronghold = await getStrongholdForAgent(connection, agent1.publicKey)
|
|
191
|
+
if (stronghold) {
|
|
192
|
+
ok('Stronghold link', `balance=${(stronghold.sol_balance / LAMPORTS_PER_SOL).toFixed(2)} SOL, agents=${stronghold.linked_agents}`)
|
|
193
|
+
} else {
|
|
194
|
+
fail('Stronghold link', { message: 'not found' })
|
|
195
|
+
}
|
|
196
|
+
} catch (e: any) {
|
|
197
|
+
fail('Stronghold link', e)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ================================================================
|
|
201
|
+
// 5. Launch faction (with py vanity mint!)
|
|
202
|
+
// ================================================================
|
|
203
|
+
log('\n[5] Launching faction (grinding py vanity mint...)')
|
|
204
|
+
let factionMint: string = ''
|
|
205
|
+
try {
|
|
206
|
+
const startTime = Date.now()
|
|
207
|
+
const result = await launchFaction(connection, {
|
|
208
|
+
founder: agent1.publicKey,
|
|
209
|
+
name: 'Devnet Pyre Faction',
|
|
210
|
+
symbol: 'DPYRE',
|
|
211
|
+
metadata_uri: 'https://torch.market/test-metadata.json',
|
|
212
|
+
community_faction: true,
|
|
213
|
+
})
|
|
214
|
+
const grindMs = Date.now() - startTime
|
|
215
|
+
await signAndSend(connection, agent1.keypair, result.transaction)
|
|
216
|
+
factionMint = result.mint.toBase58()
|
|
217
|
+
|
|
218
|
+
const hasPySuffix = isPyreMint(factionMint)
|
|
219
|
+
ok('Launch faction', `mint=${factionMint.slice(0, 8)}...${factionMint.slice(-4)} vanity=${hasPySuffix ? 'py✓' : 'MISS'} grind=${grindMs}ms`)
|
|
220
|
+
|
|
221
|
+
if (!hasPySuffix) {
|
|
222
|
+
log(' ⚠ Vanity grind did not find "py" suffix — faction still works but won\'t be filtered as pyre')
|
|
223
|
+
}
|
|
224
|
+
} catch (e: any) {
|
|
225
|
+
fail('Launch faction', e)
|
|
226
|
+
console.error('Cannot continue without faction. Exiting.')
|
|
227
|
+
process.exit(1)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
await sleep(1000)
|
|
231
|
+
|
|
232
|
+
// ================================================================
|
|
233
|
+
// 6. List factions
|
|
234
|
+
// ================================================================
|
|
235
|
+
log('\n[6] Listing factions')
|
|
236
|
+
try {
|
|
237
|
+
const factions = await getFactions(connection, { limit: 10 })
|
|
238
|
+
const ourFaction = factions.factions.find(f => f.mint === factionMint)
|
|
239
|
+
ok('List factions', `total=${factions.total}, ours=${ourFaction ? 'found' : 'not found'}`)
|
|
240
|
+
} catch (e: any) {
|
|
241
|
+
fail('List factions', e)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ================================================================
|
|
245
|
+
// 7. Get faction detail
|
|
246
|
+
// ================================================================
|
|
247
|
+
log('\n[7] Getting faction detail')
|
|
248
|
+
try {
|
|
249
|
+
const detail = await getFaction(connection, factionMint)
|
|
250
|
+
ok('Faction detail', `name=${detail.name} status=${detail.status} tier=${detail.tier}`)
|
|
251
|
+
} catch (e: any) {
|
|
252
|
+
fail('Faction detail', e)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ================================================================
|
|
256
|
+
// 8. Get join quote
|
|
257
|
+
// ================================================================
|
|
258
|
+
log('\n[8] Getting join quote (0.1 SOL)')
|
|
259
|
+
let tokensOut = 0
|
|
260
|
+
try {
|
|
261
|
+
const quote = await getJoinQuote(connection, factionMint, 0.1 * LAMPORTS_PER_SOL)
|
|
262
|
+
tokensOut = quote.tokens_to_user
|
|
263
|
+
ok('Join quote', `tokens=${tokensOut} impact=${quote.price_impact_percent}%`)
|
|
264
|
+
} catch (e: any) {
|
|
265
|
+
fail('Join quote', e)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ================================================================
|
|
269
|
+
// 9. Join faction via vault
|
|
270
|
+
// ================================================================
|
|
271
|
+
log('\n[9] Joining faction via vault')
|
|
272
|
+
try {
|
|
273
|
+
const result = await joinFaction(connection, {
|
|
274
|
+
mint: factionMint,
|
|
275
|
+
agent: agent1.publicKey,
|
|
276
|
+
amount_sol: 0.1 * LAMPORTS_PER_SOL,
|
|
277
|
+
strategy: 'fortify',
|
|
278
|
+
message: 'First blood. The pyre burns.',
|
|
279
|
+
stronghold: agent1.publicKey,
|
|
280
|
+
})
|
|
281
|
+
await signAndSend(connection, agent1.keypair, result.transaction)
|
|
282
|
+
ok('Join faction', result.message)
|
|
283
|
+
} catch (e: any) {
|
|
284
|
+
fail('Join faction', e)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
await sleep(1000)
|
|
288
|
+
|
|
289
|
+
// ================================================================
|
|
290
|
+
// 10. Agent 2 joins directly (no vault)
|
|
291
|
+
// ================================================================
|
|
292
|
+
log('\n[10] Agent 2 joins directly')
|
|
293
|
+
try {
|
|
294
|
+
const result = await directJoinFaction(connection, {
|
|
295
|
+
mint: factionMint,
|
|
296
|
+
agent: agent2.publicKey,
|
|
297
|
+
amount_sol: 0.05 * LAMPORTS_PER_SOL,
|
|
298
|
+
strategy: 'scorched_earth',
|
|
299
|
+
message: 'Reporting for duty.',
|
|
300
|
+
})
|
|
301
|
+
await signAndSend(connection, agent2.keypair, result.transaction)
|
|
302
|
+
ok('Agent 2 join', result.message)
|
|
303
|
+
} catch (e: any) {
|
|
304
|
+
fail('Agent 2 join', e)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
await sleep(1000)
|
|
308
|
+
|
|
309
|
+
// ================================================================
|
|
310
|
+
// 11. Read comms
|
|
311
|
+
// ================================================================
|
|
312
|
+
log('\n[11] Reading comms')
|
|
313
|
+
try {
|
|
314
|
+
const comms = await getComms(connection, factionMint)
|
|
315
|
+
ok('Read comms', `total=${comms.total}`)
|
|
316
|
+
for (const c of comms.comms) {
|
|
317
|
+
log(` ${c.sender.slice(0, 8)}...: "${c.memo}"`)
|
|
318
|
+
}
|
|
319
|
+
} catch (e: any) {
|
|
320
|
+
fail('Read comms', e)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ================================================================
|
|
324
|
+
// 12. Rally (agent 2 — can't rally your own faction)
|
|
325
|
+
// ================================================================
|
|
326
|
+
log('\n[12] Agent 2 rallies faction')
|
|
327
|
+
try {
|
|
328
|
+
const result = await rally(connection, {
|
|
329
|
+
mint: factionMint,
|
|
330
|
+
agent: agent2.publicKey,
|
|
331
|
+
})
|
|
332
|
+
await signAndSend(connection, agent2.keypair, result.transaction)
|
|
333
|
+
ok('Rally')
|
|
334
|
+
|
|
335
|
+
const detail = await getFaction(connection, factionMint)
|
|
336
|
+
log(` Rallies: ${detail.rallies}`)
|
|
337
|
+
} catch (e: any) {
|
|
338
|
+
fail('Rally', e)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
await sleep(500)
|
|
342
|
+
|
|
343
|
+
// ================================================================
|
|
344
|
+
// 13. Defect (agent 1 sells half)
|
|
345
|
+
// ================================================================
|
|
346
|
+
log('\n[13] Agent 1 defects (partial)')
|
|
347
|
+
try {
|
|
348
|
+
const sellAmount = Math.floor(tokensOut / 2)
|
|
349
|
+
if (sellAmount < 1) {
|
|
350
|
+
ok('Defect', 'skipped — no tokens')
|
|
351
|
+
} else {
|
|
352
|
+
const result = await defect(connection, {
|
|
353
|
+
mint: factionMint,
|
|
354
|
+
agent: agent1.publicKey,
|
|
355
|
+
amount_tokens: sellAmount,
|
|
356
|
+
message: 'Strategic withdrawal.',
|
|
357
|
+
stronghold: agent1.publicKey,
|
|
358
|
+
})
|
|
359
|
+
await signAndSend(connection, agent1.keypair, result.transaction)
|
|
360
|
+
ok('Defect', `sold ${sellAmount} tokens`)
|
|
361
|
+
}
|
|
362
|
+
} catch (e: any) {
|
|
363
|
+
fail('Defect', e)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
await sleep(500)
|
|
367
|
+
|
|
368
|
+
// ================================================================
|
|
369
|
+
// 14. Check members
|
|
370
|
+
// ================================================================
|
|
371
|
+
log('\n[14] Checking members')
|
|
372
|
+
try {
|
|
373
|
+
const members = await getMembers(connection, factionMint)
|
|
374
|
+
ok('Members', `total=${members.total_members}`)
|
|
375
|
+
for (const m of members.members.slice(0, 5)) {
|
|
376
|
+
log(` ${m.address.slice(0, 8)}... — ${m.percentage.toFixed(2)}%`)
|
|
377
|
+
}
|
|
378
|
+
} catch (e: any) {
|
|
379
|
+
fail('Members', e)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ================================================================
|
|
383
|
+
// Summary
|
|
384
|
+
// ================================================================
|
|
385
|
+
const finalBalance = await connection.getBalance(wallet.publicKey)
|
|
386
|
+
const solSpent = (balance - finalBalance) / LAMPORTS_PER_SOL
|
|
387
|
+
|
|
388
|
+
console.log('\n' + '='.repeat(60))
|
|
389
|
+
console.log(`RESULTS: ${passed} passed, ${failed} failed`)
|
|
390
|
+
console.log(`Faction mint: ${factionMint}`)
|
|
391
|
+
console.log(`Vanity "py" suffix: ${isPyreMint(factionMint) ? 'YES' : 'NO'}`)
|
|
392
|
+
console.log(`SOL spent: ${solSpent.toFixed(4)} SOL (${(finalBalance / LAMPORTS_PER_SOL).toFixed(2)} remaining)`)
|
|
393
|
+
console.log('='.repeat(60))
|
|
394
|
+
|
|
395
|
+
process.exit(failed > 0 ? 1 : 0)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
main().catch((e) => {
|
|
399
|
+
console.error('\nFATAL:', e)
|
|
400
|
+
process.exit(1)
|
|
401
|
+
})
|