@hpp-io/x402-mcp-bridge 0.0.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/LICENSE +19 -0
- package/README.md +129 -0
- package/bin/hpp-x402-safe-revoke.js +5 -0
- package/bin/hpp-x402-safe-setup.js +5 -0
- package/bin/x402-mcp-bridge.js +11 -0
- package/dist/autoTopup.d.ts +50 -0
- package/dist/autoTopup.js +193 -0
- package/dist/autoTopup.js.map +1 -0
- package/dist/cli/install-claude.d.ts +25 -0
- package/dist/cli/install-claude.js +82 -0
- package/dist/cli/install-claude.js.map +1 -0
- package/dist/cli/revoke.d.ts +1 -0
- package/dist/cli/revoke.js +123 -0
- package/dist/cli/revoke.js.map +1 -0
- package/dist/cli/safe.d.ts +20172 -0
- package/dist/cli/safe.js +299 -0
- package/dist/cli/safe.js.map +1 -0
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.js +234 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/client.d.ts +30 -0
- package/dist/client.js +65 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +46 -0
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +16 -0
- package/dist/log.js +24 -0
- package/dist/log.js.map +1 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +56 -0
- package/dist/server.js.map +1 -0
- package/dist/signer.d.ts +45 -0
- package/dist/signer.js +2 -0
- package/dist/signer.js.map +1 -0
- package/dist/signers/raw-eoa.d.ts +12 -0
- package/dist/signers/raw-eoa.js +29 -0
- package/dist/signers/raw-eoa.js.map +1 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
|
|
17
|
+
See https://www.apache.org/licenses/LICENSE-2.0 for the full text.
|
|
18
|
+
|
|
19
|
+
Copyright 2026 HPP / Noosphere contributors.
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# @hpp-io/x402-mcp-bridge
|
|
2
|
+
|
|
3
|
+
> stdio MCP bridge that lets Claude Desktop / OpenClaw / other MCP hosts make
|
|
4
|
+
> autonomous **HPP USDC.e** payments via x402 — funds stay in a Safe, the
|
|
5
|
+
> bridge auto-tops up a delegate EOA within an on-chain spend cap
|
|
6
|
+
> (AllowanceModule).
|
|
7
|
+
|
|
8
|
+
## What it is
|
|
9
|
+
|
|
10
|
+
The MCP host (Claude Desktop, OpenClaw, Cursor, …) doesn't know how to sign
|
|
11
|
+
EIP-3009 payments. This bridge runs as a stdio subprocess that the host
|
|
12
|
+
spawns, exposes itself as an MCP server to the host, and internally:
|
|
13
|
+
|
|
14
|
+
1. Connects upstream to a remote x402 MCP server (SSE / StreamableHTTP).
|
|
15
|
+
2. Forwards `listTools` / `callTool` between host and remote.
|
|
16
|
+
3. When the remote returns 402, the bridge:
|
|
17
|
+
- Tops up the delegate EOA from the user's Safe via
|
|
18
|
+
`AllowanceModule.executeAllowanceTransfer` (chain-enforced daily cap).
|
|
19
|
+
- Signs EIP-3009 `transferWithAuthorization` with the delegate key.
|
|
20
|
+
- Retries the call.
|
|
21
|
+
|
|
22
|
+
The host sees a normal MCP server and never deals with payment.
|
|
23
|
+
|
|
24
|
+
## Install / use
|
|
25
|
+
|
|
26
|
+
```jsonc
|
|
27
|
+
// claude_desktop_config.json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"hpp-x402": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "@hpp-io/x402-mcp-bridge"],
|
|
33
|
+
"env": {
|
|
34
|
+
"DELEGATE_PRIVATE_KEY": "0x...",
|
|
35
|
+
"SAFE_ADDRESS": "0x...",
|
|
36
|
+
"ALLOWANCE_MODULE_ADDRESS": "0x...",
|
|
37
|
+
"USDCE_ADDRESS": "0x...",
|
|
38
|
+
"RESOURCE_SERVER_URL": "https://your-x402-server/mcp/sse",
|
|
39
|
+
"HPP_RPC_URL": "https://sepolia.hpp.io",
|
|
40
|
+
"HPP_NETWORK": "eip155:181228"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Same config block works for OpenClaw and any other MCP host that supports
|
|
48
|
+
stdio MCP servers. See [`examples/`](./examples/) for ready-to-merge JSON
|
|
49
|
+
snippets and a [local development guide](./examples/local-dev.md) for using
|
|
50
|
+
the bridge before it's published to npm.
|
|
51
|
+
|
|
52
|
+
## Pre-flight (one-time, on-chain)
|
|
53
|
+
|
|
54
|
+
Before the bridge can do anything, you need a Safe with USDC.e and an
|
|
55
|
+
AllowanceModule entry that authorises a delegate EOA to pull from it:
|
|
56
|
+
|
|
57
|
+
1. Deploy / use an existing Safe at [`safe.hpp.io`](https://safe.hpp.io).
|
|
58
|
+
2. Send USDC.e to the Safe.
|
|
59
|
+
3. Enable the AllowanceModule on the Safe (one Safe transaction).
|
|
60
|
+
4. `addDelegate(<delegate EOA>)` and
|
|
61
|
+
`setAllowance(<delegate>, USDC.e, <daily cap>, 1440 minutes, 0)` —
|
|
62
|
+
one Safe transaction each (or batched via Safe's MultiSend).
|
|
63
|
+
5. Send a small amount of native ETH to the delegate EOA (it pays gas
|
|
64
|
+
for `executeAllowanceTransfer`).
|
|
65
|
+
|
|
66
|
+
Verified end-to-end on HPP Sepolia 2026-05-06 — the chain enforces the
|
|
67
|
+
daily cap, and `deleteAllowance` instantly blocks future top-ups.
|
|
68
|
+
|
|
69
|
+
## Architecture
|
|
70
|
+
|
|
71
|
+
See [DESIGN.md in the parent task](../noosphere-evm/.claude/tasks/x402-mcp-wallet/design/DESIGN.md).
|
|
72
|
+
|
|
73
|
+
In short:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
host (Claude Desktop)
|
|
77
|
+
│ stdio (MCP)
|
|
78
|
+
▼
|
|
79
|
+
[ this bridge ]
|
|
80
|
+
─ Signer (delegate EOA)
|
|
81
|
+
─ AutoTopup (Safe + AllowanceModule)
|
|
82
|
+
─ remote MCP client (@x402/mcp wrapped)
|
|
83
|
+
│
|
|
84
|
+
│ SSE / StreamableHTTP (MCP + x402)
|
|
85
|
+
▼
|
|
86
|
+
remote x402 MCP server
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Releasing to npm
|
|
90
|
+
|
|
91
|
+
Two paths, both via the GitHub Actions [Publish workflow](.github/workflows/publish.yml):
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
# (1) Recommended: tag-driven release
|
|
95
|
+
git checkout main && git pull
|
|
96
|
+
npm version patch # or minor / major
|
|
97
|
+
git push --follow-tags # → workflow runs, publishes to npm
|
|
98
|
+
|
|
99
|
+
# (2) Ad-hoc: workflow_dispatch from GitHub UI
|
|
100
|
+
# Actions tab → "Publish to npm" → Run workflow
|
|
101
|
+
# Optional `dry_run` toggle for verification
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Required secret on the GitHub repo: `NPM_TOKEN` (npmjs.com → Profile →
|
|
105
|
+
Access Tokens → Generate "Automation" token scoped to `@hpp-io`).
|
|
106
|
+
|
|
107
|
+
The `prepublishOnly` script in package.json runs `typecheck && build`
|
|
108
|
+
locally too, so manual `npm publish` from a developer's machine still
|
|
109
|
+
guards against shipping broken builds.
|
|
110
|
+
|
|
111
|
+
## Status
|
|
112
|
+
|
|
113
|
+
**v0.0.1 — PoC.** Working end-to-end against HPP Sepolia (smoke proves
|
|
114
|
+
both the "delegate has balance" and "delegate empty → autoTopup → pay"
|
|
115
|
+
paths). Not yet published to npm; install via local path or git clone.
|
|
116
|
+
|
|
117
|
+
Roadmap:
|
|
118
|
+
|
|
119
|
+
- v0.1: package on npm, full README with screenshots, OS keychain wrapper
|
|
120
|
+
for `DELEGATE_PRIVATE_KEY` (`keychain://hpp-x402-delegate`).
|
|
121
|
+
- v0.2: additional `Signer` implementations (HPP self-hosted server-wallet
|
|
122
|
+
API, ERC-4337 session keys).
|
|
123
|
+
- v0.3: structured guardrail layer (allowlist of `RESOURCE_SERVER_URL`,
|
|
124
|
+
per-call max payment cap) — mirrors Coinbase AgentKit's
|
|
125
|
+
`X402ActionProvider`.
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
Apache-2.0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Entry shim for `npx @hpp-io/x402-mcp-bridge` and Claude Desktop's
|
|
4
|
+
* "command": "npx" launch path. Imports the compiled ESM build.
|
|
5
|
+
*/
|
|
6
|
+
import("../dist/index.js").then((m) => {
|
|
7
|
+
return m.runBridge();
|
|
8
|
+
}).catch((err) => {
|
|
9
|
+
process.stderr.write(`x402-mcp-bridge fatal: ${err?.message ?? err}\n`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutoTopup — pulls USDC.e from the user's Safe to the delegate EOA via
|
|
3
|
+
* AllowanceModule.executeAllowanceTransfer.
|
|
4
|
+
*
|
|
5
|
+
* Triggered lazily before each x402 payment when the delegate's balance
|
|
6
|
+
* is insufficient. The chain enforces the daily cap; if a topup would
|
|
7
|
+
* exceed the allowance the call reverts with
|
|
8
|
+
*
|
|
9
|
+
* "newSpent > allowance.spent && newSpent <= allowance.amount"
|
|
10
|
+
*
|
|
11
|
+
* which the bridge maps to a clear "spend cap exceeded" error for the
|
|
12
|
+
* MCP host (and therefore the LLM and the user).
|
|
13
|
+
*
|
|
14
|
+
* Implementation note: Verified end-to-end on HPP Sepolia 2026-05-06,
|
|
15
|
+
* see ../noosphere-evm/.claude/tasks/x402-mcp-wallet/design/DEPLOYMENTS.md
|
|
16
|
+
*/
|
|
17
|
+
import { type Address, type Hex } from "viem";
|
|
18
|
+
import type { Signer } from "./signer.js";
|
|
19
|
+
export interface AutoTopupOptions {
|
|
20
|
+
/** PoC-only: explicit pull amount (atomic). If unset, a dynamic value
|
|
21
|
+
* based on `requiredAtomic` × headroom is used. */
|
|
22
|
+
fixedAmountAtomic?: bigint;
|
|
23
|
+
/** Headroom multiplier for dynamic topup. Default 10. */
|
|
24
|
+
headroomX?: bigint;
|
|
25
|
+
}
|
|
26
|
+
export declare class AutoTopup {
|
|
27
|
+
private readonly signer;
|
|
28
|
+
private readonly safe;
|
|
29
|
+
private readonly allowanceModule;
|
|
30
|
+
private readonly token;
|
|
31
|
+
private readonly chainId;
|
|
32
|
+
private readonly rpcUrl;
|
|
33
|
+
private readonly opts;
|
|
34
|
+
private readonly publicClient;
|
|
35
|
+
private readonly walletClient;
|
|
36
|
+
constructor(signer: Signer, safe: Address, allowanceModule: Address, token: Address, chainId: number, rpcUrl: string, opts?: AutoTopupOptions);
|
|
37
|
+
/**
|
|
38
|
+
* Ensure the delegate holds at least `requiredAtomic` of `token`.
|
|
39
|
+
* No-op when balance already sufficient. Returns the topup tx hash
|
|
40
|
+
* (or null if no topup was needed).
|
|
41
|
+
*/
|
|
42
|
+
ensure(requiredAtomic: bigint): Promise<Hex | null>;
|
|
43
|
+
/** Read delegate's current ERC-20 balance. */
|
|
44
|
+
private balanceOfDelegate;
|
|
45
|
+
/** allowance.amount - allowance.spent (after on-chain auto-reset, which
|
|
46
|
+
* AllowanceModule applies inside getTokenAllowance). */
|
|
47
|
+
private remainingAllowance;
|
|
48
|
+
/** Sign + send AllowanceModule.executeAllowanceTransfer. */
|
|
49
|
+
private execute;
|
|
50
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutoTopup — pulls USDC.e from the user's Safe to the delegate EOA via
|
|
3
|
+
* AllowanceModule.executeAllowanceTransfer.
|
|
4
|
+
*
|
|
5
|
+
* Triggered lazily before each x402 payment when the delegate's balance
|
|
6
|
+
* is insufficient. The chain enforces the daily cap; if a topup would
|
|
7
|
+
* exceed the allowance the call reverts with
|
|
8
|
+
*
|
|
9
|
+
* "newSpent > allowance.spent && newSpent <= allowance.amount"
|
|
10
|
+
*
|
|
11
|
+
* which the bridge maps to a clear "spend cap exceeded" error for the
|
|
12
|
+
* MCP host (and therefore the LLM and the user).
|
|
13
|
+
*
|
|
14
|
+
* Implementation note: Verified end-to-end on HPP Sepolia 2026-05-06,
|
|
15
|
+
* see ../noosphere-evm/.claude/tasks/x402-mcp-wallet/design/DEPLOYMENTS.md
|
|
16
|
+
*/
|
|
17
|
+
import { createPublicClient, createWalletClient, http, defineChain, } from "viem";
|
|
18
|
+
import { RawEoaSigner } from "./signers/raw-eoa.js";
|
|
19
|
+
const ERC20_BALANCE_ABI = [
|
|
20
|
+
{
|
|
21
|
+
type: "function",
|
|
22
|
+
name: "balanceOf",
|
|
23
|
+
inputs: [{ name: "account", type: "address" }],
|
|
24
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
25
|
+
stateMutability: "view",
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
const ALLOWANCE_ABI = [
|
|
29
|
+
{
|
|
30
|
+
type: "function",
|
|
31
|
+
name: "getTokenAllowance",
|
|
32
|
+
inputs: [
|
|
33
|
+
{ name: "safe", type: "address" },
|
|
34
|
+
{ name: "delegate", type: "address" },
|
|
35
|
+
{ name: "token", type: "address" },
|
|
36
|
+
],
|
|
37
|
+
outputs: [{ name: "", type: "uint256[5]" }],
|
|
38
|
+
stateMutability: "view",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: "function",
|
|
42
|
+
name: "executeAllowanceTransfer",
|
|
43
|
+
inputs: [
|
|
44
|
+
{ name: "safe", type: "address" },
|
|
45
|
+
{ name: "token", type: "address" },
|
|
46
|
+
{ name: "to", type: "address" },
|
|
47
|
+
{ name: "amount", type: "uint96" },
|
|
48
|
+
{ name: "paymentToken", type: "address" },
|
|
49
|
+
{ name: "payment", type: "uint96" },
|
|
50
|
+
{ name: "delegate", type: "address" },
|
|
51
|
+
{ name: "signature", type: "bytes" },
|
|
52
|
+
],
|
|
53
|
+
outputs: [],
|
|
54
|
+
stateMutability: "nonpayable",
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
export class AutoTopup {
|
|
58
|
+
signer;
|
|
59
|
+
safe;
|
|
60
|
+
allowanceModule;
|
|
61
|
+
token;
|
|
62
|
+
chainId;
|
|
63
|
+
rpcUrl;
|
|
64
|
+
opts;
|
|
65
|
+
publicClient;
|
|
66
|
+
walletClient;
|
|
67
|
+
constructor(signer, safe, allowanceModule, token, chainId, rpcUrl, opts = {}) {
|
|
68
|
+
this.signer = signer;
|
|
69
|
+
this.safe = safe;
|
|
70
|
+
this.allowanceModule = allowanceModule;
|
|
71
|
+
this.token = token;
|
|
72
|
+
this.chainId = chainId;
|
|
73
|
+
this.rpcUrl = rpcUrl;
|
|
74
|
+
this.opts = opts;
|
|
75
|
+
if (!(signer instanceof RawEoaSigner)) {
|
|
76
|
+
// The contract calls below require a viem-account-shaped signer.
|
|
77
|
+
// The Signer interface itself doesn't yet expose `sendTransaction`,
|
|
78
|
+
// so the autoTopup path currently requires RawEoaSigner. When new
|
|
79
|
+
// signer kinds land we'll widen this through a `tx.send` helper.
|
|
80
|
+
throw new Error("AutoTopup currently requires RawEoaSigner; other signer types pending");
|
|
81
|
+
}
|
|
82
|
+
const chain = defineChain({
|
|
83
|
+
id: chainId,
|
|
84
|
+
name: `chain-${chainId}`,
|
|
85
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
86
|
+
rpcUrls: { default: { http: [rpcUrl] } },
|
|
87
|
+
});
|
|
88
|
+
this.publicClient = createPublicClient({ chain, transport: http(rpcUrl) });
|
|
89
|
+
this.walletClient = createWalletClient({
|
|
90
|
+
chain,
|
|
91
|
+
transport: http(rpcUrl),
|
|
92
|
+
account: signer.viemAccount,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Ensure the delegate holds at least `requiredAtomic` of `token`.
|
|
97
|
+
* No-op when balance already sufficient. Returns the topup tx hash
|
|
98
|
+
* (or null if no topup was needed).
|
|
99
|
+
*/
|
|
100
|
+
async ensure(requiredAtomic) {
|
|
101
|
+
const balance = await this.balanceOfDelegate();
|
|
102
|
+
if (balance >= requiredAtomic)
|
|
103
|
+
return null;
|
|
104
|
+
const need = requiredAtomic - balance;
|
|
105
|
+
const headroom = this.opts.headroomX ?? 10n;
|
|
106
|
+
const wanted = this.opts.fixedAmountAtomic ?? need * headroom > 0n
|
|
107
|
+
? this.opts.fixedAmountAtomic ?? need * headroom
|
|
108
|
+
: need;
|
|
109
|
+
// Cap at remaining allowance to avoid the (otherwise inevitable) revert.
|
|
110
|
+
const remaining = await this.remainingAllowance();
|
|
111
|
+
const amount = wanted > remaining ? remaining : wanted;
|
|
112
|
+
if (amount < need) {
|
|
113
|
+
throw new Error(`spend cap exceeded — required ${requiredAtomic} but only ${balance + remaining} available before allowance reset`);
|
|
114
|
+
}
|
|
115
|
+
return this.execute(amount);
|
|
116
|
+
}
|
|
117
|
+
/** Read delegate's current ERC-20 balance. */
|
|
118
|
+
async balanceOfDelegate() {
|
|
119
|
+
return (await this.publicClient.readContract({
|
|
120
|
+
address: this.token,
|
|
121
|
+
abi: ERC20_BALANCE_ABI,
|
|
122
|
+
functionName: "balanceOf",
|
|
123
|
+
args: [this.signer.address],
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
/** allowance.amount - allowance.spent (after on-chain auto-reset, which
|
|
127
|
+
* AllowanceModule applies inside getTokenAllowance). */
|
|
128
|
+
async remainingAllowance() {
|
|
129
|
+
const a = (await this.publicClient.readContract({
|
|
130
|
+
address: this.allowanceModule,
|
|
131
|
+
abi: ALLOWANCE_ABI,
|
|
132
|
+
functionName: "getTokenAllowance",
|
|
133
|
+
args: [this.safe, this.signer.address, this.token],
|
|
134
|
+
}));
|
|
135
|
+
if (a[0] === 0n)
|
|
136
|
+
return 0n;
|
|
137
|
+
return a[1] >= a[0] ? 0n : a[0] - a[1];
|
|
138
|
+
}
|
|
139
|
+
/** Sign + send AllowanceModule.executeAllowanceTransfer. */
|
|
140
|
+
async execute(amountAtomic) {
|
|
141
|
+
const a = (await this.publicClient.readContract({
|
|
142
|
+
address: this.allowanceModule,
|
|
143
|
+
abi: ALLOWANCE_ABI,
|
|
144
|
+
functionName: "getTokenAllowance",
|
|
145
|
+
args: [this.safe, this.signer.address, this.token],
|
|
146
|
+
}));
|
|
147
|
+
const nonce = Number(a[4]);
|
|
148
|
+
const signature = await this.signer.signTypedData({
|
|
149
|
+
domain: { chainId: this.chainId, verifyingContract: this.allowanceModule },
|
|
150
|
+
types: {
|
|
151
|
+
AllowanceTransfer: [
|
|
152
|
+
{ name: "safe", type: "address" },
|
|
153
|
+
{ name: "token", type: "address" },
|
|
154
|
+
{ name: "to", type: "address" },
|
|
155
|
+
{ name: "amount", type: "uint96" },
|
|
156
|
+
{ name: "paymentToken", type: "address" },
|
|
157
|
+
{ name: "payment", type: "uint96" },
|
|
158
|
+
{ name: "nonce", type: "uint16" },
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
primaryType: "AllowanceTransfer",
|
|
162
|
+
message: {
|
|
163
|
+
safe: this.safe,
|
|
164
|
+
token: this.token,
|
|
165
|
+
to: this.signer.address,
|
|
166
|
+
amount: amountAtomic,
|
|
167
|
+
paymentToken: "0x0000000000000000000000000000000000000000",
|
|
168
|
+
payment: 0n,
|
|
169
|
+
nonce,
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
const txHash = await this.walletClient.writeContract({
|
|
173
|
+
address: this.allowanceModule,
|
|
174
|
+
abi: ALLOWANCE_ABI,
|
|
175
|
+
functionName: "executeAllowanceTransfer",
|
|
176
|
+
args: [
|
|
177
|
+
this.safe,
|
|
178
|
+
this.token,
|
|
179
|
+
this.signer.address,
|
|
180
|
+
amountAtomic,
|
|
181
|
+
"0x0000000000000000000000000000000000000000",
|
|
182
|
+
0n,
|
|
183
|
+
this.signer.address,
|
|
184
|
+
signature,
|
|
185
|
+
],
|
|
186
|
+
chain: null,
|
|
187
|
+
account: this.signer.viemAccount,
|
|
188
|
+
});
|
|
189
|
+
await this.publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
190
|
+
return txHash;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=autoTopup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoTopup.js","sourceRoot":"","sources":["../src/autoTopup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare function configPath(): string;
|
|
2
|
+
export interface InstallEnv {
|
|
3
|
+
DELEGATE_PRIVATE_KEY: string;
|
|
4
|
+
SAFE_ADDRESS: string;
|
|
5
|
+
ALLOWANCE_MODULE_ADDRESS: string;
|
|
6
|
+
USDCE_ADDRESS: string;
|
|
7
|
+
RESOURCE_SERVER_URL: string;
|
|
8
|
+
HPP_RPC_URL: string;
|
|
9
|
+
HPP_NETWORK: string;
|
|
10
|
+
LOG_LEVEL?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface InstallResult {
|
|
13
|
+
configPath: string;
|
|
14
|
+
backupPath: string | null;
|
|
15
|
+
changed: boolean;
|
|
16
|
+
preservedEntries: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Merge an `hpp-x402` server entry into the host config and write it.
|
|
20
|
+
* `command`/`args` default to launching the published bridge via npx.
|
|
21
|
+
*/
|
|
22
|
+
export declare function installClaudeDesktop(env: InstallEnv, opts?: {
|
|
23
|
+
force?: boolean;
|
|
24
|
+
bridgeAbsPath?: string;
|
|
25
|
+
}): InstallResult;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge an `hpp-x402` MCP server entry into the user's
|
|
3
|
+
* claude_desktop_config.json. Importable function (used by setup CLI
|
|
4
|
+
* with --install-claude) and a standalone bin entry.
|
|
5
|
+
*
|
|
6
|
+
* Preserves all other mcpServers entries; backs up the original.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync, } from "node:fs";
|
|
9
|
+
import { resolve, dirname } from "node:path";
|
|
10
|
+
import { homedir, platform } from "node:os";
|
|
11
|
+
const ENTRY_NAME = "hpp-x402";
|
|
12
|
+
export function configPath() {
|
|
13
|
+
const p = platform();
|
|
14
|
+
if (p === "darwin") {
|
|
15
|
+
return resolve(homedir(), "Library/Application Support/Claude/claude_desktop_config.json");
|
|
16
|
+
}
|
|
17
|
+
if (p === "win32") {
|
|
18
|
+
return resolve(process.env.APPDATA ?? "", "Claude/claude_desktop_config.json");
|
|
19
|
+
}
|
|
20
|
+
return resolve(homedir(), ".config/Claude/claude_desktop_config.json");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Merge an `hpp-x402` server entry into the host config and write it.
|
|
24
|
+
* `command`/`args` default to launching the published bridge via npx.
|
|
25
|
+
*/
|
|
26
|
+
export function installClaudeDesktop(env, opts = {}) {
|
|
27
|
+
const path = configPath();
|
|
28
|
+
let existing = {};
|
|
29
|
+
if (existsSync(path)) {
|
|
30
|
+
existing = JSON.parse(readFileSync(path, "utf8"));
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
const command = opts.bridgeAbsPath ? "node" : "npx";
|
|
36
|
+
const args = opts.bridgeAbsPath
|
|
37
|
+
? [opts.bridgeAbsPath]
|
|
38
|
+
: ["-y", "@hpp-io/x402-mcp-bridge"];
|
|
39
|
+
const entry = {
|
|
40
|
+
command,
|
|
41
|
+
args,
|
|
42
|
+
env: {
|
|
43
|
+
DELEGATE_PRIVATE_KEY: env.DELEGATE_PRIVATE_KEY,
|
|
44
|
+
SAFE_ADDRESS: env.SAFE_ADDRESS,
|
|
45
|
+
ALLOWANCE_MODULE_ADDRESS: env.ALLOWANCE_MODULE_ADDRESS,
|
|
46
|
+
USDCE_ADDRESS: env.USDCE_ADDRESS,
|
|
47
|
+
RESOURCE_SERVER_URL: env.RESOURCE_SERVER_URL,
|
|
48
|
+
HPP_RPC_URL: env.HPP_RPC_URL,
|
|
49
|
+
HPP_NETWORK: env.HPP_NETWORK,
|
|
50
|
+
LOG_LEVEL: env.LOG_LEVEL ?? "info",
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
const mcpServers = (existing.mcpServers ??= {});
|
|
54
|
+
const prior = mcpServers[ENTRY_NAME];
|
|
55
|
+
if (prior && JSON.stringify(prior) === JSON.stringify(entry)) {
|
|
56
|
+
return {
|
|
57
|
+
configPath: path,
|
|
58
|
+
backupPath: null,
|
|
59
|
+
changed: false,
|
|
60
|
+
preservedEntries: Object.keys(mcpServers).filter((k) => k !== ENTRY_NAME),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (prior && !opts.force) {
|
|
64
|
+
throw new Error(`existing hpp-x402 entry differs; pass force:true (or --force in CLI) to overwrite`);
|
|
65
|
+
}
|
|
66
|
+
mcpServers[ENTRY_NAME] = entry;
|
|
67
|
+
const merged = { ...existing, mcpServers };
|
|
68
|
+
let backupPath = null;
|
|
69
|
+
if (existsSync(path)) {
|
|
70
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
71
|
+
backupPath = path + "." + ts + ".bak";
|
|
72
|
+
copyFileSync(path, backupPath);
|
|
73
|
+
}
|
|
74
|
+
writeFileSync(path, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
75
|
+
return {
|
|
76
|
+
configPath: path,
|
|
77
|
+
backupPath,
|
|
78
|
+
changed: true,
|
|
79
|
+
preservedEntries: Object.keys(mcpServers).filter((k) => k !== ENTRY_NAME),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=install-claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-claude.js","sourceRoot":"","sources":["../../src/cli/install-claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { buildClients, execSafeTx, encodeDeleteAllowance, } from "./safe.js";
|
|
2
|
+
const DEFAULTS = {
|
|
3
|
+
"eip155:181228": {
|
|
4
|
+
rpc: "https://sepolia.hpp.io",
|
|
5
|
+
usdc: "0x401eCb1D350407f13ba348573E5630B83638E30D",
|
|
6
|
+
module: "0x3CcE72483929e0517Dafc8fD192547B3B65f9b07",
|
|
7
|
+
chainId: 181228,
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const out = {};
|
|
12
|
+
for (let i = 2; i < argv.length; i++) {
|
|
13
|
+
const a = argv[i];
|
|
14
|
+
if (!a.startsWith("--"))
|
|
15
|
+
continue;
|
|
16
|
+
const k = a.slice(2);
|
|
17
|
+
const next = argv[i + 1];
|
|
18
|
+
if (!next || next.startsWith("--")) {
|
|
19
|
+
out[k] = true;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
out[k] = next;
|
|
23
|
+
i++;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
function requireStr(a, k) {
|
|
29
|
+
const v = a[k];
|
|
30
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
31
|
+
console.error(`missing --${k}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
return v;
|
|
35
|
+
}
|
|
36
|
+
const ERC20_ABI = [
|
|
37
|
+
{
|
|
38
|
+
type: "function",
|
|
39
|
+
name: "balanceOf",
|
|
40
|
+
inputs: [{ name: "account", type: "address" }],
|
|
41
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
42
|
+
stateMutability: "view",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: "function",
|
|
46
|
+
name: "transfer",
|
|
47
|
+
inputs: [
|
|
48
|
+
{ name: "to", type: "address" },
|
|
49
|
+
{ name: "amount", type: "uint256" },
|
|
50
|
+
],
|
|
51
|
+
outputs: [{ name: "", type: "bool" }],
|
|
52
|
+
stateMutability: "nonpayable",
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
async function main() {
|
|
56
|
+
const args = parseArgs(process.argv);
|
|
57
|
+
const network = args.network || "eip155:181228";
|
|
58
|
+
const def = DEFAULTS[network];
|
|
59
|
+
if (!def) {
|
|
60
|
+
console.error(`unsupported network: ${network}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
const ownerPk = requireStr(args, "owner-pk");
|
|
64
|
+
const safe = requireStr(args, "safe");
|
|
65
|
+
const delegate = requireStr(args, "delegate");
|
|
66
|
+
const rpc = args.rpc || def.rpc;
|
|
67
|
+
const usdc = (args.usdc || def.usdc);
|
|
68
|
+
const module = (args.module || def.module);
|
|
69
|
+
const drain = args.drain === true;
|
|
70
|
+
const delegatePk = args["delegate-pk"];
|
|
71
|
+
if (drain && !delegatePk) {
|
|
72
|
+
console.error("--drain requires --delegate-pk to sign the transfer back");
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
const cfg = { rpcUrl: rpc, chainId: def.chainId };
|
|
76
|
+
console.log(`hpp-x402-safe-revoke`);
|
|
77
|
+
console.log(` Safe : ${safe}`);
|
|
78
|
+
console.log(` Delegate : ${delegate}`);
|
|
79
|
+
console.log(` Mode : ${drain ? "deleteAllowance + drain" : "deleteAllowance only"}`);
|
|
80
|
+
console.log("");
|
|
81
|
+
// ---- 1) deleteAllowance (Safe Tx, owner signs) ---------------------
|
|
82
|
+
console.log("[1/2] AllowanceModule.deleteAllowance…");
|
|
83
|
+
const deleteTx = await execSafeTx(cfg, ownerPk, safe, module, encodeDeleteAllowance(delegate, usdc));
|
|
84
|
+
console.log(` ✓ deleted (Safe tx ${deleteTx})`);
|
|
85
|
+
// ---- 2) (optional) drain delegate's remaining USDC.e ---------------
|
|
86
|
+
if (drain) {
|
|
87
|
+
console.log("[2/2] Draining delegate's USDC.e back to Safe…");
|
|
88
|
+
const { publicClient, walletClient } = buildClients(cfg, delegatePk);
|
|
89
|
+
const balance = (await publicClient.readContract({
|
|
90
|
+
address: usdc,
|
|
91
|
+
abi: ERC20_ABI,
|
|
92
|
+
functionName: "balanceOf",
|
|
93
|
+
args: [delegate],
|
|
94
|
+
}));
|
|
95
|
+
if (balance === 0n) {
|
|
96
|
+
console.log(" ✓ delegate already empty — nothing to drain");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const tx = await walletClient.writeContract({
|
|
100
|
+
address: usdc,
|
|
101
|
+
abi: ERC20_ABI,
|
|
102
|
+
functionName: "transfer",
|
|
103
|
+
args: [safe, balance],
|
|
104
|
+
});
|
|
105
|
+
await publicClient.waitForTransactionReceipt({ hash: tx });
|
|
106
|
+
console.log(` ✓ drained ${Number(balance) / 1e6} USDC.e (tx ${tx})`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.log("[2/2] Skipped drain (no --drain flag).");
|
|
111
|
+
}
|
|
112
|
+
console.log("\n🎉 revocation complete.");
|
|
113
|
+
console.log("\nThe delegate can no longer pull from the Safe via AllowanceModule.");
|
|
114
|
+
if (!drain) {
|
|
115
|
+
console.log("⚠ delegate may still hold leftover USDC.e it pulled earlier.");
|
|
116
|
+
console.log(" Run again with --drain --delegate-pk 0x... to recover that balance.");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
main().catch((err) => {
|
|
120
|
+
console.error("\nERROR:", err?.message ?? err);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
});
|
|
123
|
+
//# sourceMappingURL=revoke.js.map
|