wayleave 0.3.0 → 0.4.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/README.md +44 -10
- package/cdp-auth.js +35 -0
- package/index.d.ts +10 -2
- package/index.js +26 -6
- package/package.json +5 -3
- package/x402.d.ts +8 -1
- package/x402.js +60 -6
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/wayleave)
|
|
5
5
|
[](https://www.npmjs.com/package/wayleave)
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Observe agent traffic. Control access. Charge payment-ready agents.**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Wayleave is open-source middleware for request classification, signed-agent verification, route-level policies and pricing. The [hosted Meter](https://meter.wayleave.dev) records traffic and settlement activity.
|
|
10
10
|
|
|
11
11
|
Zero dependencies. Node's native crypto only. TypeScript declarations included.
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ Zero dependencies. Node's native crypto only. TypeScript declarations included.
|
|
|
14
14
|
|
|
15
15
|
Every request gets classified into a lane:
|
|
16
16
|
|
|
17
|
-
- **verified_agent** — valid Ed25519
|
|
17
|
+
- **verified_agent** — valid Ed25519 HTTP Message Signature (RFC 9421, compatible with emerging Web Bot Auth) against a key directory you trust
|
|
18
18
|
- **declared_agent** — identifies as a bot, no valid signature
|
|
19
19
|
- **suspected_bot** — automation fingerprints without disclosure (an *invalid* signature lands here too — faking verification is the strongest fraud signal there is)
|
|
20
20
|
- **human** — browser-shaped traffic, which means *nothing gave it away*, not that a person is there
|
|
@@ -23,7 +23,40 @@ Then policy runs per lane: allow, deny, rate-limit — and on routes you price,
|
|
|
23
23
|
|
|
24
24
|
Only the first lane is cryptographic. The other three are read off what the client says about itself, so a bot willing to lie reaches the human lane — see [Guarantees](#guarantees-honestly-stated), and set `strictPricedPaths` on anything you charge for.
|
|
25
25
|
|
|
26
|
-
## Quickstart
|
|
26
|
+
## Quickstart: observe first
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install wayleave
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
[Create a Meter account](https://meter.wayleave.dev/account.html), issue an API key,
|
|
33
|
+
and set `WAYLEAVE_METER_KEY` in your server environment. Signing in does not
|
|
34
|
+
start billing. The optional hosted Meter trial requires a card and becomes
|
|
35
|
+
$49/month after 3 days unless canceled; see your account for current terms.
|
|
36
|
+
|
|
37
|
+
In your existing Express app:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
import Wayleave from 'wayleave';
|
|
41
|
+
|
|
42
|
+
if (!process.env.WAYLEAVE_METER_KEY) {
|
|
43
|
+
throw new Error('Set WAYLEAVE_METER_KEY before starting the app');
|
|
44
|
+
}
|
|
45
|
+
const gate = new Wayleave({
|
|
46
|
+
meter: { apiKey: process.env.WAYLEAVE_METER_KEY },
|
|
47
|
+
});
|
|
48
|
+
app.use(gate.express()); // before the routes you want to observe
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Send a request to your app, allow the buffer to flush, then open the
|
|
52
|
+
[Meter dashboard](https://meter.wayleave.dev/app.html). No pricing, blocking,
|
|
53
|
+
or rate limits are configured in this example. Drain `gate.sink.close()`
|
|
54
|
+
during your application's graceful shutdown. Keep the key server-side.
|
|
55
|
+
|
|
56
|
+
The middleware also works without an account: omit `meter` and use `onEvent`
|
|
57
|
+
to send events to your own backend. See [integration docs](https://meter.wayleave.dev/docs.html).
|
|
58
|
+
|
|
59
|
+
## Add access rules and payments
|
|
27
60
|
|
|
28
61
|
```js
|
|
29
62
|
import Wayleave from 'wayleave';
|
|
@@ -58,8 +91,9 @@ rather than quietly denying every payment.
|
|
|
58
91
|
## Guarantees, honestly stated
|
|
59
92
|
|
|
60
93
|
- Signature verification is real Ed25519 over an RFC 9421 signature base — forged keys, tampered requests, expired signatures, and replay-farming windows are all rejected. Tested adversarially, and against other implementations' wire formats rather than only its own.
|
|
94
|
+
- A signature is single-use only when the signer sends a nonce. `signRequest(..., nonce)` will issue one (`true` for random, or supply your own) and the gate then refuses a repeat. Without a nonce there is nothing to remember a request by, and a captured signature stays usable until it expires — on any path of the host, since the Web Bot Auth profile covers only `@authority`. Opt in when you sign per attempt; leave it off if your client retries by resending the same headers.
|
|
61
95
|
- Signature parameters are parsed as an RFC 9421 dictionary: order-independent, `alg` enforced as Ed25519, any signature label, and the signature base is built from whatever components the signer declared. Requests signed in Cloudflare's documented format verify.
|
|
62
|
-
-
|
|
96
|
+
- Local verification is benchmarked by the tests; latency depends on your hardware and configuration. Payment-provider network calls add latency.
|
|
63
97
|
- The metering hook can throw, crash, or hang your billing backend — serving continues. Your uptime never depends on ours.
|
|
64
98
|
- Key rotation has a seam but no implementation. `directories` accepts a resolver function you can point at a cache, but there is no JWKS fetcher in the box and the resolver must not block. Wiring that cache is still your job.
|
|
65
99
|
- **A bot that sends a browser `user-agent` and an `accept-language` header is classified `human` and crosses priced routes free.** Those two headers are the entire bypass. The `human` lane is a fall-through — it is reached by tripping none of the automation tells, which is absence of evidence, not evidence of a person. There is no TLS fingerprinting and no challenge here; that work belongs at your edge or CDN, and pretending otherwise would be the dishonest version of this list. (`verifyAgentIP` checks a *declared* operator against its published ranges — it does nothing about a request claiming to be a browser, which is this bypass.) `strictPricedPaths: true` inverts the burden on priced routes so that only a verified signature or your own `confirmHuman(req)` crosses. Recommended wherever there is a price.
|
|
@@ -85,8 +119,8 @@ Pay-at-the-door, not IOUs. On a route you price:
|
|
|
85
119
|
2. A wallet-carrying agent signs payment and retries with proof attached.
|
|
86
120
|
3. The proof is verified and settlement executes on a licensed rail
|
|
87
121
|
(x402 facilitator) — money moves to your account.
|
|
88
|
-
4.
|
|
89
|
-
|
|
122
|
+
4. Access can be released after payment verification. Your verifier must
|
|
123
|
+
check the payment with the configured provider before returning success.
|
|
90
124
|
|
|
91
125
|
Agents without wallets (most crawlers today) are simply turned away on
|
|
92
126
|
priced routes — you aren't paid by them, but you also never serve them
|
|
@@ -94,9 +128,9 @@ free. The ledger shows exactly how much turned-away demand is standing
|
|
|
94
128
|
at your gate.
|
|
95
129
|
|
|
96
130
|
Wayleave never holds funds. Agent money flows agent → facilitator → you.
|
|
97
|
-
The hosted meter
|
|
98
|
-
|
|
99
|
-
you
|
|
131
|
+
The [hosted Meter](https://meter.wayleave.dev) records crossings and confirmed
|
|
132
|
+
settlement activity. The 402 challenge is built in; payment verification is
|
|
133
|
+
a function you configure. A Meter account does not configure a payment rail.
|
|
100
134
|
|
|
101
135
|
**Step 3 is yours, and it defaults to no.** `verifyPayment(proof, ctx)` is
|
|
102
136
|
the only thing that can turn a 402 into a 200. Configure nothing and every
|
package/cdp-auth.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createPrivateKey, randomBytes, sign } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
/** Short-lived, request-bound CDP bearer token. The secret never leaves this process.
|
|
4
|
+
* https://docs.cdp.coinbase.com/api-reference/v2/authentication
|
|
5
|
+
*/
|
|
6
|
+
export function cdpBearerToken({ apiKeyId, apiKeySecret, url, now = Math.floor(Date.now() / 1000) }) {
|
|
7
|
+
if (!apiKeyId || !apiKeySecret) throw new Error('CDP credentials are required');
|
|
8
|
+
const target = new URL(url);
|
|
9
|
+
if (target.protocol !== 'https:' || target.username || target.password || target.hash)
|
|
10
|
+
throw new Error('CDP endpoint must be an HTTPS URL without credentials or fragment');
|
|
11
|
+
let key;
|
|
12
|
+
try {
|
|
13
|
+
if (apiKeySecret.includes('-----BEGIN')) {
|
|
14
|
+
key = createPrivateKey(apiKeySecret.replaceAll('\\n', '\n'));
|
|
15
|
+
} else {
|
|
16
|
+
const raw = Buffer.from(apiKeySecret, 'base64');
|
|
17
|
+
if (raw.length !== 64) throw new Error('Invalid length');
|
|
18
|
+
key = createPrivateKey({ key: Buffer.concat([
|
|
19
|
+
Buffer.from('302e020100300506032b657004220420', 'hex'), raw.subarray(0, 32),
|
|
20
|
+
]), format: 'der', type: 'pkcs8' });
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
throw new Error('Invalid CDP signing key; use the Secret API Key, not a client or wallet key');
|
|
24
|
+
}
|
|
25
|
+
const ed = key.asymmetricKeyType === 'ed25519';
|
|
26
|
+
if (!ed && !(key.asymmetricKeyType === 'ec' && key.asymmetricKeyDetails?.namedCurve === 'prime256v1'))
|
|
27
|
+
throw new Error('CDP signing key must be Ed25519 or P-256');
|
|
28
|
+
const encode = value => Buffer.from(JSON.stringify(value)).toString('base64url');
|
|
29
|
+
const header = encode({ alg: ed ? 'EdDSA' : 'ES256', typ: 'JWT', kid: apiKeyId, nonce: randomBytes(16).toString('hex') });
|
|
30
|
+
const claims = encode({ sub: apiKeyId, iss: 'cdp', aud: ['cdp_service'], nbf: now, exp: now + 120,
|
|
31
|
+
uri: `POST ${target.host}${target.pathname}` });
|
|
32
|
+
const input = `${header}.${claims}`;
|
|
33
|
+
const signature = sign(ed ? null : 'sha256', Buffer.from(input), ed ? key : { key, dsaEncoding: 'ieee-p1363' });
|
|
34
|
+
return `${input}.${signature.toString('base64url')}`;
|
|
35
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -328,7 +328,9 @@ export interface WayleaveOptions {
|
|
|
328
328
|
export function buildParams(
|
|
329
329
|
keyid: string,
|
|
330
330
|
created: number,
|
|
331
|
-
expires: number
|
|
331
|
+
expires: number,
|
|
332
|
+
/** Appended as `;nonce="..."` when present. */
|
|
333
|
+
nonce?: string | null
|
|
332
334
|
): string;
|
|
333
335
|
|
|
334
336
|
/**
|
|
@@ -342,7 +344,13 @@ export function signRequest(
|
|
|
342
344
|
keyid: string,
|
|
343
345
|
directoryUrl: string,
|
|
344
346
|
created?: number,
|
|
345
|
-
expires?: number
|
|
347
|
+
expires?: number,
|
|
348
|
+
/**
|
|
349
|
+
* Opt in to a single-use signature. `true` generates 16 random bytes; a
|
|
350
|
+
* string is used verbatim. Omit it if your client retries by resending the
|
|
351
|
+
* same headers — a nonce turns that retry into a rejected replay.
|
|
352
|
+
*/
|
|
353
|
+
nonce?: string | boolean | null
|
|
346
354
|
): Record<string, string>;
|
|
347
355
|
|
|
348
356
|
/** Verify a presented signature against your configured directories. */
|
package/index.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { createPublicKey, verify as edVerify, sign as edSign,
|
|
21
|
-
createHash, randomUUID } from 'node:crypto';
|
|
21
|
+
createHash, randomUUID, randomBytes } from 'node:crypto';
|
|
22
22
|
// Same package, zero dependencies, ~9KB. Importing it unconditionally costs
|
|
23
23
|
// nothing measurable and makes `meter: { apiKey }` work without ceremony.
|
|
24
24
|
import { MeterSink } from './meter.js';
|
|
@@ -91,18 +91,38 @@ function sigBase(components, ctx, paramsRaw) {
|
|
|
91
91
|
return Buffer.from(lines.join('\n'));
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export function buildParams(keyid, created, expires) {
|
|
94
|
+
export function buildParams(keyid, created, expires, nonce = null) {
|
|
95
95
|
return `("@authority" "signature-agent");created=${created};` +
|
|
96
|
-
`keyid="${keyid}";alg="ed25519";expires=${expires};tag="web-bot-auth"
|
|
96
|
+
`keyid="${keyid}";alg="ed25519";expires=${expires};tag="web-bot-auth"` +
|
|
97
|
+
(nonce ? `;nonce="${nonce}"` : '');
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* What a legitimate agent operator's SDK does before sending.
|
|
102
|
+
*
|
|
103
|
+
* `nonce` is opt-in, and opting in is what makes a signature single-use.
|
|
104
|
+
* Without one, `verifySignature` has nothing to remember a request by, and
|
|
105
|
+
* `replayProtection` on the verifying side is inert however it is configured
|
|
106
|
+
* — the Web Bot Auth profile covers only `@authority`, so the same signature
|
|
107
|
+
* is valid for every path on the host until it expires.
|
|
108
|
+
*
|
|
109
|
+
* It is not the default because it changes what a retry means. An agent that
|
|
110
|
+
* signs once and resends those exact headers after a socket error is doing
|
|
111
|
+
* something reasonable, and a nonce turns that retry into a rejected replay.
|
|
112
|
+
* Callers that re-sign per attempt should pass `true`; callers that retry with
|
|
113
|
+
* held headers should not.
|
|
114
|
+
*
|
|
115
|
+
* Pass a string to supply your own, or `true` for 16 random bytes.
|
|
116
|
+
*/
|
|
100
117
|
export function signRequest(headers, authority, privateKey, keyid, directoryUrl,
|
|
101
118
|
created = Math.floor(Date.now() / 1000),
|
|
102
|
-
expires = created + 300
|
|
119
|
+
expires = created + 300,
|
|
120
|
+
nonce = null) {
|
|
103
121
|
// Structured-field string: the value is sent quoted.
|
|
104
122
|
headers['signature-agent'] = `"${directoryUrl}"`;
|
|
105
|
-
const
|
|
123
|
+
const n = nonce === true ? randomBytes(16).toString('base64url')
|
|
124
|
+
: (typeof nonce === 'string' && nonce ? nonce : null);
|
|
125
|
+
const params = buildParams(keyid, created, expires, n);
|
|
106
126
|
const base = sigBase(['@authority', 'signature-agent'],
|
|
107
127
|
{ authority, headers }, params);
|
|
108
128
|
const sig = edSign(null, base, privateKey);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wayleave",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Charge AI agents for passage across your app. Verify agent signatures (Web Bot Auth / Ed25519), classify every request, and return 402 with a price on routes you choose. Humans browse free.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"meter.d.ts",
|
|
28
28
|
"meter.js",
|
|
29
29
|
"x402.d.ts",
|
|
30
|
-
"x402.js"
|
|
30
|
+
"x402.js",
|
|
31
|
+
"cdp-auth.js"
|
|
31
32
|
],
|
|
32
33
|
"scripts": {
|
|
33
34
|
"test": "node --test *.test.js"
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
"ed25519"
|
|
50
51
|
],
|
|
51
52
|
"license": "MIT",
|
|
53
|
+
"author": "Gibran Corbin (https://github.com/gibrancorbin11-hub)",
|
|
52
54
|
"repository": {
|
|
53
55
|
"type": "git",
|
|
54
56
|
"url": "git+https://github.com/gibrancorbin11-hub/wayleave.git"
|
|
@@ -56,5 +58,5 @@
|
|
|
56
58
|
"bugs": {
|
|
57
59
|
"url": "https://github.com/gibrancorbin11-hub/wayleave/issues"
|
|
58
60
|
},
|
|
59
|
-
"homepage": "https://
|
|
61
|
+
"homepage": "https://wayleave.dev"
|
|
60
62
|
}
|
package/x402.d.ts
CHANGED
|
@@ -16,9 +16,16 @@ export interface CoinbaseFacilitatorOptions {
|
|
|
16
16
|
asset?: string;
|
|
17
17
|
/** Asset decimals. 6 for USDC. */
|
|
18
18
|
decimals?: number;
|
|
19
|
+
/**
|
|
20
|
+
* The EIP-712 domain the payer signed against, as the token contract
|
|
21
|
+
* reports it. Defaults are known for `base` and `base-sepolia`; supply it
|
|
22
|
+
* for any other network. Required by the facilitator — without it a payment
|
|
23
|
+
* is read, the payer identified, and the signature never checked.
|
|
24
|
+
*/
|
|
25
|
+
extra?: { name: string; version: string };
|
|
19
26
|
/**
|
|
20
27
|
* true (default) submits the payment on-chain. false only verifies it,
|
|
21
|
-
* which is useful for a dry run but pays nobody.
|
|
28
|
+
* which is useful for a dry run but pays nobody and never grants access.
|
|
22
29
|
*/
|
|
23
30
|
settle?: boolean;
|
|
24
31
|
baseUrl?: string;
|
package/x402.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cdpBearerToken } from './cdp-auth.js';
|
|
1
2
|
/**
|
|
2
3
|
* wayleave/x402 — a ready-made `verifyPayment` for the Coinbase x402 rail.
|
|
3
4
|
*
|
|
@@ -55,6 +56,37 @@ export function toAtomicUnits(usd, decimals = DEFAULT_DECIMALS) {
|
|
|
55
56
|
return (whole + frac.padEnd(decimals, '0')).replace(/^0+(?=\d)/, '');
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* The settlement asset per network, and the EIP-712 domain its contract
|
|
61
|
+
* enforces.
|
|
62
|
+
*
|
|
63
|
+
* Both are REQUIRED by the facilitator and neither can be guessed:
|
|
64
|
+
*
|
|
65
|
+
* - Omit `asset` and CDP rejects the call outright ("x402V1PaymentRequirements
|
|
66
|
+
* requires 'asset'"). Until 0.4.1 this module omitted it whenever the
|
|
67
|
+
* caller did not pass one, which was the default. No default configuration
|
|
68
|
+
* could settle a payment.
|
|
69
|
+
* - Omit `extra` and CDP identifies the payer and then refuses with "missing
|
|
70
|
+
* EIP-712 domain parameters". The agent signs an EIP-3009 authorization
|
|
71
|
+
* against the token's own domain, and the facilitator has to be told which
|
|
72
|
+
* domain to check the signature against.
|
|
73
|
+
*
|
|
74
|
+
* The name genuinely differs per deployment -- Base mainnet USDC calls itself
|
|
75
|
+
* "USD Coin", the Sepolia test token calls itself "USDC" -- so one hardcoded
|
|
76
|
+
* value is wrong on the other chain. These were read from each contract and
|
|
77
|
+
* checked against its own DOMAIN_SEPARATOR.
|
|
78
|
+
*/
|
|
79
|
+
const KNOWN_ASSETS = {
|
|
80
|
+
'base': {
|
|
81
|
+
asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
82
|
+
extra: { name: 'USD Coin', version: '2' }, decimals: 6,
|
|
83
|
+
},
|
|
84
|
+
'base-sepolia': {
|
|
85
|
+
asset: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
86
|
+
extra: { name: 'USDC', version: '2' }, decimals: 6,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
58
90
|
/**
|
|
59
91
|
* Build a `verifyPayment(proof, ctx)` for the Coinbase x402 facilitator.
|
|
60
92
|
*
|
|
@@ -73,7 +105,7 @@ export function toAtomicUnits(usd, decimals = DEFAULT_DECIMALS) {
|
|
|
73
105
|
*/
|
|
74
106
|
export function coinbaseFacilitator({
|
|
75
107
|
apiKeyId, apiKeySecret, receivingAddress,
|
|
76
|
-
network = 'base', asset = null, decimals =
|
|
108
|
+
network = 'base', asset = null, decimals = null, extra = null,
|
|
77
109
|
settle = true, baseUrl = DEFAULT_BASE, onError = () => {}, fetch: f = fetch,
|
|
78
110
|
} = {}) {
|
|
79
111
|
if (!apiKeyId || !apiKeySecret)
|
|
@@ -81,6 +113,24 @@ export function coinbaseFacilitator({
|
|
|
81
113
|
if (!receivingAddress)
|
|
82
114
|
throw new Error('coinbaseFacilitator requires receivingAddress — the address YOU get paid at');
|
|
83
115
|
|
|
116
|
+
const known = KNOWN_ASSETS[network];
|
|
117
|
+
const resolvedAsset = asset ?? known?.asset ?? null;
|
|
118
|
+
const resolvedExtra = extra ?? known?.extra ?? null;
|
|
119
|
+
const resolvedDecimals = decimals ?? known?.decimals ?? DEFAULT_DECIMALS;
|
|
120
|
+
|
|
121
|
+
// Fail here rather than on the first payment. A facilitator that cannot
|
|
122
|
+
// possibly settle should not be constructible: the alternative is a service
|
|
123
|
+
// that looks configured, passes its own tests, and denies every real payment
|
|
124
|
+
// with a 400 from someone else's API.
|
|
125
|
+
if (!resolvedAsset)
|
|
126
|
+
throw new Error(
|
|
127
|
+
`coinbaseFacilitator: no settlement asset known for network "${network}" — ` +
|
|
128
|
+
'pass asset: "0x..." (the token contract payment settles in)');
|
|
129
|
+
if (!resolvedExtra?.name || !resolvedExtra?.version)
|
|
130
|
+
throw new Error(
|
|
131
|
+
`coinbaseFacilitator: no EIP-712 domain known for network "${network}" — ` +
|
|
132
|
+
'pass extra: { name, version } as the token contract reports them');
|
|
133
|
+
|
|
84
134
|
const base = baseUrl.replace(/\/$/, '');
|
|
85
135
|
|
|
86
136
|
return async function verifyPayment(proof, ctx) {
|
|
@@ -103,13 +153,16 @@ export function coinbaseFacilitator({
|
|
|
103
153
|
// matches. Under the exact scheme the transfer MUST equal this and
|
|
104
154
|
// MUST land at payTo, so a wrong amount or wrong address cannot come
|
|
105
155
|
// back valid. That is stronger than trusting a number the payer sent.
|
|
106
|
-
maxAmountRequired: toAtomicUnits(ctx.price,
|
|
156
|
+
maxAmountRequired: toAtomicUnits(ctx.price, resolvedDecimals),
|
|
107
157
|
resource: ctx.resource ?? ctx.path ?? '',
|
|
108
158
|
description: '',
|
|
109
159
|
mimeType: 'application/json',
|
|
110
160
|
payTo: receivingAddress,
|
|
111
161
|
maxTimeoutSeconds: 60,
|
|
112
|
-
|
|
162
|
+
asset: resolvedAsset,
|
|
163
|
+
// The domain the payer signed against. Without it the facilitator can
|
|
164
|
+
// read the payload and still not check the signature.
|
|
165
|
+
extra: resolvedExtra,
|
|
113
166
|
};
|
|
114
167
|
} catch (err) {
|
|
115
168
|
return deny(`cannot build payment requirements: ${err.message}`);
|
|
@@ -148,7 +201,9 @@ export function coinbaseFacilitator({
|
|
|
148
201
|
|
|
149
202
|
if (d.isValid !== true)
|
|
150
203
|
return deny(`payment not valid: ${d.invalidReason ?? 'no reason given'}`);
|
|
151
|
-
|
|
204
|
+
// A valid authorization is not settled money. Do not release a priced
|
|
205
|
+
// resource or record billed revenue from a verification-only dry run.
|
|
206
|
+
return { ok: false, reason: 'Payment verified only; settlement is disabled' };
|
|
152
207
|
};
|
|
153
208
|
}
|
|
154
209
|
|
|
@@ -160,8 +215,7 @@ async function post(f, url, body, keyId, keySecret) {
|
|
|
160
215
|
method: 'POST',
|
|
161
216
|
headers: {
|
|
162
217
|
'content-type': 'application/json',
|
|
163
|
-
|
|
164
|
-
'cb-access-secret': keySecret,
|
|
218
|
+
authorization: `Bearer ${cdpBearerToken({ apiKeyId: keyId, apiKeySecret: keySecret, url })}`,
|
|
165
219
|
},
|
|
166
220
|
body: JSON.stringify(body),
|
|
167
221
|
signal: ctrl.signal,
|