@naulon/wayfarer 0.1.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 +39 -0
- package/dist/agent.d.ts +77 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +235 -0
- package/dist/agent.js.map +1 -0
- package/dist/allocation.d.ts +36 -0
- package/dist/allocation.d.ts.map +1 -0
- package/dist/allocation.js +45 -0
- package/dist/allocation.js.map +1 -0
- package/dist/appraise.d.ts +3 -0
- package/dist/appraise.d.ts.map +1 -0
- package/dist/appraise.js +60 -0
- package/dist/appraise.js.map +1 -0
- package/dist/buyer.d.ts +195 -0
- package/dist/buyer.d.ts.map +1 -0
- package/dist/buyer.js +254 -0
- package/dist/buyer.js.map +1 -0
- package/dist/decide.d.ts +115 -0
- package/dist/decide.d.ts.map +1 -0
- package/dist/decide.js +206 -0
- package/dist/decide.js.map +1 -0
- package/dist/discover.d.ts +10 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +5 -0
- package/dist/discover.js.map +1 -0
- package/dist/discovery.d.ts +26 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +93 -0
- package/dist/discovery.js.map +1 -0
- package/dist/gateway.d.ts +117 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +187 -0
- package/dist/gateway.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +40 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +44 -0
- package/dist/lib.js.map +1 -0
- package/dist/licenseStore.d.ts +56 -0
- package/dist/licenseStore.d.ts.map +1 -0
- package/dist/licenseStore.js +79 -0
- package/dist/licenseStore.js.map +1 -0
- package/dist/memo.d.ts +43 -0
- package/dist/memo.d.ts.map +1 -0
- package/dist/memo.js +102 -0
- package/dist/memo.js.map +1 -0
- package/dist/origin-policy.d.ts +74 -0
- package/dist/origin-policy.d.ts.map +1 -0
- package/dist/origin-policy.js +88 -0
- package/dist/origin-policy.js.map +1 -0
- package/dist/paidFetch.d.ts +14 -0
- package/dist/paidFetch.d.ts.map +1 -0
- package/dist/paidFetch.js +93 -0
- package/dist/paidFetch.js.map +1 -0
- package/dist/pay.d.ts +3 -0
- package/dist/pay.d.ts.map +1 -0
- package/dist/pay.js +82 -0
- package/dist/pay.js.map +1 -0
- package/dist/pop.d.ts +8 -0
- package/dist/pop.d.ts.map +1 -0
- package/dist/pop.js +25 -0
- package/dist/pop.js.map +1 -0
- package/dist/rail.d.ts +18 -0
- package/dist/rail.d.ts.map +1 -0
- package/dist/rail.js +73 -0
- package/dist/rail.js.map +1 -0
- package/dist/rss.d.ts +38 -0
- package/dist/rss.d.ts.map +1 -0
- package/dist/rss.js +110 -0
- package/dist/rss.js.map +1 -0
- package/dist/sign.d.ts +13 -0
- package/dist/sign.d.ts.map +1 -0
- package/dist/sign.js +52 -0
- package/dist/sign.js.map +1 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/wallet.d.ts +12 -0
- package/dist/wallet.d.ts.map +1 -0
- package/dist/wallet.js +46 -0
- package/dist/wallet.js.map +1 -0
- package/package.json +42 -0
package/dist/gateway.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway buyer — the memo-LESS Circle rail (Base + every other Gateway chain). Where the
|
|
3
|
+
* memo rail (memo.ts) relays a raw USDC EIP-3009 authorization, the gateway rail signs an
|
|
4
|
+
* EIP-3009 authorization against the Circle **GatewayWallet** contract (the `extra.
|
|
5
|
+
* verifyingContract` the gate advertises) and posts the full x402 envelope `{x402Version,
|
|
6
|
+
* payload:{authorization,signature}, resource, accepted}` as `payment-signature` — the shape
|
|
7
|
+
* Circle's facilitator `verify` requires (a bare/mock shape is rejected 400
|
|
8
|
+
* `x402Version/resource/accepted/payload: Required` — the Base-settle bug this path fixes).
|
|
9
|
+
*
|
|
10
|
+
* Custody-free seam (mirrors `memoBuyer(MemoSigner)`): a cloud host injects a sign-only
|
|
11
|
+
* `GatewaySigner` (its address + a `signTypedData` that signs the GatewayWallet-domain typed
|
|
12
|
+
* data elsewhere — a grant-checked BFF holding the encrypted session key), so the private key
|
|
13
|
+
* never lives in this process. A viem `PrivateKeyAccount` satisfies the same shape, keeping
|
|
14
|
+
* the CLI/self-host path (env `BUYER_PRIVATE_KEY`) symmetric. The signing wraps the SDK's
|
|
15
|
+
* `BatchEvmScheme` so the signed shape / validity clamp can never drift from the rail (the
|
|
16
|
+
* same reason the gate's own `gatewayLegPayload` does — see tollgate/src/x402.ts).
|
|
17
|
+
*
|
|
18
|
+
* The Gateway balance is funded out-of-band (a one-time deposit into Circle's non-custodial
|
|
19
|
+
* Gateway Wallet); the pay path here is pure sign-only. On the env/CLI path `init()` still
|
|
20
|
+
* deposits via the SDK `GatewayClient` for backwards compatibility.
|
|
21
|
+
*/
|
|
22
|
+
import {} from "viem";
|
|
23
|
+
import { activeNetwork, getConfig } from "@naulon/shared";
|
|
24
|
+
import { classifyPaymentError, classifySignerRefusal, probe, } from "./buyer.js";
|
|
25
|
+
import { runPaidFetch } from "./paidFetch.js";
|
|
26
|
+
function envAccountKey() {
|
|
27
|
+
const cfg = getConfig();
|
|
28
|
+
if (!cfg.BUYER_PRIVATE_KEY) {
|
|
29
|
+
throw new Error(`PAYMENT_MODE=gateway on ${activeNetwork().chainName} requires an injected signer or a funded ` +
|
|
30
|
+
`BUYER_PRIVATE_KEY (a wallet whose USDC is deposited in the Circle Gateway Wallet).`);
|
|
31
|
+
}
|
|
32
|
+
return (cfg.BUYER_PRIVATE_KEY.startsWith("0x") ? cfg.BUYER_PRIVATE_KEY : `0x${cfg.BUYER_PRIVATE_KEY}`);
|
|
33
|
+
}
|
|
34
|
+
/** Sign one Gateway leg's EIP-3009 authorization against the GatewayWallet domain and return
|
|
35
|
+
* the full envelope `{x402Version, payload, resource, accepted}` — exactly the gate's
|
|
36
|
+
* `gatewayLegPayload` shape. Wraps the SDK's `BatchEvmScheme` so the domain / validity clamp
|
|
37
|
+
* never drifts from the rail. SDK loaded lazily so the mock path never pulls it in. */
|
|
38
|
+
export async function gatewayLegPayload(signer, quoted, x402Version) {
|
|
39
|
+
// Both pre-sign guards live HERE, not in the callers. They used to sit in gatewayBuyer only,
|
|
40
|
+
// while railBuyer (the mixed-fleet path) called this helper directly and got neither — the
|
|
41
|
+
// classic drifted-sibling shape. Taking the whole `quoted` instead of bare requirements makes
|
|
42
|
+
// them unbypassable: a caller cannot reach the signer without passing through both.
|
|
43
|
+
//
|
|
44
|
+
// 1. The Circle SDK's batched pay signs ONE leg. An N-leg (operator-fee) quote would be silently
|
|
45
|
+
// underpaid — refuse loudly and point at the memo rail (N-leg-capable).
|
|
46
|
+
if (quoted.legs && quoted.legs.length > 1) {
|
|
47
|
+
throw new Error(`gateway (Circle SDK) mode cannot pay a ${quoted.legs.length}-leg toll (operator fee): ` +
|
|
48
|
+
`the SDK signs only the author leg. Use the memo (Arc) rail for multi-leg settlement.`);
|
|
49
|
+
}
|
|
50
|
+
const requirements = quoted.requirements;
|
|
51
|
+
// 2. Refuse to sign a Circle envelope for a 402 that is not actually a Gateway batching option.
|
|
52
|
+
if (requirements.extra?.name !== "GatewayWalletBatched") {
|
|
53
|
+
throw new Error("gateway mode expects a Circle Gateway batching option (extra.name 'GatewayWalletBatched'); " +
|
|
54
|
+
"the gate advertised a non-gateway 402. Check PAYMENT_MODE / the settlement network.");
|
|
55
|
+
}
|
|
56
|
+
const { BatchEvmScheme } = await import("@circle-fin/x402-batching/client");
|
|
57
|
+
const scheme = new BatchEvmScheme(signer);
|
|
58
|
+
const signed = await scheme.createPaymentPayload(x402Version, requirements);
|
|
59
|
+
return { ...signed, resource: quoted.resource, accepted: requirements };
|
|
60
|
+
}
|
|
61
|
+
export function gatewayBuyer(signer) {
|
|
62
|
+
const cfg = getConfig();
|
|
63
|
+
// Resolve the signer once. An injected signer NEVER reads BUYER_PRIVATE_KEY (the whole point
|
|
64
|
+
// of the cloud wallet — the key lives in the grant-checked BFF, not this process).
|
|
65
|
+
let resolved = signer ?? null;
|
|
66
|
+
const getSigner = async () => {
|
|
67
|
+
if (resolved)
|
|
68
|
+
return resolved;
|
|
69
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
70
|
+
return (resolved = privateKeyToAccount(envAccountKey()));
|
|
71
|
+
};
|
|
72
|
+
const fallbackAddress = signer ? signer.address : (cfg.BUYER_ADDRESS ?? "0x");
|
|
73
|
+
return {
|
|
74
|
+
get address() {
|
|
75
|
+
return resolved?.address ?? fallbackAddress;
|
|
76
|
+
},
|
|
77
|
+
async init() {
|
|
78
|
+
// Custody-free out-of-band deposit: the Gateway balance is funded separately, so the
|
|
79
|
+
// injected-signer (cloud) path is a no-op. The env/CLI path keeps the SDK deposit for
|
|
80
|
+
// backwards compatibility — it needs the raw key, which only exists on that path.
|
|
81
|
+
if (signer)
|
|
82
|
+
return;
|
|
83
|
+
// Resolve the env-key address here so `address` is honest from init onward, matching
|
|
84
|
+
// memoBuyer (which resolves eagerly at construction). Without this the env/CLI path
|
|
85
|
+
// reported the "0x" placeholder until the first pay — agent.ts logs `wallet ${address}`
|
|
86
|
+
// BEFORE init(), so an operator saw `wallet 0x` instead of the real derived address.
|
|
87
|
+
await getSigner();
|
|
88
|
+
const { GatewayClient } = await import("@circle-fin/x402-batching/client");
|
|
89
|
+
const client = new GatewayClient({ chain: activeNetwork().chainName, privateKey: envAccountKey() });
|
|
90
|
+
console.log(` depositing ${cfg.DEPOSIT_AMOUNT_USDC} USDC into the Gateway Wallet...`);
|
|
91
|
+
const result = await client.deposit(cfg.DEPOSIT_AMOUNT_USDC);
|
|
92
|
+
console.log(` deposit tx ${result.depositTxHash}`);
|
|
93
|
+
},
|
|
94
|
+
price(url, kind) {
|
|
95
|
+
return probe(url, kind, this.address).then((o) => (o.status === "gated" ? o.quoted : null));
|
|
96
|
+
},
|
|
97
|
+
async fetch(url, kind, guard) {
|
|
98
|
+
const address = this.address;
|
|
99
|
+
// The shared loop owns probe→moved-guard→paid-GET→classify. The gateway rail supplies only
|
|
100
|
+
// how it builds the payment (the two pre-sign guards live INSIDE the builder so a bad quote
|
|
101
|
+
// throws into onSignError, keeping a typed result) and how it classifies a sign throw
|
|
102
|
+
// (grant refusal → typed needs_topup; any other → classifyPaymentError — a gateway sign path
|
|
103
|
+
// never sees a socket error, the shared loop handles that as a rail-agnostic origin_error).
|
|
104
|
+
return runPaidFetch(url, kind, address, guard, async (quoted) => {
|
|
105
|
+
// Both pre-sign guards (N-leg refusal + GatewayWalletBatched check) now live inside
|
|
106
|
+
// gatewayLegPayload, so this rail and railBuyer enforce them identically. They still
|
|
107
|
+
// throw into onSignError below, keeping a typed Fetched.
|
|
108
|
+
const payload = await gatewayLegPayload(await getSigner(), quoted, 2);
|
|
109
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
110
|
+
}, (error) => {
|
|
111
|
+
const refusal = classifySignerRefusal(error);
|
|
112
|
+
return refusal
|
|
113
|
+
? { ok: false, error, ...refusal }
|
|
114
|
+
: { ok: false, error, ...classifyPaymentError(error) };
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Out-of-band, custody-free deposit into the Circle Gateway Wallet — the non-custodial contract that
|
|
121
|
+
* holds the buyer's unified balance (Circle infra, buyer-controlled; naulon custodies nothing). The
|
|
122
|
+
* cloud (injected-signer) path funds the Gateway balance HERE, out of band, because `gatewayBuyer.init()`
|
|
123
|
+
* is a deposit NO-OP when a signer is injected (the key lives in the grant-checked BFF, not this process).
|
|
124
|
+
* The self-host/CLI path still deposits inside `init()`; this is the standalone entry a deposit
|
|
125
|
+
* script/operator calls. SDK loaded lazily so the mock/memo paths never pull it in.
|
|
126
|
+
*/
|
|
127
|
+
export async function gatewayDeposit(opts) {
|
|
128
|
+
const { GatewayClient } = await import("@circle-fin/x402-batching/client");
|
|
129
|
+
const client = new GatewayClient({ chain: opts.chain, privateKey: opts.privateKey });
|
|
130
|
+
return client.deposit(opts.amountUsdc);
|
|
131
|
+
}
|
|
132
|
+
/** Read the wallet + Gateway balances for a key — the preflight a deposit script shows before it moves
|
|
133
|
+
* funds, and the check the buyer uses to see if its unified balance covers a toll. Pass `address` to
|
|
134
|
+
* read ANOTHER account's balances (e.g. confirm the AUTHOR received a settle) — the client key only
|
|
135
|
+
* authenticates the read, it needn't own the address. SDK loaded lazily. */
|
|
136
|
+
export async function gatewayBalances(opts) {
|
|
137
|
+
const { GatewayClient } = await import("@circle-fin/x402-batching/client");
|
|
138
|
+
return new GatewayClient({ chain: opts.chain, privateKey: opts.privateKey }).getBalances(opts.address);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Look up a single Gateway transfer (a settlement) by its Circle id — the `settlementRef` a gateway
|
|
142
|
+
* settle stamps. On the Gateway rail that ref is a **Circle UUID, not an on-chain tx hash**; the
|
|
143
|
+
* response carries the authoritative `status` plus the eventual on-chain `txHash`. This is the
|
|
144
|
+
* correct "did the settle land?" check — pair `status` with `classifyGatewaySettlement`. SDK lazy.
|
|
145
|
+
*/
|
|
146
|
+
export async function gatewayTransferStatus(opts) {
|
|
147
|
+
const { GatewayClient } = await import("@circle-fin/x402-batching/client");
|
|
148
|
+
return new GatewayClient({ chain: opts.chain, privateKey: opts.privateKey }).getTransferById(opts.id);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Search Gateway transfers with optional filters (`to`/`from`/`status`/`network`/date range). Use to
|
|
152
|
+
* confirm a payee (author) received without holding the transfer id — e.g. `{ to: authorAddress }`.
|
|
153
|
+
* SDK loaded lazily.
|
|
154
|
+
*/
|
|
155
|
+
export async function gatewayTransfers(opts) {
|
|
156
|
+
const { chain, privateKey, ...params } = opts;
|
|
157
|
+
const { GatewayClient } = await import("@circle-fin/x402-batching/client");
|
|
158
|
+
return new GatewayClient({ chain, privateKey }).searchTransfers(params);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Classify a Circle Gateway transfer `status` into settled / pending / failed. This is the CODE form
|
|
162
|
+
* of the hard-won rule that a Gateway settle credits the payee's OFF-CHAIN Gateway balance — so
|
|
163
|
+
* `balanceOf(payee)` is the wrong check and the transfer's own `status` is the authoritative signal.
|
|
164
|
+
* `completed` ⇒ settled; the in-pipeline states ⇒ pending; `failed` ⇒ failed. An unknown/future status
|
|
165
|
+
* is treated as `pending` — never falsely report the money landed.
|
|
166
|
+
*/
|
|
167
|
+
export function classifyGatewaySettlement(status) {
|
|
168
|
+
switch (status) {
|
|
169
|
+
case "completed":
|
|
170
|
+
return "settled";
|
|
171
|
+
case "failed":
|
|
172
|
+
return "failed";
|
|
173
|
+
case "received":
|
|
174
|
+
case "batched":
|
|
175
|
+
case "confirmed":
|
|
176
|
+
return "pending";
|
|
177
|
+
default: {
|
|
178
|
+
// Exhaustiveness guard: if Circle adds a TransferStatus, this line fails tsc —
|
|
179
|
+
// forcing a human to classify it, NOT silently bucketing it as pending. At
|
|
180
|
+
// runtime an unknown value is treated as pending (never falsely "settled").
|
|
181
|
+
const _exhaustive = status;
|
|
182
|
+
void _exhaustive;
|
|
183
|
+
return "pending";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wayfarer CLI — an autonomous research agent that pays per citation.
|
|
3
|
+
*
|
|
4
|
+
* npm run wayfarer -- "what does the author say about payment and passage?"
|
|
5
|
+
*
|
|
6
|
+
* It discovers essays, decides which are worth paying to cite under a budget
|
|
7
|
+
* (visible reasoning, not hardcoded), pays the naulon through the tollgate, and
|
|
8
|
+
* grounds a cited answer. Requires a running tollgate (npm run tollgate).
|
|
9
|
+
*/
|
|
10
|
+
import { run } from "./lib.js";
|
|
11
|
+
const topic = process.argv.slice(2).join(" ").trim();
|
|
12
|
+
if (!topic) {
|
|
13
|
+
console.error('usage: npm run wayfarer -- "<research topic>"');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const result = await run(topic, (line) => console.log(line));
|
|
17
|
+
console.log("\n" + "─".repeat(60));
|
|
18
|
+
console.log(result.answer);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACrD,IAAI,CAAC,KAAK,EAAE,CAAC;IACX,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;IAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AAE7D,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;AACnC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC"}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @naulon/wayfarer — public library surface.
|
|
3
|
+
*
|
|
4
|
+
* The autonomous buy-side research agent: discover → quote → appraise → decide →
|
|
5
|
+
* pay → ground, with reusable Citation Licenses (pay once, re-read free). This
|
|
6
|
+
* barrel is the side-effect-free entry the package.json `exports` map points at;
|
|
7
|
+
* the CLI (`./index.ts`) is a thin consumer of it, never the other way round.
|
|
8
|
+
*
|
|
9
|
+
* Stage-name map (buy-side spec → the genuine export):
|
|
10
|
+
* quote → probePrice (free 402 probe, no spend)
|
|
11
|
+
* pay → selectBuyer + the Buyer seam (mock | memo | gateway)
|
|
12
|
+
* read → rereadWithLicense (free re-read of a held live license)
|
|
13
|
+
*/
|
|
14
|
+
export { run } from "./agent.ts";
|
|
15
|
+
export type { Logger, RunOptions } from "./agent.ts";
|
|
16
|
+
export { tollgateBase, articleUrl, fetchJwks, verifyAgainst } from "./agent.ts";
|
|
17
|
+
export { discover } from "./discover.ts";
|
|
18
|
+
export { appraise } from "./appraise.ts";
|
|
19
|
+
export { probe, probePrice, probeFailure, assemblePayment, rereadWithLicense, selectBuyer, quotedTotalAtomic, tollMovedOrNull, classifyPaymentError, } from "./buyer.ts";
|
|
20
|
+
export type { Buyer, Quoted, LegRequirements, Fetched, FetchErrorCode, PayGuard, ProbeOutcome } from "./buyer.ts";
|
|
21
|
+
export { mockBuyer } from "./pay.ts";
|
|
22
|
+
export { gatewayBuyer, gatewayDeposit, gatewayBalances, gatewayTransferStatus, gatewayTransfers, classifyGatewaySettlement, } from "./gateway.ts";
|
|
23
|
+
export type { GatewaySigner, GatewayDepositOpts, GatewaySettlementState } from "./gateway.ts";
|
|
24
|
+
export { memoBuyer, signMemoPayment } from "./memo.ts";
|
|
25
|
+
export type { MemoSigner } from "./memo.ts";
|
|
26
|
+
export { railBuyer } from "./rail.ts";
|
|
27
|
+
export type { RailSigners } from "./rail.ts";
|
|
28
|
+
export { decide, DEFAULT_POLICY, spendGate } from "./decide.ts";
|
|
29
|
+
export type { DecisionPolicy, DecideContext, SpendVerdict } from "./decide.ts";
|
|
30
|
+
export { authorizeOrigin } from "./origin-policy.ts";
|
|
31
|
+
export type { OriginRequest, OriginVerdict, PayableTarget } from "./origin-policy.ts";
|
|
32
|
+
export { allocateByContribution } from "./allocation.ts";
|
|
33
|
+
export type { SourceAllocation } from "./allocation.ts";
|
|
34
|
+
export { decodeHeld, fileHeldStore, isLive, loadHeld, memoryHeldStore, saveHeld } from "./licenseStore.ts";
|
|
35
|
+
export type { HeldLicense, HeldStore } from "./licenseStore.ts";
|
|
36
|
+
export { buildPopProof } from "./pop.ts";
|
|
37
|
+
export { getWallet } from "./wallet.ts";
|
|
38
|
+
export type { AgentWallet } from "./wallet.ts";
|
|
39
|
+
export type { Candidate, PricedCandidate, AppraisedCandidate, Action, Decision, Source, RunResult, } from "./types.ts";
|
|
40
|
+
//# sourceMappingURL=lib.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAKrD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EACL,KAAK,EACL,UAAU,EACV,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAClH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EACL,YAAY,EACZ,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC9F,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACvD,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM/E,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGtF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC3G,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,YAAY,EACV,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,MAAM,EACN,QAAQ,EACR,MAAM,EACN,SAAS,GACV,MAAM,YAAY,CAAC"}
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @naulon/wayfarer — public library surface.
|
|
3
|
+
*
|
|
4
|
+
* The autonomous buy-side research agent: discover → quote → appraise → decide →
|
|
5
|
+
* pay → ground, with reusable Citation Licenses (pay once, re-read free). This
|
|
6
|
+
* barrel is the side-effect-free entry the package.json `exports` map points at;
|
|
7
|
+
* the CLI (`./index.ts`) is a thin consumer of it, never the other way round.
|
|
8
|
+
*
|
|
9
|
+
* Stage-name map (buy-side spec → the genuine export):
|
|
10
|
+
* quote → probePrice (free 402 probe, no spend)
|
|
11
|
+
* pay → selectBuyer + the Buyer seam (mock | memo | gateway)
|
|
12
|
+
* read → rereadWithLicense (free re-read of a held live license)
|
|
13
|
+
*/
|
|
14
|
+
// ── pipeline entry ──────────────────────────────────────────────────────────
|
|
15
|
+
export { run } from "./agent.js";
|
|
16
|
+
// Pipeline primitives a second consumer (the MCP server) reuses to resolve an
|
|
17
|
+
// article URL from a slug against the configured gate, and to verify a captured
|
|
18
|
+
// license against the gate's JWKS — single-sourced here so the two can't drift.
|
|
19
|
+
export { tollgateBase, articleUrl, fetchJwks, verifyAgainst } from "./agent.js";
|
|
20
|
+
// ── discover ────────────────────────────────────────────────────────────────
|
|
21
|
+
export { discover } from "./discover.js";
|
|
22
|
+
// ── appraise ────────────────────────────────────────────────────────────────
|
|
23
|
+
export { appraise } from "./appraise.js";
|
|
24
|
+
// ── quote + pay (the Buyer seam) ────────────────────────────────────────────
|
|
25
|
+
export { probe, probePrice, probeFailure, assemblePayment, rereadWithLicense, selectBuyer, quotedTotalAtomic, tollMovedOrNull, classifyPaymentError, } from "./buyer.js";
|
|
26
|
+
export { mockBuyer } from "./pay.js";
|
|
27
|
+
export { gatewayBuyer, gatewayDeposit, gatewayBalances, gatewayTransferStatus, gatewayTransfers, classifyGatewaySettlement, } from "./gateway.js";
|
|
28
|
+
export { memoBuyer, signMemoPayment } from "./memo.js";
|
|
29
|
+
export { railBuyer } from "./rail.js";
|
|
30
|
+
// ── decide (policy) ─────────────────────────────────────────────────────────
|
|
31
|
+
export { decide, DEFAULT_POLICY, spendGate } from "./decide.js";
|
|
32
|
+
// ── origin policy (whose origin may money touch) ─────────────────────────────
|
|
33
|
+
// The one answer to that question; `spendGate` above stays the one answer to "how
|
|
34
|
+
// much". `PayableTarget` is mintable only by `authorizeOrigin`, so a pay path that
|
|
35
|
+
// skips the check fails to typecheck rather than failing in production.
|
|
36
|
+
export { authorizeOrigin } from "./origin-policy.js";
|
|
37
|
+
// ── cross-source allocation (buyer-side citation-reward policy) ──────────────
|
|
38
|
+
export { allocateByContribution } from "./allocation.js";
|
|
39
|
+
// ── citation licenses (pay once, re-read free) ──────────────────────────────
|
|
40
|
+
export { decodeHeld, fileHeldStore, isLive, loadHeld, memoryHeldStore, saveHeld } from "./licenseStore.js";
|
|
41
|
+
export { buildPopProof } from "./pop.js";
|
|
42
|
+
// ── wallet ──────────────────────────────────────────────────────────────────
|
|
43
|
+
export { getWallet } from "./wallet.js";
|
|
44
|
+
//# sourceMappingURL=lib.js.map
|
package/dist/lib.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,+EAA+E;AAC/E,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAGjC,8EAA8E;AAC9E,gFAAgF;AAChF,gFAAgF;AAChF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhF,+EAA+E;AAC/E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,+EAA+E;AAC/E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,+EAA+E;AAC/E,OAAO,EACL,KAAK,EACL,UAAU,EACV,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EACL,YAAY,EACZ,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,+EAA+E;AAC/E,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGhE,gFAAgF;AAChF,kFAAkF;AAClF,mFAAmF;AACnF,wEAAwE;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,gFAAgF;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGzD,+EAA+E;AAC/E,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE3G,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,+EAA+E;AAC/E,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface HeldLicense {
|
|
2
|
+
slug: string;
|
|
3
|
+
title: string;
|
|
4
|
+
jti: string;
|
|
5
|
+
/** exp (epoch seconds) decoded from the token, for the liveness check. */
|
|
6
|
+
exp: number;
|
|
7
|
+
/** The token's audience (= gate identity); the value a PoP proof must bind to. */
|
|
8
|
+
aud: string;
|
|
9
|
+
/** Holder-of-key: true when the license carries a `cnf` claim — a re-read needs
|
|
10
|
+
* a wallet proof-of-possession, not just the token. */
|
|
11
|
+
pop: boolean;
|
|
12
|
+
/** The compact-JWS token to present on a re-read. */
|
|
13
|
+
jws: string;
|
|
14
|
+
/** The canonical URL this source was actually PAID at — captured at pay time so a
|
|
15
|
+
* later `read_held` re-fetches the exact link (`/articles/<slug>`, a custom domain,
|
|
16
|
+
* whatever the publisher serves) instead of reconstructing a `/essays/<slug>`
|
|
17
|
+
* template that 404s off-shape. Optional: a license held from before this field
|
|
18
|
+
* existed has none, and the re-read falls back to the template. NOT part of the
|
|
19
|
+
* decoded token — it's the buyer's own bookkeeping, so `decodeHeld` never sets it. */
|
|
20
|
+
url?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Decode a token's `jti`, `exp`, and `naulon.slug/title` WITHOUT verifying — this
|
|
24
|
+
* is the agent reading its own captured receipt for bookkeeping, not trusting a
|
|
25
|
+
* third party. (The agent separately verifies the signature against the gate's
|
|
26
|
+
* JWKS when it captures the license; see agent.ts.) Returns null if unparseable.
|
|
27
|
+
*/
|
|
28
|
+
export declare function decodeHeld(jws: string): Omit<HeldLicense, "jws"> | null;
|
|
29
|
+
/** Is this held license still valid at `nowSec` (epoch seconds)? */
|
|
30
|
+
export declare function isLive(held: HeldLicense, nowSec: number): boolean;
|
|
31
|
+
export declare function loadHeld(): Promise<Map<string, HeldLicense>>;
|
|
32
|
+
export declare function saveHeld(held: Map<string, HeldLicense>): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* The held-license backend as a seam. The stdio funnel uses the process-global
|
|
35
|
+
* file (`fileHeldStore`); the hosted path (many buyer sessions in one process)
|
|
36
|
+
* MUST inject a store scoped to the caller — else session B could re-read the
|
|
37
|
+
* license session A paid for, since the file store is keyed by slug alone and
|
|
38
|
+
* shared across every session in the process (a cross-buyer isolation leak).
|
|
39
|
+
* The interface is deliberately the shape `loadHeld`/`saveHeld` already have, so
|
|
40
|
+
* the file default and an injected store are interchangeable at every call site.
|
|
41
|
+
*/
|
|
42
|
+
export interface HeldStore {
|
|
43
|
+
load(): Promise<Map<string, HeldLicense>>;
|
|
44
|
+
save(held: Map<string, HeldLicense>): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
/** The OSS default: the process-global JSON file at `WAYFARER_LICENSE_PATH`. */
|
|
47
|
+
export declare const fileHeldStore: HeldStore;
|
|
48
|
+
/**
|
|
49
|
+
* An in-process, isolated held store — the hosted-path default. Each instance
|
|
50
|
+
* owns a private Map; two instances never share state, so building one per MCP
|
|
51
|
+
* session gives per-session isolation for free (session B's `load()` cannot see
|
|
52
|
+
* what session A `save()`d). Ephemeral by design: a held license lives with the
|
|
53
|
+
* session, not across process restarts — the right trade for a capped hot session.
|
|
54
|
+
*/
|
|
55
|
+
export declare function memoryHeldStore(seed?: Iterable<readonly [string, HeldLicense]>): HeldStore;
|
|
56
|
+
//# sourceMappingURL=licenseStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"licenseStore.d.ts","sourceRoot":"","sources":["../src/licenseStore.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ;2DACuD;IACvD,GAAG,EAAE,OAAO,CAAC;IACb,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;0FAKsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAID;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,GAAG,IAAI,CAuBvE;AAED,oEAAoE;AACpE,wBAAgB,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,wBAAsB,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CASlE;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5E;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED,gFAAgF;AAChF,eAAO,MAAM,aAAa,EAAE,SAA8C,CAAC;AAE3E;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,GAAG,SAAS,CAS1F"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent's wallet of Citation Licenses. When the gate hands back a license on
|
|
3
|
+
* a paid read, the agent keeps it; a live (unexpired) one lets it re-read that
|
|
4
|
+
* essay free on a later run instead of paying again — the buyer-side half of the
|
|
5
|
+
* "toll becomes an asset" thesis. Persisted to a small JSON file (WAYFARER_LICENSE_PATH).
|
|
6
|
+
*/
|
|
7
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
8
|
+
import { dirname } from "node:path";
|
|
9
|
+
import { getConfig } from "@naulon/shared";
|
|
10
|
+
const file = () => getConfig().WAYFARER_LICENSE_PATH;
|
|
11
|
+
/**
|
|
12
|
+
* Decode a token's `jti`, `exp`, and `naulon.slug/title` WITHOUT verifying — this
|
|
13
|
+
* is the agent reading its own captured receipt for bookkeeping, not trusting a
|
|
14
|
+
* third party. (The agent separately verifies the signature against the gate's
|
|
15
|
+
* JWKS when it captures the license; see agent.ts.) Returns null if unparseable.
|
|
16
|
+
*/
|
|
17
|
+
export function decodeHeld(jws) {
|
|
18
|
+
try {
|
|
19
|
+
const seg = jws.split(".")[1];
|
|
20
|
+
if (!seg)
|
|
21
|
+
return null;
|
|
22
|
+
const claims = JSON.parse(Buffer.from(seg, "base64url").toString("utf8"));
|
|
23
|
+
if (!claims.jti || !claims.exp || !claims.aud || !claims.naulon?.slug)
|
|
24
|
+
return null;
|
|
25
|
+
return {
|
|
26
|
+
slug: claims.naulon.slug,
|
|
27
|
+
title: claims.naulon.title ?? claims.naulon.slug,
|
|
28
|
+
jti: claims.jti,
|
|
29
|
+
exp: claims.exp,
|
|
30
|
+
aud: claims.aud,
|
|
31
|
+
pop: typeof claims.cnf?.["naulon:addr"] === "string",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Is this held license still valid at `nowSec` (epoch seconds)? */
|
|
39
|
+
export function isLive(held, nowSec) {
|
|
40
|
+
return held.exp > nowSec;
|
|
41
|
+
}
|
|
42
|
+
export async function loadHeld() {
|
|
43
|
+
try {
|
|
44
|
+
const raw = await readFile(file(), "utf8");
|
|
45
|
+
const arr = JSON.parse(raw);
|
|
46
|
+
return new Map(arr.map((h) => [h.slug, h]));
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (err.code === "ENOENT")
|
|
50
|
+
return new Map();
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export async function saveHeld(held) {
|
|
55
|
+
const path = file();
|
|
56
|
+
await mkdir(dirname(path), { recursive: true });
|
|
57
|
+
await writeFile(path, JSON.stringify([...held.values()], null, 2), "utf8");
|
|
58
|
+
}
|
|
59
|
+
/** The OSS default: the process-global JSON file at `WAYFARER_LICENSE_PATH`. */
|
|
60
|
+
export const fileHeldStore = { load: loadHeld, save: saveHeld };
|
|
61
|
+
/**
|
|
62
|
+
* An in-process, isolated held store — the hosted-path default. Each instance
|
|
63
|
+
* owns a private Map; two instances never share state, so building one per MCP
|
|
64
|
+
* session gives per-session isolation for free (session B's `load()` cannot see
|
|
65
|
+
* what session A `save()`d). Ephemeral by design: a held license lives with the
|
|
66
|
+
* session, not across process restarts — the right trade for a capped hot session.
|
|
67
|
+
*/
|
|
68
|
+
export function memoryHeldStore(seed) {
|
|
69
|
+
const store = new Map(seed);
|
|
70
|
+
return {
|
|
71
|
+
load: async () => new Map(store),
|
|
72
|
+
save: async (held) => {
|
|
73
|
+
store.clear();
|
|
74
|
+
for (const [k, v] of held)
|
|
75
|
+
store.set(k, v);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=licenseStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"licenseStore.js","sourceRoot":"","sources":["../src/licenseStore.ts"],"names":[],"mappings":"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"}
|
package/dist/memo.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type TypedDataDomain } from "viem";
|
|
2
|
+
import { TRANSFER_WITH_AUTHORIZATION_TYPES, type MemoAuthorization } from "@naulon/shared";
|
|
3
|
+
import { type Buyer, type LegRequirements, type Quoted } from "./buyer.ts";
|
|
4
|
+
/**
|
|
5
|
+
* The signer seam (BUY-2). By default the memo buyer signs each EIP-3009 leg with the local
|
|
6
|
+
* `BUYER_PRIVATE_KEY` (the OSS self-host path). A cloud host instead injects a `MemoSigner` — an
|
|
7
|
+
* object that signs the SAME typed data elsewhere (a grant-checked BFF holding an encrypted session
|
|
8
|
+
* key), so the private key never lives in the MCP process. A `PrivateKeyAccount` from viem satisfies
|
|
9
|
+
* this shape too, which keeps the two paths symmetric. The env key is only ever read on the default
|
|
10
|
+
* path — when a signer is injected, `BUYER_PRIVATE_KEY` is never touched.
|
|
11
|
+
*/
|
|
12
|
+
export interface MemoSigner {
|
|
13
|
+
address: `0x${string}`;
|
|
14
|
+
signTypedData(args: {
|
|
15
|
+
domain: TypedDataDomain;
|
|
16
|
+
types: typeof TRANSFER_WITH_AUTHORIZATION_TYPES;
|
|
17
|
+
primaryType: "TransferWithAuthorization";
|
|
18
|
+
message: {
|
|
19
|
+
from: `0x${string}`;
|
|
20
|
+
to: `0x${string}`;
|
|
21
|
+
value: bigint;
|
|
22
|
+
validAfter: bigint;
|
|
23
|
+
validBefore: bigint;
|
|
24
|
+
nonce: `0x${string}`;
|
|
25
|
+
};
|
|
26
|
+
}): Promise<`0x${string}`>;
|
|
27
|
+
}
|
|
28
|
+
/** Sign a raw USDC EIP-3009 `TransferWithAuthorization` against the active network's USDC
|
|
29
|
+
* EIP-712 domain and return the base64 `{authorization, signature}` payload the gate's
|
|
30
|
+
* memo settle path parses. The memo id is NOT signed (the gate attaches it at relay), so
|
|
31
|
+
* the buyer needs nothing extra. Exported for unit testing against the gate's pre-verify. */
|
|
32
|
+
export declare function signMemoPayment(requirements: Quoted["requirements"], nowMs: number): Promise<string>;
|
|
33
|
+
/** The raw `{ authorization, signature }` for ONE leg — signs the leg's payTo + amount
|
|
34
|
+
* against the active network's USDC domain, with its own fresh EIP-3009 nonce. The
|
|
35
|
+
* per-leg primitive `assemblePayment` calls once per advertised leg (mirrors the gate's
|
|
36
|
+
* `memoLegPayload` → `buildMemoSignatures`). The memo id is NOT signed (the relayer
|
|
37
|
+
* attaches it at submit), so each leg needs nothing beyond its own authorization. */
|
|
38
|
+
export declare function memoLegPayload(requirements: LegRequirements, nowMs: number, signer?: MemoSigner, net?: import("@naulon/shared").SettlementNetwork): Promise<{
|
|
39
|
+
authorization: MemoAuthorization;
|
|
40
|
+
signature: `0x${string}`;
|
|
41
|
+
}>;
|
|
42
|
+
export declare function memoBuyer(signer?: MemoSigner): Buyer;
|
|
43
|
+
//# sourceMappingURL=memo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memo.d.ts","sourceRoot":"","sources":["../src/memo.ts"],"names":[],"mappings":"AAeA,OAAO,EAAS,KAAK,eAAe,EAAE,MAAM,MAAM,CAAC;AACnD,OAAO,EAIL,iCAAiC,EACjC,KAAK,iBAAiB,EACvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAIL,KAAK,KAAK,EAEV,KAAK,eAAe,EAEpB,KAAK,MAAM,EACZ,MAAM,YAAY,CAAC;AAGpB;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;IACvB,aAAa,CAAC,IAAI,EAAE;QAClB,MAAM,EAAE,eAAe,CAAC;QACxB,KAAK,EAAE,OAAO,iCAAiC,CAAC;QAChD,WAAW,EAAE,2BAA2B,CAAC;QACzC,OAAO,EAAE;YACP,IAAI,EAAE,KAAK,MAAM,EAAE,CAAC;YACpB,EAAE,EAAE,KAAK,MAAM,EAAE,CAAC;YAClB,KAAK,EAAE,MAAM,CAAC;YACd,UAAU,EAAE,MAAM,CAAC;YACnB,WAAW,EAAE,MAAM,CAAC;YACpB,KAAK,EAAE,KAAK,MAAM,EAAE,CAAC;SACtB,CAAC;KACH,GAAG,OAAO,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC;CAC5B;AAaD;;;8FAG8F;AAC9F,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,CAAC,cAAc,CAAC,EACpC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;;;sFAIsF;AACtF,wBAAsB,cAAc,CAClC,YAAY,EAAE,eAAe,EAC7B,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,UAAU,EACnB,GAAG,6CAAkB,GACpB,OAAO,CAAC;IAAE,aAAa,EAAE,iBAAiB,CAAC;IAAC,SAAS,EAAE,KAAK,MAAM,EAAE,CAAA;CAAE,CAAC,CAoCzE;AAED,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,KAAK,CA+BpD"}
|
package/dist/memo.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memo buyer — the client side of the Arc self-relay rail. On a memo-capable network the
|
|
3
|
+
* gate settles by RELAYING a raw USDC EIP-3009 authorization through the Arc Memo
|
|
4
|
+
* predeploy (not Circle Gateway), so the buyer signs the authorization against the USDC
|
|
5
|
+
* EIP-712 domain — the shared descriptor — and posts `{authorization, signature}` as the
|
|
6
|
+
* `payment-signature`. There is NO Gateway deposit: the transfer moves straight from the
|
|
7
|
+
* buyer's USDC balance and the gate's relayer only pays gas (custody-free).
|
|
8
|
+
*
|
|
9
|
+
* Counterpart to gateway.ts (Circle SDK, GatewayWallet domain) and pay.ts (mock offline).
|
|
10
|
+
* The signing here is wayfarer's own viem call over shared's `TRANSFER_WITH_AUTHORIZATION`
|
|
11
|
+
* descriptor + `usdcDomain` — the same "each consumer signs against the one shared
|
|
12
|
+
* descriptor" split the gate's own `buildMemoSignature` uses (the gate then verifies
|
|
13
|
+
* against that identical descriptor). `selectBuyer` routes here when `supportsMemo`.
|
|
14
|
+
*/
|
|
15
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
16
|
+
import { toHex } from "viem";
|
|
17
|
+
import { activeNetwork, getConfig, usdcDomain, TRANSFER_WITH_AUTHORIZATION_TYPES, } from "@naulon/shared";
|
|
18
|
+
import { assemblePayment, classifySignerRefusal, probePrice, } from "./buyer.js";
|
|
19
|
+
import { runPaidFetch } from "./paidFetch.js";
|
|
20
|
+
function buyerKey() {
|
|
21
|
+
const cfg = getConfig();
|
|
22
|
+
if (!cfg.BUYER_PRIVATE_KEY) {
|
|
23
|
+
throw new Error(`PAYMENT_MODE=gateway on ${activeNetwork().chainName} (memo rail) requires BUYER_PRIVATE_KEY ` +
|
|
24
|
+
`— a wallet holding USDC on that network (the EIP-3009 transfer moves straight from it).`);
|
|
25
|
+
}
|
|
26
|
+
return (cfg.BUYER_PRIVATE_KEY.startsWith("0x") ? cfg.BUYER_PRIVATE_KEY : `0x${cfg.BUYER_PRIVATE_KEY}`);
|
|
27
|
+
}
|
|
28
|
+
/** Sign a raw USDC EIP-3009 `TransferWithAuthorization` against the active network's USDC
|
|
29
|
+
* EIP-712 domain and return the base64 `{authorization, signature}` payload the gate's
|
|
30
|
+
* memo settle path parses. The memo id is NOT signed (the gate attaches it at relay), so
|
|
31
|
+
* the buyer needs nothing extra. Exported for unit testing against the gate's pre-verify. */
|
|
32
|
+
export async function signMemoPayment(requirements, nowMs) {
|
|
33
|
+
return Buffer.from(JSON.stringify(await memoLegPayload(requirements, nowMs))).toString("base64");
|
|
34
|
+
}
|
|
35
|
+
/** The raw `{ authorization, signature }` for ONE leg — signs the leg's payTo + amount
|
|
36
|
+
* against the active network's USDC domain, with its own fresh EIP-3009 nonce. The
|
|
37
|
+
* per-leg primitive `assemblePayment` calls once per advertised leg (mirrors the gate's
|
|
38
|
+
* `memoLegPayload` → `buildMemoSignatures`). The memo id is NOT signed (the relayer
|
|
39
|
+
* attaches it at submit), so each leg needs nothing beyond its own authorization. */
|
|
40
|
+
export async function memoLegPayload(requirements, nowMs, signer, net = activeNetwork()) {
|
|
41
|
+
const cfg = getConfig();
|
|
42
|
+
// Default path resolves the local key; an injected signer NEVER reads BUYER_PRIVATE_KEY (the whole
|
|
43
|
+
// point of the cloud wallet — the key lives in the grant-checked BFF, not this process).
|
|
44
|
+
const envAccount = signer ? undefined : privateKeyToAccount(buyerKey());
|
|
45
|
+
const from = (signer?.address ?? envAccount.address);
|
|
46
|
+
// Stamp validity at PAY time (nowMs is `Date.now()` from fetch, not quote time) with a
|
|
47
|
+
// MARGIN: floor the window to WAYFARER_MIN_VALIDITY_SECONDS so a gate advertising a
|
|
48
|
+
// too-short maxTimeoutSeconds can't make the authorization expire before the relay
|
|
49
|
+
// submits it (the facilitator's `authorization_validity_too_short` — the Keryx ~1-day
|
|
50
|
+
// lesson; see memory `x402-validity-window-floor`). The window only widens, never
|
|
51
|
+
// shrinks — a long gate window is honored as-is.
|
|
52
|
+
const window = Math.max(requirements.maxTimeoutSeconds, cfg.WAYFARER_MIN_VALIDITY_SECONDS);
|
|
53
|
+
const authorization = {
|
|
54
|
+
from,
|
|
55
|
+
to: requirements.payTo,
|
|
56
|
+
value: requirements.amount,
|
|
57
|
+
validAfter: "0",
|
|
58
|
+
validBefore: String(Math.floor(nowMs / 1000) + window),
|
|
59
|
+
nonce: toHex(crypto.getRandomValues(new Uint8Array(32))),
|
|
60
|
+
};
|
|
61
|
+
const typedData = {
|
|
62
|
+
domain: usdcDomain(net, cfg.USDC_EIP712_NAME),
|
|
63
|
+
types: TRANSFER_WITH_AUTHORIZATION_TYPES,
|
|
64
|
+
primaryType: "TransferWithAuthorization",
|
|
65
|
+
message: {
|
|
66
|
+
from: authorization.from,
|
|
67
|
+
to: authorization.to,
|
|
68
|
+
value: BigInt(authorization.value),
|
|
69
|
+
validAfter: BigInt(authorization.validAfter),
|
|
70
|
+
validBefore: BigInt(authorization.validBefore),
|
|
71
|
+
nonce: authorization.nonce,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const signature = signer ? await signer.signTypedData(typedData) : await envAccount.signTypedData(typedData);
|
|
75
|
+
return { authorization, signature };
|
|
76
|
+
}
|
|
77
|
+
export function memoBuyer(signer) {
|
|
78
|
+
const address = signer ? signer.address : privateKeyToAccount(buyerKey()).address;
|
|
79
|
+
return {
|
|
80
|
+
address,
|
|
81
|
+
async init() {
|
|
82
|
+
// No Gateway deposit — the EIP-3009 transfer pays straight from the buyer's USDC
|
|
83
|
+
// balance, and the relayer (gate-side) covers gas. `make arc-preflight` checks funding.
|
|
84
|
+
},
|
|
85
|
+
price(url, kind) {
|
|
86
|
+
return probePrice(url, kind, address);
|
|
87
|
+
},
|
|
88
|
+
async fetch(url, kind, guard) {
|
|
89
|
+
// One raw EIP-3009 authorization per advertised leg (operator fee → 2-leg array); a stock
|
|
90
|
+
// single-author quote stays the bare object, byte-identical to before. The shared loop owns
|
|
91
|
+
// probe→moved-guard→paid-GET→classify; the memo rail supplies only how it signs and how it
|
|
92
|
+
// types a sign refusal (grant exhausted/expired/no session → typed; else a transient origin throw).
|
|
93
|
+
return runPaidFetch(url, kind, address, guard, (quoted, nowMs) => assemblePayment(quoted, (req) => memoLegPayload(req, nowMs, signer)), (error) => {
|
|
94
|
+
const refusal = classifySignerRefusal(error);
|
|
95
|
+
return refusal
|
|
96
|
+
? { ok: false, error, ...refusal }
|
|
97
|
+
: { ok: false, error, errorCode: "origin_error", retryable: true };
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=memo.js.map
|