@ironbridgefoundation/gitlawb 0.1.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/LICENSE +21 -0
- package/README.md +99 -0
- package/index.js +316 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 IronBridge
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# @ironbridgefoundation/gitlawb
|
|
2
|
+
|
|
3
|
+
An x402 pay-rail SDK that bounds an agent's spend **below the LLM** and emits a
|
|
4
|
+
**re-verifiable** USDC payment receipt.
|
|
5
|
+
|
|
6
|
+
It builds the HTTP `402 Payment Required` descriptor for a paid call and a
|
|
7
|
+
matching receipt you can re-check. The payment is a single Base-mainnet USDC
|
|
8
|
+
charge split into two legs:
|
|
9
|
+
|
|
10
|
+
- **dev leg** → **your own wallet** (you pass it in)
|
|
11
|
+
- **fee leg** → the IronBridge treasury Safe (the cost of using the rail; `450`
|
|
12
|
+
bps by default, set `feeBps: 0` for a self-hosted / no-fee rail)
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @ironbridgefoundation/gitlawb
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
import { charge } from "@ironbridgefoundation/gitlawb";
|
|
24
|
+
|
|
25
|
+
// Point the payout at YOUR wallet — never an IronBridge address.
|
|
26
|
+
const gate = charge({
|
|
27
|
+
price: "0.10",
|
|
28
|
+
receiver: "0xYourWalletAddressHere0000000000000000000",
|
|
29
|
+
label: "lookup",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// gate.status === 402
|
|
33
|
+
// gate.body.splits === [
|
|
34
|
+
// { leg: "dev", to: "0xYourWallet…", usdc: "0.095500" },
|
|
35
|
+
// { leg: "ironbridge_fee", to: "0x5Bb0…1680", usdc: "0.004500", fee_bps: 450 },
|
|
36
|
+
// ]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Verify a receipt is internally consistent (splits sum to price, dev leg pays
|
|
40
|
+
the stated receiver):
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import { verifyReceipt } from "@ironbridgefoundation/gitlawb";
|
|
44
|
+
|
|
45
|
+
const { ok, reasons } = verifyReceipt(gate);
|
|
46
|
+
// ok === true
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Optional dependency-free Express-style gate:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { x402Middleware } from "@ironbridgefoundation/gitlawb";
|
|
53
|
+
|
|
54
|
+
app.get(
|
|
55
|
+
"/paid-route",
|
|
56
|
+
x402Middleware({
|
|
57
|
+
price: "0.10",
|
|
58
|
+
receiver: "0xYourWallet…",
|
|
59
|
+
label: "lookup",
|
|
60
|
+
// verify: async (req) => await yourLaneVerifier(req), // return true once paid
|
|
61
|
+
}),
|
|
62
|
+
handler
|
|
63
|
+
);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## What it does
|
|
67
|
+
|
|
68
|
+
- Computes the exact two-leg USDC split (6-decimal strings) the same way the
|
|
69
|
+
live GITLAWB lane does: `dev = price * (1 - feeBps/10000)`,
|
|
70
|
+
`fee = price * feeBps/10000`.
|
|
71
|
+
- Builds the `402` descriptor your server returns to require payment.
|
|
72
|
+
- Verifies receipt **structure and arithmetic** so you can re-check that the
|
|
73
|
+
math is honest.
|
|
74
|
+
|
|
75
|
+
## What it does NOT do
|
|
76
|
+
|
|
77
|
+
- It **holds no private keys** and **custodies no funds**.
|
|
78
|
+
- It makes **no network calls** inside `charge()` — it only builds the
|
|
79
|
+
descriptor and verifies receipt math.
|
|
80
|
+
- `verifyReceipt()` checks structure/arithmetic only. It does **not** and
|
|
81
|
+
**cannot** confirm on-chain settlement. To prove a payment actually cleared,
|
|
82
|
+
re-POST to the live lane with the on-chain tx hash and let the lane verifier
|
|
83
|
+
read the Base receipt:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
POST https://ironbridge.foundation/api/pay/gitlawb/<your-wallet>/<label>
|
|
87
|
+
X-IB-Payment-Tx: <base-tx-hash>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Constants (exported)
|
|
91
|
+
|
|
92
|
+
- `USDC_ADDRESS` — Base USDC `0x8335…2913`
|
|
93
|
+
- `IRONBRIDGE_FEE_RECEIVER` — IronBridge treasury Safe `0x5Bb0…1680`
|
|
94
|
+
- `CHAIN_ID` — `8453` (Base mainnet)
|
|
95
|
+
- `DEFAULT_FEE_BPS` — `450`
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT © 2026 IronBridge — https://ironbridge.foundation
|
package/index.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ironbridgefoundation/gitlawb
|
|
3
|
+
*
|
|
4
|
+
* x402 pay-rail SDK. It builds the HTTP 402 "Payment Required" descriptor for a
|
|
5
|
+
* paid agent call and a matching, re-verifiable receipt. The payment is a single
|
|
6
|
+
* Base-mainnet USDC transfer split into two legs:
|
|
7
|
+
*
|
|
8
|
+
* - dev leg -> YOUR own wallet (the `receiver` you pass in)
|
|
9
|
+
* - fee leg -> the IronBridge treasury Safe (the cost of using the rail)
|
|
10
|
+
*
|
|
11
|
+
* This package holds NO private keys, custodies NO funds, and makes NO network
|
|
12
|
+
* calls inside `charge()`. It only computes the descriptor + verifies receipt
|
|
13
|
+
* arithmetic. It NEVER defaults the dev leg to an IronBridge address: if you do
|
|
14
|
+
* not supply `receiver`, `charge()` throws.
|
|
15
|
+
*
|
|
16
|
+
* The split math mirrors the live lane verbatim:
|
|
17
|
+
* POST https://ironbridge.foundation/api/pay/gitlawb/<wallet>/<label>
|
|
18
|
+
* dev = price * (1 - feeBps/10000)
|
|
19
|
+
* fee = price * feeBps/10000
|
|
20
|
+
* amounts emitted as 6-decimal USDC strings.
|
|
21
|
+
*
|
|
22
|
+
* @module @ironbridgefoundation/gitlawb
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
'use strict';
|
|
26
|
+
|
|
27
|
+
/** Base mainnet USDC contract (6 decimals). For reference / integrators. */
|
|
28
|
+
export const USDC_ADDRESS = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913';
|
|
29
|
+
|
|
30
|
+
/** IronBridge treasury Safe — the fee (rail) leg recipient. Fixed, public, holds no key here. */
|
|
31
|
+
export const IRONBRIDGE_FEE_RECEIVER = '0x5Bb0a8A570F73eE0575043B8A9c33b28D6891680';
|
|
32
|
+
|
|
33
|
+
/** Base mainnet chain id. */
|
|
34
|
+
export const CHAIN_ID = 8453;
|
|
35
|
+
|
|
36
|
+
/** Default rail fee in basis points — matches the live GITLAWB lane (450 bps = 4.5%). */
|
|
37
|
+
export const DEFAULT_FEE_BPS = 450;
|
|
38
|
+
|
|
39
|
+
const ADDR_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Convert a USDC amount (string or number) to integer micro-units (6dp).
|
|
43
|
+
* Avoids float drift by string-parsing the decimal portion.
|
|
44
|
+
* @param {string|number} price
|
|
45
|
+
* @returns {bigint} micro-units (price * 1e6)
|
|
46
|
+
*/
|
|
47
|
+
export function toMicros(price) {
|
|
48
|
+
const s = String(price).trim();
|
|
49
|
+
if (!/^\d+(\.\d+)?$/.test(s)) {
|
|
50
|
+
throw new Error(`gitlawb: invalid price "${price}" (expected non-negative decimal USDC)`);
|
|
51
|
+
}
|
|
52
|
+
const [whole, frac = ''] = s.split('.');
|
|
53
|
+
if (frac.length > 6) {
|
|
54
|
+
throw new Error(`gitlawb: price "${price}" has more than 6 decimal places (USDC is 6dp)`);
|
|
55
|
+
}
|
|
56
|
+
const fracPadded = (frac + '000000').slice(0, 6);
|
|
57
|
+
return BigInt(whole) * 1000000n + BigInt(fracPadded || '0');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Format integer micro-units back to a fixed 6-decimal USDC string.
|
|
62
|
+
* @param {bigint} micros
|
|
63
|
+
* @returns {string} e.g. "0.095500"
|
|
64
|
+
*/
|
|
65
|
+
export function microsToUsdc(micros) {
|
|
66
|
+
const neg = micros < 0n;
|
|
67
|
+
const m = neg ? -micros : micros;
|
|
68
|
+
const whole = m / 1000000n;
|
|
69
|
+
const frac = (m % 1000000n).toString().padStart(6, '0');
|
|
70
|
+
return `${neg ? '-' : ''}${whole.toString()}.${frac}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Build the x402 payment-required descriptor for a paid call.
|
|
75
|
+
*
|
|
76
|
+
* @param {object} opts
|
|
77
|
+
* @param {string|number} opts.price USDC price for the call (e.g. "0.10").
|
|
78
|
+
* @param {string} opts.receiver YOUR developer wallet (0x… 40-hex). REQUIRED.
|
|
79
|
+
* The dev leg is paid here. Never defaults to IronBridge.
|
|
80
|
+
* @param {string} [opts.label] Free-form label for the call (notary/audit reference).
|
|
81
|
+
* @param {number} [opts.feeBps] Rail fee in bps. Default 450 (matches live lane).
|
|
82
|
+
* Pass 0 for a self-hosted / no-fee rail.
|
|
83
|
+
* @returns {{status: number, body: object}} HTTP 402 descriptor with two-leg split.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* import { charge } from '@ironbridgefoundation/gitlawb';
|
|
87
|
+
* const gate = charge({ price: '0.10', receiver: '0xYourWallet…', label: 'lookup' });
|
|
88
|
+
* // -> { status: 402, body: { ... splits: [dev, ironbridge_fee] } }
|
|
89
|
+
*/
|
|
90
|
+
export function charge({ price, receiver, label = '', feeBps = DEFAULT_FEE_BPS } = {}) {
|
|
91
|
+
if (receiver == null || receiver === '') {
|
|
92
|
+
throw new Error(
|
|
93
|
+
'gitlawb: `receiver` is required — pass YOUR own wallet address. ' +
|
|
94
|
+
'This SDK never defaults the dev payout to an IronBridge address.'
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (typeof receiver !== 'string' || !ADDR_RE.test(receiver)) {
|
|
98
|
+
throw new Error(`gitlawb: invalid receiver address "${receiver}" (expected 0x + 40 hex)`);
|
|
99
|
+
}
|
|
100
|
+
if (receiver.toLowerCase() === IRONBRIDGE_FEE_RECEIVER.toLowerCase()) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
'gitlawb: `receiver` must be YOUR wallet, not the IronBridge fee/Safe address.'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (!Number.isInteger(feeBps) || feeBps < 0 || feeBps > 10000) {
|
|
106
|
+
throw new Error(`gitlawb: invalid feeBps "${feeBps}" (expected integer 0..10000)`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const priceMicros = toMicros(price);
|
|
110
|
+
const feeMicros = (priceMicros * BigInt(feeBps)) / 10000n;
|
|
111
|
+
const devMicros = priceMicros - feeMicros;
|
|
112
|
+
|
|
113
|
+
const splits = [
|
|
114
|
+
{ leg: 'dev', to: receiver, usdc: microsToUsdc(devMicros) },
|
|
115
|
+
];
|
|
116
|
+
// Only include the fee leg when there is a fee (feeBps:0 == self-hosted/no-fee rail).
|
|
117
|
+
if (feeMicros > 0n) {
|
|
118
|
+
splits.push({
|
|
119
|
+
leg: 'ironbridge_fee',
|
|
120
|
+
to: IRONBRIDGE_FEE_RECEIVER,
|
|
121
|
+
usdc: microsToUsdc(feeMicros),
|
|
122
|
+
fee_bps: feeBps,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
status: 402,
|
|
128
|
+
body: {
|
|
129
|
+
error: 'Payment Required',
|
|
130
|
+
price_usdc: Number(microsToUsdc(priceMicros)),
|
|
131
|
+
network: 'base',
|
|
132
|
+
asset: 'USDC',
|
|
133
|
+
receiver,
|
|
134
|
+
label,
|
|
135
|
+
splits,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Re-verify a receipt (or the body of a `charge()` result) is internally
|
|
142
|
+
* consistent: the split legs sum exactly to the stated price and the dev leg
|
|
143
|
+
* pays the stated receiver.
|
|
144
|
+
*
|
|
145
|
+
* HONEST SCOPE: this checks STRUCTURE and ARITHMETIC only. It does NOT and
|
|
146
|
+
* CANNOT confirm on-chain settlement — to prove a payment actually cleared,
|
|
147
|
+
* re-POST to the live lane with the tx hash and let the lane verifier read the
|
|
148
|
+
* Base receipt. This helper is the "the math/shape is sound" check.
|
|
149
|
+
*
|
|
150
|
+
* @param {object} receipt Either a `charge()` return ({status, body}) or its `body`.
|
|
151
|
+
* @returns {{ok: boolean, reasons: string[]}} ok=true if consistent; reasons lists failures.
|
|
152
|
+
*/
|
|
153
|
+
export function verifyReceipt(receipt) {
|
|
154
|
+
const reasons = [];
|
|
155
|
+
if (receipt == null || typeof receipt !== 'object') {
|
|
156
|
+
return { ok: false, reasons: ['receipt is not an object'] };
|
|
157
|
+
}
|
|
158
|
+
const body = receipt.body && typeof receipt.body === 'object' ? receipt.body : receipt;
|
|
159
|
+
|
|
160
|
+
if (!Array.isArray(body.splits) || body.splits.length === 0) {
|
|
161
|
+
return { ok: false, reasons: ['receipt has no splits[]'] };
|
|
162
|
+
}
|
|
163
|
+
if (body.price_usdc == null) {
|
|
164
|
+
reasons.push('receipt missing price_usdc');
|
|
165
|
+
}
|
|
166
|
+
if (!body.receiver || !ADDR_RE.test(String(body.receiver))) {
|
|
167
|
+
reasons.push('receipt missing/invalid receiver');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let sumMicros = 0n;
|
|
171
|
+
let devLeg = null;
|
|
172
|
+
let feeLeg = null;
|
|
173
|
+
for (const s of body.splits) {
|
|
174
|
+
if (!s || typeof s.usdc !== 'string' || !s.to) {
|
|
175
|
+
reasons.push('a split is malformed (needs {leg,to,usdc})');
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
let m;
|
|
179
|
+
try {
|
|
180
|
+
m = toMicros(s.usdc);
|
|
181
|
+
} catch (e) {
|
|
182
|
+
reasons.push(`split ${s.leg}: ${e.message}`);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
sumMicros += m;
|
|
186
|
+
if (s.leg === 'dev') devLeg = s;
|
|
187
|
+
if (s.leg === 'ironbridge_fee') feeLeg = s;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!devLeg) {
|
|
191
|
+
reasons.push('no dev leg present');
|
|
192
|
+
} else if (body.receiver && devLeg.to.toLowerCase() !== String(body.receiver).toLowerCase()) {
|
|
193
|
+
reasons.push('dev leg recipient does not match receiver');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (feeLeg) {
|
|
197
|
+
if (feeLeg.to.toLowerCase() !== IRONBRIDGE_FEE_RECEIVER.toLowerCase()) {
|
|
198
|
+
reasons.push('fee leg recipient is not the IronBridge fee receiver');
|
|
199
|
+
}
|
|
200
|
+
if (feeLeg.fee_bps != null) {
|
|
201
|
+
// cross-check the fee leg amount against its declared bps
|
|
202
|
+
let priceM;
|
|
203
|
+
try {
|
|
204
|
+
priceM = toMicros(body.price_usdc);
|
|
205
|
+
const expectFee = (priceM * BigInt(feeLeg.fee_bps)) / 10000n;
|
|
206
|
+
if (toMicros(feeLeg.usdc) !== expectFee) {
|
|
207
|
+
reasons.push('fee leg amount does not match fee_bps * price');
|
|
208
|
+
}
|
|
209
|
+
} catch (e) {
|
|
210
|
+
reasons.push(`fee_bps cross-check failed: ${e.message}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (body.price_usdc != null) {
|
|
216
|
+
let priceMicros;
|
|
217
|
+
try {
|
|
218
|
+
priceMicros = toMicros(body.price_usdc);
|
|
219
|
+
if (sumMicros !== priceMicros) {
|
|
220
|
+
reasons.push(
|
|
221
|
+
`splits sum ${microsToUsdc(sumMicros)} != price_usdc ${microsToUsdc(priceMicros)}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
} catch (e) {
|
|
225
|
+
reasons.push(`price_usdc parse failed: ${e.message}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return { ok: reasons.length === 0, reasons };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Optional dependency-free Express-style middleware that gates a route behind
|
|
234
|
+
* the x402 descriptor. Until a payment proof is presented it responds 402 with
|
|
235
|
+
* the descriptor body. It does NOT verify on-chain settlement itself — wire the
|
|
236
|
+
* `verify` callback to the live lane (or your own verifier) for that.
|
|
237
|
+
*
|
|
238
|
+
* @param {object} opts Same shape as `charge()` opts (price, receiver, label, feeBps).
|
|
239
|
+
* @param {(req:any)=>(boolean|Promise<boolean>)} [opts.verify] Optional: return true if paid.
|
|
240
|
+
* @returns {(req:any,res:any,next:Function)=>void}
|
|
241
|
+
*/
|
|
242
|
+
export function x402Middleware(opts = {}) {
|
|
243
|
+
const { verify, ...chargeOpts } = opts;
|
|
244
|
+
return async function gitlawbGate(req, res, next) {
|
|
245
|
+
try {
|
|
246
|
+
if (typeof verify === 'function') {
|
|
247
|
+
const paid = await verify(req);
|
|
248
|
+
if (paid) return next();
|
|
249
|
+
}
|
|
250
|
+
const descriptor = charge(chargeOpts);
|
|
251
|
+
res.status(descriptor.status).json(descriptor.body);
|
|
252
|
+
} catch (e) {
|
|
253
|
+
// Surface config errors loudly rather than silently 402-ing.
|
|
254
|
+
res.status(500).json({ error: 'gitlawb config error', detail: e.message });
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export default { charge, verifyReceipt, x402Middleware, toMicros, microsToUsdc };
|
|
260
|
+
|
|
261
|
+
/* --------------------------------------------------------------------------
|
|
262
|
+
* Inline self-test: node index.js --self-test
|
|
263
|
+
* Proves the split math and receipt verifier against the live-lane numbers.
|
|
264
|
+
* No network, no keys, exits 0 on PASS / 1 on FAIL.
|
|
265
|
+
* ------------------------------------------------------------------------ */
|
|
266
|
+
if (import.meta.url === `file://${process.argv[1]}` && process.argv.includes('--self-test')) {
|
|
267
|
+
let failures = 0;
|
|
268
|
+
const assert = (cond, msg) => {
|
|
269
|
+
if (!cond) { failures++; console.error('FAIL:', msg); }
|
|
270
|
+
else { console.log('ok :', msg); }
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const DEV = '0x000000000000000000000000000000000000dEaD';
|
|
274
|
+
|
|
275
|
+
// 1) live-lane numbers: 0.10 USDC, 450 bps -> dev 0.095500, fee 0.004500
|
|
276
|
+
const r = charge({ price: '0.10', receiver: DEV, label: 'lookup' });
|
|
277
|
+
assert(r.status === 402, 'charge() returns status 402');
|
|
278
|
+
const dev = r.body.splits.find(s => s.leg === 'dev');
|
|
279
|
+
const fee = r.body.splits.find(s => s.leg === 'ironbridge_fee');
|
|
280
|
+
assert(dev && dev.usdc === '0.095500', `dev leg = 0.095500 (got ${dev && dev.usdc})`);
|
|
281
|
+
assert(fee && fee.usdc === '0.004500', `fee leg = 0.004500 (got ${fee && fee.usdc})`);
|
|
282
|
+
assert(fee && fee.to === IRONBRIDGE_FEE_RECEIVER, 'fee leg pays IronBridge Safe');
|
|
283
|
+
assert(dev && dev.to === DEV, 'dev leg pays the supplied receiver');
|
|
284
|
+
assert(fee && fee.fee_bps === 450, 'fee leg carries fee_bps 450');
|
|
285
|
+
const sum = toMicros(dev.usdc) + toMicros(fee.usdc);
|
|
286
|
+
assert(sum === 100000n, `legs sum to 0.100000 (got ${microsToUsdc(sum)})`);
|
|
287
|
+
|
|
288
|
+
// 2) verifyReceipt accepts the honest receipt
|
|
289
|
+
const v = verifyReceipt(r);
|
|
290
|
+
assert(v.ok === true, `verifyReceipt accepts honest receipt (reasons: ${v.reasons.join('; ')})`);
|
|
291
|
+
|
|
292
|
+
// 3) verifyReceipt rejects a tampered receipt (dev leg inflated)
|
|
293
|
+
const tampered = JSON.parse(JSON.stringify(r));
|
|
294
|
+
tampered.body.splits.find(s => s.leg === 'dev').usdc = '0.099999';
|
|
295
|
+
const vt = verifyReceipt(tampered);
|
|
296
|
+
assert(vt.ok === false, 'verifyReceipt rejects tampered (inflated dev) receipt');
|
|
297
|
+
|
|
298
|
+
// 4) receiver is required — never defaults to IronBridge
|
|
299
|
+
let threw = false;
|
|
300
|
+
try { charge({ price: '0.10' }); } catch (_) { threw = true; }
|
|
301
|
+
assert(threw, 'charge() throws when receiver is missing (no IronBridge default)');
|
|
302
|
+
|
|
303
|
+
// 5) feeBps:0 -> single dev leg, no fee leg
|
|
304
|
+
const noFee = charge({ price: '0.10', receiver: DEV, feeBps: 0 });
|
|
305
|
+
assert(noFee.body.splits.length === 1 && noFee.body.splits[0].leg === 'dev',
|
|
306
|
+
'feeBps:0 yields a single dev leg (self-hosted/no-fee)');
|
|
307
|
+
assert(verifyReceipt(noFee).ok === true, 'verifyReceipt accepts no-fee receipt');
|
|
308
|
+
|
|
309
|
+
// 6) refuse receiver == fee Safe
|
|
310
|
+
let threw2 = false;
|
|
311
|
+
try { charge({ price: '0.10', receiver: IRONBRIDGE_FEE_RECEIVER }); } catch (_) { threw2 = true; }
|
|
312
|
+
assert(threw2, 'charge() refuses receiver == IronBridge fee Safe');
|
|
313
|
+
|
|
314
|
+
console.log(failures === 0 ? '\nSELF-TEST PASS' : `\nSELF-TEST FAIL (${failures})`);
|
|
315
|
+
process.exit(failures === 0 ? 0 : 1);
|
|
316
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ironbridgefoundation/gitlawb",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "x402 pay-rail SDK that bounds an agent's spend below the LLM and emits a re-verifiable USDC payment receipt.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"module": "index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"index.js",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node index.js --self-test"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "IronBridge",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/ironbridge-foundation/ironbridge-gitlawb.git"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://ironbridge.foundation",
|
|
29
|
+
"keywords": [
|
|
30
|
+
"x402",
|
|
31
|
+
"payments",
|
|
32
|
+
"usdc",
|
|
33
|
+
"base",
|
|
34
|
+
"agent",
|
|
35
|
+
"pay-rail",
|
|
36
|
+
"receipt",
|
|
37
|
+
"gitlawb",
|
|
38
|
+
"ironbridge"
|
|
39
|
+
],
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
}
|
|
43
|
+
}
|