-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagent-spend.ts
More file actions
147 lines (123 loc) · 5.42 KB
/
Copy pathagent-spend.ts
File metadata and controls
147 lines (123 loc) · 5.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import {
createPublicClient,
createWalletClient,
http,
parseAbi,
parseEther,
formatEther,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
const AGENT_TREASURY = "0x783e1512bFEa7C8B51A92cB150FEb5A04b91E9Aa";
const treasuryAbi = parseAbi([
"function spend(address to, uint256 wstETHAmount) external",
"function getAvailableYield() external view returns (uint256)",
"function principalWstETH() external view returns (uint256)",
"function totalSpentWstETH() external view returns (uint256)",
"function parentAgent() external view returns (address)",
"function owner() external view returns (address)",
]);
const RPC_URL = process.env.RPC_URL!;
const AGENT_PRIVATE_KEY = process.env.AGENT_PRIVATE_KEY!;
const SERVICE_RECIPIENT = process.env.SERVICE_RECIPIENT!;
const SPEND_AMOUNT = parseEther(process.argv[2] ?? "0.0000000001");
const BLUE = "\x1b[34m";
const GREEN = "\x1b[32m";
const YELLOW = "\x1b[33m";
const RED = "\x1b[31m";
const CYAN = "\x1b[36m";
const DIM = "\x1b[2m";
const BOLD = "\x1b[1m";
const RESET = "\x1b[0m";
function line(label: string, value: string, color = RESET) {
console.log(` ${DIM}${label.padEnd(24)}${RESET} ${color}${value}${RESET}`);
}
function divider(title: string) {
console.log(`\n${BLUE}${"─".repeat(20)} ${title} ${"─".repeat(20)}${RESET}`);
}
async function main() {
if (!AGENT_PRIVATE_KEY) {
console.log(`${RED}AGENT_PRIVATE_KEY not set in .env.local${RESET}`);
process.exit(1);
}
if (!SERVICE_RECIPIENT) {
console.log(`${RED}SERVICE_RECIPIENT not set in .env.local${RESET}`);
process.exit(1);
}
const account = privateKeyToAccount(AGENT_PRIVATE_KEY as `0x${string}`);
const client = createPublicClient({
chain: mainnet,
transport: http(RPC_URL),
});
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(RPC_URL),
});
divider("PRE-SPEND CHECK (onchain)");
const [availableYield, principal, totalSpent, parentAgent, owner, ethBalance] =
await Promise.all([
client.readContract({ address: AGENT_TREASURY as `0x${string}`, abi: treasuryAbi, functionName: "getAvailableYield" }),
client.readContract({ address: AGENT_TREASURY as `0x${string}`, abi: treasuryAbi, functionName: "principalWstETH" }),
client.readContract({ address: AGENT_TREASURY as `0x${string}`, abi: treasuryAbi, functionName: "totalSpentWstETH" }),
client.readContract({ address: AGENT_TREASURY as `0x${string}`, abi: treasuryAbi, functionName: "parentAgent" }),
client.readContract({ address: AGENT_TREASURY as `0x${string}`, abi: treasuryAbi, functionName: "owner" }),
client.getBalance({ address: account.address }),
]);
line("Contract", AGENT_TREASURY, BLUE);
line("Owner", owner as string, DIM);
line("Parent Agent", parentAgent as string, DIM);
line("Agent Wallet", account.address, DIM);
line("Agent ETH (gas)", formatEther(ethBalance), ethBalance > BigInt(0) ? GREEN : RED);
line("Principal (wstETH)", formatEther(principal), BOLD);
line("Available Yield", formatEther(availableYield), GREEN);
line("Total Spent (before)", formatEther(totalSpent));
line("Spend Amount", `${formatEther(SPEND_AMOUNT)} wstETH`, YELLOW);
line("Recipient", SERVICE_RECIPIENT, DIM);
if (ethBalance < parseEther("0.001")) {
console.log(`\n ${RED}[ERROR] Agent wallet needs ETH for gas${RESET}`);
console.log(` ${DIM}Send at least 0.005 ETH to ${account.address}${RESET}\n`);
process.exit(1);
}
if (SPEND_AMOUNT > availableYield) {
console.log(`\n ${RED}[ERROR] Insufficient yield${RESET}`);
console.log(` ${DIM}Available: ${formatEther(availableYield)} wstETH${RESET}`);
console.log(` ${DIM}Requested: ${formatEther(SPEND_AMOUNT)} wstETH${RESET}\n`);
process.exit(1);
}
divider("EXECUTING spend()");
console.log(` ${DIM}Submitting transaction...${RESET}`);
const txHash = await walletClient.writeContract({
address: AGENT_TREASURY as `0x${string}`,
abi: treasuryAbi,
functionName: "spend",
args: [SERVICE_RECIPIENT as `0x${string}`, SPEND_AMOUNT],
});
line("Tx Hash", txHash, CYAN);
console.log(` ${DIM}Waiting for confirmation...${RESET}`);
const receipt = await client.waitForTransactionReceipt({
hash: txHash,
confirmations: 1,
});
divider("SPEND CONFIRMED");
line("Status", receipt.status === "success" ? "SUCCESS" : "REVERTED", receipt.status === "success" ? GREEN : RED);
line("Block", `${receipt.blockNumber}`, DIM);
line("Gas Used", `${receipt.gasUsed}`, DIM);
line("Tx Hash", txHash, CYAN);
line("Etherscan", `https://etherscan.io/tx/${txHash}`, CYAN);
const [newYield, newTotalSpent] = await Promise.all([
client.readContract({ address: AGENT_TREASURY as `0x${string}`, abi: treasuryAbi, functionName: "getAvailableYield" }),
client.readContract({ address: AGENT_TREASURY as `0x${string}`, abi: treasuryAbi, functionName: "totalSpentWstETH" }),
]);
divider("POST-SPEND STATE (onchain)");
line("Amount Spent", `${formatEther(SPEND_AMOUNT)} wstETH`, YELLOW);
line("Total Spent (after)", formatEther(newTotalSpent), YELLOW);
line("Remaining Yield", formatEther(newYield), GREEN);
line("Principal (wstETH)", formatEther(principal), BOLD);
line("Principal Changed?", "NO — structurally locked", RED);
console.log();
}
main().catch((err) => {
console.log(`\n ${RED}[ERROR] ${err instanceof Error ? err.message.split("\n")[0] : err}${RESET}\n`);
process.exit(1);
});