leash-monad 0.1.2 → 0.1.3
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 +20 -0
- package/dist/index.cjs +6 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +6 -1
- package/dist/networks.d.ts +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,6 +48,18 @@ app.get("/api/forecast", async (req, res) => {
|
|
|
48
48
|
|
|
49
49
|
A revoked, expired or over-cap agent gets a 403 with the reason before you've done any work.
|
|
50
50
|
|
|
51
|
+
**One payment, one response.** Pass `claim` so a payment can't be replayed: it receives the payment's tx hash and
|
|
52
|
+
returns `false` if you've seen it before (a unique row in your database is enough). Payments older than
|
|
53
|
+
`maxPaymentAgeSeconds` (default 300) are refused either way, and so are payments in any token outside
|
|
54
|
+
`acceptTokens` (default: USDC and the Leash test dollar), because a leash can name any ERC-20.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const g = await leashGate(req, { seller: MY_ADDRESS, priceUsd: "1", claim: (tx) => db.insertIfNew("payments", tx) });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Full examples, each runnable: [Express](../examples/express-seller.mjs), [Next.js route](../examples/next-route.ts),
|
|
61
|
+
[check only](../examples/check-before-serving.mjs), [agent side](../examples/agent-pays.mjs).
|
|
62
|
+
|
|
51
63
|
## Pay (agent)
|
|
52
64
|
|
|
53
65
|
```ts
|
|
@@ -61,6 +73,14 @@ const res = await fetchWithLeash("https://leash-monad.vercel.app/api/forecast",
|
|
|
61
73
|
On a 402 it pays inside the leash and retries once. If the leash says no, the payment reverts on-chain and
|
|
62
74
|
nothing leaves the account.
|
|
63
75
|
|
|
76
|
+
## Getting a leashed agent
|
|
77
|
+
|
|
78
|
+
1. `npx leash-monad-mcp --new-key` prints a fresh agent address and key (or use any key your agent already has).
|
|
79
|
+
2. Open [leash-monad.vercel.app/own.html](https://leash-monad.vercel.app/own.html), sign in with a passkey and leash
|
|
80
|
+
that address: daily cap, sellers, expiry, demo dollars or USDC.
|
|
81
|
+
3. Use the key with `fetchWithLeash` / `payWithLeash`, or give it to an MCP client through
|
|
82
|
+
[`leash-monad-mcp`](https://www.npmjs.com/package/leash-monad-mcp).
|
|
83
|
+
|
|
64
84
|
## Networks
|
|
65
85
|
|
|
66
86
|
| | Chain | LeashHub |
|
package/dist/index.cjs
CHANGED
|
@@ -43,7 +43,8 @@ var networks = {
|
|
|
43
43
|
explorer: "https://monadvision.com",
|
|
44
44
|
hub: "0x64a489074dd6a4b3b977e5f635a178366a8c12c3",
|
|
45
45
|
identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
|
|
46
|
-
testUsd: "0x4adf40e6e5113339635e6dc54ff638e7b63bbea3"
|
|
46
|
+
testUsd: "0x4adf40e6e5113339635e6dc54ff638e7b63bbea3",
|
|
47
|
+
usdc: "0x754704Bc059F8C67012fEd69BC8A327a5aafb603"
|
|
47
48
|
},
|
|
48
49
|
testnet: {
|
|
49
50
|
name: "Monad Testnet",
|
|
@@ -135,6 +136,10 @@ async function leashGate(req, cfg) {
|
|
|
135
136
|
if (ev.eventName !== "Paid") continue;
|
|
136
137
|
const a = ev.args;
|
|
137
138
|
const account = await client.readContract({ address: net(cfg).hub, abi: hubAbi, functionName: "accountOf", args: [agent] });
|
|
139
|
+
const accepted = (cfg.acceptTokens ?? [net(cfg).usdc, net(cfg).testUsd]).filter(Boolean).map((t) => t.toLowerCase());
|
|
140
|
+
if (!accepted.includes(a.token.toLowerCase())) {
|
|
141
|
+
return { allow: false, status: 402, body: { error: "token_not_accepted", ref, reason: "That payment was in a token this seller doesn't accept." } };
|
|
142
|
+
}
|
|
138
143
|
if (log.address.toLowerCase() === account.toLowerCase() && a.agent.toLowerCase() === agent.toLowerCase() && a.seller.toLowerCase() === cfg.seller.toLowerCase() && a.amount >= need && a.ref === ref) {
|
|
139
144
|
if (cfg.claim && !await cfg.claim(payment)) {
|
|
140
145
|
return { allow: false, status: 402, body: { error: "payment_used", ref, reason: "That payment was already used. Pay for this request." } };
|
package/dist/index.d.ts
CHANGED
|
@@ -69,6 +69,9 @@ export type GateConfig = {
|
|
|
69
69
|
/** Record a payment as used; return false if it was already used. Plug in your store (Redis, a DB row) so one
|
|
70
70
|
* payment buys exactly one response. Without it, a payment can be replayed until it is maxPaymentAgeSeconds old. */
|
|
71
71
|
claim?: (tx: Hex) => boolean | Promise<boolean>;
|
|
72
|
+
/** Tokens you accept as payment. Default: the network's USDC and the Leash test dollar. A leash can name any
|
|
73
|
+
* ERC-20, so without this check an agent could "pay" in a token worth nothing. */
|
|
74
|
+
acceptTokens?: Address[];
|
|
72
75
|
} & Options;
|
|
73
76
|
export declare function leashGate(req: GateRequest, cfg: GateConfig): Promise<GateResult>;
|
|
74
77
|
/** Pay a seller from inside the leash. Reverts on-chain (and throws here) if it would break the leash. */
|
package/dist/index.mjs
CHANGED
|
@@ -20,7 +20,8 @@ var networks = {
|
|
|
20
20
|
explorer: "https://monadvision.com",
|
|
21
21
|
hub: "0x64a489074dd6a4b3b977e5f635a178366a8c12c3",
|
|
22
22
|
identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
|
|
23
|
-
testUsd: "0x4adf40e6e5113339635e6dc54ff638e7b63bbea3"
|
|
23
|
+
testUsd: "0x4adf40e6e5113339635e6dc54ff638e7b63bbea3",
|
|
24
|
+
usdc: "0x754704Bc059F8C67012fEd69BC8A327a5aafb603"
|
|
24
25
|
},
|
|
25
26
|
testnet: {
|
|
26
27
|
name: "Monad Testnet",
|
|
@@ -112,6 +113,10 @@ async function leashGate(req, cfg) {
|
|
|
112
113
|
if (ev.eventName !== "Paid") continue;
|
|
113
114
|
const a = ev.args;
|
|
114
115
|
const account = await client.readContract({ address: net(cfg).hub, abi: hubAbi, functionName: "accountOf", args: [agent] });
|
|
116
|
+
const accepted = (cfg.acceptTokens ?? [net(cfg).usdc, net(cfg).testUsd]).filter(Boolean).map((t) => t.toLowerCase());
|
|
117
|
+
if (!accepted.includes(a.token.toLowerCase())) {
|
|
118
|
+
return { allow: false, status: 402, body: { error: "token_not_accepted", ref, reason: "That payment was in a token this seller doesn't accept." } };
|
|
119
|
+
}
|
|
115
120
|
if (log.address.toLowerCase() === account.toLowerCase() && a.agent.toLowerCase() === agent.toLowerCase() && a.seller.toLowerCase() === cfg.seller.toLowerCase() && a.amount >= need && a.ref === ref) {
|
|
116
121
|
if (cfg.claim && !await cfg.claim(payment)) {
|
|
117
122
|
return { allow: false, status: 402, body: { error: "payment_used", ref, reason: "That payment was already used. Pay for this request." } };
|
package/dist/networks.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export type LeashNetwork = {
|
|
|
7
7
|
hub: Address;
|
|
8
8
|
identityRegistry: Address;
|
|
9
9
|
testUsd?: Address;
|
|
10
|
+
/** Circle's USDC on this network, when there is one. */
|
|
11
|
+
usdc?: Address;
|
|
10
12
|
};
|
|
11
13
|
/** Live deployments. Mainnet is the default everywhere in this package. */
|
|
12
14
|
export declare const networks: {
|
|
@@ -18,6 +20,7 @@ export declare const networks: {
|
|
|
18
20
|
hub: "0x64a489074dd6a4b3b977e5f635a178366a8c12c3";
|
|
19
21
|
identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432";
|
|
20
22
|
testUsd: "0x4adf40e6e5113339635e6dc54ff638e7b63bbea3";
|
|
23
|
+
usdc: "0x754704Bc059F8C67012fEd69BC8A327a5aafb603";
|
|
21
24
|
};
|
|
22
25
|
testnet: {
|
|
23
26
|
name: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leash-monad",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Spend limits for AI agents on Monad. One call tells a seller whose agent it is, what it may spend today, and whether a payment will go through.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Robert Moore",
|