@conquest-eth/tools 0.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/LICENSE +21 -0
- package/README.md +259 -0
- package/dist/cli-tool-generator.d.ts +22 -0
- package/dist/cli-tool-generator.d.ts.map +1 -0
- package/dist/cli-tool-generator.js +217 -0
- package/dist/cli-tool-generator.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +76 -0
- package/dist/cli.js.map +1 -0
- package/dist/contracts/space-info.d.ts +7 -0
- package/dist/contracts/space-info.d.ts.map +1 -0
- package/dist/contracts/space-info.js +34 -0
- package/dist/contracts/space-info.js.map +1 -0
- package/dist/fleet/index.d.ts +4 -0
- package/dist/fleet/index.d.ts.map +1 -0
- package/dist/fleet/index.js +7 -0
- package/dist/fleet/index.js.map +1 -0
- package/dist/fleet/manager.d.ts +70 -0
- package/dist/fleet/manager.d.ts.map +1 -0
- package/dist/fleet/manager.js +92 -0
- package/dist/fleet/manager.js.map +1 -0
- package/dist/fleet/resolve.d.ts +51 -0
- package/dist/fleet/resolve.d.ts.map +1 -0
- package/dist/fleet/resolve.js +140 -0
- package/dist/fleet/resolve.js.map +1 -0
- package/dist/fleet/send.d.ts +29 -0
- package/dist/fleet/send.d.ts.map +1 -0
- package/dist/fleet/send.js +81 -0
- package/dist/fleet/send.js.map +1 -0
- package/dist/helpers/index.d.ts +14 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +28 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -0
- package/dist/planet/acquire.d.ts +16 -0
- package/dist/planet/acquire.d.ts.map +1 -0
- package/dist/planet/acquire.js +27 -0
- package/dist/planet/acquire.js.map +1 -0
- package/dist/planet/exit.d.ts +17 -0
- package/dist/planet/exit.d.ts.map +1 -0
- package/dist/planet/exit.js +56 -0
- package/dist/planet/exit.js.map +1 -0
- package/dist/planet/index.d.ts +4 -0
- package/dist/planet/index.d.ts.map +1 -0
- package/dist/planet/index.js +6 -0
- package/dist/planet/index.js.map +1 -0
- package/dist/planet/manager.d.ts +106 -0
- package/dist/planet/manager.d.ts.map +1 -0
- package/dist/planet/manager.js +253 -0
- package/dist/planet/manager.js.map +1 -0
- package/dist/storage/interface.d.ts +93 -0
- package/dist/storage/interface.d.ts.map +1 -0
- package/dist/storage/interface.js +2 -0
- package/dist/storage/interface.js.map +1 -0
- package/dist/storage/json-storage.d.ts +28 -0
- package/dist/storage/json-storage.d.ts.map +1 -0
- package/dist/storage/json-storage.js +148 -0
- package/dist/storage/json-storage.js.map +1 -0
- package/dist/tools/acquire_planets.d.ts +7 -0
- package/dist/tools/acquire_planets.d.ts.map +1 -0
- package/dist/tools/acquire_planets.js +63 -0
- package/dist/tools/acquire_planets.js.map +1 -0
- package/dist/tools/exit_planets.d.ts +5 -0
- package/dist/tools/exit_planets.d.ts.map +1 -0
- package/dist/tools/exit_planets.js +31 -0
- package/dist/tools/exit_planets.js.map +1 -0
- package/dist/tools/get_my_planets.d.ts +5 -0
- package/dist/tools/get_my_planets.d.ts.map +1 -0
- package/dist/tools/get_my_planets.js +30 -0
- package/dist/tools/get_my_planets.js.map +1 -0
- package/dist/tools/get_pending_exits.d.ts +3 -0
- package/dist/tools/get_pending_exits.d.ts.map +1 -0
- package/dist/tools/get_pending_exits.js +37 -0
- package/dist/tools/get_pending_exits.js.map +1 -0
- package/dist/tools/get_pending_fleets.d.ts +3 -0
- package/dist/tools/get_pending_fleets.d.ts.map +1 -0
- package/dist/tools/get_pending_fleets.js +41 -0
- package/dist/tools/get_pending_fleets.js.map +1 -0
- package/dist/tools/get_planets_around.d.ts +7 -0
- package/dist/tools/get_planets_around.d.ts.map +1 -0
- package/dist/tools/get_planets_around.js +41 -0
- package/dist/tools/get_planets_around.js.map +1 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +11 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/resolve_fleet.d.ts +5 -0
- package/dist/tools/resolve_fleet.d.ts.map +1 -0
- package/dist/tools/resolve_fleet.js +37 -0
- package/dist/tools/resolve_fleet.js.map +1 -0
- package/dist/tools/send_fleet.d.ts +16 -0
- package/dist/tools/send_fleet.d.ts.map +1 -0
- package/dist/tools/send_fleet.js +62 -0
- package/dist/tools/send_fleet.js.map +1 -0
- package/dist/tools/verify_exit_status.d.ts +5 -0
- package/dist/tools/verify_exit_status.d.ts.map +1 -0
- package/dist/tools/verify_exit_status.js +39 -0
- package/dist/tools/verify_exit_status.js.map +1 -0
- package/dist/types.d.ts +126 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +34 -0
- package/dist/types.js.map +1 -0
- package/dist/util/hashing.d.ts +33 -0
- package/dist/util/hashing.d.ts.map +1 -0
- package/dist/util/hashing.js +38 -0
- package/dist/util/hashing.js.map +1 -0
- package/dist/util/time.d.ts +43 -0
- package/dist/util/time.d.ts.map +1 -0
- package/dist/util/time.js +55 -0
- package/dist/util/time.js.map +1 -0
- package/package.json +78 -0
- package/src/cli-tool-generator.ts +287 -0
- package/src/cli.ts +109 -0
- package/src/contracts/space-info.ts +41 -0
- package/src/fleet/index.ts +8 -0
- package/src/fleet/manager.ts +140 -0
- package/src/fleet/resolve.ts +187 -0
- package/src/fleet/send.ts +112 -0
- package/src/helpers/index.ts +59 -0
- package/src/index.ts +181 -0
- package/src/planet/acquire.ts +41 -0
- package/src/planet/exit.ts +71 -0
- package/src/planet/index.ts +6 -0
- package/src/planet/manager.ts +335 -0
- package/src/storage/interface.ts +111 -0
- package/src/storage/json-storage.ts +184 -0
- package/src/tools/acquire_planets.ts +81 -0
- package/src/tools/exit_planets.ts +35 -0
- package/src/tools/get_my_planets.ts +30 -0
- package/src/tools/get_pending_exits.ts +37 -0
- package/src/tools/get_pending_fleets.ts +41 -0
- package/src/tools/get_planets_around.ts +44 -0
- package/src/tools/index.ts +10 -0
- package/src/tools/resolve_fleet.ts +37 -0
- package/src/tools/send_fleet.ts +68 -0
- package/src/tools/verify_exit_status.ts +43 -0
- package/src/types.ts +178 -0
- package/src/util/hashing.ts +60 -0
- package/src/util/time.ts +66 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import {createServer} from './index.js';
|
|
4
|
+
import {Command} from 'commander';
|
|
5
|
+
import pkg from '../package.json' with {type: 'json'};
|
|
6
|
+
import {getChain} from 'tools-ethereum/helpers';
|
|
7
|
+
import {loadEnv} from 'ldenv';
|
|
8
|
+
import * as tools from './tools/index.js';
|
|
9
|
+
import {registerAllToolCommands} from './cli-tool-generator.js';
|
|
10
|
+
|
|
11
|
+
loadEnv();
|
|
12
|
+
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
// Get the binary name from package.json
|
|
16
|
+
const binName = Object.keys(pkg.bin || {})[0];
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.name(binName)
|
|
20
|
+
.description(pkg.description || 'Conquest.eth CLI - MCP server and direct tool execution')
|
|
21
|
+
.version(pkg.version)
|
|
22
|
+
// Global options available to all commands
|
|
23
|
+
.option('--rpc-url <url>', 'RPC URL for the Ethereum network', process.env.RPC_URL || '')
|
|
24
|
+
.option(
|
|
25
|
+
'--game-contract <address>',
|
|
26
|
+
'Contract address of the game',
|
|
27
|
+
process.env.GAME_CONTRACT || '',
|
|
28
|
+
)
|
|
29
|
+
.option('--storage <type>', 'Storage backend: json or sqlite', process.env.STORAGE_TYPE || 'json')
|
|
30
|
+
.option(
|
|
31
|
+
'--storage-path <path>',
|
|
32
|
+
'Path to storage directory',
|
|
33
|
+
process.env.STORAGE_PATH || './data',
|
|
34
|
+
)
|
|
35
|
+
.option(
|
|
36
|
+
'--private-key <key>',
|
|
37
|
+
'Private key for sending transactions',
|
|
38
|
+
process.env.PRIVATE_KEY || '',
|
|
39
|
+
)
|
|
40
|
+
.action(() => {
|
|
41
|
+
program.help();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// MCP subcommand - starts the MCP server
|
|
45
|
+
program
|
|
46
|
+
.command('mcp')
|
|
47
|
+
.description('Start the MCP server')
|
|
48
|
+
.option(
|
|
49
|
+
'--ethereum',
|
|
50
|
+
'Whether to also provide tools-ethereum tools',
|
|
51
|
+
process.env.ETHEREUM_TOOLS === 'true',
|
|
52
|
+
)
|
|
53
|
+
.action(async () => {
|
|
54
|
+
const options = program.opts();
|
|
55
|
+
const mcpOptions = program.commands.find((cmd) => cmd.name() === 'mcp')?.opts() || {};
|
|
56
|
+
|
|
57
|
+
const rpcUrl = options.rpcUrl;
|
|
58
|
+
const gameContract = options.gameContract;
|
|
59
|
+
const ethereum = mcpOptions.ethereum ?? process.env.ETHEREUM_TOOLS === 'true';
|
|
60
|
+
const privateKey = options.privateKey;
|
|
61
|
+
const storage = options.storage;
|
|
62
|
+
const storagePath = options.storagePath;
|
|
63
|
+
|
|
64
|
+
// Validate required options
|
|
65
|
+
if (!rpcUrl) {
|
|
66
|
+
console.error('Error: --rpc-url option or RPC_URL environment variable is required');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!gameContract) {
|
|
71
|
+
console.error(
|
|
72
|
+
'Error: --game-contract option or GAME_CONTRACT environment variable is required',
|
|
73
|
+
);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Warn if private key is not provided for write operations
|
|
78
|
+
if (!privateKey) {
|
|
79
|
+
console.warn(
|
|
80
|
+
'Warning: PRIVATE_KEY environment variable is required for sending transactions',
|
|
81
|
+
);
|
|
82
|
+
} else if (!privateKey.startsWith('0x')) {
|
|
83
|
+
console.error('Error: PRIVATE_KEY must start with 0x');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const chain = await getChain(rpcUrl);
|
|
88
|
+
const transport = new StdioServerTransport();
|
|
89
|
+
const server = createServer(
|
|
90
|
+
{
|
|
91
|
+
chain,
|
|
92
|
+
privateKey: privateKey as `0x${string}`,
|
|
93
|
+
gameContract: gameContract as `0x${string}`,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
ethereum,
|
|
97
|
+
storageConfig: {
|
|
98
|
+
type: storage as 'json' | 'sqlite',
|
|
99
|
+
dataDir: storagePath,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
await server.connect(transport);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Register all tool commands dynamically
|
|
107
|
+
registerAllToolCommands(program, tools);
|
|
108
|
+
|
|
109
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {SpaceInfo} from 'conquest-eth-v0-contracts';
|
|
2
|
+
import type {ClientsWithOptionalWallet, ContractConfig, GameContract} from '../types.js';
|
|
3
|
+
|
|
4
|
+
export async function createSpaceInfo(
|
|
5
|
+
clients: ClientsWithOptionalWallet,
|
|
6
|
+
gameContract: GameContract,
|
|
7
|
+
): Promise<{spaceInfo: SpaceInfo; contractConfig: ContractConfig}> {
|
|
8
|
+
// Fetch config from contract
|
|
9
|
+
const config = await clients.publicClient.readContract({
|
|
10
|
+
...gameContract,
|
|
11
|
+
functionName: 'getConfig',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const contractConfig: ContractConfig = {
|
|
15
|
+
genesis: BigInt(config.genesis),
|
|
16
|
+
resolveWindow: BigInt(config.resolveWindow),
|
|
17
|
+
timePerDistance: BigInt(config.timePerDistance),
|
|
18
|
+
exitDuration: BigInt(config.exitDuration),
|
|
19
|
+
acquireNumSpaceships: Number(config.acquireNumSpaceships),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Create SpaceInfo instance with config
|
|
23
|
+
const spaceInfo = new SpaceInfo({
|
|
24
|
+
genesis: config.genesis as `0x${string}`,
|
|
25
|
+
resolveWindow: Number(config.resolveWindow),
|
|
26
|
+
timePerDistance: Number(config.timePerDistance),
|
|
27
|
+
exitDuration: Number(config.exitDuration),
|
|
28
|
+
acquireNumSpaceships: Number(config.acquireNumSpaceships),
|
|
29
|
+
productionSpeedUp: Number(config.productionSpeedUp),
|
|
30
|
+
productionCapAsDuration: Number(config.productionCapAsDuration),
|
|
31
|
+
upkeepProductionDecreaseRatePer10000th: Number(config.upkeepProductionDecreaseRatePer10000th),
|
|
32
|
+
fleetSizeFactor6: Number(config.fleetSizeFactor6),
|
|
33
|
+
giftTaxPer10000: Number(config.giftTaxPer10000),
|
|
34
|
+
stakeRange: config.stakeRange,
|
|
35
|
+
stakeMultiplier10000th: Number(config.stakeMultiplier10000th),
|
|
36
|
+
bootstrapSessionEndTime: Number(config.bootstrapSessionEndTime),
|
|
37
|
+
infinityStartTime: Number(config.infinityStartTime),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {spaceInfo, contractConfig};
|
|
41
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type {Address} from 'viem';
|
|
2
|
+
import type {SpaceInfo} from 'conquest-eth-v0-contracts';
|
|
3
|
+
import type {FleetStorage} from '../storage/interface.js';
|
|
4
|
+
import type {
|
|
5
|
+
Clients,
|
|
6
|
+
ClientsWithOptionalWallet,
|
|
7
|
+
ContractConfig,
|
|
8
|
+
GameContract,
|
|
9
|
+
PendingFleet,
|
|
10
|
+
} from '../types.js';
|
|
11
|
+
import {getResolvableFleets, resolveFleetWithSpaceInfo} from './resolve.js';
|
|
12
|
+
import {sendFleet} from './send.js';
|
|
13
|
+
/**
|
|
14
|
+
* FleetManager manages the lifecycle of fleets in the Conquest game
|
|
15
|
+
* including sending new fleets and resolving existing ones
|
|
16
|
+
*/
|
|
17
|
+
export class FleetManager {
|
|
18
|
+
constructor(
|
|
19
|
+
private readonly clients: ClientsWithOptionalWallet,
|
|
20
|
+
private readonly gameContract: GameContract,
|
|
21
|
+
private readonly spaceInfo: SpaceInfo,
|
|
22
|
+
private readonly contractConfig: ContractConfig,
|
|
23
|
+
private readonly storage: FleetStorage,
|
|
24
|
+
// private readonly contractAddress: Address,
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Ensure walletClient is available for operations that require it
|
|
29
|
+
*/
|
|
30
|
+
private requireWalletClient(): Clients {
|
|
31
|
+
if (!this.clients.walletClient) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
'Wallet client is required for this operation. Please provide a PRIVATE_KEY environment variable.',
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return this.clients as Clients;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Send a fleet to a destination planet
|
|
41
|
+
*/
|
|
42
|
+
async send(
|
|
43
|
+
fromPlanetId: bigint,
|
|
44
|
+
toPlanetId: bigint,
|
|
45
|
+
quantity: number,
|
|
46
|
+
options?: {
|
|
47
|
+
gift?: boolean;
|
|
48
|
+
specific?: Address;
|
|
49
|
+
arrivalTimeWanted?: bigint;
|
|
50
|
+
secret?: `0x${string}`;
|
|
51
|
+
},
|
|
52
|
+
): Promise<PendingFleet> {
|
|
53
|
+
return sendFleet(
|
|
54
|
+
this.requireWalletClient(),
|
|
55
|
+
this.gameContract,
|
|
56
|
+
fromPlanetId,
|
|
57
|
+
toPlanetId,
|
|
58
|
+
quantity,
|
|
59
|
+
this.spaceInfo,
|
|
60
|
+
this.contractConfig,
|
|
61
|
+
this.storage,
|
|
62
|
+
options,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolve (reveal) a fleet to complete its journey
|
|
68
|
+
*/
|
|
69
|
+
async resolve(
|
|
70
|
+
fleetId: string,
|
|
71
|
+
): Promise<{resolved: true; fleet: PendingFleet} | {resolved: false; reason: string}> {
|
|
72
|
+
return resolveFleetWithSpaceInfo(
|
|
73
|
+
this.requireWalletClient(),
|
|
74
|
+
this.gameContract,
|
|
75
|
+
this.spaceInfo,
|
|
76
|
+
fleetId,
|
|
77
|
+
this.storage,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get fleets that can be resolved (not yet resolved and past resolve window)
|
|
83
|
+
*/
|
|
84
|
+
async getResolvableFleets(): Promise<PendingFleet[]> {
|
|
85
|
+
return getResolvableFleets(this.storage, this.contractConfig.resolveWindow);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get all pending fleets for the current sender
|
|
90
|
+
*/
|
|
91
|
+
async getMyPendingFleets(): Promise<PendingFleet[]> {
|
|
92
|
+
const sender = this.requireWalletClient().walletClient.account!.address;
|
|
93
|
+
return this.storage.getPendingFleetsBySender(sender);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get a specific fleet by ID
|
|
98
|
+
*/
|
|
99
|
+
async getFleet(fleetId: string): Promise<PendingFleet | null> {
|
|
100
|
+
return this.storage.getFleet(fleetId);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get all fleets in storage
|
|
105
|
+
*/
|
|
106
|
+
async getAllFleets(): Promise<PendingFleet[]> {
|
|
107
|
+
return this.storage.getAllFleets();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Resolve all fleets that are ready (batch operation)
|
|
112
|
+
*/
|
|
113
|
+
async resolveAllReady(): Promise<{
|
|
114
|
+
successful: PendingFleet[];
|
|
115
|
+
failed: Array<{fleetId: string; reason: string}>;
|
|
116
|
+
}> {
|
|
117
|
+
const readyFleets = await this.getResolvableFleets();
|
|
118
|
+
const successful: PendingFleet[] = [];
|
|
119
|
+
const failed: Array<{fleetId: string; reason: string}> = [];
|
|
120
|
+
|
|
121
|
+
for (const fleet of readyFleets) {
|
|
122
|
+
const result = await this.resolve(fleet.fleetId);
|
|
123
|
+
if (result.resolved) {
|
|
124
|
+
successful.push(result.fleet);
|
|
125
|
+
} else {
|
|
126
|
+
failed.push({fleetId: fleet.fleetId, reason: result.reason});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {successful, failed};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Clean up old resolved fleets from storage
|
|
135
|
+
*/
|
|
136
|
+
async cleanupOldResolvedFleets(olderThanDays: number = 7): Promise<void> {
|
|
137
|
+
const olderThan = Math.floor(Date.now() / 1000) - olderThanDays * 24 * 60 * 60;
|
|
138
|
+
await this.storage.cleanupOldResolvedFleets(olderThan);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type {Address, WalletClient} from 'viem';
|
|
2
|
+
import type {SpaceInfo} from 'conquest-eth-v0-contracts';
|
|
3
|
+
import type {FleetStorage} from '../storage/interface.js';
|
|
4
|
+
import {Clients, FleetResolution, GameContract, PendingFleet} from '../types.js';
|
|
5
|
+
import {getCurrentTimestamp} from '../util/time.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolve (reveal) a fleet to complete its journey and trigger combat
|
|
9
|
+
*
|
|
10
|
+
* @param walletClient - Viem wallet client for signing transactions
|
|
11
|
+
* @param fleetsRevealContract - The fleets reveal contract instance with address, abi, and clients
|
|
12
|
+
* @param fleetId - The fleet ID to resolve
|
|
13
|
+
* @param storage - Storage instance for tracking pending fleets
|
|
14
|
+
* @returns The resolution result with either resolved fleet or reason for failure
|
|
15
|
+
*/
|
|
16
|
+
export async function resolveFleet(
|
|
17
|
+
walletClient: WalletClient,
|
|
18
|
+
fleetsRevealContract: {
|
|
19
|
+
address: Address;
|
|
20
|
+
abi: readonly unknown[];
|
|
21
|
+
publicClient: unknown;
|
|
22
|
+
walletClient: WalletClient | undefined;
|
|
23
|
+
},
|
|
24
|
+
fleetId: string,
|
|
25
|
+
storage: FleetStorage,
|
|
26
|
+
): Promise<{resolved: true; fleet: PendingFleet} | {resolved: false; reason: string}> {
|
|
27
|
+
// Get the pending fleet from storage
|
|
28
|
+
const pendingFleet = await storage.getFleet(fleetId);
|
|
29
|
+
|
|
30
|
+
if (!pendingFleet) {
|
|
31
|
+
return {resolved: false, reason: `Fleet ${fleetId} not found in storage`};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (pendingFleet.resolved) {
|
|
35
|
+
return {resolved: false, reason: `Fleet ${fleetId} has already been resolved`};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The operator must be the same as the wallet client account
|
|
39
|
+
const operator = walletClient.account!.address;
|
|
40
|
+
if (pendingFleet.operator !== operator) {
|
|
41
|
+
return {resolved: false, reason: `Only the operator can resolve this fleet`};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Build the FleetResolution struct
|
|
45
|
+
const resolution: FleetResolution = {
|
|
46
|
+
from: pendingFleet.fromPlanetId,
|
|
47
|
+
to: pendingFleet.toPlanetId,
|
|
48
|
+
distance: 0n, // Will be calculated by SpaceInfo
|
|
49
|
+
arrivalTimeWanted: pendingFleet.arrivalTimeWanted,
|
|
50
|
+
gift: pendingFleet.gift,
|
|
51
|
+
specific: pendingFleet.specific,
|
|
52
|
+
secret: pendingFleet.secret,
|
|
53
|
+
fleetSender: pendingFleet.fleetSender,
|
|
54
|
+
operator: pendingFleet.operator,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Get the contract resolveFleet function signature
|
|
58
|
+
const publicClient = fleetsRevealContract.publicClient as any;
|
|
59
|
+
const request = await publicClient.simulateContract({
|
|
60
|
+
address: fleetsRevealContract.address,
|
|
61
|
+
abi: fleetsRevealContract.abi,
|
|
62
|
+
functionName: 'resolveFleet',
|
|
63
|
+
args: [BigInt('0x' + fleetId), resolution],
|
|
64
|
+
account: operator,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Send the transaction
|
|
68
|
+
const hash = await walletClient.writeContract(request);
|
|
69
|
+
|
|
70
|
+
// Mark fleet as resolved in storage
|
|
71
|
+
const resolvedAt = getCurrentTimestamp();
|
|
72
|
+
await storage.markResolved(fleetId, resolvedAt);
|
|
73
|
+
|
|
74
|
+
// Update the fleet record
|
|
75
|
+
const resolvedFleet: PendingFleet = {
|
|
76
|
+
...pendingFleet,
|
|
77
|
+
resolved: true,
|
|
78
|
+
resolvedAt,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return {resolved: true, fleet: resolvedFleet};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve a fleet with custom SpaceInfo for distance calculation
|
|
86
|
+
*
|
|
87
|
+
* @param clients - Viem clients (publicClient and walletClient)
|
|
88
|
+
* @param gameContract - The game contract instance with address and ABI
|
|
89
|
+
* @param spaceInfo - SpaceInfo instance for distance calculation
|
|
90
|
+
* @param fleetId - The fleet ID to resolve
|
|
91
|
+
* @param storage - Storage instance for tracking pending fleets
|
|
92
|
+
* @returns The resolution result with either resolved fleet or reason for failure
|
|
93
|
+
*/
|
|
94
|
+
export async function resolveFleetWithSpaceInfo(
|
|
95
|
+
clients: Clients,
|
|
96
|
+
gameContract: GameContract,
|
|
97
|
+
spaceInfo: SpaceInfo,
|
|
98
|
+
fleetId: string,
|
|
99
|
+
storage: FleetStorage,
|
|
100
|
+
): Promise<{resolved: true; fleet: PendingFleet} | {resolved: false; reason: string}> {
|
|
101
|
+
// Get the pending fleet from storage
|
|
102
|
+
const pendingFleet = await storage.getFleet(fleetId);
|
|
103
|
+
|
|
104
|
+
if (!pendingFleet) {
|
|
105
|
+
return {resolved: false, reason: `Fleet ${fleetId} not found in storage`};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (pendingFleet.resolved) {
|
|
109
|
+
return {resolved: false, reason: `Fleet ${fleetId} has already been resolved`};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// The operator must be the same as the wallet client account
|
|
113
|
+
const operator = clients.walletClient.account!.address;
|
|
114
|
+
if (pendingFleet.operator !== operator) {
|
|
115
|
+
return {resolved: false, reason: `Only the operator can resolve this fleet`};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Get planet info for distance calculation
|
|
119
|
+
const fromPlanet = spaceInfo.getPlanetInfoViaId(pendingFleet.fromPlanetId);
|
|
120
|
+
const toPlanet = spaceInfo.getPlanetInfoViaId(pendingFleet.toPlanetId);
|
|
121
|
+
|
|
122
|
+
if (!fromPlanet || !toPlanet) {
|
|
123
|
+
return {resolved: false, reason: 'Could not get planet info for one or both planets'};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Calculate distance using SpaceInfo
|
|
127
|
+
const distance = spaceInfo.distance(fromPlanet, toPlanet);
|
|
128
|
+
|
|
129
|
+
// Build the FleetResolution struct
|
|
130
|
+
const resolution: FleetResolution = {
|
|
131
|
+
from: pendingFleet.fromPlanetId,
|
|
132
|
+
to: pendingFleet.toPlanetId,
|
|
133
|
+
distance: BigInt(distance),
|
|
134
|
+
arrivalTimeWanted: pendingFleet.arrivalTimeWanted,
|
|
135
|
+
gift: pendingFleet.gift,
|
|
136
|
+
specific: pendingFleet.specific,
|
|
137
|
+
secret: pendingFleet.secret,
|
|
138
|
+
fleetSender: pendingFleet.fleetSender,
|
|
139
|
+
operator: pendingFleet.operator,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Get the contract resolveFleet function signature
|
|
143
|
+
const simulation = await clients.publicClient.simulateContract({
|
|
144
|
+
address: gameContract.address,
|
|
145
|
+
abi: gameContract.abi,
|
|
146
|
+
functionName: 'resolveFleet',
|
|
147
|
+
args: [BigInt(fleetId), resolution],
|
|
148
|
+
account: operator,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Send the transaction
|
|
152
|
+
const hash = await clients.walletClient.writeContract(simulation.request);
|
|
153
|
+
|
|
154
|
+
// Mark fleet as resolved in storage
|
|
155
|
+
const resolvedAt = getCurrentTimestamp();
|
|
156
|
+
await storage.markResolved(fleetId, resolvedAt);
|
|
157
|
+
|
|
158
|
+
// Update the fleet record
|
|
159
|
+
const resolvedFleet: PendingFleet = {
|
|
160
|
+
...pendingFleet,
|
|
161
|
+
resolved: true,
|
|
162
|
+
resolvedAt,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return {resolved: true, fleet: resolvedFleet};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get fleets that can be resolved (not yet resolved and past resolve window)
|
|
170
|
+
*
|
|
171
|
+
* @param storage - Storage instance for tracking pending fleets
|
|
172
|
+
* @param resolveWindow - The resolve window duration in seconds from contract config
|
|
173
|
+
* @returns List of fleets that can be resolved
|
|
174
|
+
*/
|
|
175
|
+
export async function getResolvableFleets(
|
|
176
|
+
storage: FleetStorage,
|
|
177
|
+
resolveWindow: bigint,
|
|
178
|
+
): Promise<PendingFleet[]> {
|
|
179
|
+
const currentTime = getCurrentTimestamp();
|
|
180
|
+
const fleets = await storage.getResolvableFleets();
|
|
181
|
+
|
|
182
|
+
return fleets.filter((fleet) => {
|
|
183
|
+
// Check if fleet is past the resolve window
|
|
184
|
+
const resolveWindowOpen = fleet.estimatedArrivalTime + Number(resolveWindow);
|
|
185
|
+
return !fleet.resolved && currentTime >= resolveWindowOpen;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {zeroAddress, type Address} from 'viem';
|
|
2
|
+
import type {SpaceInfo} from 'conquest-eth-v0-contracts';
|
|
3
|
+
import type {FleetStorage} from '../storage/interface.js';
|
|
4
|
+
import type {Clients, ContractConfig, GameContract, PendingFleet} from '../types.js';
|
|
5
|
+
import {computeFleetId, computeToHash, generateSecret} from '../util/hashing.js';
|
|
6
|
+
import {calculateEstimatedArrivalTime, getCurrentTimestamp} from '../util/time.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Send a fleet to a destination planet
|
|
10
|
+
*
|
|
11
|
+
* @param clients - Viem clients (publicClient and walletClient)
|
|
12
|
+
* @param gameContract - The game contract instance with address and ABI
|
|
13
|
+
* @param fromPlanetId - Source planet location ID
|
|
14
|
+
* @param toPlanetId - Destination planet location ID
|
|
15
|
+
* @param quantity - Number of spaceships to send
|
|
16
|
+
* @param spaceInfo - SpaceInfo instance for distance calculation
|
|
17
|
+
* @param contractConfig - Contract config for time calculations
|
|
18
|
+
* @param storage - Storage instance for tracking pending fleets
|
|
19
|
+
* @param options - Optional parameters
|
|
20
|
+
* @param options.gift - Whether the fleet is a gift (no combat)
|
|
21
|
+
* @param options.specific - Specific target address (advanced feature)
|
|
22
|
+
* @param options.arrivalTimeWanted - Preferred arrival time (advanced feature)
|
|
23
|
+
* @param options.secret - Random secret for hash commitment (auto-generated if not provided)
|
|
24
|
+
* @returns The pending fleet information
|
|
25
|
+
*/
|
|
26
|
+
export async function sendFleet(
|
|
27
|
+
clients: Clients,
|
|
28
|
+
gameContract: GameContract,
|
|
29
|
+
fromPlanetId: bigint,
|
|
30
|
+
toPlanetId: bigint,
|
|
31
|
+
quantity: number,
|
|
32
|
+
spaceInfo: SpaceInfo,
|
|
33
|
+
contractConfig: ContractConfig,
|
|
34
|
+
storage: FleetStorage,
|
|
35
|
+
options?: {
|
|
36
|
+
gift?: boolean;
|
|
37
|
+
specific?: Address;
|
|
38
|
+
arrivalTimeWanted?: bigint;
|
|
39
|
+
secret?: `0x${string}`;
|
|
40
|
+
},
|
|
41
|
+
): Promise<PendingFleet> {
|
|
42
|
+
const fleetSender = clients.walletClient.account!.address;
|
|
43
|
+
const operator = fleetSender; // Default to same address
|
|
44
|
+
|
|
45
|
+
// Get planet info for distance calculation
|
|
46
|
+
const fromPlanet = spaceInfo.getPlanetInfoViaId(fromPlanetId);
|
|
47
|
+
const toPlanet = spaceInfo.getPlanetInfoViaId(toPlanetId);
|
|
48
|
+
|
|
49
|
+
if (!fromPlanet || !toPlanet) {
|
|
50
|
+
throw new Error('Could not get planet info for one or both planets');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Calculate distance
|
|
54
|
+
const distance = spaceInfo.distance(fromPlanet, toPlanet);
|
|
55
|
+
|
|
56
|
+
// Generate secret if not provided
|
|
57
|
+
const secret = options?.secret || generateSecret();
|
|
58
|
+
|
|
59
|
+
// Calculate estimated arrival time using contract config
|
|
60
|
+
const estimatedArrivalTime = calculateEstimatedArrivalTime({
|
|
61
|
+
startTime: BigInt(getCurrentTimestamp()),
|
|
62
|
+
distance: BigInt(distance),
|
|
63
|
+
timePerDistance: contractConfig.timePerDistance,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const parameters = {
|
|
67
|
+
gift: options?.gift ?? false,
|
|
68
|
+
specific: options?.specific ?? zeroAddress,
|
|
69
|
+
arrivalTimeWanted: options?.arrivalTimeWanted || 0n,
|
|
70
|
+
};
|
|
71
|
+
const toHash = computeToHash(toPlanetId, secret, parameters);
|
|
72
|
+
|
|
73
|
+
// Get the contract send function signature
|
|
74
|
+
const simulation = await clients.publicClient.simulateContract({
|
|
75
|
+
address: gameContract.address,
|
|
76
|
+
abi: gameContract.abi,
|
|
77
|
+
functionName: 'send',
|
|
78
|
+
args: [fromPlanetId, quantity, toHash],
|
|
79
|
+
account: fleetSender,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Compute fleet ID
|
|
83
|
+
const fleetId = computeFleetId(toHash, fromPlanetId, fleetSender, operator);
|
|
84
|
+
|
|
85
|
+
// Create pending fleet record
|
|
86
|
+
const pendingFleet: PendingFleet = {
|
|
87
|
+
fleetId,
|
|
88
|
+
fromPlanetId,
|
|
89
|
+
toPlanetId,
|
|
90
|
+
quantity,
|
|
91
|
+
secret,
|
|
92
|
+
gift: parameters.gift,
|
|
93
|
+
specific: parameters.specific,
|
|
94
|
+
arrivalTimeWanted: parameters.arrivalTimeWanted,
|
|
95
|
+
fleetSender,
|
|
96
|
+
operator,
|
|
97
|
+
committedAt: getCurrentTimestamp(),
|
|
98
|
+
estimatedArrivalTime,
|
|
99
|
+
resolved: false,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// we always save to storage first in case an error happen while the tx is still broadcasted
|
|
103
|
+
await storage.saveFleet(pendingFleet);
|
|
104
|
+
|
|
105
|
+
// Send the transaction
|
|
106
|
+
const hash = await clients.walletClient.writeContract(simulation.request);
|
|
107
|
+
|
|
108
|
+
pendingFleet.hash = hash;
|
|
109
|
+
await storage.saveFleet(pendingFleet);
|
|
110
|
+
|
|
111
|
+
return pendingFleet;
|
|
112
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type {Tool, ToolEnvironment} from '../types.js';
|
|
2
|
+
import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import type {FleetManager} from '../fleet/manager.js';
|
|
4
|
+
import type {PlanetManager} from '../planet/manager.js';
|
|
5
|
+
import {convertToCallToolResult} from '../types.js';
|
|
6
|
+
|
|
7
|
+
// Helper function to handle BigInt serialization in JSON.stringify
|
|
8
|
+
export function stringifyWithBigInt(obj: any, space?: number): string {
|
|
9
|
+
return JSON.stringify(
|
|
10
|
+
obj,
|
|
11
|
+
(_key, value) => (typeof value === 'bigint' ? value.toString() : value),
|
|
12
|
+
space,
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Create tool environment with sendStatus
|
|
17
|
+
export function createToolEnvironment(
|
|
18
|
+
server: McpServer,
|
|
19
|
+
fleetManager: FleetManager,
|
|
20
|
+
planetManager: PlanetManager,
|
|
21
|
+
): ToolEnvironment {
|
|
22
|
+
return {
|
|
23
|
+
sendStatus: async (_message: string) => {
|
|
24
|
+
// TODO: Implement progress notifications when sessionId is available
|
|
25
|
+
// For now, this is a no-op since we don't have sessionId in the current architecture
|
|
26
|
+
},
|
|
27
|
+
fleetManager,
|
|
28
|
+
planetManager,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Register tool with MCP server
|
|
33
|
+
export function registerTool({
|
|
34
|
+
server,
|
|
35
|
+
name,
|
|
36
|
+
tool,
|
|
37
|
+
fleetManager,
|
|
38
|
+
planetManager,
|
|
39
|
+
}: {
|
|
40
|
+
server: McpServer;
|
|
41
|
+
name: string;
|
|
42
|
+
tool: Tool<any>;
|
|
43
|
+
fleetManager: FleetManager;
|
|
44
|
+
planetManager: PlanetManager;
|
|
45
|
+
}): void {
|
|
46
|
+
server.registerTool(
|
|
47
|
+
name,
|
|
48
|
+
{
|
|
49
|
+
description: tool.description,
|
|
50
|
+
inputSchema: tool.schema as any,
|
|
51
|
+
},
|
|
52
|
+
async (params: unknown) => {
|
|
53
|
+
const env = createToolEnvironment(server, fleetManager, planetManager);
|
|
54
|
+
|
|
55
|
+
const result = await tool.execute(env, params as any);
|
|
56
|
+
return convertToCallToolResult(result);
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
}
|