arc402-cli 0.2.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/README.md +245 -0
- package/dist/abis.d.ts +19 -0
- package/dist/abis.d.ts.map +1 -0
- package/dist/abis.js +177 -0
- package/dist/abis.js.map +1 -0
- package/dist/bundler.d.ts +65 -0
- package/dist/bundler.d.ts.map +1 -0
- package/dist/bundler.js +181 -0
- package/dist/bundler.js.map +1 -0
- package/dist/client.d.ts +14 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +24 -0
- package/dist/client.js.map +1 -0
- package/dist/coinbase-smart-wallet.d.ts +28 -0
- package/dist/coinbase-smart-wallet.d.ts.map +1 -0
- package/dist/coinbase-smart-wallet.js +38 -0
- package/dist/coinbase-smart-wallet.js.map +1 -0
- package/dist/commands/accept.d.ts +3 -0
- package/dist/commands/accept.d.ts.map +1 -0
- package/dist/commands/accept.js +26 -0
- package/dist/commands/accept.js.map +1 -0
- package/dist/commands/agent-handshake.d.ts +3 -0
- package/dist/commands/agent-handshake.d.ts.map +1 -0
- package/dist/commands/agent-handshake.js +61 -0
- package/dist/commands/agent-handshake.js.map +1 -0
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +417 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/agreements.d.ts +3 -0
- package/dist/commands/agreements.d.ts.map +1 -0
- package/dist/commands/agreements.js +344 -0
- package/dist/commands/agreements.js.map +1 -0
- package/dist/commands/arbitrator.d.ts +3 -0
- package/dist/commands/arbitrator.d.ts.map +1 -0
- package/dist/commands/arbitrator.js +157 -0
- package/dist/commands/arbitrator.js.map +1 -0
- package/dist/commands/arena-handshake.d.ts +3 -0
- package/dist/commands/arena-handshake.d.ts.map +1 -0
- package/dist/commands/arena-handshake.js +187 -0
- package/dist/commands/arena-handshake.js.map +1 -0
- package/dist/commands/cancel.d.ts +3 -0
- package/dist/commands/cancel.d.ts.map +1 -0
- package/dist/commands/cancel.js +30 -0
- package/dist/commands/cancel.js.map +1 -0
- package/dist/commands/channel.d.ts +3 -0
- package/dist/commands/channel.d.ts.map +1 -0
- package/dist/commands/channel.js +238 -0
- package/dist/commands/channel.js.map +1 -0
- package/dist/commands/coldstart.d.ts +3 -0
- package/dist/commands/coldstart.d.ts.map +1 -0
- package/dist/commands/coldstart.js +148 -0
- package/dist/commands/coldstart.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +40 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/contract-interaction.d.ts +3 -0
- package/dist/commands/contract-interaction.d.ts.map +1 -0
- package/dist/commands/contract-interaction.js +165 -0
- package/dist/commands/contract-interaction.js.map +1 -0
- package/dist/commands/daemon.d.ts +3 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +891 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/deliver.d.ts +3 -0
- package/dist/commands/deliver.d.ts.map +1 -0
- package/dist/commands/deliver.js +156 -0
- package/dist/commands/deliver.js.map +1 -0
- package/dist/commands/discover.d.ts +3 -0
- package/dist/commands/discover.d.ts.map +1 -0
- package/dist/commands/discover.js +224 -0
- package/dist/commands/discover.js.map +1 -0
- package/dist/commands/dispute.d.ts +3 -0
- package/dist/commands/dispute.d.ts.map +1 -0
- package/dist/commands/dispute.js +348 -0
- package/dist/commands/dispute.js.map +1 -0
- package/dist/commands/endpoint.d.ts +3 -0
- package/dist/commands/endpoint.d.ts.map +1 -0
- package/dist/commands/endpoint.js +604 -0
- package/dist/commands/endpoint.js.map +1 -0
- package/dist/commands/hire.d.ts +3 -0
- package/dist/commands/hire.d.ts.map +1 -0
- package/dist/commands/hire.js +189 -0
- package/dist/commands/hire.js.map +1 -0
- package/dist/commands/migrate.d.ts +3 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +163 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/negotiate.d.ts +3 -0
- package/dist/commands/negotiate.d.ts.map +1 -0
- package/dist/commands/negotiate.js +247 -0
- package/dist/commands/negotiate.js.map +1 -0
- package/dist/commands/openshell.d.ts +3 -0
- package/dist/commands/openshell.d.ts.map +1 -0
- package/dist/commands/openshell.js +952 -0
- package/dist/commands/openshell.js.map +1 -0
- package/dist/commands/owner.d.ts +3 -0
- package/dist/commands/owner.d.ts.map +1 -0
- package/dist/commands/owner.js +32 -0
- package/dist/commands/owner.js.map +1 -0
- package/dist/commands/policy.d.ts +4 -0
- package/dist/commands/policy.d.ts.map +1 -0
- package/dist/commands/policy.js +248 -0
- package/dist/commands/policy.js.map +1 -0
- package/dist/commands/relay.d.ts +3 -0
- package/dist/commands/relay.d.ts.map +1 -0
- package/dist/commands/relay.js +279 -0
- package/dist/commands/relay.js.map +1 -0
- package/dist/commands/remediate.d.ts +3 -0
- package/dist/commands/remediate.d.ts.map +1 -0
- package/dist/commands/remediate.js +42 -0
- package/dist/commands/remediate.js.map +1 -0
- package/dist/commands/reputation.d.ts +4 -0
- package/dist/commands/reputation.d.ts.map +1 -0
- package/dist/commands/reputation.js +72 -0
- package/dist/commands/reputation.js.map +1 -0
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +332 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/trust.d.ts +3 -0
- package/dist/commands/trust.d.ts.map +1 -0
- package/dist/commands/trust.js +23 -0
- package/dist/commands/trust.js.map +1 -0
- package/dist/commands/verify.d.ts +3 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +88 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/wallet.d.ts +3 -0
- package/dist/commands/wallet.d.ts.map +1 -0
- package/dist/commands/wallet.js +2520 -0
- package/dist/commands/wallet.js.map +1 -0
- package/dist/commands/watchtower.d.ts +3 -0
- package/dist/commands/watchtower.d.ts.map +1 -0
- package/dist/commands/watchtower.js +238 -0
- package/dist/commands/watchtower.js.map +1 -0
- package/dist/commands/workroom.d.ts +3 -0
- package/dist/commands/workroom.d.ts.map +1 -0
- package/dist/commands/workroom.js +855 -0
- package/dist/commands/workroom.js.map +1 -0
- package/dist/config.d.ts +62 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +141 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/config.d.ts +74 -0
- package/dist/daemon/config.d.ts.map +1 -0
- package/dist/daemon/config.js +271 -0
- package/dist/daemon/config.js.map +1 -0
- package/dist/daemon/hire-listener.d.ts +31 -0
- package/dist/daemon/hire-listener.d.ts.map +1 -0
- package/dist/daemon/hire-listener.js +207 -0
- package/dist/daemon/hire-listener.js.map +1 -0
- package/dist/daemon/index.d.ts +29 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +535 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/job-lifecycle.d.ts +62 -0
- package/dist/daemon/job-lifecycle.d.ts.map +1 -0
- package/dist/daemon/job-lifecycle.js +201 -0
- package/dist/daemon/job-lifecycle.js.map +1 -0
- package/dist/daemon/notify.d.ts +22 -0
- package/dist/daemon/notify.d.ts.map +1 -0
- package/dist/daemon/notify.js +148 -0
- package/dist/daemon/notify.js.map +1 -0
- package/dist/daemon/token-metering.d.ts +42 -0
- package/dist/daemon/token-metering.d.ts.map +1 -0
- package/dist/daemon/token-metering.js +178 -0
- package/dist/daemon/token-metering.js.map +1 -0
- package/dist/daemon/userops.d.ts +21 -0
- package/dist/daemon/userops.d.ts.map +1 -0
- package/dist/daemon/userops.js +88 -0
- package/dist/daemon/userops.js.map +1 -0
- package/dist/daemon/wallet-monitor.d.ts +16 -0
- package/dist/daemon/wallet-monitor.d.ts.map +1 -0
- package/dist/daemon/wallet-monitor.js +57 -0
- package/dist/daemon/wallet-monitor.js.map +1 -0
- package/dist/drain-v4.d.ts +2 -0
- package/dist/drain-v4.d.ts.map +1 -0
- package/dist/drain-v4.js +167 -0
- package/dist/drain-v4.js.map +1 -0
- package/dist/endpoint-config.d.ts +36 -0
- package/dist/endpoint-config.d.ts.map +1 -0
- package/dist/endpoint-config.js +96 -0
- package/dist/endpoint-config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/openshell-runtime.d.ts +55 -0
- package/dist/openshell-runtime.d.ts.map +1 -0
- package/dist/openshell-runtime.js +268 -0
- package/dist/openshell-runtime.js.map +1 -0
- package/dist/signing.d.ts +2 -0
- package/dist/signing.d.ts.map +1 -0
- package/dist/signing.js +23 -0
- package/dist/signing.js.map +1 -0
- package/dist/telegram-notify.d.ts +23 -0
- package/dist/telegram-notify.d.ts.map +1 -0
- package/dist/telegram-notify.js +106 -0
- package/dist/telegram-notify.js.map +1 -0
- package/dist/ui/banner.d.ts +7 -0
- package/dist/ui/banner.d.ts.map +1 -0
- package/dist/ui/banner.js +37 -0
- package/dist/ui/banner.js.map +1 -0
- package/dist/ui/colors.d.ts +14 -0
- package/dist/ui/colors.d.ts.map +1 -0
- package/dist/ui/colors.js +29 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/ui/format.d.ts +26 -0
- package/dist/ui/format.d.ts.map +1 -0
- package/dist/ui/format.js +77 -0
- package/dist/ui/format.js.map +1 -0
- package/dist/ui/spinner.d.ts +8 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +43 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/utils/format.d.ts +10 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +61 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/hash.d.ts +3 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +43 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/time.d.ts +3 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +21 -0
- package/dist/utils/time.js.map +1 -0
- package/dist/wallet-router.d.ts +25 -0
- package/dist/wallet-router.d.ts.map +1 -0
- package/dist/wallet-router.js +153 -0
- package/dist/wallet-router.js.map +1 -0
- package/dist/walletconnect-session.d.ts +12 -0
- package/dist/walletconnect-session.d.ts.map +1 -0
- package/dist/walletconnect-session.js +26 -0
- package/dist/walletconnect-session.js.map +1 -0
- package/dist/walletconnect.d.ts +46 -0
- package/dist/walletconnect.d.ts.map +1 -0
- package/dist/walletconnect.js +267 -0
- package/dist/walletconnect.js.map +1 -0
- package/package.json +38 -0
- package/scripts/authorize-machine-key.ts +43 -0
- package/scripts/drain-wallet.ts +149 -0
- package/scripts/execute-spend-only.ts +81 -0
- package/scripts/register-agent-userop.ts +186 -0
- package/src/abis.ts +187 -0
- package/src/bundler.ts +235 -0
- package/src/client.ts +34 -0
- package/src/coinbase-smart-wallet.ts +51 -0
- package/src/commands/accept.ts +25 -0
- package/src/commands/agent-handshake.ts +67 -0
- package/src/commands/agent.ts +458 -0
- package/src/commands/agreements.ts +324 -0
- package/src/commands/arbitrator.ts +129 -0
- package/src/commands/arena-handshake.ts +217 -0
- package/src/commands/cancel.ts +26 -0
- package/src/commands/channel.ts +208 -0
- package/src/commands/coldstart.ts +156 -0
- package/src/commands/config.ts +35 -0
- package/src/commands/contract-interaction.ts +166 -0
- package/src/commands/daemon.ts +971 -0
- package/src/commands/deliver.ts +116 -0
- package/src/commands/discover.ts +295 -0
- package/src/commands/dispute.ts +373 -0
- package/src/commands/endpoint.ts +619 -0
- package/src/commands/hire.ts +200 -0
- package/src/commands/migrate.ts +175 -0
- package/src/commands/negotiate.ts +270 -0
- package/src/commands/openshell.ts +1053 -0
- package/src/commands/owner.ts +30 -0
- package/src/commands/policy.ts +252 -0
- package/src/commands/relay.ts +272 -0
- package/src/commands/remediate.ts +22 -0
- package/src/commands/reputation.ts +71 -0
- package/src/commands/setup.ts +343 -0
- package/src/commands/trust.ts +15 -0
- package/src/commands/verify.ts +88 -0
- package/src/commands/wallet.ts +2892 -0
- package/src/commands/watchtower.ts +232 -0
- package/src/commands/workroom.ts +889 -0
- package/src/config.ts +153 -0
- package/src/daemon/config.ts +308 -0
- package/src/daemon/hire-listener.ts +226 -0
- package/src/daemon/index.ts +609 -0
- package/src/daemon/job-lifecycle.ts +215 -0
- package/src/daemon/notify.ts +157 -0
- package/src/daemon/token-metering.ts +183 -0
- package/src/daemon/userops.ts +119 -0
- package/src/daemon/wallet-monitor.ts +90 -0
- package/src/drain-v4.ts +159 -0
- package/src/endpoint-config.ts +83 -0
- package/src/index.ts +75 -0
- package/src/openshell-runtime.ts +277 -0
- package/src/signing.ts +28 -0
- package/src/telegram-notify.ts +88 -0
- package/src/ui/banner.ts +41 -0
- package/src/ui/colors.ts +30 -0
- package/src/ui/format.ts +77 -0
- package/src/ui/spinner.ts +46 -0
- package/src/utils/format.ts +48 -0
- package/src/utils/hash.ts +5 -0
- package/src/utils/time.ts +15 -0
- package/src/wallet-router.ts +178 -0
- package/src/walletconnect-session.ts +27 -0
- package/src/walletconnect.ts +294 -0
- package/test/time.test.js +11 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* register-agent-userop.ts
|
|
3
|
+
* Register GigaBrain in AgentRegistry via ERC-4337 UserOperation.
|
|
4
|
+
* Machine key signs → Pimlico bundler → EntryPoint executes → wallet is msg.sender.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ethers } from "ethers";
|
|
8
|
+
import { loadConfig } from "../src/config";
|
|
9
|
+
import { BundlerClient, buildUserOp } from "../src/bundler";
|
|
10
|
+
import { ARC402_WALLET_EXECUTE_ABI, AGENT_REGISTRY_ABI } from "../src/abis";
|
|
11
|
+
|
|
12
|
+
const ENTRY_POINT = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
|
|
13
|
+
const CHAIN_ID = 8453;
|
|
14
|
+
|
|
15
|
+
// EntryPoint v0.7 nonce getter ABI
|
|
16
|
+
const EP_ABI = [
|
|
17
|
+
"function getNonce(address sender, uint192 key) external view returns (uint256)",
|
|
18
|
+
"function depositTo(address account) external payable",
|
|
19
|
+
"function balanceOf(address account) external view returns (uint256)",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
async function main() {
|
|
23
|
+
const config = loadConfig();
|
|
24
|
+
if (!config.walletContractAddress) throw new Error("walletContractAddress not in config");
|
|
25
|
+
if (!config.privateKey) throw new Error("privateKey not in config");
|
|
26
|
+
|
|
27
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
28
|
+
const machineKey = new ethers.Wallet(config.privateKey, provider);
|
|
29
|
+
const walletAddress = config.walletContractAddress;
|
|
30
|
+
|
|
31
|
+
const agentRegistryAddress =
|
|
32
|
+
config.agentRegistryV2Address ??
|
|
33
|
+
"0xD5c2851B00090c92Ba7F4723FB548bb30C9B6865";
|
|
34
|
+
|
|
35
|
+
console.log("Wallet: ", walletAddress);
|
|
36
|
+
console.log("Machine key: ", machineKey.address);
|
|
37
|
+
console.log("AgentRegistry: ", agentRegistryAddress);
|
|
38
|
+
|
|
39
|
+
// ── 1. Check ETH balance ───────────────────────────────────────────────────
|
|
40
|
+
const balance = await provider.getBalance(walletAddress);
|
|
41
|
+
console.log("Wallet balance: ", ethers.formatEther(balance), "ETH");
|
|
42
|
+
if (balance < ethers.parseEther("0.0005")) {
|
|
43
|
+
throw new Error("Wallet balance too low — need at least 0.0005 ETH");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── 2. Get nonce from EntryPoint ───────────────────────────────────────────
|
|
47
|
+
const ep = new ethers.Contract(ENTRY_POINT, EP_ABI, provider);
|
|
48
|
+
const nonce: bigint = await ep.getNonce(walletAddress, 0);
|
|
49
|
+
console.log("Nonce: ", nonce.toString());
|
|
50
|
+
|
|
51
|
+
// ── 3. Encode AgentRegistry.register() calldata ───────────────────────────
|
|
52
|
+
const registryIface = new ethers.Interface(AGENT_REGISTRY_ABI);
|
|
53
|
+
const registerCalldata = registryIface.encodeFunctionData("register", [
|
|
54
|
+
"GigaBrain", // name
|
|
55
|
+
[], // capabilities (empty for now)
|
|
56
|
+
"ai.assistant", // serviceType
|
|
57
|
+
"https://gigabrain.arc402.xyz", // endpoint
|
|
58
|
+
"", // metadataURI
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
// ── 4. Encode ARC402Wallet.executeContractCall() — this is the UserOp callData ──
|
|
62
|
+
const walletIface = new ethers.Interface(ARC402_WALLET_EXECUTE_ABI);
|
|
63
|
+
const userOpCallData = walletIface.encodeFunctionData("executeContractCall", [{
|
|
64
|
+
target: agentRegistryAddress,
|
|
65
|
+
data: registerCalldata,
|
|
66
|
+
value: BigInt(0),
|
|
67
|
+
minReturnValue: BigInt(0),
|
|
68
|
+
maxApprovalAmount: BigInt(0),
|
|
69
|
+
approvalToken: ethers.ZeroAddress,
|
|
70
|
+
}]);
|
|
71
|
+
|
|
72
|
+
console.log("UserOp callData built ✓");
|
|
73
|
+
|
|
74
|
+
// ── 5. Build UserOp — fetch live gas price from Pimlico ───────────────────
|
|
75
|
+
// Declare bundlerUrl here so it's available for gas price fetch AND sending
|
|
76
|
+
const bundlerUrl = (config as unknown as Record<string, unknown>)["bundlerUrl"] as string | undefined ?? "https://public.pimlico.io/v2/8453/rpc";
|
|
77
|
+
const gasPriceRes = await fetch(bundlerUrl, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: { "Content-Type": "application/json" },
|
|
80
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "pimlico_getUserOperationGasPrice", params: [] }),
|
|
81
|
+
});
|
|
82
|
+
const gasPriceJson = await gasPriceRes.json() as { result?: { fast?: { maxFeePerGas: string; maxPriorityFeePerGas: string } } };
|
|
83
|
+
const fast = gasPriceJson.result?.fast;
|
|
84
|
+
if (!fast) throw new Error("Could not fetch gas price from bundler");
|
|
85
|
+
console.log("Gas price (fast): maxFee=", fast.maxFeePerGas, "maxPriority=", fast.maxPriorityFeePerGas);
|
|
86
|
+
|
|
87
|
+
const userOp = await buildUserOp(userOpCallData, walletAddress, nonce, config);
|
|
88
|
+
// Override with bundler-recommended gas prices
|
|
89
|
+
userOp.maxFeePerGas = fast.maxFeePerGas;
|
|
90
|
+
userOp.maxPriorityFeePerGas = fast.maxPriorityFeePerGas;
|
|
91
|
+
// Bump callGasLimit — executeContractCall + AgentRegistry.register needs ~330k
|
|
92
|
+
userOp.callGasLimit = ethers.toBeHex(500_000);
|
|
93
|
+
userOp.verificationGasLimit = ethers.toBeHex(200_000);
|
|
94
|
+
|
|
95
|
+
// ── 6. Sign UserOp with machine key ───────────────────────────────────────
|
|
96
|
+
// ERC-4337 v0.7: hash = keccak256(abi.encode(userOpHash, entryPoint, chainId))
|
|
97
|
+
// where userOpHash = keccak256(packed(userOp fields))
|
|
98
|
+
const packedFields = ethers.AbiCoder.defaultAbiCoder().encode(
|
|
99
|
+
["address","uint256","bytes32","bytes32","bytes32","uint256","bytes32","bytes32"],
|
|
100
|
+
[
|
|
101
|
+
userOp.sender,
|
|
102
|
+
BigInt(userOp.nonce),
|
|
103
|
+
ethers.keccak256(ethers.concat(
|
|
104
|
+
userOp.factory ? [userOp.factory, userOp.factoryData ?? "0x"] : []
|
|
105
|
+
)),
|
|
106
|
+
ethers.keccak256(userOp.callData),
|
|
107
|
+
ethers.concat([
|
|
108
|
+
ethers.zeroPadValue(ethers.toBeHex(userOp.callGasLimit), 16),
|
|
109
|
+
ethers.zeroPadValue(ethers.toBeHex(userOp.verificationGasLimit), 16),
|
|
110
|
+
]),
|
|
111
|
+
BigInt(userOp.preVerificationGas),
|
|
112
|
+
ethers.concat([
|
|
113
|
+
ethers.zeroPadValue(ethers.toBeHex(userOp.maxPriorityFeePerGas), 16),
|
|
114
|
+
ethers.zeroPadValue(ethers.toBeHex(userOp.maxFeePerGas), 16),
|
|
115
|
+
]),
|
|
116
|
+
ethers.keccak256(
|
|
117
|
+
userOp.paymaster
|
|
118
|
+
? ethers.concat([
|
|
119
|
+
userOp.paymaster,
|
|
120
|
+
ethers.zeroPadValue(ethers.toBeHex(userOp.paymasterVerificationGasLimit ?? "0x0"), 16),
|
|
121
|
+
ethers.zeroPadValue(ethers.toBeHex(userOp.paymasterPostOpGasLimit ?? "0x0"), 16),
|
|
122
|
+
userOp.paymasterData ?? "0x",
|
|
123
|
+
])
|
|
124
|
+
: "0x"
|
|
125
|
+
),
|
|
126
|
+
]
|
|
127
|
+
);
|
|
128
|
+
const userOpHash = ethers.keccak256(
|
|
129
|
+
ethers.AbiCoder.defaultAbiCoder().encode(
|
|
130
|
+
["bytes32","address","uint256"],
|
|
131
|
+
[
|
|
132
|
+
ethers.keccak256(packedFields),
|
|
133
|
+
ENTRY_POINT,
|
|
134
|
+
CHAIN_ID,
|
|
135
|
+
]
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
console.log("UserOp hash: ", userOpHash);
|
|
139
|
+
|
|
140
|
+
// validateUserOp uses toEthSignedMessageHash — sign the eth-prefixed hash
|
|
141
|
+
const signature = await machineKey.signMessage(ethers.getBytes(userOpHash));
|
|
142
|
+
userOp.signature = signature;
|
|
143
|
+
console.log("Signed ✓");
|
|
144
|
+
|
|
145
|
+
// ── 7. Send to bundler ─────────────────────────────────────────────────────
|
|
146
|
+
const bundler = new BundlerClient(bundlerUrl, ENTRY_POINT, CHAIN_ID);
|
|
147
|
+
|
|
148
|
+
console.log("Sending UserOperation to bundler...");
|
|
149
|
+
let opHash: string;
|
|
150
|
+
try {
|
|
151
|
+
opHash = await bundler.sendUserOperation(userOp);
|
|
152
|
+
} catch (e) {
|
|
153
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
154
|
+
throw new Error(`Bundler rejected UserOp: ${msg}`);
|
|
155
|
+
}
|
|
156
|
+
console.log("UserOp hash: ", opHash);
|
|
157
|
+
console.log("Waiting for confirmation (up to 60s)...");
|
|
158
|
+
|
|
159
|
+
const receipt = await bundler.getUserOperationReceipt(opHash);
|
|
160
|
+
if (!receipt.success) {
|
|
161
|
+
throw new Error(`UserOp failed on-chain. Tx: ${receipt.receipt.transactionHash}`);
|
|
162
|
+
}
|
|
163
|
+
console.log("✓ AgentRegistry.register() confirmed");
|
|
164
|
+
console.log(" Tx:", receipt.receipt.transactionHash);
|
|
165
|
+
|
|
166
|
+
// ── 8. Claim subdomain ─────────────────────────────────────────────────────
|
|
167
|
+
console.log("\nClaiming gigabrain.arc402.xyz...");
|
|
168
|
+
const res = await fetch("https://api.arc402.xyz/register-subdomain", {
|
|
169
|
+
method: "POST",
|
|
170
|
+
headers: { "Content-Type": "application/json" },
|
|
171
|
+
body: JSON.stringify({
|
|
172
|
+
subdomain: "gigabrain",
|
|
173
|
+
walletAddress,
|
|
174
|
+
tunnelTarget: "https://gigabrain.arc402.xyz",
|
|
175
|
+
}),
|
|
176
|
+
});
|
|
177
|
+
const body = await res.json() as Record<string, unknown>;
|
|
178
|
+
if (!res.ok) {
|
|
179
|
+
console.error("✗ Subdomain claim failed:", body["error"] ?? body);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
console.log("✓ Subdomain claimed:", body["subdomain"]);
|
|
183
|
+
console.log("\nAll done. GigaBrain is registered and live.");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
main().catch(e => { console.error(e); process.exit(1); });
|
package/src/abis.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ABIs extracted from the ARC-402 contract sources.
|
|
3
|
+
* Source: /products/arc-402/reference/contracts/
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const AGENT_REGISTRY_ABI = [
|
|
7
|
+
// Registration
|
|
8
|
+
"function register(string name, string[] capabilities, string serviceType, string endpoint, string metadataURI) external",
|
|
9
|
+
"function update(string name, string[] capabilities, string serviceType, string endpoint, string metadataURI) external",
|
|
10
|
+
"function deactivate() external",
|
|
11
|
+
"function reactivate() external",
|
|
12
|
+
// Queries
|
|
13
|
+
"function getAgent(address wallet) external view returns (tuple(address wallet, string name, string[] capabilities, string serviceType, string endpoint, string metadataURI, bool active, uint256 registeredAt))",
|
|
14
|
+
"function isRegistered(address wallet) external view returns (bool)",
|
|
15
|
+
"function isActive(address wallet) external view returns (bool)",
|
|
16
|
+
"function getCapabilities(address wallet) external view returns (string[])",
|
|
17
|
+
"function getTrustScore(address wallet) external view returns (uint256)",
|
|
18
|
+
"function agentCount() external view returns (uint256)",
|
|
19
|
+
"function getAgentAtIndex(uint256 index) external view returns (address)",
|
|
20
|
+
// Events
|
|
21
|
+
"event AgentRegistered(address indexed wallet, string name, string serviceType, uint256 timestamp)",
|
|
22
|
+
"event AgentUpdated(address indexed wallet, string name, string serviceType)",
|
|
23
|
+
"event AgentDeactivated(address indexed wallet)",
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
export const SERVICE_AGREEMENT_ABI = [
|
|
27
|
+
"function propose(address provider, string serviceType, string description, uint256 price, address token, uint256 deadline, bytes32 deliverablesHash) external payable returns (uint256 agreementId)",
|
|
28
|
+
"function accept(uint256 agreementId) external",
|
|
29
|
+
"function fulfill(uint256 agreementId, bytes32 actualDeliverablesHash) external",
|
|
30
|
+
"function commitDeliverable(uint256 agreementId, bytes32 deliverableHash) external",
|
|
31
|
+
"function verifyDeliverable(uint256 agreementId) external",
|
|
32
|
+
"function autoRelease(uint256 agreementId) external",
|
|
33
|
+
"function dispute(uint256 agreementId, string reason) external",
|
|
34
|
+
"function directDispute(uint256 agreementId, uint8 directReason, string reason) external",
|
|
35
|
+
"function escalateToDispute(uint256 agreementId, string reason) external",
|
|
36
|
+
"function requestRevision(uint256 agreementId, bytes32 feedbackHash, string feedbackURI, bytes32 previousTranscriptHash) external",
|
|
37
|
+
"function respondToRevision(uint256 agreementId, uint8 responseType, uint256 proposedProviderPayout, bytes32 responseHash, string responseURI, bytes32 previousTranscriptHash) external",
|
|
38
|
+
"function submitDisputeEvidence(uint256 agreementId, uint8 evidenceType, bytes32 evidenceHash, string evidenceURI) external",
|
|
39
|
+
"function nominateArbitrator(uint256 agreementId, address arbitrator) external",
|
|
40
|
+
"function castArbitrationVote(uint256 agreementId, uint8 vote, uint256 providerAward, uint256 clientAward) external",
|
|
41
|
+
"function requestHumanEscalation(uint256 agreementId, string reason) external",
|
|
42
|
+
"function cancel(uint256 agreementId) external",
|
|
43
|
+
"function expiredCancel(uint256 agreementId) external",
|
|
44
|
+
"function resolveDispute(uint256 agreementId, bool favorProvider) external",
|
|
45
|
+
"function resolveDisputeDetailed(uint256 agreementId, uint8 outcome, uint256 providerAward, uint256 clientAward) external",
|
|
46
|
+
"function openDisputeWithMode(uint256 agreementId, uint8 mode, uint8 class, string reason) external payable",
|
|
47
|
+
"function ownerResolveDispute(uint256 agreementId, bool favorProvider) external",
|
|
48
|
+
"function canDirectDispute(uint256 agreementId, uint8 directReason) external view returns (bool)",
|
|
49
|
+
"function getAgreement(uint256 id) external view returns (tuple(uint256 id, address client, address provider, string serviceType, string description, uint256 price, address token, uint256 deadline, bytes32 deliverablesHash, uint8 status, uint256 createdAt, uint256 resolvedAt, uint256 verifyWindowEnd, bytes32 committedHash))",
|
|
50
|
+
"function getDisputeCase(uint256 agreementId) external view returns (tuple(uint256 agreementId, uint256 openedAt, uint256 responseDeadlineAt, uint8 outcome, uint256 providerAward, uint256 clientAward, bool humanReviewRequested, uint256 evidenceCount))",
|
|
51
|
+
"function getDisputeEvidence(uint256 agreementId, uint256 index) external view returns (tuple(address submitter, uint8 evidenceType, bytes32 evidenceHash, string evidenceURI, uint256 timestamp))",
|
|
52
|
+
"function getArbitrationCase(uint256 agreementId) external view returns (tuple(uint256 agreementId, address[3] arbitrators, uint8 arbitratorCount, uint8 providerVotes, uint8 clientVotes, uint8 splitVotes, uint8 humanVotes, uint256 selectionDeadlineAt, uint256 decisionDeadlineAt, uint256 splitProviderAward, uint256 splitClientAward, bool finalized, bool humanBackstopUsed))",
|
|
53
|
+
"function getAgreementsByClient(address client) external view returns (uint256[])",
|
|
54
|
+
"function getAgreementsByProvider(address provider) external view returns (uint256[])",
|
|
55
|
+
"function agreementCount() external view returns (uint256)",
|
|
56
|
+
"function openSessionChannel(address provider, address token, uint256 maxAmount, uint256 ratePerCall, uint256 deadline) external payable returns (bytes32)",
|
|
57
|
+
"function closeChannel(bytes32 channelId, bytes finalState) external",
|
|
58
|
+
"function challengeChannel(bytes32 channelId, bytes latestState) external",
|
|
59
|
+
"function finaliseChallenge(bytes32 channelId) external",
|
|
60
|
+
"function reclaimExpiredChannel(bytes32 channelId) external",
|
|
61
|
+
"function getChannel(bytes32 channelId) external view returns (tuple(address client, address provider, address token, uint256 depositAmount, uint256 settledAmount, uint256 lastSequenceNumber, uint256 deadline, uint256 challengeExpiry, uint8 status))",
|
|
62
|
+
"function getChannelsByClient(address client) external view returns (bytes32[])",
|
|
63
|
+
"function getChannelsByProvider(address provider) external view returns (bytes32[])",
|
|
64
|
+
"function resolveFromArbitration(uint256 agreementId, address recipient, uint256 amount) external",
|
|
65
|
+
"event AgreementProposed(uint256 indexed id, address indexed client, address indexed provider, string serviceType, uint256 price, address token, uint256 deadline)",
|
|
66
|
+
"event AgreementAccepted(uint256 indexed id, address indexed provider)",
|
|
67
|
+
"event AgreementFulfilled(uint256 indexed id, address indexed provider, bytes32 deliverablesHash)",
|
|
68
|
+
"event AgreementDisputed(uint256 indexed id, address indexed initiator, string reason)",
|
|
69
|
+
"event AgreementCancelled(uint256 indexed id, address indexed client)",
|
|
70
|
+
] as const;
|
|
71
|
+
|
|
72
|
+
export const TRUST_REGISTRY_ABI = [
|
|
73
|
+
"function getScore(address wallet) external view returns (uint256)",
|
|
74
|
+
"function getTrustLevel(address wallet) external view returns (string)",
|
|
75
|
+
"function initWallet(address wallet) external",
|
|
76
|
+
"function recordSuccess(address wallet) external",
|
|
77
|
+
"function recordAnomaly(address wallet) external",
|
|
78
|
+
"function owner() external view returns (address)",
|
|
79
|
+
] as const;
|
|
80
|
+
|
|
81
|
+
export const ERC20_ABI = [
|
|
82
|
+
"function approve(address spender, uint256 amount) external returns (bool)",
|
|
83
|
+
"function allowance(address owner, address spender) external view returns (uint256)",
|
|
84
|
+
"function balanceOf(address owner) external view returns (uint256)",
|
|
85
|
+
"function decimals() external view returns (uint8)",
|
|
86
|
+
"function symbol() external view returns (string)",
|
|
87
|
+
] as const;
|
|
88
|
+
|
|
89
|
+
export const WALLET_FACTORY_ABI = [
|
|
90
|
+
"function createWallet(address _entryPoint) external returns (address)",
|
|
91
|
+
"function getWallets(address owner) external view returns (address[] memory)",
|
|
92
|
+
"function totalWallets() external view returns (uint256)",
|
|
93
|
+
"event WalletCreated(address indexed owner, address indexed walletAddress)",
|
|
94
|
+
] as const;
|
|
95
|
+
|
|
96
|
+
export const POLICY_ENGINE_LIMITS_ABI = [
|
|
97
|
+
// Auto-getters from public mappings (actual on-chain function names)
|
|
98
|
+
"function categoryLimits(address wallet, string category) external view returns (uint256)",
|
|
99
|
+
"function dailyCategoryLimit(address wallet, string category) external view returns (uint256)",
|
|
100
|
+
// Owner-callable setters
|
|
101
|
+
"function setCategoryLimitFor(address wallet, string category, uint256 limitPerTx) external",
|
|
102
|
+
"function setDailyLimitFor(address wallet, string category, uint256 limit) external",
|
|
103
|
+
] as const;
|
|
104
|
+
|
|
105
|
+
// PolicyEngine governance functions — for onboarding ceremony
|
|
106
|
+
export const POLICY_ENGINE_GOVERNANCE_ABI = [
|
|
107
|
+
// registerWallet requires msg.sender == wallet — route through wallet's executeContractCall
|
|
108
|
+
"function registerWallet(address wallet, address owner) external",
|
|
109
|
+
// enableDefiAccess: onlyWalletOwnerOrWallet — owner can call directly on PolicyEngine
|
|
110
|
+
"function enableDefiAccess(address wallet) external",
|
|
111
|
+
// isRegistered check helper
|
|
112
|
+
"function walletOwners(address wallet) external view returns (address)",
|
|
113
|
+
"function defiAccessEnabled(address wallet) external view returns (bool)",
|
|
114
|
+
] as const;
|
|
115
|
+
|
|
116
|
+
export const ARC402_WALLET_EXECUTE_ABI = [
|
|
117
|
+
"function executeContractCall((address target, bytes data, uint256 value, uint256 minReturnValue, uint256 maxApprovalAmount, address approvalToken) params) external returns (bytes memory)",
|
|
118
|
+
] as const;
|
|
119
|
+
|
|
120
|
+
// Direct protocol functions — all onlyOwnerOrMachineKey, never route through executeContractCall
|
|
121
|
+
export const ARC402_WALLET_PROTOCOL_ABI = [
|
|
122
|
+
"function openContext(bytes32 contextId, string calldata taskType) external",
|
|
123
|
+
"function closeContext() external",
|
|
124
|
+
"function contextOpen() external view returns (bool)",
|
|
125
|
+
"function attest(bytes32 attestationId, string calldata action, string calldata reason, address recipient, uint256 amount, address token, uint256 expiresAt) external returns (bytes32)",
|
|
126
|
+
"function executeSpend(address payable recipient, uint256 amount, string calldata category, bytes32 attestationId) external",
|
|
127
|
+
"function executeTokenSpend(address recipient, uint256 amount, address token, string calldata category, bytes32 attestationId) external",
|
|
128
|
+
] as const;
|
|
129
|
+
|
|
130
|
+
export const ARC402_WALLET_GUARDIAN_ABI = [
|
|
131
|
+
// Guardian management (owner only)
|
|
132
|
+
"function setGuardian(address _guardian) external",
|
|
133
|
+
// Guardian freeze functions (guardian key only)
|
|
134
|
+
"function freeze() external",
|
|
135
|
+
"function freezeAndDrain() external",
|
|
136
|
+
// Owner freeze (with reason)
|
|
137
|
+
"function freeze(string reason) external",
|
|
138
|
+
// Owner unfreeze
|
|
139
|
+
"function unfreeze() external",
|
|
140
|
+
// State queries
|
|
141
|
+
"function frozen() external view returns (bool)",
|
|
142
|
+
"function frozenBy() external view returns (address)",
|
|
143
|
+
"function frozenAt() external view returns (uint256)",
|
|
144
|
+
"function guardian() external view returns (address)",
|
|
145
|
+
"function owner() external view returns (address)",
|
|
146
|
+
// Events
|
|
147
|
+
"event WalletFrozen(address indexed by, string reason, uint256 timestamp)",
|
|
148
|
+
"event WalletUnfrozen(address indexed by, uint256 timestamp)",
|
|
149
|
+
"event GuardianUpdated(address indexed newGuardian)",
|
|
150
|
+
] as const;
|
|
151
|
+
|
|
152
|
+
export const ARC402_WALLET_REGISTRY_ABI = [
|
|
153
|
+
"function proposeRegistryUpdate(address newRegistry) external",
|
|
154
|
+
"function executeRegistryUpdate() external",
|
|
155
|
+
"function cancelRegistryUpdate() external",
|
|
156
|
+
"function pendingRegistry() external view returns (address)",
|
|
157
|
+
"function registryUpdateUnlockAt() external view returns (uint256)",
|
|
158
|
+
"function registry() external view returns (address)",
|
|
159
|
+
] as const;
|
|
160
|
+
|
|
161
|
+
export const ARC402_WALLET_OWNER_ABI = [
|
|
162
|
+
// Owner-only setters
|
|
163
|
+
"function setAuthorizedInterceptor(address interceptor) external",
|
|
164
|
+
"function setVelocityLimit(uint256 limit) external",
|
|
165
|
+
"function updatePolicy(bytes32 newPolicyId) external",
|
|
166
|
+
// State queries
|
|
167
|
+
"function authorizedInterceptor() external view returns (address)",
|
|
168
|
+
"function velocityLimit() external view returns (uint256)",
|
|
169
|
+
"function velocityWindowStart() external view returns (uint256)",
|
|
170
|
+
"function cumulativeSpend() external view returns (uint256)",
|
|
171
|
+
"function activePolicyId() external view returns (bytes32)",
|
|
172
|
+
] as const;
|
|
173
|
+
|
|
174
|
+
export const ARC402_WALLET_MACHINE_KEY_ABI = [
|
|
175
|
+
"function authorizeMachineKey(address key) external",
|
|
176
|
+
"function revokeMachineKey(address key) external",
|
|
177
|
+
"function authorizedMachineKeys(address key) external view returns (bool)",
|
|
178
|
+
] as const;
|
|
179
|
+
|
|
180
|
+
export const ARC402_WALLET_PASSKEY_ABI = [
|
|
181
|
+
"function setPasskey(bytes32 pubKeyX, bytes32 pubKeyY) external",
|
|
182
|
+
"function clearPasskey() external",
|
|
183
|
+
"function emergencyOwnerOverride(bytes32 newPubKeyX, bytes32 newPubKeyY) external",
|
|
184
|
+
"function emergencyOwnerOverride() external",
|
|
185
|
+
"function ownerAuth() external view returns (uint8 signerType, bytes32 pubKeyX, bytes32 pubKeyY)",
|
|
186
|
+
"event PasskeySet(bytes32 indexed pubKeyX, bytes32 pubKeyY)",
|
|
187
|
+
] as const;
|
package/src/bundler.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
import { createPrivateKey } from "crypto";
|
|
3
|
+
import type { Arc402Config } from "./config";
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_ENTRY_POINT = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
|
|
6
|
+
export const DEFAULT_BUNDLER_URL = "https://api.pimlico.io/v2/base/rpc";
|
|
7
|
+
|
|
8
|
+
export type UserOperation = {
|
|
9
|
+
sender: string;
|
|
10
|
+
nonce: string; // hex
|
|
11
|
+
callData: string; // hex
|
|
12
|
+
callGasLimit: string; // hex
|
|
13
|
+
verificationGasLimit: string; // hex
|
|
14
|
+
preVerificationGas: string; // hex
|
|
15
|
+
maxFeePerGas: string; // hex
|
|
16
|
+
maxPriorityFeePerGas: string; // hex
|
|
17
|
+
signature: string; // hex — empty for policy-auto-approved ops
|
|
18
|
+
factory?: string;
|
|
19
|
+
factoryData?: string;
|
|
20
|
+
paymaster?: string;
|
|
21
|
+
paymasterData?: string;
|
|
22
|
+
paymasterVerificationGasLimit?: string;
|
|
23
|
+
paymasterPostOpGasLimit?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type GasEstimate = {
|
|
27
|
+
callGasLimit: string;
|
|
28
|
+
verificationGasLimit: string;
|
|
29
|
+
preVerificationGas: string;
|
|
30
|
+
paymasterVerificationGasLimit?: string;
|
|
31
|
+
paymasterPostOpGasLimit?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type UserOpReceipt = {
|
|
35
|
+
userOpHash: string;
|
|
36
|
+
entryPoint: string;
|
|
37
|
+
sender: string;
|
|
38
|
+
nonce: string;
|
|
39
|
+
success: boolean;
|
|
40
|
+
actualGasCost: string;
|
|
41
|
+
actualGasUsed: string;
|
|
42
|
+
logs: unknown[];
|
|
43
|
+
receipt: {
|
|
44
|
+
transactionHash: string;
|
|
45
|
+
blockNumber: string;
|
|
46
|
+
blockHash: string;
|
|
47
|
+
[key: string]: unknown;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type RpcResponse = {
|
|
52
|
+
result?: unknown;
|
|
53
|
+
error?: { code: number; message: string };
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export class BundlerClient {
|
|
57
|
+
private bundlerUrl: string;
|
|
58
|
+
private entryPointAddress: string;
|
|
59
|
+
private chainId: number;
|
|
60
|
+
|
|
61
|
+
constructor(bundlerUrl: string, entryPointAddress: string, chainId: number) {
|
|
62
|
+
this.bundlerUrl = bundlerUrl;
|
|
63
|
+
this.entryPointAddress = entryPointAddress;
|
|
64
|
+
this.chainId = chainId;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private async rpc(method: string, params: unknown[]): Promise<unknown> {
|
|
68
|
+
const response = await fetch(this.bundlerUrl, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "Content-Type": "application/json" },
|
|
71
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(`Bundler HTTP ${response.status}: ${response.statusText}`);
|
|
75
|
+
}
|
|
76
|
+
const json = (await response.json()) as RpcResponse;
|
|
77
|
+
if (json.error) {
|
|
78
|
+
throw new Error(`Bundler RPC error [${json.error.code}]: ${json.error.message}`);
|
|
79
|
+
}
|
|
80
|
+
return json.result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async sendUserOperation(userOp: UserOperation): Promise<string> {
|
|
84
|
+
const hash = await this.rpc("eth_sendUserOperation", [userOp, this.entryPointAddress]);
|
|
85
|
+
return hash as string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getUserOperationReceipt(userOpHash: string): Promise<UserOpReceipt> {
|
|
89
|
+
const POLL_INTERVAL_MS = 2000;
|
|
90
|
+
const MAX_ATTEMPTS = 30;
|
|
91
|
+
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
|
92
|
+
const receipt = await this.rpc("eth_getUserOperationReceipt", [userOpHash]);
|
|
93
|
+
if (receipt !== null && receipt !== undefined) {
|
|
94
|
+
return receipt as UserOpReceipt;
|
|
95
|
+
}
|
|
96
|
+
await new Promise<void>((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
97
|
+
}
|
|
98
|
+
throw new Error(
|
|
99
|
+
`UserOperation ${userOpHash} not confirmed after ${(MAX_ATTEMPTS * POLL_INTERVAL_MS) / 1000}s`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async estimateUserOperationGas(userOp: Partial<UserOperation>): Promise<GasEstimate> {
|
|
104
|
+
const estimate = await this.rpc("eth_estimateUserOperationGas", [
|
|
105
|
+
userOp,
|
|
106
|
+
this.entryPointAddress,
|
|
107
|
+
]);
|
|
108
|
+
return estimate as GasEstimate;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─── PaymasterClient ──────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
export class PaymasterClient {
|
|
115
|
+
private paymasterUrl: string;
|
|
116
|
+
private cdpKeyName?: string;
|
|
117
|
+
private cdpPrivateKey?: string;
|
|
118
|
+
|
|
119
|
+
constructor(paymasterUrl: string, cdpKeyName?: string, cdpPrivateKey?: string) {
|
|
120
|
+
this.paymasterUrl = paymasterUrl;
|
|
121
|
+
this.cdpKeyName = cdpKeyName;
|
|
122
|
+
this.cdpPrivateKey = cdpPrivateKey;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private async buildJwt(): Promise<string | null> {
|
|
126
|
+
if (!this.cdpKeyName || !this.cdpPrivateKey) return null;
|
|
127
|
+
const { SignJWT, importPKCS8 } = await import("jose");
|
|
128
|
+
|
|
129
|
+
// Convert SEC1 base64 DER or SEC1 PEM → PKCS8 PEM using Node's crypto module
|
|
130
|
+
let pkcs8Pem: string;
|
|
131
|
+
if (this.cdpPrivateKey.includes("-----BEGIN")) {
|
|
132
|
+
if (this.cdpPrivateKey.includes("EC PRIVATE KEY")) {
|
|
133
|
+
const key = createPrivateKey({ key: this.cdpPrivateKey, format: "pem", type: "sec1" });
|
|
134
|
+
pkcs8Pem = key.export({ format: "pem", type: "pkcs8" }) as string;
|
|
135
|
+
} else {
|
|
136
|
+
pkcs8Pem = this.cdpPrivateKey;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
const der = Buffer.from(this.cdpPrivateKey, "base64");
|
|
140
|
+
const key = createPrivateKey({ key: der, format: "der", type: "sec1" });
|
|
141
|
+
pkcs8Pem = key.export({ format: "pem", type: "pkcs8" }) as string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const privateKey = await importPKCS8(pkcs8Pem, "ES256");
|
|
145
|
+
const now = Math.floor(Date.now() / 1000);
|
|
146
|
+
return new SignJWT({ sub: this.cdpKeyName })
|
|
147
|
+
.setProtectedHeader({ alg: "ES256", kid: this.cdpKeyName })
|
|
148
|
+
.setIssuer(this.cdpKeyName)
|
|
149
|
+
.setNotBefore(now)
|
|
150
|
+
.setExpirationTime(now + 120)
|
|
151
|
+
.sign(privateKey);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private async rpc(method: string, params: unknown[]): Promise<unknown> {
|
|
155
|
+
const jwt = await this.buildJwt();
|
|
156
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
157
|
+
if (jwt) headers["Authorization"] = `Bearer ${jwt}`;
|
|
158
|
+
const response = await fetch(this.paymasterUrl, {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers,
|
|
161
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
162
|
+
});
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
throw new Error(`Paymaster HTTP ${response.status}: ${response.statusText}`);
|
|
165
|
+
}
|
|
166
|
+
const json = (await response.json()) as { result?: unknown; error?: { code: number; message: string } };
|
|
167
|
+
if (json.error) {
|
|
168
|
+
throw new Error(`Paymaster RPC error [${json.error.code}]: ${json.error.message}`);
|
|
169
|
+
}
|
|
170
|
+
return json.result;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async sponsorUserOperation(
|
|
174
|
+
userOp: Partial<UserOperation>,
|
|
175
|
+
entryPoint: string
|
|
176
|
+
): Promise<UserOperation> {
|
|
177
|
+
const result = (await this.rpc("pm_sponsorUserOperation", [userOp, entryPoint, {}])) as {
|
|
178
|
+
paymaster: string;
|
|
179
|
+
paymasterData: string;
|
|
180
|
+
paymasterVerificationGasLimit: string;
|
|
181
|
+
paymasterPostOpGasLimit: string;
|
|
182
|
+
callGasLimit?: string;
|
|
183
|
+
verificationGasLimit?: string;
|
|
184
|
+
preVerificationGas?: string;
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
...(userOp as UserOperation),
|
|
188
|
+
paymaster: result.paymaster,
|
|
189
|
+
paymasterData: result.paymasterData,
|
|
190
|
+
paymasterVerificationGasLimit: result.paymasterVerificationGasLimit,
|
|
191
|
+
paymasterPostOpGasLimit: result.paymasterPostOpGasLimit,
|
|
192
|
+
...(result.callGasLimit && { callGasLimit: result.callGasLimit }),
|
|
193
|
+
...(result.verificationGasLimit && { verificationGasLimit: result.verificationGasLimit }),
|
|
194
|
+
...(result.preVerificationGas && { preVerificationGas: result.preVerificationGas }),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ─── buildUserOp / buildSponsoredUserOp ───────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
export async function buildUserOp(
|
|
202
|
+
callData: string,
|
|
203
|
+
sender: string,
|
|
204
|
+
nonce: bigint,
|
|
205
|
+
config: Arc402Config
|
|
206
|
+
): Promise<UserOperation> {
|
|
207
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
208
|
+
const feeData = await provider.getFeeData();
|
|
209
|
+
|
|
210
|
+
const maxFeePerGas = feeData.maxFeePerGas ?? BigInt(1_000_000_000);
|
|
211
|
+
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? BigInt(100_000_000);
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
sender,
|
|
215
|
+
nonce: ethers.toBeHex(nonce),
|
|
216
|
+
callData,
|
|
217
|
+
callGasLimit: ethers.toBeHex(300_000),
|
|
218
|
+
verificationGasLimit: ethers.toBeHex(150_000),
|
|
219
|
+
preVerificationGas: ethers.toBeHex(50_000),
|
|
220
|
+
maxFeePerGas: ethers.toBeHex(maxFeePerGas),
|
|
221
|
+
maxPriorityFeePerGas: ethers.toBeHex(maxPriorityFeePerGas),
|
|
222
|
+
signature: "0x",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export async function buildSponsoredUserOp(
|
|
227
|
+
callData: string,
|
|
228
|
+
sender: string,
|
|
229
|
+
nonce: bigint,
|
|
230
|
+
config: Arc402Config,
|
|
231
|
+
paymasterClient: PaymasterClient
|
|
232
|
+
): Promise<UserOperation> {
|
|
233
|
+
const userOp = await buildUserOp(callData, sender, nonce, config);
|
|
234
|
+
return paymasterClient.sponsorUserOperation(userOp, DEFAULT_ENTRY_POINT);
|
|
235
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
import { Arc402Config } from "./config";
|
|
3
|
+
|
|
4
|
+
export interface Arc402Client {
|
|
5
|
+
provider: ethers.JsonRpcProvider;
|
|
6
|
+
signer: ethers.Wallet | null;
|
|
7
|
+
address: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function getClient(config: Arc402Config): Promise<Arc402Client> {
|
|
11
|
+
const provider = new ethers.JsonRpcProvider(config.rpcUrl);
|
|
12
|
+
|
|
13
|
+
if (config.privateKey) {
|
|
14
|
+
const signer = new ethers.Wallet(config.privateKey, provider);
|
|
15
|
+
const address = await signer.getAddress();
|
|
16
|
+
return { provider, signer, address };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return { provider, signer: null, address: null };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function requireSigner(
|
|
23
|
+
config: Arc402Config
|
|
24
|
+
): Promise<{ provider: ethers.JsonRpcProvider; signer: ethers.Wallet; address: string }> {
|
|
25
|
+
const { provider, signer } = await getClient(config);
|
|
26
|
+
if (!signer) {
|
|
27
|
+
console.error(
|
|
28
|
+
"No private key configured. Run `arc402 config init` and provide a private key."
|
|
29
|
+
);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const address = await signer.getAddress();
|
|
33
|
+
return { provider, signer, address };
|
|
34
|
+
}
|