yappr 0.1.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/.env.example +115 -0
- package/config/context/personality.md +7 -0
- package/config/context/security.md +10 -0
- package/config/hooks/example.ts +47 -0
- package/config/hooks/holder.ts +154 -0
- package/config/hooks/user-memory.ts +102 -0
- package/config/skills/compute/handler.ts +6 -0
- package/config/skills/compute/skill.md +7 -0
- package/config/skills/cron/handler.ts +89 -0
- package/config/skills/cron/skill.md +36 -0
- package/config/skills/generate-image/handler.ts +133 -0
- package/config/skills/generate-image/skill.md +20 -0
- package/config/skills/generate-meme-prompt/handler.ts +40 -0
- package/config/skills/generate-meme-prompt/skill.md +23 -0
- package/config/skills/stats/handler.ts +76 -0
- package/config/skills/stats/skill.md +18 -0
- package/config/skills/wallet/handler.ts +56 -0
- package/config/skills/wallet/skill.md +17 -0
- package/config/skills/x/handler.ts +135 -0
- package/config/skills/x/skill.md +163 -0
- package/dist/config/hooks/example.d.ts +2 -0
- package/dist/config/hooks/example.js +37 -0
- package/dist/config/hooks/holder.d.ts +2 -0
- package/dist/config/hooks/holder.js +147 -0
- package/dist/config/hooks/user-memory.d.ts +2 -0
- package/dist/config/hooks/user-memory.js +79 -0
- package/dist/config/skills/compute/handler.d.ts +2 -0
- package/dist/config/skills/compute/handler.js +5 -0
- package/dist/config/skills/cron/handler.d.ts +2 -0
- package/dist/config/skills/cron/handler.js +84 -0
- package/dist/config/skills/generate-image/handler.d.ts +2 -0
- package/dist/config/skills/generate-image/handler.js +122 -0
- package/dist/config/skills/generate-meme/handler.d.ts +2 -0
- package/dist/config/skills/generate-meme/handler.js +121 -0
- package/dist/config/skills/generate-meme-prompt/handler.d.ts +2 -0
- package/dist/config/skills/generate-meme-prompt/handler.js +38 -0
- package/dist/config/skills/stats/handler.d.ts +2 -0
- package/dist/config/skills/stats/handler.js +71 -0
- package/dist/config/skills/wallet/handler.d.ts +2 -0
- package/dist/config/skills/wallet/handler.js +54 -0
- package/dist/config/skills/x/handler.d.ts +2 -0
- package/dist/config/skills/x/handler.js +115 -0
- package/dist/src/agent-prompt.d.ts +1 -0
- package/dist/src/agent-prompt.js +45 -0
- package/dist/src/bankr.d.ts +41 -0
- package/dist/src/bankr.js +76 -0
- package/dist/src/cli/backup.d.ts +7 -0
- package/dist/src/cli/backup.js +78 -0
- package/dist/src/cli/charts.d.ts +32 -0
- package/dist/src/cli/charts.js +222 -0
- package/dist/src/cli/config-sync.d.ts +7 -0
- package/dist/src/cli/config-sync.js +71 -0
- package/dist/src/cli/deploy.d.ts +2 -0
- package/dist/src/cli/deploy.js +1059 -0
- package/dist/src/cli/env.d.ts +4 -0
- package/dist/src/cli/env.js +50 -0
- package/dist/src/cli/host-key.d.ts +4 -0
- package/dist/src/cli/host-key.js +50 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.js +71 -0
- package/dist/src/cli/init.d.ts +1 -0
- package/dist/src/cli/init.js +51 -0
- package/dist/src/cli/ssh.d.ts +2 -0
- package/dist/src/cli/ssh.js +141 -0
- package/dist/src/cli/status.d.ts +7 -0
- package/dist/src/cli/status.js +1184 -0
- package/dist/src/cli/tui.d.ts +18 -0
- package/dist/src/cli/tui.js +115 -0
- package/dist/src/cli/ui.d.ts +30 -0
- package/dist/src/cli/ui.js +164 -0
- package/dist/src/cli/update.d.ts +1 -0
- package/dist/src/cli/update.js +263 -0
- package/dist/src/cli/x-login.d.ts +6 -0
- package/dist/src/cli/x-login.js +70 -0
- package/dist/src/compute.d.ts +11 -0
- package/dist/src/compute.js +109 -0
- package/dist/src/config-loader.d.ts +19 -0
- package/dist/src/config-loader.js +82 -0
- package/dist/src/config.d.ts +29 -0
- package/dist/src/config.js +68 -0
- package/dist/src/cron/capability.d.ts +6 -0
- package/dist/src/cron/capability.js +66 -0
- package/dist/src/cron/runner.d.ts +2 -0
- package/dist/src/cron/runner.js +113 -0
- package/dist/src/cron/schedule.d.ts +19 -0
- package/dist/src/cron/schedule.js +154 -0
- package/dist/src/cron/store.d.ts +46 -0
- package/dist/src/cron/store.js +220 -0
- package/dist/src/db.d.ts +4 -0
- package/dist/src/db.js +53 -0
- package/dist/src/hooks/loader.d.ts +1 -0
- package/dist/src/hooks/loader.js +17 -0
- package/dist/src/hooks/registry.d.ts +17 -0
- package/dist/src/hooks/registry.js +78 -0
- package/dist/src/hooks/types.d.ts +45 -0
- package/dist/src/hooks/types.js +1 -0
- package/dist/src/index.d.ts +25 -0
- package/dist/src/index.js +35 -0
- package/dist/src/llm/index.d.ts +23 -0
- package/dist/src/llm/index.js +213 -0
- package/dist/src/llm/prompts.d.ts +6 -0
- package/dist/src/llm/prompts.js +99 -0
- package/dist/src/log.d.ts +2 -0
- package/dist/src/log.js +30 -0
- package/dist/src/reply/agent.d.ts +20 -0
- package/dist/src/reply/agent.js +215 -0
- package/dist/src/reply/context-blocks.d.ts +12 -0
- package/dist/src/reply/context-blocks.js +22 -0
- package/dist/src/reply/gating.d.ts +3 -0
- package/dist/src/reply/gating.js +35 -0
- package/dist/src/reply/pipeline.d.ts +3 -0
- package/dist/src/reply/pipeline.js +144 -0
- package/dist/src/reply/poller.d.ts +5 -0
- package/dist/src/reply/poller.js +79 -0
- package/dist/src/skills/holder-access.d.ts +7 -0
- package/dist/src/skills/holder-access.js +53 -0
- package/dist/src/skills/loader.d.ts +2 -0
- package/dist/src/skills/loader.js +64 -0
- package/dist/src/skills/registry.d.ts +4 -0
- package/dist/src/skills/registry.js +10 -0
- package/dist/src/skills/types.d.ts +16 -0
- package/dist/src/skills/types.js +1 -0
- package/dist/src/state.d.ts +5 -0
- package/dist/src/state.js +26 -0
- package/dist/src/stats-cli.d.ts +1 -0
- package/dist/src/stats-cli.js +82 -0
- package/dist/src/stats.d.ts +41 -0
- package/dist/src/stats.js +236 -0
- package/dist/src/storage.d.ts +16 -0
- package/dist/src/storage.js +107 -0
- package/dist/src/treasury/abi.d.ts +99 -0
- package/dist/src/treasury/abi.js +71 -0
- package/dist/src/treasury/cycle.d.ts +16 -0
- package/dist/src/treasury/cycle.js +154 -0
- package/dist/src/treasury/index.d.ts +28 -0
- package/dist/src/treasury/index.js +222 -0
- package/dist/src/util.d.ts +3 -0
- package/dist/src/util.js +18 -0
- package/dist/src/wallet.d.ts +5 -0
- package/dist/src/wallet.js +241 -0
- package/dist/src/x/client.d.ts +74 -0
- package/dist/src/x/client.js +323 -0
- package/dist/src/x/types.d.ts +61 -0
- package/dist/src/x/types.js +1 -0
- package/dist/src/x402.d.ts +6 -0
- package/dist/src/x402.js +11 -0
- package/dist/src/yappr.d.ts +1 -0
- package/dist/src/yappr.js +85 -0
- package/package.json +52 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { config } from "../config.js";
|
|
2
|
+
import { runOnBeforeClaim, runOnAfterClaim, runOnSwap, } from "../hooks/registry.js";
|
|
3
|
+
import { WETH } from "./index.js";
|
|
4
|
+
import { formatUnits } from "viem";
|
|
5
|
+
import { recordDevWeth } from "../stats.js";
|
|
6
|
+
// The self-funding loop, run on a timer (TREASURY_INTERVAL_MS). Each cycle:
|
|
7
|
+
// claim fees → pay optional dev cut → burn a share of the token → keep a small ETH
|
|
8
|
+
// gas reserve → swap remaining WETH to USDC → top up compute when it's expiring.
|
|
9
|
+
// All amounts are wei BigInts; treasury.* methods are no-ops under TREASURY_DRY_RUN.
|
|
10
|
+
// 0.0001 ETH in wei — kept unwrapped as a gas reserve so the wallet can transact.
|
|
11
|
+
const ETH_RESERVE = 100000000000000n;
|
|
12
|
+
// Run one full treasury/claim cycle. Exported so the entry point can fire one at
|
|
13
|
+
// startup (every launch) before the recurring scheduler takes over.
|
|
14
|
+
export async function runTreasuryCycle(treasury, log) {
|
|
15
|
+
log.info("treasury cycle start");
|
|
16
|
+
const result = {
|
|
17
|
+
tokenClaimed: 0n,
|
|
18
|
+
wethClaimed: 0n,
|
|
19
|
+
tokenBurned: 0n,
|
|
20
|
+
tokenToDev: 0n,
|
|
21
|
+
wethToDev: 0n,
|
|
22
|
+
wethUnwrapped: 0n,
|
|
23
|
+
wethSwapped: 0n,
|
|
24
|
+
computeExtended: false,
|
|
25
|
+
txHashes: [],
|
|
26
|
+
cycledAt: new Date().toISOString(),
|
|
27
|
+
};
|
|
28
|
+
try {
|
|
29
|
+
log.info("treasury step: reading wallet balances");
|
|
30
|
+
const balances = await treasury.balances();
|
|
31
|
+
log.info({
|
|
32
|
+
token: formatUnits(balances.token, 18),
|
|
33
|
+
weth: formatUnits(balances.weth, 18),
|
|
34
|
+
usdc: formatUnits(balances.usdc, 6),
|
|
35
|
+
eth: formatUnits(balances.eth, 18),
|
|
36
|
+
dryRun: config.treasuryDryRun || undefined,
|
|
37
|
+
}, "treasury balances");
|
|
38
|
+
await runOnBeforeClaim(balances);
|
|
39
|
+
// Only claim when there's actually something to collect — the claim is an
|
|
40
|
+
// on-chain tx that costs gas even when no fees have accrued. On a failed check
|
|
41
|
+
// we fall back to attempting the claim so we never silently stop collecting.
|
|
42
|
+
let token = 0n;
|
|
43
|
+
let weth = 0n;
|
|
44
|
+
let shouldClaim = true;
|
|
45
|
+
log.info("treasury step: checking claimable fees");
|
|
46
|
+
try {
|
|
47
|
+
const claimable = await treasury.claimableFees();
|
|
48
|
+
shouldClaim = claimable.hasClaimable;
|
|
49
|
+
log.info({ token0: claimable.token0, token1: claimable.token1, hasClaimable: claimable.hasClaimable }, "claimable fees");
|
|
50
|
+
if (!shouldClaim) {
|
|
51
|
+
log.info("no unclaimed fees — skipping claim this cycle");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
log.warn({ err }, "claimable-fee check failed — attempting claim anyway");
|
|
56
|
+
}
|
|
57
|
+
if (shouldClaim) {
|
|
58
|
+
log.info("treasury step: claiming fees");
|
|
59
|
+
({ token, weth } = await treasury.claimFees());
|
|
60
|
+
result.tokenClaimed = token;
|
|
61
|
+
result.wethClaimed = weth;
|
|
62
|
+
log.info({ token: formatUnits(token, 18), weth: formatUnits(weth, 18) }, "fees claimed");
|
|
63
|
+
}
|
|
64
|
+
// Dev fee — send a cut of each claimed asset to the dev address. The cut is
|
|
65
|
+
// computed on the total amount claimed this cycle, before burn/swap.
|
|
66
|
+
let wethRemaining = weth;
|
|
67
|
+
if (token === 0n && weth === 0n) {
|
|
68
|
+
log.info("nothing claimed this cycle — skipping dev cut, burn and swap");
|
|
69
|
+
}
|
|
70
|
+
if (config.devAddress && (token > 0n || weth > 0n)) {
|
|
71
|
+
log.info("treasury step: dev cut");
|
|
72
|
+
if (config.devTokenBps > 0 && token > 0n) {
|
|
73
|
+
const devToken = (token * BigInt(config.devTokenBps)) / 10000n;
|
|
74
|
+
if (devToken > 0n) {
|
|
75
|
+
const txHash = await treasury.transferToken(config.tokenAddress, config.devAddress, devToken);
|
|
76
|
+
result.tokenToDev = devToken;
|
|
77
|
+
result.txHashes.push(txHash);
|
|
78
|
+
log.info({ devToken: devToken.toString(), txHash }, "token sent to dev");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (config.devWethBps > 0 && weth > 0n) {
|
|
82
|
+
const devWeth = (weth * BigInt(config.devWethBps)) / 10000n;
|
|
83
|
+
if (devWeth > 0n) {
|
|
84
|
+
const txHash = await treasury.transferToken(WETH, config.devAddress, devWeth);
|
|
85
|
+
result.wethToDev = devWeth;
|
|
86
|
+
result.txHashes.push(txHash);
|
|
87
|
+
wethRemaining = weth - devWeth;
|
|
88
|
+
recordDevWeth(Number(formatUnits(devWeth, 18))); // track cumulative dev revenue (WETH)
|
|
89
|
+
log.info({ devWeth: devWeth.toString(), txHash }, "weth sent to dev");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (token > 0n) {
|
|
94
|
+
const burnAmount = (token * BigInt(config.burnBps)) / 10000n;
|
|
95
|
+
if (burnAmount > 0n) {
|
|
96
|
+
log.info({ burnAmount: formatUnits(burnAmount, 18) }, "treasury step: burning token");
|
|
97
|
+
await runOnSwap("burn", burnAmount);
|
|
98
|
+
const txHash = await treasury.burnToken(burnAmount);
|
|
99
|
+
result.tokenBurned = burnAmount;
|
|
100
|
+
result.txHashes.push(txHash);
|
|
101
|
+
log.info({ burnAmount: burnAmount.toString(), txHash }, "token burned");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (wethRemaining > 0n) {
|
|
105
|
+
let wethToSwap = wethRemaining;
|
|
106
|
+
if (wethRemaining > ETH_RESERVE && balances.eth < ETH_RESERVE) {
|
|
107
|
+
const txHash = await treasury.unwrapWethToEth(ETH_RESERVE);
|
|
108
|
+
result.wethUnwrapped = ETH_RESERVE;
|
|
109
|
+
result.txHashes.push(txHash);
|
|
110
|
+
wethToSwap = wethRemaining - ETH_RESERVE;
|
|
111
|
+
log.info({ amount: ETH_RESERVE.toString(), txHash }, "unwrapped WETH to ETH for gas reserve");
|
|
112
|
+
}
|
|
113
|
+
if (wethToSwap > 0n) {
|
|
114
|
+
log.info({ weth: formatUnits(wethToSwap, 18) }, "treasury step: swapping WETH to USDC");
|
|
115
|
+
await runOnSwap("swap", wethToSwap);
|
|
116
|
+
const txHash = await treasury.swapWethToUsdc(wethToSwap);
|
|
117
|
+
result.wethSwapped = wethToSwap;
|
|
118
|
+
result.txHashes.push(txHash);
|
|
119
|
+
log.info({ weth: wethToSwap.toString(), txHash }, "weth swapped to usdc");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (config.computeInstanceId) {
|
|
123
|
+
log.info("treasury step: checking compute expiry");
|
|
124
|
+
const expiry = await treasury.computeExpiry();
|
|
125
|
+
if (!expiry) {
|
|
126
|
+
// Unknown expiry → do NOT buy. Extending on a failed lookup would silently
|
|
127
|
+
// purchase compute every cycle. Better to skip and surface the problem.
|
|
128
|
+
log.warn("could not determine compute expiry — skipping extension to avoid an unintended purchase");
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const hoursRemaining = (expiry.getTime() - Date.now()) / (1000 * 60 * 60);
|
|
132
|
+
if (hoursRemaining < 24) {
|
|
133
|
+
await treasury.extendCompute();
|
|
134
|
+
result.computeExtended = true;
|
|
135
|
+
log.info({ hoursRemaining: hoursRemaining.toFixed(1) }, "compute extended (+24h)");
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
log.info({ hoursRemaining: hoursRemaining.toFixed(1), expiry }, "compute has sufficient time remaining — skipping extension");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
await runOnAfterClaim(result);
|
|
143
|
+
log.info({ cycledAt: result.cycledAt }, "treasury cycle complete");
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
log.error({ err }, "treasury cycle failed");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Schedule the recurring treasury cycle. The caller runs the initial startup cycle
|
|
150
|
+
// itself (see src/yappr.ts), so this only sets up the timer for subsequent runs.
|
|
151
|
+
export function startTreasury(treasury, log) {
|
|
152
|
+
const timer = setInterval(() => void runTreasuryCycle(treasury, log), config.treasuryIntervalMs);
|
|
153
|
+
return () => clearInterval(timer);
|
|
154
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type TreasuryBalances = {
|
|
2
|
+
token: bigint;
|
|
3
|
+
weth: bigint;
|
|
4
|
+
usdc: bigint;
|
|
5
|
+
eth: bigint;
|
|
6
|
+
};
|
|
7
|
+
export interface Treasury {
|
|
8
|
+
claimableFees(): Promise<{
|
|
9
|
+
token0: string;
|
|
10
|
+
token1: string;
|
|
11
|
+
hasClaimable: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
claimFees(): Promise<{
|
|
14
|
+
token: bigint;
|
|
15
|
+
weth: bigint;
|
|
16
|
+
}>;
|
|
17
|
+
burnToken(amount: bigint): Promise<string>;
|
|
18
|
+
transferToken(token: `0x${string}`, to: `0x${string}`, amount: bigint): Promise<string>;
|
|
19
|
+
unwrapWethToEth(amount: bigint): Promise<string>;
|
|
20
|
+
swapWethToUsdc(amount: bigint): Promise<string>;
|
|
21
|
+
extendCompute(): Promise<void>;
|
|
22
|
+
computeExpiry(): Promise<Date | null>;
|
|
23
|
+
balances(): Promise<TreasuryBalances>;
|
|
24
|
+
lifetimeEarned(): Promise<number>;
|
|
25
|
+
}
|
|
26
|
+
export declare const WETH: `0x${string}`;
|
|
27
|
+
export declare function getTreasury(): Treasury;
|
|
28
|
+
export declare function createBankrTreasury(): Treasury;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { encodeFunctionData, createPublicClient, http } from "viem";
|
|
2
|
+
import { base } from "viem/chains";
|
|
3
|
+
import { submitTx, walletAddress, payFetch, paidUsd } from "../wallet.js";
|
|
4
|
+
import { bankrApi } from "../bankr.js";
|
|
5
|
+
import { fetchComputeInstance, computeInstanceExpiry } from "../compute.js";
|
|
6
|
+
import { config } from "../config.js";
|
|
7
|
+
import { log } from "../log.js";
|
|
8
|
+
import { sleep, envNumber } from "../util.js";
|
|
9
|
+
import { ERC20_ABI, WETH_ABI, UNISWAP_ROUTER_ABI } from "./abi.js";
|
|
10
|
+
const UNISWAP_ROUTER = "0x2626664c2603336E57B271c5C0b26F421741e481";
|
|
11
|
+
export const WETH = "0x4200000000000000000000000000000000000006";
|
|
12
|
+
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
13
|
+
const BURN_ADDRESS = "0x000000000000000000000000000000000000dead";
|
|
14
|
+
const publicClient = createPublicClient({ chain: base, transport: http() });
|
|
15
|
+
function erc20Balance(token, owner) {
|
|
16
|
+
return publicClient.readContract({
|
|
17
|
+
address: token, abi: ERC20_ABI, functionName: "balanceOf", args: [owner],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
// Retry a read-only step with exponential backoff (2s → 4s → 8s → 16s). The public
|
|
21
|
+
// Base RPC rate-limits bursts (seen as "over rate limit" eth_call failures), and a
|
|
22
|
+
// transient read failure must never abort a cycle that has already moved money.
|
|
23
|
+
// Only ever wrap reads — never claim/submit calls.
|
|
24
|
+
const READ_ATTEMPTS = envNumber("TREASURY_READ_ATTEMPTS", 5);
|
|
25
|
+
async function readWithRetry(step, fn) {
|
|
26
|
+
let lastErr;
|
|
27
|
+
for (let attempt = 1; attempt <= READ_ATTEMPTS; attempt++) {
|
|
28
|
+
try {
|
|
29
|
+
return await fn();
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
lastErr = err;
|
|
33
|
+
if (attempt < READ_ATTEMPTS) {
|
|
34
|
+
const delayMs = 2000 * 2 ** (attempt - 1);
|
|
35
|
+
log.warn({ step, attempt, maxAttempts: READ_ATTEMPTS, delayMs, err: err instanceof Error ? err.message : String(err) }, "treasury read failed — retrying after backoff");
|
|
36
|
+
await sleep(delayMs);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
throw lastErr;
|
|
41
|
+
}
|
|
42
|
+
let _treasury = null;
|
|
43
|
+
export function getTreasury() {
|
|
44
|
+
if (!_treasury)
|
|
45
|
+
_treasury = createBankrTreasury();
|
|
46
|
+
return _treasury;
|
|
47
|
+
}
|
|
48
|
+
export function createBankrTreasury() {
|
|
49
|
+
return {
|
|
50
|
+
// Read-only check (no tx, no payment) of unclaimed creator fees for our token,
|
|
51
|
+
// via Bankr's GET /token-launches/:addr/fees. The treasury cycle uses this to
|
|
52
|
+
// skip the claim transaction — and its gas — when there's nothing to collect.
|
|
53
|
+
async claimableFees() {
|
|
54
|
+
const res = await readWithRetry("claimableFees", () => bankrApi(config.bankrApiKey, `/token-launches/${config.tokenAddress}/fees`));
|
|
55
|
+
const token = res.tokens?.find((t) => t.tokenAddress?.toLowerCase() === config.tokenAddress.toLowerCase())
|
|
56
|
+
?? res.tokens?.[0];
|
|
57
|
+
const token0 = token?.claimable?.token0 ?? "0";
|
|
58
|
+
const token1 = token?.claimable?.token1 ?? "0";
|
|
59
|
+
// Mirrors the Bankr CLI's own "has claimable fees" test: strip any "<"/">"
|
|
60
|
+
// rounding prefix and treat a positive amount on either side as claimable.
|
|
61
|
+
const amount = (s) => parseFloat(s.replace(/^[<>]/, "")) || 0;
|
|
62
|
+
return { token0, token1, hasClaimable: amount(token0) > 0 || amount(token1) > 0 };
|
|
63
|
+
},
|
|
64
|
+
async claimFees() {
|
|
65
|
+
if (config.treasuryDryRun) {
|
|
66
|
+
log.info("treasury [dry run] claimFees");
|
|
67
|
+
return { token: 0n, weth: 0n };
|
|
68
|
+
}
|
|
69
|
+
// Measure the claim by diffing wallet balances before/after the on-chain
|
|
70
|
+
// settlement — the claim endpoint returns only a tx hash, not amounts.
|
|
71
|
+
const address = walletAddress();
|
|
72
|
+
log.info("treasury claimFees: reading pre-claim balances");
|
|
73
|
+
const [tokenBefore, wethBefore] = await readWithRetry("claimFees pre-claim balances", () => Promise.all([
|
|
74
|
+
erc20Balance(config.tokenAddress, address),
|
|
75
|
+
erc20Balance(WETH, address),
|
|
76
|
+
]));
|
|
77
|
+
log.info("treasury claimFees submitting");
|
|
78
|
+
const { transactionHash } = await bankrApi(config.bankrApiKey, `/token-launches/${config.tokenAddress}/fees/claim`, { method: "POST", auth: "bearer", body: JSON.stringify({}) });
|
|
79
|
+
log.info({ txHash: transactionHash }, "treasury claimFees: waiting for confirmation");
|
|
80
|
+
await readWithRetry("claimFees receipt", () => publicClient.waitForTransactionReceipt({ hash: transactionHash }));
|
|
81
|
+
// Past this point the claim has settled on-chain. A failure to MEASURE it must
|
|
82
|
+
// not abort the cycle — degrade to zero amounts (this batch just won't be
|
|
83
|
+
// burned/swapped this cycle) and let the rest of the cycle run.
|
|
84
|
+
log.info("treasury claimFees: reading post-claim balances");
|
|
85
|
+
try {
|
|
86
|
+
const [tokenAfter, wethAfter] = await readWithRetry("claimFees post-claim balances", () => Promise.all([
|
|
87
|
+
erc20Balance(config.tokenAddress, address),
|
|
88
|
+
erc20Balance(WETH, address),
|
|
89
|
+
]));
|
|
90
|
+
const token = tokenAfter > tokenBefore ? tokenAfter - tokenBefore : 0n;
|
|
91
|
+
const weth = wethAfter > wethBefore ? wethAfter - wethBefore : 0n;
|
|
92
|
+
log.info({ txHash: transactionHash, token: token.toString(), weth: weth.toString() }, "treasury claimFees ok");
|
|
93
|
+
return { token, weth };
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
log.error({ txHash: transactionHash, err: err instanceof Error ? err.message : String(err) }, "treasury claimFees: claim settled but post-claim balance read failed — treating claimed amounts as 0 (no burn/swap this cycle)");
|
|
97
|
+
return { token: 0n, weth: 0n };
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
async burnToken(amount) {
|
|
101
|
+
if (config.treasuryDryRun) {
|
|
102
|
+
log.info({ amount: amount.toString() }, "treasury [dry run] burnToken");
|
|
103
|
+
return "0xdry";
|
|
104
|
+
}
|
|
105
|
+
log.info({ amount: amount.toString() }, "treasury burnToken submitting");
|
|
106
|
+
const data = encodeFunctionData({ abi: ERC20_ABI, functionName: "transfer", args: [BURN_ADDRESS, amount] });
|
|
107
|
+
const txHash = await submitTx(config.tokenAddress, data);
|
|
108
|
+
log.info({ txHash, amount: amount.toString() }, "treasury burnToken ok");
|
|
109
|
+
return txHash;
|
|
110
|
+
},
|
|
111
|
+
async transferToken(token, to, amount) {
|
|
112
|
+
if (config.treasuryDryRun) {
|
|
113
|
+
log.info({ token, to, amount: amount.toString() }, "treasury [dry run] transferToken");
|
|
114
|
+
return "0xdry";
|
|
115
|
+
}
|
|
116
|
+
log.info({ token, to, amount: amount.toString() }, "treasury transferToken submitting");
|
|
117
|
+
const data = encodeFunctionData({ abi: ERC20_ABI, functionName: "transfer", args: [to, amount] });
|
|
118
|
+
const txHash = await submitTx(token, data);
|
|
119
|
+
log.info({ txHash, token, to, amount: amount.toString() }, "treasury transferToken ok");
|
|
120
|
+
return txHash;
|
|
121
|
+
},
|
|
122
|
+
async unwrapWethToEth(amount) {
|
|
123
|
+
if (config.treasuryDryRun) {
|
|
124
|
+
log.info({ amount: amount.toString() }, "treasury [dry run] unwrapWethToEth");
|
|
125
|
+
return "0xdry";
|
|
126
|
+
}
|
|
127
|
+
log.info({ amount: amount.toString() }, "treasury unwrapWethToEth submitting");
|
|
128
|
+
const data = encodeFunctionData({ abi: WETH_ABI, functionName: "withdraw", args: [amount] });
|
|
129
|
+
const txHash = await submitTx(WETH, data);
|
|
130
|
+
log.info({ txHash, amount: amount.toString() }, "treasury unwrapWethToEth ok");
|
|
131
|
+
return txHash;
|
|
132
|
+
},
|
|
133
|
+
async swapWethToUsdc(amount) {
|
|
134
|
+
if (config.treasuryDryRun) {
|
|
135
|
+
log.info({ amount: amount.toString() }, "treasury [dry run] swapWethToUsdc");
|
|
136
|
+
return "0xdry";
|
|
137
|
+
}
|
|
138
|
+
// The router pulls WETH via transferFrom, so it needs an allowance. Approve
|
|
139
|
+
// per-amount each swap (never an unlimited approval): check the current
|
|
140
|
+
// allowance first and only top it up when it's short.
|
|
141
|
+
const allowance = await readWithRetry("swap allowance", () => publicClient.readContract({
|
|
142
|
+
address: WETH, abi: ERC20_ABI, functionName: "allowance",
|
|
143
|
+
args: [walletAddress(), UNISWAP_ROUTER],
|
|
144
|
+
}));
|
|
145
|
+
if (allowance < amount) {
|
|
146
|
+
log.info({ allowance: allowance.toString(), amount: amount.toString() }, "treasury swapWethToUsdc approving router");
|
|
147
|
+
const approveData = encodeFunctionData({
|
|
148
|
+
abi: ERC20_ABI, functionName: "approve", args: [UNISWAP_ROUTER, amount],
|
|
149
|
+
});
|
|
150
|
+
const approveTx = await submitTx(WETH, approveData);
|
|
151
|
+
log.info({ txHash: approveTx }, "treasury swapWethToUsdc approval ok");
|
|
152
|
+
}
|
|
153
|
+
log.info({ amount: amount.toString() }, "treasury swapWethToUsdc submitting");
|
|
154
|
+
const data = encodeFunctionData({
|
|
155
|
+
abi: UNISWAP_ROUTER_ABI,
|
|
156
|
+
functionName: "exactInputSingle",
|
|
157
|
+
args: [{
|
|
158
|
+
tokenIn: WETH, tokenOut: USDC, fee: 500,
|
|
159
|
+
recipient: walletAddress(),
|
|
160
|
+
amountIn: amount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n,
|
|
161
|
+
}],
|
|
162
|
+
});
|
|
163
|
+
const txHash = await submitTx(UNISWAP_ROUTER, data);
|
|
164
|
+
log.info({ txHash, amount: amount.toString() }, "treasury swapWethToUsdc ok");
|
|
165
|
+
return txHash;
|
|
166
|
+
},
|
|
167
|
+
async extendCompute() {
|
|
168
|
+
if (!config.computeInstanceId)
|
|
169
|
+
return;
|
|
170
|
+
if (config.treasuryDryRun) {
|
|
171
|
+
log.info({ instanceId: config.computeInstanceId }, "treasury [dry run] extendCompute 24h");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
log.info({ instanceId: config.computeInstanceId }, "treasury extendCompute submitting 24h");
|
|
175
|
+
const res = await payFetch(`https://compute.x402layer.cc/compute/instances/${config.computeInstanceId}/extend`, {
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: { "Content-Type": "application/json" },
|
|
178
|
+
body: JSON.stringify({ extend_hours: 24, network: "base" }),
|
|
179
|
+
});
|
|
180
|
+
if (!res.ok) {
|
|
181
|
+
throw new Error(`Compute extend failed: ${res.status} ${await res.text()}`);
|
|
182
|
+
}
|
|
183
|
+
log.info({ usd: paidUsd(res) }, "treasury extendCompute ok (+24h)");
|
|
184
|
+
},
|
|
185
|
+
async computeExpiry() {
|
|
186
|
+
if (!config.computeInstanceId)
|
|
187
|
+
return null;
|
|
188
|
+
// Instance lookup needs wallet-signature auth — the same authenticated path
|
|
189
|
+
// the deploy script uses. A plain fetch here would 401 and return null, which
|
|
190
|
+
// the cycle would misread as "expiring soon" and buy compute every run.
|
|
191
|
+
try {
|
|
192
|
+
const instance = await fetchComputeInstance(config.bankrApiKey, walletAddress(), config.computeInstanceId);
|
|
193
|
+
return computeInstanceExpiry(instance);
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
log.warn({ err }, "compute expiry lookup failed");
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
// All-time gross creator fees earned (WETH), from Bankr's fees endpoint:
|
|
201
|
+
// `lifetimeEarnedWeth` (fees that have left the claimable bucket — claimed +
|
|
202
|
+
// swapped) plus `totals.claimableWeth` (fees pending right now, not yet in
|
|
203
|
+
// `lifetimeEarnedWeth`). Summing the two covers the full lifetime as fees flow
|
|
204
|
+
// pending → claimed. Read-only (no payment); polled on a timer to feed the
|
|
205
|
+
// stats ledger. (WETH side only — the token-side fee isn't valued in WETH here.)
|
|
206
|
+
async lifetimeEarned() {
|
|
207
|
+
const res = await bankrApi(config.bankrApiKey, `/token-launches/${config.tokenAddress}/fees`);
|
|
208
|
+
const num = (s) => parseFloat(s ?? "0") || 0;
|
|
209
|
+
return num(res.lifetimeEarnedWeth) + num(res.totals?.claimableWeth);
|
|
210
|
+
},
|
|
211
|
+
async balances() {
|
|
212
|
+
const address = walletAddress();
|
|
213
|
+
const [token, weth, usdc, eth] = await readWithRetry("balances", () => Promise.all([
|
|
214
|
+
erc20Balance(config.tokenAddress, address),
|
|
215
|
+
erc20Balance(WETH, address),
|
|
216
|
+
erc20Balance(USDC, address),
|
|
217
|
+
publicClient.getBalance({ address }),
|
|
218
|
+
]));
|
|
219
|
+
return { token, weth, usdc, eth };
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
package/dist/src/util.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2
|
+
export function requireEnv(name) {
|
|
3
|
+
const v = process.env[name];
|
|
4
|
+
if (!v)
|
|
5
|
+
throw new Error(`Missing required env var: ${name}`);
|
|
6
|
+
return v;
|
|
7
|
+
}
|
|
8
|
+
// Lenient numeric env var: a missing/empty/malformed value falls back instead of
|
|
9
|
+
// becoming NaN (which setInterval would treat as ~1ms, and charts as garbage).
|
|
10
|
+
// For agent-critical knobs that should *fail* the boot on a malformed value, use
|
|
11
|
+
// `numeric()` in config.ts instead.
|
|
12
|
+
export function envNumber(name, fallback) {
|
|
13
|
+
const raw = process.env[name];
|
|
14
|
+
if (raw == null || raw === "")
|
|
15
|
+
return fallback;
|
|
16
|
+
const n = Number(raw);
|
|
17
|
+
return Number.isFinite(n) ? n : fallback;
|
|
18
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function initBankr(): Promise<`0x${string}`>;
|
|
2
|
+
export declare function walletAddress(): `0x${string}`;
|
|
3
|
+
export declare function payFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
4
|
+
export declare function paidUsd(res: Response): number | undefined;
|
|
5
|
+
export declare function submitTx(to: string, data: string): Promise<string>;
|