@persistenceone/bridgekitty 0.3.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 +232 -0
- package/dist/backends/across.d.ts +10 -0
- package/dist/backends/across.js +285 -0
- package/dist/backends/debridge.d.ts +11 -0
- package/dist/backends/debridge.js +380 -0
- package/dist/backends/lifi.d.ts +19 -0
- package/dist/backends/lifi.js +295 -0
- package/dist/backends/persistence.d.ts +86 -0
- package/dist/backends/persistence.js +642 -0
- package/dist/backends/relay.d.ts +11 -0
- package/dist/backends/relay.js +292 -0
- package/dist/backends/squid.d.ts +31 -0
- package/dist/backends/squid.js +476 -0
- package/dist/backends/types.d.ts +125 -0
- package/dist/backends/types.js +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +154 -0
- package/dist/routing/engine.d.ts +49 -0
- package/dist/routing/engine.js +336 -0
- package/dist/tools/check-status.d.ts +3 -0
- package/dist/tools/check-status.js +93 -0
- package/dist/tools/execute-bridge.d.ts +3 -0
- package/dist/tools/execute-bridge.js +428 -0
- package/dist/tools/get-chains.d.ts +3 -0
- package/dist/tools/get-chains.js +162 -0
- package/dist/tools/get-quote.d.ts +3 -0
- package/dist/tools/get-quote.js +534 -0
- package/dist/tools/get-tokens.d.ts +3 -0
- package/dist/tools/get-tokens.js +128 -0
- package/dist/tools/help.d.ts +2 -0
- package/dist/tools/help.js +204 -0
- package/dist/tools/multi-quote.d.ts +3 -0
- package/dist/tools/multi-quote.js +310 -0
- package/dist/tools/onboard.d.ts +3 -0
- package/dist/tools/onboard.js +218 -0
- package/dist/tools/wallet.d.ts +14 -0
- package/dist/tools/wallet.js +744 -0
- package/dist/tools/xprt-farm.d.ts +3 -0
- package/dist/tools/xprt-farm.js +1308 -0
- package/dist/tools/xprt-rewards.d.ts +2 -0
- package/dist/tools/xprt-rewards.js +177 -0
- package/dist/tools/xprt-staking.d.ts +2 -0
- package/dist/tools/xprt-staking.js +565 -0
- package/dist/utils/chains.d.ts +22 -0
- package/dist/utils/chains.js +154 -0
- package/dist/utils/circuit-breaker.d.ts +64 -0
- package/dist/utils/circuit-breaker.js +160 -0
- package/dist/utils/evm.d.ts +18 -0
- package/dist/utils/evm.js +46 -0
- package/dist/utils/fill-detector.d.ts +70 -0
- package/dist/utils/fill-detector.js +298 -0
- package/dist/utils/gas-estimator.d.ts +67 -0
- package/dist/utils/gas-estimator.js +340 -0
- package/dist/utils/sanitize-error.d.ts +23 -0
- package/dist/utils/sanitize-error.js +101 -0
- package/dist/utils/token-registry.d.ts +70 -0
- package/dist/utils/token-registry.js +669 -0
- package/dist/utils/tokens.d.ts +17 -0
- package/dist/utils/tokens.js +37 -0
- package/dist/utils/tx-simulator.d.ts +27 -0
- package/dist/utils/tx-simulator.js +105 -0
- package/package.json +75 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { getKey, getConfigDir } from "./wallet.js";
|
|
2
|
+
import { ethers } from "ethers";
|
|
3
|
+
import { getProvider } from "../utils/gas-estimator.js";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
const REWARDS_API = "https://rewards.interop.persistence.one";
|
|
6
|
+
const TIMEOUT_MS = 15_000;
|
|
7
|
+
const CBTCB_BASE = "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf"; // cbBTC on Base (8 decimals)
|
|
8
|
+
const BTCB_BSC = "0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c"; // BTCB on BSC (18 decimals)
|
|
9
|
+
const ERC20_BALANCE_ABI = [
|
|
10
|
+
"function balanceOf(address) view returns (uint256)",
|
|
11
|
+
];
|
|
12
|
+
async function fetchJson(url) {
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
const text = await res.text().catch(() => "");
|
|
19
|
+
throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`);
|
|
20
|
+
}
|
|
21
|
+
return res.json();
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function registerOnboardTool(server, engine) {
|
|
28
|
+
server.tool("xprt_onboard", "Personalized onboarding for XPRT farming. Detects current wallet state and returns an ordered " +
|
|
29
|
+
"action plan to go from your current position to actively farming XPRT rewards.", {}, async () => {
|
|
30
|
+
// Step 0: Check if wallet is configured
|
|
31
|
+
const privateKey = getKey("privateKey");
|
|
32
|
+
if (!privateKey) {
|
|
33
|
+
const envPath = path.resolve(getConfigDir(), ".env");
|
|
34
|
+
return {
|
|
35
|
+
content: [{
|
|
36
|
+
type: "text",
|
|
37
|
+
text: JSON.stringify({
|
|
38
|
+
status: "wallet_needed",
|
|
39
|
+
steps: [{
|
|
40
|
+
step: 1,
|
|
41
|
+
action: "Set up a wallet",
|
|
42
|
+
tool: "wallet_setup",
|
|
43
|
+
suggestedParams: {},
|
|
44
|
+
explanation: "You need a wallet before you can start farming. wallet_setup will generate keys for EVM, Cosmos, and Solana chains and save them securely.",
|
|
45
|
+
}],
|
|
46
|
+
nextAction: "Run wallet_setup to create your wallet, then run xprt_onboard again.",
|
|
47
|
+
configPath: envPath,
|
|
48
|
+
}, null, 2),
|
|
49
|
+
}],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const wallet = new ethers.Wallet(privateKey);
|
|
53
|
+
const evmAddress = wallet.address;
|
|
54
|
+
const steps = [];
|
|
55
|
+
let stepNum = 1;
|
|
56
|
+
// Gather wallet state in parallel
|
|
57
|
+
const balanceChecks = await Promise.allSettled([
|
|
58
|
+
// ETH on Base (for gas)
|
|
59
|
+
(async () => {
|
|
60
|
+
const provider = await getProvider(8453);
|
|
61
|
+
const bal = await provider.getBalance(evmAddress);
|
|
62
|
+
return { key: "ethBase", value: parseFloat(ethers.formatEther(bal)) };
|
|
63
|
+
})(),
|
|
64
|
+
// cbBTC on Base
|
|
65
|
+
(async () => {
|
|
66
|
+
const provider = await getProvider(8453);
|
|
67
|
+
const contract = new ethers.Contract(CBTCB_BASE, ERC20_BALANCE_ABI, provider);
|
|
68
|
+
const bal = await contract.balanceOf(evmAddress);
|
|
69
|
+
return { key: "cbbtcBase", value: parseFloat(ethers.formatUnits(bal, 8)) };
|
|
70
|
+
})(),
|
|
71
|
+
// BTCB on BSC
|
|
72
|
+
(async () => {
|
|
73
|
+
const provider = await getProvider(56);
|
|
74
|
+
const contract = new ethers.Contract(BTCB_BSC, ERC20_BALANCE_ABI, provider);
|
|
75
|
+
const bal = await contract.balanceOf(evmAddress);
|
|
76
|
+
return { key: "btcbBsc", value: parseFloat(ethers.formatUnits(bal, 18)) };
|
|
77
|
+
})(),
|
|
78
|
+
// BNB on BSC (for gas)
|
|
79
|
+
(async () => {
|
|
80
|
+
const provider = await getProvider(56);
|
|
81
|
+
const bal = await provider.getBalance(evmAddress);
|
|
82
|
+
return { key: "bnbBsc", value: parseFloat(ethers.formatEther(bal)) };
|
|
83
|
+
})(),
|
|
84
|
+
]);
|
|
85
|
+
const balances = {
|
|
86
|
+
ethBase: 0,
|
|
87
|
+
cbbtcBase: 0,
|
|
88
|
+
btcbBsc: 0,
|
|
89
|
+
bnbBsc: 0,
|
|
90
|
+
};
|
|
91
|
+
for (const result of balanceChecks) {
|
|
92
|
+
if (result.status === "fulfilled") {
|
|
93
|
+
balances[result.value.key] = result.value.value;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Check Persistence address link status
|
|
97
|
+
let isLinked = false;
|
|
98
|
+
let persistenceAddress;
|
|
99
|
+
try {
|
|
100
|
+
const linkData = await fetchJson(`${REWARDS_API}/address-verification/check/${evmAddress}`);
|
|
101
|
+
isLinked = linkData.isRegistered ?? false;
|
|
102
|
+
persistenceAddress = linkData.persistenceAddress;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Non-fatal -- we'll suggest linking anyway
|
|
106
|
+
}
|
|
107
|
+
// Build personalized steps based on state
|
|
108
|
+
// Step: Need ETH on Base for gas
|
|
109
|
+
const needsBaseGas = balances.ethBase < 0.003;
|
|
110
|
+
const hasCbbtc = balances.cbbtcBase >= 0.00005;
|
|
111
|
+
const hasBtcb = balances.btcbBsc >= 0.00005;
|
|
112
|
+
const needsBscGas = balances.bnbBsc < 0.001;
|
|
113
|
+
const hasBtcAnywhere = hasCbbtc || hasBtcb;
|
|
114
|
+
if (needsBaseGas && !hasBtcAnywhere) {
|
|
115
|
+
// User has nothing -- need to fund wallet first
|
|
116
|
+
steps.push({
|
|
117
|
+
step: stepNum++,
|
|
118
|
+
action: "Fund your wallet with ETH on Base",
|
|
119
|
+
tool: "wallet_balance",
|
|
120
|
+
suggestedParams: { chains: ["base"] },
|
|
121
|
+
explanation: `Send at least 0.005 ETH to ${evmAddress} on Base (chain ID 8453). This covers gas fees and will be partially converted to cbBTC for farming. Current balance: ${balances.ethBase.toFixed(6)} ETH.`,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (!needsBaseGas && !hasBtcAnywhere) {
|
|
125
|
+
// Has ETH but no BTC -- needs to prepare
|
|
126
|
+
steps.push({
|
|
127
|
+
step: stepNum++,
|
|
128
|
+
action: "Convert ETH to cbBTC and bridge gas to BSC",
|
|
129
|
+
tool: "xprt_farm_prepare",
|
|
130
|
+
suggestedParams: {},
|
|
131
|
+
explanation: `You have ${balances.ethBase.toFixed(6)} ETH on Base. xprt_farm_prepare will swap some to cbBTC for farming and bridge a small amount to BSC for gas.`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (hasBtcAnywhere && needsBscGas) {
|
|
135
|
+
// Has BTC but no BSC gas
|
|
136
|
+
steps.push({
|
|
137
|
+
step: stepNum++,
|
|
138
|
+
action: "Bridge some ETH to BSC for gas",
|
|
139
|
+
tool: "bridge_get_quote",
|
|
140
|
+
suggestedParams: {
|
|
141
|
+
fromChain: "base",
|
|
142
|
+
toChain: "bsc",
|
|
143
|
+
fromToken: "ETH",
|
|
144
|
+
toToken: "BNB",
|
|
145
|
+
amount: "0.0002",
|
|
146
|
+
fromAddress: evmAddress,
|
|
147
|
+
},
|
|
148
|
+
explanation: `You need BNB on BSC for gas fees during farming round-trips. Current BNB balance: ${balances.bnbBsc.toFixed(6)}. Bridge a small amount of ETH to BNB.`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (hasBtcAnywhere && !needsBscGas) {
|
|
152
|
+
// Ready to farm
|
|
153
|
+
steps.push({
|
|
154
|
+
step: stepNum++,
|
|
155
|
+
action: "Start XPRT farming",
|
|
156
|
+
tool: "xprt_farm_start",
|
|
157
|
+
suggestedParams: {
|
|
158
|
+
rounds: 5,
|
|
159
|
+
startFrom: "auto",
|
|
160
|
+
},
|
|
161
|
+
explanation: `You have ${hasCbbtc ? balances.cbbtcBase.toFixed(8) + " cbBTC on Base" : ""}${hasCbbtc && hasBtcb ? " and " : ""}${hasBtcb ? balances.btcbBsc.toFixed(8) + " BTCB on BSC" : ""}. Ready to start round-trip farming to earn XPRT rewards.`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (!isLinked) {
|
|
165
|
+
steps.push({
|
|
166
|
+
step: stepNum++,
|
|
167
|
+
action: "Link your Persistence address for rewards",
|
|
168
|
+
tool: "xprt_farm_status",
|
|
169
|
+
suggestedParams: {},
|
|
170
|
+
explanation: "Link your EVM wallet to a Persistence address so XPRT rewards can be distributed to your Cosmos wallet. Run xprt_farm_status to check link status and follow the instructions.",
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
// Always suggest boost as optional final step
|
|
174
|
+
steps.push({
|
|
175
|
+
step: stepNum++,
|
|
176
|
+
action: "Optional: Stake XPRT for multiplier boost",
|
|
177
|
+
tool: "xprt_farm_boost",
|
|
178
|
+
suggestedParams: {},
|
|
179
|
+
explanation: "Stake XPRT on Persistence chain to increase your farming multiplier. Tiers: Explorer (1x, 0 XPRT), Voyager (2x, 10K XPRT), Pioneer (5x, 1M XPRT).",
|
|
180
|
+
});
|
|
181
|
+
// Determine overall readiness
|
|
182
|
+
let readiness;
|
|
183
|
+
if (!needsBaseGas && hasBtcAnywhere && !needsBscGas) {
|
|
184
|
+
readiness = "ready_to_farm";
|
|
185
|
+
}
|
|
186
|
+
else if (!needsBaseGas && !hasBtcAnywhere) {
|
|
187
|
+
readiness = "needs_btc_conversion";
|
|
188
|
+
}
|
|
189
|
+
else if (hasBtcAnywhere && needsBscGas) {
|
|
190
|
+
readiness = "needs_bsc_gas";
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
readiness = "needs_funding";
|
|
194
|
+
}
|
|
195
|
+
const response = {
|
|
196
|
+
status: readiness,
|
|
197
|
+
wallet: evmAddress,
|
|
198
|
+
currentBalances: {
|
|
199
|
+
"ETH (Base)": `${balances.ethBase.toFixed(6)} ETH`,
|
|
200
|
+
"cbBTC (Base)": `${balances.cbbtcBase.toFixed(8)} BTC`,
|
|
201
|
+
"BTCB (BSC)": `${balances.btcbBsc.toFixed(8)} BTC`,
|
|
202
|
+
"BNB (BSC)": `${balances.bnbBsc.toFixed(6)} BNB`,
|
|
203
|
+
},
|
|
204
|
+
persistenceLinked: isLinked,
|
|
205
|
+
steps,
|
|
206
|
+
estimatedYield: "XPRT rewards are distributed daily based on your bridging volume and multiplier tier. Estimated, not guaranteed.",
|
|
207
|
+
};
|
|
208
|
+
if (persistenceAddress) {
|
|
209
|
+
response.persistenceAddress = persistenceAddress;
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
content: [{
|
|
213
|
+
type: "text",
|
|
214
|
+
text: JSON.stringify(response, null, 2),
|
|
215
|
+
}],
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
/** Read a key: keyStore first, then process.env fallback (clearing env after read) */
|
|
3
|
+
export declare function getKey(name: "privateKey" | "mnemonic" | "solanaKey"): string | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Returns the BridgeKitty config directory. Resolution order (highest priority first):
|
|
6
|
+
* 1. BRIDGEKITTY_CONFIG env var → use as explicit path
|
|
7
|
+
* 2. ./.bridgekitty/ (local directory) → preferred for sandboxed agents
|
|
8
|
+
* 3. BRIDGEKITTY_HOME env var (if set)
|
|
9
|
+
* 4. ~/.bridgekitty/ (home directory) → traditional default
|
|
10
|
+
*
|
|
11
|
+
* Creates the directory if it doesn't exist (mode 0o700).
|
|
12
|
+
*/
|
|
13
|
+
export declare function getConfigDir(): string;
|
|
14
|
+
export declare function registerWalletTools(server: McpServer): void;
|