sovereignclaw 0.1.0 → 0.1.1
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/bin/sovereignclaw.js +5 -2
- package/dist/bridge.js +336 -0
- package/dist/bungee.js +556 -0
- package/dist/wallet.js +155 -0
- package/package.json +12 -6
- package/scripts/bridge.ts +0 -431
- package/scripts/bungee.ts +0 -557
- package/scripts/wallet.ts +0 -195
package/bin/sovereignclaw.js
CHANGED
|
@@ -109,8 +109,11 @@ Then run: sovereignclaw portfolio
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
function runScript(script, ...args) {
|
|
112
|
-
|
|
113
|
-
const
|
|
112
|
+
// Use bundled JS from dist/ instead of TS
|
|
113
|
+
const jsScript = script.replace('.ts', '.js');
|
|
114
|
+
const scriptPath = join(__dirname, '..', 'dist', jsScript);
|
|
115
|
+
|
|
116
|
+
const child = spawn('node', [scriptPath, ...args, ...process.argv.slice(3)], {
|
|
114
117
|
stdio: 'inherit',
|
|
115
118
|
env: {
|
|
116
119
|
...process.env,
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// scripts/bridge.ts
|
|
2
|
+
import { createWalletClient, createPublicClient, http, encodeFunctionData, parseUnits, formatUnits } from "viem";
|
|
3
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
4
|
+
import { sepolia, baseSepolia, arbitrumSepolia, optimismSepolia } from "viem/chains";
|
|
5
|
+
var TESTNET_CHAINS = {
|
|
6
|
+
"ethereum-sepolia": {
|
|
7
|
+
name: "Ethereum Sepolia",
|
|
8
|
+
domain: 0,
|
|
9
|
+
chainId: 11155111,
|
|
10
|
+
chain: sepolia,
|
|
11
|
+
usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
|
|
12
|
+
rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com"
|
|
13
|
+
},
|
|
14
|
+
"base-sepolia": {
|
|
15
|
+
name: "Base Sepolia",
|
|
16
|
+
domain: 6,
|
|
17
|
+
chainId: 84532,
|
|
18
|
+
chain: baseSepolia,
|
|
19
|
+
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
20
|
+
rpcUrl: "https://base-sepolia-rpc.publicnode.com"
|
|
21
|
+
},
|
|
22
|
+
"arbitrum-sepolia": {
|
|
23
|
+
name: "Arbitrum Sepolia",
|
|
24
|
+
domain: 3,
|
|
25
|
+
chainId: 421614,
|
|
26
|
+
chain: arbitrumSepolia,
|
|
27
|
+
usdc: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d"
|
|
28
|
+
},
|
|
29
|
+
"op-sepolia": {
|
|
30
|
+
name: "OP Sepolia",
|
|
31
|
+
domain: 2,
|
|
32
|
+
chainId: 11155420,
|
|
33
|
+
chain: optimismSepolia,
|
|
34
|
+
usdc: "0x5fd84259d66Cd46123540766Be93DFE6D43130D7"
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var CCTP_CONTRACTS = {
|
|
38
|
+
testnet: {
|
|
39
|
+
tokenMessenger: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
|
|
40
|
+
messageTransmitter: "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275",
|
|
41
|
+
attestationApi: "https://iris-api-sandbox.circle.com"
|
|
42
|
+
},
|
|
43
|
+
mainnet: {
|
|
44
|
+
tokenMessenger: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
|
|
45
|
+
messageTransmitter: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
|
|
46
|
+
attestationApi: "https://iris-api.circle.com"
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var ERC20_ABI = [
|
|
50
|
+
{
|
|
51
|
+
type: "function",
|
|
52
|
+
name: "approve",
|
|
53
|
+
stateMutability: "nonpayable",
|
|
54
|
+
inputs: [
|
|
55
|
+
{ name: "spender", type: "address" },
|
|
56
|
+
{ name: "amount", type: "uint256" }
|
|
57
|
+
],
|
|
58
|
+
outputs: [{ name: "", type: "bool" }]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: "function",
|
|
62
|
+
name: "balanceOf",
|
|
63
|
+
stateMutability: "view",
|
|
64
|
+
inputs: [{ name: "account", type: "address" }],
|
|
65
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: "function",
|
|
69
|
+
name: "allowance",
|
|
70
|
+
stateMutability: "view",
|
|
71
|
+
inputs: [
|
|
72
|
+
{ name: "owner", type: "address" },
|
|
73
|
+
{ name: "spender", type: "address" }
|
|
74
|
+
],
|
|
75
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
76
|
+
}
|
|
77
|
+
];
|
|
78
|
+
var TOKEN_MESSENGER_ABI = [
|
|
79
|
+
{
|
|
80
|
+
type: "function",
|
|
81
|
+
name: "depositForBurn",
|
|
82
|
+
stateMutability: "nonpayable",
|
|
83
|
+
inputs: [
|
|
84
|
+
{ name: "amount", type: "uint256" },
|
|
85
|
+
{ name: "destinationDomain", type: "uint32" },
|
|
86
|
+
{ name: "mintRecipient", type: "bytes32" },
|
|
87
|
+
{ name: "burnToken", type: "address" },
|
|
88
|
+
{ name: "destinationCaller", type: "bytes32" },
|
|
89
|
+
{ name: "maxFee", type: "uint256" },
|
|
90
|
+
{ name: "minFinalityThreshold", type: "uint32" }
|
|
91
|
+
],
|
|
92
|
+
outputs: []
|
|
93
|
+
}
|
|
94
|
+
];
|
|
95
|
+
var MESSAGE_TRANSMITTER_ABI = [
|
|
96
|
+
{
|
|
97
|
+
type: "function",
|
|
98
|
+
name: "receiveMessage",
|
|
99
|
+
stateMutability: "nonpayable",
|
|
100
|
+
inputs: [
|
|
101
|
+
{ name: "message", type: "bytes" },
|
|
102
|
+
{ name: "attestation", type: "bytes" }
|
|
103
|
+
],
|
|
104
|
+
outputs: [{ name: "success", type: "bool" }]
|
|
105
|
+
}
|
|
106
|
+
];
|
|
107
|
+
function addressToBytes32(address) {
|
|
108
|
+
return `0x000000000000000000000000${address.slice(2)}`;
|
|
109
|
+
}
|
|
110
|
+
var ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
111
|
+
async function sleep(ms) {
|
|
112
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
113
|
+
}
|
|
114
|
+
var CCTPBridge = class {
|
|
115
|
+
privateKey;
|
|
116
|
+
isTestnet;
|
|
117
|
+
contracts;
|
|
118
|
+
constructor(privateKey, isTestnet = true) {
|
|
119
|
+
this.privateKey = privateKey;
|
|
120
|
+
this.isTestnet = isTestnet;
|
|
121
|
+
this.contracts = isTestnet ? CCTP_CONTRACTS.testnet : CCTP_CONTRACTS.mainnet;
|
|
122
|
+
}
|
|
123
|
+
getChainConfig(chainName) {
|
|
124
|
+
const normalized = chainName.toLowerCase().replace(/\s+/g, "-");
|
|
125
|
+
const config = TESTNET_CHAINS[normalized];
|
|
126
|
+
if (!config) {
|
|
127
|
+
throw new Error(`Unsupported chain: ${chainName}. Supported: ${Object.keys(TESTNET_CHAINS).join(", ")}`);
|
|
128
|
+
}
|
|
129
|
+
return config;
|
|
130
|
+
}
|
|
131
|
+
createClients(chainConfig) {
|
|
132
|
+
const account = privateKeyToAccount(this.privateKey);
|
|
133
|
+
const transport = chainConfig.rpcUrl ? http(chainConfig.rpcUrl) : http();
|
|
134
|
+
const walletClient = createWalletClient({
|
|
135
|
+
chain: chainConfig.chain,
|
|
136
|
+
transport,
|
|
137
|
+
account
|
|
138
|
+
});
|
|
139
|
+
const publicClient = createPublicClient({
|
|
140
|
+
chain: chainConfig.chain,
|
|
141
|
+
transport
|
|
142
|
+
});
|
|
143
|
+
return { walletClient, publicClient, account };
|
|
144
|
+
}
|
|
145
|
+
async getBalance(chainName) {
|
|
146
|
+
const chainConfig = this.getChainConfig(chainName);
|
|
147
|
+
const { publicClient, account } = this.createClients(chainConfig);
|
|
148
|
+
const balance = await publicClient.readContract({
|
|
149
|
+
address: chainConfig.usdc,
|
|
150
|
+
abi: ERC20_ABI,
|
|
151
|
+
functionName: "balanceOf",
|
|
152
|
+
args: [account.address]
|
|
153
|
+
});
|
|
154
|
+
return formatUnits(balance, 6);
|
|
155
|
+
}
|
|
156
|
+
async quote(sourceChain, destChain, amount) {
|
|
157
|
+
const source = this.getChainConfig(sourceChain);
|
|
158
|
+
const dest = this.getChainConfig(destChain);
|
|
159
|
+
const fee = "0.0005";
|
|
160
|
+
const received = (parseFloat(amount) - parseFloat(fee)).toFixed(4);
|
|
161
|
+
return {
|
|
162
|
+
sourceChain: source.name,
|
|
163
|
+
destChain: dest.name,
|
|
164
|
+
amount,
|
|
165
|
+
estimatedReceived: received,
|
|
166
|
+
fee,
|
|
167
|
+
estimatedTime: "1-2 minutes (Fast Transfer)"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
async bridge(sourceChain, destChain, amount, recipient, onStatus) {
|
|
171
|
+
const log = onStatus || console.log;
|
|
172
|
+
const source = this.getChainConfig(sourceChain);
|
|
173
|
+
const dest = this.getChainConfig(destChain);
|
|
174
|
+
const amountWei = parseUnits(amount, 6);
|
|
175
|
+
const { walletClient: sourceWallet, publicClient: sourcePublic, account } = this.createClients(source);
|
|
176
|
+
const { walletClient: destWallet } = this.createClients(dest);
|
|
177
|
+
const recipientAddress = recipient || account.address;
|
|
178
|
+
const recipientBytes32 = addressToBytes32(recipientAddress);
|
|
179
|
+
log("\u{1F4CA} Checking USDC balance...");
|
|
180
|
+
const balance = await sourcePublic.readContract({
|
|
181
|
+
address: source.usdc,
|
|
182
|
+
abi: ERC20_ABI,
|
|
183
|
+
functionName: "balanceOf",
|
|
184
|
+
args: [account.address]
|
|
185
|
+
});
|
|
186
|
+
if (balance < amountWei) {
|
|
187
|
+
throw new Error(`Insufficient USDC. Have: ${formatUnits(balance, 6)}, Need: ${amount}`);
|
|
188
|
+
}
|
|
189
|
+
log("\u2705 Step 1/4: Approving USDC...");
|
|
190
|
+
const allowance = await sourcePublic.readContract({
|
|
191
|
+
address: source.usdc,
|
|
192
|
+
abi: ERC20_ABI,
|
|
193
|
+
functionName: "allowance",
|
|
194
|
+
args: [account.address, this.contracts.tokenMessenger]
|
|
195
|
+
});
|
|
196
|
+
if (allowance < amountWei) {
|
|
197
|
+
const approveTx = await sourceWallet.sendTransaction({
|
|
198
|
+
to: source.usdc,
|
|
199
|
+
data: encodeFunctionData({
|
|
200
|
+
abi: ERC20_ABI,
|
|
201
|
+
functionName: "approve",
|
|
202
|
+
args: [this.contracts.tokenMessenger, amountWei * 10n]
|
|
203
|
+
// Approve extra for future txs
|
|
204
|
+
})
|
|
205
|
+
});
|
|
206
|
+
log(` Approval TX: ${approveTx}`);
|
|
207
|
+
await sourcePublic.waitForTransactionReceipt({ hash: approveTx });
|
|
208
|
+
} else {
|
|
209
|
+
log(" Already approved.");
|
|
210
|
+
}
|
|
211
|
+
log("\u{1F525} Step 2/4: Burning USDC on source chain...");
|
|
212
|
+
const maxFee = 100000n;
|
|
213
|
+
const minFinalityThreshold = 1e3;
|
|
214
|
+
const burnTx = await sourceWallet.sendTransaction({
|
|
215
|
+
to: this.contracts.tokenMessenger,
|
|
216
|
+
data: encodeFunctionData({
|
|
217
|
+
abi: TOKEN_MESSENGER_ABI,
|
|
218
|
+
functionName: "depositForBurn",
|
|
219
|
+
args: [
|
|
220
|
+
amountWei,
|
|
221
|
+
dest.domain,
|
|
222
|
+
recipientBytes32,
|
|
223
|
+
source.usdc,
|
|
224
|
+
ZERO_BYTES32,
|
|
225
|
+
maxFee,
|
|
226
|
+
minFinalityThreshold
|
|
227
|
+
]
|
|
228
|
+
})
|
|
229
|
+
});
|
|
230
|
+
log(` Burn TX: ${burnTx}`);
|
|
231
|
+
await sourcePublic.waitForTransactionReceipt({ hash: burnTx });
|
|
232
|
+
log("\u23F3 Step 3/4: Waiting for attestation...");
|
|
233
|
+
const attestation = await this.waitForAttestation(source.domain, burnTx, log);
|
|
234
|
+
log(" \u2705 Attestation received!");
|
|
235
|
+
log("\u{1FA99} Step 4/4: Minting USDC on destination chain...");
|
|
236
|
+
const mintTx = await destWallet.sendTransaction({
|
|
237
|
+
to: this.contracts.messageTransmitter,
|
|
238
|
+
data: encodeFunctionData({
|
|
239
|
+
abi: MESSAGE_TRANSMITTER_ABI,
|
|
240
|
+
functionName: "receiveMessage",
|
|
241
|
+
args: [attestation.message, attestation.attestation]
|
|
242
|
+
})
|
|
243
|
+
});
|
|
244
|
+
log(` Mint TX: ${mintTx}`);
|
|
245
|
+
const destPublic = createPublicClient({
|
|
246
|
+
chain: dest.chain,
|
|
247
|
+
transport: http()
|
|
248
|
+
});
|
|
249
|
+
await destPublic.waitForTransactionReceipt({ hash: mintTx });
|
|
250
|
+
log("\u2705 Bridge complete!");
|
|
251
|
+
return {
|
|
252
|
+
burnTxHash: burnTx,
|
|
253
|
+
mintTxHash: mintTx,
|
|
254
|
+
amountReceived: formatUnits(amountWei - maxFee, 6)
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
async waitForAttestation(sourceDomain, txHash, log) {
|
|
258
|
+
const url = `${this.contracts.attestationApi}/v2/messages/${sourceDomain}?transactionHash=${txHash}`;
|
|
259
|
+
let attempts = 0;
|
|
260
|
+
const maxAttempts = 60;
|
|
261
|
+
while (attempts < maxAttempts) {
|
|
262
|
+
try {
|
|
263
|
+
const response = await fetch(url);
|
|
264
|
+
if (response.ok) {
|
|
265
|
+
const data = await response.json();
|
|
266
|
+
if (data.messages?.[0]?.status === "complete") {
|
|
267
|
+
return {
|
|
268
|
+
message: data.messages[0].message,
|
|
269
|
+
attestation: data.messages[0].attestation
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
attempts++;
|
|
274
|
+
if (attempts % 6 === 0) {
|
|
275
|
+
log(` Still waiting... (${attempts * 5}s)`);
|
|
276
|
+
}
|
|
277
|
+
await sleep(5e3);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
attempts++;
|
|
280
|
+
await sleep(5e3);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
throw new Error(`Attestation timeout after ${maxAttempts * 5}s. TX: ${txHash}`);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
async function main() {
|
|
287
|
+
const args = process.argv.slice(2);
|
|
288
|
+
if (args.length < 4) {
|
|
289
|
+
console.log(`
|
|
290
|
+
Usage: npx tsx bridge.ts <action> <source> <dest> <amount> [recipient]
|
|
291
|
+
|
|
292
|
+
Actions:
|
|
293
|
+
quote - Get a quote without executing
|
|
294
|
+
bridge - Execute the bridge
|
|
295
|
+
|
|
296
|
+
Examples:
|
|
297
|
+
npx tsx bridge.ts quote ethereum-sepolia base-sepolia 10
|
|
298
|
+
npx tsx bridge.ts bridge ethereum-sepolia base-sepolia 10
|
|
299
|
+
|
|
300
|
+
Environment:
|
|
301
|
+
PRIVATE_KEY - Your wallet private key
|
|
302
|
+
`);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
const [action, source, dest, amount, recipient] = args;
|
|
306
|
+
const privateKey = process.env.PRIVATE_KEY;
|
|
307
|
+
if (action === "bridge" && !privateKey) {
|
|
308
|
+
console.error("Error: PRIVATE_KEY environment variable required for bridging");
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
const bridge = new CCTPBridge(privateKey || "0x0", true);
|
|
312
|
+
if (action === "quote") {
|
|
313
|
+
const quote = await bridge.quote(source, dest, amount);
|
|
314
|
+
console.log(`
|
|
315
|
+
\u{1F504} CCTP Bridge Quote: ${quote.amount} USDC
|
|
316
|
+
\u{1F4E4} From: ${quote.sourceChain}
|
|
317
|
+
\u{1F4E5} To: ${quote.destChain}
|
|
318
|
+
|
|
319
|
+
\u{1F4B0} You receive: ~${quote.estimatedReceived} USDC
|
|
320
|
+
\u26FD Fee: ~${quote.fee} USDC
|
|
321
|
+
\u23F1\uFE0F Est. time: ${quote.estimatedTime}
|
|
322
|
+
`);
|
|
323
|
+
} else if (action === "bridge") {
|
|
324
|
+
const result = await bridge.bridge(source, dest, amount, recipient);
|
|
325
|
+
console.log(`
|
|
326
|
+
\u2705 Bridge Complete!
|
|
327
|
+
\u{1F525} Burn TX: ${result.burnTxHash}
|
|
328
|
+
\u{1FA99} Mint TX: ${result.mintTxHash}
|
|
329
|
+
\u{1F4B0} Received: ${result.amountReceived} USDC
|
|
330
|
+
`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
main().catch(console.error);
|
|
334
|
+
export {
|
|
335
|
+
CCTPBridge
|
|
336
|
+
};
|