leash-monad 0.1.1 → 0.1.2

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/dist/index.cjs CHANGED
@@ -124,6 +124,10 @@ async function leashGate(req, cfg) {
124
124
  const client = clientFor(cfg);
125
125
  const rc = await client.getTransactionReceipt({ hash: payment }).catch(() => null);
126
126
  if (!rc || rc.status !== "success") return { allow: false, status: 402, body: { error: "payment_not_found", ref } };
127
+ const mined = await client.getBlock({ blockNumber: rc.blockNumber });
128
+ if (Date.now() / 1e3 - Number(mined.timestamp) > (cfg.maxPaymentAgeSeconds ?? 300)) {
129
+ return { allow: false, status: 402, body: { error: "payment_expired", ref, reason: "That payment is too old to use again. Pay for this request." } };
130
+ }
127
131
  const d = cfg.decimals ?? 6, need = (0, import_viem2.parseUnits)(cfg.priceUsd, d);
128
132
  for (const log of rc.logs) {
129
133
  try {
@@ -132,6 +136,9 @@ async function leashGate(req, cfg) {
132
136
  const a = ev.args;
133
137
  const account = await client.readContract({ address: net(cfg).hub, abi: hubAbi, functionName: "accountOf", args: [agent] });
134
138
  if (log.address.toLowerCase() === account.toLowerCase() && a.agent.toLowerCase() === agent.toLowerCase() && a.seller.toLowerCase() === cfg.seller.toLowerCase() && a.amount >= need && a.ref === ref) {
139
+ if (cfg.claim && !await cfg.claim(payment)) {
140
+ return { allow: false, status: 402, body: { error: "payment_used", ref, reason: "That payment was already used. Pay for this request." } };
141
+ }
135
142
  const verdict = await checkAgent(agent, cfg.seller, "0", cfg);
136
143
  return { allow: true, agent, paid: (0, import_viem2.formatUnits)(a.amount, d), tx: payment, verdict };
137
144
  }
package/dist/index.d.ts CHANGED
@@ -60,11 +60,17 @@ export type GateResult = {
60
60
  * agent pays through its Leash (one tx), then retries with x-leash-payment: <txHash>
61
61
  * 2nd request: the gate checks the receipt's Paid event (agent, seller, amount >= price, ref) -> allow
62
62
  */
63
- export declare function leashGate(req: GateRequest, cfg: {
63
+ export type GateConfig = {
64
64
  seller: Address;
65
65
  priceUsd: string;
66
66
  resource?: string;
67
- } & Options): Promise<GateResult>;
67
+ /** Refuse payments mined longer ago than this (default 300 s). Limits how long one payment proof stays usable. */
68
+ maxPaymentAgeSeconds?: number;
69
+ /** Record a payment as used; return false if it was already used. Plug in your store (Redis, a DB row) so one
70
+ * payment buys exactly one response. Without it, a payment can be replayed until it is maxPaymentAgeSeconds old. */
71
+ claim?: (tx: Hex) => boolean | Promise<boolean>;
72
+ } & Options;
73
+ export declare function leashGate(req: GateRequest, cfg: GateConfig): Promise<GateResult>;
68
74
  /** Pay a seller from inside the leash. Reverts on-chain (and throws here) if it would break the leash. */
69
75
  export declare function payWithLeash(agentAccount: Account, seller: Address, amountUsd: string, ref: Hex, o?: Options & {
70
76
  gas?: bigint;
package/dist/index.mjs CHANGED
@@ -101,6 +101,10 @@ async function leashGate(req, cfg) {
101
101
  const client = clientFor(cfg);
102
102
  const rc = await client.getTransactionReceipt({ hash: payment }).catch(() => null);
103
103
  if (!rc || rc.status !== "success") return { allow: false, status: 402, body: { error: "payment_not_found", ref } };
104
+ const mined = await client.getBlock({ blockNumber: rc.blockNumber });
105
+ if (Date.now() / 1e3 - Number(mined.timestamp) > (cfg.maxPaymentAgeSeconds ?? 300)) {
106
+ return { allow: false, status: 402, body: { error: "payment_expired", ref, reason: "That payment is too old to use again. Pay for this request." } };
107
+ }
104
108
  const d = cfg.decimals ?? 6, need = parseUnits(cfg.priceUsd, d);
105
109
  for (const log of rc.logs) {
106
110
  try {
@@ -109,6 +113,9 @@ async function leashGate(req, cfg) {
109
113
  const a = ev.args;
110
114
  const account = await client.readContract({ address: net(cfg).hub, abi: hubAbi, functionName: "accountOf", args: [agent] });
111
115
  if (log.address.toLowerCase() === account.toLowerCase() && a.agent.toLowerCase() === agent.toLowerCase() && a.seller.toLowerCase() === cfg.seller.toLowerCase() && a.amount >= need && a.ref === ref) {
116
+ if (cfg.claim && !await cfg.claim(payment)) {
117
+ return { allow: false, status: 402, body: { error: "payment_used", ref, reason: "That payment was already used. Pay for this request." } };
118
+ }
112
119
  const verdict = await checkAgent(agent, cfg.seller, "0", cfg);
113
120
  return { allow: true, agent, paid: formatUnits(a.amount, d), tx: payment, verdict };
114
121
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leash-monad",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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",